summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--AconfigFlags.bp5
-rw-r--r--apex/jobscheduler/service/aconfig/alarm.aconfig10
-rw-r--r--apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java24
-rw-r--r--api/StubLibraries.bp6
-rw-r--r--core/api/current.txt18
-rw-r--r--core/api/system-current.txt8
-rw-r--r--core/api/test-current.txt8
-rw-r--r--core/java/android/app/ActivityTaskManager.java14
-rw-r--r--core/java/android/app/IActivityTaskManager.aidl6
-rw-r--r--core/java/android/app/INotificationManager.aidl1
-rw-r--r--core/java/android/app/Notification.java13
-rw-r--r--core/java/android/app/NotificationManager.java16
-rw-r--r--core/java/android/app/StatusBarManager.java57
-rw-r--r--core/java/android/app/UiAutomation.java149
-rw-r--r--core/java/android/app/notification.aconfig21
-rw-r--r--core/java/android/app/supervision/ISupervisionManager.aidl1
-rw-r--r--core/java/android/app/supervision/SupervisionManager.java17
-rw-r--r--core/java/android/app/supervision/flags.aconfig16
-rw-r--r--core/java/android/appwidget/AppWidgetHostView.java4
-rw-r--r--core/java/android/companion/virtual/flags/flags.aconfig7
-rw-r--r--core/java/android/content/ContentResolver.java4
-rw-r--r--core/java/android/content/Context.java4
-rw-r--r--core/java/android/content/pm/UserInfo.java6
-rw-r--r--core/java/android/credentials/flags.aconfig1
-rw-r--r--core/java/android/hardware/biometrics/ParentalControlsUtilsInternal.java38
-rw-r--r--core/java/android/hardware/camera2/CameraCharacteristics.java24
-rw-r--r--core/java/android/hardware/camera2/params/SharedSessionConfiguration.java2
-rw-r--r--core/java/android/hardware/input/input_framework.aconfig10
-rw-r--r--core/java/android/hardware/usb/OWNERS6
-rw-r--r--core/java/android/inputmethodservice/NavigationBarController.java22
-rw-r--r--core/java/android/inputmethodservice/navigationbar/NavigationBarView.java47
-rw-r--r--core/java/android/os/BaseBundle.java11
-rw-r--r--core/java/android/os/BatteryUsageStats.java11
-rw-r--r--core/java/android/os/BatteryUsageStatsQuery.java25
-rw-r--r--core/java/android/os/Binder.java16
-rw-r--r--core/java/android/os/Bundle.java21
-rw-r--r--core/java/android/os/CombinedMessageQueue/MessageQueue.java93
-rw-r--r--core/java/android/os/ConcurrentMessageQueue/MessageQueue.java14
-rw-r--r--core/java/android/os/LegacyMessageQueue/MessageQueue.java36
-rw-r--r--core/java/android/os/Parcel.java58
-rw-r--r--core/java/android/os/UidBatteryConsumer.java2
-rw-r--r--core/java/android/os/UserManager.java14
-rw-r--r--core/java/android/provider/Settings.java25
-rw-r--r--core/java/android/service/notification/Adjustment.java9
-rw-r--r--core/java/android/service/notification/NotificationListenerService.java24
-rw-r--r--core/java/android/service/notification/StatusBarNotification.java7
-rw-r--r--core/java/android/text/StaticLayout.java1
-rw-r--r--core/java/android/text/style/TtsSpan.java22
-rw-r--r--core/java/android/view/InsetsSourceControl.java2
-rw-r--r--core/java/android/widget/RemoteViews.java11
-rw-r--r--core/java/android/window/DesktopExperienceFlags.java124
-rw-r--r--core/java/android/window/DesktopModeFlags.java117
-rw-r--r--core/java/android/window/OWNERS1
-rw-r--r--core/java/android/window/WindowContainerTransaction.java725
-rw-r--r--core/java/android/window/flags/device_state_auto_rotate_setting.aconfig22
-rw-r--r--core/java/android/window/flags/lse_desktop_experience.aconfig39
-rw-r--r--core/java/android/window/flags/responsible_apis.aconfig7
-rw-r--r--core/java/com/android/internal/os/BatteryStatsHistory.java51
-rw-r--r--core/java/com/android/internal/protolog/ProcessedPerfettoProtoLogImpl.java33
-rw-r--r--core/java/com/android/internal/protolog/ProtoLogViewerConfigReader.java30
-rw-r--r--core/java/com/android/internal/statusbar/IStatusBar.aidl8
-rw-r--r--core/java/com/android/internal/statusbar/IStatusBarService.aidl8
-rw-r--r--core/java/com/android/internal/widget/ConversationLayout.java29
-rw-r--r--core/java/com/android/internal/widget/MessagingData.java13
-rw-r--r--core/java/com/android/internal/widget/MessagingMessage.java2
-rw-r--r--core/java/com/android/internal/widget/NotificationProgressDrawable.java7
-rw-r--r--core/java/com/android/internal/widget/remotecompose/accessibility/CoreDocumentAccessibility.java7
-rw-r--r--core/java/com/android/internal/widget/remotecompose/accessibility/PlatformRemoteComposeAccessibilityRegistrar.java4
-rw-r--r--core/java/com/android/internal/widget/remotecompose/accessibility/PlatformRemoteComposeTouchHelper.java4
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/CoreDocument.java10
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/Operations.java16
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/RemoteComposeBuffer.java116
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/RemoteContextAware.java (renamed from packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromNotificationsShadeToQuickSettingsShadeTransition.kt)26
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/BitmapFontData.java219
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/ColorExpression.java244
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase2.java4
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmapFontText.java232
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/FloatFunctionCall.java185
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/FloatFunctionDefine.java168
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/ParticlesCreate.java2
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/ParticlesLoop.java10
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/Utils.java17
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/Component.java36
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/LayoutComponent.java24
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/TouchCancelModifierOperation.java10
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/TouchDownModifierOperation.java16
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/TouchUpModifierOperation.java16
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/animation/AnimateMeasure.java24
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/animation/AnimationSpec.java72
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/animation/ParticleAnimation.java9
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/BoxLayout.java34
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/CanvasLayout.java31
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/ColumnLayout.java34
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/RowLayout.java34
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/StateLayout.java38
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/TextLayout.java27
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/measure/ComponentMeasure.java11
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/measure/MeasurePass.java25
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/BackgroundModifierOperation.java16
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/BorderModifierOperation.java16
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ClipRectModifierOperation.java5
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ComponentModifiers.java51
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ComponentVisibilityOperation.java11
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/DimensionInModifierOperation.java109
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/DimensionModifierOperation.java10
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/GraphicsLayerModifierOperation.java31
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HeightInModifierOperation.java53
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HeightModifierOperation.java7
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HostActionOperation.java15
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HostNamedActionOperation.java13
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/MarqueeModifierOperation.java38
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ModifierOperation.java6
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/OffsetModifierOperation.java24
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/PaddingModifierOperation.java9
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/RippleModifierOperation.java21
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ScrollModifierOperation.java38
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueFloatChangeActionOperation.java23
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueFloatExpressionChangeActionOperation.java12
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueIntegerChangeActionOperation.java12
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueIntegerExpressionChangeActionOperation.java12
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueStringChangeActionOperation.java12
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/WidthInModifierOperation.java53
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/WidthModifierOperation.java7
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ZIndexModifierOperation.java23
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/paint/PaintBundle.java21
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/paint/TextPaint.java199
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/utilities/CollectionsAccess.java8
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/utilities/DataMap.java18
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/utilities/ImageScaling.java20
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/utilities/IntMap.java25
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/utilities/NanMap.java24
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/semantics/AccessibleComponent.java6
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/serialize/MapSerializer.java122
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/serialize/Serializable.java27
-rw-r--r--core/java/com/android/internal/widget/remotecompose/player/RemoteComposePlayer.java8
-rw-r--r--core/java/com/android/internal/widget/remotecompose/player/platform/AndroidRemoteContext.java2
-rw-r--r--core/java/com/android/internal/widget/remotecompose/player/platform/RemoteComposeCanvas.java6
-rw-r--r--core/jni/android_util_Binder.cpp34
-rw-r--r--core/res/res/values/config.xml4
-rw-r--r--core/res/res/values/public-final.xml82
-rw-r--r--core/res/res/values/public-staging.xml77
-rw-r--r--core/res/res/values/symbols.xml1
-rw-r--r--core/tests/FileSystemUtilsTest/Android.bp38
-rw-r--r--core/tests/FileSystemUtilsTest/AndroidTest.xml1
-rw-r--r--core/tests/FileSystemUtilsTest/app_with_4kb_elf/app_with_4kb_elf.xml1
-rw-r--r--core/tests/FileSystemUtilsTest/app_with_4kb_elf/app_with_4kb_no_override.xml37
-rw-r--r--core/tests/FileSystemUtilsTest/app_with_4kb_elf/page_size_compat_disabled.xml36
-rw-r--r--core/tests/FileSystemUtilsTest/app_with_4kb_elf/res/layout/hello.xml28
-rw-r--r--core/tests/FileSystemUtilsTest/app_with_4kb_elf/src/android/test/pagesizecompat/MainActivity.java3
-rw-r--r--core/tests/FileSystemUtilsTest/app_with_4kb_elf/src/android/test/pagesizecompat/PageSizeCompatTest.java37
-rw-r--r--core/tests/FileSystemUtilsTest/src/com/android/internal/content/FileSystemUtilsTest.java54
-rw-r--r--core/tests/coretests/src/android/app/NotificationManagerTest.java44
-rw-r--r--core/tests/coretests/src/android/service/notification/NotificationRankingUpdateTest.java20
-rw-r--r--core/tests/coretests/src/android/window/DesktopExperienceFlagsTest.java164
-rw-r--r--core/tests/coretests/src/android/window/DesktopModeFlagsTest.java284
-rw-r--r--data/etc/com.android.systemui.xml1
-rw-r--r--graphics/java/android/graphics/Paint.java8
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java14
-rw-r--r--libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleControllerBubbleBarTest.kt4
-rw-r--r--libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedTaskInfo.java2
-rw-r--r--libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/TransitionUtil.java40
-rw-r--r--libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeCompatPolicy.kt60
-rw-r--r--libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java9
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java75
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java157
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java12
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java12
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java6
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java9
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTransitions.java518
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java13
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubblesListener.aidl12
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java54
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java93
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/MultiInstanceHelper.kt90
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/UserProfileContexts.kt83
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/compatui/AppCompatUtils.kt47
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java19
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java78
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java12
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandler.kt87
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt6
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandler.kt5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt87
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeShellCommandHandler.kt4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt112
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt77
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopUserRepositories.kt10
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl8
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/compatui/SystemModalsTransitionHandler.kt14
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/AppHandleEducationController.kt349
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/AppHandleEducationFilter.kt33
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/data/AppHandleEducationDatastoreRepository.kt34
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/DesksOrganizer.kt51
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/OnDeskRemovedListener.kt22
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/RootTaskDesksOrganizer.kt182
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/persistence/DesktopRepositoryInitializerImpl.kt13
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java6
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java7
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PhonePipMenuController.java10
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java18
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipDismissTargetHandler.java34
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipInputConsumer.java16
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipResizeGestureHandler.java8
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java13
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java22
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java22
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java16
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java50
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskView.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java46
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java17
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedTransition.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/MixedTransitionHelper.java5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/RecentsMixedTransition.java13
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java40
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CarWindowDecorViewModel.java38
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CarWindowDecoration.java28
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java18
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java7
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MoveToDesktopAnimator.kt4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/WindowDecorTaskResourceLoader.kt37
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDecorViewModel.kt9
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecoration.kt105
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHeaderViewHolder.kt10
-rw-r--r--libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/OpenAppFromAllAppsLandscape.kt44
-rw-r--r--libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/OpenAppFromAllAppsPortrait.kt44
-rw-r--r--libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/OpenAppFromTaskbarLandscape.kt44
-rw-r--r--libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/OpenAppFromTaskbarPortrait.kt44
-rw-r--r--libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MaximizeAppWindow.kt3
-rw-r--r--libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MinimizeAppWindows.kt3
-rw-r--r--libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/OpenAppFromAllApps.kt10
-rw-r--r--libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/SnapResizeAppWindowWithKeyboardShortcuts.kt3
-rw-r--r--libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/CopyContentInSplit.kt2
-rw-r--r--libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/DismissSplitScreenByDivider.kt2
-rw-r--r--libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/DismissSplitScreenByGoHome.kt2
-rw-r--r--libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/DragDividerToResize.kt2
-rw-r--r--libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/FromSplitScreenAutoEnterPipOnGoToHomeTest.kt3
-rw-r--r--libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/FromSplitScreenEnterPipOnUserLeaveHintTest.kt3
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleTransitionsTest.java211
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/MultiInstanceHelperTest.kt77
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/UserProfileContextsTest.kt166
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopActivityOrientationChangeHandlerTest.kt12
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandlerTest.kt166
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt130
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt117
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt54
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTestHelpers.kt13
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/compatui/SystemModalsTransitionHandlerTest.kt20
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/education/AppHandleEducationControllerTest.kt342
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/education/AppHandleEducationDatastoreRepositoryTest.kt18
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/education/AppHandleEducationFilterTest.kt104
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/multidesks/RootTaskDesksOrganizerTest.kt256
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/persistence/DesktopRepositoryInitializerTest.kt27
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskListenerTests.java17
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java6
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeCompatPolicyTest.kt (renamed from libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/AppCompatUtilsTest.kt)29
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java10
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java19
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/util/WindowingEducationTestUtils.kt10
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTestsBase.kt4
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java5
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositionerTest.kt4
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/common/WindowDecorTaskResourceLoaderTest.kt15
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDecorViewModelTest.kt8
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecorationTest.kt20
-rw-r--r--libs/hwui/Android.bp3
-rw-r--r--media/java/android/media/AudioManager.java109
-rw-r--r--media/java/android/media/MediaCodec.java33
-rw-r--r--media/java/android/media/MediaRouter2.java2
-rw-r--r--media/java/android/media/flags/projection.aconfig10
-rw-r--r--media/java/android/media/projection/IMediaProjectionWatcherCallback.aidl16
-rw-r--r--media/java/android/media/projection/MediaProjectionEvent.aidl3
-rw-r--r--media/java/android/media/projection/MediaProjectionEvent.java103
-rw-r--r--media/java/android/media/projection/MediaProjectionManager.java21
-rw-r--r--media/java/android/media/quality/MediaQualityContract.java332
-rw-r--r--media/java/android/media/quality/MediaQualityManager.java11
-rw-r--r--media/java/android/mtp/OWNERS6
-rw-r--r--media/jni/android_mtp_MtpDatabase.cpp4
-rw-r--r--media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioManagerTest.java51
-rw-r--r--media/tests/MtpTests/OWNERS6
-rw-r--r--packages/PrintSpooler/src/com/android/printspooler/model/PageContentRepository.java5
-rw-r--r--packages/PrintSpooler/src/com/android/printspooler/model/RemotePrintDocument.java17
-rw-r--r--packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java43
-rw-r--r--packages/SettingsLib/BannerMessagePreference/Android.bp4
-rw-r--r--packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_filled.xml3
-rw-r--r--packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_filled_extra.xml3
-rw-r--r--packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_filled_large.xml3
-rw-r--r--packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_outline.xml3
-rw-r--r--packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_outline_extra.xml3
-rw-r--r--packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_outline_large.xml3
-rw-r--r--packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_tonal.xml3
-rw-r--r--packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_tonal_extra.xml3
-rw-r--r--packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_tonal_large.xml3
-rw-r--r--packages/SettingsLib/ButtonPreference/res/layout/settingslib_button_layout.xml3
-rw-r--r--packages/SettingsLib/ButtonPreference/res/layout/settingslib_number_button.xml3
-rw-r--r--packages/SettingsLib/ButtonPreference/res/layout/settingslib_section_button.xml3
-rw-r--r--packages/SettingsLib/DisplayUtils/Android.bp2
-rw-r--r--packages/SettingsLib/DisplayUtils/src/com/android/settingslib/display/DisplayDensityConfiguration.java45
-rw-r--r--packages/SettingsLib/Graph/graph.proto8
-rw-r--r--packages/SettingsLib/Graph/src/com/android/settingslib/graph/GetPreferenceGraphApiHandler.kt39
-rw-r--r--packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGetterApi.kt7
-rw-r--r--packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGraphBuilder.kt89
-rw-r--r--packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceSetterApi.kt18
-rw-r--r--packages/SettingsLib/Graph/src/com/android/settingslib/graph/ProtoDsl.kt7
-rw-r--r--packages/SettingsLib/IntroPreference/res/layout/settingslib_expressive_preference_intro.xml2
-rw-r--r--packages/SettingsLib/Metadata/processor/src/com/android/settingslib/metadata/PreferenceScreenAnnotationProcessor.kt130
-rw-r--r--packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/Annotations.kt15
-rw-r--r--packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/Bundles.kt46
-rw-r--r--packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PersistentPreference.kt9
-rw-r--r--packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceCoordinate.kt68
-rw-r--r--packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceHierarchy.kt31
-rw-r--r--packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceScreenBindingKeyProvider.kt7
-rw-r--r--packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceScreenMetadata.kt63
-rw-r--r--packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceScreenRegistry.kt28
-rw-r--r--packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBindings.kt5
-rw-r--r--packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceFragment.kt11
-rw-r--r--packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceScreenBindingHelper.kt7
-rw-r--r--packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceScreenFactory.kt13
-rw-r--r--packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictionsProvider.kt4
-rw-r--r--packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt2
-rw-r--r--packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppList.kt5
-rw-r--r--packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt3
-rw-r--r--packages/SettingsLib/TopIntroPreference/res/layout/settingslib_expressive_top_intro.xml3
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/RestrictedDropDownPreference.java15
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/RestrictedPreference.java15
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java19
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/RestrictedSelectorWithWidgetPreference.java7
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java17
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java34
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java5
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/enterprise/ActionDisabledByAdminControllerFactory.java6
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java35
-rw-r--r--packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java1
-rw-r--r--packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java1
-rw-r--r--packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java6
-rw-r--r--packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java47
-rw-r--r--packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java1
-rw-r--r--packages/SystemUI/AndroidManifest.xml21
-rw-r--r--packages/SystemUI/accessibility/accessibilitymenu/aconfig/Android.bp2
-rw-r--r--packages/SystemUI/accessibility/accessibilitymenu/aconfig/accessibility.aconfig2
-rw-r--r--packages/SystemUI/aconfig/systemui.aconfig80
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/animation/FontVariationUtils.kt25
-rw-r--r--packages/SystemUI/compose/core/src/com/android/compose/gesture/effect/OffsetOverscrollEffect.kt4
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ResponsiveLazyHorizontalGrid.kt3
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/MediaCarouselSection.kt12
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt8
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt85
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt9
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/PredictiveBackHandler.kt70
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt4
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt2
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeAnimation.kt70
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/Seek.kt203
-rw-r--r--packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt7
-rw-r--r--packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt75
-rw-r--r--packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt17
-rw-r--r--packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockController.kt11
-rw-r--r--packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockFaceController.kt16
-rw-r--r--packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/FlexClockView.kt40
-rw-r--r--packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockTextView.kt61
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/IMagnificationConnectionTest.java6
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/animation/FontVariationUtilsTest.kt22
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorParameterizedTest.kt8
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorTest.kt12
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/CustomInputGesturesRepositoryTest.kt62
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/CustomShortcutCategoriesRepositoryTest.kt79
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/DefaultShortcutCategoriesRepositoryTest.kt20
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutHelperCategoriesInteractorTest.kt2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/ui/ShortcutHelperDialogStarterTest.kt2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutCustomizationViewModelTest.kt98
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModelTest.kt50
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt40
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorTest.kt35
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToPrimaryBouncerTransitionViewModelTest.kt6
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardMediaViewModelTest.kt84
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModelTest.kt6
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToPrimaryBouncerTransitionViewModelTest.kt78
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModelTest.kt39
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToOccludedTransitionViewModelTest.kt35
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java1
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/media/dialog/OWNERS1
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/NavBarHelperTest.java8
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/TaskbarDelegateTest.kt6
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/views/NavigationBarButtonTest.java4
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/views/NavigationBarInflaterViewTest.java4
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/views/NavigationBarTest.java100
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/views/NavigationBarTransitionsTest.java4
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/views/buttons/KeyButtonViewTest.java4
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayActionsViewModelTest.kt20
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeUserActionsViewModelTest.kt161
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java15
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/InternetTileNewImplTest.kt3
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/ModesTileTest.kt2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileUserActionInteractorTest.kt35
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayActionsViewModelTest.kt20
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt4
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt23
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/shade/QsBatteryModeControllerTest.kt2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/shade/data/repository/ShadeDisplaysRepositoryTest.kt56
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/shade/data/repository/ShadePrimaryDisplayCommandTest.kt19
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/shade/display/FocusShadeDisplayPolicyTest.kt58
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/shade/display/StatusBarTouchShadeDisplayPolicyTest.kt65
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeModeInteractorImplTest.kt41
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java6
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt56
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/TargetSdkResolverTest.kt3
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProviderTest.kt39
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/BundleNotificationInfoTest.java9
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.kt4
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java127
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/PromotedNotificationInfoTest.java156
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculatorTest.kt67
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.kt3
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogCallbacksInteractorTest.kt30
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/volume/dialog/ringer/domain/VolumeDialogRingerInteractorTest.kt22
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/volume/dialog/sliders/domain/interactor/VolumeDialogSliderInputEventsInteractorTest.kt13
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/wallpapers/GradientColorWallpaperTest.kt16
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/wallpapers/data/repository/FakeWallpaperRepository.kt1
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/wallpapers/data/repository/WallpaperRepositoryImplTest.kt5
-rw-r--r--packages/SystemUI/res-keyguard/layout/bouncer_message_view.xml4
-rw-r--r--packages/SystemUI/res-keyguard/layout/keyguard_password_motion_layout.xml2
-rw-r--r--packages/SystemUI/res-keyguard/layout/keyguard_password_view.xml2
-rw-r--r--packages/SystemUI/res-keyguard/layout/keyguard_pattern_motion_layout.xml2
-rw-r--r--packages/SystemUI/res-keyguard/layout/keyguard_pattern_view.xml2
-rw-r--r--packages/SystemUI/res-keyguard/layout/keyguard_pin_motion_layout.xml2
-rw-r--r--packages/SystemUI/res-keyguard/layout/keyguard_pin_view.xml2
-rw-r--r--packages/SystemUI/res-keyguard/layout/shade_carrier_new.xml2
-rw-r--r--packages/SystemUI/res-keyguard/values/styles.xml11
-rw-r--r--packages/SystemUI/res/drawable/notif_footer_btn_background.xml3
-rw-r--r--packages/SystemUI/res/layout/low_light_clock_dream.xml39
-rw-r--r--packages/SystemUI/res/layout/promoted_notification_info.xml387
-rw-r--r--packages/SystemUI/res/layout/shade_carrier.xml2
-rw-r--r--packages/SystemUI/res/layout/shade_carrier_group.xml2
-rw-r--r--packages/SystemUI/res/layout/volume_dialog_slider.xml8
-rw-r--r--packages/SystemUI/res/values-xlarge-land/config.xml1
-rw-r--r--packages/SystemUI/res/values/colors.xml5
-rw-r--r--packages/SystemUI/res/values/config.xml5
-rw-r--r--packages/SystemUI/res/values/dimens.xml15
-rw-r--r--packages/SystemUI/res/values/strings.xml30
-rw-r--r--packages/SystemUI/res/values/styles.xml18
-rw-r--r--packages/SystemUI/res/xml/gradient_color_wallpaper.xml20
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/recents/ILauncherProxy.aidl (renamed from packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl)11
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl4
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java42
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/Utilities.java47
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/shadow/DoubleShadowTextView.kt30
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java26
-rw-r--r--packages/SystemUI/src/com/android/keyguard/CarrierText.java9
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardFingerprintListenModel.kt4
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java4
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java32
-rw-r--r--packages/SystemUI/src/com/android/systemui/Dependency.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/MagnificationImpl.java14
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerController.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/assist/AssistManager.java24
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/DeviceInactiveCondition.java101
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java17
-rw-r--r--packages/SystemUI/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractor.kt14
-rw-r--r--packages/SystemUI/src/com/android/systemui/grid/ui/compose/SpannedGrids.kt28
-rw-r--r--packages/SystemUI/src/com/android/systemui/haptics/msdl/qs/TileHapticsViewModel.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/CustomInputGesturesRepository.kt43
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/CustomShortcutCategoriesRepository.kt25
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/InputGestureMaps.kt46
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/source/AccessibilityShortcutsSource.kt108
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/source/MultitaskingShortcutsSource.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/source/SystemShortcutsSource.kt19
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutCustomizationInteractor.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/ShortcutCustomizationDialogStarter.kt62
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/ShortcutHelperDialogStarter.kt12
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutCustomizer.kt169
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutCustomizationViewModel.kt60
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt14
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt20
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WallpaperFocalAreaInteractor.kt176
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt15
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardIndicationAreaBinder.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt22
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/transitions/BlurConfig.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/view/InWindowLauncherUnlockAnimationManager.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToPrimaryBouncerTransitionViewModel.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardMediaViewModel.kt48
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModel.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToPrimaryBouncerTransitionViewModel.kt26
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModel.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToOccludedTransitionViewModel.kt20
-rw-r--r--packages/SystemUI/src/com/android/systemui/log/table/TableLogBufferFactory.kt30
-rw-r--r--packages/SystemUI/src/com/android/systemui/lowlightclock/AmbientLightModeMonitor.kt137
-rw-r--r--packages/SystemUI/src/com/android/systemui/lowlightclock/ChargingStatusProvider.java258
-rw-r--r--packages/SystemUI/src/com/android/systemui/lowlightclock/DirectBootCondition.kt60
-rw-r--r--packages/SystemUI/src/com/android/systemui/lowlightclock/ForceLowLightCondition.java137
-rw-r--r--packages/SystemUI/src/com/android/systemui/lowlightclock/LowLightClockAnimationProvider.java127
-rw-r--r--packages/SystemUI/src/com/android/systemui/lowlightclock/LowLightCondition.java77
-rw-r--r--packages/SystemUI/src/com/android/systemui/lowlightclock/LowLightDisplayController.kt (renamed from packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/NotificationsShadeUserActionsViewModelKosmos.kt)11
-rw-r--r--packages/SystemUI/src/com/android/systemui/lowlightclock/LowLightDockEvent.kt (renamed from packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderHapticsViewBinderKosmos.kt)26
-rw-r--r--packages/SystemUI/src/com/android/systemui/lowlightclock/LowLightLogger.kt28
-rw-r--r--packages/SystemUI/src/com/android/systemui/lowlightclock/LowLightMonitor.java133
-rw-r--r--packages/SystemUI/src/com/android/systemui/lowlightclock/ScreenSaverEnabledCondition.java87
-rw-r--r--packages/SystemUI/src/com/android/systemui/lowlightclock/dagger/LowLightLog.kt22
-rw-r--r--packages/SystemUI/src/com/android/systemui/lowlightclock/dagger/LowLightModule.java150
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImpl.kt16
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataLoader.kt19
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessor.kt16
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/model/SysUiState.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java16
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java129
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java18
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBar.java115
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBarInflaterView.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBarView.java69
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/views/buttons/KeyButtonView.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayActionsViewModel.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeUserActionsViewModel.kt52
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/BounceableInfo.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt32
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/EditTile.kt28
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/InfiniteGridLayout.kt29
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/TileSpec.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTileNewImpl.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/ModesTile.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDetailsContentManager.kt81
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDetailsViewModel.kt65
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/ModesDetailsViewModel.kt48
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileUserActionInteractor.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractor.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayActionsViewModel.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/LauncherProxyService.java (renamed from packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java)126
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/OverviewProxyRecentsImpl.java32
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotCrossProfileService.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/scrim/ScrimView.java16
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java21
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java23
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt14
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/QsBatteryModeController.kt20
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt26
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/ShadePrimaryDisplayCommand.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/data/repository/FakeShadeDisplayRepository.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeDisplaysRepository.kt48
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/display/FakeShadeDisplayPolicy.kt36
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/display/FocusShadeDisplayPolicy.kt34
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/display/ShadeDisplayPolicy.kt33
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/display/StatusBarTouchShadeDisplayPolicy.kt22
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeModeInteractor.kt43
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java74
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java12
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModel.kt40
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/OngoingActivityChip.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/OngoingActivityChipModel.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/headsup/shared/StatusBarNoHunBehavior.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/NmSummarizationUiFlag.kt50
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustment.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProvider.kt1
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java22
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerImpl.java16
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/BundleNotificationInfo.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java91
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridConversationNotificationView.java15
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/MagicActionBackgroundDrawable.kt124
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java17
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java15
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PromotedNotificationInfo.java104
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/SingleLineViewInflater.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewbinder/SingleLineViewBinder.kt1
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/SingleLineViewModel.kt1
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt32
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardIndicationTextView.java20
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java17
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt15
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java16
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java22
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTransitionAnimatorController.kt19
-rw-r--r--packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/TutorialSelectionScreen.kt20
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/VolumeDialog.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogCallbacksInteractor.kt44
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/domain/model/VolumeDialogEventModel.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/domain/VolumeDialogRingerInteractor.kt1
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/dagger/VolumeDialogSliderComponent.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/data/repository/VolumeDialogSliderTouchEventsRepository.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/domain/interactor/VolumeDialogSliderInputEventsInteractor.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/shared/model/SliderInputEvent.kt14
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogOverscrollViewBinder.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderHapticsViewBinder.kt82
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinder.kt249
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSlidersViewBinder.kt1
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/compose/VolumeDialogSliderTrack.kt347
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogOverscrollViewModel.kt14
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderInputEventsViewModel.kt43
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderStateModel.kt13
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderViewModel.kt51
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/haptics/ui/VolumeHapticsConfigsProvider.kt41
-rw-r--r--packages/SystemUI/src/com/android/systemui/wallpapers/GradientColorWallpaper.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/wallpapers/data/repository/NoopWallpaperRepository.kt1
-rw-r--r--packages/SystemUI/src/com/android/systemui/wallpapers/data/repository/WallpaperRepository.kt70
-rw-r--r--packages/SystemUI/src/com/google/android/systemui/lowlightclock/LowLightClockDreamService.java161
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java56
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationTest.java22
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt8
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/lowlightclock/AmbientLightModeMonitorTest.kt110
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/lowlightclock/ChargingStatusProviderTest.java226
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/lowlightclock/DirectBootConditionTest.kt102
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/lowlightclock/ForceLowLightConditionTest.java110
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/lowlightclock/LowLightClockAnimationProviderTest.kt75
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/lowlightclock/LowLightClockDreamServiceTest.java160
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/lowlightclock/LowLightConditionTest.java143
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/lowlightclock/LowLightMonitorTest.java183
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/lowlightclock/ScreenSaverEnabledConditionTest.java108
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerImplTest.java4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDetailsContentManagerTest.kt40
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/recents/LauncherProxyServiceTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.kt)54
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt19
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt16
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerTest.kt16
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/ShadeHeaderControllerTest.kt43
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java131
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerWithScenesTest.kt3
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/SingleLineViewBinderTest.kt70
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/SingleLineViewInflaterTest.kt32
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt8
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java11
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java7
-rw-r--r--packages/SystemUI/tests/utils/src/android/hardware/input/FakeInputManager.kt92
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeFocusedDisplayRepository.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorKosmos.kt6
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/KeyboardShortcutHelperKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt9
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt33
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/WallpaperFocalAreaInteractorKosmos.kt39
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardMediaViewModelFactoryKosmos.kt (renamed from packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderInputEventsViewModelKosmos.kt)19
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToPrimaryBouncerTransitionViewModelKosmos.kt1
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToOccludedTransitionViewModelKosmos.kt1
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/plugins/FakeVolumeDialogController.kt3
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/ShadeDisplaysRepositoryKosmos.kt17
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeModeInteractorKosmos.kt43
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/RankingBuilder.java10
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/dagger/VolumeDialogSliderComponentKosmos.kt5
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinderKosmos.kt6
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderViewModelKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/wallpapers/data/repository/WallpaperRepositoryKosmos.kt4
-rw-r--r--packages/Vcn/framework-b/framework-vcn-jarjar-rules.txt3
-rw-r--r--packages/Vcn/service-b/src/com/android/server/ConnectivityServiceInitializerB.java3
-rw-r--r--packages/Vcn/service-b/src/com/android/server/VcnManagementService.java3
-rw-r--r--packages/Vcn/service-b/src/com/android/server/vcn/TelephonySubscriptionTracker.java3
-rw-r--r--packages/Vcn/service-b/src/com/android/server/vcn/Vcn.java3
-rw-r--r--packages/Vcn/service-b/src/com/android/server/vcn/VcnGatewayConnection.java3
-rw-r--r--packages/Vcn/service-b/src/com/android/server/vcn/VcnNetworkProvider.java3
-rw-r--r--packages/Vcn/service-b/src/com/android/server/vcn/routeselection/IpSecPacketLossDetector.java3
-rw-r--r--packages/Vcn/service-b/src/com/android/server/vcn/routeselection/NetworkMetricMonitor.java3
-rw-r--r--packages/Vcn/service-b/src/com/android/server/vcn/routeselection/NetworkPriorityClassifier.java3
-rw-r--r--packages/Vcn/service-b/src/com/android/server/vcn/routeselection/UnderlyingNetworkController.java3
-rw-r--r--packages/Vcn/service-b/src/com/android/server/vcn/routeselection/UnderlyingNetworkEvaluator.java3
-rw-r--r--ravenwood/Android.bp7
-rw-r--r--ravenwood/Framework.bp52
-rwxr-xr-xravenwood/scripts/pta-framework.sh3
-rw-r--r--ravenwood/texts/ravenwood-framework-policies.txt2
-rw-r--r--ravenwood/texts/ravenwood-standard-options.txt3
-rw-r--r--ravenwood/tools/ravenhelper/src/com/android/platform/test/ravenwood/ravenhelper/policytoannot/Annotations.kt3
-rw-r--r--ravenwood/tools/ravenizer/Android.bp2
-rw-r--r--services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java1
-rw-r--r--services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java37
-rw-r--r--services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickController.java (renamed from services/accessibility/java/com/android/server/accessibility/AutoclickController.java)156
-rw-r--r--services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickIndicatorView.java (renamed from services/accessibility/java/com/android/server/accessibility/AutoclickIndicatorView.java)26
-rw-r--r--services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java73
-rw-r--r--services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java10
-rw-r--r--services/core/java/com/android/server/MasterClearReceiver.java2
-rw-r--r--services/core/java/com/android/server/am/BroadcastController.java2
-rw-r--r--services/core/java/com/android/server/am/PendingIntentRecord.java12
-rw-r--r--services/core/java/com/android/server/appop/AppOpsService.java89
-rw-r--r--services/core/java/com/android/server/audio/AudioManagerShellCommand.java39
-rw-r--r--services/core/java/com/android/server/audio/AudioService.java69
-rw-r--r--services/core/java/com/android/server/audio/PlaybackActivityMonitor.java67
-rw-r--r--services/core/java/com/android/server/display/feature/DisplayManagerFlags.java3
-rw-r--r--services/core/java/com/android/server/display/plugin/PluginManager.java10
-rw-r--r--services/core/java/com/android/server/display/plugin/PluginStorage.java200
-rw-r--r--services/core/java/com/android/server/hdmi/HdmiControlService.java4
-rw-r--r--services/core/java/com/android/server/infra/OWNERS1
-rw-r--r--services/core/java/com/android/server/input/InputGestureManager.java14
-rw-r--r--services/core/java/com/android/server/location/contexthub/ContextHubEndpointBroker.java20
-rw-r--r--services/core/java/com/android/server/media/SystemMediaRoute2Provider2.java40
-rw-r--r--services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java102
-rw-r--r--services/core/java/com/android/server/media/projection/MediaProjectionStopController.java37
-rw-r--r--services/core/java/com/android/server/media/quality/MediaQualityService.java504
-rw-r--r--services/core/java/com/android/server/net/NetworkPolicyManagerService.java9
-rw-r--r--services/core/java/com/android/server/notification/NotificationManagerService.java76
-rw-r--r--services/core/java/com/android/server/notification/NotificationRecord.java17
-rw-r--r--services/core/java/com/android/server/notification/NotificationRecordExtractorData.java11
-rw-r--r--services/core/java/com/android/server/notification/PreferencesHelper.java29
-rw-r--r--services/core/java/com/android/server/notification/ZenLog.java5
-rw-r--r--services/core/java/com/android/server/notification/flags.aconfig10
-rw-r--r--services/core/java/com/android/server/pm/DexOptHelper.java99
-rw-r--r--services/core/java/com/android/server/pm/InstallPackageHelper.java1
-rw-r--r--services/core/java/com/android/server/pm/UserManagerInternal.java6
-rw-r--r--services/core/java/com/android/server/pm/UserManagerService.java6
-rw-r--r--services/core/java/com/android/server/policy/PhoneWindowManager.java28
-rw-r--r--services/core/java/com/android/server/policy/SingleKeyGestureDetector.java15
-rw-r--r--services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java8
-rw-r--r--services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java10
-rw-r--r--services/core/java/com/android/server/wallpaper/WallpaperManagerInternal.java3
-rw-r--r--services/core/java/com/android/server/wallpaper/WallpaperManagerService.java238
-rw-r--r--services/core/java/com/android/server/wm/AccessibilityController.java24
-rw-r--r--services/core/java/com/android/server/wm/ActivityRecord.java20
-rw-r--r--services/core/java/com/android/server/wm/ActivityStarter.java144
-rw-r--r--services/core/java/com/android/server/wm/ActivityTaskManagerService.java10
-rw-r--r--services/core/java/com/android/server/wm/AppCompatOrientationPolicy.java5
-rw-r--r--services/core/java/com/android/server/wm/BackgroundActivityStartController.java5
-rw-r--r--services/core/java/com/android/server/wm/DisplayPolicy.java5
-rw-r--r--services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java2
-rw-r--r--services/core/java/com/android/server/wm/ScreenRecordingCallbackController.java7
-rw-r--r--services/core/java/com/android/server/wm/Transition.java11
-rw-r--r--services/core/java/com/android/server/wm/WallpaperWindowToken.java5
-rw-r--r--services/core/java/com/android/server/wm/WindowContainer.java21
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerService.java20
-rw-r--r--services/core/java/com/android/server/wm/WindowOrganizerController.java34
-rw-r--r--services/core/jni/com_android_server_utils_AnrTimer.cpp10
-rw-r--r--services/java/com/android/server/SystemServer.java2
-rw-r--r--services/supervision/java/com/android/server/supervision/SupervisionService.java150
-rw-r--r--services/supervision/java/com/android/server/supervision/SupervisionServiceShellCommand.java12
-rw-r--r--services/supervision/java/com/android/server/supervision/SupervisionUserData.java2
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/plugin/PluginManagerTest.kt9
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/plugin/PluginStorageTest.kt75
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java26
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/am/PendingIntentControllerTest.java13
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java184
-rw-r--r--services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsProviderTest.java67
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/AccessibilityInputFilterTest.java1
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java18
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickControllerTest.java (renamed from services/tests/servicestests/src/com/android/server/accessibility/AutoclickControllerTest.java)4
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java121
-rw-r--r--services/tests/servicestests/src/com/android/server/audio/AbsoluteVolumeBehaviorTest.java8
-rw-r--r--services/tests/servicestests/src/com/android/server/audio/DeviceVolumeBehaviorTest.java2
-rw-r--r--services/tests/servicestests/src/com/android/server/audio/VolumeHelperTest.java3
-rw-r--r--services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java107
-rw-r--r--services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionStopControllerTest.java102
-rw-r--r--services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java6
-rw-r--r--services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest8.java16
-rw-r--r--services/tests/servicestests/src/com/android/server/supervision/SupervisionServiceTest.kt50
-rw-r--r--services/tests/uiservicestests/Android.bp1
-rw-r--r--services/tests/uiservicestests/AndroidManifest.xml2
-rw-r--r--services/tests/uiservicestests/src/android/app/ExampleActivity.java20
-rw-r--r--services/tests/uiservicestests/src/android/app/NotificationManagerZenTest.java284
-rw-r--r--services/tests/uiservicestests/src/android/app/NotificationSystemUtil.java71
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java10
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java83
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordExtractorDataTest.java55
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java47
-rw-r--r--services/tests/wmtests/src/com/android/server/policy/CombinationKeyTests.java2
-rw-r--r--services/tests/wmtests/src/com/android/server/policy/DeferredKeyActionExecutorTests.java2
-rw-r--r--services/tests/wmtests/src/com/android/server/policy/KeyCombinationManagerTests.java3
-rw-r--r--services/tests/wmtests/src/com/android/server/policy/KeyGestureEventTests.java11
-rw-r--r--services/tests/wmtests/src/com/android/server/policy/ModifierShortcutManagerTests.java5
-rw-r--r--services/tests/wmtests/src/com/android/server/policy/PhoneWindowManagerTests.java2
-rw-r--r--services/tests/wmtests/src/com/android/server/policy/PowerKeyGestureTests.java2
-rw-r--r--services/tests/wmtests/src/com/android/server/policy/SingleKeyGestureTests.java48
-rw-r--r--services/tests/wmtests/src/com/android/server/policy/StemKeyGestureTests.java2
-rw-r--r--services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java5
-rw-r--r--services/tests/wmtests/src/com/android/server/policy/WindowWakeUpPolicyTests.java2
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerTests.java6
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java39
-rw-r--r--services/usb/OWNERS6
-rw-r--r--telecomm/java/com/android/internal/telecom/ITelecomService.aidl1
-rw-r--r--telephony/java/android/telephony/satellite/SatelliteSubscriberInfo.java4
-rw-r--r--telephony/java/android/telephony/satellite/SatelliteSubscriberProvisionStatus.java4
-rw-r--r--telephony/java/com/android/internal/telephony/util/WorkerThread.java130
-rw-r--r--tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/RecentTasksUtils.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/RecentTasksUtils.kt)4
-rw-r--r--tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt24
-rw-r--r--tests/Tracing/src/com/android/internal/protolog/ProtoLogViewerConfigReaderTest.java11
-rw-r--r--tests/testables/src/android/testing/OWNERS2
-rw-r--r--tests/testables/src/android/testing/TestableLooper.java58
-rw-r--r--tests/utils/testutils/java/android/os/test/OWNERS3
-rw-r--r--tests/utils/testutils/java/android/os/test/TestLooper.java140
-rwxr-xr-xtools/aapt2/tools/finalize_res.py111
799 files changed, 22496 insertions, 6415 deletions
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index 302168d845fa..834398e5c2c2 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -824,6 +824,11 @@ java_aconfig_library {
defaults: ["framework-minus-apex-aconfig-java-defaults"],
}
+cc_aconfig_library {
+ name: "android.media.tv.flags-aconfig-cc",
+ aconfig_declarations: "android.media.tv.flags-aconfig",
+}
+
// Permissions
aconfig_declarations {
name: "android.permission.flags-aconfig",
diff --git a/apex/jobscheduler/service/aconfig/alarm.aconfig b/apex/jobscheduler/service/aconfig/alarm.aconfig
index a6e980726a9a..9181ef0c532a 100644
--- a/apex/jobscheduler/service/aconfig/alarm.aconfig
+++ b/apex/jobscheduler/service/aconfig/alarm.aconfig
@@ -7,3 +7,13 @@ flag {
description: "Persist list of users with alarms scheduled and wakeup stopped users before alarms are due"
bug: "314907186"
}
+
+flag {
+ name: "acquire_wakelock_before_send"
+ namespace: "backstage_power"
+ description: "Acquire the userspace alarm wakelock before sending the alarm"
+ bug: "391413964"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
index 829442aed6ac..f89b13dce307 100644
--- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
@@ -5334,6 +5334,18 @@ public class AlarmManagerService extends SystemService {
public void deliverLocked(Alarm alarm, long nowELAPSED) {
final long workSourceToken = ThreadLocalWorkSource.setUid(
getAlarmAttributionUid(alarm));
+
+ if (Flags.acquireWakelockBeforeSend()) {
+ // Acquire the wakelock before starting the app. This needs to be done to avoid
+ // random stalls in the receiving app in case a suspend attempt is already in
+ // progress. See b/391413964 for an incident where this was found to happen.
+ if (mBroadcastRefCount == 0) {
+ setWakelockWorkSource(alarm.workSource, alarm.creatorUid, alarm.statsTag, true);
+ mWakeLock.acquire();
+ mHandler.obtainMessage(AlarmHandler.REPORT_ALARMS_ACTIVE, 1, 0).sendToTarget();
+ }
+ }
+
try {
if (alarm.operation != null) {
// PendingIntent alarm
@@ -5399,14 +5411,16 @@ public class AlarmManagerService extends SystemService {
ThreadLocalWorkSource.restore(workSourceToken);
}
- // The alarm is now in flight; now arrange wakelock and stats tracking
if (DEBUG_WAKELOCK) {
Slog.d(TAG, "mBroadcastRefCount -> " + (mBroadcastRefCount + 1));
}
- if (mBroadcastRefCount == 0) {
- setWakelockWorkSource(alarm.workSource, alarm.creatorUid, alarm.statsTag, true);
- mWakeLock.acquire();
- mHandler.obtainMessage(AlarmHandler.REPORT_ALARMS_ACTIVE, 1, 0).sendToTarget();
+ if (!Flags.acquireWakelockBeforeSend()) {
+ // The alarm is now in flight; now arrange wakelock and stats tracking
+ if (mBroadcastRefCount == 0) {
+ setWakelockWorkSource(alarm.workSource, alarm.creatorUid, alarm.statsTag, true);
+ mWakeLock.acquire();
+ mHandler.obtainMessage(AlarmHandler.REPORT_ALARMS_ACTIVE, 1, 0).sendToTarget();
+ }
}
final InFlight inflight = new InFlight(AlarmManagerService.this, alarm, nowELAPSED);
mInFlight.add(inflight);
diff --git a/api/StubLibraries.bp b/api/StubLibraries.bp
index 787fdee6ee16..3314b4aa0d71 100644
--- a/api/StubLibraries.bp
+++ b/api/StubLibraries.bp
@@ -648,7 +648,7 @@ java_api_library {
java_api_library {
name: "android-non-updatable.stubs.module_lib.from-text",
- api_surface: "module_lib",
+ api_surface: "module-lib",
api_contributions: [
"api-stubs-docs-non-updatable.api.contribution",
"system-api-stubs-docs-non-updatable.api.contribution",
@@ -668,7 +668,7 @@ java_api_library {
// generated from this module, as this module is strictly used for hiddenapi only.
java_api_library {
name: "android-non-updatable.stubs.test_module_lib",
- api_surface: "module_lib",
+ api_surface: "module-lib",
api_contributions: [
"api-stubs-docs-non-updatable.api.contribution",
"system-api-stubs-docs-non-updatable.api.contribution",
@@ -689,7 +689,7 @@ java_api_library {
java_api_library {
name: "android-non-updatable.stubs.system_server.from-text",
- api_surface: "system_server",
+ api_surface: "system-server",
api_contributions: [
"api-stubs-docs-non-updatable.api.contribution",
"system-api-stubs-docs-non-updatable.api.contribution",
diff --git a/core/api/current.txt b/core/api/current.txt
index 6964866db7f3..e9a63f74d59f 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -452,7 +452,7 @@ package android {
field public static final int activityCloseExitAnimation = 16842939; // 0x10100bb
field public static final int activityOpenEnterAnimation = 16842936; // 0x10100b8
field public static final int activityOpenExitAnimation = 16842937; // 0x10100b9
- field @FlaggedApi("android.media.tv.flags.enable_ad_service_fw") public static final int adServiceTypes;
+ field @FlaggedApi("android.media.tv.flags.enable_ad_service_fw") public static final int adServiceTypes = 16844452; // 0x10106a4
field public static final int addPrintersActivity = 16843750; // 0x10103e6
field public static final int addStatesFromChildren = 16842992; // 0x10100f0
field public static final int adjustViewBounds = 16843038; // 0x101011e
@@ -1017,7 +1017,7 @@ package android {
field public static final int insetRight = 16843192; // 0x10101b8
field public static final int insetTop = 16843193; // 0x10101b9
field public static final int installLocation = 16843447; // 0x10102b7
- field @FlaggedApi("android.security.enable_intent_matching_flags") public static final int intentMatchingFlags;
+ field @FlaggedApi("android.security.enable_intent_matching_flags") public static final int intentMatchingFlags = 16844457; // 0x10106a9
field public static final int interactiveUiTimeout = 16844181; // 0x1010595
field public static final int interpolator = 16843073; // 0x1010141
field public static final int intro = 16844395; // 0x101066b
@@ -1072,7 +1072,7 @@ package android {
field public static final int label = 16842753; // 0x1010001
field public static final int labelFor = 16843718; // 0x10103c6
field @Deprecated public static final int labelTextSize = 16843317; // 0x1010235
- field @FlaggedApi("android.view.inputmethod.ime_switcher_revamp_api") public static final int languageSettingsActivity;
+ field @FlaggedApi("android.view.inputmethod.ime_switcher_revamp_api") public static final int languageSettingsActivity = 16844453; // 0x10106a5
field public static final int languageTag = 16844040; // 0x1010508
field public static final int largeHeap = 16843610; // 0x101035a
field public static final int largeScreens = 16843398; // 0x1010286
@@ -1085,7 +1085,7 @@ package android {
field public static final int layout = 16842994; // 0x10100f2
field public static final int layoutAnimation = 16842988; // 0x10100ec
field public static final int layoutDirection = 16843698; // 0x10103b2
- field @FlaggedApi("android.view.inputmethod.ime_switcher_revamp_api") public static final int layoutLabel;
+ field @FlaggedApi("android.view.inputmethod.ime_switcher_revamp_api") public static final int layoutLabel = 16844458; // 0x10106aa
field public static final int layoutMode = 16843738; // 0x10103da
field public static final int layout_above = 16843140; // 0x1010184
field public static final int layout_alignBaseline = 16843142; // 0x1010186
@@ -1207,7 +1207,7 @@ package android {
field public static final int minResizeHeight = 16843670; // 0x1010396
field public static final int minResizeWidth = 16843669; // 0x1010395
field public static final int minSdkVersion = 16843276; // 0x101020c
- field @FlaggedApi("android.sdk.major_minor_versioning_scheme") public static final int minSdkVersionFull;
+ field @FlaggedApi("android.sdk.major_minor_versioning_scheme") public static final int minSdkVersionFull = 16844461; // 0x10106ad
field public static final int minWidth = 16843071; // 0x101013f
field public static final int minimumHorizontalAngle = 16843901; // 0x101047d
field public static final int minimumVerticalAngle = 16843902; // 0x101047e
@@ -1282,7 +1282,7 @@ package android {
field public static final int paddingStart = 16843699; // 0x10103b3
field public static final int paddingTop = 16842967; // 0x10100d7
field public static final int paddingVertical = 16844094; // 0x101053e
- field @FlaggedApi("android.content.pm.app_compat_option_16kb") public static final int pageSizeCompat;
+ field @FlaggedApi("android.content.pm.app_compat_option_16kb") public static final int pageSizeCompat = 16844459; // 0x10106ab
field public static final int panelBackground = 16842846; // 0x101005e
field public static final int panelColorBackground = 16842849; // 0x1010061
field public static final int panelColorForeground = 16842848; // 0x1010060
@@ -1624,7 +1624,7 @@ package android {
field public static final int summaryColumn = 16843426; // 0x10102a2
field public static final int summaryOff = 16843248; // 0x10101f0
field public static final int summaryOn = 16843247; // 0x10101ef
- field @FlaggedApi("android.view.accessibility.supplemental_description") public static final int supplementalDescription;
+ field @FlaggedApi("android.view.accessibility.supplemental_description") public static final int supplementalDescription = 16844456; // 0x10106a8
field public static final int supportedTypes = 16844369; // 0x1010651
field public static final int supportsAssist = 16844016; // 0x10104f0
field public static final int supportsBatteryGameMode = 16844374; // 0x1010656
@@ -1871,7 +1871,7 @@ package android {
field public static final int wallpaperIntraOpenExitAnimation = 16843416; // 0x1010298
field public static final int wallpaperOpenEnterAnimation = 16843411; // 0x1010293
field public static final int wallpaperOpenExitAnimation = 16843412; // 0x1010294
- field @FlaggedApi("android.nfc.nfc_associated_role_services") public static final int wantsRoleHolderPriority;
+ field @FlaggedApi("android.nfc.nfc_associated_role_services") public static final int wantsRoleHolderPriority = 16844460; // 0x10106ac
field public static final int webTextViewStyle = 16843449; // 0x10102b9
field public static final int webViewStyle = 16842885; // 0x1010085
field public static final int weekDayTextAppearance = 16843592; // 0x1010348
@@ -11134,6 +11134,7 @@ package android.content {
field public static final String TELEPHONY_IMS_SERVICE = "telephony_ims";
field public static final String TELEPHONY_SERVICE = "phone";
field public static final String TELEPHONY_SUBSCRIPTION_SERVICE = "telephony_subscription_service";
+ field public static final String TETHERING_SERVICE = "tethering";
field public static final String TEXT_CLASSIFICATION_SERVICE = "textclassification";
field public static final String TEXT_SERVICES_MANAGER_SERVICE = "textservices";
field @FlaggedApi("android.media.tv.flags.enable_ad_service_fw") public static final String TV_AD_SERVICE = "tv_ad";
@@ -42267,6 +42268,7 @@ package android.service.notification {
method public int getRank();
method @NonNull public java.util.List<android.app.Notification.Action> getSmartActions();
method @NonNull public java.util.List<java.lang.CharSequence> getSmartReplies();
+ method @FlaggedApi("android.app.nm_summarization") @Nullable public String getSummarization();
method public int getSuppressedVisualEffects();
method public int getUserSentiment();
method public boolean isAmbient();
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 937a9ffaf210..7483316e94b0 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -464,7 +464,7 @@ package android {
public static final class R.attr {
field public static final int allowClearUserDataOnFailedRestore = 16844288; // 0x1010600
- field @FlaggedApi("android.permission.flags.replace_body_sensor_permission_enabled") public static final int backgroundPermission;
+ field @FlaggedApi("android.permission.flags.replace_body_sensor_permission_enabled") public static final int backgroundPermission = 16844455; // 0x10106a7
field @FlaggedApi("android.content.res.manifest_flagging") public static final int featureFlag = 16844428; // 0x101068c
field public static final int gameSessionService = 16844373; // 0x1010655
field public static final int hotwordDetectionService = 16844326; // 0x1010626
@@ -543,7 +543,7 @@ package android {
field public static final int config_systemCallStreaming = 17039431; // 0x1040047
field public static final int config_systemCompanionDeviceProvider = 17039417; // 0x1040039
field public static final int config_systemContacts = 17039403; // 0x104002b
- field @FlaggedApi("android.content.pm.sdk_dependency_installer") public static final int config_systemDependencyInstaller;
+ field @FlaggedApi("android.content.pm.sdk_dependency_installer") public static final int config_systemDependencyInstaller = 17039434; // 0x104004a
field public static final int config_systemFinancedDeviceController = 17039430; // 0x1040046
field public static final int config_systemGallery = 17039399; // 0x1040027
field public static final int config_systemNotificationIntelligence = 17039413; // 0x1040035
@@ -555,7 +555,7 @@ package android {
field public static final int config_systemTextIntelligence = 17039414; // 0x1040036
field public static final int config_systemUi = 17039418; // 0x104003a
field public static final int config_systemUiIntelligence = 17039410; // 0x1040032
- field @FlaggedApi("android.permission.flags.system_vendor_intelligence_role_enabled") public static final int config_systemVendorIntelligence;
+ field @FlaggedApi("android.permission.flags.system_vendor_intelligence_role_enabled") public static final int config_systemVendorIntelligence = 17039435; // 0x104004b
field public static final int config_systemVisualIntelligence = 17039415; // 0x1040037
field public static final int config_systemWearHealthService = 17039428; // 0x1040044
field public static final int config_systemWellbeing = 17039408; // 0x1040030
@@ -3800,7 +3800,6 @@ package android.content {
field public static final String STATS_MANAGER = "stats";
field public static final String SYSTEM_CONFIG_SERVICE = "system_config";
field public static final String SYSTEM_UPDATE_SERVICE = "system_update";
- field public static final String TETHERING_SERVICE = "tethering";
field @FlaggedApi("com.android.net.thread.platform.flags.thread_enabled_platform") public static final String THREAD_NETWORK_SERVICE = "thread_network";
field public static final String TIME_MANAGER_SERVICE = "time_manager";
field public static final String TRANSLATION_MANAGER_SERVICE = "translation";
@@ -13446,6 +13445,7 @@ package android.service.notification {
field public static final String KEY_RANKING_SCORE = "key_ranking_score";
field public static final String KEY_SENSITIVE_CONTENT = "key_sensitive_content";
field public static final String KEY_SNOOZE_CRITERIA = "key_snooze_criteria";
+ field @FlaggedApi("android.app.nm_summarization") public static final String KEY_SUMMARIZATION = "key_summarization";
field public static final String KEY_TEXT_REPLIES = "key_text_replies";
field @FlaggedApi("android.service.notification.notification_classification") public static final String KEY_TYPE = "key_type";
field public static final String KEY_USER_SENTIMENT = "key_user_sentiment";
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index f06bd48a8cd8..36ef4f5f06ee 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -478,8 +478,8 @@ package android.app {
method public void destroy();
method @NonNull public java.util.Set<java.lang.String> getAdoptedShellPermissions();
method @Deprecated public boolean grantRuntimePermission(String, String, android.os.UserHandle);
- method public boolean injectInputEvent(@NonNull android.view.InputEvent, boolean, boolean);
- method public void injectInputEventToInputFilter(@NonNull android.view.InputEvent);
+ method @Deprecated @FlaggedApi("com.android.input.flags.deprecate_uiautomation_input_injection") public boolean injectInputEvent(@NonNull android.view.InputEvent, boolean, boolean);
+ method @Deprecated @FlaggedApi("com.android.input.flags.deprecate_uiautomation_input_injection") public void injectInputEventToInputFilter(@NonNull android.view.InputEvent);
method public boolean isNodeInCache(@NonNull android.view.accessibility.AccessibilityNodeInfo);
method public void removeOverridePermissionState(int, @NonNull String);
method @Deprecated public boolean revokeRuntimePermission(String, String, android.os.UserHandle);
@@ -1634,6 +1634,10 @@ package android.hardware.camera2.params {
method public void setColorSpace(@NonNull android.graphics.ColorSpace.Named);
}
+ @FlaggedApi("com.android.internal.camera.flags.camera_multi_client") public final class SharedSessionConfiguration {
+ ctor public SharedSessionConfiguration(int, @NonNull long[]);
+ }
+
}
package android.hardware.devicestate {
diff --git a/core/java/android/app/ActivityTaskManager.java b/core/java/android/app/ActivityTaskManager.java
index 16dcf2ad7e45..8d20e46c7df8 100644
--- a/core/java/android/app/ActivityTaskManager.java
+++ b/core/java/android/app/ActivityTaskManager.java
@@ -25,6 +25,7 @@ import android.annotation.SystemService;
import android.annotation.TestApi;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
+import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.content.res.Resources;
@@ -483,6 +484,19 @@ public class ActivityTaskManager {
}
/**
+ * @return Whether the app could be universal resizeable (assuming it's on a large screen and
+ * ignoring possible overrides)
+ * @hide
+ */
+ public boolean canBeUniversalResizeable(@NonNull ApplicationInfo appInfo) {
+ try {
+ return getService().canBeUniversalResizeable(appInfo);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Detaches the navigation bar from the app it was attached to during a transition.
* @hide
*/
diff --git a/core/java/android/app/IActivityTaskManager.aidl b/core/java/android/app/IActivityTaskManager.aidl
index c6f62a21641d..4b1afa517122 100644
--- a/core/java/android/app/IActivityTaskManager.aidl
+++ b/core/java/android/app/IActivityTaskManager.aidl
@@ -158,6 +158,12 @@ interface IActivityTaskManager {
void reportAssistContextExtras(in IBinder assistToken, in Bundle extras,
in AssistStructure structure, in AssistContent content, in Uri referrer);
+ /**
+ * @return whether the app could be universal resizeable (assuming it's on a large screen and
+ * ignoring possible overrides)
+ */
+ boolean canBeUniversalResizeable(in ApplicationInfo appInfo);
+
void setFocusedRootTask(int taskId);
ActivityTaskManager.RootTaskInfo getFocusedRootTaskInfo();
Rect getTaskBounds(int taskId);
diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl
index 8fa2362139a1..ff39329a0d2d 100644
--- a/core/java/android/app/INotificationManager.aidl
+++ b/core/java/android/app/INotificationManager.aidl
@@ -114,7 +114,6 @@ interface INotificationManager
NotificationChannel getNotificationChannelForPackage(String pkg, int uid, String channelId, String conversationId, boolean includeDeleted);
void deleteNotificationChannel(String pkg, String channelId);
ParceledListSlice getNotificationChannels(String callingPkg, String targetPkg, int userId);
- ParceledListSlice getOrCreateNotificationChannels(String callingPkg, String targetPkg, int userId, boolean createPrefsIfNeeded);
ParceledListSlice getNotificationChannelsForPackage(String pkg, int uid, boolean includeDeleted);
int getNumNotificationChannelsForPackage(String pkg, int uid, boolean includeDeleted);
int getDeletedChannelCount(String pkg, int uid);
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index fcb817ede6b3..40db6dd1b0ba 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -1772,6 +1772,11 @@ public class Notification implements Parcelable
*/
public static final String EXTRA_FOREGROUND_APPS = "android.foregroundApps";
+ /**
+ * @hide
+ */
+ public static final String EXTRA_SUMMARIZED_CONTENT = "android.summarization";
+
@UnsupportedAppUsage
private Icon mSmallIcon;
@UnsupportedAppUsage
@@ -4393,6 +4398,9 @@ public class Notification implements Parcelable
*/
@Nullable
public Pair<RemoteInput, Action> findRemoteInputActionPair(boolean requiresFreeform) {
+ if (isPromotedOngoing()) {
+ return null;
+ }
if (actions == null) {
return null;
}
@@ -6454,6 +6462,11 @@ public class Notification implements Parcelable
if (mActions == null) return Collections.emptyList();
List<Notification.Action> standardActions = new ArrayList<>();
for (Notification.Action action : mActions) {
+ // Actions with RemoteInput are ignored for RONs.
+ if (mN.isPromotedOngoing()
+ && hasValidRemoteInput(action)) {
+ continue;
+ }
if (!action.isContextual()) {
standardActions.add(action);
}
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index 1e1ec602d0a2..21dad28560df 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -1264,8 +1264,7 @@ public class NotificationManager {
mNotificationChannelListCache.query(new NotificationChannelQuery(
mContext.getOpPackageName(),
mContext.getPackageName(),
- mContext.getUserId(),
- true))); // create (default channel) if needed
+ mContext.getUserId())));
} else {
INotificationManager service = service();
try {
@@ -1293,8 +1292,7 @@ public class NotificationManager {
mNotificationChannelListCache.query(new NotificationChannelQuery(
mContext.getOpPackageName(),
mContext.getPackageName(),
- mContext.getUserId(),
- true))); // create (default channel) if needed
+ mContext.getUserId())));
} else {
INotificationManager service = service();
try {
@@ -1320,8 +1318,7 @@ public class NotificationManager {
return mNotificationChannelListCache.query(new NotificationChannelQuery(
mContext.getOpPackageName(),
mContext.getPackageName(),
- mContext.getUserId(),
- false));
+ mContext.getUserId()));
} else {
INotificationManager service = service();
try {
@@ -1461,8 +1458,8 @@ public class NotificationManager {
public List<NotificationChannel> apply(NotificationChannelQuery query) {
INotificationManager service = service();
try {
- return service.getOrCreateNotificationChannels(query.callingPkg,
- query.targetPkg, query.userId, query.createIfNeeded).getList();
+ return service.getNotificationChannels(query.callingPkg,
+ query.targetPkg, query.userId).getList();
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -1490,8 +1487,7 @@ public class NotificationManager {
private record NotificationChannelQuery(
String callingPkg,
String targetPkg,
- int userId,
- boolean createIfNeeded) {}
+ int userId) {}
/**
* @hide
diff --git a/core/java/android/app/StatusBarManager.java b/core/java/android/app/StatusBarManager.java
index b7285c38290c..01868cc601fe 100644
--- a/core/java/android/app/StatusBarManager.java
+++ b/core/java/android/app/StatusBarManager.java
@@ -60,6 +60,7 @@ import com.android.internal.statusbar.NotificationVisibility;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -195,12 +196,40 @@ public class StatusBarManager {
*/
private static final int DEFAULT_SIM_LOCKED_DISABLED_FLAGS = DISABLE_EXPAND;
- /** @hide */
- public static final int NAVIGATION_HINT_BACK_ALT = 1 << 0;
- /** @hide */
- public static final int NAVIGATION_HINT_IME_SHOWN = 1 << 1;
- /** @hide */
- public static final int NAVIGATION_HINT_IME_SWITCHER_SHOWN = 1 << 2;
+ /**
+ * The back button is visually adjusted to indicate that it will dismiss the IME when pressed.
+ * This only takes effect while the IME is visible. By default, it is set while the IME is
+ * visible, but may be overridden by the
+ * {@link android.inputmethodservice.InputMethodService.BackDispositionMode backDispositionMode}
+ * set by the IME.
+ *
+ * @hide
+ */
+ public static final int NAVBAR_BACK_DISMISS_IME = 1 << 0;
+ /**
+ * The IME is visible.
+ *
+ * @hide
+ */
+ public static final int NAVBAR_IME_VISIBLE = 1 << 1;
+ /**
+ * The IME Switcher button is visible. This only takes effect while the IME is visible.
+ *
+ * @hide
+ */
+ public static final int NAVBAR_IME_SWITCHER_BUTTON_VISIBLE = 1 << 2;
+ /**
+ * Navigation bar state flags.
+ *
+ * @hide
+ */
+ @IntDef(flag = true, prefix = { "NAVBAR_" }, value = {
+ NAVBAR_BACK_DISMISS_IME,
+ NAVBAR_IME_VISIBLE,
+ NAVBAR_IME_SWITCHER_BUTTON_VISIBLE,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface NavbarFlags {}
/** @hide */
public static final int WINDOW_STATUS_BAR = 1;
@@ -1325,6 +1354,22 @@ public class StatusBarManager {
}
/** @hide */
+ @NonNull
+ public static String navbarFlagsToString(@NavbarFlags int flags) {
+ final var flagStrings = new ArrayList<String>();
+ if ((flags & NAVBAR_BACK_DISMISS_IME) != 0) {
+ flagStrings.add("NAVBAR_BACK_DISMISS_IME");
+ }
+ if ((flags & NAVBAR_IME_VISIBLE) != 0) {
+ flagStrings.add("NAVBAR_IME_VISIBLE");
+ }
+ if ((flags & NAVBAR_IME_SWITCHER_BUTTON_VISIBLE) != 0) {
+ flagStrings.add("NAVBAR_IME_SWITCHER_BUTTON_VISIBLE");
+ }
+ return String.join(" | ", flagStrings);
+ }
+
+ /** @hide */
public static String windowStateToString(int state) {
if (state == WINDOW_STATE_HIDING) return "WINDOW_STATE_HIDING";
if (state == WINDOW_STATE_HIDDEN) return "WINDOW_STATE_HIDDEN";
diff --git a/core/java/android/app/UiAutomation.java b/core/java/android/app/UiAutomation.java
index 6f8e335cff80..8021ab4865af 100644
--- a/core/java/android/app/UiAutomation.java
+++ b/core/java/android/app/UiAutomation.java
@@ -18,6 +18,8 @@ package android.app;
import static android.view.Display.DEFAULT_DISPLAY;
+import static com.android.input.flags.Flags.FLAG_DEPRECATE_UIAUTOMATION_INPUT_INJECTION;
+
import android.accessibilityservice.AccessibilityGestureEvent;
import android.accessibilityservice.AccessibilityService;
import android.accessibilityservice.AccessibilityService.Callbacks;
@@ -26,6 +28,7 @@ import android.accessibilityservice.AccessibilityServiceInfo;
import android.accessibilityservice.IAccessibilityServiceClient;
import android.accessibilityservice.IAccessibilityServiceConnection;
import android.accessibilityservice.MagnificationConfig;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -108,7 +111,10 @@ import java.util.concurrent.TimeoutException;
* client should be using a higher-level library or implement high-level functions.
* For example, performing a tap on the screen requires construction and injecting
* of a touch down and up events which have to be delivered to the system by a
- * call to {@link #injectInputEvent(InputEvent, boolean)}.
+ * call to {@link #injectInputEvent(InputEvent, boolean)}. <strong>Note:</strong> For CTS tests, it
+ * is preferable to inject input events using uinput (com.android.cts.input.UinputDevice) or hid
+ * devices (com.android.cts.input.HidDevice). Alternatively, use InjectInputInProcess
+ * (com.android.cts.input.InjectInputInProcess) for in-process injection.
* </p>
* <p>
* The APIs exposed by this class operate across applications enabling a client
@@ -222,7 +228,8 @@ public final class UiAutomation {
private OnAccessibilityEventListener mOnAccessibilityEventListener;
- private boolean mWaitingForEventDelivery;
+ // Count the nested clients waiting for data delivery
+ private int mCurrentEventWatchersCount = 0;
private long mLastEventTimeMillis;
@@ -956,9 +963,17 @@ public final class UiAutomation {
* <strong>Note:</strong> It is caller's responsibility to recycle the event.
* </p>
*
- * @param event The event to inject.
- * @param sync Whether to inject the event synchronously.
- * @return Whether event injection succeeded.
+ * <p>
+ * <strong>Note:</strong> Avoid this method when injecting input events in CTS tests. Instead
+ * use uinput (com.android.cts.input.UinputDevice)
+ * or hid devices (com.android.cts.input.HidDevice), as they provide a more accurate simulation
+ * of real device behavior. Alternatively, InjectInputInProcess
+ * (com.android.cts.input.InjectInputProcess) can be used for in-process injection.
+ * </p>
+ *
+ * @param event the event to inject
+ * @param sync whether to inject the event synchronously
+ * @return {@code true} if event injection succeeded
*/
public boolean injectInputEvent(InputEvent event, boolean sync) {
return injectInputEvent(event, sync, true /* waitForAnimations */);
@@ -971,15 +986,21 @@ public final class UiAutomation {
* <strong>Note:</strong> It is caller's responsibility to recycle the event.
* </p>
*
- * @param event The event to inject.
- * @param sync Whether to inject the event synchronously.
- * @param waitForAnimations Whether to wait for all window container animations and surface
- * operations to complete.
- * @return Whether event injection succeeded.
+ * @param event the event to inject
+ * @param sync whether to inject the event synchronously.
+ * @param waitForAnimations whether to wait for all window container animations and surface
+ * operations to complete
+ * @return {@code true} if event injection succeeded
*
+ * @deprecated for CTS tests prefer inject input events using uinput
+ * (com.android.cts.input.UinputDevice) or hid devices (com.android.cts.input.HidDevice).
+ * Alternatively, InjectInputInProcess (com.android.cts.input.InjectInputProcess) can be used
+ * for in-process injection.
* @hide
*/
@TestApi
+ @Deprecated // Deprecated for CTS tests
+ @FlaggedApi(FLAG_DEPRECATE_UIAUTOMATION_INPUT_INJECTION)
public boolean injectInputEvent(@NonNull InputEvent event, boolean sync,
boolean waitForAnimations) {
try {
@@ -1002,9 +1023,15 @@ public final class UiAutomation {
* Events injected to the input subsystem using the standard {@link #injectInputEvent} method
* skip the accessibility input filter to avoid feedback loops.
*
+ * @deprecated for CTS tests prefer inject input events using uinput
+ * (com.android.cts.input.UinputDevice) or hid devices (com.android.cts.input.HidDevice).
+ * Alternatively, InjectInputInProcess (com.android.cts.input.InjectInputProcess) can be used
+ * for in-process injection.
* @hide
*/
@TestApi
+ @Deprecated
+ @FlaggedApi(FLAG_DEPRECATE_UIAUTOMATION_INPUT_INJECTION)
public void injectInputEventToInputFilter(@NonNull InputEvent event) {
try {
mUiAutomationConnection.injectInputEventToInputFilter(event);
@@ -1132,75 +1159,75 @@ public final class UiAutomation {
*/
public AccessibilityEvent executeAndWaitForEvent(Runnable command,
AccessibilityEventFilter filter, long timeoutMillis) throws TimeoutException {
+ int watchersDepth;
+ // Track events added after the index for this command, it is to support nested calls.
+ // This doesn't support concurrent calls correctly.
+ int eventQueueStartIndex;
+ final long executionStartTimeMillis;
+
// Acquire the lock and prepare for receiving events.
synchronized (mLock) {
throwIfNotConnectedLocked();
- mEventQueue.clear();
- // Prepare to wait for an event.
- mWaitingForEventDelivery = true;
+ watchersDepth = ++mCurrentEventWatchersCount;
+ executionStartTimeMillis = SystemClock.uptimeMillis();
+ eventQueueStartIndex = mEventQueue.size();
+ }
+ if (VERBOSE) {
+ Log.v(LOG_TAG, "executeAndWaitForEvent starts at depth=" + watchersDepth + ", "
+ + "command=" + command + ", filter=" + filter + ", timeout=" + timeoutMillis);
}
- // Note: We have to release the lock since calling out with this lock held
- // can bite. We will correctly filter out events from other interactions,
- // so starting to collect events before running the action is just fine.
-
- // We will ignore events from previous interactions.
- final long executionStartTimeMillis = SystemClock.uptimeMillis();
- // Execute the command *without* the lock being held.
- command.run();
-
- List<AccessibilityEvent> receivedEvents = new ArrayList<>();
-
- // Acquire the lock and wait for the event.
try {
- // Wait for the event.
- final long startTimeMillis = SystemClock.uptimeMillis();
- while (true) {
- List<AccessibilityEvent> localEvents = new ArrayList<>();
- synchronized (mLock) {
- localEvents.addAll(mEventQueue);
- mEventQueue.clear();
- }
- // Drain the event queue
- while (!localEvents.isEmpty()) {
- AccessibilityEvent event = localEvents.remove(0);
- // Ignore events from previous interactions.
- if (event.getEventTime() < executionStartTimeMillis) {
- continue;
- }
- if (filter.accept(event)) {
- return event;
- }
- receivedEvents.add(event);
- }
- // Check if timed out and if not wait.
- final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis;
- final long remainingTimeMillis = timeoutMillis - elapsedTimeMillis;
- if (remainingTimeMillis <= 0) {
- throw new TimeoutException("Expected event not received within: "
- + timeoutMillis + " ms among: " + receivedEvents);
+ // Execute the command *without* the lock being held.
+ command.run();
+ synchronized (mLock) {
+ if (watchersDepth != mCurrentEventWatchersCount) {
+ throw new IllegalStateException("Unexpected event watchers count, expected: "
+ + watchersDepth + ", actual: " + mCurrentEventWatchersCount);
}
+ }
+ final long startTimeMillis = SystemClock.uptimeMillis();
+ List<AccessibilityEvent> receivedEvents = new ArrayList<>();
+ long elapsedTimeMillis = 0;
+ int currentQueueSize = 0;
+ while (timeoutMillis > elapsedTimeMillis) {
+ AccessibilityEvent event = null;
synchronized (mLock) {
- if (mEventQueue.isEmpty()) {
+ currentQueueSize = mEventQueue.size();
+ if (eventQueueStartIndex < currentQueueSize) {
+ event = mEventQueue.get(eventQueueStartIndex++);
+ } else {
try {
- mLock.wait(remainingTimeMillis);
+ mLock.wait(timeoutMillis - elapsedTimeMillis);
} catch (InterruptedException ie) {
/* ignore */
}
}
}
+ elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis;
+ if (event == null || event.getEventTime() < executionStartTimeMillis) {
+ continue;
+ }
+ if (filter.accept(event)) {
+ return event;
+ }
+ receivedEvents.add(event);
}
- } finally {
- int size = receivedEvents.size();
- for (int i = 0; i < size; i++) {
- receivedEvents.get(i).recycle();
+ if (eventQueueStartIndex < currentQueueSize) {
+ Log.w(LOG_TAG, "Timed out before reading all events from the queue");
}
-
+ throw new TimeoutException("Expected event not received before timeout, events: "
+ + receivedEvents);
+ } finally {
synchronized (mLock) {
- mWaitingForEventDelivery = false;
- mEventQueue.clear();
+ if (--mCurrentEventWatchersCount == 0) {
+ mEventQueue.clear();
+ }
mLock.notifyAll();
}
+ if (VERBOSE) {
+ Log.v(LOG_TAG, "executeAndWaitForEvent ends at depth=" + watchersDepth);
+ }
}
}
@@ -1957,7 +1984,7 @@ public final class UiAutomation {
// It is not guaranteed that the accessibility framework sends events by the
// order of event timestamp.
mLastEventTimeMillis = Math.max(mLastEventTimeMillis, event.getEventTime());
- if (mWaitingForEventDelivery) {
+ if (mCurrentEventWatchersCount > 0) {
mEventQueue.add(AccessibilityEvent.obtain(event));
}
mLock.notifyAll();
diff --git a/core/java/android/app/notification.aconfig b/core/java/android/app/notification.aconfig
index 914ca73f1ce4..733a348aa825 100644
--- a/core/java/android/app/notification.aconfig
+++ b/core/java/android/app/notification.aconfig
@@ -62,13 +62,6 @@ flag {
}
flag {
- name: "modes_ui_test"
- namespace: "systemui"
- description: "Guards new CTS tests for Modes; dependent on flags modes_api and modes_ui"
- bug: "360862012"
-}
-
-flag {
name: "modes_hsum"
namespace: "systemui"
description: "Fixes for modes (and DND/Zen in general) with HSUM or secondary users"
@@ -310,3 +303,17 @@ flag {
description: "removes sbnholder from NLS"
bug: "362981561"
}
+
+flag {
+ name: "nm_summarization"
+ namespace: "systemui"
+ description: "Allows the NAS to summarize notifications"
+ bug: "390417189"
+}
+
+flag {
+ name: "nm_summarization_ui"
+ namespace: "systemui"
+ description: "Shows summarized notifications in the UI"
+ bug: "390217880"
+}
diff --git a/core/java/android/app/supervision/ISupervisionManager.aidl b/core/java/android/app/supervision/ISupervisionManager.aidl
index 4598421eb3bc..c3f3b1ced33c 100644
--- a/core/java/android/app/supervision/ISupervisionManager.aidl
+++ b/core/java/android/app/supervision/ISupervisionManager.aidl
@@ -22,4 +22,5 @@ package android.app.supervision;
*/
interface ISupervisionManager {
boolean isSupervisionEnabledForUser(int userId);
+ String getActiveSupervisionAppPackage(int userId);
}
diff --git a/core/java/android/app/supervision/SupervisionManager.java b/core/java/android/app/supervision/SupervisionManager.java
index 92241f3634e8..12432ddd0eb9 100644
--- a/core/java/android/app/supervision/SupervisionManager.java
+++ b/core/java/android/app/supervision/SupervisionManager.java
@@ -16,6 +16,7 @@
package android.app.supervision;
+import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SystemService;
import android.annotation.UserHandleAware;
@@ -98,4 +99,20 @@ public class SupervisionManager {
throw e.rethrowFromSystemServer();
}
}
+
+ /**
+ * Returns the package name of the app that is acting as the active supervision app or null if
+ * supervision is disabled.
+ *
+ * @hide
+ */
+ @UserHandleAware
+ @Nullable
+ public String getActiveSupervisionAppPackage() {
+ try {
+ return mService.getActiveSupervisionAppPackage(mContext.getUserId());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
}
diff --git a/core/java/android/app/supervision/flags.aconfig b/core/java/android/app/supervision/flags.aconfig
index 18182b804627..232883cbfe00 100644
--- a/core/java/android/app/supervision/flags.aconfig
+++ b/core/java/android/app/supervision/flags.aconfig
@@ -48,3 +48,19 @@ flag {
description: "Flag that enables the Supervision settings screen with top-level Android settings entry point"
bug: "383404606"
}
+
+flag {
+ name: "enable_app_approval"
+ is_exported: true
+ namespace: "supervision"
+ description: "Flag to enable the App Approval settings in Android settings UI"
+ bug: "390185393"
+}
+
+flag {
+ name: "enable_supervision_pin_recovery_screen"
+ is_exported: true
+ namespace: "supervision"
+ description: "Flag that enables the Supervision pin recovery screen with Supervision settings entry point"
+ bug: "390500290"
+}
diff --git a/core/java/android/appwidget/AppWidgetHostView.java b/core/java/android/appwidget/AppWidgetHostView.java
index df1028e9e04c..b9b5c6a8bbc3 100644
--- a/core/java/android/appwidget/AppWidgetHostView.java
+++ b/core/java/android/appwidget/AppWidgetHostView.java
@@ -20,7 +20,6 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.Activity;
import android.app.ActivityOptions;
-import android.app.LoadedApk;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.ComponentName;
import android.content.Context;
@@ -753,9 +752,6 @@ public class AppWidgetHostView extends FrameLayout implements AppWidgetHost.AppW
*/
protected Context getRemoteContextEnsuringCorrectCachedApkPath() {
try {
- ApplicationInfo expectedAppInfo = mInfo.providerInfo.applicationInfo;
- LoadedApk.checkAndUpdateApkPaths(expectedAppInfo);
- // Return if cloned successfully, otherwise default
Context newContext = mContext.createApplicationContext(
mInfo.providerInfo.applicationInfo,
Context.CONTEXT_RESTRICTED);
diff --git a/core/java/android/companion/virtual/flags/flags.aconfig b/core/java/android/companion/virtual/flags/flags.aconfig
index 6da2a073ec19..1cf42820f356 100644
--- a/core/java/android/companion/virtual/flags/flags.aconfig
+++ b/core/java/android/companion/virtual/flags/flags.aconfig
@@ -18,13 +18,6 @@ flag {
}
flag {
- namespace: "virtual_devices"
- name: "media_projection_keyguard_restrictions"
- description: "Auto-stop MP when the device locks"
- bug: "348335290"
-}
-
-flag {
namespace: "virtual_devices"
name: "virtual_display_insets"
description: "APIs for specifying virtual display insets (via cutout)"
diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java
index a126363237b8..efcaa0ea6f07 100644
--- a/core/java/android/content/ContentResolver.java
+++ b/core/java/android/content/ContentResolver.java
@@ -2722,10 +2722,10 @@ public abstract class ContentResolver implements ContentInterface {
/** @hide - designated user version */
@UnsupportedAppUsage
- public final void registerContentObserver(Uri uri, boolean notifyForDescendents,
+ public final void registerContentObserver(Uri uri, boolean notifyForDescendants,
ContentObserver observer, @UserIdInt int userHandle) {
try {
- getContentService().registerContentObserver(uri, notifyForDescendents,
+ getContentService().registerContentObserver(uri, notifyForDescendants,
observer.getContentObserver(), userHandle, mTargetSdkVersion);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 8d54673df74c..d811c0791c6c 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -4964,10 +4964,10 @@ public abstract class Context {
/**
* Use with {@link #getSystemService(String)} to retrieve a {@link android.net.TetheringManager}
* for managing tethering functions.
- * @hide
+ *
* @see android.net.TetheringManager
*/
- @SystemApi
+ @SuppressLint("UnflaggedApi")
public static final String TETHERING_SERVICE = "tethering";
/**
diff --git a/core/java/android/content/pm/UserInfo.java b/core/java/android/content/pm/UserInfo.java
index 8a3a3ad56a7b..582a1a9442ce 100644
--- a/core/java/android/content/pm/UserInfo.java
+++ b/core/java/android/content/pm/UserInfo.java
@@ -183,6 +183,12 @@ public class UserInfo implements Parcelable {
*
* <p>This is not necessarily the system user. For example, it will not be the system user on
* devices for which {@link UserManager#isHeadlessSystemUserMode()} returns true.
+ *
+ * <p>NB: Features should ideally not limit functionality to the main user. Ideally, they
+ * should either work for all users or for all admin users. If a feature should only work for
+ * select users, its determination of which user should be done intelligently or be
+ * customizable. Not all devices support a main user, and the idea of singling out one user as
+ * special is contrary to overall multiuser goals.
*/
public static final int FLAG_MAIN = 0x00004000;
diff --git a/core/java/android/credentials/flags.aconfig b/core/java/android/credentials/flags.aconfig
index 430ed2b68342..449423f1ea1f 100644
--- a/core/java/android/credentials/flags.aconfig
+++ b/core/java/android/credentials/flags.aconfig
@@ -133,6 +133,7 @@ flag {
metadata {
purpose: PURPOSE_BUGFIX
}
+ is_exported: true
}
flag {
diff --git a/core/java/android/hardware/biometrics/ParentalControlsUtilsInternal.java b/core/java/android/hardware/biometrics/ParentalControlsUtilsInternal.java
index de93234445ca..d3fb93588762 100644
--- a/core/java/android/hardware/biometrics/ParentalControlsUtilsInternal.java
+++ b/core/java/android/hardware/biometrics/ParentalControlsUtilsInternal.java
@@ -19,6 +19,7 @@ package android.hardware.biometrics;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.admin.DevicePolicyManager;
+import android.app.supervision.SupervisionManager;
import android.content.ComponentName;
import android.content.Context;
import android.os.Build;
@@ -55,27 +56,44 @@ public class ParentalControlsUtilsInternal {
return null;
}
- public static boolean parentConsentRequired(@NonNull Context context,
- @NonNull DevicePolicyManager dpm, @BiometricAuthenticator.Modality int modality,
+ /** @return true if parental consent is required in order for biometric sensors to be used. */
+ public static boolean parentConsentRequired(
+ @NonNull Context context,
+ @NonNull DevicePolicyManager dpm,
+ @Nullable SupervisionManager sm,
+ @BiometricAuthenticator.Modality int modality,
@NonNull UserHandle userHandle) {
if (getTestComponentName(context, userHandle.getIdentifier()) != null) {
return true;
}
- return parentConsentRequired(dpm, modality, userHandle);
+ return parentConsentRequired(dpm, sm, modality, userHandle);
}
/**
* @return true if parental consent is required in order for biometric sensors to be used.
*/
- public static boolean parentConsentRequired(@NonNull DevicePolicyManager dpm,
- @BiometricAuthenticator.Modality int modality, @NonNull UserHandle userHandle) {
- final ComponentName cn = getSupervisionComponentName(dpm, userHandle);
- if (cn == null) {
- return false;
+ public static boolean parentConsentRequired(
+ @NonNull DevicePolicyManager dpm,
+ @Nullable SupervisionManager sm,
+ @BiometricAuthenticator.Modality int modality,
+ @NonNull UserHandle userHandle) {
+ final int keyguardDisabledFeatures;
+
+ if (android.app.supervision.flags.Flags.deprecateDpmSupervisionApis()) {
+ if (sm != null && !sm.isSupervisionEnabledForUser(userHandle.getIdentifier())) {
+ return false;
+ }
+ // Check for keyguard features disabled by any admin.
+ keyguardDisabledFeatures = dpm.getKeyguardDisabledFeatures(/* admin= */ null);
+ } else {
+ final ComponentName cn = getSupervisionComponentName(dpm, userHandle);
+ if (cn == null) {
+ return false;
+ }
+ keyguardDisabledFeatures = dpm.getKeyguardDisabledFeatures(cn);
}
- final int keyguardDisabledFeatures = dpm.getKeyguardDisabledFeatures(cn);
final boolean dpmFpDisabled = containsFlag(keyguardDisabledFeatures,
DevicePolicyManager.KEYGUARD_DISABLE_FINGERPRINT);
final boolean dpmFaceDisabled = containsFlag(keyguardDisabledFeatures,
@@ -97,7 +115,9 @@ public class ParentalControlsUtilsInternal {
return consentRequired;
}
+ /** @deprecated Use {@link SupervisionManager} to check for supervision. */
@Nullable
+ @Deprecated
public static ComponentName getSupervisionComponentName(@NonNull DevicePolicyManager dpm,
@NonNull UserHandle userHandle) {
return dpm.getProfileOwnerOrDeviceOwnerSupervisionComponent(userHandle);
diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java
index 5533a640b9d8..210653bb41e5 100644
--- a/core/java/android/hardware/camera2/CameraCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraCharacteristics.java
@@ -5256,9 +5256,6 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri
* <p>DYNAMIC_RANGE_PROFILE: {STANDARD, HLG10}</p>
* </li>
* </ul>
- * <p>All of the above configurations can be set up with a SessionConfiguration. The list of
- * OutputConfiguration contains the stream configurations and DYNAMIC_RANGE_PROFILE, and
- * the AE_TARGET_FPS_RANGE and VIDEO_STABILIZATION_MODE are set as session parameters.</p>
* <p>When set to BAKLAVA, the additional stream combinations below are verified
* by the compliance tests:</p>
* <table>
@@ -5268,6 +5265,8 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri
* <th style="text-align: center;">Size</th>
* <th style="text-align: center;">Target 2</th>
* <th style="text-align: center;">Size</th>
+ * <th style="text-align: center;">Target 3</th>
+ * <th style="text-align: center;">Size</th>
* </tr>
* </thead>
* <tbody>
@@ -5276,15 +5275,34 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri
* <td style="text-align: center;">S1080P</td>
* <td style="text-align: center;">PRIV</td>
* <td style="text-align: center;">S1080P</td>
+ * <td style="text-align: center;"></td>
+ * <td style="text-align: center;"></td>
* </tr>
* <tr>
* <td style="text-align: center;">PRIV</td>
* <td style="text-align: center;">S1080P</td>
* <td style="text-align: center;">PRIV</td>
* <td style="text-align: center;">S1440P</td>
+ * <td style="text-align: center;"></td>
+ * <td style="text-align: center;"></td>
+ * </tr>
+ * <tr>
+ * <td style="text-align: center;">PRIV</td>
+ * <td style="text-align: center;">S1080P</td>
+ * <td style="text-align: center;">YUV</td>
+ * <td style="text-align: center;">S1080P</td>
+ * <td style="text-align: center;">S1080P</td>
+ * <td style="text-align: center;">PRIV</td>
* </tr>
* </tbody>
* </table>
+ * <ul>
+ * <li>VIDEO_STABILIZATION_MODE: {OFF, ON} for the newly added stream combinations given the
+ * presence of dedicated video stream</li>
+ * </ul>
+ * <p>All of the above configurations can be set up with a SessionConfiguration. The list of
+ * OutputConfiguration contains the stream configurations and DYNAMIC_RANGE_PROFILE, and
+ * the AE_TARGET_FPS_RANGE and VIDEO_STABILIZATION_MODE are set as session parameters.</p>
* <p>This key is available on all devices.</p>
*/
@PublicKey
diff --git a/core/java/android/hardware/camera2/params/SharedSessionConfiguration.java b/core/java/android/hardware/camera2/params/SharedSessionConfiguration.java
index 365f870ba22d..b40c7d35c06b 100644
--- a/core/java/android/hardware/camera2/params/SharedSessionConfiguration.java
+++ b/core/java/android/hardware/camera2/params/SharedSessionConfiguration.java
@@ -21,6 +21,7 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SuppressLint;
import android.annotation.SystemApi;
+import android.annotation.TestApi;
import android.graphics.ColorSpace;
import android.graphics.ImageFormat.Format;
import android.hardware.DataSpace.NamedDataSpace;
@@ -228,6 +229,7 @@ public final class SharedSessionConfiguration {
*
* @hide
*/
+ @TestApi
public SharedSessionConfiguration(int sharedColorSpace,
@NonNull long[] sharedOutputConfigurations) {
mColorSpace = sharedColorSpace;
diff --git a/core/java/android/hardware/input/input_framework.aconfig b/core/java/android/hardware/input/input_framework.aconfig
index 62126963cba4..79323bf2f2f7 100644
--- a/core/java/android/hardware/input/input_framework.aconfig
+++ b/core/java/android/hardware/input/input_framework.aconfig
@@ -225,3 +225,13 @@ flag {
description: "Removes modifiers from the original key event that activated the fallback, ensuring that only the intended fallback event is sent."
bug: "382545048"
}
+
+flag {
+ name: "abort_slow_multi_press"
+ namespace: "wear_frameworks"
+ description: "If a press that's a part of a multipress takes too long, the multipress gesture will be cancelled."
+ bug: "370095426"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/core/java/android/hardware/usb/OWNERS b/core/java/android/hardware/usb/OWNERS
index 37604bc2eb65..1de8a242acfc 100644
--- a/core/java/android/hardware/usb/OWNERS
+++ b/core/java/android/hardware/usb/OWNERS
@@ -1,7 +1,7 @@
# Bug component: 175220
-anothermark@google.com
+vmartensson@google.com
+nkapron@google.com
febinthattil@google.com
-aprasath@google.com
+shubhankarm@google.com
badhri@google.com
-kumarashishg@google.com \ No newline at end of file
diff --git a/core/java/android/inputmethodservice/NavigationBarController.java b/core/java/android/inputmethodservice/NavigationBarController.java
index 019ba0045916..f420b5d7b886 100644
--- a/core/java/android/inputmethodservice/NavigationBarController.java
+++ b/core/java/android/inputmethodservice/NavigationBarController.java
@@ -16,9 +16,9 @@
package android.inputmethodservice;
-import static android.app.StatusBarManager.NAVIGATION_HINT_BACK_ALT;
-import static android.app.StatusBarManager.NAVIGATION_HINT_IME_SHOWN;
-import static android.app.StatusBarManager.NAVIGATION_HINT_IME_SWITCHER_SHOWN;
+import static android.app.StatusBarManager.NAVBAR_BACK_DISMISS_IME;
+import static android.app.StatusBarManager.NAVBAR_IME_SWITCHER_BUTTON_VISIBLE;
+import static android.app.StatusBarManager.NAVBAR_IME_VISIBLE;
import static android.view.WindowInsets.Type.captionBar;
import static android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS;
@@ -242,11 +242,11 @@ final class NavigationBarController {
NavigationBarView.class::isInstance);
if (navigationBarView != null) {
// TODO(b/213337792): Support InputMethodService#setBackDisposition().
- // TODO(b/213337792): Set NAVIGATION_HINT_IME_SHOWN only when necessary.
- final int hints = NAVIGATION_HINT_BACK_ALT | NAVIGATION_HINT_IME_SHOWN
+ // TODO(b/213337792): Set NAVBAR_IME_VISIBLE only when necessary.
+ final int flags = NAVBAR_BACK_DISMISS_IME | NAVBAR_IME_VISIBLE
| (mShouldShowImeSwitcherWhenImeIsShown
- ? NAVIGATION_HINT_IME_SWITCHER_SHOWN : 0);
- navigationBarView.setNavigationIconHints(hints);
+ ? NAVBAR_IME_SWITCHER_BUTTON_VISIBLE : 0);
+ navigationBarView.setNavbarFlags(flags);
navigationBarView.prepareNavButtons(this);
}
} else {
@@ -515,11 +515,11 @@ final class NavigationBarController {
NavigationBarView.class::isInstance);
if (navigationBarView != null) {
// TODO(b/213337792): Support InputMethodService#setBackDisposition().
- // TODO(b/213337792): Set NAVIGATION_HINT_IME_SHOWN only when necessary.
- final int hints = NAVIGATION_HINT_BACK_ALT | NAVIGATION_HINT_IME_SHOWN
+ // TODO(b/213337792): Set NAVBAR_IME_VISIBLE only when necessary.
+ final int flags = NAVBAR_BACK_DISMISS_IME | NAVBAR_IME_VISIBLE
| (mShouldShowImeSwitcherWhenImeIsShown
- ? NAVIGATION_HINT_IME_SWITCHER_SHOWN : 0);
- navigationBarView.setNavigationIconHints(hints);
+ ? NAVBAR_IME_SWITCHER_BUTTON_VISIBLE : 0);
+ navigationBarView.setNavbarFlags(flags);
}
} else {
uninstallNavigationBarFrameIfNecessary();
diff --git a/core/java/android/inputmethodservice/navigationbar/NavigationBarView.java b/core/java/android/inputmethodservice/navigationbar/NavigationBarView.java
index e7e46a9482c8..960a5b33434a 100644
--- a/core/java/android/inputmethodservice/navigationbar/NavigationBarView.java
+++ b/core/java/android/inputmethodservice/navigationbar/NavigationBarView.java
@@ -16,6 +16,8 @@
package android.inputmethodservice.navigationbar;
+import static android.app.StatusBarManager.NAVBAR_BACK_DISMISS_IME;
+import static android.app.StatusBarManager.NAVBAR_IME_SWITCHER_BUTTON_VISIBLE;
import static android.inputmethodservice.navigationbar.NavigationBarConstants.DARK_MODE_ICON_COLOR_SINGLE_TONE;
import static android.inputmethodservice.navigationbar.NavigationBarConstants.LIGHT_MODE_ICON_COLOR_SINGLE_TONE;
import static android.inputmethodservice.navigationbar.NavigationBarConstants.NAVBAR_BACK_BUTTON_IME_OFFSET;
@@ -28,6 +30,7 @@ import android.annotation.DrawableRes;
import android.annotation.FloatRange;
import android.annotation.NonNull;
import android.app.StatusBarManager;
+import android.app.StatusBarManager.NavbarFlags;
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.Canvas;
@@ -63,7 +66,8 @@ public final class NavigationBarView extends FrameLayout {
private int mCurrentRotation = -1;
int mDisabledFlags = 0;
- int mNavigationIconHints = StatusBarManager.NAVIGATION_HINT_BACK_ALT;
+ @NavbarFlags
+ private int mNavbarFlags;
private final int mNavBarMode = NAV_BAR_MODE_GESTURAL;
private KeyButtonDrawable mBackIcon;
@@ -241,10 +245,9 @@ public final class NavigationBarView extends FrameLayout {
}
private void orientBackButton(KeyButtonDrawable drawable) {
- final boolean useAltBack =
- (mNavigationIconHints & StatusBarManager.NAVIGATION_HINT_BACK_ALT) != 0;
+ final boolean isBackDismissIme = (mNavbarFlags & NAVBAR_BACK_DISMISS_IME) != 0;
final boolean isRtl = mConfiguration.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
- float degrees = useAltBack ? (isRtl ? 90 : -90) : 0;
+ float degrees = isBackDismissIme ? (isRtl ? 90 : -90) : 0;
if (drawable.getRotation() == degrees) {
return;
}
@@ -256,7 +259,7 @@ public final class NavigationBarView extends FrameLayout {
// Animate the back button's rotation to the new degrees and only in portrait move up the
// back button to line up with the other buttons
- float targetY = useAltBack
+ float targetY = isBackDismissIme
? -dpToPx(NAVBAR_BACK_BUTTON_IME_OFFSET, getResources())
: 0;
ObjectAnimator navBarAnimator = ObjectAnimator.ofPropertyValuesHolder(drawable,
@@ -280,24 +283,26 @@ public final class NavigationBarView extends FrameLayout {
}
/**
- * Updates the navigation icons based on {@code hints}.
+ * Sets the navigation bar state flags.
*
- * @param hints bit flags defined in {@link StatusBarManager}.
+ * @param flags the navigation bar state flags.
*/
- public void setNavigationIconHints(int hints) {
- if (hints == mNavigationIconHints) return;
- final boolean newBackAlt = (hints & StatusBarManager.NAVIGATION_HINT_BACK_ALT) != 0;
- final boolean oldBackAlt =
- (mNavigationIconHints & StatusBarManager.NAVIGATION_HINT_BACK_ALT) != 0;
- if (newBackAlt != oldBackAlt) {
- //onBackAltChanged(newBackAlt);
+ public void setNavbarFlags(@NavbarFlags int flags) {
+ if (flags == mNavbarFlags) {
+ return;
+ }
+ final boolean backDismissIme = (flags & StatusBarManager.NAVBAR_BACK_DISMISS_IME) != 0;
+ final boolean oldBackDismissIme =
+ (mNavbarFlags & StatusBarManager.NAVBAR_BACK_DISMISS_IME) != 0;
+ if (backDismissIme != oldBackDismissIme) {
+ //onBackDismissImeChanged(backDismissIme);
}
if (DEBUG) {
- android.widget.Toast.makeText(getContext(), "Navigation icon hints = " + hints, 500)
+ android.widget.Toast.makeText(getContext(), "Navbar flags = " + flags, 500)
.show();
}
- mNavigationIconHints = hints;
+ mNavbarFlags = flags;
updateNavButtonIcons();
}
@@ -311,10 +316,12 @@ public final class NavigationBarView extends FrameLayout {
getImeSwitchButton().setImageDrawable(mImeSwitcherIcon);
- // Update IME button visibility, a11y and rotate button always overrides the appearance
- final boolean imeSwitcherVisible =
- (mNavigationIconHints & StatusBarManager.NAVIGATION_HINT_IME_SWITCHER_SHOWN) != 0;
- getImeSwitchButton().setVisibility(imeSwitcherVisible ? View.VISIBLE : View.INVISIBLE);
+ // Update IME switcher button visibility, a11y and rotate button always overrides
+ // the appearance.
+ final boolean isImeSwitcherButtonVisible =
+ (mNavbarFlags & NAVBAR_IME_SWITCHER_BUTTON_VISIBLE) != 0;
+ getImeSwitchButton()
+ .setVisibility(isImeSwitcherButtonVisible ? View.VISIBLE : View.INVISIBLE);
getBackButton().setVisibility(View.VISIBLE);
getHomeHandle().setVisibility(View.INVISIBLE);
diff --git a/core/java/android/os/BaseBundle.java b/core/java/android/os/BaseBundle.java
index 1041041b2a27..1cf293d46350 100644
--- a/core/java/android/os/BaseBundle.java
+++ b/core/java/android/os/BaseBundle.java
@@ -45,8 +45,7 @@ import java.util.function.BiFunction;
* {@link PersistableBundle} subclass.
*/
@android.ravenwood.annotation.RavenwoodKeepWholeClass
-@SuppressWarnings("HiddenSuperclass")
-public class BaseBundle implements Parcel.ClassLoaderProvider {
+public class BaseBundle {
/** @hide */
protected static final String TAG = "Bundle";
static final boolean DEBUG = false;
@@ -300,9 +299,8 @@ public class BaseBundle implements Parcel.ClassLoaderProvider {
/**
* Return the ClassLoader currently associated with this Bundle.
- * @hide
*/
- public ClassLoader getClassLoader() {
+ ClassLoader getClassLoader() {
return mClassLoader;
}
@@ -416,9 +414,6 @@ public class BaseBundle implements Parcel.ClassLoaderProvider {
if ((mFlags & Bundle.FLAG_VERIFY_TOKENS_PRESENT) != 0) {
Intent.maybeMarkAsMissingCreatorToken(object);
}
- } else if (object instanceof Bundle) {
- Bundle bundle = (Bundle) object;
- bundle.setClassLoaderSameAsContainerBundleWhenRetrievedFirstTime(this);
}
return (clazz != null) ? clazz.cast(object) : (T) object;
}
@@ -492,7 +487,7 @@ public class BaseBundle implements Parcel.ClassLoaderProvider {
int[] numLazyValues = new int[]{0};
try {
parcelledData.readArrayMap(map, count, !parcelledByNative,
- /* lazy */ ownsParcel, this, numLazyValues);
+ /* lazy */ ownsParcel, mClassLoader, numLazyValues);
} catch (BadParcelableException e) {
if (sShouldDefuse) {
Log.w(TAG, "Failed to parse Bundle, but defusing quietly", e);
diff --git a/core/java/android/os/BatteryUsageStats.java b/core/java/android/os/BatteryUsageStats.java
index f913fcfd56d4..86b8fad16275 100644
--- a/core/java/android/os/BatteryUsageStats.java
+++ b/core/java/android/os/BatteryUsageStats.java
@@ -161,6 +161,7 @@ public final class BatteryUsageStats implements Parcelable, Closeable {
private final List<UserBatteryConsumer> mUserBatteryConsumers;
private final AggregateBatteryConsumer[] mAggregateBatteryConsumers;
private final BatteryStatsHistory mBatteryStatsHistory;
+ private final long mPreferredHistoryDurationMs;
private final BatteryConsumer.BatteryConsumerDataLayout mBatteryConsumerDataLayout;
private CursorWindow mBatteryConsumersCursorWindow;
@@ -174,6 +175,7 @@ public final class BatteryUsageStats implements Parcelable, Closeable {
mDischargedPowerUpperBound = builder.mDischargedPowerUpperBoundMah;
mDischargeDurationMs = builder.mDischargeDurationMs;
mBatteryStatsHistory = builder.mBatteryStatsHistory;
+ mPreferredHistoryDurationMs = builder.mPreferredHistoryDurationMs;
mBatteryTimeRemainingMs = builder.mBatteryTimeRemainingMs;
mChargeTimeRemainingMs = builder.mChargeTimeRemainingMs;
mCustomPowerComponentNames = builder.mCustomPowerComponentNames;
@@ -402,8 +404,10 @@ public final class BatteryUsageStats implements Parcelable, Closeable {
if (source.readBoolean()) {
mBatteryStatsHistory = BatteryStatsHistory.createFromBatteryUsageStatsParcel(source);
+ mPreferredHistoryDurationMs = source.readLong();
} else {
mBatteryStatsHistory = null;
+ mPreferredHistoryDurationMs = 0;
}
}
@@ -428,7 +432,7 @@ public final class BatteryUsageStats implements Parcelable, Closeable {
if (mBatteryStatsHistory != null) {
dest.writeBoolean(true);
- mBatteryStatsHistory.writeToBatteryUsageStatsParcel(dest);
+ mBatteryStatsHistory.writeToBatteryUsageStatsParcel(dest, mPreferredHistoryDurationMs);
} else {
dest.writeBoolean(false);
}
@@ -919,6 +923,7 @@ public final class BatteryUsageStats implements Parcelable, Closeable {
private final SparseArray<UserBatteryConsumer.Builder> mUserBatteryConsumerBuilders =
new SparseArray<>();
private BatteryStatsHistory mBatteryStatsHistory;
+ private long mPreferredHistoryDurationMs;
public Builder(@NonNull String[] customPowerComponentNames) {
this(customPowerComponentNames, false, false, false, 0);
@@ -1092,8 +1097,10 @@ public final class BatteryUsageStats implements Parcelable, Closeable {
* Sets the parceled recent history.
*/
@NonNull
- public Builder setBatteryHistory(BatteryStatsHistory batteryStatsHistory) {
+ public Builder setBatteryHistory(BatteryStatsHistory batteryStatsHistory,
+ long preferredHistoryDurationMs) {
mBatteryStatsHistory = batteryStatsHistory;
+ mPreferredHistoryDurationMs = preferredHistoryDurationMs;
return this;
}
diff --git a/core/java/android/os/BatteryUsageStatsQuery.java b/core/java/android/os/BatteryUsageStatsQuery.java
index 6e67578fadc8..5aed39bd8fa6 100644
--- a/core/java/android/os/BatteryUsageStatsQuery.java
+++ b/core/java/android/os/BatteryUsageStatsQuery.java
@@ -25,6 +25,7 @@ import com.android.internal.os.MonotonicClock;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Arrays;
+import java.util.concurrent.TimeUnit;
/**
* Query parameters for the {@link BatteryStatsManager#getBatteryUsageStats()} call.
@@ -77,6 +78,7 @@ public final class BatteryUsageStatsQuery implements Parcelable {
public static final int FLAG_BATTERY_USAGE_STATS_ACCUMULATED = 0x0080;
private static final long DEFAULT_MAX_STATS_AGE_MS = 5 * 60 * 1000;
+ private static final long DEFAULT_PREFERRED_HISTORY_DURATION_MS = TimeUnit.HOURS.toMillis(2);
private final int mFlags;
@NonNull
@@ -89,6 +91,7 @@ public final class BatteryUsageStatsQuery implements Parcelable {
private long mMonotonicEndTime;
private final double mMinConsumedPowerThreshold;
private final @BatteryConsumer.PowerComponentId int[] mPowerComponents;
+ private final long mPreferredHistoryDurationMs;
private BatteryUsageStatsQuery(@NonNull Builder builder) {
mFlags = builder.mFlags;
@@ -101,6 +104,7 @@ public final class BatteryUsageStatsQuery implements Parcelable {
mMonotonicStartTime = builder.mMonotonicStartTime;
mMonotonicEndTime = builder.mMonotonicEndTime;
mPowerComponents = builder.mPowerComponents;
+ mPreferredHistoryDurationMs = builder.mPreferredHistoryDurationMs;
}
@BatteryUsageStatsFlags
@@ -197,6 +201,13 @@ public final class BatteryUsageStatsQuery implements Parcelable {
return mAggregatedToTimestamp;
}
+ /**
+ * Returns the preferred duration of battery history (tail) to be included in the query result.
+ */
+ public long getPreferredHistoryDurationMs() {
+ return mPreferredHistoryDurationMs;
+ }
+
@Override
public String toString() {
return "BatteryUsageStatsQuery{"
@@ -209,6 +220,7 @@ public final class BatteryUsageStatsQuery implements Parcelable {
+ ", mMonotonicEndTime=" + mMonotonicEndTime
+ ", mMinConsumedPowerThreshold=" + mMinConsumedPowerThreshold
+ ", mPowerComponents=" + Arrays.toString(mPowerComponents)
+ + ", mMaxHistoryDurationMs=" + mPreferredHistoryDurationMs
+ '}';
}
@@ -223,6 +235,7 @@ public final class BatteryUsageStatsQuery implements Parcelable {
mAggregatedFromTimestamp = in.readLong();
mAggregatedToTimestamp = in.readLong();
mPowerComponents = in.createIntArray();
+ mPreferredHistoryDurationMs = in.readLong();
}
@Override
@@ -237,6 +250,7 @@ public final class BatteryUsageStatsQuery implements Parcelable {
dest.writeLong(mAggregatedFromTimestamp);
dest.writeLong(mAggregatedToTimestamp);
dest.writeIntArray(mPowerComponents);
+ dest.writeLong(mPreferredHistoryDurationMs);
}
@Override
@@ -271,6 +285,7 @@ public final class BatteryUsageStatsQuery implements Parcelable {
private long mAggregateToTimestamp;
private double mMinConsumedPowerThreshold = 0;
private @BatteryConsumer.PowerComponentId int[] mPowerComponents;
+ private long mPreferredHistoryDurationMs = DEFAULT_PREFERRED_HISTORY_DURATION_MS;
/**
* Builds a read-only BatteryUsageStatsQuery object.
@@ -311,6 +326,16 @@ public final class BatteryUsageStatsQuery implements Parcelable {
}
/**
+ * Set the preferred amount of battery history to be included in the result, provided
+ * that `includeBatteryHistory` is also called. The actual amount of history included in
+ * the result may vary for performance reasons and may exceed the specified preference.
+ */
+ public Builder setPreferredHistoryDurationMs(long preferredHistoryDurationMs) {
+ mPreferredHistoryDurationMs = preferredHistoryDurationMs;
+ return this;
+ }
+
+ /**
* Requests that per-process state data be included in the BatteryUsageStats, if
* available. Check {@link BatteryUsageStats#isProcessStateDataIncluded()} on the result
* to see if the data is available.
diff --git a/core/java/android/os/Binder.java b/core/java/android/os/Binder.java
index ee62dea7f9e5..6b1e918a3c47 100644
--- a/core/java/android/os/Binder.java
+++ b/core/java/android/os/Binder.java
@@ -149,6 +149,11 @@ public class Binder implements IBinder {
private static volatile boolean sStackTrackingEnabled = false;
/**
+ * The extension binder object
+ */
+ private IBinder mExtension = null;
+
+ /**
* Enable Binder IPC stack tracking. If enabled, every binder transaction will be logged to
* {@link TransactionTracker}.
*
@@ -1237,7 +1242,9 @@ public class Binder implements IBinder {
/** @hide */
@Override
- public final native @Nullable IBinder getExtension();
+ public final @Nullable IBinder getExtension() {
+ return mExtension;
+ }
/**
* Set the binder extension.
@@ -1245,7 +1252,12 @@ public class Binder implements IBinder {
*
* @hide
*/
- public final native void setExtension(@Nullable IBinder extension);
+ public final void setExtension(@Nullable IBinder extension) {
+ mExtension = extension;
+ setExtensionNative(extension);
+ }
+
+ private final native void setExtensionNative(@Nullable IBinder extension);
/**
* Default implementation rewinds the parcels and calls onTransact. On
diff --git a/core/java/android/os/Bundle.java b/core/java/android/os/Bundle.java
index 55bfd451d97a..819d58d9f059 100644
--- a/core/java/android/os/Bundle.java
+++ b/core/java/android/os/Bundle.java
@@ -141,8 +141,6 @@ public final class Bundle extends BaseBundle implements Cloneable, Parcelable {
STRIPPED.putInt("STRIPPED", 1);
}
- private boolean isFirstRetrievedFromABundle = false;
-
/**
* Constructs a new, empty Bundle.
*/
@@ -1022,9 +1020,7 @@ public final class Bundle extends BaseBundle implements Cloneable, Parcelable {
return null;
}
try {
- Bundle bundle = (Bundle) o;
- bundle.setClassLoaderSameAsContainerBundleWhenRetrievedFirstTime(this);
- return bundle;
+ return (Bundle) o;
} catch (ClassCastException e) {
typeWarning(key, o, "Bundle", e);
return null;
@@ -1032,21 +1028,6 @@ public final class Bundle extends BaseBundle implements Cloneable, Parcelable {
}
/**
- * Set the ClassLoader of a bundle to its container bundle. This is necessary so that when a
- * bundle's ClassLoader is changed, it can be propagated to its children. Do this only when it
- * is retrieved from the container bundle first time though. Once it is accessed outside of its
- * container, its ClassLoader should no longer be changed by its container anymore.
- *
- * @param containerBundle the bundle this bundle is retrieved from.
- */
- void setClassLoaderSameAsContainerBundleWhenRetrievedFirstTime(BaseBundle containerBundle) {
- if (!isFirstRetrievedFromABundle) {
- setClassLoader(containerBundle.getClassLoader());
- isFirstRetrievedFromABundle = true;
- }
- }
-
- /**
* Returns the value associated with the given key, or {@code null} if
* no mapping of the desired type exists for the given key or a {@code null}
* value is explicitly associated with the key.
diff --git a/core/java/android/os/CombinedMessageQueue/MessageQueue.java b/core/java/android/os/CombinedMessageQueue/MessageQueue.java
index 50b621c46778..877f130a8b5a 100644
--- a/core/java/android/os/CombinedMessageQueue/MessageQueue.java
+++ b/core/java/android/os/CombinedMessageQueue/MessageQueue.java
@@ -39,7 +39,6 @@ import android.util.proto.ProtoOutputStream;
import com.android.internal.ravenwood.RavenwoodEnvironment;
import dalvik.annotation.optimization.NeverCompile;
-import dalvik.annotation.optimization.NeverInline;
import java.io.FileDescriptor;
import java.lang.annotation.Retention;
@@ -238,7 +237,6 @@ public final class MessageQueue {
private final MatchDeliverableMessages mMatchDeliverableMessages =
new MatchDeliverableMessages();
- @NeverInline
private boolean isIdleConcurrent() {
final long now = SystemClock.uptimeMillis();
@@ -269,7 +267,6 @@ public final class MessageQueue {
return true;
}
- @NeverInline
private boolean isIdleLegacy() {
synchronized (this) {
final long now = SystemClock.uptimeMillis();
@@ -292,14 +289,12 @@ public final class MessageQueue {
}
}
- @NeverInline
private void addIdleHandlerConcurrent(@NonNull IdleHandler handler) {
synchronized (mIdleHandlersLock) {
mIdleHandlers.add(handler);
}
}
- @NeverInline
private void addIdleHandlerLegacy(@NonNull IdleHandler handler) {
synchronized (this) {
mIdleHandlers.add(handler);
@@ -326,15 +321,11 @@ public final class MessageQueue {
addIdleHandlerLegacy(handler);
}
}
-
- @NeverInline
private void removeIdleHandlerConcurrent(@NonNull IdleHandler handler) {
synchronized (mIdleHandlersLock) {
mIdleHandlers.remove(handler);
}
}
-
- @NeverInline
private void removeIdleHandlerLegacy(@NonNull IdleHandler handler) {
synchronized (this) {
mIdleHandlers.remove(handler);
@@ -358,14 +349,12 @@ public final class MessageQueue {
}
}
- @NeverInline
private boolean isPollingConcurrent() {
// If the loop is quitting then it must not be idling.
// We can assume mPtr != 0 when sQuitting is false.
return !((boolean) sQuitting.getVolatile(this)) && nativeIsPolling(mPtr);
}
- @NeverInline
private boolean isPollingLegacy() {
synchronized (this) {
return isPollingLocked();
@@ -396,7 +385,6 @@ public final class MessageQueue {
// We can assume mPtr != 0 when mQuitting is false.
return !mQuitting && nativeIsPolling(mPtr);
}
- @NeverInline
private void addOnFileDescriptorEventListenerConcurrent(@NonNull FileDescriptor fd,
@OnFileDescriptorEventListener.Events int events,
@NonNull OnFileDescriptorEventListener listener) {
@@ -405,7 +393,6 @@ public final class MessageQueue {
}
}
- @NeverInline
private void addOnFileDescriptorEventListenerLegacy(@NonNull FileDescriptor fd,
@OnFileDescriptorEventListener.Events int events,
@NonNull OnFileDescriptorEventListener listener) {
@@ -455,14 +442,12 @@ public final class MessageQueue {
}
}
- @NeverInline
private void removeOnFileDescriptorEventListenerConcurrent(@NonNull FileDescriptor fd) {
synchronized (mFileDescriptorRecordsLock) {
updateOnFileDescriptorEventListenerLocked(fd, 0, null);
}
}
- @NeverInline
private void removeOnFileDescriptorEventListenerLegacy(@NonNull FileDescriptor fd) {
synchronized (this) {
updateOnFileDescriptorEventListenerLocked(fd, 0, null);
@@ -616,7 +601,7 @@ public final class MessageQueue {
/* This is only read/written from the Looper thread. For use with Concurrent MQ */
private int mNextPollTimeoutMillis;
private boolean mMessageDirectlyQueued;
- private Message nextMessage(boolean peek) {
+ private Message nextMessage(boolean peek, boolean returnEarliest) {
int i = 0;
while (true) {
@@ -693,7 +678,7 @@ public final class MessageQueue {
* If we have a barrier we should return the async node (if it exists and is ready)
*/
if (msgNode != null && msgNode.isBarrier()) {
- if (asyncMsgNode != null && now >= asyncMsgNode.getWhen()) {
+ if (asyncMsgNode != null && (returnEarliest || now >= asyncMsgNode.getWhen())) {
found = asyncMsgNode;
} else {
next = asyncMsgNode;
@@ -707,7 +692,7 @@ public final class MessageQueue {
earliest = pickEarliestNode(msgNode, asyncMsgNode);
if (earliest != null) {
- if (now >= earliest.getWhen()) {
+ if (returnEarliest || now >= earliest.getWhen()) {
found = earliest;
} else {
next = earliest;
@@ -796,7 +781,6 @@ public final class MessageQueue {
}
}
- @NeverInline
private Message nextConcurrent() {
final long ptr = mPtr;
if (ptr == 0) {
@@ -813,7 +797,7 @@ public final class MessageQueue {
mMessageDirectlyQueued = false;
nativePollOnce(ptr, mNextPollTimeoutMillis);
- Message msg = nextMessage(false);
+ Message msg = nextMessage(false, false);
if (msg != null) {
msg.markInUse();
return msg;
@@ -871,7 +855,6 @@ public final class MessageQueue {
}
}
- @NeverInline
private Message nextLegacy() {
// Return here if the message loop has already quit and been disposed.
// This can happen if the application tries to restart a looper after quit
@@ -1036,13 +1019,11 @@ public final class MessageQueue {
}
}
- @NeverInline
private int postSyncBarrierConcurrent() {
return postSyncBarrier(SystemClock.uptimeMillis());
}
- @NeverInline
private int postSyncBarrierLegacy() {
return postSyncBarrier(SystemClock.uptimeMillis());
}
@@ -1162,7 +1143,6 @@ public final class MessageQueue {
}
}
- @NeverInline
private void removeSyncBarrierConcurrent(int token) {
boolean removed;
MessageNode first;
@@ -1189,7 +1169,6 @@ public final class MessageQueue {
}
}
- @NeverInline
private void removeSyncBarrierLegacy(int token) {
synchronized (this) {
Message prev = null;
@@ -1249,7 +1228,6 @@ public final class MessageQueue {
}
- @NeverInline
private boolean enqueueMessageConcurrent(Message msg, long when) {
if (msg.isInUse()) {
throw new IllegalStateException(msg + " This message is already in use.");
@@ -1258,7 +1236,6 @@ public final class MessageQueue {
return enqueueMessageUnchecked(msg, when);
}
- @NeverInline
private boolean enqueueMessageLegacy(Message msg, long when) {
synchronized (this) {
if (msg.isInUse()) {
@@ -1397,27 +1374,27 @@ public final class MessageQueue {
if (now >= msg.when) {
// Got a message.
mBlocked = false;
- if (prevMsg != null) {
- prevMsg.next = msg.next;
- if (prevMsg.next == null) {
- mLast = prevMsg;
- }
- } else {
- mMessages = msg.next;
- if (msg.next == null) {
- mLast = null;
- }
- }
- msg.next = null;
- msg.markInUse();
- if (msg.isAsynchronous()) {
- mAsyncMessageCount--;
+ }
+ if (prevMsg != null) {
+ prevMsg.next = msg.next;
+ if (prevMsg.next == null) {
+ mLast = prevMsg;
}
- if (TRACE) {
- Trace.setCounter("MQ.Delivered", mMessagesDelivered.incrementAndGet());
+ } else {
+ mMessages = msg.next;
+ if (msg.next == null) {
+ mLast = null;
}
- return msg;
}
+ msg.next = null;
+ msg.markInUse();
+ if (msg.isAsynchronous()) {
+ mAsyncMessageCount--;
+ }
+ if (TRACE) {
+ Trace.setCounter("MQ.Delivered", mMessagesDelivered.incrementAndGet());
+ }
+ return msg;
}
}
return null;
@@ -1434,7 +1411,7 @@ public final class MessageQueue {
throwIfNotTest();
Message ret;
if (mUseConcurrent) {
- ret = nextMessage(true);
+ ret = nextMessage(true, true);
} else {
ret = legacyPeekOrPoll(true);
}
@@ -1452,7 +1429,7 @@ public final class MessageQueue {
Message pollForTest() {
throwIfNotTest();
if (mUseConcurrent) {
- return nextMessage(false);
+ return nextMessage(false, true);
} else {
return legacyPeekOrPoll(false);
}
@@ -1469,7 +1446,7 @@ public final class MessageQueue {
throwIfNotTest();
if (mUseConcurrent) {
// Call nextMessage to get the stack drained into our priority queues
- nextMessage(true);
+ nextMessage(true, false);
Iterator<MessageNode> queueIter = mPriorityQueue.iterator();
MessageNode queueNode = iterateNext(queueIter);
@@ -1495,13 +1472,11 @@ public final class MessageQueue {
private final MatchHandlerWhatAndObject mMatchHandlerWhatAndObject =
new MatchHandlerWhatAndObject();
- @NeverInline
private boolean hasMessagesConcurrent(Handler h, int what, Object object) {
return findOrRemoveMessages(h, what, object, null, 0, mMatchHandlerWhatAndObject,
false);
}
- @NeverInline
private boolean hasMessagesLegacy(Handler h, int what, Object object) {
synchronized (this) {
Message p = mMessages;
@@ -1540,13 +1515,11 @@ public final class MessageQueue {
private final MatchHandlerWhatAndObjectEquals mMatchHandlerWhatAndObjectEquals =
new MatchHandlerWhatAndObjectEquals();
- @NeverInline
private boolean hasEqualMessagesConcurrent(Handler h, int what, Object object) {
return findOrRemoveMessages(h, what, object, null, 0, mMatchHandlerWhatAndObjectEquals,
false);
}
- @NeverInline
private boolean hasEqualMessagesLegacy(Handler h, int what, Object object) {
synchronized (this) {
Message p = mMessages;
@@ -1585,13 +1558,11 @@ public final class MessageQueue {
private final MatchHandlerRunnableAndObject mMatchHandlerRunnableAndObject =
new MatchHandlerRunnableAndObject();
- @NeverInline
private boolean hasMessagesConcurrent(Handler h, Runnable r, Object object) {
return findOrRemoveMessages(h, -1, object, r, 0, mMatchHandlerRunnableAndObject,
false);
}
- @NeverInline
private boolean hasMessagesLegacy(Handler h, Runnable r, Object object) {
synchronized (this) {
Message p = mMessages;
@@ -1626,12 +1597,10 @@ public final class MessageQueue {
}
private final MatchHandler mMatchHandler = new MatchHandler();
- @NeverInline
private boolean hasMessagesConcurrent(Handler h) {
return findOrRemoveMessages(h, -1, null, null, 0, mMatchHandler, false);
}
- @NeverInline
private boolean hasMessagesLegacy(Handler h) {
synchronized (this) {
Message p = mMessages;
@@ -1656,12 +1625,10 @@ public final class MessageQueue {
}
}
- @NeverInline
private void removeMessagesConcurrent(Handler h, int what, Object object) {
findOrRemoveMessages(h, what, object, null, 0, mMatchHandlerWhatAndObject, true);
}
- @NeverInline
private void removeMessagesLegacy(Handler h, int what, Object object) {
synchronized (this) {
Message p = mMessages;
@@ -1716,12 +1683,10 @@ public final class MessageQueue {
}
}
- @NeverInline
private void removeEqualMessagesConcurrent(Handler h, int what, Object object) {
findOrRemoveMessages(h, what, object, null, 0, mMatchHandlerWhatAndObjectEquals, true);
}
- @NeverInline
private void removeEqualMessagesLegacy(Handler h, int what, Object object) {
synchronized (this) {
Message p = mMessages;
@@ -1777,12 +1742,10 @@ public final class MessageQueue {
}
}
- @NeverInline
private void removeMessagesConcurrent(Handler h, Runnable r, Object object) {
findOrRemoveMessages(h, -1, object, r, 0, mMatchHandlerRunnableAndObject, true);
}
- @NeverInline
private void removeMessagesLegacy(Handler h, Runnable r, Object object) {
synchronized (this) {
Message p = mMessages;
@@ -1852,12 +1815,10 @@ public final class MessageQueue {
private final MatchHandlerRunnableAndObjectEquals mMatchHandlerRunnableAndObjectEquals =
new MatchHandlerRunnableAndObjectEquals();
- @NeverInline
private void removeEqualMessagesConcurrent(Handler h, Runnable r, Object object) {
findOrRemoveMessages(h, -1, object, r, 0, mMatchHandlerRunnableAndObjectEquals, true);
}
- @NeverInline
private void removeEqualMessagesLegacy(Handler h, Runnable r, Object object) {
synchronized (this) {
Message p = mMessages;
@@ -1926,12 +1887,10 @@ public final class MessageQueue {
}
private final MatchHandlerAndObject mMatchHandlerAndObject = new MatchHandlerAndObject();
- @NeverInline
private void removeCallbacksAndMessagesConcurrent(Handler h, Object object) {
findOrRemoveMessages(h, -1, object, null, 0, mMatchHandlerAndObject, true);
}
- @NeverInline
private void removeCallbacksAndMessagesLegacy(Handler h, Object object) {
synchronized (this) {
Message p = mMessages;
@@ -2000,12 +1959,10 @@ public final class MessageQueue {
private final MatchHandlerAndObjectEquals mMatchHandlerAndObjectEquals =
new MatchHandlerAndObjectEquals();
- @NeverInline
void removeCallbacksAndEqualMessagesConcurrent(Handler h, Object object) {
findOrRemoveMessages(h, -1, object, null, 0, mMatchHandlerAndObjectEquals, true);
}
- @NeverInline
void removeCallbacksAndEqualMessagesLegacy(Handler h, Object object) {
synchronized (this) {
Message p = mMessages;
diff --git a/core/java/android/os/ConcurrentMessageQueue/MessageQueue.java b/core/java/android/os/ConcurrentMessageQueue/MessageQueue.java
index 80da487a1358..7e0995c251b8 100644
--- a/core/java/android/os/ConcurrentMessageQueue/MessageQueue.java
+++ b/core/java/android/os/ConcurrentMessageQueue/MessageQueue.java
@@ -588,7 +588,7 @@ public final class MessageQueue {
private static final AtomicLong mMessagesDelivered = new AtomicLong();
private boolean mMessageDirectlyQueued;
- private Message nextMessage(boolean peek) {
+ private Message nextMessage(boolean peek, boolean returnEarliest) {
int i = 0;
while (true) {
@@ -665,7 +665,7 @@ public final class MessageQueue {
* If we have a barrier we should return the async node (if it exists and is ready)
*/
if (msgNode != null && msgNode.isBarrier()) {
- if (asyncMsgNode != null && now >= asyncMsgNode.getWhen()) {
+ if (asyncMsgNode != null && (returnEarliest || now >= asyncMsgNode.getWhen())) {
found = asyncMsgNode;
} else {
next = asyncMsgNode;
@@ -679,7 +679,7 @@ public final class MessageQueue {
earliest = pickEarliestNode(msgNode, asyncMsgNode);
if (earliest != null) {
- if (now >= earliest.getWhen()) {
+ if (returnEarliest || now >= earliest.getWhen()) {
found = earliest;
} else {
next = earliest;
@@ -784,7 +784,7 @@ public final class MessageQueue {
mMessageDirectlyQueued = false;
nativePollOnce(ptr, mNextPollTimeoutMillis);
- Message msg = nextMessage(false);
+ Message msg = nextMessage(false, false);
if (msg != null) {
msg.markInUse();
return msg;
@@ -1089,7 +1089,7 @@ public final class MessageQueue {
*/
Long peekWhenForTest() {
throwIfNotTest();
- Message ret = nextMessage(true);
+ Message ret = nextMessage(true, true);
return ret != null ? ret.when : null;
}
@@ -1102,7 +1102,7 @@ public final class MessageQueue {
@Nullable
Message pollForTest() {
throwIfNotTest();
- return nextMessage(false);
+ return nextMessage(false, true);
}
/**
@@ -1116,7 +1116,7 @@ public final class MessageQueue {
throwIfNotTest();
// Call nextMessage to get the stack drained into our priority queues
- nextMessage(true);
+ nextMessage(true, false);
Iterator<MessageNode> queueIter = mPriorityQueue.iterator();
MessageNode queueNode = iterateNext(queueIter);
diff --git a/core/java/android/os/LegacyMessageQueue/MessageQueue.java b/core/java/android/os/LegacyMessageQueue/MessageQueue.java
index cde2ba56fcba..132bdd1e56b8 100644
--- a/core/java/android/os/LegacyMessageQueue/MessageQueue.java
+++ b/core/java/android/os/LegacyMessageQueue/MessageQueue.java
@@ -759,27 +759,27 @@ public final class MessageQueue {
if (now >= msg.when) {
// Got a message.
mBlocked = false;
- if (prevMsg != null) {
- prevMsg.next = msg.next;
- if (prevMsg.next == null) {
- mLast = prevMsg;
- }
- } else {
- mMessages = msg.next;
- if (msg.next == null) {
- mLast = null;
- }
- }
- msg.next = null;
- msg.markInUse();
- if (msg.isAsynchronous()) {
- mAsyncMessageCount--;
+ }
+ if (prevMsg != null) {
+ prevMsg.next = msg.next;
+ if (prevMsg.next == null) {
+ mLast = prevMsg;
}
- if (TRACE) {
- Trace.setCounter("MQ.Delivered", mMessagesDelivered.incrementAndGet());
+ } else {
+ mMessages = msg.next;
+ if (msg.next == null) {
+ mLast = null;
}
- return msg;
}
+ msg.next = null;
+ msg.markInUse();
+ if (msg.isAsynchronous()) {
+ mAsyncMessageCount--;
+ }
+ if (TRACE) {
+ Trace.setCounter("MQ.Delivered", mMessagesDelivered.incrementAndGet());
+ }
+ return msg;
}
}
return null;
diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java
index 3c4139d39762..e58934746c14 100644
--- a/core/java/android/os/Parcel.java
+++ b/core/java/android/os/Parcel.java
@@ -4661,7 +4661,7 @@ public final class Parcel {
* @hide
*/
@Nullable
- private Object readLazyValue(@Nullable ClassLoaderProvider loaderProvider) {
+ public Object readLazyValue(@Nullable ClassLoader loader) {
int start = dataPosition();
int type = readInt();
if (isLengthPrefixed(type)) {
@@ -4672,17 +4672,12 @@ public final class Parcel {
int end = MathUtils.addOrThrow(dataPosition(), objectLength);
int valueLength = end - start;
setDataPosition(end);
- return new LazyValue(this, start, valueLength, type, loaderProvider);
+ return new LazyValue(this, start, valueLength, type, loader);
} else {
- return readValue(type, getClassLoader(loaderProvider), /* clazz */ null);
+ return readValue(type, loader, /* clazz */ null);
}
}
- @Nullable
- private static ClassLoader getClassLoader(@Nullable ClassLoaderProvider loaderProvider) {
- return loaderProvider == null ? null : loaderProvider.getClassLoader();
- }
-
private static final class LazyValue implements BiFunction<Class<?>, Class<?>[], Object> {
/**
@@ -4696,12 +4691,7 @@ public final class Parcel {
private final int mPosition;
private final int mLength;
private final int mType;
- // this member is set when a bundle that includes a LazyValue is unparceled. But it is used
- // when apply method is called. Between these 2 events, the bundle's ClassLoader could have
- // changed. Let the bundle be a ClassLoaderProvider allows the bundle provides its current
- // ClassLoader at the time apply method is called.
- @NonNull
- private final ClassLoaderProvider mLoaderProvider;
+ @Nullable private final ClassLoader mLoader;
@Nullable private Object mObject;
/**
@@ -4712,13 +4702,12 @@ public final class Parcel {
*/
@Nullable private volatile Parcel mSource;
- LazyValue(Parcel source, int position, int length, int type,
- @NonNull ClassLoaderProvider loaderProvider) {
+ LazyValue(Parcel source, int position, int length, int type, @Nullable ClassLoader loader) {
mSource = requireNonNull(source);
mPosition = position;
mLength = length;
mType = type;
- mLoaderProvider = loaderProvider;
+ mLoader = loader;
}
@Override
@@ -4731,8 +4720,7 @@ public final class Parcel {
int restore = source.dataPosition();
try {
source.setDataPosition(mPosition);
- mObject = source.readValue(mLoaderProvider.getClassLoader(), clazz,
- itemTypes);
+ mObject = source.readValue(mLoader, clazz, itemTypes);
} finally {
source.setDataPosition(restore);
}
@@ -4805,8 +4793,7 @@ public final class Parcel {
return Objects.equals(mObject, value.mObject);
}
// Better safely fail here since this could mean we get different objects.
- if (!Objects.equals(mLoaderProvider.getClassLoader(),
- value.mLoaderProvider.getClassLoader())) {
+ if (!Objects.equals(mLoader, value.mLoader)) {
return false;
}
// Otherwise compare metadata prior to comparing payload.
@@ -4820,24 +4807,10 @@ public final class Parcel {
@Override
public int hashCode() {
// Accessing mSource first to provide memory barrier for mObject
- return Objects.hash(mSource == null, mObject, mLoaderProvider.getClassLoader(), mType,
- mLength);
+ return Objects.hash(mSource == null, mObject, mLoader, mType, mLength);
}
}
- /**
- * Provides a ClassLoader.
- * @hide
- */
- public interface ClassLoaderProvider {
- /**
- * Returns a ClassLoader.
- *
- * @return ClassLoader
- */
- ClassLoader getClassLoader();
- }
-
/** Same as {@link #readValue(ClassLoader, Class, Class[])} without any item types. */
private <T> T readValue(int type, @Nullable ClassLoader loader, @Nullable Class<T> clazz) {
// Avoids allocating Class[0] array
@@ -5578,8 +5551,8 @@ public final class Parcel {
}
private void readArrayMapInternal(@NonNull ArrayMap<? super String, Object> outVal,
- int size, @Nullable ClassLoaderProvider loaderProvider) {
- readArrayMap(outVal, size, /* sorted */ true, /* lazy */ false, loaderProvider, null);
+ int size, @Nullable ClassLoader loader) {
+ readArrayMap(outVal, size, /* sorted */ true, /* lazy */ false, loader, null);
}
/**
@@ -5593,12 +5566,11 @@ public final class Parcel {
* @hide
*/
void readArrayMap(ArrayMap<? super String, Object> map, int size, boolean sorted,
- boolean lazy, @Nullable ClassLoaderProvider loaderProvider, int[] lazyValueCount) {
+ boolean lazy, @Nullable ClassLoader loader, int[] lazyValueCount) {
ensureWithinMemoryLimit(SIZE_COMPLEX_TYPE, size);
while (size > 0) {
String key = readString();
- Object value = (lazy) ? readLazyValue(loaderProvider) : readValue(
- getClassLoader(loaderProvider));
+ Object value = (lazy) ? readLazyValue(loader) : readValue(loader);
if (value instanceof LazyValue) {
lazyValueCount[0]++;
}
@@ -5619,12 +5591,12 @@ public final class Parcel {
*/
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
public void readArrayMap(@NonNull ArrayMap<? super String, Object> outVal,
- @Nullable ClassLoaderProvider loaderProvider) {
+ @Nullable ClassLoader loader) {
final int N = readInt();
if (N < 0) {
return;
}
- readArrayMapInternal(outVal, N, loaderProvider);
+ readArrayMapInternal(outVal, N, loader);
}
/**
diff --git a/core/java/android/os/UidBatteryConsumer.java b/core/java/android/os/UidBatteryConsumer.java
index 976bfe41ba45..62d5015af914 100644
--- a/core/java/android/os/UidBatteryConsumer.java
+++ b/core/java/android/os/UidBatteryConsumer.java
@@ -147,7 +147,7 @@ public final class UidBatteryConsumer extends BatteryConsumer {
for (int screenState = 0; screenState < SCREEN_STATE_COUNT; screenState++) {
if (mData.layout.screenStateDataIncluded
- && screenState == POWER_STATE_UNSPECIFIED) {
+ && screenState == SCREEN_STATE_UNSPECIFIED) {
continue;
}
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 967f55ce7a88..6c21dbf126bb 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -2910,10 +2910,18 @@ public class UserManager {
* <p>Currently, on most form factors the first human user on the device will be the main user;
* in the future, the concept may be transferable, so a different user (or even no user at all)
* may be designated the main user instead. On other form factors there might not be a main
+ * user. In the future, the concept may be removed, i.e. typical future devices may have no main
* user.
*
* <p>Note that this will not be the system user on devices for which
* {@link #isHeadlessSystemUserMode()} returns true.
+ *
+ * <p>NB: Features should ideally not limit functionality to the main user. Ideally, they
+ * should either work for all users or for all admin users. If a feature should only work for
+ * select users, its determination of which user should be done intelligently or be
+ * customizable. Not all devices support a main user, and the idea of singling out one user as
+ * special is contrary to overall multiuser goals.
+ *
* @hide
*/
@SystemApi
@@ -2930,6 +2938,12 @@ public class UserManager {
/**
* Returns the designated "main user" of the device, or {@code null} if there is no main user.
*
+ * <p>NB: Features should ideally not limit functionality to the main user. Ideally, they
+ * should either work for all users or for all admin users. If a feature should only work for
+ * select users, its determination of which user should be done intelligently or be
+ * customizable. Not all devices support a main user, and the idea of singling out one user as
+ * special is contrary to overall multiuser goals.
+ *
* @see #isMainUser()
* @hide
*/
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 2e231e3957c6..65c857a51b29 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -11008,6 +11008,21 @@ public final class Settings {
@Readable
public static final String SHOW_NOTIFICATION_SNOOZE = "show_notification_snooze";
+ /**
+ * Controls whether dual shade is enabled. This splits notifications and quick settings to
+ * have their own independently expandable/collapsible panels, appearing on either side of
+ * the large screen (including unfolded device) or sharing a space on a narrow screen
+ * (including a folded device). Both panels will now cover the screen only partially
+ * (wrapping their content), so a running app or the lockscreen will remain visible in the
+ * background.
+ * <p>
+ * Type: int (0 for false, 1 for true)
+ *
+ * @hide
+ */
+ @android.provider.Settings.Readable
+ public static final String DUAL_SHADE = "dual_shade";
+
/**
* 1 if it is allowed to remove the primary GAIA account. 0 by default.
* @hide
@@ -13788,6 +13803,16 @@ public final class Settings {
= "enable_freeform_support";
/**
+ * Whether to override the availability of the desktop experiences features on the
+ * device. With desktop experiences enabled, secondary displays can be used to run
+ * apps, in desktop mode by default. Otherwise they can only be used for mirroring.
+ * @hide
+ */
+ @Readable
+ public static final String DEVELOPMENT_OVERRIDE_DESKTOP_EXPERIENCE_FEATURES =
+ "override_desktop_experience_features";
+
+ /**
* Whether to override the availability of the desktop mode on the main display of the
* device. If on, users can make move an app to the desktop, allowing a freeform windowing
* experience.
diff --git a/core/java/android/service/notification/Adjustment.java b/core/java/android/service/notification/Adjustment.java
index 073f512a85fb..09ebc9f030c2 100644
--- a/core/java/android/service/notification/Adjustment.java
+++ b/core/java/android/service/notification/Adjustment.java
@@ -21,6 +21,7 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.StringDef;
import android.annotation.SystemApi;
+import android.annotation.TestApi;
import android.app.Notification;
import android.os.Build;
import android.os.Bundle;
@@ -223,6 +224,14 @@ public final class Adjustment implements Parcelable {
public static final int TYPE_CONTENT_RECOMMENDATION = 4;
/**
+ * Data type: String, the classification type of this notification. The OS may display
+ * notifications differently depending on the type, and may change the alerting level of the
+ * notification.
+ */
+ @FlaggedApi(android.app.Flags.FLAG_NM_SUMMARIZATION)
+ public static final String KEY_SUMMARIZATION = "key_summarization";
+
+ /**
* Create a notification adjustment.
*
* @param pkg The package of the notification.
diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java
index 72569075c2ed..f23006584621 100644
--- a/core/java/android/service/notification/NotificationListenerService.java
+++ b/core/java/android/service/notification/NotificationListenerService.java
@@ -23,6 +23,7 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SdkConstant;
import android.annotation.SystemApi;
+import android.annotation.TestApi;
import android.annotation.UiThread;
import android.app.ActivityManager;
import android.app.INotificationManager;
@@ -56,6 +57,7 @@ import android.os.Parcelable;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.UserHandle;
+import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.Log;
import android.widget.RemoteViews;
@@ -1824,6 +1826,7 @@ public abstract class NotificationListenerService extends Service {
private int mProposedImportance;
// Sensitive info detected by the notification assistant
private boolean mSensitiveContent;
+ private String mSummarization;
private static final int PARCEL_VERSION = 2;
@@ -1864,6 +1867,7 @@ public abstract class NotificationListenerService extends Service {
out.writeBoolean(mIsBubble);
out.writeInt(mProposedImportance);
out.writeBoolean(mSensitiveContent);
+ out.writeString(mSummarization);
}
/** @hide */
@@ -1904,6 +1908,7 @@ public abstract class NotificationListenerService extends Service {
mIsBubble = in.readBoolean();
mProposedImportance = in.readInt();
mSensitiveContent = in.readBoolean();
+ mSummarization = in.readString();
}
@@ -2180,6 +2185,16 @@ public abstract class NotificationListenerService extends Service {
}
/**
+ * Returns a summary of the content in the notification, or potentially of the current
+ * notification and related notifications (for example, if this is provided for a group
+ * summary notification it may be summarizing all the child notifications).
+ */
+ @FlaggedApi(android.app.Flags.FLAG_NM_SUMMARIZATION)
+ public @Nullable String getSummarization() {
+ return mSummarization;
+ }
+
+ /**
* Returns the intended transition to ranking passed by {@link NotificationAssistantService}
* @hide
*/
@@ -2201,7 +2216,7 @@ public abstract class NotificationListenerService extends Service {
ArrayList<CharSequence> smartReplies, boolean canBubble,
boolean isTextChanged, boolean isConversation, ShortcutInfo shortcutInfo,
int rankingAdjustment, boolean isBubble, int proposedImportance,
- boolean sensitiveContent) {
+ boolean sensitiveContent, String summarization) {
mKey = key;
mRank = rank;
mIsAmbient = importance < NotificationManager.IMPORTANCE_LOW;
@@ -2229,6 +2244,7 @@ public abstract class NotificationListenerService extends Service {
mIsBubble = isBubble;
mProposedImportance = proposedImportance;
mSensitiveContent = sensitiveContent;
+ mSummarization = TextUtils.nullIfEmpty(summarization);
}
/**
@@ -2271,7 +2287,8 @@ public abstract class NotificationListenerService extends Service {
other.mRankingAdjustment,
other.mIsBubble,
other.mProposedImportance,
- other.mSensitiveContent);
+ other.mSensitiveContent,
+ other.mSummarization);
}
/**
@@ -2332,7 +2349,8 @@ public abstract class NotificationListenerService extends Service {
&& Objects.equals(mRankingAdjustment, other.mRankingAdjustment)
&& Objects.equals(mIsBubble, other.mIsBubble)
&& Objects.equals(mProposedImportance, other.mProposedImportance)
- && Objects.equals(mSensitiveContent, other.mSensitiveContent);
+ && Objects.equals(mSensitiveContent, other.mSensitiveContent)
+ && Objects.equals(mSummarization, other.mSummarization);
}
}
diff --git a/core/java/android/service/notification/StatusBarNotification.java b/core/java/android/service/notification/StatusBarNotification.java
index 105fa3ffd4cd..79957f411597 100644
--- a/core/java/android/service/notification/StatusBarNotification.java
+++ b/core/java/android/service/notification/StatusBarNotification.java
@@ -18,6 +18,8 @@ package android.service.notification;
import static android.text.TextUtils.formatSimple;
+import static com.android.window.flags.Flags.enablePerDisplayPackageContextCacheInStatusbarNotif;
+
import android.annotation.NonNull;
import android.app.Notification;
import android.app.NotificationManager;
@@ -37,9 +39,9 @@ import android.util.ArrayMap;
import com.android.internal.logging.InstanceId;
import com.android.internal.logging.nano.MetricsProto;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import static com.android.window.flags.Flags.enablePerDisplayPackageContextCacheInStatusbarNotif;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.Map;
/**
@@ -81,7 +83,8 @@ public class StatusBarNotification implements Parcelable {
@Deprecated
private Context mContext; // used for inflation & icon expansion
// Maps display id to context used for remote view content inflation and status bar icon.
- private final Map<Integer, Context> mContextForDisplayId = new ArrayMap<>();
+ private final Map<Integer, Context> mContextForDisplayId =
+ Collections.synchronizedMap(new ArrayMap<>());
/** @hide */
public StatusBarNotification(String pkg, String opPkg, int id,
diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java
index cb498503f201..a5d52957c40e 100644
--- a/core/java/android/text/StaticLayout.java
+++ b/core/java/android/text/StaticLayout.java
@@ -121,6 +121,7 @@ public class StaticLayout extends Layout {
b.mHyphenationFrequency = Layout.HYPHENATION_FREQUENCY_NONE;
b.mJustificationMode = Layout.JUSTIFICATION_MODE_NONE;
b.mLineBreakConfig = LineBreakConfig.NONE;
+ b.mUseBoundsForWidth = false;
b.mMinimumFontMetrics = null;
return b;
}
diff --git a/core/java/android/text/style/TtsSpan.java b/core/java/android/text/style/TtsSpan.java
index a337ba2a57fb..e0d4ec1ca826 100644
--- a/core/java/android/text/style/TtsSpan.java
+++ b/core/java/android/text/style/TtsSpan.java
@@ -108,11 +108,13 @@ public class TtsSpan implements ParcelableSpan {
/**
* The text associated with this span is a time, consisting of a number of
- * hours and minutes, specified with {@link #ARG_HOURS} and
- * {@link #ARG_MINUTES}.
+ * hours, minutes, and seconds specified with {@link #ARG_HOURS}, {@link #ARG_MINUTES}, and
+ * {@link #ARG_SECONDS}.
* Also accepts the arguments {@link #ARG_GENDER},
* {@link #ARG_ANIMACY}, {@link #ARG_MULTIPLICITY} and
- * {@link #ARG_CASE}.
+ * {@link #ARG_CASE}. This is different from {@link #TYPE_DURATION}. This should be used to
+ * convey a particular moment in time, such as a clock time, while {@link #TYPE_DURATION} should
+ * be used to convey an interval of time.
*/
public static final String TYPE_TIME = "android.type.time";
@@ -310,16 +312,18 @@ public class TtsSpan implements ParcelableSpan {
public static final String ARG_UNIT = "android.arg.unit";
/**
- * Argument used to specify the hours of a time. The hours should be
- * provided as an integer in the range from 0 up to and including 24.
- * Can be used with {@link #TYPE_TIME}.
+ * Argument used to specify the hours of a time or duration. The hours should be
+ * provided as an integer in the range from 0 up to and including 24 for
+ * {@link #TYPE_TIME}.
+ * Can be used with {@link #TYPE_TIME} or {@link #TYPE_DURATION}.
*/
public static final String ARG_HOURS = "android.arg.hours";
/**
- * Argument used to specify the minutes of a time. The minutes should be
- * provided as an integer in the range from 0 up to and including 59.
- * Can be used with {@link #TYPE_TIME}.
+ * Argument used to specify the minutes of a time or duration. The minutes should be
+ * provided as an integer in the range from 0 up to and including 59 for
+ * {@link #TYPE_TIME}.
+ * Can be used with {@link #TYPE_TIME} or {@link #TYPE_DURATION}.
*/
public static final String ARG_MINUTES = "android.arg.minutes";
diff --git a/core/java/android/view/InsetsSourceControl.java b/core/java/android/view/InsetsSourceControl.java
index 7f2f0e8863df..cfb4835a13f7 100644
--- a/core/java/android/view/InsetsSourceControl.java
+++ b/core/java/android/view/InsetsSourceControl.java
@@ -194,7 +194,7 @@ public class InsetsSourceControl implements Parcelable {
}
public void release(Consumer<SurfaceControl> surfaceReleaseConsumer) {
- if (mLeash != null) {
+ if (mLeash != null && mLeash.isValid()) {
surfaceReleaseConsumer.accept(mLeash);
}
}
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index 7c75d7b30037..0e329c2859db 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -44,7 +44,6 @@ import android.app.Activity;
import android.app.ActivityOptions;
import android.app.ActivityThread;
import android.app.Application;
-import android.app.LoadedApk;
import android.app.PendingIntent;
import android.app.RemoteInput;
import android.appwidget.AppWidgetHostView;
@@ -8484,8 +8483,14 @@ public class RemoteViews implements Parcelable, Filter {
return context;
}
try {
- LoadedApk.checkAndUpdateApkPaths(mApplication);
- Context applicationContext = context.createApplicationContext(mApplication,
+ // Use PackageManager as the source of truth for application information, rather
+ // than the parceled ApplicationInfo provided by the app.
+ ApplicationInfo sanitizedApplication =
+ context.getPackageManager().getApplicationInfoAsUser(
+ mApplication.packageName, 0,
+ UserHandle.getUserId(mApplication.uid));
+ Context applicationContext = context.createApplicationContext(
+ sanitizedApplication,
Context.CONTEXT_RESTRICTED);
// Get the correct apk paths while maintaining the current context's configuration.
return applicationContext.createConfigurationContext(
diff --git a/core/java/android/window/DesktopExperienceFlags.java b/core/java/android/window/DesktopExperienceFlags.java
new file mode 100644
index 000000000000..0d1bb77ae8a2
--- /dev/null
+++ b/core/java/android/window/DesktopExperienceFlags.java
@@ -0,0 +1,124 @@
+/*
+ * 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.window;
+
+import static com.android.server.display.feature.flags.Flags.enableDisplayContentModeManagement;
+
+import android.annotation.Nullable;
+import android.os.SystemProperties;
+import android.util.Log;
+
+import com.android.window.flags.Flags;
+
+import java.util.function.BooleanSupplier;
+
+/**
+ * Checks Desktop Experience flag state.
+ *
+ * <p>This enum provides a centralized way to control the behavior of flags related to desktop
+ * experience features which are aiming for developer preview before their release. It allows
+ * developer option to override the default behavior of these flags.
+ *
+ * <p>The flags here will be controlled by the {@code
+ * persist.wm.debug.desktop_experience_devopts} system property.
+ *
+ * <p>NOTE: Flags should only be added to this enum when they have received Product and UX alignment
+ * that the feature is ready for developer preview, otherwise just do a flag check.
+ *
+ * @hide
+ */
+public enum DesktopExperienceFlags {
+ ENABLE_DISPLAY_CONTENT_MODE_MANAGEMENT(() -> enableDisplayContentModeManagement(), true);
+
+ /**
+ * Flag class, to be used in case the enum cannot be used because the flag is not accessible.
+ *
+ * <p>This class will still use the process-wide cache.
+ */
+ public static class DesktopExperienceFlag {
+ // Function called to obtain aconfig flag value.
+ private final BooleanSupplier mFlagFunction;
+ // Whether the flag state should be affected by developer option.
+ private final boolean mShouldOverrideByDevOption;
+
+ public DesktopExperienceFlag(BooleanSupplier flagFunction, boolean shouldOverrideByDevOption) {
+ this.mFlagFunction = flagFunction;
+ this.mShouldOverrideByDevOption = shouldOverrideByDevOption;
+ }
+
+ /**
+ * Determines state of flag based on the actual flag and desktop experience developer option
+ * overrides.
+ */
+ public boolean isTrue() {
+ return isFlagTrue(mFlagFunction, mShouldOverrideByDevOption);
+ }
+ }
+
+ private static final String TAG = "DesktopExperienceFlags";
+ // Function called to obtain aconfig flag value.
+ private final BooleanSupplier mFlagFunction;
+ // Whether the flag state should be affected by developer option.
+ private final boolean mShouldOverrideByDevOption;
+
+ // Local cache for toggle override, which is initialized once on its first access. It needs to
+ // be refreshed only on reboots as overridden state is expected to take effect on reboots.
+ @Nullable private static Boolean sCachedToggleOverride;
+
+ public static final String SYSTEM_PROPERTY_NAME = "persist.wm.debug.desktop_experience_devopts";
+
+ DesktopExperienceFlags(BooleanSupplier flagFunction, boolean shouldOverrideByDevOption) {
+ this.mFlagFunction = flagFunction;
+ this.mShouldOverrideByDevOption = shouldOverrideByDevOption;
+ }
+
+ /**
+ * Determines state of flag based on the actual flag and desktop experience developer option
+ * overrides.
+ */
+ public boolean isTrue() {
+ return isFlagTrue(mFlagFunction, mShouldOverrideByDevOption);
+ }
+
+ private static boolean isFlagTrue(
+ BooleanSupplier flagFunction, boolean shouldOverrideByDevOption) {
+ if (shouldOverrideByDevOption
+ && Flags.showDesktopExperienceDevOption()
+ && getToggleOverride()) {
+ return true;
+ }
+ return flagFunction.getAsBoolean();
+ }
+
+ private static boolean getToggleOverride() {
+ // If cached, return it
+ if (sCachedToggleOverride != null) {
+ return sCachedToggleOverride;
+ }
+
+ // Otherwise, fetch and cache it
+ boolean override = getToggleOverrideFromSystem();
+ sCachedToggleOverride = override;
+ Log.d(TAG, "Toggle override initialized to: " + override);
+ return override;
+ }
+
+ /** Returns the {@link ToggleOverride} from the system property.. */
+ private static boolean getToggleOverrideFromSystem() {
+ return SystemProperties.getBoolean(SYSTEM_PROPERTY_NAME, false);
+ }
+}
diff --git a/core/java/android/window/DesktopModeFlags.java b/core/java/android/window/DesktopModeFlags.java
index be69d3da3874..d1e3a2d953ef 100644
--- a/core/java/android/window/DesktopModeFlags.java
+++ b/core/java/android/window/DesktopModeFlags.java
@@ -20,12 +20,13 @@ import android.annotation.Nullable;
import android.app.ActivityThread;
import android.app.Application;
import android.content.ContentResolver;
+import android.os.SystemProperties;
import android.provider.Settings;
import android.util.Log;
import com.android.window.flags.Flags;
-import java.util.function.Supplier;
+import java.util.function.BooleanSupplier;
/**
* Checks desktop mode flag state.
@@ -80,19 +81,54 @@ public enum DesktopModeFlags {
ENABLE_DESKTOP_WINDOWING_PERSISTENCE(Flags::enableDesktopWindowingPersistence, false),
ENABLE_HANDLE_INPUT_FIX(Flags::enableHandleInputFix, true),
ENABLE_DESKTOP_WINDOWING_ENTER_TRANSITIONS_BUGFIX(
- Flags::enableDesktopWindowingEnterTransitionBugfix, false),
+ Flags::enableDesktopWindowingEnterTransitionBugfix, true),
ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS_BUGFIX(
- Flags::enableDesktopWindowingExitTransitionsBugfix, false),
+ Flags::enableDesktopWindowingExitTransitionsBugfix, true),
ENABLE_DESKTOP_APP_LAUNCH_ALTTAB_TRANSITIONS_BUGFIX(
- Flags::enableDesktopAppLaunchAlttabTransitionsBugfix, false),
+ Flags::enableDesktopAppLaunchAlttabTransitionsBugfix, true),
ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS_BUGFIX(
- Flags::enableDesktopAppLaunchTransitionsBugfix, false),
+ Flags::enableDesktopAppLaunchTransitionsBugfix, true),
INCLUDE_TOP_TRANSPARENT_FULLSCREEN_TASK_IN_DESKTOP_HEURISTIC(
- Flags::includeTopTransparentFullscreenTaskInDesktopHeuristic, true);
+ Flags::includeTopTransparentFullscreenTaskInDesktopHeuristic, true),
+ ENABLE_DESKTOP_WINDOWING_HSUM(Flags::enableDesktopWindowingHsum, true),
+ ENABLE_MINIMIZE_BUTTON(Flags::enableMinimizeButton, true),
+ ENABLE_RESIZING_METRICS(Flags::enableResizingMetrics, true),
+ ENABLE_TASK_RESIZING_KEYBOARD_SHORTCUTS(Flags::enableTaskResizingKeyboardShortcuts, true),
+ ENABLE_DESKTOP_WALLPAPER_ACTIVITY_FOR_SYSTEM_USER(
+ Flags::enableDesktopWallpaperActivityForSystemUser, true),
+ ENABLE_DESKTOP_RECENTS_TRANSITIONS_CORNERS_BUGFIX(
+ Flags::enableDesktopRecentsTransitionsCornersBugfix, false),
+ ENABLE_DESKTOP_SYSTEM_DIALOGS_TRANSITIONS(Flags::enableDesktopSystemDialogsTransitions, true);
- private static final String TAG = "DesktopModeFlagsUtil";
+ /**
+ * Flag class, to be used in case the enum cannot be used because the flag is not accessible.
+ *
+ * <p> This class will still use the process-wide cache.
+ */
+ public static class DesktopModeFlag {
+ // Function called to obtain aconfig flag value.
+ private final BooleanSupplier mFlagFunction;
+ // Whether the flag state should be affected by developer option.
+ private final boolean mShouldOverrideByDevOption;
+
+ public DesktopModeFlag(BooleanSupplier flagFunction, boolean shouldOverrideByDevOption) {
+ this.mFlagFunction = flagFunction;
+ this.mShouldOverrideByDevOption = shouldOverrideByDevOption;
+ }
+
+ /**
+ * Determines state of flag based on the actual flag and desktop mode developer option
+ * overrides.
+ */
+ public boolean isTrue() {
+ return isFlagTrue(mFlagFunction, mShouldOverrideByDevOption);
+ }
+
+ }
+
+ private static final String TAG = "DesktopModeFlags";
// Function called to obtain aconfig flag value.
- private final Supplier<Boolean> mFlagFunction;
+ private final BooleanSupplier mFlagFunction;
// Whether the flag state should be affected by developer option.
private final boolean mShouldOverrideByDevOption;
@@ -100,7 +136,9 @@ public enum DesktopModeFlags {
// be refreshed only on reboots as overridden state is expected to take effect on reboots.
private static ToggleOverride sCachedToggleOverride;
- DesktopModeFlags(Supplier<Boolean> flagFunction, boolean shouldOverrideByDevOption) {
+ public static final String SYSTEM_PROPERTY_NAME = "persist.wm.debug.desktop_experience_devopts";
+
+ DesktopModeFlags(BooleanSupplier flagFunction, boolean shouldOverrideByDevOption) {
this.mFlagFunction = flagFunction;
this.mShouldOverrideByDevOption = shouldOverrideByDevOption;
}
@@ -110,24 +148,42 @@ public enum DesktopModeFlags {
* overrides.
*/
public boolean isTrue() {
- Application application = ActivityThread.currentApplication();
- if (!Flags.showDesktopWindowingDevOption()
- || !mShouldOverrideByDevOption
- || application == null) {
- return mFlagFunction.get();
- } else {
+ return isFlagTrue(mFlagFunction, mShouldOverrideByDevOption);
+ }
+
+ private static boolean isFlagTrue(BooleanSupplier flagFunction,
+ boolean shouldOverrideByDevOption) {
+ if (!shouldOverrideByDevOption) return flagFunction.getAsBoolean();
+ if (Flags.showDesktopExperienceDevOption()) {
+ return switch (getToggleOverride(null)) {
+ case OVERRIDE_UNSET, OVERRIDE_OFF -> flagFunction.getAsBoolean();
+ case OVERRIDE_ON -> true;
+ };
+ }
+ if (Flags.showDesktopWindowingDevOption()) {
+ Application application = ActivityThread.currentApplication();
+ if (application == null) {
+ Log.w(TAG, "Could not get the current application.");
+ return flagFunction.getAsBoolean();
+ }
+ ContentResolver contentResolver = application.getContentResolver();
+ if (contentResolver == null) {
+ Log.w(TAG, "Could not get the content resolver for the application.");
+ return flagFunction.getAsBoolean();
+ }
boolean shouldToggleBeEnabledByDefault = Flags.enableDesktopWindowingMode();
- return switch (getToggleOverride(application.getContentResolver())) {
- case OVERRIDE_UNSET -> mFlagFunction.get();
+ return switch (getToggleOverride(contentResolver)) {
+ case OVERRIDE_UNSET -> flagFunction.getAsBoolean();
// When toggle override matches its default state, don't override flags. This
// helps users reset their feature overrides.
- case OVERRIDE_OFF -> !shouldToggleBeEnabledByDefault && mFlagFunction.get();
- case OVERRIDE_ON -> shouldToggleBeEnabledByDefault ? mFlagFunction.get() : true;
+ case OVERRIDE_OFF -> !shouldToggleBeEnabledByDefault && flagFunction.getAsBoolean();
+ case OVERRIDE_ON -> !shouldToggleBeEnabledByDefault || flagFunction.getAsBoolean();
};
}
+ return flagFunction.getAsBoolean();
}
- private ToggleOverride getToggleOverride(ContentResolver contentResolver) {
+ private static ToggleOverride getToggleOverride(@Nullable ContentResolver contentResolver) {
// If cached, return it
if (sCachedToggleOverride != null) {
return sCachedToggleOverride;
@@ -143,12 +199,21 @@ public enum DesktopModeFlags {
/**
* Returns {@link ToggleOverride} from Settings.Global set by toggle.
*/
- private ToggleOverride getToggleOverrideFromSystem(ContentResolver contentResolver) {
- int settingValue = Settings.Global.getInt(
- contentResolver,
- Settings.Global.DEVELOPMENT_OVERRIDE_DESKTOP_MODE_FEATURES,
- ToggleOverride.OVERRIDE_UNSET.getSetting()
- );
+ private static ToggleOverride getToggleOverrideFromSystem(
+ @Nullable ContentResolver contentResolver) {
+ int settingValue;
+ if (Flags.showDesktopExperienceDevOption()) {
+ settingValue = SystemProperties.getInt(
+ SYSTEM_PROPERTY_NAME,
+ ToggleOverride.OVERRIDE_UNSET.getSetting()
+ );
+ } else {
+ settingValue = Settings.Global.getInt(
+ contentResolver,
+ Settings.Global.DEVELOPMENT_OVERRIDE_DESKTOP_MODE_FEATURES,
+ ToggleOverride.OVERRIDE_UNSET.getSetting()
+ );
+ }
return ToggleOverride.fromSetting(settingValue, ToggleOverride.OVERRIDE_UNSET);
}
diff --git a/core/java/android/window/OWNERS b/core/java/android/window/OWNERS
index 77c99b98cf4a..82d37244dc70 100644
--- a/core/java/android/window/OWNERS
+++ b/core/java/android/window/OWNERS
@@ -3,3 +3,4 @@ set noparent
include /services/core/java/com/android/server/wm/OWNERS
per-file DesktopModeFlags.java = file:/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/OWNERS
+per-file DesktopExperienceFlags.java = file:/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/OWNERS
diff --git a/core/java/android/window/WindowContainerTransaction.java b/core/java/android/window/WindowContainerTransaction.java
index ce0ccd5c6d0d..68b5a261f507 100644
--- a/core/java/android/window/WindowContainerTransaction.java
+++ b/core/java/android/window/WindowContainerTransaction.java
@@ -112,6 +112,12 @@ public final class WindowContainerTransaction implements Parcelable {
mTaskFragmentOrganizer = null;
}
+ /*
+ * ===========================================================================================
+ * Window container properties
+ * ===========================================================================================
+ */
+
/**
* Resize a container.
*/
@@ -170,20 +176,6 @@ public final class WindowContainerTransaction implements Parcelable {
}
/**
- * Notify {@link com.android.server.wm.PinnedTaskController} that the picture-in-picture task
- * has finished the enter animation with the given bounds.
- */
- @NonNull
- public WindowContainerTransaction scheduleFinishEnterPip(
- @NonNull WindowContainerToken container, @NonNull Rect bounds) {
- final Change chg = getOrCreateChange(container.asBinder());
- chg.mPinnedBounds = new Rect(bounds);
- chg.mChangeMask |= Change.CHANGE_PIP_CALLBACK;
-
- return this;
- }
-
- /**
* Send a SurfaceControl transaction to the server, which the server will apply in sync with
* the next bounds change. As this uses deferred transaction and not BLAST it is only
* able to sync with a single window, and the first visible window in this hierarchy of type
@@ -204,36 +196,6 @@ public final class WindowContainerTransaction implements Parcelable {
}
/**
- * Like {@link #setBoundsChangeTransaction} but instead queues up a setPosition/WindowCrop
- * on a container's surface control. This is useful when a boundsChangeTransaction needs to be
- * queued up on a Task that won't be organized until the end of this window-container
- * transaction.
- *
- * This requires that, at the end of this transaction, `task` will be organized; otherwise
- * the server will throw an IllegalArgumentException.
- *
- * WARNING: Use this carefully. Whatever is set here should match the expected bounds after
- * the transaction completes since it will likely be replaced by it. This call is
- * intended to pre-emptively set bounds on a surface in sync with a buffer when
- * otherwise the new bounds and the new buffer would update on different frames.
- *
- * TODO(b/134365562): remove once TaskOrg drives full-screen or BLAST is enabled.
- *
- * @hide
- */
- @NonNull
- public WindowContainerTransaction setBoundsChangeTransaction(
- @NonNull WindowContainerToken task, @NonNull Rect surfaceBounds) {
- Change chg = getOrCreateChange(task.asBinder());
- if (chg.mBoundsChangeSurfaceBounds == null) {
- chg.mBoundsChangeSurfaceBounds = new Rect();
- }
- chg.mBoundsChangeSurfaceBounds.set(surfaceBounds);
- chg.mChangeMask |= Change.CHANGE_BOUNDS_TRANSACTION_RECT;
- return this;
- }
-
- /**
* Set the windowing mode of children of a given root task, without changing
* the windowing mode of the Task itself. This can be used during transitions
* for example to make the activity render it's fullscreen configuration
@@ -381,22 +343,115 @@ public final class WindowContainerTransaction implements Parcelable {
}
/**
- * Reparents a container into another one. The effect of a {@code null} parent can vary. For
- * example, reparenting a stack to {@code null} will reparent it to its display.
+ * Sets whether a container is being drag-resized.
+ * When {@code true}, the client will reuse a single (larger) surface size to avoid
+ * continuous allocations on every size change.
*
- * @param onTop When {@code true}, the child goes to the top of parent; otherwise it goes to
- * the bottom.
+ * @param container WindowContainerToken of the task that changed its drag resizing state
+ * @hide
*/
@NonNull
- public WindowContainerTransaction reparent(@NonNull WindowContainerToken child,
- @Nullable WindowContainerToken parent, boolean onTop) {
- mHierarchyOps.add(HierarchyOp.createForReparent(child.asBinder(),
- parent == null ? null : parent.asBinder(),
- onTop));
+ public WindowContainerTransaction setDragResizing(@NonNull WindowContainerToken container,
+ boolean dragResizing) {
+ final Change change = getOrCreateChange(container.asBinder());
+ change.mChangeMask |= Change.CHANGE_DRAG_RESIZING;
+ change.mDragResizing = dragResizing;
return this;
}
/**
+ * Sets/removes the always on top flag for this {@code windowContainer}. See
+ * {@link com.android.server.wm.ConfigurationContainer#setAlwaysOnTop(boolean)}.
+ * Please note that this method is only intended to be used for a
+ * {@link com.android.server.wm.Task} or {@link com.android.server.wm.DisplayArea}.
+ *
+ * <p>
+ * Setting always on top to {@code True} will also make the {@code windowContainer} to move
+ * to the top.
+ * </p>
+ * <p>
+ * Setting always on top to {@code False} will make this {@code windowContainer} to move
+ * below the other always on top sibling containers.
+ * </p>
+ *
+ * @param windowContainer the container which the flag need to be updated for.
+ * @param alwaysOnTop denotes whether or not always on top flag should be set.
+ * @hide
+ */
+ @NonNull
+ public WindowContainerTransaction setAlwaysOnTop(
+ @NonNull WindowContainerToken windowContainer, boolean alwaysOnTop) {
+ final HierarchyOp hierarchyOp =
+ new HierarchyOp.Builder(
+ HierarchyOp.HIERARCHY_OP_TYPE_SET_ALWAYS_ON_TOP)
+ .setContainer(windowContainer.asBinder())
+ .setAlwaysOnTop(alwaysOnTop)
+ .build();
+ mHierarchyOps.add(hierarchyOp);
+ return this;
+ }
+
+ /**
+ * Sets/removes the reparent leaf task flag for this {@code windowContainer}.
+ * When this is set, the server side will try to reparent the leaf task to task display area
+ * if there is an existing activity in history during the activity launch. This operation only
+ * support on the organized root task.
+ * @hide
+ */
+ @NonNull
+ public WindowContainerTransaction setReparentLeafTaskIfRelaunch(
+ @NonNull WindowContainerToken windowContainer, boolean reparentLeafTaskIfRelaunch) {
+ final HierarchyOp hierarchyOp =
+ new HierarchyOp.Builder(
+ HierarchyOp.HIERARCHY_OP_TYPE_SET_REPARENT_LEAF_TASK_IF_RELAUNCH)
+ .setContainer(windowContainer.asBinder())
+ .setReparentLeafTaskIfRelaunch(reparentLeafTaskIfRelaunch)
+ .build();
+ mHierarchyOps.add(hierarchyOp);
+ return this;
+ }
+
+ /**
+ * Defers client-facing configuration changes for activities in `container` until the end of
+ * the transition animation. The configuration will still be applied to the WMCore hierarchy
+ * at the normal time (beginning); so, special consideration must be made for this in the
+ * animation.
+ *
+ * @param container WindowContainerToken who's children should defer config notification.
+ * @hide
+ */
+ @NonNull
+ public WindowContainerTransaction deferConfigToTransitionEnd(
+ @NonNull WindowContainerToken container) {
+ final Change change = getOrCreateChange(container.asBinder());
+ change.mConfigAtTransitionEnd = true;
+ return this;
+ }
+
+ /**
+ * Sets the task as trimmable or not. This can be used to prevent the task from being trimmed by
+ * recents. This attribute is set to true on task creation by default.
+ *
+ * @param isTrimmableFromRecents When {@code true}, task is set as trimmable from recents.
+ * @hide
+ */
+ @NonNull
+ public WindowContainerTransaction setTaskTrimmableFromRecents(
+ @NonNull WindowContainerToken container,
+ boolean isTrimmableFromRecents) {
+ mHierarchyOps.add(
+ HierarchyOp.createForSetTaskTrimmableFromRecents(container.asBinder(),
+ isTrimmableFromRecents));
+ return this;
+ }
+
+ /*
+ * ===========================================================================================
+ * Hierarchy updates (create/destroy/reorder/reparent containers)
+ * ===========================================================================================
+ */
+
+ /**
* Reorders a container within its parent.
*
* @param onTop When {@code true}, the child goes to the top of parent; otherwise it goes to
@@ -425,6 +480,22 @@ public final class WindowContainerTransaction implements Parcelable {
}
/**
+ * Reparents a container into another one. The effect of a {@code null} parent can vary. For
+ * example, reparenting a stack to {@code null} will reparent it to its display.
+ *
+ * @param onTop When {@code true}, the child goes to the top of parent; otherwise it goes to
+ * the bottom.
+ */
+ @NonNull
+ public WindowContainerTransaction reparent(@NonNull WindowContainerToken child,
+ @Nullable WindowContainerToken parent, boolean onTop) {
+ mHierarchyOps.add(HierarchyOp.createForReparent(child.asBinder(),
+ parent == null ? null : parent.asBinder(),
+ onTop));
+ return this;
+ }
+
+ /**
* Reparent's all children tasks or the top task of {@param currentParent} in the specified
* {@param windowingMode} and {@param activityType} to {@param newParent} in their current
* z-order.
@@ -478,6 +549,116 @@ public final class WindowContainerTransaction implements Parcelable {
}
/**
+ * Finds and removes a task and its children using its container token. The task is removed
+ * from recents.
+ *
+ * If the task is a root task, its leaves are removed but the root task is not. Use
+ * {@link #removeRootTask(WindowContainerToken)} to remove the root task.
+ *
+ * @param containerToken ContainerToken of Task to be removed
+ */
+ @NonNull
+ public WindowContainerTransaction removeTask(@NonNull WindowContainerToken containerToken) {
+ mHierarchyOps.add(HierarchyOp.createForRemoveTask(containerToken.asBinder()));
+ return this;
+ }
+
+ /**
+ * Finds and removes a root task created by an organizer and its leaves using its container
+ * token.
+ *
+ * @param containerToken ContainerToken of the root task to be removed
+ * @hide
+ */
+ @NonNull
+ public WindowContainerTransaction removeRootTask(@NonNull WindowContainerToken containerToken) {
+ mHierarchyOps.add(HierarchyOp.createForRemoveRootTask(containerToken.asBinder()));
+ return this;
+ }
+
+ /**
+ * If `container` was brought to front as a transient-launch (eg. recents), this will reorder
+ * the container back to where it was prior to the transient-launch. This way if a transient
+ * launch is "aborted", the z-ordering of containers in WM should be restored to before the
+ * launch.
+ * @hide
+ */
+ @NonNull
+ public WindowContainerTransaction restoreTransientOrder(
+ @NonNull WindowContainerToken container) {
+ final HierarchyOp hierarchyOp =
+ new HierarchyOp.Builder(HierarchyOp.HIERARCHY_OP_TYPE_RESTORE_TRANSIENT_ORDER)
+ .setContainer(container.asBinder())
+ .build();
+ mHierarchyOps.add(hierarchyOp);
+ return this;
+ }
+
+ /**
+ * Restore the back navigation target from visible to invisible for canceling gesture animation.
+ * @hide
+ */
+ @NonNull
+ public WindowContainerTransaction restoreBackNavi() {
+ final HierarchyOp hierarchyOp =
+ new HierarchyOp.Builder(HierarchyOp.HIERARCHY_OP_TYPE_RESTORE_BACK_NAVIGATION)
+ .build();
+ mHierarchyOps.add(hierarchyOp);
+ return this;
+ }
+
+ /*
+ * ===========================================================================================
+ * Activity launch
+ * ===========================================================================================
+ */
+
+ /**
+ * Starts a task by id. The task is expected to already exist (eg. as a recent task).
+ * @param taskId Id of task to start.
+ * @param options bundle containing ActivityOptions for the task's top activity.
+ * @hide
+ */
+ @NonNull
+ public WindowContainerTransaction startTask(int taskId, @Nullable Bundle options) {
+ mHierarchyOps.add(HierarchyOp.createForTaskLaunch(taskId, options));
+ return this;
+ }
+
+ /**
+ * Sends a pending intent in sync.
+ * @param sender The PendingIntent sender.
+ * @param intent The fillIn intent to patch over the sender's base intent.
+ * @param options bundle containing ActivityOptions for the task's top activity.
+ * @hide
+ */
+ @NonNull
+ public WindowContainerTransaction sendPendingIntent(@Nullable PendingIntent sender,
+ @Nullable Intent intent, @Nullable Bundle options) {
+ mHierarchyOps.add(new HierarchyOp.Builder(HierarchyOp.HIERARCHY_OP_TYPE_PENDING_INTENT)
+ .setLaunchOptions(options)
+ .setPendingIntent(sender)
+ .setActivityIntent(intent)
+ .build());
+ return this;
+ }
+
+ /**
+ * Starts activity(s) from a shortcut.
+ * @param callingPackage The package launching the shortcut.
+ * @param shortcutInfo Information about the shortcut to start
+ * @param options bundle containing ActivityOptions for the task's top activity.
+ * @hide
+ */
+ @NonNull
+ public WindowContainerTransaction startShortcut(@NonNull String callingPackage,
+ @NonNull ShortcutInfo shortcutInfo, @Nullable Bundle options) {
+ mHierarchyOps.add(HierarchyOp.createForStartShortcut(
+ callingPackage, shortcutInfo, options));
+ return this;
+ }
+
+ /**
* Sets whether a container should be the launch root for the specified windowing mode and
* activity type. This currently only applies to Task containers created by organizer.
*/
@@ -491,6 +672,12 @@ public final class WindowContainerTransaction implements Parcelable {
return this;
}
+ /*
+ * ===========================================================================================
+ * Multitasking
+ * ===========================================================================================
+ */
+
/**
* Sets two containers adjacent to each other. Containers below two visible adjacent roots will
* be made invisible. This currently only applies to TaskFragment containers created by
@@ -599,93 +786,162 @@ public final class WindowContainerTransaction implements Parcelable {
return this;
}
+ /*
+ * ===========================================================================================
+ * PIP
+ * ===========================================================================================
+ */
+
/**
- * Starts a task by id. The task is expected to already exist (eg. as a recent task).
- * @param taskId Id of task to start.
- * @param options bundle containing ActivityOptions for the task's top activity.
+ * Moves the PiP activity of a parent task to a pinned root task.
+ * @param parentToken the parent task of the PiP activity
+ * @param bounds the entry bounds
* @hide
*/
@NonNull
- public WindowContainerTransaction startTask(int taskId, @Nullable Bundle options) {
- mHierarchyOps.add(HierarchyOp.createForTaskLaunch(taskId, options));
+ public WindowContainerTransaction movePipActivityToPinnedRootTask(
+ @NonNull WindowContainerToken parentToken, @NonNull Rect bounds) {
+ mHierarchyOps.add(new HierarchyOp
+ .Builder(HierarchyOp.HIERARCHY_OP_TYPE_MOVE_PIP_ACTIVITY_TO_PINNED_TASK)
+ .setContainer(parentToken.asBinder())
+ .setBounds(bounds)
+ .build());
return this;
}
/**
- * Finds and removes a task and its children using its container token. The task is removed
- * from recents.
- *
- * If the task is a root task, its leaves are removed but the root task is not. Use
- * {@link #removeRootTask(WindowContainerToken)} to remove the root task.
- *
- * @param containerToken ContainerToken of Task to be removed
+ * Notify {@link com.android.server.wm.PinnedTaskController} that the picture-in-picture task
+ * has finished the enter animation with the given bounds.
*/
@NonNull
- public WindowContainerTransaction removeTask(@NonNull WindowContainerToken containerToken) {
- mHierarchyOps.add(HierarchyOp.createForRemoveTask(containerToken.asBinder()));
+ public WindowContainerTransaction scheduleFinishEnterPip(
+ @NonNull WindowContainerToken container, @NonNull Rect bounds) {
+ final Change chg = getOrCreateChange(container.asBinder());
+ chg.mPinnedBounds = new Rect(bounds);
+ chg.mChangeMask |= Change.CHANGE_PIP_CALLBACK;
+
return this;
}
+ /*
+ * ===========================================================================================
+ * Insets
+ * ===========================================================================================
+ */
+
/**
- * Finds and removes a root task created by an organizer and its leaves using its container
- * token.
+ * Adds a given {@code Rect} as an insets source frame on the {@code receiver}.
*
- * @param containerToken ContainerToken of the root task to be removed
+ * @param receiver The window container that the insets source is added to.
+ * @param owner The owner of the insets source. An insets source can only be modified by its
+ * owner.
+ * @param index An owner might add multiple insets sources with the same type.
+ * This identifies them.
+ * @param type The {@link InsetsType} of the insets source.
+ * @param frame The rectangle area of the insets source.
+ * @param boundingRects The bounding rects within this inset, relative to the |frame|.
* @hide
*/
@NonNull
- public WindowContainerTransaction removeRootTask(@NonNull WindowContainerToken containerToken) {
- mHierarchyOps.add(HierarchyOp.createForRemoveRootTask(containerToken.asBinder()));
+ public WindowContainerTransaction addInsetsSource(
+ @NonNull WindowContainerToken receiver,
+ @Nullable IBinder owner, int index, @InsetsType int type, @Nullable Rect frame,
+ @Nullable Rect[] boundingRects, @InsetsSource.Flags int flags) {
+ final HierarchyOp hierarchyOp =
+ new HierarchyOp.Builder(HierarchyOp.HIERARCHY_OP_TYPE_ADD_INSETS_FRAME_PROVIDER)
+ .setContainer(receiver.asBinder())
+ .setInsetsFrameProvider(new InsetsFrameProvider(owner, index, type)
+ .setSource(InsetsFrameProvider.SOURCE_ARBITRARY_RECTANGLE)
+ .setArbitraryRectangle(frame)
+ .setBoundingRects(boundingRects)
+ .setFlags(flags))
+ .setInsetsFrameOwner(owner)
+ .build();
+ mHierarchyOps.add(hierarchyOp);
return this;
}
/**
- * Sets whether a container is being drag-resized.
- * When {@code true}, the client will reuse a single (larger) surface size to avoid
- * continuous allocations on every size change.
+ * Removes the insets source from the {@code receiver}.
*
- * @param container WindowContainerToken of the task that changed its drag resizing state
+ * @param receiver The window container that the insets source was added to.
+ * @param owner The owner of the insets source. An insets source can only be modified by its
+ * owner.
+ * @param index An owner might add multiple insets sources with the same type.
+ * This identifies them.
+ * @param type The {@link InsetsType} of the insets source.
* @hide
*/
@NonNull
- public WindowContainerTransaction setDragResizing(@NonNull WindowContainerToken container,
- boolean dragResizing) {
- final Change change = getOrCreateChange(container.asBinder());
- change.mChangeMask |= Change.CHANGE_DRAG_RESIZING;
- change.mDragResizing = dragResizing;
+ public WindowContainerTransaction removeInsetsSource(@NonNull WindowContainerToken receiver,
+ @Nullable IBinder owner, int index, @InsetsType int type) {
+ final HierarchyOp hierarchyOp =
+ new HierarchyOp.Builder(HierarchyOp.HIERARCHY_OP_TYPE_REMOVE_INSETS_FRAME_PROVIDER)
+ .setContainer(receiver.asBinder())
+ .setInsetsFrameProvider(new InsetsFrameProvider(owner, index, type))
+ .setInsetsFrameOwner(owner)
+ .build();
+ mHierarchyOps.add(hierarchyOp);
return this;
}
+ /*
+ * ===========================================================================================
+ * Keyguard
+ * ===========================================================================================
+ */
+
/**
- * Sends a pending intent in sync.
- * @param sender The PendingIntent sender.
- * @param intent The fillIn intent to patch over the sender's base intent.
- * @param options bundle containing ActivityOptions for the task's top activity.
+ * Adds a {@link KeyguardState} to apply to the given displays.
+ *
* @hide
*/
@NonNull
- public WindowContainerTransaction sendPendingIntent(@Nullable PendingIntent sender,
- @Nullable Intent intent, @Nullable Bundle options) {
- mHierarchyOps.add(new HierarchyOp.Builder(HierarchyOp.HIERARCHY_OP_TYPE_PENDING_INTENT)
- .setLaunchOptions(options)
- .setPendingIntent(sender)
- .setActivityIntent(intent)
- .build());
+ public WindowContainerTransaction addKeyguardState(@NonNull KeyguardState keyguardState) {
+ Objects.requireNonNull(keyguardState);
+ final HierarchyOp hierarchyOp =
+ new HierarchyOp.Builder(
+ HierarchyOp.HIERARCHY_OP_TYPE_SET_KEYGUARD_STATE)
+ .setKeyguardState(keyguardState)
+ .build();
+ mHierarchyOps.add(hierarchyOp);
return this;
}
+ /*
+ * ===========================================================================================
+ * Task fragments
+ * ===========================================================================================
+ */
+
/**
- * Starts activity(s) from a shortcut.
- * @param callingPackage The package launching the shortcut.
- * @param shortcutInfo Information about the shortcut to start
- * @param options bundle containing ActivityOptions for the task's top activity.
+ * Sets the {@link TaskFragmentOrganizer} that applies this {@link WindowContainerTransaction}.
+ * When this is set, the server side will not check for the permission of
+ * {@link android.Manifest.permission#MANAGE_ACTIVITY_TASKS}, but will ensure this WCT only
+ * contains operations that are allowed for this organizer, such as modifying TaskFragments that
+ * are organized by this organizer.
* @hide
*/
@NonNull
- public WindowContainerTransaction startShortcut(@NonNull String callingPackage,
- @NonNull ShortcutInfo shortcutInfo, @Nullable Bundle options) {
- mHierarchyOps.add(HierarchyOp.createForStartShortcut(
- callingPackage, shortcutInfo, options));
+ public WindowContainerTransaction setTaskFragmentOrganizer(
+ @NonNull ITaskFragmentOrganizer organizer) {
+ mTaskFragmentOrganizer = organizer;
+ return this;
+ }
+
+ /**
+ * When this {@link WindowContainerTransaction} failed to finish on the server side, it will
+ * trigger callback with this {@param errorCallbackToken}.
+ * @param errorCallbackToken client provided token that will be passed back as parameter in
+ * the callback if there is an error on the server side.
+ * @see ITaskFragmentOrganizer#onTaskFragmentError
+ */
+ @NonNull
+ public WindowContainerTransaction setErrorCallbackToken(@NonNull IBinder errorCallbackToken) {
+ if (mErrorCallbackToken != null) {
+ throw new IllegalStateException("Can't set multiple error token for one transaction.");
+ }
+ mErrorCallbackToken = errorCallbackToken;
return this;
}
@@ -793,93 +1049,6 @@ public final class WindowContainerTransaction implements Parcelable {
}
/**
- * If `container` was brought to front as a transient-launch (eg. recents), this will reorder
- * the container back to where it was prior to the transient-launch. This way if a transient
- * launch is "aborted", the z-ordering of containers in WM should be restored to before the
- * launch.
- * @hide
- */
- @NonNull
- public WindowContainerTransaction restoreTransientOrder(
- @NonNull WindowContainerToken container) {
- final HierarchyOp hierarchyOp =
- new HierarchyOp.Builder(HierarchyOp.HIERARCHY_OP_TYPE_RESTORE_TRANSIENT_ORDER)
- .setContainer(container.asBinder())
- .build();
- mHierarchyOps.add(hierarchyOp);
- return this;
- }
-
- /**
- * Restore the back navigation target from visible to invisible for canceling gesture animation.
- * @hide
- */
- @NonNull
- public WindowContainerTransaction restoreBackNavi() {
- final HierarchyOp hierarchyOp =
- new HierarchyOp.Builder(HierarchyOp.HIERARCHY_OP_TYPE_RESTORE_BACK_NAVIGATION)
- .build();
- mHierarchyOps.add(hierarchyOp);
- return this;
- }
-
- /**
- * Adds a given {@code Rect} as an insets source frame on the {@code receiver}.
- *
- * @param receiver The window container that the insets source is added to.
- * @param owner The owner of the insets source. An insets source can only be modified by its
- * owner.
- * @param index An owner might add multiple insets sources with the same type.
- * This identifies them.
- * @param type The {@link InsetsType} of the insets source.
- * @param frame The rectangle area of the insets source.
- * @param boundingRects The bounding rects within this inset, relative to the |frame|.
- * @hide
- */
- @NonNull
- public WindowContainerTransaction addInsetsSource(
- @NonNull WindowContainerToken receiver,
- @Nullable IBinder owner, int index, @InsetsType int type, @Nullable Rect frame,
- @Nullable Rect[] boundingRects, @InsetsSource.Flags int flags) {
- final HierarchyOp hierarchyOp =
- new HierarchyOp.Builder(HierarchyOp.HIERARCHY_OP_TYPE_ADD_INSETS_FRAME_PROVIDER)
- .setContainer(receiver.asBinder())
- .setInsetsFrameProvider(new InsetsFrameProvider(owner, index, type)
- .setSource(InsetsFrameProvider.SOURCE_ARBITRARY_RECTANGLE)
- .setArbitraryRectangle(frame)
- .setBoundingRects(boundingRects)
- .setFlags(flags))
- .setInsetsFrameOwner(owner)
- .build();
- mHierarchyOps.add(hierarchyOp);
- return this;
- }
-
- /**
- * Removes the insets source from the {@code receiver}.
- *
- * @param receiver The window container that the insets source was added to.
- * @param owner The owner of the insets source. An insets source can only be modified by its
- * owner.
- * @param index An owner might add multiple insets sources with the same type.
- * This identifies them.
- * @param type The {@link InsetsType} of the insets source.
- * @hide
- */
- @NonNull
- public WindowContainerTransaction removeInsetsSource(@NonNull WindowContainerToken receiver,
- @Nullable IBinder owner, int index, @InsetsType int type) {
- final HierarchyOp hierarchyOp =
- new HierarchyOp.Builder(HierarchyOp.HIERARCHY_OP_TYPE_REMOVE_INSETS_FRAME_PROVIDER)
- .setContainer(receiver.asBinder())
- .setInsetsFrameProvider(new InsetsFrameProvider(owner, index, type))
- .setInsetsFrameOwner(owner)
- .build();
- mHierarchyOps.add(hierarchyOp);
- return this;
- }
-
- /**
* Requests focus on the top running Activity in the given TaskFragment. This will only take
* effect if there is no focus, or if the current focus is in the same Task as the requested
* TaskFragment.
@@ -961,157 +1130,6 @@ public final class WindowContainerTransaction implements Parcelable {
}
/**
- * Adds a {@link KeyguardState} to apply to the given displays.
- *
- * @hide
- */
- @NonNull
- public WindowContainerTransaction addKeyguardState(@NonNull KeyguardState keyguardState) {
- Objects.requireNonNull(keyguardState);
- final HierarchyOp hierarchyOp =
- new HierarchyOp.Builder(
- HierarchyOp.HIERARCHY_OP_TYPE_SET_KEYGUARD_STATE)
- .setKeyguardState(keyguardState)
- .build();
- mHierarchyOps.add(hierarchyOp);
- return this;
- }
-
- /**
- * Sets/removes the always on top flag for this {@code windowContainer}. See
- * {@link com.android.server.wm.ConfigurationContainer#setAlwaysOnTop(boolean)}.
- * Please note that this method is only intended to be used for a
- * {@link com.android.server.wm.Task} or {@link com.android.server.wm.DisplayArea}.
- *
- * <p>
- * Setting always on top to {@code True} will also make the {@code windowContainer} to move
- * to the top.
- * </p>
- * <p>
- * Setting always on top to {@code False} will make this {@code windowContainer} to move
- * below the other always on top sibling containers.
- * </p>
- *
- * @param windowContainer the container which the flag need to be updated for.
- * @param alwaysOnTop denotes whether or not always on top flag should be set.
- * @hide
- */
- @NonNull
- public WindowContainerTransaction setAlwaysOnTop(
- @NonNull WindowContainerToken windowContainer, boolean alwaysOnTop) {
- final HierarchyOp hierarchyOp =
- new HierarchyOp.Builder(
- HierarchyOp.HIERARCHY_OP_TYPE_SET_ALWAYS_ON_TOP)
- .setContainer(windowContainer.asBinder())
- .setAlwaysOnTop(alwaysOnTop)
- .build();
- mHierarchyOps.add(hierarchyOp);
- return this;
- }
-
- /**
- * When this {@link WindowContainerTransaction} failed to finish on the server side, it will
- * trigger callback with this {@param errorCallbackToken}.
- * @param errorCallbackToken client provided token that will be passed back as parameter in
- * the callback if there is an error on the server side.
- * @see ITaskFragmentOrganizer#onTaskFragmentError
- */
- @NonNull
- public WindowContainerTransaction setErrorCallbackToken(@NonNull IBinder errorCallbackToken) {
- if (mErrorCallbackToken != null) {
- throw new IllegalStateException("Can't set multiple error token for one transaction.");
- }
- mErrorCallbackToken = errorCallbackToken;
- return this;
- }
-
- /**
- * Sets the {@link TaskFragmentOrganizer} that applies this {@link WindowContainerTransaction}.
- * When this is set, the server side will not check for the permission of
- * {@link android.Manifest.permission#MANAGE_ACTIVITY_TASKS}, but will ensure this WCT only
- * contains operations that are allowed for this organizer, such as modifying TaskFragments that
- * are organized by this organizer.
- * @hide
- */
- @NonNull
- public WindowContainerTransaction setTaskFragmentOrganizer(
- @NonNull ITaskFragmentOrganizer organizer) {
- mTaskFragmentOrganizer = organizer;
- return this;
- }
-
- /**
- * Sets/removes the reparent leaf task flag for this {@code windowContainer}.
- * When this is set, the server side will try to reparent the leaf task to task display area
- * if there is an existing activity in history during the activity launch. This operation only
- * support on the organized root task.
- * @hide
- */
- @NonNull
- public WindowContainerTransaction setReparentLeafTaskIfRelaunch(
- @NonNull WindowContainerToken windowContainer, boolean reparentLeafTaskIfRelaunch) {
- final HierarchyOp hierarchyOp =
- new HierarchyOp.Builder(
- HierarchyOp.HIERARCHY_OP_TYPE_SET_REPARENT_LEAF_TASK_IF_RELAUNCH)
- .setContainer(windowContainer.asBinder())
- .setReparentLeafTaskIfRelaunch(reparentLeafTaskIfRelaunch)
- .build();
- mHierarchyOps.add(hierarchyOp);
- return this;
- }
-
- /**
- * Moves the PiP activity of a parent task to a pinned root task.
- * @param parentToken the parent task of the PiP activity
- * @param bounds the entry bounds
- * @hide
- */
- @NonNull
- public WindowContainerTransaction movePipActivityToPinnedRootTask(
- @NonNull WindowContainerToken parentToken, @NonNull Rect bounds) {
- mHierarchyOps.add(new HierarchyOp
- .Builder(HierarchyOp.HIERARCHY_OP_TYPE_MOVE_PIP_ACTIVITY_TO_PINNED_TASK)
- .setContainer(parentToken.asBinder())
- .setBounds(bounds)
- .build());
- return this;
- }
-
- /**
- * Defers client-facing configuration changes for activities in `container` until the end of
- * the transition animation. The configuration will still be applied to the WMCore hierarchy
- * at the normal time (beginning); so, special consideration must be made for this in the
- * animation.
- *
- * @param container WindowContainerToken who's children should defer config notification.
- * @hide
- */
- @NonNull
- public WindowContainerTransaction deferConfigToTransitionEnd(
- @NonNull WindowContainerToken container) {
- final Change change = getOrCreateChange(container.asBinder());
- change.mConfigAtTransitionEnd = true;
- return this;
- }
-
- /**
- * Sets the task as trimmable or not. This can be used to prevent the task from being trimmed by
- * recents. This attribute is set to true on task creation by default.
- *
- * @param isTrimmableFromRecents When {@code true}, task is set as trimmable from recents.
- * @hide
- */
- @NonNull
- public WindowContainerTransaction setTaskTrimmableFromRecents(
- @NonNull WindowContainerToken container,
- boolean isTrimmableFromRecents) {
- mHierarchyOps.add(
- HierarchyOp.createForSetTaskTrimmableFromRecents(container.asBinder(),
- isTrimmableFromRecents));
- return this;
- }
-
- /**
* Merges another WCT into this one.
* @param transfer When true, this will transfer everything from other potentially leaving
* other in an unusable state. When false, other is left alone, but
@@ -1206,7 +1224,7 @@ public final class WindowContainerTransaction implements Parcelable {
@NonNull
public static final Creator<WindowContainerTransaction> CREATOR =
- new Creator<WindowContainerTransaction>() {
+ new Creator<>() {
@Override
public WindowContainerTransaction createFromParcel(@NonNull Parcel in) {
return new WindowContainerTransaction(in);
@@ -1227,19 +1245,17 @@ public final class WindowContainerTransaction implements Parcelable {
public static final int CHANGE_BOUNDS_TRANSACTION = 1 << 1;
public static final int CHANGE_PIP_CALLBACK = 1 << 2;
public static final int CHANGE_HIDDEN = 1 << 3;
- public static final int CHANGE_BOUNDS_TRANSACTION_RECT = 1 << 4;
- public static final int CHANGE_IGNORE_ORIENTATION_REQUEST = 1 << 5;
- public static final int CHANGE_FORCE_NO_PIP = 1 << 6;
- public static final int CHANGE_FORCE_TRANSLUCENT = 1 << 7;
- public static final int CHANGE_DRAG_RESIZING = 1 << 8;
- public static final int CHANGE_RELATIVE_BOUNDS = 1 << 9;
+ public static final int CHANGE_IGNORE_ORIENTATION_REQUEST = 1 << 4;
+ public static final int CHANGE_FORCE_NO_PIP = 1 << 5;
+ public static final int CHANGE_FORCE_TRANSLUCENT = 1 << 6;
+ public static final int CHANGE_DRAG_RESIZING = 1 << 7;
+ public static final int CHANGE_RELATIVE_BOUNDS = 1 << 8;
@IntDef(flag = true, prefix = { "CHANGE_" }, value = {
CHANGE_FOCUSABLE,
CHANGE_BOUNDS_TRANSACTION,
CHANGE_PIP_CALLBACK,
CHANGE_HIDDEN,
- CHANGE_BOUNDS_TRANSACTION_RECT,
CHANGE_IGNORE_ORIENTATION_REQUEST,
CHANGE_FORCE_NO_PIP,
CHANGE_FORCE_TRANSLUCENT,
@@ -1262,7 +1278,6 @@ public final class WindowContainerTransaction implements Parcelable {
private Rect mPinnedBounds = null;
private SurfaceControl.Transaction mBoundsChangeTransaction = null;
- private Rect mBoundsChangeSurfaceBounds = null;
@Nullable
private Rect mRelativeBounds = null;
private boolean mConfigAtTransitionEnd = false;
@@ -1290,10 +1305,6 @@ public final class WindowContainerTransaction implements Parcelable {
mBoundsChangeTransaction =
SurfaceControl.Transaction.CREATOR.createFromParcel(in);
}
- if ((mChangeMask & Change.CHANGE_BOUNDS_TRANSACTION_RECT) != 0) {
- mBoundsChangeSurfaceBounds = new Rect();
- mBoundsChangeSurfaceBounds.readFromParcel(in);
- }
if ((mChangeMask & Change.CHANGE_RELATIVE_BOUNDS) != 0) {
mRelativeBounds = new Rect();
mRelativeBounds.readFromParcel(in);
@@ -1342,10 +1353,6 @@ public final class WindowContainerTransaction implements Parcelable {
if (other.mWindowingMode >= WINDOWING_MODE_UNDEFINED) {
mWindowingMode = other.mWindowingMode;
}
- if (other.mBoundsChangeSurfaceBounds != null) {
- mBoundsChangeSurfaceBounds = transfer ? other.mBoundsChangeSurfaceBounds
- : new Rect(other.mBoundsChangeSurfaceBounds);
- }
if (other.mRelativeBounds != null) {
mRelativeBounds = transfer
? other.mRelativeBounds
@@ -1446,11 +1453,6 @@ public final class WindowContainerTransaction implements Parcelable {
}
@Nullable
- public Rect getBoundsChangeSurfaceBounds() {
- return mBoundsChangeSurfaceBounds;
- }
-
- @Nullable
public Rect getRelativeBounds() {
return mRelativeBounds;
}
@@ -1529,9 +1531,6 @@ public final class WindowContainerTransaction implements Parcelable {
if (mBoundsChangeTransaction != null) {
mBoundsChangeTransaction.writeToParcel(dest, flags);
}
- if (mBoundsChangeSurfaceBounds != null) {
- mBoundsChangeSurfaceBounds.writeToParcel(dest, flags);
- }
if (mRelativeBounds != null) {
mRelativeBounds.writeToParcel(dest, flags);
}
diff --git a/core/java/android/window/flags/device_state_auto_rotate_setting.aconfig b/core/java/android/window/flags/device_state_auto_rotate_setting.aconfig
new file mode 100644
index 000000000000..bb66989b9946
--- /dev/null
+++ b/core/java/android/window/flags/device_state_auto_rotate_setting.aconfig
@@ -0,0 +1,22 @@
+package: "com.android.window.flags"
+container: "system"
+
+flag {
+ name: "enable_device_state_auto_rotate_setting_logging"
+ namespace: "windowing_frontend"
+ description: "Enable device state auto rotate setting logging"
+ bug: "391147112"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
+ name: "enable_device_state_auto_rotate_setting_refactor"
+ namespace: "windowing_frontend"
+ description: "Enable refactored device state auto rotate setting logic"
+ bug: "350946537"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+} \ 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 b4e7675402b9..222088e8a8b9 100644
--- a/core/java/android/window/flags/lse_desktop_experience.aconfig
+++ b/core/java/android/window/flags/lse_desktop_experience.aconfig
@@ -555,4 +555,41 @@ flag {
metadata {
purpose: PURPOSE_BUGFIX
}
-} \ No newline at end of file
+}
+
+flag {
+ name: "show_desktop_experience_dev_option"
+ namespace: "lse_desktop_experience"
+ description: "Replace the freeform windowing dev options with a desktop experience one."
+ bug: "389092752"
+}
+
+flag {
+ name: "enable_quickswitch_desktop_split_bugfix"
+ namespace: "lse_desktop_experience"
+ description: "Enables splitting QuickSwitch between fullscreen apps and Desktop workspaces."
+ bug: "345296916"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
+ name: "enable_desktop_windowing_exit_by_minimize_transition_bugfix"
+ namespace: "lse_desktop_experience"
+ description: "Enables exit desktop windowing by minimize transition & motion polish changes"
+ bug: "390161102"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
+ name: "enable_start_launch_transition_from_taskbar_bugfix"
+ namespace: "lse_desktop_experience"
+ description: "Enables starting a launch transition directly from the taskbar if desktop tasks are visible."
+ bug: "361366053"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/core/java/android/window/flags/responsible_apis.aconfig b/core/java/android/window/flags/responsible_apis.aconfig
index 4b5adfcc2c9b..36219812c002 100644
--- a/core/java/android/window/flags/responsible_apis.aconfig
+++ b/core/java/android/window/flags/responsible_apis.aconfig
@@ -81,3 +81,10 @@ flag {
description: "Strict mode violation triggered by grace period usage"
bug: "384807495"
}
+
+flag {
+ name: "bal_clear_allowlist_duration"
+ namespace: "responsible_apis"
+ description: "Clear the allowlist duration when clearAllowBgActivityStarts is called"
+ bug: "322159724"
+}
diff --git a/core/java/com/android/internal/os/BatteryStatsHistory.java b/core/java/com/android/internal/os/BatteryStatsHistory.java
index dc440e36ca0d..f49c5f1c2b0f 100644
--- a/core/java/com/android/internal/os/BatteryStatsHistory.java
+++ b/core/java/com/android/internal/os/BatteryStatsHistory.java
@@ -84,7 +84,7 @@ public class BatteryStatsHistory {
private static final String TAG = "BatteryStatsHistory";
// Current on-disk Parcel version. Must be updated when the format of the parcelable changes
- private static final int VERSION = 211;
+ private static final int VERSION = 212;
private static final String HISTORY_DIR = "battery-history";
private static final String FILE_SUFFIX = ".bh";
@@ -211,6 +211,8 @@ public class BatteryStatsHistory {
private final MonotonicClock mMonotonicClock;
// Monotonic time when we started writing to the history buffer
private long mHistoryBufferStartTime;
+ // Monotonic time when the last event was written to the history buffer
+ private long mHistoryMonotonicEndTime;
// Monotonically increasing size of written history
private long mMonotonicHistorySize;
private final ArraySet<PowerStats.Descriptor> mWrittenPowerStatsDescriptors = new ArraySet<>();
@@ -423,13 +425,22 @@ public class BatteryStatsHistory {
return file;
}
- void writeToParcel(Parcel out, boolean useBlobs) {
+ void writeToParcel(Parcel out, boolean useBlobs,
+ long preferredEarliestIncludedTimestampMs) {
Trace.traceBegin(TRACE_TAG_SYSTEM_SERVER, "BatteryStatsHistory.writeToParcel");
lock();
try {
final long start = SystemClock.uptimeMillis();
- out.writeInt(mHistoryFiles.size() - 1);
for (int i = 0; i < mHistoryFiles.size() - 1; i++) {
+ long monotonicEndTime = Long.MAX_VALUE;
+ if (i < mHistoryFiles.size() - 1) {
+ monotonicEndTime = mHistoryFiles.get(i + 1).monotonicTimeMs;
+ }
+
+ if (monotonicEndTime < preferredEarliestIncludedTimestampMs) {
+ continue;
+ }
+
AtomicFile file = mHistoryFiles.get(i).atomicFile;
byte[] raw = new byte[0];
try {
@@ -437,6 +448,8 @@ public class BatteryStatsHistory {
} catch (Exception e) {
Slog.e(TAG, "Error reading file " + file.getBaseFile().getPath(), e);
}
+
+ out.writeBoolean(true);
if (useBlobs) {
out.writeBlob(raw);
} else {
@@ -444,6 +457,7 @@ public class BatteryStatsHistory {
out.writeByteArray(raw);
}
}
+ out.writeBoolean(false);
if (DEBUG) {
Slog.d(TAG,
"writeToParcel duration ms:" + (SystemClock.uptimeMillis() - start));
@@ -634,6 +648,7 @@ public class BatteryStatsHistory {
mWritableHistory = writableHistory;
if (mWritableHistory != null) {
mMutable = false;
+ mHistoryMonotonicEndTime = mWritableHistory.mHistoryMonotonicEndTime;
}
if (historyBuffer != null) {
@@ -937,6 +952,8 @@ public class BatteryStatsHistory {
}
// skip monotonic time field.
p.readLong();
+ // skip monotonic end time field
+ p.readLong();
// skip monotonic size field
p.readLong();
@@ -996,6 +1013,8 @@ public class BatteryStatsHistory {
}
// skip monotonic time field.
out.readLong();
+ // skip monotonic end time field
+ out.readLong();
// skip monotonic size field
out.readLong();
return true;
@@ -1024,6 +1043,7 @@ public class BatteryStatsHistory {
p.setDataPosition(0);
p.readInt(); // Skip the version field
long monotonicTime = p.readLong();
+ p.readLong(); // Skip monotonic end time field
p.readLong(); // Skip monotonic size field
p.setDataPosition(pos);
return monotonicTime;
@@ -1086,7 +1106,10 @@ public class BatteryStatsHistory {
public void writeToParcel(Parcel out) {
synchronized (this) {
writeHistoryBuffer(out);
- writeToParcel(out, false /* useBlobs */);
+ /* useBlobs */
+ if (mHistoryDir != null) {
+ mHistoryDir.writeToParcel(out, false /* useBlobs */, 0);
+ }
}
}
@@ -1096,16 +1119,13 @@ public class BatteryStatsHistory {
*
* @param out the output parcel
*/
- public void writeToBatteryUsageStatsParcel(Parcel out) {
+ public void writeToBatteryUsageStatsParcel(Parcel out, long preferredHistoryDurationMs) {
synchronized (this) {
out.writeBlob(mHistoryBuffer.marshall());
- writeToParcel(out, true /* useBlobs */);
- }
- }
-
- private void writeToParcel(Parcel out, boolean useBlobs) {
- if (mHistoryDir != null) {
- mHistoryDir.writeToParcel(out, useBlobs);
+ if (mHistoryDir != null) {
+ mHistoryDir.writeToParcel(out, true /* useBlobs */,
+ mHistoryMonotonicEndTime - preferredHistoryDurationMs);
+ }
}
}
@@ -1166,8 +1186,7 @@ public class BatteryStatsHistory {
private void readFromParcel(Parcel in, boolean useBlobs) {
final long start = SystemClock.uptimeMillis();
mHistoryParcels = new ArrayList<>();
- final int count = in.readInt();
- for (int i = 0; i < count; i++) {
+ while (in.readBoolean()) {
byte[] temp = useBlobs ? in.readBlob() : in.createByteArray();
if (temp == null || temp.length == 0) {
continue;
@@ -2081,6 +2100,8 @@ public class BatteryStatsHistory {
*/
@GuardedBy("this")
private void writeHistoryDelta(Parcel dest, HistoryItem cur, HistoryItem last) {
+ mHistoryMonotonicEndTime = cur.time;
+
if (last == null || cur.cmd != HistoryItem.CMD_UPDATE) {
dest.writeInt(BatteryStatsHistory.DELTA_TIME_ABS);
cur.writeToParcel(dest, 0);
@@ -2396,6 +2417,7 @@ public class BatteryStatsHistory {
}
mHistoryBufferStartTime = in.readLong();
+ mHistoryMonotonicEndTime = in.readLong();
mMonotonicHistorySize = in.readLong();
mHistoryBuffer.setDataSize(0);
@@ -2424,6 +2446,7 @@ public class BatteryStatsHistory {
private void writeHistoryBuffer(Parcel out) {
out.writeInt(BatteryStatsHistory.VERSION);
out.writeLong(mHistoryBufferStartTime);
+ out.writeLong(mHistoryMonotonicEndTime);
out.writeLong(mMonotonicHistorySize);
out.writeInt(mHistoryBuffer.dataSize());
if (DEBUG) {
diff --git a/core/java/com/android/internal/protolog/ProcessedPerfettoProtoLogImpl.java b/core/java/com/android/internal/protolog/ProcessedPerfettoProtoLogImpl.java
index e0a77d2be724..1f9df3cc842a 100644
--- a/core/java/com/android/internal/protolog/ProcessedPerfettoProtoLogImpl.java
+++ b/core/java/com/android/internal/protolog/ProcessedPerfettoProtoLogImpl.java
@@ -28,6 +28,7 @@ import com.android.internal.protolog.common.IProtoLogGroup;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
+import java.io.IOException;
import java.util.ArrayList;
public class ProcessedPerfettoProtoLogImpl extends PerfettoProtoLogImpl {
@@ -161,15 +162,39 @@ public class ProcessedPerfettoProtoLogImpl extends PerfettoProtoLogImpl {
messageString = message.getMessage(mViewerConfigReader);
if (messageString == null) {
- throw new RuntimeException("Failed to decode message for logcat. "
- + "Message hash (" + message.getMessageHash() + ") either not available in "
- + "viewerConfig file (" + mViewerConfigFilePath + ") or "
- + "not loaded into memory from file before decoding.");
+ // Either we failed to load the config for this log message from the viewer config file
+ // into memory, or the message hash is simply not available in the viewer config file.
+ // We want to confirm that the message hash is not available in the viewer config file
+ // before throwing an exception.
+ throw new RuntimeException(getReasonForFailureToGetMessageString(message));
}
return messageString;
}
+ private String getReasonForFailureToGetMessageString(Message message) {
+ if (message.getMessageHash() == null) {
+ return "Trying to get message from null message hash";
+ }
+
+ try {
+ if (mViewerConfigReader.messageHashIsAvailableInFile(message.getMessageHash())) {
+ return "Failed to decode message for logcat logging. "
+ + "Message hash (" + message.getMessageHash() + ") is not available in "
+ + "viewerConfig file (" + mViewerConfigFilePath + "). This might be due "
+ + "to the viewer config file and the executing code being out of sync.";
+ } else {
+ return "Failed to decode message for logcat. "
+ + "Message hash (" + message.getMessageHash() + ") was available in the "
+ + "viewerConfig file (" + mViewerConfigFilePath + ") but wasn't loaded "
+ + "into memory from file before decoding! This is likely a bug.";
+ }
+ } catch (IOException e) {
+ return "Failed to get string message to log but could not identify the root cause due "
+ + "to an IO error in reading the viewer config file.";
+ }
+ }
+
private void loadLogcatGroupsViewerConfig(@NonNull IProtoLogGroup[] protoLogGroups) {
final var groupsLoggingToLogcat = new ArrayList<String>();
for (IProtoLogGroup protoLogGroup : protoLogGroups) {
diff --git a/core/java/com/android/internal/protolog/ProtoLogViewerConfigReader.java b/core/java/com/android/internal/protolog/ProtoLogViewerConfigReader.java
index 524f64225084..f77179949fbf 100644
--- a/core/java/com/android/internal/protolog/ProtoLogViewerConfigReader.java
+++ b/core/java/com/android/internal/protolog/ProtoLogViewerConfigReader.java
@@ -100,6 +100,36 @@ public class ProtoLogViewerConfigReader {
}
}
+ /**
+ * Return whether or not the viewer config file contains a message with the specified hash.
+ * @param messageHash The hash message we are looking for in the viewer config file
+ * @return True iff the message with message hash is contained in the viewer config.
+ * @throws IOException if there was an issue reading the viewer config file.
+ */
+ public boolean messageHashIsAvailableInFile(long messageHash)
+ throws IOException {
+ try (var pisWrapper = mViewerConfigInputStreamProvider.getInputStream()) {
+ final var pis = pisWrapper.get();
+ while (pis.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+ if (pis.getFieldNumber() == (int) MESSAGES) {
+ final long inMessageToken = pis.start(MESSAGES);
+
+ while (pis.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+ if (pis.getFieldNumber() == (int) MESSAGE_ID) {
+ if (pis.readLong(MESSAGE_ID) == messageHash) {
+ return true;
+ }
+ }
+ }
+
+ pis.end(inMessageToken);
+ }
+ }
+ }
+
+ return false;
+ }
+
@NonNull
private Map<Long, String> loadViewerConfigMappingForGroup(@NonNull String group)
throws IOException {
diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl
index ec3975205542..72cb9d1a20ac 100644
--- a/core/java/com/android/internal/statusbar/IStatusBar.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl
@@ -51,6 +51,14 @@ oneway interface IStatusBar
void showWirelessChargingAnimation(int batteryLevel);
+ /**
+ * Sets the new IME window status.
+ *
+ * @param displayId The id of the display to which the IME is bound.
+ * @param vis The IME window visibility.
+ * @param backDisposition The IME back disposition mode.
+ * @param showImeSwitcher Whether the IME Switcher button should be shown.
+ */
void setImeWindowStatus(int displayId, int vis, int backDisposition, boolean showImeSwitcher);
void setWindowState(int display, int window, int state);
diff --git a/core/java/com/android/internal/statusbar/IStatusBarService.aidl b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
index ec0954d5590a..1fa1e0bbc69a 100644
--- a/core/java/com/android/internal/statusbar/IStatusBarService.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
@@ -61,6 +61,14 @@ interface IStatusBarService
void setIconVisibility(String slot, boolean visible);
@UnsupportedAppUsage
void removeIcon(String slot);
+ /**
+ * Sets the new IME window status.
+ *
+ * @param displayId The id of the display to which the IME is bound.
+ * @param vis The IME window visibility.
+ * @param backDisposition The IME back disposition mode.
+ * @param showImeSwitcher Whether the IME Switcher button should be shown.
+ */
void setImeWindowStatus(int displayId, int vis, int backDisposition, boolean showImeSwitcher);
void expandSettingsPanel(String subPanel);
diff --git a/core/java/com/android/internal/widget/ConversationLayout.java b/core/java/com/android/internal/widget/ConversationLayout.java
index b3ab5d3cd258..04ce9bcd7afd 100644
--- a/core/java/com/android/internal/widget/ConversationLayout.java
+++ b/core/java/com/android/internal/widget/ConversationLayout.java
@@ -105,6 +105,7 @@ public class ConversationLayout extends FrameLayout
private ArrayList<MessagingGroup> mAddedGroups = new ArrayList<>();
private Person mUser;
private CharSequence mNameReplacement;
+ private CharSequence mSummarizedContent;
private boolean mIsCollapsed;
private ImageResolver mImageResolver;
private CachingIconView mConversationIconView;
@@ -397,7 +398,7 @@ public class ConversationLayout extends FrameLayout
*
* @param isCollapsed is it collapsed
*/
- @RemotableViewMethod
+ @RemotableViewMethod(asyncImpl = "setIsCollapsedAsync")
public void setIsCollapsed(boolean isCollapsed) {
mIsCollapsed = isCollapsed;
mMessagingLinearLayout.setMaxDisplayedLines(isCollapsed ? 1 : Integer.MAX_VALUE);
@@ -406,6 +407,15 @@ public class ConversationLayout extends FrameLayout
}
/**
+ * setDataAsync needs to do different stuff for the collapsed vs expanded view, so store the
+ * collapsed state early.
+ */
+ public Runnable setIsCollapsedAsync(boolean isCollapsed) {
+ mIsCollapsed = isCollapsed;
+ return () -> setIsCollapsed(isCollapsed);
+ }
+
+ /**
* Set conversation data
*
* @param extras Bundle contains conversation data
@@ -439,8 +449,16 @@ public class ConversationLayout extends FrameLayout
extras.getBoolean(Notification.EXTRA_SHOW_REMOTE_INPUT_SPINNER, false);
int unreadCount = extras.getInt(Notification.EXTRA_CONVERSATION_UNREAD_MESSAGE_COUNT);
- final List<MessagingMessage> newMessagingMessages =
- createMessages(newMessages, /* isHistoric= */false, usePrecomputedText);
+ List<MessagingMessage> newMessagingMessages;
+ mSummarizedContent = extras.getCharSequence(Notification.EXTRA_SUMMARIZED_CONTENT);
+ if (mSummarizedContent != null && mIsCollapsed) {
+ Notification.MessagingStyle.Message summary =
+ new Notification.MessagingStyle.Message(mSummarizedContent, 0, "");
+ newMessagingMessages = createMessages(List.of(summary), false, usePrecomputedText);
+ } else {
+ newMessagingMessages =
+ createMessages(newMessages, /* isHistoric= */false, usePrecomputedText);
+ }
final List<MessagingMessage> newHistoricMessagingMessages =
createMessages(newHistoricMessages, /* isHistoric= */true, usePrecomputedText);
@@ -463,7 +481,7 @@ public class ConversationLayout extends FrameLayout
return new MessagingData(user, showSpinner, unreadCount,
newHistoricMessagingMessages, newMessagingMessages, groups, senders,
- conversationHeaderData);
+ conversationHeaderData, mSummarizedContent);
}
/**
@@ -1622,6 +1640,9 @@ public class ConversationLayout extends FrameLayout
@Nullable
public CharSequence getConversationText() {
+ if (mSummarizedContent != null) {
+ return mSummarizedContent;
+ }
if (mMessages.isEmpty()) {
return null;
}
diff --git a/core/java/com/android/internal/widget/MessagingData.java b/core/java/com/android/internal/widget/MessagingData.java
index fb1f28fb8ef3..cb5041efd10f 100644
--- a/core/java/com/android/internal/widget/MessagingData.java
+++ b/core/java/com/android/internal/widget/MessagingData.java
@@ -32,6 +32,7 @@ final class MessagingData {
private final List<List<MessagingMessage>> mGroups;
private final List<Person> mSenders;
private final int mUnreadCount;
+ private final CharSequence mSummarization;
private ConversationHeaderData mConversationHeaderData;
@@ -41,8 +42,7 @@ final class MessagingData {
List<Person> senders) {
this(user, showSpinner, /* unreadCount= */0,
historicMessagingMessages, newMessagingMessages,
- groups,
- senders, null);
+ groups, senders, null, null);
}
MessagingData(Person user, boolean showSpinner,
@@ -51,7 +51,8 @@ final class MessagingData {
List<MessagingMessage> newMessagingMessages,
List<List<MessagingMessage>> groups,
List<Person> senders,
- @Nullable ConversationHeaderData conversationHeaderData) {
+ @Nullable ConversationHeaderData conversationHeaderData,
+ CharSequence summarization) {
mUser = user;
mShowSpinner = showSpinner;
mUnreadCount = unreadCount;
@@ -60,6 +61,7 @@ final class MessagingData {
mGroups = groups;
mSenders = senders;
mConversationHeaderData = conversationHeaderData;
+ mSummarization = summarization;
}
public Person getUser() {
@@ -94,4 +96,9 @@ final class MessagingData {
public ConversationHeaderData getConversationHeaderData() {
return mConversationHeaderData;
}
+
+ @Nullable
+ public CharSequence getSummarization() {
+ return mSummarization;
+ }
}
diff --git a/core/java/com/android/internal/widget/MessagingMessage.java b/core/java/com/android/internal/widget/MessagingMessage.java
index a59ee77cc693..c7f22836dd93 100644
--- a/core/java/com/android/internal/widget/MessagingMessage.java
+++ b/core/java/com/android/internal/widget/MessagingMessage.java
@@ -24,7 +24,7 @@ import java.util.ArrayList;
import java.util.Objects;
/**
- * A message of a {@link MessagingLayout}.
+ * A message or summary of a {@link MessagingLayout}.
*/
public interface MessagingMessage extends MessagingLinearLayout.MessagingChild {
diff --git a/core/java/com/android/internal/widget/NotificationProgressDrawable.java b/core/java/com/android/internal/widget/NotificationProgressDrawable.java
index 4ece81c24edc..30dcc67d9ce5 100644
--- a/core/java/com/android/internal/widget/NotificationProgressDrawable.java
+++ b/core/java/com/android/internal/widget/NotificationProgressDrawable.java
@@ -132,6 +132,8 @@ public final class NotificationProgressDrawable extends Drawable {
final float centerY = (float) getBounds().centerY();
final int numParts = mParts.size();
+ final float pointTop = Math.round(centerY - pointRadius);
+ final float pointBottom = Math.round(centerY + pointRadius);
for (int iPart = 0; iPart < numParts; iPart++) {
final DrawablePart part = mParts.get(iPart);
final float start = left + part.mStart;
@@ -146,12 +148,13 @@ public final class NotificationProgressDrawable extends Drawable {
mFillPaint.setColor(segment.mColor);
- mSegRectF.set(start, centerY - radiusY, end, centerY + radiusY);
+ mSegRectF.set(Math.round(start), Math.round(centerY - radiusY), Math.round(end),
+ Math.round(centerY + radiusY));
canvas.drawRoundRect(mSegRectF, cornerRadius, cornerRadius, mFillPaint);
} else if (part instanceof DrawablePoint point) {
// TODO: b/367804171 - actually use a vector asset for the default point
// rather than drawing it as a box?
- mPointRectF.set(start, centerY - pointRadius, end, centerY + pointRadius);
+ mPointRectF.set(Math.round(start), pointTop, Math.round(end), pointBottom);
final float inset = mState.mPointRectInset;
final float cornerRadius = mState.mPointRectCornerRadius;
mPointRectF.inset(inset, inset);
diff --git a/core/java/com/android/internal/widget/remotecompose/accessibility/CoreDocumentAccessibility.java b/core/java/com/android/internal/widget/remotecompose/accessibility/CoreDocumentAccessibility.java
index 2cd4f0362306..52d51539867d 100644
--- a/core/java/com/android/internal/widget/remotecompose/accessibility/CoreDocumentAccessibility.java
+++ b/core/java/com/android/internal/widget/remotecompose/accessibility/CoreDocumentAccessibility.java
@@ -21,6 +21,7 @@ import android.os.Bundle;
import com.android.internal.widget.remotecompose.core.CoreDocument;
import com.android.internal.widget.remotecompose.core.Operation;
+import com.android.internal.widget.remotecompose.core.RemoteContext;
import com.android.internal.widget.remotecompose.core.operations.layout.ClickModifierOperation;
import com.android.internal.widget.remotecompose.core.operations.layout.Component;
import com.android.internal.widget.remotecompose.core.operations.layout.LayoutComponent;
@@ -45,9 +46,11 @@ import java.util.stream.Stream;
*/
public class CoreDocumentAccessibility implements RemoteComposeDocumentAccessibility {
private final CoreDocument mDocument;
+ private final RemoteContext mRemoteContext;
- public CoreDocumentAccessibility(CoreDocument document) {
+ public CoreDocumentAccessibility(CoreDocument document, RemoteContext remoteContext) {
this.mDocument = document;
+ this.mRemoteContext = remoteContext;
}
@Nullable
@@ -95,7 +98,7 @@ public class CoreDocumentAccessibility implements RemoteComposeDocumentAccessibi
@Override
public boolean performAction(Component component, int action, Bundle arguments) {
if (action == ACTION_CLICK) {
- mDocument.performClick(component.getComponentId());
+ mDocument.performClick(mRemoteContext, component.getComponentId());
return true;
} else {
return false;
diff --git a/core/java/com/android/internal/widget/remotecompose/accessibility/PlatformRemoteComposeAccessibilityRegistrar.java b/core/java/com/android/internal/widget/remotecompose/accessibility/PlatformRemoteComposeAccessibilityRegistrar.java
index 010253e9cb95..975383ee36b4 100644
--- a/core/java/com/android/internal/widget/remotecompose/accessibility/PlatformRemoteComposeAccessibilityRegistrar.java
+++ b/core/java/com/android/internal/widget/remotecompose/accessibility/PlatformRemoteComposeAccessibilityRegistrar.java
@@ -19,6 +19,7 @@ import android.annotation.NonNull;
import android.view.View;
import com.android.internal.widget.remotecompose.core.CoreDocument;
+import com.android.internal.widget.remotecompose.core.RemoteContextAware;
/**
* Trivial wrapper for calling setAccessibilityDelegate on a View. This exists primarily because the
@@ -31,7 +32,8 @@ public class PlatformRemoteComposeAccessibilityRegistrar
View player, @NonNull CoreDocument coreDocument) {
return new PlatformRemoteComposeTouchHelper(
player,
- new CoreDocumentAccessibility(coreDocument),
+ new CoreDocumentAccessibility(
+ coreDocument, ((RemoteContextAware) player).getRemoteContext()),
new AndroidPlatformSemanticNodeApplier());
}
diff --git a/core/java/com/android/internal/widget/remotecompose/accessibility/PlatformRemoteComposeTouchHelper.java b/core/java/com/android/internal/widget/remotecompose/accessibility/PlatformRemoteComposeTouchHelper.java
index 43118a0800fb..c8474b19058f 100644
--- a/core/java/com/android/internal/widget/remotecompose/accessibility/PlatformRemoteComposeTouchHelper.java
+++ b/core/java/com/android/internal/widget/remotecompose/accessibility/PlatformRemoteComposeTouchHelper.java
@@ -28,6 +28,7 @@ import android.view.accessibility.AccessibilityNodeInfo;
import com.android.internal.widget.ExploreByTouchHelper;
import com.android.internal.widget.remotecompose.core.CoreDocument;
+import com.android.internal.widget.remotecompose.core.RemoteContextAware;
import com.android.internal.widget.remotecompose.core.operations.layout.Component;
import com.android.internal.widget.remotecompose.core.semantics.AccessibilitySemantics;
import com.android.internal.widget.remotecompose.core.semantics.AccessibleComponent.Mode;
@@ -55,7 +56,8 @@ public class PlatformRemoteComposeTouchHelper extends ExploreByTouchHelper {
View player, @NonNull CoreDocument coreDocument) {
return new PlatformRemoteComposeTouchHelper(
player,
- new CoreDocumentAccessibility(coreDocument),
+ new CoreDocumentAccessibility(
+ coreDocument, ((RemoteContextAware) player).getRemoteContext()),
new AndroidPlatformSemanticNodeApplier());
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/CoreDocument.java b/core/java/com/android/internal/widget/remotecompose/core/CoreDocument.java
index f5f4e4332d28..0cfaf5592d6f 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/CoreDocument.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/CoreDocument.java
@@ -62,7 +62,7 @@ public class CoreDocument {
// We also keep a more fine-grained BUILD number, exposed as
// ID_API_LEVEL = DOCUMENT_API_LEVEL + BUILD
- static final float BUILD = 0.3f;
+ static final float BUILD = 0.4f;
@NonNull ArrayList<Operation> mOperations = new ArrayList<>();
@@ -860,16 +860,22 @@ public class CoreDocument {
*
* @param id the click area id
*/
- public void performClick(int id) {
+ public void performClick(@NonNull RemoteContext context, int id) {
for (ClickAreaRepresentation clickArea : mClickAreas) {
if (clickArea.mId == id) {
warnClickListeners(clickArea);
return;
}
}
+
for (IdActionCallback listener : mIdActionListeners) {
listener.onAction(id, "");
}
+
+ Component component = getComponent(id);
+ if (component != null) {
+ component.onClick(context, this, -1, -1);
+ }
}
/** Warn click listeners when a click area is activated */
diff --git a/core/java/com/android/internal/widget/remotecompose/core/Operations.java b/core/java/com/android/internal/widget/remotecompose/core/Operations.java
index 0b6a3c415e4a..3760af2f7460 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/Operations.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/Operations.java
@@ -18,6 +18,7 @@ package com.android.internal.widget.remotecompose.core;
import android.annotation.NonNull;
import com.android.internal.widget.remotecompose.core.operations.BitmapData;
+import com.android.internal.widget.remotecompose.core.operations.BitmapFontData;
import com.android.internal.widget.remotecompose.core.operations.ClickArea;
import com.android.internal.widget.remotecompose.core.operations.ClipPath;
import com.android.internal.widget.remotecompose.core.operations.ClipRect;
@@ -30,6 +31,7 @@ import com.android.internal.widget.remotecompose.core.operations.DataMapIds;
import com.android.internal.widget.remotecompose.core.operations.DataMapLookup;
import com.android.internal.widget.remotecompose.core.operations.DrawArc;
import com.android.internal.widget.remotecompose.core.operations.DrawBitmap;
+import com.android.internal.widget.remotecompose.core.operations.DrawBitmapFontText;
import com.android.internal.widget.remotecompose.core.operations.DrawBitmapInt;
import com.android.internal.widget.remotecompose.core.operations.DrawBitmapScaled;
import com.android.internal.widget.remotecompose.core.operations.DrawCircle;
@@ -45,6 +47,8 @@ import com.android.internal.widget.remotecompose.core.operations.DrawTextOnPath;
import com.android.internal.widget.remotecompose.core.operations.DrawTweenPath;
import com.android.internal.widget.remotecompose.core.operations.FloatConstant;
import com.android.internal.widget.remotecompose.core.operations.FloatExpression;
+import com.android.internal.widget.remotecompose.core.operations.FloatFunctionCall;
+import com.android.internal.widget.remotecompose.core.operations.FloatFunctionDefine;
import com.android.internal.widget.remotecompose.core.operations.Header;
import com.android.internal.widget.remotecompose.core.operations.IntegerExpression;
import com.android.internal.widget.remotecompose.core.operations.MatrixRestore;
@@ -147,12 +151,14 @@ public class Operations {
public static final int DATA_BITMAP = 101;
public static final int DATA_SHADER = 45;
public static final int DATA_TEXT = 102;
+ public static final int DATA_BITMAP_FONT = 167;
///////////////////////////// =====================
public static final int CLIP_PATH = 38;
public static final int CLIP_RECT = 39;
public static final int PAINT_VALUES = 40;
public static final int DRAW_RECT = 42;
+ public static final int DRAW_BITMAP_FONT_TEXT_RUN = 48;
public static final int DRAW_TEXT_RUN = 43;
public static final int DRAW_CIRCLE = 46;
public static final int DRAW_LINE = 47;
@@ -196,11 +202,13 @@ public class Operations {
public static final int PATH_TWEEN = 158;
public static final int PATH_CREATE = 159;
public static final int PATH_ADD = 160;
- public static final int PARTICLE_CREATE = 161;
+ public static final int PARTICLE_DEFINE = 161;
public static final int PARTICLE_PROCESS = 162;
public static final int PARTICLE_LOOP = 163;
public static final int IMPULSE_START = 164;
public static final int IMPULSE_PROCESS = 165;
+ public static final int FUNCTION_CALL = 166;
+ public static final int FUNCTION_DEFINE = 168;
///////////////////////////////////////// ======================
@@ -276,6 +284,7 @@ public class Operations {
map.put(HEADER, Header::read);
map.put(DRAW_BITMAP_INT, DrawBitmapInt::read);
map.put(DATA_BITMAP, BitmapData::read);
+ map.put(DATA_BITMAP_FONT, BitmapFontData::read);
map.put(DATA_TEXT, TextData::read);
map.put(THEME, Theme::read);
map.put(CLICK_AREA, ClickArea::read);
@@ -292,6 +301,7 @@ public class Operations {
map.put(DRAW_ROUND_RECT, DrawRoundRect::read);
map.put(DRAW_TEXT_ON_PATH, DrawTextOnPath::read);
map.put(DRAW_TEXT_RUN, DrawText::read);
+ map.put(DRAW_BITMAP_FONT_TEXT_RUN, DrawBitmapFontText::read);
map.put(DRAW_TWEEN_PATH, DrawTweenPath::read);
map.put(DATA_PATH, PathData::read);
map.put(PAINT_VALUES, PaintData::read);
@@ -389,8 +399,10 @@ public class Operations {
map.put(PATH_ADD, PathAppend::read);
map.put(IMPULSE_START, ImpulseOperation::read);
map.put(IMPULSE_PROCESS, ImpulseProcess::read);
- map.put(PARTICLE_CREATE, ParticlesCreate::read);
+ map.put(PARTICLE_DEFINE, ParticlesCreate::read);
map.put(PARTICLE_LOOP, ParticlesLoop::read);
+ map.put(FUNCTION_CALL, FloatFunctionCall::read);
+ map.put(FUNCTION_DEFINE, FloatFunctionDefine::read);
map.put(ACCESSIBILITY_SEMANTICS, CoreSemantics::read);
// map.put(ACCESSIBILITY_CUSTOM_ACTION, CoreSemantics::read);
diff --git a/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeBuffer.java b/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeBuffer.java
index 1cb8fefde80c..f83ecef1074d 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeBuffer.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeBuffer.java
@@ -21,6 +21,7 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import com.android.internal.widget.remotecompose.core.operations.BitmapData;
+import com.android.internal.widget.remotecompose.core.operations.BitmapFontData;
import com.android.internal.widget.remotecompose.core.operations.ClickArea;
import com.android.internal.widget.remotecompose.core.operations.ClipPath;
import com.android.internal.widget.remotecompose.core.operations.ClipRect;
@@ -33,6 +34,7 @@ import com.android.internal.widget.remotecompose.core.operations.DataMapIds;
import com.android.internal.widget.remotecompose.core.operations.DataMapLookup;
import com.android.internal.widget.remotecompose.core.operations.DrawArc;
import com.android.internal.widget.remotecompose.core.operations.DrawBitmap;
+import com.android.internal.widget.remotecompose.core.operations.DrawBitmapFontText;
import com.android.internal.widget.remotecompose.core.operations.DrawBitmapInt;
import com.android.internal.widget.remotecompose.core.operations.DrawBitmapScaled;
import com.android.internal.widget.remotecompose.core.operations.DrawCircle;
@@ -48,6 +50,8 @@ import com.android.internal.widget.remotecompose.core.operations.DrawTextOnPath;
import com.android.internal.widget.remotecompose.core.operations.DrawTweenPath;
import com.android.internal.widget.remotecompose.core.operations.FloatConstant;
import com.android.internal.widget.remotecompose.core.operations.FloatExpression;
+import com.android.internal.widget.remotecompose.core.operations.FloatFunctionCall;
+import com.android.internal.widget.remotecompose.core.operations.FloatFunctionDefine;
import com.android.internal.widget.remotecompose.core.operations.Header;
import com.android.internal.widget.remotecompose.core.operations.IntegerExpression;
import com.android.internal.widget.remotecompose.core.operations.MatrixRestore;
@@ -557,6 +561,18 @@ public class RemoteComposeBuffer {
}
/**
+ * Records a bitmap font and returns an ID.
+ *
+ * @param glyphs The glyphs that define the bitmap font
+ * @return id of the BitmapFont
+ */
+ public int addBitmapFont(BitmapFontData.Glyph[] glyphs) {
+ int id = mRemoteComposeState.nextId();
+ BitmapFontData.apply(mBuffer, id, glyphs);
+ return id;
+ }
+
+ /**
* This defines the name of the bitmap given the id.
*
* @param id of the Bitmap
@@ -825,6 +841,22 @@ public class RemoteComposeBuffer {
}
/**
+ * Draw the text with a bitmap font, with origin at (x,y). The origin is interpreted based on
+ * the Align setting in the paint.
+ *
+ * @param textId The text to be drawn
+ * @param bitmapFontId The id of the bitmap font to draw with
+ * @param start The index of the first character in text to draw
+ * @param end (end - 1) is the index of the last character in text to draw
+ * @param x The x-coordinate of the origin of the text being drawn
+ * @param y The y-coordinate of the baseline of the text being drawn
+ */
+ public void addDrawBitmapFontTextRun(
+ int textId, int bitmapFontId, int start, int end, float x, float y) {
+ DrawBitmapFontText.apply(mBuffer, textId, bitmapFontId, start, end, x, y);
+ }
+
+ /**
* Draw a text on canvas at relative to position (x, y), offset panX and panY. <br>
* The panning factors (panX, panY) mapped to the resulting bounding box of the text, in such a
* way that a panning factor of (0.0, 0.0) would center the text at (x, y)
@@ -1060,6 +1092,14 @@ public class RemoteComposeBuffer {
return "v1.0";
}
+ /**
+ * Initialize a buffer from a file
+ *
+ * @param path the file path
+ * @param remoteComposeState the associated state
+ * @return the RemoteComposeBuffer
+ * @throws IOException
+ */
@NonNull
public static RemoteComposeBuffer fromFile(
@NonNull String path, @NonNull RemoteComposeState remoteComposeState)
@@ -1134,11 +1174,24 @@ public class RemoteComposeBuffer {
}
}
+ /**
+ * Read the content of the file into the buffer
+ *
+ * @param file a target file
+ * @param buffer a RemoteComposeBuffer
+ * @throws IOException
+ */
static void read(@NonNull File file, @NonNull RemoteComposeBuffer buffer) throws IOException {
FileInputStream fd = new FileInputStream(file);
read(fd, buffer);
}
+ /**
+ * Initialize a buffer from an input stream
+ *
+ * @param fd the input stream
+ * @param buffer a RemoteComposeBuffer
+ */
public static void read(@NonNull InputStream fd, @NonNull RemoteComposeBuffer buffer) {
try {
byte[] bytes = readAllBytes(fd);
@@ -1150,6 +1203,13 @@ public class RemoteComposeBuffer {
}
}
+ /**
+ * Load a byte buffer from the input stream
+ *
+ * @param is the input stream
+ * @return a byte buffer containing the input stream content
+ * @throws IOException
+ */
private static byte[] readAllBytes(@NonNull InputStream is) throws IOException {
byte[] buff = new byte[32 * 1024]; // moderate size buff to start
int red = 0;
@@ -1684,7 +1744,27 @@ public class RemoteComposeBuffer {
* @return id of the color (color ids are short)
*/
public short addColorExpression(int alpha, float hue, float sat, float value) {
- ColorExpression c = new ColorExpression(0, alpha, hue, sat, value);
+ ColorExpression c =
+ new ColorExpression(0, ColorExpression.HSV_MODE, alpha, hue, sat, value);
+ short id = (short) mRemoteComposeState.cacheData(c);
+ c.mId = id;
+ c.write(mBuffer);
+ return id;
+ }
+
+ /**
+ * Color calculated by Alpha, Red, Green and Blue. (as floats they can be variables used to
+ * create color transitions)
+ *
+ * @param alpha the alpha value of the color
+ * @param red the red component of the color
+ * @param green the green component of the color
+ * @param blue the blue component of the color
+ * @return id of the color (color ids are short)
+ */
+ public short addColorExpression(float alpha, float red, float green, float blue) {
+ ColorExpression c =
+ new ColorExpression(0, ColorExpression.ARGB_MODE, alpha, red, green, blue);
short id = (short) mRemoteComposeState.cacheData(c);
c.mId = id;
c.write(mBuffer);
@@ -2179,10 +2259,21 @@ public class RemoteComposeBuffer {
textAlign);
}
+ /**
+ * Returns the next available id for the given type
+ *
+ * @param type the type of the value
+ * @return a unique id
+ */
public int createID(int type) {
return mRemoteComposeState.nextId(type);
}
+ /**
+ * Returns the next available id
+ *
+ * @return a unique id
+ */
public int nextId() {
return mRemoteComposeState.nextId();
}
@@ -2243,4 +2334,27 @@ public class RemoteComposeBuffer {
public void addParticleLoopEnd() {
ContainerEnd.apply(mBuffer);
}
+
+ /**
+ * @param fid The id of the function
+ * @param args The arguments of the function
+ */
+ public void defineFloatFunction(int fid, int[] args) {
+ FloatFunctionDefine.apply(mBuffer, fid, args);
+ }
+
+ /** end the definition of the function */
+ public void addEndFloatFunctionDef() {
+ ContainerEnd.apply(mBuffer);
+ }
+
+ /**
+ * add a function call
+ *
+ * @param id the id of the function to call
+ * @param args the arguments of the function
+ */
+ public void callFloatFunction(int id, float[] args) {
+ FloatFunctionCall.apply(mBuffer, id, args);
+ }
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromNotificationsShadeToQuickSettingsShadeTransition.kt b/core/java/com/android/internal/widget/remotecompose/core/RemoteContextAware.java
index 9a7f99f3247b..bf9a8c02d525 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromNotificationsShadeToQuickSettingsShadeTransition.kt
+++ b/core/java/com/android/internal/widget/remotecompose/core/RemoteContextAware.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2024 The Android Open Source Project
+ * Copyright (C) 2025 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -13,17 +13,19 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+package com.android.internal.widget.remotecompose.core;
-package com.android.systemui.scene.ui.composable.transitions
-
-import androidx.compose.animation.core.tween
-import com.android.compose.animation.scene.TransitionBuilder
-import kotlin.time.Duration.Companion.milliseconds
+/**
+ * This interface defines a contract for objects that are aware of a {@link RemoteContext}.
+ *
+ * <p>PlayerViews should implement to provide access to the RemoteContext.
+ */
+public interface RemoteContextAware {
-fun TransitionBuilder.notificationsShadeToQuickSettingsShadeTransition(
- durationScale: Double = 1.0
-) {
- spec = tween(durationMillis = (DefaultDuration * durationScale).inWholeMilliseconds.toInt())
+ /**
+ * Returns the remote context
+ *
+ * @return a RemoteContext
+ */
+ RemoteContext getRemoteContext();
}
-
-private val DefaultDuration = 300.milliseconds
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/BitmapFontData.java b/core/java/com/android/internal/widget/remotecompose/core/operations/BitmapFontData.java
new file mode 100644
index 000000000000..cbd30dc21caf
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/BitmapFontData.java
@@ -0,0 +1,219 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.widget.remotecompose.core.operations;
+
+import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT_ARRAY;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import com.android.internal.widget.remotecompose.core.Operation;
+import com.android.internal.widget.remotecompose.core.Operations;
+import com.android.internal.widget.remotecompose.core.RemoteContext;
+import com.android.internal.widget.remotecompose.core.WireBuffer;
+import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder;
+import com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation;
+
+import java.util.Arrays;
+import java.util.List;
+
+/** Operation to deal with bitmap font data. */
+public class BitmapFontData extends Operation {
+ private static final int OP_CODE = Operations.DATA_BITMAP_FONT;
+ private static final String CLASS_NAME = "BitmapFontData";
+
+ int mId;
+
+ // Sorted in order of decreasing mChars length.
+ @NonNull Glyph[] mFontGlyphs;
+
+ /**
+ * A bitmap font is comprised of a collection of Glyphs. Note each Glyph has its own bitmap
+ * rather than using a texture atlas.
+ */
+ public static class Glyph {
+ /** The character(s) this glyph represents. */
+ public String mChars;
+
+ /** The id of the bitmap for this glyph, or -1 for space. */
+ public int mBitmapId;
+
+ /** The margin in pixels to the left of the glyph bitmap. */
+ public short mMarginLeft;
+
+ /** The margin in pixels above of the glyph bitmap. */
+ public short mMarginTop;
+
+ /** The margin in pixels to the right of the glyph bitmap. */
+ public short mMarginRight;
+
+ /** The margin in pixels below the glyph bitmap. */
+ public short mMarginBottom;
+
+ public short mBitmapWidth;
+ public short mBitmapHeight;
+
+ public Glyph() {}
+
+ public Glyph(
+ String chars,
+ int bitmapId,
+ short marginLeft,
+ short marginTop,
+ short marginRight,
+ short marginBottom,
+ short width,
+ short height) {
+ mChars = chars;
+ mBitmapId = bitmapId;
+ mMarginLeft = marginLeft;
+ mMarginTop = marginTop;
+ mMarginRight = marginRight;
+ mMarginBottom = marginBottom;
+ mBitmapWidth = width;
+ mBitmapHeight = height;
+ }
+ }
+
+ /**
+ * create a bitmap font structure.
+ *
+ * @param id the id of the bitmap font
+ * @param fontGlyphs the glyphs that define the bitmap font
+ */
+ public BitmapFontData(int id, @NonNull Glyph[] fontGlyphs) {
+ mId = id;
+ mFontGlyphs = fontGlyphs;
+
+ // Sort in order of decreasing mChars length.
+ Arrays.sort(mFontGlyphs, (o1, o2) -> o2.mChars.length() - o1.mChars.length());
+ }
+
+ @Override
+ public void write(@NonNull WireBuffer buffer) {
+ apply(buffer, mId, mFontGlyphs);
+ }
+
+ @NonNull
+ @Override
+ public String toString() {
+ return "BITMAP FONT DATA " + mId;
+ }
+
+ /**
+ * The name of the class
+ *
+ * @return the name
+ */
+ @NonNull
+ public static String name() {
+ return CLASS_NAME;
+ }
+
+ /**
+ * The OP_CODE for this command
+ *
+ * @return the opcode
+ */
+ public static int id() {
+ return OP_CODE;
+ }
+
+ /**
+ * Add the image to the document
+ *
+ * @param buffer document to write to
+ * @param id the id the bitmap font will be stored under
+ * @param glyphs glyph metadata
+ */
+ public static void apply(@NonNull WireBuffer buffer, int id, @NonNull Glyph[] glyphs) {
+ buffer.start(OP_CODE);
+ buffer.writeInt(id);
+ buffer.writeInt(glyphs.length);
+ for (Glyph element : glyphs) {
+ buffer.writeUTF8(element.mChars);
+ buffer.writeInt(element.mBitmapId);
+ buffer.writeShort(element.mMarginLeft);
+ buffer.writeShort(element.mMarginTop);
+ buffer.writeShort(element.mMarginRight);
+ buffer.writeShort(element.mMarginBottom);
+ buffer.writeShort(element.mBitmapWidth);
+ buffer.writeShort(element.mBitmapHeight);
+ }
+ }
+
+ /**
+ * Read this operation and add it to the list of operations
+ *
+ * @param buffer the buffer to read
+ * @param operations the list of operations that will be added to
+ */
+ public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
+ int id = buffer.readInt();
+ int numGlyphElements = buffer.readInt();
+ Glyph[] glyphs = new Glyph[numGlyphElements];
+ for (int i = 0; i < numGlyphElements; i++) {
+ glyphs[i] = new Glyph();
+ glyphs[i].mChars = buffer.readUTF8();
+ glyphs[i].mBitmapId = buffer.readInt();
+ glyphs[i].mMarginLeft = (short) buffer.readShort();
+ glyphs[i].mMarginTop = (short) buffer.readShort();
+ glyphs[i].mMarginRight = (short) buffer.readShort();
+ glyphs[i].mMarginBottom = (short) buffer.readShort();
+ glyphs[i].mBitmapWidth = (short) buffer.readShort();
+ glyphs[i].mBitmapHeight = (short) buffer.readShort();
+ }
+
+ operations.add(new BitmapFontData(id, glyphs));
+ }
+
+ /**
+ * Populate the documentation with a description of this operation
+ *
+ * @param doc to append the description to.
+ */
+ public static void documentation(@NonNull DocumentationBuilder doc) {
+ doc.operation("Data Operations", OP_CODE, CLASS_NAME)
+ .description("Bitmap font data")
+ .field(DocumentedOperation.INT, "id", "id of bitmap font data")
+ .field(INT_ARRAY, "glyphNodes", "list used to greedily convert strings into glyphs")
+ .field(INT_ARRAY, "glyphElements", "");
+ }
+
+ @Override
+ public void apply(@NonNull RemoteContext context) {
+ context.putObject(mId, this);
+ }
+
+ @NonNull
+ @Override
+ public String deepToString(@NonNull String indent) {
+ return indent + toString();
+ }
+
+ /** Finds the largest glyph matching the string at the specified offset, or returns null. */
+ @Nullable
+ public Glyph lookupGlyph(String string, int offset) {
+ // Since mFontGlyphs is sorted on decreasing size, it will match the longest items first.
+ // It is expected that the mFontGlyphs array will be fairly small.
+ for (Glyph glyph : mFontGlyphs) {
+ if (string.startsWith(glyph.mChars, offset)) {
+ return glyph;
+ }
+ }
+ return null;
+ }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/ColorExpression.java b/core/java/com/android/internal/widget/remotecompose/core/operations/ColorExpression.java
index b385ecd9e5f7..73f99ccb4405 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/ColorExpression.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/ColorExpression.java
@@ -38,7 +38,13 @@ public class ColorExpression extends Operation implements VariableSupport {
private static final int OP_CODE = Operations.COLOR_EXPRESSIONS;
private static final String CLASS_NAME = "ColorExpression";
public int mId;
+
+ /**
+ * Mode of the color expression 0 = two colors and a tween 1 = color1 is a colorID. 2 color2 is
+ * a colorID. 3 = color1 & color2 are ids 4 = H S V mode 5 = RGB mode 6 = ARGB mode
+ */
int mMode;
+
public int mColor1;
public int mColor2;
public float mTween = 0.0f;
@@ -51,11 +57,49 @@ public class ColorExpression extends Operation implements VariableSupport {
public float mOutValue = 0;
public int mAlpha = 0xFF; // only used in hsv mode
+ private float mArgbAlpha = 0.0f;
+ private float mArgbRed = 0.0f;
+ private float mArgbGreen = 0.0f;
+ private float mArgbBlue = 0.0f;
+
+ private float mOutArgbAlpha = 0.0f;
+ private float mOutArgbRed = 0.0f;
+ private float mOutArgbGreen = 0.0f;
+ private float mOutArgbBlue = 0.0f;
+
public float mOutTween = 0.0f;
public int mOutColor1;
public int mOutColor2;
- public static final int HSV_MODE = 4;
+ /** COLOR_COLOR_INTERPOLATE */
+ public static final byte COLOR_COLOR_INTERPOLATE = 0;
+
+ /** COLOR_ID_INTERPOLATE */
+ public static final byte ID_COLOR_INTERPOLATE = 1;
+
+ /** ID_COLOR_INTERPOLATE */
+ public static final byte COLOR_ID_INTERPOLATE = 2;
+
+ /** ID_ID_INTERPOLATE */
+ public static final byte ID_ID_INTERPOLATE = 3;
+
+ /** H S V mode */
+ public static final byte HSV_MODE = 4;
+
+ /** ARGB mode */
+ public static final byte ARGB_MODE = 5;
+
+ /** ARGB mode with a being an id */
+ public static final byte IDARGB_MODE = 6;
+
+ /**
+ * Create a new ColorExpression object
+ *
+ * @param id the id of the color
+ * @param hue the hue of the color
+ * @param sat the saturation of the color
+ * @param value the value of the color
+ */
public ColorExpression(int id, float hue, float sat, float value) {
mMode = HSV_MODE;
mAlpha = 0xFF;
@@ -67,7 +111,21 @@ public class ColorExpression extends Operation implements VariableSupport {
mTween = value;
}
- public ColorExpression(int id, int alpha, float hue, float sat, float value) {
+ /**
+ * Create a new ColorExpression object based on HSV
+ *
+ * @param id id of the color
+ * @param mode the mode of the color
+ * @param alpha the alpha of the color
+ * @param hue the hue of the color
+ * @param sat the saturation of the color
+ * @param value the value (brightness) of the color
+ */
+ public ColorExpression(int id, byte mode, int alpha, float hue, float sat, float value) {
+ if (mode != HSV_MODE) {
+ throw new RuntimeException("Invalid mode " + mode);
+ }
+ mId = id;
mMode = HSV_MODE;
mAlpha = alpha;
mOutHue = mHue = hue;
@@ -78,6 +136,15 @@ public class ColorExpression extends Operation implements VariableSupport {
mTween = value;
}
+ /**
+ * Create a new ColorExpression object based interpolationg two colors
+ *
+ * @param id the id of the color
+ * @param mode the type of mode (are colors ids or actual values)
+ * @param color1 the first color to use
+ * @param color2 the second color to use
+ * @param tween the value to use to interpolate between the two colors
+ */
public ColorExpression(int id, int mode, int color1, int color2, float tween) {
this.mId = id;
this.mMode = mode & 0xFF;
@@ -95,6 +162,28 @@ public class ColorExpression extends Operation implements VariableSupport {
this.mOutColor2 = color2;
}
+ /**
+ * Create a new ColorExpression object based on ARGB
+ *
+ * @param id the id of the color
+ * @param mode the mode must be ARGB_MODE
+ * @param alpha the alpha value of the color
+ * @param red the red of component the color
+ * @param green the greej component of the color
+ * @param blue the blue of component the color
+ */
+ public ColorExpression(int id, byte mode, float alpha, float red, float green, float blue) {
+ if (mode != ARGB_MODE) {
+ throw new RuntimeException("Invalid mode " + mode);
+ }
+ mMode = ARGB_MODE;
+ mId = id;
+ mOutArgbAlpha = mArgbAlpha = alpha;
+ mOutArgbRed = mArgbRed = red;
+ mOutArgbGreen = mArgbGreen = green;
+ mOutArgbBlue = mArgbBlue = blue;
+ }
+
@Override
public void updateVariables(@NonNull RemoteContext context) {
if (mMode == 4) {
@@ -108,6 +197,20 @@ public class ColorExpression extends Operation implements VariableSupport {
mOutValue = context.getFloat(Utils.idFromNan(mValue));
}
}
+ if (mMode == ARGB_MODE) {
+ if (Float.isNaN(mArgbAlpha)) {
+ mOutArgbAlpha = context.getFloat(Utils.idFromNan(mArgbAlpha));
+ }
+ if (Float.isNaN(mArgbRed)) {
+ mOutArgbRed = context.getFloat(Utils.idFromNan(mArgbRed));
+ }
+ if (Float.isNaN(mArgbGreen)) {
+ mOutArgbGreen = context.getFloat(Utils.idFromNan(mArgbGreen));
+ }
+ if (Float.isNaN(mArgbBlue)) {
+ mOutArgbBlue = context.getFloat(Utils.idFromNan(mArgbBlue));
+ }
+ }
if (Float.isNaN(mTween)) {
mOutTween = context.getFloat(Utils.idFromNan(mTween));
}
@@ -146,13 +249,21 @@ public class ColorExpression extends Operation implements VariableSupport {
@Override
public void apply(@NonNull RemoteContext context) {
- if (mMode == 4) {
+ if (mMode == HSV_MODE) {
context.loadColor(
mId, (mAlpha << 24) | (0xFFFFFF & Utils.hsvToRgb(mOutHue, mOutSat, mOutValue)));
return;
}
+ if (mMode == ARGB_MODE) {
+ context.loadColor(
+ mId, Utils.toARGB(mOutArgbAlpha, mOutArgbRed, mOutArgbGreen, mOutArgbBlue));
+ return;
+ }
if (mOutTween == 0.0) {
- context.loadColor(mId, mColor1);
+ if ((mMode & 1) == 1) {
+ mOutColor1 = context.getColor(mColor1);
+ }
+ context.loadColor(mId, mOutColor1);
} else {
if ((mMode & 1) == 1) {
mOutColor1 = context.getColor(mColor1);
@@ -167,14 +278,36 @@ public class ColorExpression extends Operation implements VariableSupport {
@Override
public void write(@NonNull WireBuffer buffer) {
- int mode = mMode | (mAlpha << 16);
- apply(buffer, mId, mode, mColor1, mColor2, mTween);
+ int mode;
+ switch (mMode) {
+ case ARGB_MODE:
+ apply(buffer, mId, mArgbAlpha, mArgbRed, mArgbGreen, mArgbBlue);
+ break;
+
+ case HSV_MODE:
+ mOutValue = mValue;
+ mColor1 = Float.floatToRawIntBits(mHue);
+ mColor2 = Float.floatToRawIntBits(mSat);
+ mode = mMode | (mAlpha << 16);
+ apply(buffer, mId, mode, mColor1, mColor2, mTween);
+
+ break;
+ case COLOR_ID_INTERPOLATE:
+ case ID_COLOR_INTERPOLATE:
+ case ID_ID_INTERPOLATE:
+ case COLOR_COLOR_INTERPOLATE:
+ apply(buffer, mId, mMode, mColor1, mColor2, mTween);
+
+ break;
+ default:
+ throw new RuntimeException("Invalid mode ");
+ }
}
@NonNull
@Override
public String toString() {
- if (mMode == 4) {
+ if (mMode == HSV_MODE) {
return "ColorExpression["
+ mId
+ "] = hsv ("
@@ -185,7 +318,20 @@ public class ColorExpression extends Operation implements VariableSupport {
+ Utils.floatToString(mValue)
+ ")";
}
-
+ Utils.log(" ColorExpression toString" + mId + " " + mMode);
+ if (mMode == ARGB_MODE) {
+ return "ColorExpression["
+ + mId
+ + "] = rgb ("
+ + Utils.floatToString(mArgbAlpha)
+ + ", "
+ + Utils.floatToString(mArgbRed)
+ + ", "
+ + Utils.floatToString(mArgbGreen)
+ + ", "
+ + Utils.floatToString(mArgbRed)
+ + ")";
+ }
String c1 = (mMode & 1) == 1 ? "[" + mColor1 + "]" : Utils.colorInt(mColor1);
String c2 = (mMode & 2) == 2 ? "[" + mColor2 + "]" : Utils.colorInt(mColor2);
return "ColorExpression["
@@ -230,12 +376,38 @@ public class ColorExpression extends Operation implements VariableSupport {
*/
public static void apply(
@NonNull WireBuffer buffer, int id, int mode, int color1, int color2, float tween) {
+ apply(buffer, id, mode, color1, color2, Float.floatToRawIntBits(tween));
+ }
+
+ /**
+ * Call to write a ColorExpression object on the buffer
+ *
+ * @param buffer
+ * @param id of the ColorExpression object
+ * @param alpha
+ * @param red
+ * @param green
+ * @param blue
+ */
+ public static void apply(
+ @NonNull WireBuffer buffer, int id, float alpha, float red, float green, float blue) {
+ int param1 = (Float.isNaN(alpha)) ? IDARGB_MODE : ARGB_MODE;
+ param1 |=
+ (Float.isNaN(alpha)) ? Utils.idFromNan(alpha) << 16 : ((int) (alpha * 1024)) << 16;
+ int param2 = Float.floatToRawIntBits(red);
+ int param3 = Float.floatToRawIntBits(green);
+ int param4 = Float.floatToRawIntBits(blue);
+ apply(buffer, id, param1, param2, param3, param4);
+ }
+
+ private static void apply(
+ @NonNull WireBuffer buffer, int id, int param1, int param2, int param3, int param4) {
buffer.start(OP_CODE);
buffer.writeInt(id);
- buffer.writeInt(mode);
- buffer.writeInt(color1);
- buffer.writeInt(color2);
- buffer.writeFloat(tween);
+ buffer.writeInt(param1);
+ buffer.writeInt(param2);
+ buffer.writeInt(param3);
+ buffer.writeInt(param4);
}
/**
@@ -246,12 +418,48 @@ public class ColorExpression extends Operation implements VariableSupport {
*/
public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
int id = buffer.readInt();
- int mode = buffer.readInt();
- int color1 = buffer.readInt();
- int color2 = buffer.readInt();
- float tween = buffer.readFloat();
-
- operations.add(new ColorExpression(id, mode, color1, color2, tween));
+ int param1 = buffer.readInt();
+ int param2 = buffer.readInt();
+ int param3 = buffer.readInt();
+ int param4 = buffer.readInt();
+ int mode = param1 & 0xFF;
+ float alpha;
+ float red;
+ float green;
+ float blue;
+ switch (mode) {
+ case IDARGB_MODE:
+ alpha = Utils.asNan(param1 >> 16);
+ red = Float.intBitsToFloat(param2);
+ green = Float.intBitsToFloat(param3);
+ blue = Float.intBitsToFloat(param4);
+ operations.add(new ColorExpression(id, (byte) ARGB_MODE, alpha, red, green, blue));
+ break;
+ case ARGB_MODE:
+ alpha = (param1 >> 16) / 1024.0f;
+ red = Float.intBitsToFloat(param2);
+ green = Float.intBitsToFloat(param3);
+ blue = Float.intBitsToFloat(param4);
+ operations.add(new ColorExpression(id, (byte) ARGB_MODE, alpha, red, green, blue));
+ break;
+ case HSV_MODE:
+ alpha = (param1 >> 16) / 1024.0f;
+ float hue = Float.intBitsToFloat(param2);
+ float sat = Float.intBitsToFloat(param3);
+ float value = Float.intBitsToFloat(param4);
+ operations.add(new ColorExpression(id, HSV_MODE, (param1 >> 16), hue, sat, value));
+ break;
+ case COLOR_ID_INTERPOLATE:
+ case ID_COLOR_INTERPOLATE:
+ case ID_ID_INTERPOLATE:
+ case COLOR_COLOR_INTERPOLATE:
+ operations.add(
+ new ColorExpression(
+ id, mode, param2, param3, Float.intBitsToFloat(param4)));
+ break;
+ default:
+ throw new RuntimeException("Invalid mode " + mode);
+ }
}
/**
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase2.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase2.java
index 7f1ba6f94065..411353bd3509 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase2.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase2.java
@@ -31,8 +31,8 @@ import java.util.List;
/** Base class for commands that take 3 float */
public abstract class DrawBase2 extends PaintOperation implements VariableSupport {
@NonNull protected String mName = "DrawRectBase";
- protected float mV1;
- protected float mV2;
+ float mV1;
+ float mV2;
float mValue1;
float mValue2;
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmapFontText.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmapFontText.java
new file mode 100644
index 000000000000..258988e8b00a
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmapFontText.java
@@ -0,0 +1,232 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.widget.remotecompose.core.operations;
+
+import static com.android.internal.widget.remotecompose.core.operations.Utils.floatToString;
+
+import android.annotation.NonNull;
+
+import com.android.internal.widget.remotecompose.core.Operation;
+import com.android.internal.widget.remotecompose.core.Operations;
+import com.android.internal.widget.remotecompose.core.PaintContext;
+import com.android.internal.widget.remotecompose.core.PaintOperation;
+import com.android.internal.widget.remotecompose.core.RemoteContext;
+import com.android.internal.widget.remotecompose.core.VariableSupport;
+import com.android.internal.widget.remotecompose.core.WireBuffer;
+import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder;
+import com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation;
+
+import java.util.List;
+
+/** Draw Text */
+public class DrawBitmapFontText extends PaintOperation implements VariableSupport {
+ private static final int OP_CODE = Operations.DRAW_BITMAP_FONT_TEXT_RUN;
+ private static final String CLASS_NAME = "DrawBitmapFontText";
+ int mTextID;
+ int mBitmapFontID;
+ int mStart;
+ int mEnd;
+ float mX;
+ float mY;
+ float mOutX;
+ float mOutY;
+
+ public DrawBitmapFontText(int textID, int bitmapFontID, int start, int end, float x, float y) {
+ mTextID = textID;
+ mBitmapFontID = bitmapFontID;
+ mStart = start;
+ mEnd = end;
+ mOutX = mX = x;
+ mOutY = mY = y;
+ }
+
+ @Override
+ public void updateVariables(@NonNull RemoteContext context) {
+ mOutX = Float.isNaN(mX) ? context.getFloat(Utils.idFromNan(mX)) : mX;
+ mOutY = Float.isNaN(mY) ? context.getFloat(Utils.idFromNan(mY)) : mY;
+ }
+
+ @Override
+ public void registerListening(@NonNull RemoteContext context) {
+ if (Float.isNaN(mX)) {
+ context.listensTo(Utils.idFromNan(mX), this);
+ }
+ if (Float.isNaN(mY)) {
+ context.listensTo(Utils.idFromNan(mY), this);
+ }
+ }
+
+ @Override
+ public void write(@NonNull WireBuffer buffer) {
+ apply(buffer, mTextID, mBitmapFontID, mStart, mEnd, mX, mY);
+ }
+
+ @NonNull
+ @Override
+ public String toString() {
+ return "DrawBitmapFontText ["
+ + mTextID
+ + "] "
+ + mBitmapFontID
+ + ", "
+ + mStart
+ + ", "
+ + mEnd
+ + ", "
+ + floatToString(mX, mOutX)
+ + ", "
+ + floatToString(mY, mOutY);
+ }
+
+ /**
+ * Read this operation and add it to the list of operations
+ *
+ * @param buffer the buffer to read
+ * @param operations the list of operations that will be added to
+ */
+ public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
+ int text = buffer.readInt();
+ int bitmapFont = buffer.readInt();
+ int start = buffer.readInt();
+ int end = buffer.readInt();
+ float x = buffer.readFloat();
+ float y = buffer.readFloat();
+ DrawBitmapFontText op = new DrawBitmapFontText(text, bitmapFont, start, end, x, y);
+
+ operations.add(op);
+ }
+
+ /**
+ * The name of the class
+ *
+ * @return the name
+ */
+ @NonNull
+ public static String name() {
+ return CLASS_NAME;
+ }
+
+ /**
+ * The OP_CODE for this command
+ *
+ * @return the opcode
+ */
+ public static int id() {
+ return OP_CODE;
+ }
+
+ /**
+ * Writes out the operation to the buffer
+ *
+ * @param buffer write the command to the buffer
+ * @param textID id of the text
+ * @param bitmapFontID id of the bitmap font
+ * @param start Start position
+ * @param end end position
+ * @param x position of where to draw
+ * @param y position of where to draw
+ */
+ public static void apply(
+ @NonNull WireBuffer buffer,
+ int textID,
+ int bitmapFontID,
+ int start,
+ int end,
+ float x,
+ float y) {
+ buffer.start(Operations.DRAW_BITMAP_FONT_TEXT_RUN);
+ buffer.writeInt(textID);
+ buffer.writeInt(bitmapFontID);
+ buffer.writeInt(start);
+ buffer.writeInt(end);
+ buffer.writeFloat(x);
+ buffer.writeFloat(y);
+ }
+
+ /**
+ * Populate the documentation with a description of this operation
+ *
+ * @param doc to append the description to.
+ */
+ public static void documentation(@NonNull DocumentationBuilder doc) {
+ doc.operation("Draw Operations", id(), CLASS_NAME)
+ .description("Draw a run of bitmap font text, all in a single direction")
+ .field(DocumentedOperation.INT, "textId", "id of bitmap")
+ .field(DocumentedOperation.INT, "bitmapFontId", "id of the bitmap font")
+ .field(
+ DocumentedOperation.INT,
+ "start",
+ "The start of the text to render. -1=end of string")
+ .field(DocumentedOperation.INT, "end", "The end of the text to render")
+ .field(
+ DocumentedOperation.INT,
+ "contextStart",
+ "the index of the start of the shaping context")
+ .field(
+ DocumentedOperation.INT,
+ "contextEnd",
+ "the index of the end of the shaping context")
+ .field(DocumentedOperation.FLOAT, "x", "The x position at which to draw the text")
+ .field(DocumentedOperation.FLOAT, "y", "The y position at which to draw the text")
+ .field(DocumentedOperation.BOOLEAN, "RTL", "Whether the run is in RTL direction");
+ }
+
+ @Override
+ public void paint(@NonNull PaintContext context) {
+ RemoteContext remoteContext = context.getContext();
+ String textToPaint = remoteContext.getText(mTextID);
+ if (textToPaint == null) {
+ return;
+ }
+ if (mEnd == -1) {
+ if (mStart != 0) {
+ textToPaint = textToPaint.substring(mStart);
+ }
+ } else if (mEnd > textToPaint.length()) {
+ textToPaint = textToPaint.substring(mStart);
+ } else {
+ textToPaint = textToPaint.substring(mStart, mEnd);
+ }
+
+ BitmapFontData bitmapFont = (BitmapFontData) remoteContext.getObject(mBitmapFontID);
+ if (bitmapFont == null) {
+ return;
+ }
+
+ float xPos = mX;
+ int pos = 0;
+ while (pos < textToPaint.length()) {
+ BitmapFontData.Glyph glyph = bitmapFont.lookupGlyph(textToPaint, pos);
+ if (glyph == null) {
+ pos++;
+ continue;
+ }
+
+ pos += glyph.mChars.length();
+ if (glyph.mBitmapId == -1) {
+ // Space is represented by a glyph of -1.
+ xPos += glyph.mMarginLeft + glyph.mMarginRight;
+ continue;
+ }
+
+ xPos += glyph.mMarginLeft;
+ float xPos2 = xPos + glyph.mBitmapWidth;
+ context.drawBitmap(
+ glyph.mBitmapId, xPos, mY + glyph.mMarginTop, xPos2, mY + glyph.mBitmapHeight);
+ xPos = xPos2 + glyph.mMarginRight;
+ }
+ }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/FloatFunctionCall.java b/core/java/com/android/internal/widget/remotecompose/core/operations/FloatFunctionCall.java
new file mode 100644
index 000000000000..eccc00a18308
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/FloatFunctionCall.java
@@ -0,0 +1,185 @@
+/*
+ * 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.widget.remotecompose.core.operations;
+
+import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.FLOAT_ARRAY;
+import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import com.android.internal.widget.remotecompose.core.Operation;
+import com.android.internal.widget.remotecompose.core.Operations;
+import com.android.internal.widget.remotecompose.core.PaintContext;
+import com.android.internal.widget.remotecompose.core.PaintOperation;
+import com.android.internal.widget.remotecompose.core.RemoteContext;
+import com.android.internal.widget.remotecompose.core.VariableSupport;
+import com.android.internal.widget.remotecompose.core.WireBuffer;
+import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder;
+import com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation;
+import com.android.internal.widget.remotecompose.core.operations.utilities.AnimatedFloatExpression;
+import com.android.internal.widget.remotecompose.core.operations.utilities.NanMap;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/** This provides the command to call a floatfunction defined in floatfunction */
+public class FloatFunctionCall extends PaintOperation implements VariableSupport {
+ private static final int OP_CODE = Operations.FUNCTION_CALL;
+ private static final String CLASS_NAME = "FunctionCall";
+ private final int mId;
+ private final float[] mArgs;
+ private final float[] mOutArgs;
+
+ FloatFunctionDefine mFunction;
+
+ @NonNull private ArrayList<Operation> mList = new ArrayList<>();
+
+ @NonNull AnimatedFloatExpression mExp = new AnimatedFloatExpression();
+
+ /**
+ * Create a new FloatFunctionCall operation
+ *
+ * @param id The function to call
+ * @param args the arguments to call it with
+ */
+ public FloatFunctionCall(int id, float[] args) {
+ mId = id;
+ mArgs = args;
+ if (args != null) {
+ mOutArgs = new float[args.length];
+ System.arraycopy(args, 0, mOutArgs, 0, args.length);
+ } else {
+ mOutArgs = null;
+ }
+ }
+
+ @Override
+ public void updateVariables(@NonNull RemoteContext context) {
+ if (mOutArgs != null) {
+ for (int i = 0; i < mArgs.length; i++) {
+ float v = mArgs[i];
+ mOutArgs[i] =
+ (Float.isNaN(v)
+ && !AnimatedFloatExpression.isMathOperator(v)
+ && !NanMap.isDataVariable(v))
+ ? context.getFloat(Utils.idFromNan(v))
+ : v;
+ }
+ }
+ }
+
+ @Override
+ public void registerListening(@NonNull RemoteContext context) {
+ mFunction = (FloatFunctionDefine) context.getObject(mId);
+ if (mArgs != null) {
+ for (int i = 0; i < mArgs.length; i++) {
+ float v = mArgs[i];
+ if (Float.isNaN(v)
+ && !AnimatedFloatExpression.isMathOperator(v)
+ && !NanMap.isDataVariable(v)) {
+ context.listensTo(Utils.idFromNan(v), this);
+ }
+ }
+ }
+ }
+
+ @Override
+ public void write(@NonNull WireBuffer buffer) {
+ apply(buffer, mId, mArgs);
+ }
+
+ @NonNull
+ @Override
+ public String toString() {
+ String str = "callFunction[" + Utils.idString(mId) + "] ";
+ for (int i = 0; i < mArgs.length; i++) {
+ str += ((i == 0) ? "" : " ,") + Utils.floatToString(mArgs[i], mOutArgs[i]);
+ }
+ return str;
+ }
+
+ /**
+ * Write the operation on the buffer
+ *
+ * @param buffer the buffer to write to
+ * @param id the id of the function to call
+ * @param args the arguments to call the function with
+ */
+ public static void apply(@NonNull WireBuffer buffer, int id, @Nullable float[] args) {
+ buffer.start(OP_CODE);
+ buffer.writeInt(id);
+ if (args != null) {
+ buffer.writeInt(args.length);
+ for (int i = 0; i < args.length; i++) {
+ buffer.writeFloat(args[i]);
+ }
+ } else {
+ buffer.writeInt(0);
+ }
+ }
+
+ /**
+ * Read this operation and add it to the list of operations
+ *
+ * @param buffer the buffer to read
+ * @param operations the list of operations that will be added to
+ */
+ public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
+ int id = buffer.readInt();
+ int argLen = buffer.readInt();
+ float[] args = null;
+ if (argLen > 0) {
+ args = new float[argLen];
+ for (int i = 0; i < argLen; i++) {
+ args[i] = buffer.readFloat();
+ }
+ }
+
+ FloatFunctionCall data = new FloatFunctionCall(id, args);
+ operations.add(data);
+ }
+
+ /**
+ * Populate the documentation with a description of this operation
+ *
+ * @param doc to append the description to.
+ */
+ public static void documentation(@NonNull DocumentationBuilder doc) {
+ doc.operation("Data Operations", OP_CODE, CLASS_NAME)
+ .description("Command to call the function")
+ .field(DocumentedOperation.INT, "id", "id of function to call")
+ .field(INT, "argLen", "the number of Arguments")
+ .field(FLOAT_ARRAY, "values", "argLen", "array of float arguments");
+ }
+
+ @NonNull
+ @Override
+ public String deepToString(@NonNull String indent) {
+ return indent + toString();
+ }
+
+ @Override
+ public void paint(@NonNull PaintContext context) {
+ RemoteContext remoteContext = context.getContext();
+ int[] args = mFunction.getArgs();
+ for (int j = 0; j < mOutArgs.length; j++) {
+ remoteContext.loadFloat(args[j], mOutArgs[j]);
+ updateVariables(remoteContext);
+ }
+ mFunction.execute(remoteContext);
+ }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/FloatFunctionDefine.java b/core/java/com/android/internal/widget/remotecompose/core/operations/FloatFunctionDefine.java
new file mode 100644
index 000000000000..efd4eecb807e
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/FloatFunctionDefine.java
@@ -0,0 +1,168 @@
+/*
+ * 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.widget.remotecompose.core.operations;
+
+import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.FLOAT_ARRAY;
+import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT;
+
+import android.annotation.NonNull;
+
+import com.android.internal.widget.remotecompose.core.Operation;
+import com.android.internal.widget.remotecompose.core.Operations;
+import com.android.internal.widget.remotecompose.core.RemoteContext;
+import com.android.internal.widget.remotecompose.core.VariableSupport;
+import com.android.internal.widget.remotecompose.core.WireBuffer;
+import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder;
+import com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation;
+import com.android.internal.widget.remotecompose.core.operations.layout.Container;
+import com.android.internal.widget.remotecompose.core.operations.utilities.AnimatedFloatExpression;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This defines a function Operator. It contains a collection of commands which are then executed by
+ * the FloatFunctionCall command
+ */
+public class FloatFunctionDefine extends Operation implements VariableSupport, Container {
+ private static final int OP_CODE = Operations.FUNCTION_DEFINE;
+ private static final String CLASS_NAME = "FunctionDefine";
+ private final int mId;
+ private final int[] mFloatVarId;
+ @NonNull private ArrayList<Operation> mList = new ArrayList<>();
+
+ @NonNull AnimatedFloatExpression mExp = new AnimatedFloatExpression();
+
+ /**
+ * @param id The id of the function
+ * @param floatVarId the ids of the variables
+ */
+ public FloatFunctionDefine(int id, int[] floatVarId) {
+ mId = id;
+ mFloatVarId = floatVarId;
+ }
+
+ @NonNull
+ @Override
+ public ArrayList<Operation> getList() {
+ return mList;
+ }
+
+ @Override
+ public void updateVariables(@NonNull RemoteContext context) {}
+
+ @Override
+ public void registerListening(@NonNull RemoteContext context) {
+ context.putObject(mId, this);
+ }
+
+ @Override
+ public void write(@NonNull WireBuffer buffer) {
+ apply(buffer, mId, mFloatVarId);
+ }
+
+ @NonNull
+ @Override
+ public String toString() {
+ String str = "FloatFunctionDefine[" + Utils.idString(mId) + "] (";
+ for (int j = 0; j < mFloatVarId.length; j++) {
+ str += "[" + mFloatVarId[j] + "] ";
+ }
+ str += ")";
+ for (Operation operation : mList) {
+ str += " \n " + operation.toString();
+ }
+ return str;
+ }
+
+ /**
+ * Write the operation on the buffer
+ *
+ * @param buffer the buffer to write to
+ * @param id the id of the function
+ * @param varId the ids of the variables
+ */
+ public static void apply(@NonNull WireBuffer buffer, int id, @NonNull int[] varId) {
+ buffer.start(OP_CODE);
+ buffer.writeInt(id);
+ buffer.writeInt(varId.length);
+ for (int i = 0; i < varId.length; i++) {
+ buffer.writeInt(varId[i]);
+ }
+ }
+
+ /**
+ * Read this operation and add it to the list of operations
+ *
+ * @param buffer the buffer to read
+ * @param operations the list of operations that will be added to
+ */
+ public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
+ int id = buffer.readInt();
+ int varLen = buffer.readInt();
+ int[] varId = new int[varLen];
+ for (int i = 0; i < varId.length; i++) {
+ varId[i] = buffer.readInt();
+ }
+ FloatFunctionDefine data = new FloatFunctionDefine(id, varId);
+ operations.add(data);
+ }
+
+ /**
+ * Populate the documentation with a description of this operation
+ *
+ * @param doc to append the description to.
+ */
+ public static void documentation(@NonNull DocumentationBuilder doc) {
+ doc.operation("Data Operations", OP_CODE, CLASS_NAME)
+ .description("Define a function")
+ .field(DocumentedOperation.INT, "id", "The reference of the function")
+ .field(INT, "varLen", "number of arguments to the function")
+ .field(FLOAT_ARRAY, "id", "varLen", "id equations");
+ }
+
+ @NonNull
+ @Override
+ public String deepToString(@NonNull String indent) {
+ return indent + toString();
+ }
+
+ /**
+ * @return the array of id's
+ */
+ public int[] getArgs() {
+ return mFloatVarId;
+ }
+
+ @Override
+ public void apply(@NonNull RemoteContext context) {}
+
+ /**
+ * Execute the function by applying the list of operations
+ *
+ * @param context the current RemoteContext
+ */
+ public void execute(@NonNull RemoteContext context) {
+ for (Operation op : mList) {
+ if (op instanceof VariableSupport) {
+ ((VariableSupport) op).updateVariables(context);
+ }
+
+ context.incrementOpCount();
+ op.apply(context);
+ }
+ }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/ParticlesCreate.java b/core/java/com/android/internal/widget/remotecompose/core/operations/ParticlesCreate.java
index 9e891c48c065..ee9e7a4045cb 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/ParticlesCreate.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/ParticlesCreate.java
@@ -39,7 +39,7 @@ import java.util.List;
* for constructing the particles
*/
public class ParticlesCreate extends Operation implements VariableSupport {
- private static final int OP_CODE = Operations.PARTICLE_CREATE;
+ private static final int OP_CODE = Operations.PARTICLE_DEFINE;
private static final String CLASS_NAME = "ParticlesCreate";
private final int mId;
private final float[][] mEquations;
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/ParticlesLoop.java b/core/java/com/android/internal/widget/remotecompose/core/operations/ParticlesLoop.java
index 791079070622..8d19c94df604 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/ParticlesLoop.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/ParticlesLoop.java
@@ -38,7 +38,7 @@ import java.util.ArrayList;
import java.util.List;
/**
- * This provides the mechinism to evolve the particles It consist of a restart equation and a list
+ * This provides the mechanism to evolve the particles It consist of a restart equation and a list
* of equations particle restarts if restart equation > 0
*/
public class ParticlesLoop extends PaintOperation implements VariableSupport, Container {
@@ -159,10 +159,10 @@ public class ParticlesLoop extends PaintOperation implements VariableSupport, Co
/**
* Write the operation on the buffer
*
- * @param buffer
- * @param id
- * @param restart
- * @param equations
+ * @param buffer the buffer to write to
+ * @param id the id of the particle system
+ * @param restart the restart equation
+ * @param equations the equations to evolve the particles
*/
public static void apply(
@NonNull WireBuffer buffer,
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/Utils.java b/core/java/com/android/internal/widget/remotecompose/core/operations/Utils.java
index bd68d5a8c180..5f505409e254 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/Utils.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/Utils.java
@@ -289,4 +289,21 @@ public class Utils {
}
return 0;
}
+
+ /**
+ * Convert float alpha, red,g reen, blue to ARGB int
+ *
+ * @param alpha alpha value
+ * @param red red value
+ * @param green green value
+ * @param blue blue value
+ * @return ARGB int
+ */
+ public static int toARGB(float alpha, float red, float green, float blue) {
+ int a = (int) (alpha * 255.0f + 0.5f);
+ int r = (int) (red * 255.0f + 0.5f);
+ int g = (int) (green * 255.0f + 0.5f);
+ int b = (int) (blue * 255.0f + 0.5f);
+ return (a << 24 | r << 16 | g << 8 | b);
+ }
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/Component.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/Component.java
index 8e733ce1d808..96a31aec7dc4 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/Component.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/Component.java
@@ -37,13 +37,15 @@ import com.android.internal.widget.remotecompose.core.operations.layout.measure.
import com.android.internal.widget.remotecompose.core.operations.layout.measure.MeasurePass;
import com.android.internal.widget.remotecompose.core.operations.paint.PaintBundle;
import com.android.internal.widget.remotecompose.core.operations.utilities.StringSerializer;
+import com.android.internal.widget.remotecompose.core.serialize.MapSerializer;
+import com.android.internal.widget.remotecompose.core.serialize.Serializable;
import java.util.ArrayList;
import java.util.HashSet;
/** Generic Component class */
public class Component extends PaintOperation
- implements Container, Measurable, SerializableToString {
+ implements Container, Measurable, SerializableToString, Serializable {
private static final boolean DEBUG = false;
@@ -61,7 +63,7 @@ public class Component extends PaintOperation
public boolean mNeedsMeasure = true;
public boolean mNeedsRepaint = false;
@Nullable public AnimateMeasure mAnimateMeasure;
- @NonNull public AnimationSpec mAnimationSpec = new AnimationSpec();
+ @NonNull public AnimationSpec mAnimationSpec = AnimationSpec.DEFAULT;
public boolean mFirstLayout = true;
@NonNull PaintBundle mPaint = new PaintBundle();
@NonNull protected HashSet<ComponentValue> mComponentValues = new HashSet<>();
@@ -318,6 +320,14 @@ public class Component extends PaintOperation
}
}
+ protected AnimationSpec getAnimationSpec() {
+ return mAnimationSpec;
+ }
+
+ protected void setAnimationSpec(@NonNull AnimationSpec animationSpec) {
+ mAnimationSpec = animationSpec;
+ }
+
public enum Visibility {
GONE,
VISIBLE,
@@ -501,16 +511,17 @@ public class Component extends PaintOperation
*
* @param context
* @param document
- * @param x
- * @param y
+ * @param x x location on screen or -1 if unconditional click
+ * @param y y location on screen or -1 if unconditional click
*/
public void onClick(
@NonNull RemoteContext context, @NonNull CoreDocument document, float x, float y) {
- if (!contains(x, y)) {
+ boolean isUnconditional = x == -1 & y == -1;
+ if (!isUnconditional && !contains(x, y)) {
return;
}
- float cx = x - getScrollX();
- float cy = y - getScrollY();
+ float cx = isUnconditional ? -1 : x - getScrollX();
+ float cy = isUnconditional ? -1 : y - getScrollY();
for (Operation op : mList) {
if (op instanceof Component) {
((Component) op).onClick(context, document, cx, cy);
@@ -1035,4 +1046,15 @@ public class Component extends PaintOperation
}
return null;
}
+
+ @Override
+ public void serialize(MapSerializer serializer) {
+ serializer.add("type", getSerializedName());
+ serializer.add("id", mComponentId);
+ serializer.add("x", mX);
+ serializer.add("y", mY);
+ serializer.add("width", mWidth);
+ serializer.add("height", mHeight);
+ serializer.add("visibility", mVisibility);
+ }
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/LayoutComponent.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/LayoutComponent.java
index dcd334822010..c517e50f35d3 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/LayoutComponent.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/LayoutComponent.java
@@ -32,6 +32,7 @@ import com.android.internal.widget.remotecompose.core.operations.MatrixTranslate
import com.android.internal.widget.remotecompose.core.operations.PaintData;
import com.android.internal.widget.remotecompose.core.operations.TextData;
import com.android.internal.widget.remotecompose.core.operations.TouchExpression;
+import com.android.internal.widget.remotecompose.core.operations.layout.animation.AnimationSpec;
import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ComponentModifiers;
import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ComponentVisibilityOperation;
import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.DimensionModifierOperation;
@@ -44,6 +45,7 @@ import com.android.internal.widget.remotecompose.core.operations.layout.modifier
import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.WidthInModifierOperation;
import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.WidthModifierOperation;
import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ZIndexModifierOperation;
+import com.android.internal.widget.remotecompose.core.serialize.MapSerializer;
import java.util.ArrayList;
@@ -234,6 +236,8 @@ public class LayoutComponent extends Component {
mZIndexModifier = (ZIndexModifierOperation) op;
} else if (op instanceof GraphicsLayerModifierOperation) {
mGraphicsLayerModifier = (GraphicsLayerModifierOperation) op;
+ } else if (op instanceof AnimationSpec) {
+ mAnimationSpec = (AnimationSpec) op;
} else if (op instanceof ScrollDelegate) {
ScrollDelegate scrollDelegate = (ScrollDelegate) op;
if (scrollDelegate.handlesHorizontalScroll()) {
@@ -256,6 +260,16 @@ public class LayoutComponent extends Component {
if (heightInConstraints != null) {
mHeightModifier.setHeightIn(heightInConstraints);
}
+
+ if (mAnimationSpec != AnimationSpec.DEFAULT) {
+ for (int i = 0; i < mChildrenComponents.size(); i++) {
+ Component c = mChildrenComponents.get(i);
+ if (c != null && c.getAnimationSpec() == AnimationSpec.DEFAULT) {
+ c.setAnimationSpec(mAnimationSpec);
+ }
+ }
+ }
+
setWidth(computeModifierDefinedWidth(null));
setHeight(computeModifierDefinedHeight(null));
}
@@ -473,4 +487,14 @@ public class LayoutComponent extends Component {
public ArrayList<Component> getChildrenComponents() {
return mChildrenComponents;
}
+
+ @Override
+ public void serialize(MapSerializer serializer) {
+ super.serialize(serializer);
+ serializer.add("children", mChildrenComponents);
+ serializer.add("paddingLeft", mPaddingLeft);
+ serializer.add("paddingRight", mPaddingRight);
+ serializer.add("paddingTop", mPaddingTop);
+ serializer.add("paddingBottom", mPaddingBottom);
+ }
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/TouchCancelModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/TouchCancelModifierOperation.java
index 4977a15e2dc1..a4e8f5c5f18e 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/TouchCancelModifierOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/TouchCancelModifierOperation.java
@@ -94,6 +94,11 @@ public class TouchCancelModifierOperation extends ListActionsOperation implement
return "TouchCancelModifier";
}
+ /**
+ * Write the operation on the buffer
+ *
+ * @param buffer a WireBuffer
+ */
public static void apply(WireBuffer buffer) {
buffer.start(OP_CODE);
}
@@ -108,6 +113,11 @@ public class TouchCancelModifierOperation extends ListActionsOperation implement
operations.add(new TouchCancelModifierOperation());
}
+ /**
+ * Add documentation for this operation
+ *
+ * @param doc a DocumentationBuilder
+ */
public static void documentation(DocumentationBuilder doc) {
doc.operation("Modifier Operations", OP_CODE, name())
.description(
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/TouchDownModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/TouchDownModifierOperation.java
index 8c51f2eac383..6191bf4e4ad2 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/TouchDownModifierOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/TouchDownModifierOperation.java
@@ -96,14 +96,30 @@ public class TouchDownModifierOperation extends ListActionsOperation implements
return "TouchModifier";
}
+ /**
+ * Write the operation to the buffer
+ *
+ * @param buffer a WireBuffer
+ */
public static void apply(WireBuffer buffer) {
buffer.start(OP_CODE);
}
+ /**
+ * Read the operation from the buffer
+ *
+ * @param buffer a WireBuffer
+ * @param operations the list of operations we read so far
+ */
public static void read(WireBuffer buffer, List<Operation> operations) {
operations.add(new TouchDownModifierOperation());
}
+ /**
+ * Add documentation for this operation
+ *
+ * @param doc a DocumentationBuilder
+ */
public static void documentation(DocumentationBuilder doc) {
doc.operation("Modifier Operations", OP_CODE, name())
.description(
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/TouchUpModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/TouchUpModifierOperation.java
index a12c356f7c48..a7e423e67940 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/TouchUpModifierOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/TouchUpModifierOperation.java
@@ -94,14 +94,30 @@ public class TouchUpModifierOperation extends ListActionsOperation implements To
return "TouchUpModifier";
}
+ /**
+ * Write the operation to the buffer
+ *
+ * @param buffer a WireBuffer
+ */
public static void apply(WireBuffer buffer) {
buffer.start(OP_CODE);
}
+ /**
+ * Read the operation from the buffer
+ *
+ * @param buffer a WireBuffer
+ * @param operations the list of operations we read so far
+ */
public static void read(WireBuffer buffer, List<Operation> operations) {
operations.add(new TouchUpModifierOperation());
}
+ /**
+ * Add documentation for this operation
+ *
+ * @param doc a DocumentationBuilder
+ */
public static void documentation(DocumentationBuilder doc) {
doc.operation("Modifier Operations", OP_CODE, name())
.description(
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/animation/AnimateMeasure.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/animation/AnimateMeasure.java
index d3b3e0e775f2..e5cd485967e8 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/animation/AnimateMeasure.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/animation/AnimateMeasure.java
@@ -38,8 +38,8 @@ public class AnimateMeasure {
private final @NonNull Component mComponent;
private final @NonNull ComponentMeasure mOriginal;
private final @NonNull ComponentMeasure mTarget;
- private int mDuration;
- private int mDurationVisibilityChange = mDuration;
+ private float mDuration;
+ private float mDurationVisibilityChange = mDuration;
private @NonNull AnimationSpec.ANIMATION mEnterAnimation = AnimationSpec.ANIMATION.FADE_IN;
private @NonNull AnimationSpec.ANIMATION mExitAnimation = AnimationSpec.ANIMATION.FADE_OUT;
private int mMotionEasingType = GeneralEasing.CUBIC_STANDARD;
@@ -64,8 +64,8 @@ public class AnimateMeasure {
@NonNull Component component,
@NonNull ComponentMeasure original,
@NonNull ComponentMeasure target,
- int duration,
- int durationVisibilityChange,
+ float duration,
+ float durationVisibilityChange,
@NonNull AnimationSpec.ANIMATION enterAnimation,
@NonNull AnimationSpec.ANIMATION exitAnimation,
int motionEasingType,
@@ -94,6 +94,11 @@ public class AnimateMeasure {
component.mVisibility = target.getVisibility();
}
+ /**
+ * Update the current bounds/visibility/etc given the current time
+ *
+ * @param currentTime the time we use to evaluate the animation
+ */
public void update(long currentTime) {
long elapsed = currentTime - mStartTime;
float motionProgress = elapsed / (float) mDuration;
@@ -347,6 +352,11 @@ public class AnimateMeasure {
return mOriginal.getH() * (1 - mP) + mTarget.getH() * mP;
}
+ /**
+ * Returns the visibility for this measure
+ *
+ * @return the current visibility (possibly interpolated)
+ */
public float getVisibility() {
if (mOriginal.getVisibility() == mTarget.getVisibility()) {
return 1f;
@@ -357,6 +367,12 @@ public class AnimateMeasure {
}
}
+ /**
+ * Set the target values from the given measure
+ *
+ * @param measure the target measure
+ * @param currentTime the current time
+ */
public void updateTarget(@NonNull ComponentMeasure measure, long currentTime) {
mOriginal.setX(getX());
mOriginal.setY(getY());
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/animation/AnimationSpec.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/animation/AnimationSpec.java
index 6dff4a87088b..6e9de58e354a 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/animation/AnimationSpec.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/animation/AnimationSpec.java
@@ -24,25 +24,28 @@ import com.android.internal.widget.remotecompose.core.Operations;
import com.android.internal.widget.remotecompose.core.RemoteContext;
import com.android.internal.widget.remotecompose.core.WireBuffer;
import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder;
+import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ModifierOperation;
+import com.android.internal.widget.remotecompose.core.operations.utilities.StringSerializer;
import com.android.internal.widget.remotecompose.core.operations.utilities.easing.GeneralEasing;
import java.util.List;
/** Basic component animation spec */
-public class AnimationSpec extends Operation {
+public class AnimationSpec extends Operation implements ModifierOperation {
+ public static final AnimationSpec DEFAULT = new AnimationSpec();
int mAnimationId = -1;
- int mMotionDuration = 300;
+ float mMotionDuration = 300;
int mMotionEasingType = GeneralEasing.CUBIC_STANDARD;
- int mVisibilityDuration = 300;
+ float mVisibilityDuration = 300;
int mVisibilityEasingType = GeneralEasing.CUBIC_STANDARD;
@NonNull ANIMATION mEnterAnimation = ANIMATION.FADE_IN;
@NonNull ANIMATION mExitAnimation = ANIMATION.FADE_OUT;
public AnimationSpec(
int animationId,
- int motionDuration,
+ float motionDuration,
int motionEasingType,
- int visibilityDuration,
+ float visibilityDuration,
int visibilityEasingType,
@NonNull ANIMATION enterAnimation,
@NonNull ANIMATION exitAnimation) {
@@ -70,7 +73,7 @@ public class AnimationSpec extends Operation {
return mAnimationId;
}
- public int getMotionDuration() {
+ public float getMotionDuration() {
return mMotionDuration;
}
@@ -78,7 +81,7 @@ public class AnimationSpec extends Operation {
return mMotionEasingType;
}
- public int getVisibilityDuration() {
+ public float getVisibilityDuration() {
return mVisibilityDuration;
}
@@ -102,6 +105,25 @@ public class AnimationSpec extends Operation {
return "ANIMATION_SPEC (" + mMotionDuration + " ms)";
}
+ @Override
+ public void serializeToString(int indent, @NonNull StringSerializer serializer) {
+ serializer.append(
+ indent,
+ "ANIMATION_SPEC = ["
+ + getMotionDuration()
+ + ", "
+ + getMotionEasingType()
+ + ", "
+ + getVisibilityDuration()
+ + ", "
+ + getVisibilityEasingType()
+ + ", "
+ + getEnterAnimation()
+ + ", "
+ + getExitAnimation()
+ + "]");
+ }
+
public enum ANIMATION {
FADE_IN,
FADE_OUT,
@@ -156,10 +178,22 @@ public class AnimationSpec extends Operation {
return Operations.ANIMATION_SPEC;
}
+ /**
+ * Returns an int for the given ANIMATION
+ *
+ * @param animation an ANIMATION enum value
+ * @return a corresponding int value
+ */
public static int animationToInt(@NonNull ANIMATION animation) {
return animation.ordinal();
}
+ /**
+ * Maps int value to the corresponding ANIMATION enum values
+ *
+ * @param value int value mapped to the enum
+ * @return the corresponding ANIMATION enum value
+ */
@NonNull
public static ANIMATION intToAnimation(int value) {
switch (value) {
@@ -184,20 +218,32 @@ public class AnimationSpec extends Operation {
}
}
+ /**
+ * Write the operation to the buffer
+ *
+ * @param buffer a WireBuffer
+ * @param animationId the animation id
+ * @param motionDuration the duration of the motion animation
+ * @param motionEasingType the type of easing for the motion animation
+ * @param visibilityDuration the duration of the visibility animation
+ * @param visibilityEasingType the type of easing for the visibility animation
+ * @param enterAnimation the type of animation when "entering" (newly visible)
+ * @param exitAnimation the type of animation when "exiting" (newly gone)
+ */
public static void apply(
@NonNull WireBuffer buffer,
int animationId,
- int motionDuration,
+ float motionDuration,
int motionEasingType,
- int visibilityDuration,
+ float visibilityDuration,
int visibilityEasingType,
@NonNull ANIMATION enterAnimation,
@NonNull ANIMATION exitAnimation) {
buffer.start(Operations.ANIMATION_SPEC);
buffer.writeInt(animationId);
- buffer.writeInt(motionDuration);
+ buffer.writeFloat(motionDuration);
buffer.writeInt(motionEasingType);
- buffer.writeInt(visibilityDuration);
+ buffer.writeFloat(visibilityDuration);
buffer.writeInt(visibilityEasingType);
buffer.writeInt(animationToInt(enterAnimation));
buffer.writeInt(animationToInt(exitAnimation));
@@ -211,9 +257,9 @@ public class AnimationSpec extends Operation {
*/
public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
int animationId = buffer.readInt();
- int motionDuration = buffer.readInt();
+ float motionDuration = buffer.readFloat();
int motionEasingType = buffer.readInt();
- int visibilityDuration = buffer.readInt();
+ float visibilityDuration = buffer.readFloat();
int visibilityEasingType = buffer.readInt();
ANIMATION enterAnimation = intToAnimation(buffer.readInt());
ANIMATION exitAnimation = intToAnimation(buffer.readInt());
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/animation/ParticleAnimation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/animation/ParticleAnimation.java
index 64e2f004cb65..051579b02cee 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/animation/ParticleAnimation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/animation/ParticleAnimation.java
@@ -30,6 +30,15 @@ public class ParticleAnimation {
@NonNull PaintBundle mPaint = new PaintBundle();
+ /**
+ * Animate the particle animation
+ *
+ * @param context the current paint context
+ * @param component the target component
+ * @param start the component's measure at the end of the animation
+ * @param end the component's measure at the end of the animation
+ * @param progress the current animation progress
+ */
public void animate(
@NonNull PaintContext context,
@NonNull Component component,
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/BoxLayout.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/BoxLayout.java
index a37f35f0c8d8..35d639e65385 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/BoxLayout.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/BoxLayout.java
@@ -29,6 +29,7 @@ import com.android.internal.widget.remotecompose.core.operations.layout.Componen
import com.android.internal.widget.remotecompose.core.operations.layout.measure.ComponentMeasure;
import com.android.internal.widget.remotecompose.core.operations.layout.measure.MeasurePass;
import com.android.internal.widget.remotecompose.core.operations.layout.measure.Size;
+import com.android.internal.widget.remotecompose.core.serialize.MapSerializer;
import java.util.List;
@@ -191,6 +192,15 @@ public class BoxLayout extends LayoutManager {
return Operations.LAYOUT_BOX;
}
+ /**
+ * Write the operation to the buffer
+ *
+ * @param buffer a WireBuffer
+ * @param componentId the component id
+ * @param animationId the component animation id
+ * @param horizontalPositioning the horizontal positioning rules
+ * @param verticalPositioning the vertical positioning rules
+ */
public static void apply(
@NonNull WireBuffer buffer,
int componentId,
@@ -260,4 +270,28 @@ public class BoxLayout extends LayoutManager {
public void write(@NonNull WireBuffer buffer) {
apply(buffer, mComponentId, mAnimationId, mHorizontalPositioning, mVerticalPositioning);
}
+
+ @Override
+ public void serialize(MapSerializer serializer) {
+ super.serialize(serializer);
+ serializer.add("verticalPositioning", getPositioningString(mVerticalPositioning));
+ serializer.add("horizontalPositioning", getPositioningString(mHorizontalPositioning));
+ }
+
+ private String getPositioningString(int pos) {
+ switch (pos) {
+ case START:
+ return "START";
+ case CENTER:
+ return "CENTER";
+ case END:
+ return "END";
+ case TOP:
+ return "TOP";
+ case BOTTOM:
+ return "BOTTOM";
+ default:
+ return "NONE";
+ }
+ }
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/CanvasLayout.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/CanvasLayout.java
index 0091a47eebfb..8448132cbcc1 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/CanvasLayout.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/CanvasLayout.java
@@ -28,6 +28,7 @@ import com.android.internal.widget.remotecompose.core.documentation.Documentatio
import com.android.internal.widget.remotecompose.core.operations.layout.Component;
import com.android.internal.widget.remotecompose.core.operations.layout.measure.ComponentMeasure;
import com.android.internal.widget.remotecompose.core.operations.layout.measure.MeasurePass;
+import com.android.internal.widget.remotecompose.core.serialize.MapSerializer;
import java.util.List;
@@ -91,6 +92,13 @@ public class CanvasLayout extends BoxLayout {
return Operations.LAYOUT_CANVAS;
}
+ /**
+ * Write the operation to the buffer
+ *
+ * @param buffer a WireBuffer
+ * @param componentId the component id
+ * @param animationId the component animation id
+ */
public static void apply(@NonNull WireBuffer buffer, int componentId, int animationId) {
buffer.start(Operations.LAYOUT_CANVAS);
buffer.writeInt(componentId);
@@ -142,4 +150,27 @@ public class CanvasLayout extends BoxLayout {
public void write(@NonNull WireBuffer buffer) {
apply(buffer, mComponentId, mAnimationId);
}
+
+ @Override
+ public void serialize(MapSerializer serializer) {
+ super.serialize(serializer);
+ serializer.add("", mHorizontalPositioning);
+ }
+
+ private String getPositioningString(int pos) {
+ switch (pos) {
+ case START:
+ return "START";
+ case CENTER:
+ return "CENTER";
+ case END:
+ return "END";
+ case TOP:
+ return "TOP";
+ case BOTTOM:
+ return "BOTTOM";
+ default:
+ return "NONE";
+ }
+ }
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/ColumnLayout.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/ColumnLayout.java
index 4d0cbefb0c92..47a55b6ed82a 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/ColumnLayout.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/ColumnLayout.java
@@ -34,6 +34,7 @@ import com.android.internal.widget.remotecompose.core.operations.layout.measure.
import com.android.internal.widget.remotecompose.core.operations.layout.measure.Size;
import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.HeightInModifierOperation;
import com.android.internal.widget.remotecompose.core.operations.layout.utils.DebugLog;
+import com.android.internal.widget.remotecompose.core.serialize.MapSerializer;
import java.util.List;
@@ -218,6 +219,8 @@ public class ColumnLayout extends LayoutManager {
boolean checkWeights = true;
while (checkWeights) {
checkWeights = false;
+ childrenWidth = 0f;
+ childrenHeight = 0f;
boolean hasWeights = false;
float totalWeights = 0f;
for (Component child : mChildrenComponents) {
@@ -477,4 +480,35 @@ public class ColumnLayout extends LayoutManager {
mVerticalPositioning,
mSpacedBy);
}
+
+ @Override
+ public void serialize(MapSerializer serializer) {
+ super.serialize(serializer);
+ serializer.add("verticalPositioning", getPositioningString(mVerticalPositioning));
+ serializer.add("horizontalPositioning", getPositioningString(mHorizontalPositioning));
+ serializer.add("spacedBy", mSpacedBy);
+ }
+
+ private String getPositioningString(int pos) {
+ switch (pos) {
+ case START:
+ return "START";
+ case CENTER:
+ return "CENTER";
+ case END:
+ return "END";
+ case TOP:
+ return "TOP";
+ case BOTTOM:
+ return "BOTTOM";
+ case SPACE_BETWEEN:
+ return "SPACE_BETWEEN";
+ case SPACE_EVENLY:
+ return "SPACE_EVENLY";
+ case SPACE_AROUND:
+ return "SPACE_AROUND";
+ default:
+ return "NONE";
+ }
+ }
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/RowLayout.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/RowLayout.java
index 5b35c4c70702..e93cbd74b0b5 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/RowLayout.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/RowLayout.java
@@ -34,6 +34,7 @@ import com.android.internal.widget.remotecompose.core.operations.layout.measure.
import com.android.internal.widget.remotecompose.core.operations.layout.measure.Size;
import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.WidthInModifierOperation;
import com.android.internal.widget.remotecompose.core.operations.layout.utils.DebugLog;
+import com.android.internal.widget.remotecompose.core.serialize.MapSerializer;
import java.util.List;
@@ -218,6 +219,8 @@ public class RowLayout extends LayoutManager {
while (checkWeights) {
checkWeights = false;
+ childrenWidth = 0f;
+ childrenHeight = 0f;
boolean hasWeights = false;
float totalWeights = 0f;
for (Component child : mChildrenComponents) {
@@ -481,4 +484,35 @@ public class RowLayout extends LayoutManager {
mVerticalPositioning,
mSpacedBy);
}
+
+ @Override
+ public void serialize(MapSerializer serializer) {
+ super.serialize(serializer);
+ serializer.add("verticalPositioning", getPositioningString(mVerticalPositioning));
+ serializer.add("horizontalPositioning", getPositioningString(mHorizontalPositioning));
+ serializer.add("spacedBy", mSpacedBy);
+ }
+
+ private String getPositioningString(int pos) {
+ switch (pos) {
+ case START:
+ return "START";
+ case CENTER:
+ return "CENTER";
+ case END:
+ return "END";
+ case TOP:
+ return "TOP";
+ case BOTTOM:
+ return "BOTTOM";
+ case SPACE_BETWEEN:
+ return "SPACE_BETWEEN";
+ case SPACE_EVENLY:
+ return "SPACE_EVENLY";
+ case SPACE_AROUND:
+ return "SPACE_AROUND";
+ default:
+ return "NONE";
+ }
+ }
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/StateLayout.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/StateLayout.java
index 3044797b17c9..ee16bc2f4459 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/StateLayout.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/StateLayout.java
@@ -30,6 +30,7 @@ import com.android.internal.widget.remotecompose.core.operations.layout.LayoutCo
import com.android.internal.widget.remotecompose.core.operations.layout.measure.ComponentMeasure;
import com.android.internal.widget.remotecompose.core.operations.layout.measure.MeasurePass;
import com.android.internal.widget.remotecompose.core.operations.layout.measure.Size;
+import com.android.internal.widget.remotecompose.core.serialize.MapSerializer;
import java.util.ArrayList;
import java.util.HashMap;
@@ -81,6 +82,7 @@ public class StateLayout extends LayoutManager {
hideLayoutsOtherThan(currentLayoutIndex);
}
+ /** Traverse the list of children and identify animated components across states */
public void findAnimatedComponents() {
for (int i = 0; i < mChildrenComponents.size(); i++) {
Component cs = mChildrenComponents.get(i);
@@ -105,6 +107,10 @@ public class StateLayout extends LayoutManager {
collapsePaintedComponents();
}
+ /**
+ * Traverse the list of components in different states, and if they are similar pick the first
+ * component for painting in all states.
+ */
public void collapsePaintedComponents() {
int numStates = mChildrenComponents.size();
for (Integer id : statePaintedComponents.keySet()) {
@@ -346,6 +352,11 @@ public class StateLayout extends LayoutManager {
measuredLayoutIndex = currentLayoutIndex;
}
+ /**
+ * Hides all layouts that are not the one with the given id
+ *
+ * @param idx the layout id
+ */
public void hideLayoutsOtherThan(int idx) {
int index = 0;
for (Component pane : mChildrenComponents) {
@@ -360,6 +371,12 @@ public class StateLayout extends LayoutManager {
}
}
+ /**
+ * Returns the layout with the given id
+ *
+ * @param idx the component id
+ * @return the LayoutManager with the given id, or the first child of StateLayout if not found
+ */
public @NonNull LayoutManager getLayout(int idx) {
int index = 0;
for (Component pane : mChildrenComponents) {
@@ -485,6 +502,7 @@ public class StateLayout extends LayoutManager {
}
}
+ /** Check if we are at the end of the transition, and if so handles it. */
public void checkEndOfTransition() {
LayoutManager currentLayout = getLayout(measuredLayoutIndex);
LayoutManager previousLayout = getLayout(previousLayoutIndex);
@@ -536,10 +554,16 @@ public class StateLayout extends LayoutManager {
return "STATE_LAYOUT";
}
- // companion object {
- // fun documentation(doc: OrigamiDocumentation) {}
- // }
-
+ /**
+ * write the operation to the buffer
+ *
+ * @param buffer the current buffer
+ * @param componentId the component id
+ * @param animationId the animation id if there's one, -1 otherwise.
+ * @param horizontalPositioning the horizontal positioning rule
+ * @param verticalPositioning the vertical positioning rule
+ * @param indexId the current index
+ */
public static void apply(
@NonNull WireBuffer buffer,
int componentId,
@@ -570,4 +594,10 @@ public class StateLayout extends LayoutManager {
operations.add(
new StateLayout(null, componentId, animationId, 0f, 0f, 100f, 100f, indexId));
}
+
+ @Override
+ public void serialize(MapSerializer serializer) {
+ super.serialize(serializer);
+ serializer.add("indexId", mIndexId);
+ }
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/TextLayout.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/TextLayout.java
index 8157ea05ec45..e8e95db8141d 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/TextLayout.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/TextLayout.java
@@ -34,6 +34,7 @@ import com.android.internal.widget.remotecompose.core.operations.layout.measure.
import com.android.internal.widget.remotecompose.core.operations.paint.PaintBundle;
import com.android.internal.widget.remotecompose.core.operations.utilities.StringSerializer;
import com.android.internal.widget.remotecompose.core.semantics.AccessibleComponent;
+import com.android.internal.widget.remotecompose.core.serialize.MapSerializer;
import java.util.List;
@@ -331,6 +332,20 @@ public class TextLayout extends LayoutManager implements VariableSupport, Access
return Operations.LAYOUT_TEXT;
}
+ /**
+ * Write the operation in the buffer
+ *
+ * @param buffer the WireBuffer we write on
+ * @param componentId the component id
+ * @param animationId the animation id (-1 if not set)
+ * @param textId the text id
+ * @param color the text color
+ * @param fontSize the font size
+ * @param fontStyle the font style
+ * @param fontWeight the font weight
+ * @param fontFamilyId the font family id
+ * @param textAlign the alignment rules
+ */
public static void apply(
@NonNull WireBuffer buffer,
int componentId,
@@ -418,4 +433,16 @@ public class TextLayout extends LayoutManager implements VariableSupport, Access
mFontFamilyId,
mTextAlign);
}
+
+ @Override
+ public void serialize(MapSerializer serializer) {
+ super.serialize(serializer);
+ serializer.add("textId", mTextId);
+ serializer.add("color", mColor);
+ serializer.add("fontSize", mFontSize);
+ serializer.add("fontStyle", mFontStyle);
+ serializer.add("fontWeight", mFontWeight);
+ serializer.add("fontFamilyId", mFontFamilyId);
+ serializer.add("textAlign", mTextAlign);
+ }
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/measure/ComponentMeasure.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/measure/ComponentMeasure.java
index 82f23cdcf766..11ed9f435070 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/measure/ComponentMeasure.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/measure/ComponentMeasure.java
@@ -92,6 +92,11 @@ public class ComponentMeasure {
component.mVisibility);
}
+ /**
+ * Initialize this ComponentMeasure from another ComponentMeasure instance.
+ *
+ * @param m the ComponentMeasure to copy from
+ */
public void copyFrom(@NonNull ComponentMeasure m) {
mX = m.mX;
mY = m.mY;
@@ -100,6 +105,12 @@ public class ComponentMeasure {
mVisibility = m.mVisibility;
}
+ /**
+ * Returns true if the ComponentMeasure passed is identical to us
+ *
+ * @param m the ComponentMeasure to check
+ * @return true if the passed ComponentMeasure is identical to ourself
+ */
public boolean same(@NonNull ComponentMeasure m) {
return mX == m.mX && mY == m.mY && mW == m.mW && mH == m.mH && mVisibility == m.mVisibility;
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/measure/MeasurePass.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/measure/MeasurePass.java
index 5cfb1b43cf15..b14f2d9f8a94 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/measure/MeasurePass.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/measure/MeasurePass.java
@@ -28,10 +28,17 @@ import java.util.HashMap;
public class MeasurePass {
@NonNull HashMap<Integer, ComponentMeasure> mList = new HashMap<>();
+ /** Clear the MeasurePass */
public void clear() {
mList.clear();
}
+ /**
+ * Add a ComponentMeasure to the MeasurePass
+ *
+ * @param measure the ComponentMeasure to add
+ * @throws Exception
+ */
public void add(@NonNull ComponentMeasure measure) throws Exception {
if (measure.mId == -1) {
throw new Exception("Component has no id!");
@@ -39,10 +46,22 @@ public class MeasurePass {
mList.put(measure.mId, measure);
}
+ /**
+ * Returns true if the current MeasurePass already contains a ComponentMeasure for the given id.
+ *
+ * @param id
+ * @return
+ */
public boolean contains(int id) {
return mList.containsKey(id);
}
+ /**
+ * return the ComponentMeasure associated with a given component
+ *
+ * @param c the Component
+ * @return the associated ComponentMeasure
+ */
public @NonNull ComponentMeasure get(@NonNull Component c) {
if (!mList.containsKey(c.getComponentId())) {
ComponentMeasure measure =
@@ -54,6 +73,12 @@ public class MeasurePass {
return mList.get(c.getComponentId());
}
+ /**
+ * Returns the ComponentMeasure associated with the id, creating one if none exists.
+ *
+ * @param id the component id
+ * @return the associated ComponentMeasure
+ */
public @NonNull ComponentMeasure get(int id) {
if (!mList.containsKey(id)) {
ComponentMeasure measure =
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/BackgroundModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/BackgroundModifierOperation.java
index b4240d0e08a7..ac23db0ed599 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/BackgroundModifierOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/BackgroundModifierOperation.java
@@ -130,6 +130,20 @@ public class BackgroundModifierOperation extends DecoratorModifierOperation {
return OP_CODE;
}
+ /**
+ * Write the operation to the buffer
+ *
+ * @param buffer the WireBuffer
+ * @param x x coordinate of the background rect
+ * @param y y coordinate of the background rect
+ * @param width width of the background rect
+ * @param height height of the background rect
+ * @param r red component of the background color
+ * @param g green component of the background color
+ * @param b blue component of the background color
+ * @param a alpha component of the background color
+ * @param shapeType the shape of the background (RECTANGLE=0, CIRCLE=1)
+ */
public static void apply(
@NonNull WireBuffer buffer,
float x,
@@ -205,6 +219,6 @@ public class BackgroundModifierOperation extends DecoratorModifierOperation {
.field(FLOAT, "g", "")
.field(FLOAT, "b", "")
.field(FLOAT, "a", "")
- .field(FLOAT, "shapeType", "");
+ .field(FLOAT, "shapeType", "0 for RECTANGLE, 1 for CIRCLE");
}
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/BorderModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/BorderModifierOperation.java
index df30d9f615e5..06c21bd49f33 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/BorderModifierOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/BorderModifierOperation.java
@@ -176,6 +176,22 @@ public class BorderModifierOperation extends DecoratorModifierOperation {
return OP_CODE;
}
+ /**
+ * Write the operation to the buffer
+ *
+ * @param buffer the WireBuffer
+ * @param x x coordinate of the border rect
+ * @param y y coordinate of the border rect
+ * @param width width of the border rect
+ * @param height height of the border rect
+ * @param borderWidth the width of the border outline
+ * @param roundedCorner rounded corner value in pixels
+ * @param r red component of the border color
+ * @param g green component of the border color
+ * @param b blue component of the border color
+ * @param a alpha component of the border color
+ * @param shapeType the shape type (0 = RECTANGLE, 1 = CIRCLE)
+ */
public static void apply(
@NonNull WireBuffer buffer,
float x,
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ClipRectModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ClipRectModifierOperation.java
index b27fb9200398..ce4449355434 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ClipRectModifierOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ClipRectModifierOperation.java
@@ -76,6 +76,11 @@ public class ClipRectModifierOperation extends DecoratorModifierOperation {
return OP_CODE;
}
+ /**
+ * Write the operation to the buffer
+ *
+ * @param buffer the WireBuffer
+ */
public static void apply(@NonNull WireBuffer buffer) {
buffer.start(OP_CODE);
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ComponentModifiers.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ComponentModifiers.java
index a1609ace2138..dd27f8b6cfe6 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ComponentModifiers.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ComponentModifiers.java
@@ -21,6 +21,7 @@ import com.android.internal.widget.remotecompose.core.CoreDocument;
import com.android.internal.widget.remotecompose.core.PaintContext;
import com.android.internal.widget.remotecompose.core.PaintOperation;
import com.android.internal.widget.remotecompose.core.RemoteContext;
+import com.android.internal.widget.remotecompose.core.SerializableToString;
import com.android.internal.widget.remotecompose.core.VariableSupport;
import com.android.internal.widget.remotecompose.core.WireBuffer;
import com.android.internal.widget.remotecompose.core.operations.MatrixRestore;
@@ -36,7 +37,7 @@ import java.util.ArrayList;
/** Maintain a list of modifiers */
public class ComponentModifiers extends PaintOperation
- implements DecoratorComponent, ClickHandler, TouchHandler {
+ implements DecoratorComponent, ClickHandler, TouchHandler, SerializableToString {
@NonNull ArrayList<ModifierOperation> mList = new ArrayList<>();
@NonNull
@@ -68,6 +69,7 @@ public class ComponentModifiers extends PaintOperation
// nothing
}
+ @Override
public void serializeToString(int indent, @NonNull StringSerializer serializer) {
serializer.append(indent, "MODIFIERS");
for (ModifierOperation m : mList) {
@@ -75,10 +77,20 @@ public class ComponentModifiers extends PaintOperation
}
}
+ /**
+ * Add a ModifierOperation
+ *
+ * @param operation a ModifierOperation
+ */
public void add(@NonNull ModifierOperation operation) {
mList.add(operation);
}
+ /**
+ * Returns the size of the modifier list
+ *
+ * @return number of modifiers
+ */
public int size() {
return mList.size();
}
@@ -133,6 +145,11 @@ public class ComponentModifiers extends PaintOperation
}
}
+ /**
+ * Add the operations to this ComponentModifier
+ *
+ * @param operations list of ModifierOperation
+ */
public void addAll(@NonNull ArrayList<ModifierOperation> operations) {
mList.addAll(operations);
}
@@ -197,6 +214,11 @@ public class ComponentModifiers extends PaintOperation
}
}
+ /**
+ * Returns true if we have a horizontal scroll modifier
+ *
+ * @return true if we have a horizontal scroll modifier, false otherwise
+ */
public boolean hasHorizontalScroll() {
for (ModifierOperation op : mList) {
if (op instanceof ScrollModifierOperation) {
@@ -209,6 +231,11 @@ public class ComponentModifiers extends PaintOperation
return false;
}
+ /**
+ * Returns true if we have a vertical scroll modifier
+ *
+ * @return true if we have a vertical scroll modifier, false otherwise
+ */
public boolean hasVerticalScroll() {
for (ModifierOperation op : mList) {
if (op instanceof ScrollModifierOperation) {
@@ -221,6 +248,12 @@ public class ComponentModifiers extends PaintOperation
return false;
}
+ /**
+ * Set the horizontal scroll dimension (if we have a scroll modifier)
+ *
+ * @param hostDimension the host component horizontal dimension
+ * @param contentDimension the content horizontal dimension
+ */
public void setHorizontalScrollDimension(float hostDimension, float contentDimension) {
for (ModifierOperation op : mList) {
if (op instanceof ScrollModifierOperation) {
@@ -232,6 +265,12 @@ public class ComponentModifiers extends PaintOperation
}
}
+ /**
+ * Set the vertical scroll dimension (if we have a scroll modifier)
+ *
+ * @param hostDimension the host component vertical dimension
+ * @param contentDimension the content vertical dimension
+ */
public void setVerticalScrollDimension(float hostDimension, float contentDimension) {
for (ModifierOperation op : mList) {
if (op instanceof ScrollModifierOperation) {
@@ -243,6 +282,11 @@ public class ComponentModifiers extends PaintOperation
}
}
+ /**
+ * Returns the horizontal scroll dimension if we have a scroll modifier
+ *
+ * @return the horizontal scroll dimension, or 0 if no scroll modifier
+ */
public float getHorizontalScrollDimension() {
for (ModifierOperation op : mList) {
if (op instanceof ScrollModifierOperation) {
@@ -255,6 +299,11 @@ public class ComponentModifiers extends PaintOperation
return 0f;
}
+ /**
+ * Returns the vertical scroll dimension if we have a scroll modifier
+ *
+ * @return the vertical scroll dimension, or 0 if no scroll modifier
+ */
public float getVerticalScrollDimension() {
for (ModifierOperation op : mList) {
if (op instanceof ScrollModifierOperation) {
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ComponentVisibilityOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ComponentVisibilityOperation.java
index c377b756ff38..dd22391c43ac 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ComponentVisibilityOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ComponentVisibilityOperation.java
@@ -52,6 +52,11 @@ public class ComponentVisibilityOperation extends Operation
return "ComponentVisibilityOperation(" + mVisibilityId + ")";
}
+ /**
+ * Returns the serialized name for this operation
+ *
+ * @return the serialized name
+ */
@NonNull
public String serializedName() {
return "COMPONENT_VISIBILITY";
@@ -74,6 +79,12 @@ public class ComponentVisibilityOperation extends Operation
@Override
public void write(@NonNull WireBuffer buffer) {}
+ /**
+ * Write the operation to the buffer
+ *
+ * @param buffer a WireBuffer
+ * @param valueId visibility value
+ */
public static void apply(@NonNull WireBuffer buffer, int valueId) {
buffer.start(OP_CODE);
buffer.writeInt(valueId);
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/DimensionInModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/DimensionInModifierOperation.java
new file mode 100644
index 000000000000..7c9acfe8d2e6
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/DimensionInModifierOperation.java
@@ -0,0 +1,109 @@
+/*
+ * 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.widget.remotecompose.core.operations.layout.modifiers;
+
+import android.annotation.NonNull;
+
+import com.android.internal.widget.remotecompose.core.Operation;
+import com.android.internal.widget.remotecompose.core.RemoteContext;
+import com.android.internal.widget.remotecompose.core.VariableSupport;
+import com.android.internal.widget.remotecompose.core.WireBuffer;
+import com.android.internal.widget.remotecompose.core.operations.Utils;
+import com.android.internal.widget.remotecompose.core.operations.utilities.StringSerializer;
+
+/** Helper class to set the min / max dimension on a component */
+public class DimensionInModifierOperation extends Operation
+ implements ModifierOperation, VariableSupport {
+ int mOpCode = -1;
+
+ float mV1;
+ float mV2;
+ float mValue1;
+ float mValue2;
+
+ public DimensionInModifierOperation(int opcode, float min, float max) {
+ mOpCode = opcode;
+ mValue1 = min;
+ mValue2 = max;
+ if (!Float.isNaN(mValue1)) {
+ mV1 = mValue1;
+ }
+ if (!Float.isNaN(mValue2)) {
+ mV2 = mValue2;
+ }
+ }
+
+ @Override
+ public void updateVariables(@NonNull RemoteContext context) {
+ mV1 = Float.isNaN(mValue1) ? context.getFloat(Utils.idFromNan(mValue1)) : mValue1;
+ mV2 = Float.isNaN(mValue2) ? context.getFloat(Utils.idFromNan(mValue2)) : mValue2;
+ if (mV1 != -1) {
+ mV1 = mV1 * context.getDensity();
+ }
+ if (mV2 != -1) {
+ mV2 = mV2 * context.getDensity();
+ }
+ }
+
+ @Override
+ public void registerListening(@NonNull RemoteContext context) {
+ if (Float.isNaN(mValue1)) {
+ context.listensTo(Utils.idFromNan(mValue1), this);
+ }
+ if (Float.isNaN(mValue2)) {
+ context.listensTo(Utils.idFromNan(mValue2), this);
+ }
+ }
+
+ @Override
+ public void write(@NonNull WireBuffer buffer) {
+ // nothing
+ }
+
+ @Override
+ public void apply(@NonNull RemoteContext context) {
+ // nothing
+ }
+
+ @NonNull
+ @Override
+ public String deepToString(@NonNull String indent) {
+ return indent + toString();
+ }
+
+ /**
+ * Returns the min value
+ *
+ * @return minimum value
+ */
+ public float getMin() {
+ return mV1;
+ }
+
+ /**
+ * Returns the max value
+ *
+ * @return maximum value
+ */
+ public float getMax() {
+ return mV2;
+ }
+
+ @Override
+ public void serializeToString(int indent, @NonNull StringSerializer serializer) {
+ serializer.append(indent, "WIDTH_IN = [" + getMin() + ", " + getMax() + "]");
+ }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/DimensionModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/DimensionModifierOperation.java
index b11deae3d196..88449c4a9016 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/DimensionModifierOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/DimensionModifierOperation.java
@@ -104,6 +104,11 @@ public abstract class DimensionModifierOperation extends Operation
}
}
+ /**
+ * Returns true if the dimension is set using a weight
+ *
+ * @return true if using weight, false otherwise
+ */
public boolean hasWeight() {
return mType == Type.WEIGHT;
}
@@ -136,6 +141,11 @@ public abstract class DimensionModifierOperation extends Operation
mOutValue = mValue = value;
}
+ /**
+ * Returns the serialized name for this operation
+ *
+ * @return the serialized name
+ */
@NonNull
public String serializedName() {
return "DIMENSION";
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/GraphicsLayerModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/GraphicsLayerModifierOperation.java
index 15c2f46093d2..dc5918037946 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/GraphicsLayerModifierOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/GraphicsLayerModifierOperation.java
@@ -222,6 +222,26 @@ public class GraphicsLayerModifierOperation extends DecoratorModifierOperation {
return OP_CODE;
}
+ /**
+ * Write the operation to the buffer
+ *
+ * @param buffer a WireBuffer
+ * @param scaleX scaleX of the layer
+ * @param scaleY scaleY of the layer
+ * @param rotationX rotationX of the layer
+ * @param rotationY rotationY of the layer
+ * @param rotationZ rotationZ of the layer
+ * @param shadowElevation the shadow elevation
+ * @param transformOriginX the X origin of the transformations
+ * @param transformOriginY the Y origin of the transformations
+ * @param alpha the alpha of the layer
+ * @param cameraDistance the camera distance
+ * @param blendMode blending mode of the layer
+ * @param spotShadowColorId the spot shadow color id
+ * @param ambientShadowColorId the ambient shadow color id
+ * @param colorFilterId the color filter id
+ * @param renderEffectId the render effect id
+ */
public static void apply(
WireBuffer buffer,
float scaleX,
@@ -257,6 +277,12 @@ public class GraphicsLayerModifierOperation extends DecoratorModifierOperation {
buffer.writeInt(renderEffectId);
}
+ /**
+ * Read the operation from the buffer
+ *
+ * @param buffer a WireBuffer
+ * @param operations the list of operations read so far
+ */
public static void read(WireBuffer buffer, List<Operation> operations) {
float scaleX = buffer.readFloat();
float scaleY = buffer.readFloat();
@@ -292,6 +318,11 @@ public class GraphicsLayerModifierOperation extends DecoratorModifierOperation {
renderEffectId));
}
+ /**
+ * Populate the documentation with a description of this operation
+ *
+ * @param doc to append the description to.
+ */
public static void documentation(DocumentationBuilder doc) {
doc.operation("Modifier Operations", OP_CODE, CLASS_NAME)
.description("define the GraphicsLayer Modifier")
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HeightInModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HeightInModifierOperation.java
index c19bd2f6b7c0..cc32f2699dfe 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HeightInModifierOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HeightInModifierOperation.java
@@ -19,47 +19,37 @@ import android.annotation.NonNull;
import com.android.internal.widget.remotecompose.core.Operation;
import com.android.internal.widget.remotecompose.core.Operations;
-import com.android.internal.widget.remotecompose.core.PaintContext;
import com.android.internal.widget.remotecompose.core.WireBuffer;
import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder;
import com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation;
-import com.android.internal.widget.remotecompose.core.operations.DrawBase2;
import com.android.internal.widget.remotecompose.core.operations.utilities.StringSerializer;
import java.util.List;
/** Set the min / max height dimension on a component */
-public class HeightInModifierOperation extends DrawBase2 implements ModifierOperation {
+public class HeightInModifierOperation extends DimensionInModifierOperation {
private static final int OP_CODE = Operations.MODIFIER_HEIGHT_IN;
public static final String CLASS_NAME = "HeightInModifierOperation";
- /**
- * Read this operation and add it to the list of operations
- *
- * @param buffer the buffer to read
- * @param operations the list of operations that will be added to
- */
- public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
- Maker m = HeightInModifierOperation::new;
- read(m, buffer, operations);
+ public HeightInModifierOperation(float min, float max) {
+ super(OP_CODE, min, max);
}
- /**
- * Returns the min value
- *
- * @return minimum value
- */
- public float getMin() {
- return mV1;
+ @Override
+ public void write(@NonNull WireBuffer buffer) {
+ apply(buffer, getMin(), getMax());
}
/**
- * Returns the max value
+ * Read this operation and add it to the list of operations
*
- * @return maximum value
+ * @param buffer the buffer to read
+ * @param operations the list of operations that will be added to
*/
- public float getMax() {
- return mV2;
+ public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
+ float v1 = buffer.readFloat();
+ float v2 = buffer.readFloat();
+ operations.add(new HeightInModifierOperation(v1, v2));
}
/**
@@ -81,11 +71,6 @@ public class HeightInModifierOperation extends DrawBase2 implements ModifierOper
return CLASS_NAME;
}
- @Override
- protected void write(@NonNull WireBuffer buffer, float v1, float v2) {
- apply(buffer, v1, v2);
- }
-
/**
* Populate the documentation with a description of this operation
*
@@ -98,14 +83,6 @@ public class HeightInModifierOperation extends DrawBase2 implements ModifierOper
.field(DocumentedOperation.FLOAT, "max", "The maximum height, -1 if not applied");
}
- public HeightInModifierOperation(float min, float max) {
- super(min, max);
- mName = CLASS_NAME;
- }
-
- @Override
- public void paint(@NonNull PaintContext context) {}
-
/**
* Writes out the HeightInModifier to the buffer
*
@@ -114,7 +91,9 @@ public class HeightInModifierOperation extends DrawBase2 implements ModifierOper
* @param y1 start y of the DrawOval
*/
public static void apply(@NonNull WireBuffer buffer, float x1, float y1) {
- write(buffer, OP_CODE, x1, y1);
+ buffer.start(OP_CODE);
+ buffer.writeFloat(x1);
+ buffer.writeFloat(y1);
}
@Override
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HeightModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HeightModifierOperation.java
index 4b50a916b9cd..154740d5536c 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HeightModifierOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HeightModifierOperation.java
@@ -52,6 +52,13 @@ public class HeightModifierOperation extends DimensionModifierOperation {
return OP_CODE;
}
+ /**
+ * Write the operation to the buffer
+ *
+ * @param buffer a WireBuffer
+ * @param type the type of dimension rule (DimensionModifierOperation.Type)
+ * @param value the value of the dimension
+ */
public static void apply(@NonNull WireBuffer buffer, int type, float value) {
buffer.start(OP_CODE);
buffer.writeInt(type);
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HostActionOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HostActionOperation.java
index 2e9d6619d011..09e2228b847a 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HostActionOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HostActionOperation.java
@@ -23,6 +23,7 @@ import com.android.internal.widget.remotecompose.core.CoreDocument;
import com.android.internal.widget.remotecompose.core.Operation;
import com.android.internal.widget.remotecompose.core.Operations;
import com.android.internal.widget.remotecompose.core.RemoteContext;
+import com.android.internal.widget.remotecompose.core.SerializableToString;
import com.android.internal.widget.remotecompose.core.WireBuffer;
import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder;
import com.android.internal.widget.remotecompose.core.operations.layout.ActionOperation;
@@ -32,7 +33,8 @@ import com.android.internal.widget.remotecompose.core.operations.utilities.Strin
import java.util.List;
/** Capture a host action information. This can be triggered on eg. a click. */
-public class HostActionOperation extends Operation implements ActionOperation {
+public class HostActionOperation extends Operation
+ implements ActionOperation, SerializableToString {
private static final int OP_CODE = Operations.HOST_ACTION;
int mActionId = -1;
@@ -51,6 +53,11 @@ public class HostActionOperation extends Operation implements ActionOperation {
return mActionId;
}
+ /**
+ * Returns the serialized name for this operation
+ *
+ * @return the serialized name
+ */
@NonNull
public String serializedName() {
return "HOST_ACTION";
@@ -83,6 +90,12 @@ public class HostActionOperation extends Operation implements ActionOperation {
context.runAction(mActionId, "");
}
+ /**
+ * Write the operation to the buffer
+ *
+ * @param buffer a WireBuffer
+ * @param actionId the action id
+ */
public static void apply(@NonNull WireBuffer buffer, int actionId) {
buffer.start(OP_CODE);
buffer.writeInt(actionId);
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HostNamedActionOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HostNamedActionOperation.java
index 49ef58e0fe53..8a8809c653f8 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HostNamedActionOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HostNamedActionOperation.java
@@ -57,6 +57,11 @@ public class HostNamedActionOperation extends Operation implements ActionOperati
return "HostNamedActionOperation(" + mTextId + " : " + mValueId + ")";
}
+ /**
+ * Name used during serialization
+ *
+ * @return the serialized name for this operation
+ */
@NonNull
public String serializedName() {
return "HOST_NAMED_ACTION";
@@ -105,6 +110,14 @@ public class HostNamedActionOperation extends Operation implements ActionOperati
context.runNamedAction(mTextId, value);
}
+ /**
+ * Write the operation to the buffer
+ *
+ * @param buffer a WireBuffer
+ * @param textId the text id of the action
+ * @param type the type of the action
+ * @param valueId the value id associated with the action
+ */
public static void apply(@NonNull WireBuffer buffer, int textId, int type, int valueId) {
buffer.start(OP_CODE);
buffer.writeInt(textId);
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/MarqueeModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/MarqueeModifierOperation.java
index 9588e99a65b6..4ad11d267406 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/MarqueeModifierOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/MarqueeModifierOperation.java
@@ -110,6 +110,12 @@ public class MarqueeModifierOperation extends DecoratorModifierOperation impleme
mVelocity);
}
+ /**
+ * Serialize the string
+ *
+ * @param indent padding to display
+ * @param serializer append the string
+ */
// @Override
public void serializeToString(int indent, StringSerializer serializer) {
serializer.append(indent, "MARQUEE = [" + mIterations + "]");
@@ -153,14 +159,35 @@ public class MarqueeModifierOperation extends DecoratorModifierOperation impleme
return "MarqueeModifierOperation(" + mIterations + ")";
}
+ /**
+ * Name of the operation
+ *
+ * @return name
+ */
public static String name() {
return CLASS_NAME;
}
+ /**
+ * id of the operation
+ *
+ * @return the operation id
+ */
public static int id() {
return OP_CODE;
}
+ /**
+ * Write the operation to the buffer
+ *
+ * @param buffer a WireBuffer
+ * @param iterations the number of iterations
+ * @param animationMode animation mode
+ * @param repeatDelayMillis repeat delay in ms
+ * @param initialDelayMillis initial delay before the marquee start in ms
+ * @param spacing the spacing between marquee
+ * @param velocity the velocity of the marquee animation
+ */
public static void apply(
WireBuffer buffer,
int iterations,
@@ -178,6 +205,12 @@ public class MarqueeModifierOperation extends DecoratorModifierOperation impleme
buffer.writeFloat(velocity);
}
+ /**
+ * Read this operation and add it to the list of operations
+ *
+ * @param buffer the buffer to read
+ * @param operations the list of operations that will be added to
+ */
public static void read(WireBuffer buffer, List<Operation> operations) {
int iterations = buffer.readInt();
int animationMode = buffer.readInt();
@@ -195,6 +228,11 @@ public class MarqueeModifierOperation extends DecoratorModifierOperation impleme
velocity));
}
+ /**
+ * Populate the documentation with a description of this operation
+ *
+ * @param doc to append the description to.
+ */
public static void documentation(DocumentationBuilder doc) {
doc.operation("Modifier Operations", OP_CODE, CLASS_NAME)
.description("specify a Marquee Modifier")
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ModifierOperation.java
index f8926fef56fa..a86fb2c1f6a5 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ModifierOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ModifierOperation.java
@@ -22,5 +22,11 @@ import com.android.internal.widget.remotecompose.core.operations.utilities.Strin
/** Represents a modifier */
public interface ModifierOperation extends OperationInterface {
+ /**
+ * Serialize the string
+ *
+ * @param indent padding to display
+ * @param serializer append the string
+ */
void serializeToString(int indent, @NonNull StringSerializer serializer);
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/OffsetModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/OffsetModifierOperation.java
index 42719478faf0..2cd2728f0720 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/OffsetModifierOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/OffsetModifierOperation.java
@@ -65,6 +65,12 @@ public class OffsetModifierOperation extends DecoratorModifierOperation {
apply(buffer, mX, mY);
}
+ /**
+ * Serialize the string
+ *
+ * @param indent padding to display
+ * @param serializer append the string
+ */
// @Override
public void serializeToString(int indent, StringSerializer serializer) {
serializer.append(indent, "OFFSET = [" + mX + ", " + mY + "]");
@@ -110,18 +116,36 @@ public class OffsetModifierOperation extends DecoratorModifierOperation {
return OP_CODE;
}
+ /**
+ * Write the operation to the buffer
+ *
+ * @param buffer a WireBuffer
+ * @param x x offset
+ * @param y y offset
+ */
public static void apply(WireBuffer buffer, float x, float y) {
buffer.start(OP_CODE);
buffer.writeFloat(x);
buffer.writeFloat(y);
}
+ /**
+ * Read this operation and add it to the list of operations
+ *
+ * @param buffer the buffer to read
+ * @param operations the list of operations that will be added to
+ */
public static void read(WireBuffer buffer, List<Operation> operations) {
float x = buffer.readFloat();
float y = buffer.readFloat();
operations.add(new OffsetModifierOperation(x, y));
}
+ /**
+ * Populate the documentation with a description of this operation
+ *
+ * @param doc to append the description to.
+ */
public static void documentation(DocumentationBuilder doc) {
doc.operation("Modifier Operations", OP_CODE, CLASS_NAME)
.description("define the Offset Modifier")
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/PaddingModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/PaddingModifierOperation.java
index bcfbdd68472f..3225d5c6f92c 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/PaddingModifierOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/PaddingModifierOperation.java
@@ -132,6 +132,15 @@ public class PaddingModifierOperation extends Operation implements ModifierOpera
return Operations.MODIFIER_PADDING;
}
+ /**
+ * Write operation to the buffer
+ *
+ * @param buffer a WireBuffer
+ * @param left left padding
+ * @param top top padding
+ * @param right right padding
+ * @param bottom bottom padding
+ */
public static void apply(
@NonNull WireBuffer buffer, float left, float top, float right, float bottom) {
buffer.start(Operations.MODIFIER_PADDING);
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/RippleModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/RippleModifierOperation.java
index fe074e4754e2..9787d9b4b399 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/RippleModifierOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/RippleModifierOperation.java
@@ -143,19 +143,40 @@ public class RippleModifierOperation extends DecoratorModifierOperation implemen
serializer.append(indent, "RIPPLE_MODIFIER");
}
+ /**
+ * The operation name
+ *
+ * @return operation name
+ */
@NonNull
public static String name() {
return "RippleModifier";
}
+ /**
+ * Write the operation to the buffer
+ *
+ * @param buffer a WireBuffer
+ */
public static void apply(@NonNull WireBuffer buffer) {
buffer.start(OP_CODE);
}
+ /**
+ * Read this operation and add it to the list of operations
+ *
+ * @param buffer the buffer to read
+ * @param operations the list of operations that will be added to
+ */
public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
operations.add(new RippleModifierOperation());
}
+ /**
+ * Populate the documentation with a description of this operation
+ *
+ * @param doc to append the description to.
+ */
public static void documentation(@NonNull DocumentationBuilder doc) {
doc.operation("Layout Operations", OP_CODE, name())
.description(
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ScrollModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ScrollModifierOperation.java
index 8950579354b7..76b3373a52d9 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ScrollModifierOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ScrollModifierOperation.java
@@ -135,6 +135,12 @@ public class ScrollModifierOperation extends ListActionsOperation
apply(buffer, mDirection, mPositionExpression, mMax, mNotchMax);
}
+ /**
+ * Serialize the string
+ *
+ * @param indent padding to display
+ * @param serializer append the string
+ */
// @Override
public void serializeToString(int indent, StringSerializer serializer) {
serializer.append(indent, "SCROLL = [" + mDirection + "]");
@@ -190,6 +196,15 @@ public class ScrollModifierOperation extends ListActionsOperation
return OP_CODE;
}
+ /**
+ * Write the operation to the buffer
+ *
+ * @param buffer a WireBuffer
+ * @param direction direction of the scroll (HORIZONTAL, VERTICAL)
+ * @param position the current position
+ * @param max the maximum position
+ * @param notchMax the maximum notch
+ */
public static void apply(
WireBuffer buffer, int direction, float position, float max, float notchMax) {
buffer.start(OP_CODE);
@@ -199,6 +214,12 @@ public class ScrollModifierOperation extends ListActionsOperation
buffer.writeFloat(notchMax);
}
+ /**
+ * Read this operation and add it to the list of operations
+ *
+ * @param buffer the buffer to read
+ * @param operations the list of operations that will be added to
+ */
public static void read(WireBuffer buffer, List<Operation> operations) {
int direction = buffer.readInt();
float position = buffer.readFloat();
@@ -207,6 +228,11 @@ public class ScrollModifierOperation extends ListActionsOperation
operations.add(new ScrollModifierOperation(direction, position, max, notchMax));
}
+ /**
+ * Populate the documentation with a description of this operation
+ *
+ * @param doc to append the description to.
+ */
public static void documentation(DocumentationBuilder doc) {
doc.operation("Modifier Operations", OP_CODE, CLASS_NAME)
.description("define a Scroll Modifier")
@@ -300,12 +326,24 @@ public class ScrollModifierOperation extends ListActionsOperation
public void onTouchCancel(
RemoteContext context, CoreDocument document, Component component, float x, float y) {}
+ /**
+ * Set the horizontal scroll dimension
+ *
+ * @param hostDimension the horizontal host dimension
+ * @param contentDimension the horizontal content dimension
+ */
public void setHorizontalScrollDimension(float hostDimension, float contentDimension) {
mHostDimension = hostDimension;
mContentDimension = contentDimension;
mMaxScrollX = contentDimension - hostDimension;
}
+ /**
+ * Set the vertical scroll dimension
+ *
+ * @param hostDimension the vertical host dimension
+ * @param contentDimension the vertical content dimension
+ */
public void setVerticalScrollDimension(float hostDimension, float contentDimension) {
mHostDimension = hostDimension;
mContentDimension = contentDimension;
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueFloatChangeActionOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueFloatChangeActionOperation.java
index b6977a035c9e..d625900fcf2e 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueFloatChangeActionOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueFloatChangeActionOperation.java
@@ -49,6 +49,11 @@ public class ValueFloatChangeActionOperation extends Operation implements Action
return "ValueFloatChangeActionOperation(" + mTargetValueId + ")";
}
+ /**
+ * The name of the operation used during serialization
+ *
+ * @return the operation serialized name
+ */
public String serializedName() {
return "VALUE_FLOAT_CHANGE";
}
@@ -76,18 +81,36 @@ public class ValueFloatChangeActionOperation extends Operation implements Action
context.overrideFloat(mTargetValueId, mValue);
}
+ /**
+ * Write the operation to the buffer
+ *
+ * @param buffer a WireBuffer
+ * @param valueId the value id
+ * @param value the value to set
+ */
public static void apply(WireBuffer buffer, int valueId, float value) {
buffer.start(OP_CODE);
buffer.writeInt(valueId);
buffer.writeFloat(value);
}
+ /**
+ * Read this operation and add it to the list of operations
+ *
+ * @param buffer the buffer to read
+ * @param operations the list of operations that will be added to
+ */
public static void read(WireBuffer buffer, List<Operation> operations) {
int valueId = buffer.readInt();
float value = buffer.readFloat();
operations.add(new ValueFloatChangeActionOperation(valueId, value));
}
+ /**
+ * Populate the documentation with a description of this operation
+ *
+ * @param doc to append the description to.
+ */
public static void documentation(DocumentationBuilder doc) {
doc.operation("Layout Operations", OP_CODE, "ValueFloatChangeActionOperation")
.description(
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueFloatExpressionChangeActionOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueFloatExpressionChangeActionOperation.java
index 766271a70ce4..3f26c5e5575b 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueFloatExpressionChangeActionOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueFloatExpressionChangeActionOperation.java
@@ -50,6 +50,11 @@ public class ValueFloatExpressionChangeActionOperation extends Operation
return "ValueFloatExpressionChangeActionOperation(" + mTargetValueId + ")";
}
+ /**
+ * The name of the operation used during serialization
+ *
+ * @return the operation serialized name
+ */
@NonNull
public String serializedName() {
return "VALUE_FLOAT_EXPRESSION_CHANGE";
@@ -83,6 +88,13 @@ public class ValueFloatExpressionChangeActionOperation extends Operation
document.evaluateFloatExpression(mValueExpressionId, mTargetValueId, context);
}
+ /**
+ * Write the operation to the buffer
+ *
+ * @param buffer a WireBuffer
+ * @param valueId the value id
+ * @param value the value to set
+ */
public static void apply(@NonNull WireBuffer buffer, int valueId, int value) {
buffer.start(OP_CODE);
buffer.writeInt(valueId);
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueIntegerChangeActionOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueIntegerChangeActionOperation.java
index 60166a7b2102..8c5bb6fdb268 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueIntegerChangeActionOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueIntegerChangeActionOperation.java
@@ -49,6 +49,11 @@ public class ValueIntegerChangeActionOperation extends Operation implements Acti
return "ValueChangeActionOperation(" + mTargetValueId + ")";
}
+ /**
+ * The name of the operation used during serialization
+ *
+ * @return the operation serialized name
+ */
@NonNull
public String serializedName() {
return "VALUE_INTEGER_CHANGE";
@@ -81,6 +86,13 @@ public class ValueIntegerChangeActionOperation extends Operation implements Acti
context.overrideInteger(mTargetValueId, mValue);
}
+ /**
+ * Write the operation to the buffer
+ *
+ * @param buffer a WireBuffer
+ * @param valueId the value id
+ * @param value the value to set
+ */
public static void apply(@NonNull WireBuffer buffer, int valueId, int value) {
buffer.start(OP_CODE);
buffer.writeInt(valueId);
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueIntegerExpressionChangeActionOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueIntegerExpressionChangeActionOperation.java
index 502508058465..00c80f12aaba 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueIntegerExpressionChangeActionOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueIntegerExpressionChangeActionOperation.java
@@ -50,6 +50,11 @@ public class ValueIntegerExpressionChangeActionOperation extends Operation
return "ValueIntegerExpressionChangeActionOperation(" + mTargetValueId + ")";
}
+ /**
+ * The name of the operation used during serialization
+ *
+ * @return the operation serialized name
+ */
@NonNull
public String serializedName() {
return "VALUE_INTEGER_EXPRESSION_CHANGE";
@@ -83,6 +88,13 @@ public class ValueIntegerExpressionChangeActionOperation extends Operation
document.evaluateIntExpression(mValueExpressionId, (int) mTargetValueId, context);
}
+ /**
+ * Write the operation to the buffer
+ *
+ * @param buffer a WireBuffer
+ * @param valueId the long id pointing to an int value
+ * @param value the value to set (long id)`
+ */
public static void apply(@NonNull WireBuffer buffer, long valueId, long value) {
buffer.start(OP_CODE);
buffer.writeLong(valueId);
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueStringChangeActionOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueStringChangeActionOperation.java
index 8093bb3c64ec..57e30d4126ba 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueStringChangeActionOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueStringChangeActionOperation.java
@@ -53,6 +53,11 @@ public class ValueStringChangeActionOperation extends Operation implements Actio
return mTargetValueId;
}
+ /**
+ * The name of the operation used during serialization
+ *
+ * @return the operation serialized name
+ */
@NonNull
public String serializedName() {
return "VALUE_CHANGE";
@@ -85,6 +90,13 @@ public class ValueStringChangeActionOperation extends Operation implements Actio
context.overrideText(mTargetValueId, mValueId);
}
+ /**
+ * Write the operation to the buffer
+ *
+ * @param buffer a WireBuffer
+ * @param valueId the string id
+ * @param value the value to set (string id)`
+ */
public static void apply(@NonNull WireBuffer buffer, int valueId, int value) {
buffer.start(OP_CODE);
buffer.writeInt(valueId);
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/WidthInModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/WidthInModifierOperation.java
index c3624e5b3d88..8c1ffbd2c500 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/WidthInModifierOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/WidthInModifierOperation.java
@@ -19,47 +19,37 @@ import android.annotation.NonNull;
import com.android.internal.widget.remotecompose.core.Operation;
import com.android.internal.widget.remotecompose.core.Operations;
-import com.android.internal.widget.remotecompose.core.PaintContext;
import com.android.internal.widget.remotecompose.core.WireBuffer;
import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder;
import com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation;
-import com.android.internal.widget.remotecompose.core.operations.DrawBase2;
import com.android.internal.widget.remotecompose.core.operations.utilities.StringSerializer;
import java.util.List;
/** Set the min / max width dimension on a component */
-public class WidthInModifierOperation extends DrawBase2 implements ModifierOperation {
+public class WidthInModifierOperation extends DimensionInModifierOperation {
private static final int OP_CODE = Operations.MODIFIER_WIDTH_IN;
public static final String CLASS_NAME = "WidthInModifierOperation";
- /**
- * Read this operation and add it to the list of operations
- *
- * @param buffer the buffer to read
- * @param operations the list of operations that will be added to
- */
- public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
- Maker m = WidthInModifierOperation::new;
- read(m, buffer, operations);
+ public WidthInModifierOperation(float min, float max) {
+ super(OP_CODE, min, max);
}
- /**
- * Returns the min value
- *
- * @return minimum value
- */
- public float getMin() {
- return mV1;
+ @Override
+ public void write(@NonNull WireBuffer buffer) {
+ apply(buffer, getMin(), getMax());
}
/**
- * Returns the max value
+ * Read this operation and add it to the list of operations
*
- * @return maximum value
+ * @param buffer the buffer to read
+ * @param operations the list of operations that will be added to
*/
- public float getMax() {
- return mV2;
+ public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
+ float v1 = buffer.readFloat();
+ float v2 = buffer.readFloat();
+ operations.add(new WidthInModifierOperation(v1, v2));
}
/**
@@ -81,11 +71,6 @@ public class WidthInModifierOperation extends DrawBase2 implements ModifierOpera
return CLASS_NAME;
}
- @Override
- protected void write(@NonNull WireBuffer buffer, float v1, float v2) {
- apply(buffer, v1, v2);
- }
-
/**
* Populate the documentation with a description of this operation
*
@@ -98,14 +83,6 @@ public class WidthInModifierOperation extends DrawBase2 implements ModifierOpera
.field(DocumentedOperation.FLOAT, "max", "The maximum width, -1 if not applied");
}
- public WidthInModifierOperation(float min, float max) {
- super(min, max);
- mName = CLASS_NAME;
- }
-
- @Override
- public void paint(@NonNull PaintContext context) {}
-
/**
* Writes out the WidthInModifier to the buffer
*
@@ -114,7 +91,9 @@ public class WidthInModifierOperation extends DrawBase2 implements ModifierOpera
* @param y1 start y of the DrawOval
*/
public static void apply(@NonNull WireBuffer buffer, float x1, float y1) {
- write(buffer, OP_CODE, x1, y1);
+ buffer.start(OP_CODE);
+ buffer.writeFloat(x1);
+ buffer.writeFloat(y1);
}
@Override
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/WidthModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/WidthModifierOperation.java
index 532027ab2087..687238e62bac 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/WidthModifierOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/WidthModifierOperation.java
@@ -52,6 +52,13 @@ public class WidthModifierOperation extends DimensionModifierOperation {
return OP_CODE;
}
+ /**
+ * Write the operation to the buffer
+ *
+ * @param buffer a WireBuffer
+ * @param type the type of dimension rule (DimensionModifierOperation.Type)
+ * @param value the value of the dimension
+ */
public static void apply(@NonNull WireBuffer buffer, int type, float value) {
buffer.start(OP_CODE);
buffer.writeInt(type);
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ZIndexModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ZIndexModifierOperation.java
index 35de33a9997a..52841a7e6779 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ZIndexModifierOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ZIndexModifierOperation.java
@@ -55,6 +55,12 @@ public class ZIndexModifierOperation extends DecoratorModifierOperation {
apply(buffer, mValue);
}
+ /**
+ * Serialize the string
+ *
+ * @param indent padding to display
+ * @param serializer append the string
+ */
// @Override
public void serializeToString(int indent, StringSerializer serializer) {
serializer.append(indent, "ZINDEX = [" + mValue + "]");
@@ -99,16 +105,33 @@ public class ZIndexModifierOperation extends DecoratorModifierOperation {
return OP_CODE;
}
+ /**
+ * Write the operation to the buffer
+ *
+ * @param buffer a WireBuffer
+ * @param value the z-index value
+ */
public static void apply(WireBuffer buffer, float value) {
buffer.start(OP_CODE);
buffer.writeFloat(value);
}
+ /**
+ * Read this operation and add it to the list of operations
+ *
+ * @param buffer the buffer to read
+ * @param operations the list of operations that will be added to
+ */
public static void read(WireBuffer buffer, List<Operation> operations) {
float value = buffer.readFloat();
operations.add(new ZIndexModifierOperation(value));
}
+ /**
+ * Populate the documentation with a description of this operation
+ *
+ * @param doc to append the description to.
+ */
public static void documentation(DocumentationBuilder doc) {
doc.operation("Modifier Operations", OP_CODE, CLASS_NAME)
.description("define the Z-Index Modifier")
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/paint/PaintBundle.java b/core/java/com/android/internal/widget/remotecompose/core/operations/paint/PaintBundle.java
index 95434696abdc..4c7f503e0bf8 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/paint/PaintBundle.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/paint/PaintBundle.java
@@ -682,9 +682,9 @@ public class PaintBundle {
* @param radius Must be positive. The radius of the gradient.
* @param colors The sRGB colors distributed between the center and edge
* @param stops May be <code>null</code>. Valid values are between <code>0.0f</code> and <code>
- * 1.0f</code>. The relative position of each corresponding color in the colors array. If
- * <code>null</code>, colors are distributed evenly between the center and edge of the
- * circle.
+ * 1.0f</code>. The relative position of each corresponding color in the colors
+ * array. If <code>null</code>, colors are distributed evenly between the center and edge of
+ * the circle.
* @param tileMode The Shader tiling mode
*/
public void setRadialGradient(
@@ -808,7 +808,7 @@ public class PaintBundle {
}
/**
- * Set the color based the R,G,B,A values
+ * Set the color based the R,G,B,A values (Warning this does not support NaN ids)
*
* @param r red (0.0 to 1.0)
* @param g green (0.0 to 1.0)
@@ -816,7 +816,7 @@ public class PaintBundle {
* @param a alpha (0.0 to 1.0)
*/
public void setColor(float r, float g, float b, float a) {
- setColor((int) (r * 255), (int) (g * 255), (int) (b * 255), (int) (a * 255));
+ setColor(Utils.toARGB(a, r, g, b));
}
/**
@@ -897,6 +897,11 @@ public class PaintBundle {
mPos++;
}
+ /**
+ * set Filter Bitmap
+ *
+ * @param filter set to false to disable interpolation
+ */
public void setFilterBitmap(boolean filter) {
mArray[mPos] = FILTER_BITMAP | (filter ? (1 << 16) : 0);
mPos++;
@@ -944,6 +949,12 @@ public class PaintBundle {
}
}
+ /**
+ * Convert a blend mode integer as a string
+ *
+ * @param mode the blend mode
+ * @return the blend mode as a string
+ */
public static @NonNull String blendModeString(int mode) {
switch (mode) {
case PaintBundle.BLEND_MODE_CLEAR:
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/paint/TextPaint.java b/core/java/com/android/internal/widget/remotecompose/core/operations/paint/TextPaint.java
index ff6f45db5385..2812eed8a5ab 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/paint/TextPaint.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/paint/TextPaint.java
@@ -17,51 +17,250 @@ package com.android.internal.widget.remotecompose.core.operations.paint;
import android.annotation.NonNull;
+import java.util.Locale;
+
// TODO: this interface is unused. Delete it.
public interface TextPaint {
+
+ /**
+ * Helper to setColor(), that takes a,r,g,b and constructs the color int
+ *
+ * @param a The new alpha component (0..255) of the paint's color.
+ * @param r The new red component (0..255) of the paint's color.
+ * @param g The new green component (0..255) of the paint's color.
+ * @param b The new blue component (0..255) of the paint's color.
+ */
void setARGB(int a, int r, int g, int b);
+ /**
+ * Helper for setFlags(), setting or clearing the DITHER_FLAG bit Dithering affects how colors
+ * that are higher precision than the device are down-sampled. No dithering is generally faster,
+ * but higher precision colors are just truncated down (e.g. 8888 -> 565). Dithering tries to
+ * distribute the error inherent in this process, to reduce the visual artifacts.
+ *
+ * @param dither true to set the dithering bit in flags, false to clear it
+ */
void setDither(boolean dither);
+ /**
+ * Set the paint's elegant height metrics flag. This setting selects font variants that have not
+ * been compacted to fit Latin-based vertical metrics, and also increases top and bottom bounds
+ * to provide more space.
+ *
+ * @param elegant set the paint's elegant metrics flag for drawing text.
+ */
void setElegantTextHeight(boolean elegant);
+ /**
+ * Set a end hyphen edit on the paint.
+ *
+ * <p>By setting end hyphen edit, the measurement and drawing is performed with modifying
+ * hyphenation at the end of line. For example, by passing character is appended at the end of
+ * line.
+ *
+ * <pre>
+ * <code>
+ * Paint paint = new Paint();
+ * paint.setEndHyphenEdit(Paint.END_HYPHEN_EDIT_INSERT_HYPHEN);
+ * paint.measureText("abc", 0, 3); // Returns the width of "abc-"
+ * Canvas.drawText("abc", 0, 3, 0f, 0f, paint); // Draws "abc-"
+ * </code>
+ * </pre>
+ *
+ * @param endHyphen a end hyphen edit value.
+ */
void setEndHyphenEdit(int endHyphen);
+ /**
+ * Helper for setFlags(), setting or clearing the FAKE_BOLD_TEXT_FLAG bit
+ *
+ * @param fakeBoldText true to set the fakeBoldText bit in the paint's flags, false to clear it.
+ */
void setFakeBoldText(boolean fakeBoldText);
+ /**
+ * Set the paint's flags. Use the Flag enum to specific flag values.
+ *
+ * @param flags The new flag bits for the paint
+ */
void setFlags(int flags);
+ /**
+ * Set font feature settings.
+ *
+ * <p>The format is the same as the CSS font-feature-settings attribute: <a
+ * href="https://www.w3.org/TR/css-fonts-3/#font-feature-settings-prop">
+ * https://www.w3.org/TR/css-fonts-3/#font-feature-settings-prop</a>
+ *
+ * @param settings the font feature settings string to use, may be null.
+ */
void setFontFeatureSettings(@NonNull String settings);
+ /**
+ * Set the paint's hinting mode. May be either
+ *
+ * @param mode The new hinting mode. (HINTING_OFF or HINTING_ON)
+ */
void setHinting(int mode);
+ /**
+ * Set the paint's letter-spacing for text. The default value is 0. The value is in 'EM' units.
+ * Typical values for slight expansion will be around 0.05. Negative values tighten text.
+ *
+ * @param letterSpacing set the paint's letter-spacing for drawing text.
+ */
void setLetterSpacing(float letterSpacing);
+ /**
+ * Helper for setFlags(), setting or clearing the LINEAR_TEXT_FLAG bit
+ *
+ * @param linearText true to set the linearText bit in the paint's flags, false to clear it.
+ */
void setLinearText(boolean linearText);
+ /**
+ * This draws a shadow layer below the main layer, with the specified offset and color, and blur
+ * radius. If radius is 0, then the shadow layer is removed.
+ *
+ * <p>Can be used to create a blurred shadow underneath text. Support for use with other drawing
+ * operations is constrained to the software rendering pipeline.
+ *
+ * <p>The alpha of the shadow will be the paint's alpha if the shadow color is opaque, or the
+ * alpha from the shadow color if not.
+ *
+ * @param radius the radius of the shadows
+ * @param dx the x offset of the shadow
+ * @param dy the y offset of the shadow
+ * @param shadowColor the color of the shadow
+ */
void setShadowLayer(float radius, float dx, float dy, int shadowColor);
+ /**
+ * Set a start hyphen edit on the paint.
+ *
+ * <p>By setting start hyphen edit, the measurement and drawing is performed with modifying
+ * hyphenation at the start of line. For example, by passing character is appended at the start
+ * of line.
+ *
+ * <pre>
+ * <code>
+ * Paint paint = new Paint();
+ * paint.setStartHyphenEdit(Paint.START_HYPHEN_EDIT_INSERT_HYPHEN);
+ * paint.measureText("abc", 0, 3); // Returns the width of "-abc"
+ * Canvas.drawText("abc", 0, 3, 0f, 0f, paint); // Draws "-abc"
+ * </code>
+ * </pre>
+ *
+ * The default value is 0 which is equivalent to
+ *
+ * @param startHyphen a start hyphen edit value.
+ */
void setStartHyphenEdit(int startHyphen);
+ /**
+ * Helper for setFlags(), setting or clearing the STRIKE_THRU_TEXT_FLAG bit
+ *
+ * @param strikeThruText true to set the strikeThruText bit in the paint's flags, false to clear
+ * it.
+ */
void setStrikeThruText(boolean strikeThruText);
+ /**
+ * Set the paint's Cap.
+ *
+ * @param cap set the paint's line cap style, used whenever the paint's style is Stroke or
+ * StrokeAndFill.
+ */
void setStrokeCap(int cap);
+ /**
+ * Helper for setFlags(), setting or clearing the SUBPIXEL_TEXT_FLAG bit
+ *
+ * @param subpixelText true to set the subpixelText bit in the paint's flags, false to clear it.
+ */
void setSubpixelText(boolean subpixelText);
+ /**
+ * Set the paint's text alignment. This controls how the text is positioned relative to its
+ * origin. LEFT align means that all of the text will be drawn to the right of its origin (i.e.
+ * the origin specifies the LEFT edge of the text) and so on.
+ *
+ * @param align set the paint's Align value for drawing text.
+ */
void setTextAlign(int align);
+ /**
+ * Set the text locale list to a one-member list consisting of just the locale.
+ *
+ * @param locale the paint's locale value for drawing text, must not be null.
+ */
void setTextLocale(int locale);
+ /**
+ * Set the text locale list.
+ *
+ * <p>The text locale list affects how the text is drawn for some languages.
+ *
+ * <p>For example, if the locale list contains {@link Locale#CHINESE} or {@link Locale#CHINA},
+ * then the text renderer will prefer to draw text using a Chinese font. Likewise, if the locale
+ * list contains {@link Locale#JAPANESE} or {@link Locale#JAPAN}, then the text renderer will
+ * prefer to draw text using a Japanese font. If the locale list contains both, the order those
+ * locales appear in the list is considered for deciding the font.
+ *
+ * <p>This distinction is important because Chinese and Japanese text both use many of the same
+ * Unicode code points but their appearance is subtly different for each language.
+ *
+ * <p>By default, the text locale list is initialized to a one-member list just containing the
+ * system locales. This assumes that the text to be rendered will most likely be in the user's
+ * preferred language.
+ *
+ * <p>If the actual language or languages of the text is/are known, then they can be provided to
+ * the text renderer using this method. The text renderer may attempt to guess the language
+ * script based on the contents of the text to be drawn independent of the text locale here.
+ * Specifying the text locales just helps it do a better job in certain ambiguous cases.
+ *
+ * @param localesArray the paint's locale list for drawing text, must not be null or empty.
+ */
void setTextLocales(int localesArray);
+ /**
+ * Set the paint's horizontal scale factor for text. The default value is 1.0. Values > 1.0 will
+ * stretch the text wider. Values < 1.0 will stretch the text narrower.
+ *
+ * @param scaleX set the paint's scale in X for drawing/measuring text.
+ */
void setTextScaleX(float scaleX);
+ /**
+ * Set the paint's text size. This value must be > 0
+ *
+ * @param textSize set the paint's text size in pixel units.
+ */
void setTextSize(float textSize);
+ /**
+ * Set the paint's horizontal skew factor for text. The default value is 0. For approximating
+ * oblique text, use values around -0.25.
+ *
+ * @param skewX set the paint's skew factor in X for drawing text.
+ */
void setTextSkewX(float skewX);
+ /**
+ * Helper for setFlags(), setting or clearing the UNDERLINE_TEXT_FLAG bit
+ *
+ * @param underlineText true to set the underlineText bit in the paint's flags, false to clear
+ * it.
+ */
void setUnderlineText(boolean underlineText);
+ /**
+ * Set the paint's extra word-spacing for text.
+ *
+ * <p>Increases the white space width between words with the given amount of pixels. The default
+ * value is 0.
+ *
+ * @param wordSpacing set the paint's extra word-spacing for drawing text in pixels.
+ */
void setWordSpacing(float wordSpacing);
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/CollectionsAccess.java b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/CollectionsAccess.java
index b92f96f63eb9..704f6ce3447a 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/CollectionsAccess.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/CollectionsAccess.java
@@ -22,6 +22,14 @@ import android.annotation.Nullable;
* unavailable
*/
public interface CollectionsAccess {
+
+ /**
+ * Get the float value in the array at the given index
+ *
+ * @param id the id of the float array
+ * @param index the index of the value
+ * @return
+ */
float getFloatValue(int id, int index);
/**
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/DataMap.java b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/DataMap.java
index 07a3d8482db2..04beba3c4af8 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/DataMap.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/DataMap.java
@@ -28,6 +28,12 @@ public class DataMap {
mIds = ids;
}
+ /**
+ * Return position for given string
+ *
+ * @param str string
+ * @return position associated with the string
+ */
public int getPos(@NonNull String str) {
for (int i = 0; i < mNames.length; i++) {
String name = mNames[i];
@@ -38,10 +44,22 @@ public class DataMap {
return -1;
}
+ /**
+ * Return type for given index
+ *
+ * @param pos index
+ * @return type at index
+ */
public byte getType(int pos) {
return mTypes[pos];
}
+ /**
+ * Return id for given index
+ *
+ * @param pos index
+ * @return id at index
+ */
public int getId(int pos) {
return mIds[pos];
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/ImageScaling.java b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/ImageScaling.java
index 98ee91b370e0..93af8bcef206 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/ImageScaling.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/ImageScaling.java
@@ -76,6 +76,20 @@ public class ImageScaling {
adjustDrawToType();
}
+ /**
+ * Setup the ImageScaling
+ *
+ * @param srcLeft src left
+ * @param srcTop src top
+ * @param srcRight src right
+ * @param srcBottom src bottom
+ * @param dstLeft destination left
+ * @param dstTop destination top
+ * @param dstRight destination right
+ * @param dstBottom destination bottom
+ * @param type type of scaling
+ * @param scale scale factor
+ */
public void setup(
float srcLeft,
float srcTop,
@@ -215,6 +229,12 @@ public class ImageScaling {
}
}
+ /**
+ * Utility to map a string to the given type
+ *
+ * @param type
+ * @return
+ */
@NonNull
public static String typeToString(int type) {
String[] typeString = {
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/IntMap.java b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/IntMap.java
index b9aa88146f2a..257eb0645938 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/IntMap.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/IntMap.java
@@ -39,12 +39,20 @@ public class IntMap<T> {
}
}
+ /** Clear the map */
public void clear() {
Arrays.fill(mKeys, NOT_PRESENT);
mValues.clear();
mSize = 0;
}
+ /**
+ * Insert the value into the map with the given key
+ *
+ * @param key
+ * @param value
+ * @return
+ */
@Nullable
public T put(int key, @NonNull T value) {
if (key == NOT_PRESENT) throw new IllegalArgumentException("Key cannot be NOT_PRESENT");
@@ -54,6 +62,12 @@ public class IntMap<T> {
return insert(key, value);
}
+ /**
+ * Return the value associated with the given key
+ *
+ * @param key
+ * @return
+ */
@Nullable
public T get(int key) {
int index = findKey(key);
@@ -62,6 +76,11 @@ public class IntMap<T> {
} else return mValues.get(index);
}
+ /**
+ * Return the size of the map
+ *
+ * @return
+ */
public int size() {
return mSize;
}
@@ -117,6 +136,12 @@ public class IntMap<T> {
}
}
+ /**
+ * Remote the key from the map
+ *
+ * @param key
+ * @return
+ */
@Nullable
public T remove(int key) {
int index = hash(key) % mKeys.length;
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/NanMap.java b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/NanMap.java
index 0616cc7306f5..1f98f62a20b3 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/NanMap.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/NanMap.java
@@ -41,18 +41,42 @@ public class NanMap {
public static final float CLOSE_NAN = Utils.asNan(CLOSE);
public static final float DONE_NAN = Utils.asNan(DONE);
+ /**
+ * Returns true if the float id is a system variable
+ *
+ * @param value the id encoded as float NaN
+ * @return
+ */
public static boolean isSystemVariable(float value) {
return (fromNaN(value) >> 20) == 0;
}
+ /**
+ * Returns true if the float id is a normal variable
+ *
+ * @param value the id encoded as float NaN
+ * @return
+ */
public static boolean isNormalVariable(float value) {
return (fromNaN(value) >> 20) == 1;
}
+ /**
+ * Returns true if the float id is a data variable
+ *
+ * @param value the id encoded as float NaN
+ * @return
+ */
public static boolean isDataVariable(float value) {
return (fromNaN(value) >> 20) == 2;
}
+ /**
+ * Returns true if the float id is an operation variable
+ *
+ * @param value the id encoded as float NaN
+ * @return
+ */
public static boolean isOperationVariable(float value) {
return (fromNaN(value) >> 20) == 3;
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/semantics/AccessibleComponent.java b/core/java/com/android/internal/widget/remotecompose/core/semantics/AccessibleComponent.java
index cc6c2a6ac7a9..928e9ea1a280 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/semantics/AccessibleComponent.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/semantics/AccessibleComponent.java
@@ -134,6 +134,12 @@ public interface AccessibleComponent extends AccessibilitySemantics {
return mDescription;
}
+ /**
+ * Map int value to Role enum value
+ *
+ * @param i int value
+ * @return corresponding enum value
+ */
public static Role fromInt(int i) {
if (i < UNKNOWN.ordinal()) {
return Role.values()[i];
diff --git a/core/java/com/android/internal/widget/remotecompose/core/serialize/MapSerializer.java b/core/java/com/android/internal/widget/remotecompose/core/serialize/MapSerializer.java
new file mode 100644
index 000000000000..2be8057ce097
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/serialize/MapSerializer.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.widget.remotecompose.core.serialize;
+
+import android.annotation.Nullable;
+
+import java.util.List;
+import java.util.Map;
+
+/** Represents a serializer for a map */
+public interface MapSerializer {
+
+ /**
+ * Add a list entry to this map. The List values can be any primitive, List, Map, or
+ * Serializable
+ *
+ * @param key The key
+ * @param value The list
+ */
+ <T> void add(String key, @Nullable List<T> value);
+
+ /**
+ * Add a map entry to this map. The map values can be any primitive, List, Map, or Serializable
+ *
+ * @param key The key
+ * @param value The list
+ */
+ <T> void add(String key, @Nullable Map<String, T> value);
+
+ /**
+ * Adds any Serializable type to this map
+ *
+ * @param key The key
+ * @param value The Serializable
+ */
+ void add(String key, @Nullable Serializable value);
+
+ /**
+ * Adds a String entry
+ *
+ * @param key The key
+ * @param value The String
+ */
+ void add(String key, @Nullable String value);
+
+ /**
+ * Adds a Byte entry
+ *
+ * @param key The key
+ * @param value The Byte
+ */
+ void add(String key, @Nullable Byte value);
+
+ /**
+ * Adds a Short entry
+ *
+ * @param key The key
+ * @param value The Short
+ */
+ void add(String key, @Nullable Short value);
+
+ /**
+ * Adds an Integer entry
+ *
+ * @param key The key
+ * @param value The Integer
+ */
+ void add(String key, @Nullable Integer value);
+
+ /**
+ * Adds a Long entry
+ *
+ * @param key The key
+ * @param value The Long
+ */
+ void add(String key, @Nullable Long value);
+
+ /**
+ * Adds a Float entry
+ *
+ * @param key The key
+ * @param value The Float
+ */
+ void add(String key, @Nullable Float value);
+
+ /**
+ * Adds a Double entry
+ *
+ * @param key The key
+ * @param value The Double
+ */
+ void add(String key, @Nullable Double value);
+
+ /**
+ * Adds a Boolean entry
+ *
+ * @param key The key
+ * @param value The Boolean
+ */
+ void add(String key, @Nullable Boolean value);
+
+ /**
+ * Adds a Enum entry
+ *
+ * @param key The key
+ * @param value The Enum
+ */
+ <T extends Enum<T>> void add(String key, @Nullable Enum<T> value);
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/serialize/Serializable.java b/core/java/com/android/internal/widget/remotecompose/core/serialize/Serializable.java
new file mode 100644
index 000000000000..820cdccfeabb
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/serialize/Serializable.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.widget.remotecompose.core.serialize;
+
+/** Implementation for any class that wants to serialize itself */
+public interface Serializable {
+
+ /**
+ * Called when this class is to be serialized
+ *
+ * @param serializer Interface to the serializer
+ */
+ void serialize(MapSerializer serializer);
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/player/RemoteComposePlayer.java b/core/java/com/android/internal/widget/remotecompose/player/RemoteComposePlayer.java
index b17e3dc82d50..77f4b6a83eef 100644
--- a/core/java/com/android/internal/widget/remotecompose/player/RemoteComposePlayer.java
+++ b/core/java/com/android/internal/widget/remotecompose/player/RemoteComposePlayer.java
@@ -39,12 +39,13 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.widget.remotecompose.accessibility.RemoteComposeTouchHelper;
import com.android.internal.widget.remotecompose.core.CoreDocument;
import com.android.internal.widget.remotecompose.core.RemoteContext;
+import com.android.internal.widget.remotecompose.core.RemoteContextAware;
import com.android.internal.widget.remotecompose.core.operations.NamedVariable;
import com.android.internal.widget.remotecompose.core.operations.RootContentBehavior;
import com.android.internal.widget.remotecompose.player.platform.RemoteComposeCanvas;
/** A view to to display and play RemoteCompose documents */
-public class RemoteComposePlayer extends FrameLayout {
+public class RemoteComposePlayer extends FrameLayout implements RemoteContextAware {
private RemoteComposeCanvas mInner;
private static final int MAX_SUPPORTED_MAJOR_VERSION = MAJOR_VERSION;
@@ -65,6 +66,11 @@ public class RemoteComposePlayer extends FrameLayout {
init(context, attrs, defStyleAttr);
}
+ @Override
+ public RemoteContext getRemoteContext() {
+ return mInner.getRemoteContext();
+ }
+
/**
* @inheritDoc
*/
diff --git a/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidRemoteContext.java b/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidRemoteContext.java
index 9d385ddafe19..14349b028819 100644
--- a/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidRemoteContext.java
+++ b/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidRemoteContext.java
@@ -185,7 +185,7 @@ public class AndroidRemoteContext extends RemoteContext {
@Override
public void runAction(int id, @NonNull String metadata) {
- mDocument.performClick(id);
+ mDocument.performClick(this, id);
}
@Override
diff --git a/core/java/com/android/internal/widget/remotecompose/player/platform/RemoteComposeCanvas.java b/core/java/com/android/internal/widget/remotecompose/player/platform/RemoteComposeCanvas.java
index 334ba62636ff..f76794fc0372 100644
--- a/core/java/com/android/internal/widget/remotecompose/player/platform/RemoteComposeCanvas.java
+++ b/core/java/com/android/internal/widget/remotecompose/player/platform/RemoteComposeCanvas.java
@@ -147,7 +147,7 @@ public class RemoteComposeCanvas extends FrameLayout implements View.OnAttachSta
param.leftMargin = (int) area.getLeft();
param.topMargin = (int) area.getTop();
viewArea.setOnClickListener(
- view1 -> mDocument.getDocument().performClick(area.getId()));
+ view1 -> mDocument.getDocument().performClick(mARContext, area.getId()));
addView(viewArea, param);
}
if (!clickAreas.isEmpty()) {
@@ -303,6 +303,10 @@ public class RemoteComposeCanvas extends FrameLayout implements View.OnAttachSta
mARContext.setUseChoreographer(value);
}
+ public RemoteContext getRemoteContext() {
+ return mARContext;
+ }
+
public interface ClickCallbacks {
void click(int id, String metadata);
}
diff --git a/core/jni/android_util_Binder.cpp b/core/jni/android_util_Binder.cpp
index 639f5bff7614..91b25c2bda06 100644
--- a/core/jni/android_util_Binder.cpp
+++ b/core/jni/android_util_Binder.cpp
@@ -74,6 +74,7 @@ static struct bindernative_offsets_t
jmethodID mExecTransact;
jmethodID mGetInterfaceDescriptor;
jmethodID mTransactionCallback;
+ jmethodID mGetExtension;
// Object state.
jfieldID mObject;
@@ -489,8 +490,12 @@ public:
if (mVintf) {
::android::internal::Stability::markVintf(b.get());
}
- if (mExtension != nullptr) {
- b.get()->setExtension(mExtension);
+ if (mSetExtensionCalled) {
+ jobject javaIBinderObject = env->CallObjectMethod(obj, gBinderOffsets.mGetExtension);
+ sp<IBinder> extensionFromJava = ibinderForJavaObject(env, javaIBinderObject);
+ if (extensionFromJava != nullptr) {
+ b.get()->setExtension(extensionFromJava);
+ }
}
mBinder = b;
ALOGV("Creating JavaBinder %p (refs %p) for Object %p, weakCount=%" PRId32 "\n",
@@ -516,21 +521,12 @@ public:
mVintf = false;
}
- sp<IBinder> getExtension() {
- AutoMutex _l(mLock);
- sp<JavaBBinder> b = mBinder.promote();
- if (b != nullptr) {
- return b.get()->getExtension();
- }
- return mExtension;
- }
-
void setExtension(const sp<IBinder>& extension) {
AutoMutex _l(mLock);
- mExtension = extension;
+ mSetExtensionCalled = true;
sp<JavaBBinder> b = mBinder.promote();
if (b != nullptr) {
- b.get()->setExtension(mExtension);
+ b.get()->setExtension(extension);
}
}
@@ -542,8 +538,7 @@ private:
// is too much binder state here, we can think about making JavaBBinder an
// sp here (avoid recreating it)
bool mVintf = false;
-
- sp<IBinder> mExtension;
+ bool mSetExtensionCalled = false;
};
// ----------------------------------------------------------------------------
@@ -1249,10 +1244,6 @@ static void android_os_Binder_blockUntilThreadAvailable(JNIEnv* env, jobject cla
return IPCThreadState::self()->blockUntilThreadAvailable();
}
-static jobject android_os_Binder_getExtension(JNIEnv* env, jobject obj) {
- JavaBBinderHolder* jbh = (JavaBBinderHolder*) env->GetLongField(obj, gBinderOffsets.mObject);
- return javaObjectForIBinder(env, jbh->getExtension());
-}
static void android_os_Binder_setExtension(JNIEnv* env, jobject obj, jobject extensionObject) {
JavaBBinderHolder* jbh = (JavaBBinderHolder*) env->GetLongField(obj, gBinderOffsets.mObject);
@@ -1295,8 +1286,7 @@ static const JNINativeMethod gBinderMethods[] = {
{ "getNativeBBinderHolder", "()J", (void*)android_os_Binder_getNativeBBinderHolder },
{ "getNativeFinalizer", "()J", (void*)android_os_Binder_getNativeFinalizer },
{ "blockUntilThreadAvailable", "()V", (void*)android_os_Binder_blockUntilThreadAvailable },
- { "getExtension", "()Landroid/os/IBinder;", (void*)android_os_Binder_getExtension },
- { "setExtension", "(Landroid/os/IBinder;)V", (void*)android_os_Binder_setExtension },
+ { "setExtensionNative", "(Landroid/os/IBinder;)V", (void*)android_os_Binder_setExtension },
};
// clang-format on
@@ -1313,6 +1303,8 @@ static int int_register_android_os_Binder(JNIEnv* env)
gBinderOffsets.mTransactionCallback =
GetStaticMethodIDOrDie(env, clazz, "transactionCallback", "(IIII)V");
gBinderOffsets.mObject = GetFieldIDOrDie(env, clazz, "mObject", "J");
+ gBinderOffsets.mGetExtension = GetMethodIDOrDie(env, clazz, "getExtension",
+ "()Landroid/os/IBinder;");
return RegisterMethodsOrDie(
env, kBinderPathName,
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index b894d3a6888f..586cafdd2b57 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -2009,6 +2009,10 @@
<!-- Component name of the built in wallpaper used to display bitmap wallpapers. This must not be null. -->
<string name="image_wallpaper_component" translatable="false">com.android.systemui/com.android.systemui.wallpapers.ImageWallpaper</string>
+ <!-- Component name of the built in wallpaper that is used when the user-selected wallpaper is
+ incompatible with the display's resolution or aspect ratio. -->
+ <string name="fallback_wallpaper_component" translatable="false">com.android.systemui/com.android.systemui.wallpapers.GradientColorWallpaper</string>
+
<!-- True if WallpaperService is enabled -->
<bool name="config_enableWallpaperService">true</bool>
diff --git a/core/res/res/values/public-final.xml b/core/res/res/values/public-final.xml
index d421944917ea..d8e89318a134 100644
--- a/core/res/res/values/public-final.xml
+++ b/core/res/res/values/public-final.xml
@@ -3922,4 +3922,86 @@
<public type="color" name="system_error_900" id="0x010600d0" />
<public type="color" name="system_error_1000" id="0x010600d1" />
+ <!-- ===============================================================
+ Resources added in version NEXT of the platform
+
+ NOTE: After this version of the platform is forked, changes cannot be made to the root
+ branch's groups for that release. Only merge changes to the forked platform branch.
+ =============================================================== -->
+ <eat-comment/>
+
+ <staging-public-group-final type="attr" first-id="0x01b70000">
+ <public name="removed_" />
+ <!-- @FlaggedApi("android.media.tv.flags.enable_ad_service_fw") -->
+ <public name="adServiceTypes" />
+ <!-- @FlaggedApi("android.view.inputmethod.ime_switcher_revamp_api") -->
+ <public name="languageSettingsActivity"/>
+ <!-- @FlaggedApi(android.service.controls.flags.Flags.FLAG_HOME_PANEL_DREAM) -->
+ <public name="dreamCategory"/>
+ <!-- @FlaggedApi("android.permission.flags.replace_body_sensor_permission_enabled")
+ @hide @SystemApi -->
+ <public name="backgroundPermission"/>
+ <!-- @FlaggedApi(android.view.accessibility.supplemental_description) -->
+ <public name="supplementalDescription"/>
+ <!-- @FlaggedApi("android.security.enable_intent_matching_flags") -->
+ <public name="intentMatchingFlags"/>
+ <!-- @FlaggedApi(android.view.inputmethod.Flags.FLAG_IME_SWITCHER_REVAMP_API) -->
+ <public name="layoutLabel"/>
+ <public name="removed_" />
+ <public name="removed_" />
+ <!-- @FlaggedApi(android.content.pm.Flags.FLAG_APP_COMPAT_OPTION_16KB) -->
+ <public name="pageSizeCompat" />
+ <!-- @FlaggedApi(android.nfc.Flags.FLAG_NFC_ASSOCIATED_ROLE_SERVICES) -->
+ <public name="wantsRoleHolderPriority"/>
+ <!-- @FlaggedApi(android.sdk.Flags.FLAG_MAJOR_MINOR_VERSIONING_SCHEME) -->
+ <public name="minSdkVersionFull"/>
+ <public name="removed_" />
+ <public name="removed_" />
+ <public name="removed_" />
+ <public name="removed_" />
+ </staging-public-group-final>
+
+ <!-- @FlaggedApi("android.media.tv.flags.enable_ad_service_fw") -->
+ <public type="attr" name="adServiceTypes" id="0x010106a4" />
+ <!-- @FlaggedApi("android.view.inputmethod.ime_switcher_revamp_api") -->
+ <public type="attr" name="languageSettingsActivity" id="0x010106a5" />
+ <!-- @FlaggedApi(android.service.controls.flags.Flags.FLAG_HOME_PANEL_DREAM) -->
+ <public type="attr" name="dreamCategory" id="0x010106a6" />
+ <!-- @FlaggedApi("android.permission.flags.replace_body_sensor_permission_enabled")
+ @hide @SystemApi -->
+ <public type="attr" name="backgroundPermission" id="0x010106a7" />
+ <!-- @FlaggedApi(android.view.accessibility.supplemental_description) -->
+ <public type="attr" name="supplementalDescription" id="0x010106a8" />
+ <!-- @FlaggedApi("android.security.enable_intent_matching_flags") -->
+ <public type="attr" name="intentMatchingFlags" id="0x010106a9" />
+ <!-- @FlaggedApi(android.view.inputmethod.Flags.FLAG_IME_SWITCHER_REVAMP_API) -->
+ <public type="attr" name="layoutLabel" id="0x010106aa" />
+ <!-- @FlaggedApi(android.content.pm.Flags.FLAG_APP_COMPAT_OPTION_16KB) -->
+ <public type="attr" name="pageSizeCompat" id="0x010106ab" />
+ <!-- @FlaggedApi(android.nfc.Flags.FLAG_NFC_ASSOCIATED_ROLE_SERVICES) -->
+ <public type="attr" name="wantsRoleHolderPriority" id="0x010106ac" />
+ <!-- @FlaggedApi(android.sdk.Flags.FLAG_MAJOR_MINOR_VERSIONING_SCHEME) -->
+ <public type="attr" name="minSdkVersionFull" id="0x010106ad" />
+
+ <staging-public-group-final type="string" first-id="0x01b40000">
+ <!-- @FlaggedApi(android.content.pm.Flags.FLAG_SDK_DEPENDENCY_INSTALLER)
+ @hide @SystemApi -->
+ <public name="config_systemDependencyInstaller" />
+ <!-- @hide @SystemApi -->
+ <public name="removed_config_defaultReservedForTestingProfileGroupExclusivity" />
+ <!-- @FlaggedApi(android.permission.flags.Flags.FLAG_SYSTEM_VENDOR_INTELLIGENCE_ROLE_ENABLED)
+ @hide @SystemApi -->
+ <public name="config_systemVendorIntelligence" />
+ <public name="removed_" />
+ <public name="removed_" />
+ <public name="removed_" />
+ </staging-public-group-final>
+
+ <!-- @FlaggedApi(android.content.pm.Flags.FLAG_SDK_DEPENDENCY_INSTALLER)
+ @hide @SystemApi -->
+ <public type="string" name="config_systemDependencyInstaller" id="0x0104004a" />
+ <!-- @FlaggedApi(android.permission.flags.Flags.FLAG_SYSTEM_VENDOR_INTELLIGENCE_ROLE_ENABLED)
+ @hide @SystemApi -->
+ <public type="string" name="config_systemVendorIntelligence" id="0x0104004b" />
+
</resources>
diff --git a/core/res/res/values/public-staging.xml b/core/res/res/values/public-staging.xml
index 7baaa6d590f2..2d411d0268b3 100644
--- a/core/res/res/values/public-staging.xml
+++ b/core/res/res/values/public-staging.xml
@@ -109,69 +109,44 @@
=============================================================== -->
<eat-comment/>
- <staging-public-group type="attr" first-id="0x01b70000">
+ <staging-public-group type="attr" first-id="0x01b30000">
<!-- @FlaggedApi("android.content.pm.sdk_lib_independence") -->
<public name="optional"/>
- <!-- @FlaggedApi("android.media.tv.flags.enable_ad_service_fw") -->
- <public name="adServiceTypes" />
- <!-- @FlaggedApi("android.view.inputmethod.ime_switcher_revamp_api") -->
- <public name="languageSettingsActivity"/>
- <!-- @FlaggedApi(android.service.controls.flags.Flags.FLAG_HOME_PANEL_DREAM) -->
- <public name="dreamCategory"/>
- <!-- @FlaggedApi("android.permission.flags.replace_body_sensor_permission_enabled")
- @hide @SystemApi -->
- <public name="backgroundPermission"/>
- <!-- @FlaggedApi(android.view.accessibility.supplemental_description) -->
- <public name="supplementalDescription"/>
- <!-- @FlaggedApi("android.security.enable_intent_matching_flags") -->
- <public name="intentMatchingFlags"/>
- <!-- @FlaggedApi(android.view.inputmethod.Flags.FLAG_IME_SWITCHER_REVAMP_API) -->
- <public name="layoutLabel"/>
<!-- @FlaggedApi(android.content.pm.Flags.FLAG_CHANGE_LAUNCHER_BADGING) -->
<public name="alternateLauncherIcons"/>
<!-- @FlaggedApi(android.content.pm.Flags.FLAG_CHANGE_LAUNCHER_BADGING) -->
<public name="alternateLauncherLabels"/>
- <!-- @FlaggedApi(android.content.pm.Flags.FLAG_APP_COMPAT_OPTION_16KB) -->
- <public name="pageSizeCompat" />
- <!-- @FlaggedApi(android.nfc.Flags.FLAG_NFC_ASSOCIATED_ROLE_SERVICES) -->
- <public name="wantsRoleHolderPriority"/>
- <!-- @FlaggedApi(android.sdk.Flags.FLAG_MAJOR_MINOR_VERSIONING_SCHEME) -->
- <public name="minSdkVersionFull"/>
+ <!-- @hide Only for device overlay to use this. -->
+ <public name="pointerIconVectorFill"/>
+ <!-- @hide Only for device overlay to use this. -->
+ <public name="pointerIconVectorFillInverse"/>
+ <!-- @hide Only for device overlay to use this. -->
+ <public name="pointerIconVectorStroke"/>
+ <!-- @hide Only for device overlay to use this. -->
+ <public name="pointerIconVectorStrokeInverse"/>
</staging-public-group>
- <staging-public-group type="id" first-id="0x01b60000">
+ <staging-public-group type="id" first-id="0x01b20000">
<!-- @FlaggedApi(android.appwidget.flags.Flags.FLAG_ENGAGEMENT_METRICS) -->
<public name="remoteViewsMetricsId"/>
</staging-public-group>
- <staging-public-group type="style" first-id="0x01b50000">
+ <staging-public-group type="style" first-id="0x01b10000">
</staging-public-group>
- <staging-public-group type="string" first-id="0x01b40000">
- <!-- @FlaggedApi(android.content.pm.Flags.FLAG_SDK_DEPENDENCY_INSTALLER)
- @hide @SystemApi -->
- <public name="config_systemDependencyInstaller" />
- <!-- @hide @SystemApi -->
- <public name="removed_config_defaultReservedForTestingProfileGroupExclusivity" />
- <!-- @FlaggedApi(android.permission.flags.Flags.FLAG_SYSTEM_VENDOR_INTELLIGENCE_ROLE_ENABLED)
- @hide @SystemApi -->
- <public name="config_systemVendorIntelligence" />
-
+ <staging-public-group type="string" first-id="0x01b00000">
<!-- @FlaggedApi(android.app.ondeviceintelligence.flags.Flags.FLAG_ENABLE_ON_DEVICE_INTELLIGENCE_MODULE)
@hide @SystemApi -->
<public name="config_defaultOnDeviceIntelligenceService" />
-
<!-- @FlaggedApi(android.app.ondeviceintelligence.flags.Flags.FLAG_ENABLE_ON_DEVICE_INTELLIGENCE_MODULE)
@hide @SystemApi -->
<public name="config_defaultOnDeviceSandboxedInferenceService" />
-
<!-- @FlaggedApi(android.app.ondeviceintelligence.flags.Flags.FLAG_ENABLE_ON_DEVICE_INTELLIGENCE_MODULE)
@hide @SystemApi -->
<public name="config_defaultOnDeviceIntelligenceDeviceConfigNamespace" />
-
</staging-public-group>
- <staging-public-group type="dimen" first-id="0x01b30000">
+ <staging-public-group type="dimen" first-id="0x01af0000">
<!-- @FlaggedApi(android.os.Flags.FLAG_MATERIAL_MOTION_TOKENS)-->
<public name="config_motionStandardFastSpatialDamping"/>
<!-- @FlaggedApi(android.os.Flags.FLAG_MATERIAL_MOTION_TOKENS)-->
@@ -208,7 +183,7 @@
<public name="config_shapeCornerRadiusXlarge"/>
</staging-public-group>
- <staging-public-group type="color" first-id="0x01b20000">
+ <staging-public-group type="color" first-id="0x01ae0000">
<!-- @FlaggedApi(android.os.Flags.FLAG_MATERIAL_COLORS_10_2024)-->
<public name="system_inverse_on_surface_light"/>
<!-- @FlaggedApi(android.os.Flags.FLAG_MATERIAL_COLORS_10_2024)-->
@@ -235,28 +210,28 @@
<public name="system_surface_tint_dark"/>
</staging-public-group>
- <staging-public-group type="array" first-id="0x01b10000">
+ <staging-public-group type="array" first-id="0x01ad0000">
</staging-public-group>
- <staging-public-group type="drawable" first-id="0x01b00000">
+ <staging-public-group type="drawable" first-id="0x01ac0000">
</staging-public-group>
- <staging-public-group type="layout" first-id="0x01af0000">
+ <staging-public-group type="layout" first-id="0x01ab0000">
</staging-public-group>
- <staging-public-group type="anim" first-id="0x01ae0000">
+ <staging-public-group type="anim" first-id="0x01aa0000">
</staging-public-group>
- <staging-public-group type="animator" first-id="0x01ad0000">
+ <staging-public-group type="animator" first-id="0x01a90000">
</staging-public-group>
- <staging-public-group type="interpolator" first-id="0x01ac0000">
+ <staging-public-group type="interpolator" first-id="0x01a80000">
</staging-public-group>
- <staging-public-group type="mipmap" first-id="0x01ab0000">
+ <staging-public-group type="mipmap" first-id="0x01a70000">
</staging-public-group>
- <staging-public-group type="integer" first-id="0x01aa0000">
+ <staging-public-group type="integer" first-id="0x01a60000">
<!-- @FlaggedApi(android.os.Flags.FLAG_MATERIAL_MOTION_TOKENS)-->
<public name="config_motionStandardFastSpatialStiffness"/>
<!-- @FlaggedApi(android.os.Flags.FLAG_MATERIAL_MOTION_TOKENS)-->
@@ -283,16 +258,16 @@
<public name="config_motionExpressiveSlowEffectStiffness"/>
</staging-public-group>
- <staging-public-group type="transition" first-id="0x01a90000">
+ <staging-public-group type="transition" first-id="0x01a50000">
</staging-public-group>
- <staging-public-group type="raw" first-id="0x01a80000">
+ <staging-public-group type="raw" first-id="0x01a40000">
</staging-public-group>
- <staging-public-group type="bool" first-id="0x01a70000">
+ <staging-public-group type="bool" first-id="0x01a30000">
</staging-public-group>
- <staging-public-group type="fraction" first-id="0x01a60000">
+ <staging-public-group type="fraction" first-id="0x01a20000">
</staging-public-group>
</resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 8315e3cf1a11..772a7413a4a7 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2270,6 +2270,7 @@
<java-symbol type="string" name="heavy_weight_notification" />
<java-symbol type="string" name="heavy_weight_notification_detail" />
<java-symbol type="string" name="image_wallpaper_component" />
+ <java-symbol type="string" name="fallback_wallpaper_component" />
<java-symbol type="string" name="input_method_binding_label" />
<java-symbol type="string" name="input_method_ime_switch_long_click_action_desc" />
<java-symbol type="string" name="launch_warning_original" />
diff --git a/core/tests/FileSystemUtilsTest/Android.bp b/core/tests/FileSystemUtilsTest/Android.bp
index 962ff3c0a6e0..f4d92522bb25 100644
--- a/core/tests/FileSystemUtilsTest/Android.bp
+++ b/core/tests/FileSystemUtilsTest/Android.bp
@@ -36,10 +36,10 @@ cc_library {
ldflags: ["-z max-page-size=0x1000"],
}
-android_test_helper_app {
- name: "app_with_4kb_elf",
+java_defaults {
+ name: "app_with_4kb_elf_defaults",
srcs: ["app_with_4kb_elf/src/**/*.java"],
- manifest: "app_with_4kb_elf/app_with_4kb_elf.xml",
+ resource_dirs: ["app_with_4kb_elf/res"],
compile_multilib: "64",
jni_libs: [
"libpunchtest_4kb",
@@ -47,7 +47,36 @@ android_test_helper_app {
static_libs: [
"androidx.test.rules",
"platform-test-annotations",
+ "androidx.test.uiautomator_uiautomator",
+ "sysui-helper",
],
+}
+
+android_test_helper_app {
+ name: "app_with_4kb_elf",
+ defaults: ["app_with_4kb_elf_defaults"],
+ manifest: "app_with_4kb_elf/app_with_4kb_elf.xml",
+ use_embedded_native_libs: true,
+}
+
+android_test_helper_app {
+ name: "app_with_4kb_compressed_elf",
+ defaults: ["app_with_4kb_elf_defaults"],
+ manifest: "app_with_4kb_elf/app_with_4kb_elf.xml",
+ use_embedded_native_libs: false,
+}
+
+android_test_helper_app {
+ name: "page_size_compat_disabled_app",
+ defaults: ["app_with_4kb_elf_defaults"],
+ manifest: "app_with_4kb_elf/page_size_compat_disabled.xml",
+ use_embedded_native_libs: true,
+}
+
+android_test_helper_app {
+ name: "app_with_4kb_elf_no_override",
+ defaults: ["app_with_4kb_elf_defaults"],
+ manifest: "app_with_4kb_elf/app_with_4kb_no_override.xml",
use_embedded_native_libs: true,
}
@@ -99,6 +128,9 @@ java_test_host {
":embedded_native_libs_test_app",
":extract_native_libs_test_app",
":app_with_4kb_elf",
+ ":page_size_compat_disabled_app",
+ ":app_with_4kb_compressed_elf",
+ ":app_with_4kb_elf_no_override",
],
test_suites: ["general-tests"],
test_config: "AndroidTest.xml",
diff --git a/core/tests/FileSystemUtilsTest/AndroidTest.xml b/core/tests/FileSystemUtilsTest/AndroidTest.xml
index 651a7ca15dac..27f49b2289ba 100644
--- a/core/tests/FileSystemUtilsTest/AndroidTest.xml
+++ b/core/tests/FileSystemUtilsTest/AndroidTest.xml
@@ -22,7 +22,6 @@
<option name="cleanup-apks" value="true" />
<option name="test-file-name" value="embedded_native_libs_test_app.apk" />
<option name="test-file-name" value="extract_native_libs_test_app.apk" />
- <option name="test-file-name" value="app_with_4kb_elf.apk" />
</target_preparer>
<test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
diff --git a/core/tests/FileSystemUtilsTest/app_with_4kb_elf/app_with_4kb_elf.xml b/core/tests/FileSystemUtilsTest/app_with_4kb_elf/app_with_4kb_elf.xml
index b9d6d4db2c81..d7a37336cbc3 100644
--- a/core/tests/FileSystemUtilsTest/app_with_4kb_elf/app_with_4kb_elf.xml
+++ b/core/tests/FileSystemUtilsTest/app_with_4kb_elf/app_with_4kb_elf.xml
@@ -18,7 +18,6 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="android.test.pagesizecompat">
<application
- android:extractNativeLibs="false"
android:pageSizeCompat="enabled">
<uses-library android:name="android.test.runner"/>
<activity android:name=".MainActivity"
diff --git a/core/tests/FileSystemUtilsTest/app_with_4kb_elf/app_with_4kb_no_override.xml b/core/tests/FileSystemUtilsTest/app_with_4kb_elf/app_with_4kb_no_override.xml
new file mode 100644
index 000000000000..b0b5204d6e80
--- /dev/null
+++ b/core/tests/FileSystemUtilsTest/app_with_4kb_elf/app_with_4kb_no_override.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2025 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.test.pagesizecompat">
+ <application
+ android:label="PageSizeCompatTestApp">
+ <uses-library android:name="android.test.runner"/>
+ <activity android:name=".MainActivity"
+ android:exported="true"
+ android:label="Home page"
+ android:process=":NewProcess">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.LAUNCHER"/>
+ <category android:name="android.intent.category.DEFAULT"/>
+ </intent-filter>
+ </activity>
+ </application>
+ <instrumentation
+ android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="android.test.pagesizecompat"/>
+</manifest> \ No newline at end of file
diff --git a/core/tests/FileSystemUtilsTest/app_with_4kb_elf/page_size_compat_disabled.xml b/core/tests/FileSystemUtilsTest/app_with_4kb_elf/page_size_compat_disabled.xml
new file mode 100644
index 000000000000..641c5e741014
--- /dev/null
+++ b/core/tests/FileSystemUtilsTest/app_with_4kb_elf/page_size_compat_disabled.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2025 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.test.pagesizecompat">
+ <application
+ android:pageSizeCompat="disabled">
+ <uses-library android:name="android.test.runner"/>
+ <activity android:name=".MainActivity"
+ android:exported="true"
+ android:process=":NewProcess">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.LAUNCHER"/>
+ <category android:name="android.intent.category.DEFAULT"/>
+ </intent-filter>
+ </activity>
+ </application>
+ <instrumentation
+ android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="android.test.pagesizecompat"/>
+</manifest> \ No newline at end of file
diff --git a/core/tests/FileSystemUtilsTest/app_with_4kb_elf/res/layout/hello.xml b/core/tests/FileSystemUtilsTest/app_with_4kb_elf/res/layout/hello.xml
new file mode 100644
index 000000000000..473f3f9f9402
--- /dev/null
+++ b/core/tests/FileSystemUtilsTest/app_with_4kb_elf/res/layout/hello.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2025 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:keepScreenOn="true">
+ <TextView
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:text="this is a test activity"
+ />
+</LinearLayout>
+
diff --git a/core/tests/FileSystemUtilsTest/app_with_4kb_elf/src/android/test/pagesizecompat/MainActivity.java b/core/tests/FileSystemUtilsTest/app_with_4kb_elf/src/android/test/pagesizecompat/MainActivity.java
index 893f9cd01497..5d8d8081b0e5 100644
--- a/core/tests/FileSystemUtilsTest/app_with_4kb_elf/src/android/test/pagesizecompat/MainActivity.java
+++ b/core/tests/FileSystemUtilsTest/app_with_4kb_elf/src/android/test/pagesizecompat/MainActivity.java
@@ -19,6 +19,7 @@ package android.test.pagesizecompat;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
+import android.view.View;
import androidx.annotation.VisibleForTesting;
@@ -43,6 +44,8 @@ public class MainActivity extends Activity {
@Override
public void onCreate(Bundle savedOnstanceState) {
super.onCreate(savedOnstanceState);
+ View view = getLayoutInflater().inflate(R.layout.hello, null);
+ setContentView(view);
Intent received = getIntent();
int op1 = received.getIntExtra(KEY_OPERAND_1, -1);
diff --git a/core/tests/FileSystemUtilsTest/app_with_4kb_elf/src/android/test/pagesizecompat/PageSizeCompatTest.java b/core/tests/FileSystemUtilsTest/app_with_4kb_elf/src/android/test/pagesizecompat/PageSizeCompatTest.java
index 9cbe414a0993..7d05c64f7624 100644
--- a/core/tests/FileSystemUtilsTest/app_with_4kb_elf/src/android/test/pagesizecompat/PageSizeCompatTest.java
+++ b/core/tests/FileSystemUtilsTest/app_with_4kb_elf/src/android/test/pagesizecompat/PageSizeCompatTest.java
@@ -16,6 +16,8 @@
package android.test.pagesizecompat;
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -23,6 +25,10 @@ import android.content.IntentFilter;
import androidx.test.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
+import androidx.test.uiautomator.By;
+import androidx.test.uiautomator.UiDevice;
+import androidx.test.uiautomator.UiObject2;
+import androidx.test.uiautomator.Until;
import org.junit.Assert;
import org.junit.Test;
@@ -33,9 +39,10 @@ import java.util.concurrent.TimeUnit;
@RunWith(AndroidJUnit4.class)
public class PageSizeCompatTest {
+ private static final String WARNING_TEXT = "PageSizeCompatTestApp";
+ private static final long TIMEOUT = 5000;
- @Test
- public void testPageSizeCompat_embedded4KbLib() throws Exception {
+ public void testPageSizeCompat_appLaunch(boolean shouldPass) throws Exception {
Context context = InstrumentationRegistry.getContext();
CountDownLatch receivedSignal = new CountDownLatch(1);
@@ -62,6 +69,30 @@ public class PageSizeCompatTest {
launchIntent.putExtra(MainActivity.KEY_OPERAND_2, op2);
context.startActivity(launchIntent);
- Assert.assertTrue("Failed to launch app", receivedSignal.await(10, TimeUnit.SECONDS));
+ UiDevice device = UiDevice.getInstance(getInstrumentation());
+ device.waitForWindowUpdate(null, TIMEOUT);
+
+ Assert.assertEquals(receivedSignal.await(10, TimeUnit.SECONDS), shouldPass);
+ }
+
+ @Test
+ public void testPageSizeCompat_compatEnabled() throws Exception {
+ testPageSizeCompat_appLaunch(true);
+ }
+
+ @Test
+ public void testPageSizeCompat_compatDisabled() throws Exception {
+ testPageSizeCompat_appLaunch(false);
+ }
+
+ @Test
+ public void testPageSizeCompat_compatByAlignmentChecks() throws Exception {
+ testPageSizeCompat_appLaunch(true);
+
+ //verify warning dialog
+ UiDevice device = UiDevice.getInstance(getInstrumentation());
+ device.waitForWindowUpdate(null, TIMEOUT);
+ UiObject2 targetObject = device.wait(Until.findObject(By.text(WARNING_TEXT)), TIMEOUT);
+ Assert.assertTrue(targetObject != null);
}
}
diff --git a/core/tests/FileSystemUtilsTest/src/com/android/internal/content/FileSystemUtilsTest.java b/core/tests/FileSystemUtilsTest/src/com/android/internal/content/FileSystemUtilsTest.java
index aed907a0242f..208d74e49afe 100644
--- a/core/tests/FileSystemUtilsTest/src/com/android/internal/content/FileSystemUtilsTest.java
+++ b/core/tests/FileSystemUtilsTest/src/com/android/internal/content/FileSystemUtilsTest.java
@@ -17,10 +17,12 @@
package com.android.internal.content;
import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
import android.platform.test.annotations.AppModeFull;
import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.targetprep.TargetSetupError;
import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
@@ -29,6 +31,12 @@ import org.junit.runner.RunWith;
@RunWith(DeviceJUnit4ClassRunner.class)
public class FileSystemUtilsTest extends BaseHostJUnit4Test {
+ private static final String PAGE_SIZE_COMPAT_ENABLED = "app_with_4kb_elf.apk";
+ private static final String PAGE_SIZE_COMPAT_DISABLED = "page_size_compat_disabled_app.apk";
+ private static final String PAGE_SIZE_COMPAT_ENABLED_COMPRESSED_ELF =
+ "app_with_4kb_compressed_elf.apk";
+ private static final String PAGE_SIZE_COMPAT_ENABLED_BY_PLATFORM =
+ "app_with_4kb_elf_no_override.apk";
@Test
@AppModeFull
@@ -48,12 +56,50 @@ public class FileSystemUtilsTest extends BaseHostJUnit4Test {
runDeviceTests(appPackage, appPackage + "." + testName);
}
- @Test
- @AppModeFull
- public void runAppWith4KbLib_overrideCompatMode() throws DeviceNotAvailableException {
+ private void runPageSizeCompatTest(String appName, String testMethodName)
+ throws DeviceNotAvailableException, TargetSetupError {
+ getDevice().enableAdbRoot();
+ String result = getDevice().executeShellCommand("getconf PAGE_SIZE");
+ assumeTrue("16384".equals(result.strip()));
+ installPackage(appName, "-r");
String appPackage = "android.test.pagesizecompat";
String testName = "PageSizeCompatTest";
assertTrue(isPackageInstalled(appPackage));
- runDeviceTests(appPackage, appPackage + "." + testName);
+ assertTrue(runDeviceTests(appPackage, appPackage + "." + testName,
+ testMethodName));
+ uninstallPackage(appPackage);
+ }
+
+ @Test
+ @AppModeFull
+ public void runAppWith4KbLib_overrideCompatMode()
+ throws DeviceNotAvailableException, TargetSetupError {
+ runPageSizeCompatTest(PAGE_SIZE_COMPAT_ENABLED, "testPageSizeCompat_compatEnabled");
+ }
+
+ @Test
+ @AppModeFull
+ public void runAppWith4KbCompressedLib_overrideCompatMode()
+ throws DeviceNotAvailableException, TargetSetupError {
+ runPageSizeCompatTest(PAGE_SIZE_COMPAT_ENABLED_COMPRESSED_ELF,
+ "testPageSizeCompat_compatEnabled");
+ }
+
+ @Test
+ @AppModeFull
+ public void runAppWith4KbLib_disabledCompatMode()
+ throws DeviceNotAvailableException, TargetSetupError {
+ // This test is expected to fail since compat is disabled in manifest
+ runPageSizeCompatTest(PAGE_SIZE_COMPAT_DISABLED,
+ "testPageSizeCompat_compatDisabled");
+ }
+
+ @Test
+ @AppModeFull
+ public void runAppWith4KbLib_compatByAlignmentChecks()
+ throws DeviceNotAvailableException, TargetSetupError {
+ // This test is expected to fail since compat is disabled in manifest
+ runPageSizeCompatTest(PAGE_SIZE_COMPAT_ENABLED_BY_PLATFORM,
+ "testPageSizeCompat_compatByAlignmentChecks");
}
}
diff --git a/core/tests/coretests/src/android/app/NotificationManagerTest.java b/core/tests/coretests/src/android/app/NotificationManagerTest.java
index 41432294b3c2..9b97c8feaf12 100644
--- a/core/tests/coretests/src/android/app/NotificationManagerTest.java
+++ b/core/tests/coretests/src/android/app/NotificationManagerTest.java
@@ -19,7 +19,6 @@ package android.app;
import static com.google.common.truth.Truth.assertThat;
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.atLeast;
@@ -304,9 +303,8 @@ public class NotificationManagerTest {
// It doesn't matter what the returned contents are, as long as we return a channel.
// This setup must set up getNotificationChannels(), as that's the method called.
- when(mNotificationManager.mBackendService.getOrCreateNotificationChannels(any(), any(),
- anyInt(), anyBoolean())).thenReturn(
- new ParceledListSlice<>(List.of(exampleChannel())));
+ when(mNotificationManager.mBackendService.getNotificationChannels(any(), any(),
+ anyInt())).thenReturn(new ParceledListSlice<>(List.of(exampleChannel())));
// ask for the same channel 100 times without invalidating the cache
for (int i = 0; i < 100; i++) {
@@ -318,7 +316,7 @@ public class NotificationManagerTest {
NotificationChannel unused = mNotificationManager.getNotificationChannel("id");
verify(mNotificationManager.mBackendService, times(2))
- .getOrCreateNotificationChannels(any(), any(), anyInt(), anyBoolean());
+ .getNotificationChannels(any(), any(), anyInt());
}
@Test
@@ -331,24 +329,23 @@ public class NotificationManagerTest {
NotificationChannel c2 = new NotificationChannel("id2", "name2",
NotificationManager.IMPORTANCE_NONE);
- when(mNotificationManager.mBackendService.getOrCreateNotificationChannels(any(), any(),
- anyInt(), anyBoolean())).thenReturn(new ParceledListSlice<>(List.of(c1, c2)));
+ when(mNotificationManager.mBackendService.getNotificationChannels(any(), any(),
+ anyInt())).thenReturn(new ParceledListSlice<>(List.of(c1, c2)));
assertThat(mNotificationManager.getNotificationChannel("id1")).isEqualTo(c1);
assertThat(mNotificationManager.getNotificationChannel("id2")).isEqualTo(c2);
assertThat(mNotificationManager.getNotificationChannel("id3")).isNull();
verify(mNotificationManager.mBackendService, times(1))
- .getOrCreateNotificationChannels(any(), any(), anyInt(), anyBoolean());
+ .getNotificationChannels(any(), any(), anyInt());
}
@Test
@EnableFlags(Flags.FLAG_NM_BINDER_PERF_CACHE_CHANNELS)
public void getNotificationChannels_cachedUntilInvalidated() throws Exception {
NotificationManager.invalidateNotificationChannelCache();
- when(mNotificationManager.mBackendService.getOrCreateNotificationChannels(any(), any(),
- anyInt(), anyBoolean())).thenReturn(
- new ParceledListSlice<>(List.of(exampleChannel())));
+ when(mNotificationManager.mBackendService.getNotificationChannels(any(), any(),
+ anyInt())).thenReturn(new ParceledListSlice<>(List.of(exampleChannel())));
// ask for channels 100 times without invalidating the cache
for (int i = 0; i < 100; i++) {
@@ -360,7 +357,7 @@ public class NotificationManagerTest {
List<NotificationChannel> res = mNotificationManager.getNotificationChannels();
verify(mNotificationManager.mBackendService, times(2))
- .getOrCreateNotificationChannels(any(), any(), anyInt(), anyBoolean());
+ .getNotificationChannels(any(), any(), anyInt());
assertThat(res).containsExactlyElementsIn(List.of(exampleChannel()));
}
@@ -378,9 +375,8 @@ public class NotificationManagerTest {
NotificationChannel c2 = new NotificationChannel("other", "name2",
NotificationManager.IMPORTANCE_DEFAULT);
- when(mNotificationManager.mBackendService.getOrCreateNotificationChannels(any(), any(),
- anyInt(), anyBoolean())).thenReturn(
- new ParceledListSlice<>(List.of(c1, conv1, c2)));
+ when(mNotificationManager.mBackendService.getNotificationChannels(any(), any(),
+ anyInt())).thenReturn(new ParceledListSlice<>(List.of(c1, conv1, c2)));
// Lookup for channel c1 and c2: returned as expected
assertThat(mNotificationManager.getNotificationChannel("id")).isEqualTo(c1);
@@ -397,9 +393,9 @@ public class NotificationManagerTest {
// Lookup of a nonexistent channel is null
assertThat(mNotificationManager.getNotificationChannel("id3")).isNull();
- // All of that should have been one call to getOrCreateNotificationChannels()
+ // All of that should have been one call to getNotificationChannels()
verify(mNotificationManager.mBackendService, times(1))
- .getOrCreateNotificationChannels(any(), any(), anyInt(), anyBoolean());
+ .getNotificationChannels(any(), any(), anyInt());
}
@Test
@@ -419,12 +415,12 @@ public class NotificationManagerTest {
NotificationChannel channel3 = channel1.copy();
channel3.setName("name3");
- when(mNotificationManager.mBackendService.getOrCreateNotificationChannels(any(), eq(pkg1),
- eq(userId), anyBoolean())).thenReturn(new ParceledListSlice<>(List.of(channel1)));
- when(mNotificationManager.mBackendService.getOrCreateNotificationChannels(any(), eq(pkg2),
- eq(userId), anyBoolean())).thenReturn(new ParceledListSlice<>(List.of(channel2)));
- when(mNotificationManager.mBackendService.getOrCreateNotificationChannels(any(), eq(pkg1),
- eq(userId1), anyBoolean())).thenReturn(new ParceledListSlice<>(List.of(channel3)));
+ when(mNotificationManager.mBackendService.getNotificationChannels(any(), eq(pkg1),
+ eq(userId))).thenReturn(new ParceledListSlice<>(List.of(channel1)));
+ when(mNotificationManager.mBackendService.getNotificationChannels(any(), eq(pkg2),
+ eq(userId))).thenReturn(new ParceledListSlice<>(List.of(channel2)));
+ when(mNotificationManager.mBackendService.getNotificationChannels(any(), eq(pkg1),
+ eq(userId1))).thenReturn(new ParceledListSlice<>(List.of(channel3)));
// set our context to pretend to be from package 1 and userId 0
mContext.setParameters(pkg1, pkg1, userId);
@@ -440,7 +436,7 @@ public class NotificationManagerTest {
// Those should have been three different calls
verify(mNotificationManager.mBackendService, times(3))
- .getOrCreateNotificationChannels(any(), any(), anyInt(), anyBoolean());
+ .getNotificationChannels(any(), any(), anyInt());
}
@Test
diff --git a/core/tests/coretests/src/android/service/notification/NotificationRankingUpdateTest.java b/core/tests/coretests/src/android/service/notification/NotificationRankingUpdateTest.java
index 1bdb006c3465..9f1580cb8b57 100644
--- a/core/tests/coretests/src/android/service/notification/NotificationRankingUpdateTest.java
+++ b/core/tests/coretests/src/android/service/notification/NotificationRankingUpdateTest.java
@@ -124,7 +124,8 @@ public class NotificationRankingUpdateTest {
getRankingAdjustment(i),
isBubble(i),
getProposedImportance(i),
- hasSensitiveContent(i)
+ hasSensitiveContent(i),
+ getSummarization(i)
);
rankings[i] = ranking;
}
@@ -334,6 +335,17 @@ public class NotificationRankingUpdateTest {
}
/**
+ * Produces a String that can be used to represent getSummarization, based on the provided
+ * index.
+ */
+ public static String getSummarization(int index) {
+ if ((android.app.Flags.nmSummarizationUi() || android.app.Flags.nmSummarization())) {
+ return "summary " + index;
+ }
+ return null;
+ }
+
+ /**
* Produces a boolean that can be used to represent isBubble, based on the provided index.
*/
public static boolean isBubble(int index) {
@@ -461,7 +473,8 @@ public class NotificationRankingUpdateTest {
/* rankingAdjustment= */ 0,
/* isBubble= */ false,
/* proposedImportance= */ 0,
- /* sensitiveContent= */ false
+ /* sensitiveContent= */ false,
+ /* summarization = */ null
);
return ranking;
}
@@ -550,7 +563,8 @@ public class NotificationRankingUpdateTest {
tweak.getRankingAdjustment(),
tweak.isBubble(),
tweak.getProposedImportance(),
- tweak.hasSensitiveContent()
+ tweak.hasSensitiveContent(),
+ tweak.getSummarization()
);
assertNotEquals(nru, nru2);
}
diff --git a/core/tests/coretests/src/android/window/DesktopExperienceFlagsTest.java b/core/tests/coretests/src/android/window/DesktopExperienceFlagsTest.java
new file mode 100644
index 000000000000..cc06f3d21332
--- /dev/null
+++ b/core/tests/coretests/src/android/window/DesktopExperienceFlagsTest.java
@@ -0,0 +1,164 @@
+/*
+ * 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.window;
+
+import static com.android.window.flags.Flags.FLAG_SHOW_DESKTOP_EXPERIENCE_DEV_OPTION;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assume.assumeTrue;
+import static org.junit.Assume.assumeFalse;
+
+import android.content.Context;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.annotations.Presubmit;
+import android.platform.test.flag.junit.FlagsParameterization;
+import android.platform.test.flag.junit.SetFlagsRule;
+import android.support.test.uiautomator.UiDevice;
+import android.window.DesktopExperienceFlags.DesktopExperienceFlag;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.window.flags.Flags;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4;
+import platform.test.runner.parameterized.Parameters;
+
+import java.lang.reflect.Field;
+import java.util.List;
+
+/**
+ * Test class for {@link android.window.DesktopExperienceFlags}
+ *
+ * <p>Build/Install/Run: atest FrameworksCoreTests:DesktopExperienceFlagsTest
+ */
+@SmallTest
+@Presubmit
+@RunWith(ParameterizedAndroidJunit4.class)
+public class DesktopExperienceFlagsTest {
+
+ @Parameters(name = "{0}")
+ public static List<FlagsParameterization> getParams() {
+ return FlagsParameterization.allCombinationsOf(FLAG_SHOW_DESKTOP_EXPERIENCE_DEV_OPTION);
+ }
+
+ @Rule public SetFlagsRule mSetFlagsRule;
+
+ private UiDevice mUiDevice;
+ private Context mContext;
+ private boolean mLocalFlagValue = false;
+ private final DesktopExperienceFlag mOverriddenLocalFlag =
+ new DesktopExperienceFlag(() -> mLocalFlagValue, true);
+ private final DesktopExperienceFlag mNotOverriddenLocalFlag =
+ new DesktopExperienceFlag(() -> mLocalFlagValue, false);
+
+ private static final String OVERRIDE_OFF_SETTING = "0";
+ private static final String OVERRIDE_ON_SETTING = "1";
+ private static final String OVERRIDE_INVALID_SETTING = "garbage";
+
+ public DesktopExperienceFlagsTest(FlagsParameterization flags) {
+ mSetFlagsRule = new SetFlagsRule(flags);
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
+ mUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+ setSysProp(null);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ resetCache();
+ setSysProp(null);
+ }
+
+ @Test
+ public void isTrue_overrideOff_featureFlagOn_returnsTrue() throws Exception {
+ mLocalFlagValue = true;
+ setSysProp(OVERRIDE_OFF_SETTING);
+
+ assertThat(mOverriddenLocalFlag.isTrue()).isTrue();
+ assertThat(mNotOverriddenLocalFlag.isTrue()).isTrue();
+ }
+
+ @Test
+ public void isTrue_overrideOn_featureFlagOn_returnsTrue() throws Exception {
+ mLocalFlagValue = true;
+ setSysProp(OVERRIDE_ON_SETTING);
+
+ assertThat(mOverriddenLocalFlag.isTrue()).isTrue();
+ assertThat(mNotOverriddenLocalFlag.isTrue()).isTrue();
+ }
+
+ @Test
+ public void isTrue_overrideOff_featureFlagOff_returnsFalse() throws Exception {
+ mLocalFlagValue = false;
+ setSysProp(OVERRIDE_OFF_SETTING);
+
+ assertThat(mOverriddenLocalFlag.isTrue()).isFalse();
+ assertThat(mNotOverriddenLocalFlag.isTrue()).isFalse();
+ }
+
+ @Test
+ public void isTrue_devOptionEnabled_overrideOn_featureFlagOff() throws Exception {
+ assumeTrue(Flags.showDesktopExperienceDevOption());
+ mLocalFlagValue = false;
+ setSysProp(OVERRIDE_ON_SETTING);
+
+ assertThat(mOverriddenLocalFlag.isTrue()).isTrue();
+ assertThat(mNotOverriddenLocalFlag.isTrue()).isFalse();
+ }
+
+ @Test
+ public void isTrue_devOptionDisabled_overrideOn_featureFlagOff_returnsFalse() throws Exception {
+ assumeFalse(Flags.showDesktopExperienceDevOption());
+ mLocalFlagValue = false;
+ setSysProp(OVERRIDE_ON_SETTING);
+
+ assertThat(mOverriddenLocalFlag.isTrue()).isFalse();
+ assertThat(mNotOverriddenLocalFlag.isTrue()).isFalse();
+ }
+
+ private void setSysProp(String value) throws Exception {
+ if (value == null) {
+ resetSysProp();
+ } else {
+ mUiDevice.executeShellCommand(
+ "setprop " + DesktopModeFlags.SYSTEM_PROPERTY_NAME + " " + value);
+ }
+ }
+
+ private void resetSysProp() throws Exception {
+ mUiDevice.executeShellCommand("setprop " + DesktopModeFlags.SYSTEM_PROPERTY_NAME + " ''");
+ }
+
+ private void resetCache() throws Exception {
+ Field cachedToggleOverride =
+ DesktopExperienceFlags.class.getDeclaredField("sCachedToggleOverride");
+ cachedToggleOverride.setAccessible(true);
+ cachedToggleOverride.set(null, null);
+ }
+}
diff --git a/core/tests/coretests/src/android/window/DesktopModeFlagsTest.java b/core/tests/coretests/src/android/window/DesktopModeFlagsTest.java
index b28e2b04b342..49927be65ae5 100644
--- a/core/tests/coretests/src/android/window/DesktopModeFlagsTest.java
+++ b/core/tests/coretests/src/android/window/DesktopModeFlagsTest.java
@@ -21,33 +21,42 @@ import static android.window.DesktopModeFlags.ToggleOverride.OVERRIDE_OFF;
import static android.window.DesktopModeFlags.ToggleOverride.OVERRIDE_ON;
import static android.window.DesktopModeFlags.ToggleOverride.OVERRIDE_UNSET;
import static android.window.DesktopModeFlags.ToggleOverride.fromSetting;
-import static android.window.DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_TRANSITIONS;
import static com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE;
-import static com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_TRANSITIONS;
+import static com.android.window.flags.Flags.FLAG_SHOW_DESKTOP_EXPERIENCE_DEV_OPTION;
import static com.android.window.flags.Flags.FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION;
import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assume.assumeTrue;
+
import android.content.ContentResolver;
import android.content.Context;
import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.annotations.Presubmit;
+import android.platform.test.flag.junit.FlagsParameterization;
import android.platform.test.flag.junit.SetFlagsRule;
import android.provider.Settings;
+import android.support.test.uiautomator.UiDevice;
+import android.window.DesktopModeFlags.DesktopModeFlag;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import androidx.test.platform.app.InstrumentationRegistry;
+import com.android.window.flags.Flags;
+
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4;
+import platform.test.runner.parameterized.Parameters;
+
import java.lang.reflect.Field;
+import java.util.List;
/**
* Test class for {@link android.window.DesktopModeFlags}
@@ -57,21 +66,39 @@ import java.lang.reflect.Field;
*/
@SmallTest
@Presubmit
-@RunWith(AndroidJUnit4.class)
+@RunWith(ParameterizedAndroidJunit4.class)
public class DesktopModeFlagsTest {
+ @Parameters(name = "{0}")
+ public static List<FlagsParameterization> getParams() {
+ return FlagsParameterization.allCombinationsOf(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION,
+ FLAG_SHOW_DESKTOP_EXPERIENCE_DEV_OPTION);
+ }
+
@Rule
- public SetFlagsRule setFlagsRule = new SetFlagsRule();
+ public SetFlagsRule mSetFlagsRule;
+ private UiDevice mUiDevice;
private Context mContext;
+ private boolean mLocalFlagValue = false;
+ private final DesktopModeFlag mOverriddenLocalFlag = new DesktopModeFlag(
+ () -> mLocalFlagValue, true);
+ private final DesktopModeFlag mNotOverriddenLocalFlag = new DesktopModeFlag(
+ () -> mLocalFlagValue, false);
private static final int OVERRIDE_OFF_SETTING = 0;
private static final int OVERRIDE_ON_SETTING = 1;
private static final int OVERRIDE_UNSET_SETTING = -1;
+ public DesktopModeFlagsTest(FlagsParameterization flags) {
+ mSetFlagsRule = new SetFlagsRule(flags);
+ }
+
@Before
- public void setUp() {
+ public void setUp() throws Exception {
mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
+ mUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+ setOverride(null);
}
@After
@@ -80,26 +107,35 @@ public class DesktopModeFlagsTest {
}
@Test
- @DisableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION)
@EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
- public void isTrue_devOptionFlagDisabled_overrideOff_featureFlagOn_returnsTrue() {
+ public void isTrue_overrideOff_featureFlagOn() throws Exception {
setOverride(OVERRIDE_OFF_SETTING);
- // In absence of dev options, follow flag
- assertThat(ENABLE_DESKTOP_WINDOWING_MODE.isTrue()).isTrue();
+
+ if (showDesktopWindowingDevOpts()) {
+ // DW Dev Opts turns off flags when ON
+ assertThat(ENABLE_DESKTOP_WINDOWING_MODE.isTrue()).isFalse();
+ } else {
+ // DE Dev Opts doesn't turn flags OFF
+ assertThat(ENABLE_DESKTOP_WINDOWING_MODE.isTrue()).isTrue();
+ }
}
@Test
- @DisableFlags({FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE})
- public void isTrue_devOptionFlagDisabled_overrideOn_featureFlagOff_returnsFalse() {
+ @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+ public void isTrue_overrideOn_featureFlagOff() throws Exception {
setOverride(OVERRIDE_ON_SETTING);
- assertThat(ENABLE_DESKTOP_WINDOWING_MODE.isTrue()).isFalse();
+ if (showAnyDevOpts()) {
+ assertThat(ENABLE_DESKTOP_WINDOWING_MODE.isTrue()).isTrue();
+ } else {
+ assertThat(ENABLE_DESKTOP_WINDOWING_MODE.isTrue()).isFalse();
+ }
}
@Test
- @EnableFlags({FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE})
- public void isTrue_overrideUnset_featureFlagOn_returnsTrue() {
+ @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+ public void isTrue_overrideUnset_featureFlagOn() throws Exception {
setOverride(OVERRIDE_UNSET_SETTING);
// For overridableFlag, for unset overrides, follow flag
@@ -107,9 +143,8 @@ public class DesktopModeFlagsTest {
}
@Test
- @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION)
@DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
- public void isTrue_overrideUnset_featureFlagOff_returnsFalse() {
+ public void isTrue_overrideUnset_featureFlagOff() throws Exception {
setOverride(OVERRIDE_UNSET_SETTING);
// For overridableFlag, for unset overrides, follow flag
@@ -117,8 +152,8 @@ public class DesktopModeFlagsTest {
}
@Test
- @EnableFlags({FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE})
- public void isTrue_noOverride_featureFlagOn_returnsTrue() {
+ @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+ public void isTrue_noOverride_featureFlagOn_returnsTrue() throws Exception {
setOverride(null);
// For overridableFlag, in absence of overrides, follow flag
@@ -126,9 +161,8 @@ public class DesktopModeFlagsTest {
}
@Test
- @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION)
@DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
- public void isTrue_noOverride_featureFlagOff_returnsFalse() {
+ public void isTrue_noOverride_featureFlagOff_returnsFalse() throws Exception {
setOverride(null);
// For overridableFlag, in absence of overrides, follow flag
@@ -136,8 +170,8 @@ public class DesktopModeFlagsTest {
}
@Test
- @EnableFlags({FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE})
- public void isTrue_unrecognizableOverride_featureFlagOn_returnsTrue() {
+ @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+ public void isTrue_unrecognizableOverride_featureFlagOn_returnsTrue() throws Exception {
setOverride(-2);
// For overridableFlag, for unrecognized overrides, follow flag
@@ -145,9 +179,8 @@ public class DesktopModeFlagsTest {
}
@Test
- @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION)
@DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
- public void isTrue_unrecognizableOverride_featureFlagOff_returnsFalse() {
+ public void isTrue_unrecognizableOverride_featureFlagOff_returnsFalse() throws Exception {
setOverride(-2);
// For overridableFlag, for unrecognizable overrides, follow flag
@@ -155,27 +188,10 @@ public class DesktopModeFlagsTest {
}
@Test
- @EnableFlags({FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE})
- public void isTrue_overrideOff_featureFlagOn_returnsFalse() {
- setOverride(OVERRIDE_OFF_SETTING);
-
- // For overridableFlag, follow override if they exist
- assertThat(ENABLE_DESKTOP_WINDOWING_MODE.isTrue()).isFalse();
- }
-
- @Test
- @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION)
- @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
- public void isTrue_overrideOn_featureFlagOff_returnsTrue() {
- setOverride(OVERRIDE_ON_SETTING);
-
- // For overridableFlag, follow override if they exist
- assertThat(ENABLE_DESKTOP_WINDOWING_MODE.isTrue()).isTrue();
- }
+ @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+ public void isTrue_overrideOffThenOn_featureFlagOn_returnsFalseAndFalse() throws Exception {
+ assumeTrue(showDesktopWindowingDevOpts());
- @Test
- @EnableFlags({FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE})
- public void isTrue_overrideOffThenOn_featureFlagOn_returnsFalseAndFalse() {
setOverride(OVERRIDE_OFF_SETTING);
// For overridableFlag, follow override if they exist
@@ -188,9 +204,9 @@ public class DesktopModeFlagsTest {
}
@Test
- @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION)
@DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
- public void isTrue_overrideOnThenOff_featureFlagOff_returnsTrueAndTrue() {
+ public void isTrue_overrideOnThenOff_featureFlagOff_returnsTrueAndTrue() throws Exception {
+ assumeTrue(showAnyDevOpts());
setOverride(OVERRIDE_ON_SETTING);
// For overridableFlag, follow override if they exist
@@ -203,146 +219,144 @@ public class DesktopModeFlagsTest {
}
@Test
- @EnableFlags({FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE,
- FLAG_ENABLE_DESKTOP_WINDOWING_TRANSITIONS})
- public void isTrue_dwFlagOn_overrideUnset_featureFlagOn_returnsTrue() {
+ @EnableFlags({FLAG_ENABLE_DESKTOP_WINDOWING_MODE})
+ public void isTrue_dwFlagOn_overrideUnset_featureFlagOn() throws Exception {
+ mLocalFlagValue = true;
setOverride(OVERRIDE_UNSET_SETTING);
// For unset overrides, follow flag
- assertThat(ENABLE_DESKTOP_WINDOWING_TRANSITIONS.isTrue()).isTrue();
+ assertThat(mOverriddenLocalFlag.isTrue()).isTrue();
+ assertThat(mNotOverriddenLocalFlag.isTrue()).isTrue();
}
@Test
- @EnableFlags({FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE})
- @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_TRANSITIONS)
- public void isTrue_dwFlagOn_overrideUnset_featureFlagOff_returnsFalse() {
+ @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+ public void isTrue_dwFlagOn_overrideUnset_featureFlagOff() throws Exception {
+ mLocalFlagValue = false;
setOverride(OVERRIDE_UNSET_SETTING);
// For unset overrides, follow flag
- assertThat(ENABLE_DESKTOP_WINDOWING_TRANSITIONS.isTrue()).isFalse();
+ assertThat(mOverriddenLocalFlag.isTrue()).isFalse();
+ assertThat(mNotOverriddenLocalFlag.isTrue()).isFalse();
}
@Test
- @EnableFlags({
- FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION,
- FLAG_ENABLE_DESKTOP_WINDOWING_MODE,
- FLAG_ENABLE_DESKTOP_WINDOWING_TRANSITIONS
- })
- public void isTrue_dwFlagOn_overrideOn_featureFlagOn_returnsTrue() {
+ @EnableFlags({FLAG_ENABLE_DESKTOP_WINDOWING_MODE})
+ public void isTrue_dwFlagOn_overrideOn_featureFlagOn() throws Exception {
+ mLocalFlagValue = true;
setOverride(OVERRIDE_ON_SETTING);
// When toggle override matches its default state (dw flag), don't override flags
- assertThat(ENABLE_DESKTOP_WINDOWING_TRANSITIONS.isTrue()).isTrue();
+ assertThat(mOverriddenLocalFlag.isTrue()).isTrue();
+ assertThat(mNotOverriddenLocalFlag.isTrue()).isTrue();
}
@Test
- @EnableFlags({FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE})
- @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_TRANSITIONS)
- public void isTrue_dwFlagOn_overrideOn_featureFlagOff_returnsFalse() {
+ @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+ public void isTrue_dwFlagOn_overrideOn_featureFlagOff() throws Exception {
+ mLocalFlagValue = false;
setOverride(OVERRIDE_ON_SETTING);
- // When toggle override matches its default state (dw flag), don't override flags
- assertThat(ENABLE_DESKTOP_WINDOWING_TRANSITIONS.isTrue()).isFalse();
+ if (showDesktopExperienceDevOpts()) {
+ assertThat(mOverriddenLocalFlag.isTrue()).isTrue();
+ } else {
+ assertThat(mOverriddenLocalFlag.isTrue()).isFalse();
+ }
+ assertThat(mNotOverriddenLocalFlag.isTrue()).isFalse();
}
@Test
- @EnableFlags({
- FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION,
- FLAG_ENABLE_DESKTOP_WINDOWING_MODE,
- FLAG_ENABLE_DESKTOP_WINDOWING_TRANSITIONS
- })
- public void isTrue_dwFlagOn_overrideOff_featureFlagOn_returnsTrue() {
+ @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+ public void isTrue_dwFlagOn_overrideOff_featureFlagOn() throws Exception {
+ mLocalFlagValue = true;
setOverride(OVERRIDE_OFF_SETTING);
- // Follow override if they exist, and is not equal to default toggle state (dw flag)
- assertThat(ENABLE_DESKTOP_WINDOWING_TRANSITIONS.isTrue()).isTrue();
+ if (showDesktopWindowingDevOpts()) {
+ // Follow override if they exist, and is not equal to default toggle state (dw flag)
+ assertThat(mOverriddenLocalFlag.isTrue()).isFalse();
+ } else {
+ assertThat(mOverriddenLocalFlag.isTrue()).isTrue();
+ }
+ assertThat(mNotOverriddenLocalFlag.isTrue()).isTrue();
}
@Test
- @EnableFlags({FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE})
- @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_TRANSITIONS)
- public void isTrue_dwFlagOn_overrideOff_featureFlagOff_returnsFalse() {
+ @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+ public void isTrue_dwFlagOn_overrideOff_featureFlagOff_returnsFalse() throws Exception {
+ mLocalFlagValue = false;
setOverride(OVERRIDE_OFF_SETTING);
// Follow override if they exist, and is not equal to default toggle state (dw flag)
- assertThat(ENABLE_DESKTOP_WINDOWING_TRANSITIONS.isTrue()).isFalse();
+ assertThat(mOverriddenLocalFlag.isTrue()).isFalse();
+ assertThat(mNotOverriddenLocalFlag.isTrue()).isFalse();
}
@Test
- @EnableFlags({
- FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION,
- FLAG_ENABLE_DESKTOP_WINDOWING_TRANSITIONS
- })
@DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
- public void isTrue_dwFlagOff_overrideUnset_featureFlagOn_returnsTrue() {
+ public void isTrue_dwFlagOff_overrideUnset_featureFlagOn_returnsTrue() throws Exception {
+ mLocalFlagValue = true;
setOverride(OVERRIDE_UNSET_SETTING);
// For unset overrides, follow flag
- assertThat(ENABLE_DESKTOP_WINDOWING_TRANSITIONS.isTrue()).isTrue();
+ assertThat(mOverriddenLocalFlag.isTrue()).isTrue();
+ assertThat(mNotOverriddenLocalFlag.isTrue()).isTrue();
}
@Test
- @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION)
- @DisableFlags({
- FLAG_ENABLE_DESKTOP_WINDOWING_MODE,
- FLAG_ENABLE_DESKTOP_WINDOWING_TRANSITIONS
- })
- public void isTrue_dwFlagOff_overrideUnset_featureFlagOff_returnsFalse() {
+ @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+ public void isTrue_dwFlagOff_overrideUnset_featureFlagOff_returnsFalse() throws Exception {
+ mLocalFlagValue = false;
setOverride(OVERRIDE_UNSET_SETTING);
// For unset overrides, follow flag
- assertThat(ENABLE_DESKTOP_WINDOWING_TRANSITIONS.isTrue()).isFalse();
+ assertThat(mOverriddenLocalFlag.isTrue()).isFalse();
+ assertThat(mNotOverriddenLocalFlag.isTrue()).isFalse();
}
@Test
- @EnableFlags({
- FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION,
- FLAG_ENABLE_DESKTOP_WINDOWING_TRANSITIONS
- })
@DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
- public void isTrue_dwFlagOff_overrideOn_featureFlagOn_returnsTrue() {
+ public void isTrue_dwFlagOff_overrideOn_featureFlagOn_returnsTrue() throws Exception {
+ mLocalFlagValue = true;
setOverride(OVERRIDE_ON_SETTING);
// Follow override if they exist, and is not equal to default toggle state (dw flag)
- assertThat(ENABLE_DESKTOP_WINDOWING_TRANSITIONS.isTrue()).isTrue();
+ assertThat(mOverriddenLocalFlag.isTrue()).isTrue();
+ assertThat(mNotOverriddenLocalFlag.isTrue()).isTrue();
}
@Test
- @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION)
- @DisableFlags({
- FLAG_ENABLE_DESKTOP_WINDOWING_MODE,
- FLAG_ENABLE_DESKTOP_WINDOWING_TRANSITIONS
- })
- public void isTrue_dwFlagOff_overrideOn_featureFlagOff_returnFalse() {
+ @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+ public void isTrue_dwFlagOff_overrideOn_featureFlagOff() throws Exception {
+ mLocalFlagValue = false;
setOverride(OVERRIDE_ON_SETTING);
- // Follow override if they exist, and is not equal to default toggle state (dw flag)
- assertThat(ENABLE_DESKTOP_WINDOWING_TRANSITIONS.isTrue()).isFalse();
+ if (showAnyDevOpts()) {
+ assertThat(mOverriddenLocalFlag.isTrue()).isTrue();
+ } else {
+ // Follow override if they exist, and is not equal to default toggle state (dw flag)
+ assertThat(mOverriddenLocalFlag.isTrue()).isFalse();
+ }
+ assertThat(mNotOverriddenLocalFlag.isTrue()).isFalse();
}
@Test
- @EnableFlags({
- FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION,
- FLAG_ENABLE_DESKTOP_WINDOWING_TRANSITIONS
- })
@DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
- public void isTrue_dwFlagOff_overrideOff_featureFlagOn_returnsTrue() {
+ public void isTrue_dwFlagOff_overrideOff_featureFlagOn_returnsTrue() throws Exception {
+ mLocalFlagValue = true;
setOverride(OVERRIDE_OFF_SETTING);
// When toggle override matches its default state (dw flag), don't override flags
- assertThat(ENABLE_DESKTOP_WINDOWING_TRANSITIONS.isTrue()).isTrue();
+ assertThat(mOverriddenLocalFlag.isTrue()).isTrue();
+ assertThat(mNotOverriddenLocalFlag.isTrue()).isTrue();
}
@Test
- @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION)
- @DisableFlags({
- FLAG_ENABLE_DESKTOP_WINDOWING_MODE,
- FLAG_ENABLE_DESKTOP_WINDOWING_TRANSITIONS
- })
- public void isTrue_dwFlagOff_overrideOff_featureFlagOff_returnsFalse() {
+ @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+ public void isTrue_dwFlagOff_overrideOff_featureFlagOff_returnsFalse() throws Exception {
+ mLocalFlagValue = false;
setOverride(OVERRIDE_OFF_SETTING);
- // When toggle override matches its default state (dw flag), don't override flags
- assertThat(ENABLE_DESKTOP_WINDOWING_TRANSITIONS.isTrue()).isFalse();
+ assertThat(mOverriddenLocalFlag.isTrue()).isFalse();
+ assertThat(mNotOverriddenLocalFlag.isTrue()).isFalse();
}
@Test
@@ -365,7 +379,9 @@ public class DesktopModeFlagsTest {
assertThat(OVERRIDE_UNSET.getSetting()).isEqualTo(-1);
}
- private void setOverride(Integer setting) {
+ private void setOverride(Integer setting) throws Exception {
+ setSysProp(setting);
+
ContentResolver contentResolver = mContext.getContentResolver();
String key = Settings.Global.DEVELOPMENT_OVERRIDE_DESKTOP_MODE_FEATURES;
@@ -376,11 +392,35 @@ public class DesktopModeFlagsTest {
}
}
+ private void setSysProp(Integer value) throws Exception {
+ if (value == null) {
+ resetSysProp();
+ } else {
+ mUiDevice.executeShellCommand(
+ "setprop " + DesktopModeFlags.SYSTEM_PROPERTY_NAME + " " + value);
+ }
+ }
+
+ private void resetSysProp() throws Exception {
+ mUiDevice.executeShellCommand("setprop " + DesktopModeFlags.SYSTEM_PROPERTY_NAME + " ''");
+ }
+
private void resetCache() throws Exception {
Field cachedToggleOverride = DesktopModeFlags.class.getDeclaredField(
"sCachedToggleOverride");
cachedToggleOverride.setAccessible(true);
cachedToggleOverride.set(null, null);
- setOverride(OVERRIDE_UNSET_SETTING);
+ }
+
+ private boolean showDesktopWindowingDevOpts() {
+ return Flags.showDesktopWindowingDevOption() && !Flags.showDesktopExperienceDevOption();
+ }
+
+ private boolean showDesktopExperienceDevOpts() {
+ return Flags.showDesktopExperienceDevOption();
+ }
+
+ private boolean showAnyDevOpts() {
+ return Flags.showDesktopWindowingDevOption() || Flags.showDesktopExperienceDevOption();
}
}
diff --git a/data/etc/com.android.systemui.xml b/data/etc/com.android.systemui.xml
index 45952ea75b6f..3eadf3b94515 100644
--- a/data/etc/com.android.systemui.xml
+++ b/data/etc/com.android.systemui.xml
@@ -95,5 +95,6 @@
<permission name="android.permission.START_FOREGROUND_SERVICES_FROM_BACKGROUND" />
<permission name="android.permission.OVERRIDE_SYSTEM_KEY_BEHAVIOR_IN_FOCUSED_WINDOW"/>
<permission name="android.permission.SUBSCRIBE_TO_KEYGUARD_LOCKED_STATE" />
+ <permission name="android.permission.SET_UNRESTRICTED_GESTURE_EXCLUSION" />
</privapp-permissions>
</permissions>
diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java
index b332cf0d751f..3d4dccf095f5 100644
--- a/graphics/java/android/graphics/Paint.java
+++ b/graphics/java/android/graphics/Paint.java
@@ -2198,10 +2198,12 @@ public class Paint {
* is configured as {@code 'wght' 500, 'ital' 1}, and if the override is specified as
* {@code 'wght' 700, `wdth` 150}, then the effective font variation setting is
* {@code `wght' 700, 'ital' 1, 'wdth' 150}. The `wght` value is updated by override, 'ital'
- * value is preserved because no overrides, and `wdth` value is added by override.
+ * value is preserved because no overrides, and `wdth` value is added by override. If the font
+ * variation override is empty or null, nothing overrides and original font variation settings
+ * assigned to the font instance is used as it is.
*
- * @param fontVariationOverride font variation settings. You can pass null or empty string as
- * no variation settings.
+ * @param fontVariationOverride font variation override. You can pass null or empty string for
+ * clearing font variation override.
*
* @return true if the provided font variation settings is valid. Otherwise returns false.
*
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
index d0d1721115cb..1bcb0bb91515 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -18,6 +18,7 @@ package androidx.window.extensions.embedding;
import static android.app.ActivityManager.START_SUCCESS;
import static android.app.ActivityOptions.KEY_LAUNCH_TASK_FRAGMENT_TOKEN;
+import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.view.Display.DEFAULT_DISPLAY;
@@ -3154,15 +3155,22 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
final WindowContainerTransaction wct = transactionRecord.getTransaction();
final TaskFragmentContainer launchedInTaskFragment;
if (launchingActivity != null) {
- final int taskId = getTaskId(launchingActivity);
final String overlayTag = options.getString(KEY_OVERLAY_TAG);
if (Flags.activityEmbeddingOverlayPresentationFlag()
&& overlayTag != null) {
launchedInTaskFragment = createOrUpdateOverlayTaskFragmentIfNeeded(wct,
options, intent, launchingActivity);
} else {
- launchedInTaskFragment = resolveStartActivityIntent(wct, taskId, intent,
- launchingActivity);
+ final int taskId = getTaskId(launchingActivity);
+ if (taskId != INVALID_TASK_ID) {
+ launchedInTaskFragment = resolveStartActivityIntent(wct, taskId, intent,
+ launchingActivity);
+ } else {
+ // We cannot get a valid task id of launchingActivity so we fall back to
+ // treat it as a non-Activity context.
+ launchedInTaskFragment =
+ resolveStartActivityIntentFromNonActivityContext(wct, intent);
+ }
}
} else {
launchedInTaskFragment = resolveStartActivityIntentFromNonActivityContext(wct,
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleControllerBubbleBarTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleControllerBubbleBarTest.kt
index dd387b382dc6..09a93d501f8e 100644
--- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleControllerBubbleBarTest.kt
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleControllerBubbleBarTest.kt
@@ -296,5 +296,9 @@ class BubbleControllerBubbleBarTest {
override fun onBubbleStateChange(update: BubbleBarUpdate?) {}
override fun animateBubbleBarLocation(location: BubbleBarLocation?) {}
+
+ override fun onDragItemOverBubbleBarDragZone(location: BubbleBarLocation) {}
+
+ override fun onItemDraggedOutsideBubbleBarDropZone() {}
}
}
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedTaskInfo.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedTaskInfo.java
index 2ca011bfe000..0a1e3b9495a0 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedTaskInfo.java
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedTaskInfo.java
@@ -111,7 +111,7 @@ public class GroupedTaskInfo implements Parcelable {
* Create new for a pair of tasks in split screen
*/
public static GroupedTaskInfo forSplitTasks(@NonNull TaskInfo task1,
- @NonNull TaskInfo task2, @Nullable SplitBounds splitBounds) {
+ @NonNull TaskInfo task2, @NonNull SplitBounds splitBounds) {
return new GroupedTaskInfo(List.of(task1, task2), splitBounds, TYPE_SPLIT,
null /* minimizedFreeformTasks */);
}
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/TransitionUtil.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/TransitionUtil.java
index 840de2c86f92..4d00c74155a8 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/TransitionUtil.java
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/TransitionUtil.java
@@ -127,6 +127,46 @@ public class TransitionUtil {
}
/**
+ * Check if all changes in this transition are only ordering changes. If so, we won't animate.
+ */
+ public static boolean isAllOrderOnly(TransitionInfo info) {
+ for (int i = info.getChanges().size() - 1; i >= 0; --i) {
+ if (!isOrderOnly(info.getChanges().get(i))) return false;
+ }
+ return true;
+ }
+
+ /**
+ * Look through a transition and see if all non-closing changes are no-animation. If so, no
+ * animation should play.
+ */
+ public static boolean isAllNoAnimation(TransitionInfo info) {
+ if (isClosingType(info.getType())) {
+ // no-animation is only relevant for launching (open) activities.
+ return false;
+ }
+ boolean hasNoAnimation = false;
+ final int changeSize = info.getChanges().size();
+ for (int i = changeSize - 1; i >= 0; --i) {
+ final TransitionInfo.Change change = info.getChanges().get(i);
+ if (isClosingType(change.getMode())) {
+ // ignore closing apps since they are a side-effect of the transition and don't
+ // animate.
+ continue;
+ }
+ if (change.hasFlags(TransitionInfo.FLAG_NO_ANIMATION)) {
+ hasNoAnimation = true;
+ } else if (!isOrderOnly(change) && !change.hasFlags(TransitionInfo.FLAG_IS_OCCLUDED)) {
+ // Ignore the order only or occluded changes since they shouldn't be visible during
+ // animation. For anything else, we need to animate if at-least one relevant
+ // participant *is* animated,
+ return false;
+ }
+ }
+ return hasNoAnimation;
+ }
+
+ /**
* Filter that selects leaf-tasks only. THIS IS ORDER-DEPENDENT! For it to work properly, you
* MUST call `test` in the same order that the changes appear in the TransitionInfo.
*/
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeCompatPolicy.kt b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeCompatPolicy.kt
new file mode 100644
index 000000000000..0ea3c2a80fb4
--- /dev/null
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeCompatPolicy.kt
@@ -0,0 +1,60 @@
+/*
+ * 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.shared.desktopmode
+
+import android.app.TaskInfo
+import android.content.Context
+import android.window.DesktopModeFlags
+import com.android.internal.R
+
+/**
+ * Class to decide whether to apply app compat policies in desktop mode.
+ */
+// TODO(b/347289970): Consider replacing with API
+class DesktopModeCompatPolicy(context: Context) {
+
+ private val systemUiPackage: String = context.resources.getString(R.string.config_systemUi)
+
+ /**
+ * If the top activity should be exempt from desktop windowing and forced back to fullscreen.
+ * Currently includes all system ui activities and modal dialogs. However if the top activity is
+ * not being displayed, regardless of its configuration, we will not exempt it as to remain in
+ * the desktop windowing environment.
+ */
+ fun isTopActivityExemptFromDesktopWindowing(task: TaskInfo) =
+ isTopActivityExemptFromDesktopWindowing(task.baseActivity?.packageName,
+ task.numActivities, task.isTopActivityNoDisplay, task.isActivityStackTransparent)
+
+ fun isTopActivityExemptFromDesktopWindowing(packageName: String?,
+ numActivities: Int, isTopActivityNoDisplay: Boolean, isActivityStackTransparent: Boolean) =
+ DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_MODALS_POLICY.isTrue
+ && ((isSystemUiTask(packageName)
+ || isTransparentTask(isActivityStackTransparent, numActivities))
+ && !isTopActivityNoDisplay)
+
+ /**
+ * Returns true if all activities in a tasks stack are transparent. If there are no activities
+ * will return false.
+ */
+ fun isTransparentTask(task: TaskInfo): Boolean =
+ isTransparentTask(task.isActivityStackTransparent, task.numActivities)
+
+ private fun isTransparentTask(isActivityStackTransparent: Boolean, numActivities: Int) =
+ isActivityStackTransparent && numActivities > 0
+
+ private fun isSystemUiTask(packageName: String?) = packageName == systemUiPackage
+}
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java
index 2fed1380b635..1ee71ca78815 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java
@@ -218,6 +218,13 @@ public class DesktopModeStatus {
return isDeviceEligibleForDesktopMode(context) && Flags.showDesktopWindowingDevOption();
}
+ /**
+ * Return {@code true} if desktop mode dev option should be shown on current device
+ */
+ public static boolean canShowDesktopExperienceDevOption(@NonNull Context context) {
+ return Flags.showDesktopExperienceDevOption();
+ }
+
/** Returns if desktop mode dev option should be enabled if there is no user override. */
public static boolean shouldDevOptionBeEnabledByDefault() {
return Flags.enableDesktopWindowingMode();
@@ -290,7 +297,7 @@ public class DesktopModeStatus {
/**
* Return {@code true} if desktop mode is unrestricted and is supported in the device.
*/
- private static boolean isDeviceEligibleForDesktopMode(@NonNull Context context) {
+ public static boolean isDeviceEligibleForDesktopMode(@NonNull Context context) {
return !enforceDeviceRestrictions() || isDesktopModeSupported(context);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
index e8e25e20d8d8..c40a276cb7bd 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
@@ -28,6 +28,7 @@ import android.annotation.Nullable;
import android.app.Notification;
import android.app.PendingIntent;
import android.app.Person;
+import android.app.TaskInfo;
import android.content.Context;
import android.content.Intent;
import android.content.LocusId;
@@ -55,8 +56,10 @@ import com.android.wm.shell.bubbles.bar.BubbleBarExpandedView;
import com.android.wm.shell.bubbles.bar.BubbleBarLayerView;
import com.android.wm.shell.shared.annotations.ShellBackgroundThread;
import com.android.wm.shell.shared.annotations.ShellMainThread;
+import com.android.wm.shell.shared.bubbles.BubbleAnythingFlagHelper;
import com.android.wm.shell.shared.bubbles.BubbleInfo;
import com.android.wm.shell.shared.bubbles.ParcelableFlyoutMessage;
+import com.android.wm.shell.taskview.TaskView;
import java.io.PrintWriter;
import java.util.List;
@@ -204,6 +207,13 @@ public class Bubble implements BubbleViewProvider {
private Intent mAppIntent;
/**
+ * Set while preparing a transition for animation. Several steps are needed before animation
+ * starts, so this is used to detect and route associated events to the coordinating transition.
+ */
+ @Nullable
+ private BubbleTransitions.BubbleTransition mPreparingTransition;
+
+ /**
* Create a bubble with limited information based on given {@link ShortcutInfo}.
* Note: Currently this is only being used when the bubble is persisted to disk.
*/
@@ -280,6 +290,30 @@ public class Bubble implements BubbleViewProvider {
mShortcutInfo = info;
}
+ private Bubble(
+ TaskInfo task,
+ UserHandle user,
+ @Nullable Icon icon,
+ String key,
+ @ShellMainThread Executor mainExecutor,
+ @ShellBackgroundThread Executor bgExecutor) {
+ mGroupKey = null;
+ mLocusId = null;
+ mFlags = 0;
+ mUser = user;
+ mIcon = icon;
+ mIsAppBubble = true;
+ mKey = key;
+ mShowBubbleUpdateDot = false;
+ mMainExecutor = mainExecutor;
+ mBgExecutor = bgExecutor;
+ mTaskId = task.taskId;
+ mAppIntent = null;
+ mDesiredHeight = Integer.MAX_VALUE;
+ mPackageName = task.baseActivity.getPackageName();
+ }
+
+
/** Creates an app bubble. */
public static Bubble createAppBubble(Intent intent, UserHandle user, @Nullable Icon icon,
@ShellMainThread Executor mainExecutor, @ShellBackgroundThread Executor bgExecutor) {
@@ -291,6 +325,16 @@ public class Bubble implements BubbleViewProvider {
mainExecutor, bgExecutor);
}
+ /** Creates a task bubble. */
+ public static Bubble createTaskBubble(TaskInfo info, UserHandle user, @Nullable Icon icon,
+ @ShellMainThread Executor mainExecutor, @ShellBackgroundThread Executor bgExecutor) {
+ return new Bubble(info,
+ user,
+ icon,
+ getAppBubbleKeyForTask(info),
+ mainExecutor, bgExecutor);
+ }
+
/** Creates a shortcut bubble. */
public static Bubble createShortcutBubble(
ShortcutInfo info,
@@ -316,6 +360,15 @@ public class Bubble implements BubbleViewProvider {
return info.getPackage() + ":" + info.getUserId() + ":" + info.getId();
}
+ /**
+ * Returns the key for an app bubble from an app with package name, {@code packageName} on an
+ * Android user, {@code user}.
+ */
+ public static String getAppBubbleKeyForTask(TaskInfo taskInfo) {
+ Objects.requireNonNull(taskInfo);
+ return KEY_APP_BUBBLE + ":" + taskInfo.taskId;
+ }
+
@VisibleForTesting(visibility = PRIVATE)
public Bubble(@NonNull final BubbleEntry entry,
final Bubbles.BubbleMetadataFlagListener listener,
@@ -469,6 +522,10 @@ public class Bubble implements BubbleViewProvider {
return mBubbleTaskView;
}
+ public TaskView getTaskView() {
+ return mBubbleTaskView.getTaskView();
+ }
+
/**
* @return the ShortcutInfo id if it exists, or the metadata shortcut id otherwise.
*/
@@ -486,6 +543,10 @@ public class Bubble implements BubbleViewProvider {
return (mMetadataShortcutId != null && !mMetadataShortcutId.isEmpty());
}
+ public BubbleTransitions.BubbleTransition getPreparingTransition() {
+ return mPreparingTransition;
+ }
+
/**
* Call this to clean up the task for the bubble. Ensure this is always called when done with
* the bubble.
@@ -512,7 +573,8 @@ public class Bubble implements BubbleViewProvider {
mIntentActive = false;
}
- private void cleanupTaskView() {
+ /** Cleans-up the taskview associated with this bubble (possibly removing the task from wm) */
+ public void cleanupTaskView() {
if (mBubbleTaskView != null) {
mBubbleTaskView.cleanup();
mBubbleTaskView = null;
@@ -533,7 +595,7 @@ public class Bubble implements BubbleViewProvider {
* <p>If we're switching between bar and floating modes, pass {@code false} on
* {@code cleanupTaskView} to avoid recreating it in the new mode.
*/
- void cleanupViews(boolean cleanupTaskView) {
+ public void cleanupViews(boolean cleanupTaskView) {
cleanupExpandedView(cleanupTaskView);
mIconView = null;
}
@@ -556,6 +618,13 @@ public class Bubble implements BubbleViewProvider {
}
/**
+ * Sets the current bubble-transition that is coordinating a change in this bubble.
+ */
+ void setPreparingTransition(BubbleTransitions.BubbleTransition transit) {
+ mPreparingTransition = transit;
+ }
+
+ /**
* Sets whether this bubble is considered text changed. This method is purely for
* testing.
*/
@@ -1025,7 +1094,7 @@ public class Bubble implements BubbleViewProvider {
* intent for an app. In this case we don't show a badge on the icon.
*/
public boolean isAppLaunchIntent() {
- if (Flags.enableBubbleAnything() && mAppIntent != null) {
+ if (BubbleAnythingFlagHelper.enableCreateAnyBubble() && mAppIntent != null) {
return mAppIntent.hasCategory("android.intent.category.LAUNCHER");
}
return false;
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 4f9028e8aaf3..5cd04b11bbfd 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
@@ -40,9 +40,11 @@ import android.annotation.BinderThread;
import android.annotation.NonNull;
import android.annotation.UserIdInt;
import android.app.ActivityManager;
+import android.app.ActivityOptions;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.PendingIntent;
+import android.app.TaskInfo;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -78,6 +80,8 @@ import android.view.WindowInsets;
import android.view.WindowManager;
import android.window.ScreenCapture;
import android.window.ScreenCapture.SynchronousScreenCaptureListener;
+import android.window.WindowContainerToken;
+import android.window.WindowContainerTransaction;
import androidx.annotation.MainThread;
import androidx.annotation.Nullable;
@@ -110,6 +114,7 @@ import com.android.wm.shell.onehanded.OneHandedController;
import com.android.wm.shell.onehanded.OneHandedTransitionCallback;
import com.android.wm.shell.shared.annotations.ShellBackgroundThread;
import com.android.wm.shell.shared.annotations.ShellMainThread;
+import com.android.wm.shell.shared.bubbles.BubbleAnythingFlagHelper;
import com.android.wm.shell.shared.bubbles.BubbleBarLocation;
import com.android.wm.shell.shared.bubbles.BubbleBarUpdate;
import com.android.wm.shell.sysui.ConfigurationChangeListener;
@@ -287,6 +292,8 @@ public class BubbleController implements ConfigurationChangeListener,
/** Used to send updates to the views from {@link #mBubbleDataListener}. */
private BubbleViewCallback mBubbleViewCallback;
+ private final BubbleTransitions mBubbleTransitions;
+
public BubbleController(Context context,
ShellInit shellInit,
ShellCommandHandler shellCommandHandler,
@@ -350,12 +357,16 @@ public class BubbleController implements ConfigurationChangeListener,
context.getResources().getDimensionPixelSize(
com.android.internal.R.dimen.importance_ring_stroke_width));
mDisplayController = displayController;
+ final TaskViewTransitions tvTransitions;
if (TaskViewTransitions.useRepo()) {
- mTaskViewController = new TaskViewTransitions(transitions, taskViewRepository,
- organizer, syncQueue);
+ tvTransitions = new TaskViewTransitions(transitions, taskViewRepository, organizer,
+ syncQueue);
} else {
- mTaskViewController = taskViewTransitions;
+ tvTransitions = taskViewTransitions;
}
+ mTaskViewController = new BubbleTaskViewController(tvTransitions);
+ mBubbleTransitions = new BubbleTransitions(transitions, organizer, taskViewRepository, data,
+ tvTransitions, context);
mTransitions = transitions;
mOneHandedOptional = oneHandedOptional;
mDragAndDropController = dragAndDropController;
@@ -1422,7 +1433,7 @@ public class BubbleController implements ConfigurationChangeListener,
* @param info the shortcut info for the bubble.
*/
public void expandStackAndSelectBubble(ShortcutInfo info) {
- if (!Flags.enableBubbleAnything()) return;
+ if (!BubbleAnythingFlagHelper.enableCreateAnyBubble()) return;
Bubble b = mBubbleData.getOrCreateBubble(info); // Removes from overflow
ProtoLog.v(WM_SHELL_BUBBLES, "expandStackAndSelectBubble - shortcut=%s", info);
if (b.isInflated()) {
@@ -1439,7 +1450,7 @@ public class BubbleController implements ConfigurationChangeListener,
* @param intent the intent for the bubble.
*/
public void expandStackAndSelectBubble(Intent intent, UserHandle user) {
- if (!Flags.enableBubbleAnything()) return;
+ if (!BubbleAnythingFlagHelper.enableCreateAnyBubble()) return;
Bubble b = mBubbleData.getOrCreateBubble(intent, user); // Removes from overflow
ProtoLog.v(WM_SHELL_BUBBLES, "expandStackAndSelectBubble - intent=%s", intent);
if (b.isInflated()) {
@@ -1456,7 +1467,19 @@ public class BubbleController implements ConfigurationChangeListener,
* @param taskInfo the task.
*/
public void expandStackAndSelectBubble(ActivityManager.RunningTaskInfo taskInfo) {
- // TODO(384976265): Not implemented yet
+ if (!BubbleAnythingFlagHelper.enableBubbleToFullscreen()) return;
+ Bubble b = mBubbleData.getOrCreateBubble(taskInfo); // Removes from overflow
+ ProtoLog.v(WM_SHELL_BUBBLES, "expandStackAndSelectBubble - intent=%s", taskInfo.taskId);
+ if (b.isInflated()) {
+ mBubbleData.setSelectedBubbleAndExpandStack(b);
+ } else {
+ b.enable(Notification.BubbleMetadata.FLAG_AUTO_EXPAND_BUBBLE);
+ // Lazy init stack view when a bubble is created
+ ensureBubbleViewsAndWindowCreated();
+ mBubbleTransitions.startConvertToBubble(b, taskInfo, mExpandedViewManager,
+ mBubbleTaskViewFactory, mBubblePositioner, mLogger, mStackView, mLayerView,
+ mBubbleIconFactory, mInflateSynchronously);
+ }
}
/**
@@ -2057,7 +2080,12 @@ public class BubbleController implements ConfigurationChangeListener,
@Override
public void removeBubble(Bubble removedBubble) {
if (mLayerView != null) {
+ final BubbleTransitions.BubbleTransition bubbleTransit =
+ removedBubble.getPreparingTransition();
mLayerView.removeBubble(removedBubble, () -> {
+ if (bubbleTransit != null) {
+ bubbleTransit.continueCollapse();
+ }
if (!mBubbleData.hasBubbles() && !isStackExpanded()) {
mLayerView.setVisibility(INVISIBLE);
removeFromWindowManagerMaybe();
@@ -2261,9 +2289,16 @@ public class BubbleController implements ConfigurationChangeListener,
private void showExpandedViewForBubbleBar() {
BubbleViewProvider selectedBubble = mBubbleData.getSelectedBubble();
- if (selectedBubble != null && mLayerView != null) {
- mLayerView.showExpandedView(selectedBubble);
+ if (selectedBubble == null) return;
+ if (selectedBubble instanceof Bubble) {
+ final Bubble bubble = (Bubble) selectedBubble;
+ if (bubble.getPreparingTransition() != null) {
+ bubble.getPreparingTransition().continueExpand();
+ return;
+ }
}
+ if (mLayerView == null) return;
+ mLayerView.showExpandedView(selectedBubble);
}
private void collapseExpandedViewForBubbleBar() {
@@ -2481,7 +2516,7 @@ public class BubbleController implements ConfigurationChangeListener,
* @param entry the entry to bubble.
*/
static boolean canLaunchInTaskView(Context context, BubbleEntry entry) {
- if (Flags.enableBubbleAnything()) return true;
+ if (BubbleAnythingFlagHelper.enableCreateAnyBubble()) return true;
PendingIntent intent = entry.getBubbleMetadata() != null
? entry.getBubbleMetadata().getIntent()
: null;
@@ -2613,6 +2648,17 @@ public class BubbleController implements ConfigurationChangeListener,
public void animateBubbleBarLocation(BubbleBarLocation location) {
mListener.call(l -> l.animateBubbleBarLocation(location));
}
+
+ @Override
+ public void onDragItemOverBubbleBarDragZone(
+ @NonNull BubbleBarLocation location) {
+ mListener.call(l -> l.onDragItemOverBubbleBarDragZone(location));
+ }
+
+ @Override
+ public void onItemDraggedOutsideBubbleBarDropZone() {
+ mListener.call(IBubblesListener::onItemDraggedOutsideBubbleBarDropZone);
+ }
};
IBubblesImpl(BubbleController controller) {
@@ -2665,7 +2711,18 @@ public class BubbleController implements ConfigurationChangeListener,
@Override
public void collapseBubbles() {
- mMainExecutor.execute(() -> mController.collapseStack());
+ mMainExecutor.execute(() -> {
+ if (mBubbleData.getSelectedBubble() instanceof Bubble) {
+ if (((Bubble) mBubbleData.getSelectedBubble()).getPreparingTransition()
+ != null) {
+ // Currently preparing a transition which will, itself, collapse the bubble.
+ // For transition preparation, the timing of bubble-collapse must be in
+ // sync with the rest of the set-up.
+ return;
+ }
+ }
+ mController.collapseStack();
+ });
}
@Override
@@ -3057,4 +3114,84 @@ public class BubbleController implements ConfigurationChangeListener,
return mKeyToShownInShadeMap.get(key);
}
}
+
+ private class BubbleTaskViewController implements TaskViewController {
+ private final TaskViewTransitions mBaseTransitions;
+
+ BubbleTaskViewController(TaskViewTransitions baseTransitions) {
+ mBaseTransitions = baseTransitions;
+ }
+
+ @Override
+ public void registerTaskView(TaskViewTaskController tv) {
+ mBaseTransitions.registerTaskView(tv);
+ }
+
+ @Override
+ public void unregisterTaskView(TaskViewTaskController tv) {
+ mBaseTransitions.unregisterTaskView(tv);
+ }
+
+ @Override
+ public void startShortcutActivity(@NonNull TaskViewTaskController destination,
+ @NonNull ShortcutInfo shortcut, @NonNull ActivityOptions options,
+ @Nullable Rect launchBounds) {
+ mBaseTransitions.startShortcutActivity(destination, shortcut, options, launchBounds);
+ }
+
+ @Override
+ public void startActivity(@NonNull TaskViewTaskController destination,
+ @NonNull PendingIntent pendingIntent, @Nullable Intent fillInIntent,
+ @NonNull ActivityOptions options, @Nullable Rect launchBounds) {
+ mBaseTransitions.startActivity(destination, pendingIntent, fillInIntent,
+ options, launchBounds);
+ }
+
+ @Override
+ public void startRootTask(@NonNull TaskViewTaskController destination,
+ ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash,
+ @Nullable WindowContainerTransaction wct) {
+ mBaseTransitions.startRootTask(destination, taskInfo, leash, wct);
+ }
+
+ @Override
+ public void removeTaskView(@NonNull TaskViewTaskController taskView,
+ @Nullable WindowContainerToken taskToken) {
+ mBaseTransitions.removeTaskView(taskView, taskToken);
+ }
+
+ @Override
+ public void moveTaskViewToFullscreen(@NonNull TaskViewTaskController taskView) {
+ final TaskInfo tinfo = taskView.getTaskInfo();
+ if (tinfo == null) {
+ return;
+ }
+ Bubble bub = null;
+ for (Bubble b : mBubbleData.getBubbles()) {
+ if (b.getTaskId() == tinfo.taskId) {
+ bub = b;
+ break;
+ }
+ }
+ if (bub == null) {
+ return;
+ }
+ mBubbleTransitions.startConvertFromBubble(bub, tinfo);
+ }
+
+ @Override
+ public void setTaskViewVisible(TaskViewTaskController taskView, boolean visible) {
+ mBaseTransitions.setTaskViewVisible(taskView, visible);
+ }
+
+ @Override
+ public void setTaskBounds(TaskViewTaskController taskView, Rect boundsOnScreen) {
+ mBaseTransitions.setTaskBounds(taskView, boundsOnScreen);
+ }
+
+ @Override
+ public boolean isUsingShellTransitions() {
+ return mBaseTransitions.isUsingShellTransitions();
+ }
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
index 74302094a296..96d0f6d5654e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
@@ -22,6 +22,7 @@ import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BUBBLES;
import android.annotation.NonNull;
import android.app.PendingIntent;
+import android.app.TaskInfo;
import android.content.Context;
import android.content.Intent;
import android.content.LocusId;
@@ -470,6 +471,17 @@ public class BubbleData {
return bubbleToReturn;
}
+ Bubble getOrCreateBubble(TaskInfo taskInfo) {
+ UserHandle user = UserHandle.of(mCurrentUserId);
+ String bubbleKey = Bubble.getAppBubbleKeyForTask(taskInfo);
+ Bubble bubbleToReturn = findAndRemoveBubbleFromOverflow(bubbleKey);
+ if (bubbleToReturn == null) {
+ bubbleToReturn = Bubble.createTaskBubble(taskInfo, user, null, mMainExecutor,
+ mBgExecutor);
+ }
+ return bubbleToReturn;
+ }
+
@Nullable
private Bubble findAndRemoveBubbleFromOverflow(String key) {
Bubble bubbleToReturn = getBubbleInStackWithKey(key);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
index 13f8e9ef9dd3..e98d53e85b94 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
@@ -71,6 +71,7 @@ import com.android.wm.shell.Flags;
import com.android.wm.shell.R;
import com.android.wm.shell.common.AlphaOptimizedButton;
import com.android.wm.shell.shared.TriangleShape;
+import com.android.wm.shell.shared.bubbles.BubbleAnythingFlagHelper;
import com.android.wm.shell.taskview.TaskView;
import java.io.PrintWriter;
@@ -226,7 +227,8 @@ public class BubbleExpandedView extends LinearLayout {
MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS);
final boolean isShortcutBubble = (mBubble.hasMetadataShortcutId()
- || (mBubble.getShortcutInfo() != null && Flags.enableBubbleAnything()));
+ || (mBubble.getShortcutInfo() != null
+ && BubbleAnythingFlagHelper.enableCreateAnyBubble()));
if (mBubble.isAppBubble()) {
Context context =
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt
index 62995319db80..086c91985ae3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt
@@ -137,14 +137,15 @@ class BubbleOverflow(private val context: Context, private val positioner: Bubbl
// Update bitmap
val fg = InsetDrawable(overflowBtn?.iconDrawable, overflowIconInset)
val drawable = AdaptiveIconDrawable(ColorDrawable(colorAccent), fg)
- bitmap = iconFactory.createBadgedIconBitmap(drawable).icon
+ val bubbleBitmapScale = FloatArray(1)
+ bitmap = iconFactory.getBubbleBitmap(drawable, bubbleBitmapScale)
// Update dot path
dotPath =
PathParser.createPathFromPathData(
res.getString(com.android.internal.R.string.config_icon_mask)
)
- val scale = iconFactory.normalizer.getScale(iconView!!.iconDrawable)
+ val scale = bubbleBitmapScale[0]
val radius = BadgedImageView.DEFAULT_PATH_SIZE / 2f
val matrix = Matrix()
matrix.setScale(
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
index 1a61793eab87..a725e04d3f8a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
@@ -87,6 +87,7 @@ public class BubblePositioner {
private int mExpandedViewLargeScreenWidth;
private int mExpandedViewLargeScreenInsetClosestEdge;
private int mExpandedViewLargeScreenInsetFurthestEdge;
+ private int mExpandedViewBubbleBarWidth;
private int mOverflowWidth;
private int mExpandedViewPadding;
@@ -158,12 +159,13 @@ public class BubblePositioner {
mBubbleOffscreenAmount = res.getDimensionPixelSize(R.dimen.bubble_stack_offscreen);
mStackOffset = res.getDimensionPixelSize(R.dimen.bubble_stack_offset);
mBubbleElevation = res.getDimensionPixelSize(R.dimen.bubble_elevation);
+ mExpandedViewBubbleBarWidth = Math.min(
+ res.getDimensionPixelSize(R.dimen.bubble_bar_expanded_view_width),
+ mPositionRect.width() - 2 * mExpandedViewPadding
+ );
if (mShowingInBubbleBar) {
- mExpandedViewLargeScreenWidth = Math.min(
- res.getDimensionPixelSize(R.dimen.bubble_bar_expanded_view_width),
- mPositionRect.width() - 2 * mExpandedViewPadding
- );
+ mExpandedViewLargeScreenWidth = mExpandedViewBubbleBarWidth;
} else if (mDeviceConfig.isSmallTablet()) {
mExpandedViewLargeScreenWidth = (int) (bounds.width()
* EXPANDED_VIEW_SMALL_TABLET_WIDTH_PERCENT);
@@ -888,7 +890,7 @@ public class BubblePositioner {
* How wide the expanded view should be when showing from the bubble bar.
*/
public int getExpandedViewWidthForBubbleBar(boolean isOverflow) {
- return isOverflow ? mOverflowWidth : mExpandedViewLargeScreenWidth;
+ return isOverflow ? mOverflowWidth : mExpandedViewBubbleBarWidth;
}
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
index 4e7f87c48a86..f1f49eda75b6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
@@ -633,8 +633,6 @@ public class BubbleStackView extends FrameLayout
mMagneticTarget,
mIndividualBubbleMagnetListener);
- hideCurrentInputMethod();
-
// Save the magnetized individual bubble so we can dispatch touch events to it.
mMagnetizedObject = mExpandedAnimationController.getMagnetizedBubbleDraggingOut();
} else {
@@ -671,6 +669,10 @@ public class BubbleStackView extends FrameLayout
return;
}
+ if (mPositioner.isImeVisible()) {
+ hideCurrentInputMethod();
+ }
+
// Show the dismiss target, if we haven't already.
mDismissView.show();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java
index 89c038b4a26b..a6b858500dcb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java
@@ -36,7 +36,7 @@ import android.view.ViewGroup;
import androidx.annotation.Nullable;
import com.android.internal.protolog.ProtoLog;
-import com.android.wm.shell.Flags;
+import com.android.wm.shell.shared.bubbles.BubbleAnythingFlagHelper;
import com.android.wm.shell.taskview.TaskView;
/**
@@ -108,8 +108,11 @@ public class BubbleTaskViewHelper {
options.setPendingIntentBackgroundActivityStartMode(
MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS);
final boolean isShortcutBubble = (mBubble.hasMetadataShortcutId()
- || (mBubble.getShortcutInfo() != null && Flags.enableBubbleAnything()));
- if (mBubble.isAppBubble()) {
+ || (mBubble.getShortcutInfo() != null
+ && BubbleAnythingFlagHelper.enableCreateAnyBubble()));
+ if (mBubble.getPreparingTransition() != null) {
+ mBubble.getPreparingTransition().surfaceCreated();
+ } else if (mBubble.isAppBubble()) {
Context context =
mContext.createContextAsUser(
mBubble.getUser(), Context.CONTEXT_RESTRICTED);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTransitions.java
new file mode 100644
index 000000000000..29fb1a23017c
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTransitions.java
@@ -0,0 +1,518 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.bubbles;
+
+import static android.app.ActivityTaskManager.INVALID_TASK_ID;
+import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+import static android.view.View.INVISIBLE;
+import static android.view.WindowManager.TRANSIT_CHANGE;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.ActivityManager;
+import android.app.TaskInfo;
+import android.content.Context;
+import android.graphics.Rect;
+import android.os.IBinder;
+import android.util.Slog;
+import android.view.SurfaceControl;
+import android.view.SurfaceView;
+import android.view.View;
+import android.window.TransitionInfo;
+import android.window.TransitionRequestInfo;
+import android.window.WindowContainerToken;
+import android.window.WindowContainerTransaction;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.launcher3.icons.BubbleIconFactory;
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.bubbles.bar.BubbleBarExpandedView;
+import com.android.wm.shell.bubbles.bar.BubbleBarLayerView;
+import com.android.wm.shell.taskview.TaskView;
+import com.android.wm.shell.taskview.TaskViewRepository;
+import com.android.wm.shell.taskview.TaskViewTaskController;
+import com.android.wm.shell.taskview.TaskViewTransitions;
+import com.android.wm.shell.transition.Transitions;
+
+import java.util.concurrent.Executor;
+
+/**
+ * Implements transition coordination for bubble operations.
+ */
+public class BubbleTransitions {
+ private static final String TAG = "BubbleTransitions";
+
+ /**
+ * Multiplier used to convert a view elevation to an "equivalent" shadow-radius. This is the
+ * same multiple used by skia and surface-outsets in WMS.
+ */
+ private static final float ELEVATION_TO_RADIUS = 2;
+
+ @NonNull final Transitions mTransitions;
+ @NonNull final ShellTaskOrganizer mTaskOrganizer;
+ @NonNull final TaskViewRepository mRepository;
+ @NonNull final Executor mMainExecutor;
+ @NonNull final BubbleData mBubbleData;
+ @NonNull final TaskViewTransitions mTaskViewTransitions;
+ @NonNull final Context mContext;
+
+ BubbleTransitions(@NonNull Transitions transitions, @NonNull ShellTaskOrganizer organizer,
+ @NonNull TaskViewRepository repository, @NonNull BubbleData bubbleData,
+ @NonNull TaskViewTransitions taskViewTransitions, Context context) {
+ mTransitions = transitions;
+ mTaskOrganizer = organizer;
+ mRepository = repository;
+ mMainExecutor = transitions.getMainExecutor();
+ mBubbleData = bubbleData;
+ mTaskViewTransitions = taskViewTransitions;
+ mContext = context;
+ }
+
+ /**
+ * Starts a convert-to-bubble transition.
+ *
+ * @see ConvertToBubble
+ */
+ public BubbleTransition startConvertToBubble(Bubble bubble, TaskInfo taskInfo,
+ BubbleExpandedViewManager expandedViewManager, BubbleTaskViewFactory factory,
+ BubblePositioner positioner, BubbleLogger logger, BubbleStackView stackView,
+ BubbleBarLayerView layerView, BubbleIconFactory iconFactory,
+ boolean inflateSync) {
+ ConvertToBubble convert = new ConvertToBubble(bubble, taskInfo, mContext,
+ expandedViewManager, factory, positioner, logger, stackView, layerView, iconFactory,
+ inflateSync);
+ return convert;
+ }
+
+ /**
+ * Starts a convert-from-bubble transition.
+ *
+ * @see ConvertFromBubble
+ */
+ public BubbleTransition startConvertFromBubble(Bubble bubble,
+ TaskInfo taskInfo) {
+ ConvertFromBubble convert = new ConvertFromBubble(bubble, taskInfo);
+ return convert;
+ }
+
+ /**
+ * Plucks the task-surface out of an ancestor view while making the view invisible. This helper
+ * attempts to do this seamlessly (ie. view becomes invisible in sync with task reparent).
+ */
+ private void pluck(SurfaceControl taskLeash, View fromView, SurfaceControl dest,
+ float destX, float destY, float cornerRadius, SurfaceControl.Transaction t,
+ Runnable onPlucked) {
+ SurfaceControl.Transaction pluckT = new SurfaceControl.Transaction();
+ pluckT.reparent(taskLeash, dest);
+ t.reparent(taskLeash, dest);
+ pluckT.setPosition(taskLeash, destX, destY);
+ t.setPosition(taskLeash, destX, destY);
+ pluckT.show(taskLeash);
+ pluckT.setAlpha(taskLeash, 1.f);
+ float shadowRadius = fromView.getElevation() * ELEVATION_TO_RADIUS;
+ pluckT.setShadowRadius(taskLeash, shadowRadius);
+ pluckT.setCornerRadius(taskLeash, cornerRadius);
+ t.setShadowRadius(taskLeash, shadowRadius);
+ t.setCornerRadius(taskLeash, cornerRadius);
+
+ // Need to remove the taskview AFTER applying the startTransaction because it isn't
+ // synchronized.
+ pluckT.addTransactionCommittedListener(mMainExecutor, onPlucked::run);
+ fromView.getViewRootImpl().applyTransactionOnDraw(pluckT);
+ fromView.setVisibility(INVISIBLE);
+ }
+
+ /**
+ * Interface to a bubble-specific transition. Bubble transitions have a multi-step lifecycle
+ * in order to coordinate with the bubble view logic. These steps are communicated on this
+ * interface.
+ */
+ interface BubbleTransition {
+ default void surfaceCreated() {}
+ default void continueExpand() {}
+ void skip();
+ default void continueCollapse() {}
+ }
+
+ /**
+ * BubbleTransition that coordinates the process of a non-bubble task becoming a bubble. The
+ * steps are as follows:
+ *
+ * 1. Start inflating the bubble view
+ * 2. Once inflated (but not-yet visible), tell WM to do the shell-transition.
+ * 3. Transition becomes ready, so notify Launcher
+ * 4. Launcher responds with showExpandedView which calls continueExpand() to make view visible
+ * 5. Surface is created which kicks off actual animation
+ *
+ * So, constructor -> onInflated -> startAnimation -> continueExpand -> surfaceCreated.
+ *
+ * continueExpand and surfaceCreated are set-up to happen in either order, though, to support
+ * UX/timing adjustments.
+ */
+ @VisibleForTesting
+ class ConvertToBubble implements Transitions.TransitionHandler, BubbleTransition {
+ final BubbleBarLayerView mLayerView;
+ Bubble mBubble;
+ IBinder mTransition;
+ Transitions.TransitionFinishCallback mFinishCb;
+ WindowContainerTransaction mFinishWct = null;
+ final Rect mStartBounds = new Rect();
+ SurfaceControl mSnapshot = null;
+ TaskInfo mTaskInfo;
+ boolean mFinishedExpand = false;
+ BubbleViewProvider mPriorBubble = null;
+
+ private SurfaceControl.Transaction mFinishT;
+ private SurfaceControl mTaskLeash;
+
+ ConvertToBubble(Bubble bubble, TaskInfo taskInfo, Context context,
+ BubbleExpandedViewManager expandedViewManager, BubbleTaskViewFactory factory,
+ BubblePositioner positioner, BubbleLogger logger, BubbleStackView stackView,
+ BubbleBarLayerView layerView, BubbleIconFactory iconFactory, boolean inflateSync) {
+ mBubble = bubble;
+ mTaskInfo = taskInfo;
+ mLayerView = layerView;
+ mBubble.setInflateSynchronously(inflateSync);
+ mBubble.setPreparingTransition(this);
+ mBubble.inflate(
+ this::onInflated,
+ context,
+ expandedViewManager,
+ factory,
+ positioner,
+ logger,
+ stackView,
+ layerView,
+ iconFactory,
+ false /* skipInflation */);
+ }
+
+ @VisibleForTesting
+ void onInflated(Bubble b) {
+ if (b != mBubble) {
+ throw new IllegalArgumentException("inflate callback doesn't match bubble");
+ }
+ final Rect launchBounds = new Rect();
+ mLayerView.getExpandedViewRestBounds(launchBounds);
+ WindowContainerTransaction wct = new WindowContainerTransaction();
+ if (mTaskInfo.getWindowingMode() == WINDOWING_MODE_MULTI_WINDOW) {
+ if (mTaskInfo.getParentTaskId() != INVALID_TASK_ID) {
+ wct.reparent(mTaskInfo.token, null, true);
+ }
+ }
+
+ wct.setAlwaysOnTop(mTaskInfo.token, true);
+ wct.setWindowingMode(mTaskInfo.token, WINDOWING_MODE_MULTI_WINDOW);
+ wct.setBounds(mTaskInfo.token, launchBounds);
+
+ final TaskView tv = b.getTaskView();
+ tv.setSurfaceLifecycle(SurfaceView.SURFACE_LIFECYCLE_FOLLOWS_ATTACHMENT);
+ final TaskViewRepository.TaskViewState state = mRepository.byTaskView(
+ tv.getController());
+ if (state != null) {
+ state.mVisible = true;
+ }
+ mTaskViewTransitions.enqueueExternal(tv.getController(), () -> {
+ mTransition = mTransitions.startTransition(TRANSIT_CHANGE, wct, this);
+ return mTransition;
+ });
+ }
+
+ @Override
+ public void skip() {
+ mBubble.setPreparingTransition(null);
+ mFinishCb.onTransitionFinished(mFinishWct);
+ mFinishCb = null;
+ }
+
+ @Override
+ public WindowContainerTransaction handleRequest(@NonNull IBinder transition,
+ @Nullable TransitionRequestInfo request) {
+ return null;
+ }
+
+ @Override
+ public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
+ @NonNull Transitions.TransitionFinishCallback finishCallback) {
+ }
+
+ @Override
+ public void onTransitionConsumed(@NonNull IBinder transition, boolean aborted,
+ @NonNull SurfaceControl.Transaction finishTransaction) {
+ if (!aborted) return;
+ mTransition = null;
+ mTaskViewTransitions.onExternalDone(transition);
+ }
+
+ @Override
+ public boolean startAnimation(@NonNull IBinder transition,
+ @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction,
+ @NonNull Transitions.TransitionFinishCallback finishCallback) {
+ if (mTransition != transition) return false;
+ boolean found = false;
+ for (int i = 0; i < info.getChanges().size(); ++i) {
+ final TransitionInfo.Change chg = info.getChanges().get(i);
+ if (chg.getTaskInfo() == null) continue;
+ if (chg.getMode() != TRANSIT_CHANGE) continue;
+ if (!mTaskInfo.token.equals(chg.getTaskInfo().token)) continue;
+ mStartBounds.set(chg.getStartAbsBounds());
+ // Converting a task into taskview, so treat as "new"
+ mFinishWct = new WindowContainerTransaction();
+ mTaskInfo = chg.getTaskInfo();
+ mFinishT = finishTransaction;
+ mTaskLeash = chg.getLeash();
+ found = true;
+ mSnapshot = chg.getSnapshot();
+ break;
+ }
+ if (!found) {
+ Slog.w(TAG, "Expected a TaskView conversion in this transition but didn't get "
+ + "one, cleaning up the task view");
+ mBubble.getTaskView().getController().setTaskNotFound();
+ mTaskViewTransitions.onExternalDone(transition);
+ return false;
+ }
+ mFinishCb = finishCallback;
+
+ // Now update state (and talk to launcher) in parallel with snapshot stuff
+ mBubbleData.notificationEntryUpdated(mBubble, /* suppressFlyout= */ true,
+ /* showInShade= */ false);
+
+ startTransaction.show(mSnapshot);
+ // Move snapshot to root so that it remains visible while task is moved to taskview
+ startTransaction.reparent(mSnapshot, info.getRoot(0).getLeash());
+ startTransaction.setPosition(mSnapshot,
+ mStartBounds.left - info.getRoot(0).getOffset().x,
+ mStartBounds.top - info.getRoot(0).getOffset().y);
+ startTransaction.setLayer(mSnapshot, Integer.MAX_VALUE);
+ startTransaction.apply();
+
+ mTaskViewTransitions.onExternalDone(transition);
+ return true;
+ }
+
+ @Override
+ public void continueExpand() {
+ mFinishedExpand = true;
+ final boolean animate = mLayerView.canExpandView(mBubble);
+ if (animate) {
+ mPriorBubble = mLayerView.prepareConvertedView(mBubble);
+ }
+ if (mPriorBubble != null) {
+ // TODO: an animation. For now though, just remove it.
+ final BubbleBarExpandedView priorView = mPriorBubble.getBubbleBarExpandedView();
+ mLayerView.removeView(priorView);
+ mPriorBubble = null;
+ }
+ if (!animate || mBubble.getTaskView().getSurfaceControl() != null) {
+ playAnimation(animate);
+ }
+ }
+
+ @Override
+ public void surfaceCreated() {
+ mMainExecutor.execute(() -> {
+ final TaskViewTaskController tvc = mBubble.getTaskView().getController();
+ final TaskViewRepository.TaskViewState state = mRepository.byTaskView(tvc);
+ if (state == null) return;
+ state.mVisible = true;
+ if (mFinishedExpand) {
+ playAnimation(true /* animate */);
+ }
+ });
+ }
+
+ private void playAnimation(boolean animate) {
+ final TaskViewTaskController tv = mBubble.getTaskView().getController();
+ final SurfaceControl.Transaction startT = new SurfaceControl.Transaction();
+ mTaskViewTransitions.prepareOpenAnimation(tv, true /* new */, startT, mFinishT,
+ (ActivityManager.RunningTaskInfo) mTaskInfo, mTaskLeash, mFinishWct);
+
+ if (mFinishWct.isEmpty()) {
+ mFinishWct = null;
+ }
+
+ // Preparation is complete.
+ mBubble.setPreparingTransition(null);
+
+ if (animate) {
+ mLayerView.animateConvert(startT, mStartBounds, mSnapshot, mTaskLeash, () -> {
+ mFinishCb.onTransitionFinished(mFinishWct);
+ mFinishCb = null;
+ });
+ } else {
+ startT.apply();
+ mFinishCb.onTransitionFinished(mFinishWct);
+ mFinishCb = null;
+ }
+ }
+ }
+
+ /**
+ * BubbleTransition that coordinates the setup for moving a task out of a bubble. The actual
+ * animation is owned by the "receiver" of the task; however, because Bubbles uses TaskView,
+ * we need to do some extra coordination work to get the task surface out of the view
+ * "seamlessly".
+ *
+ * The process here looks like:
+ * 1. Send transition to WM for leaving bubbles mode
+ * 2. in startAnimation, set-up a "pluck" operation to pull the task surface out of taskview
+ * 3. Once "plucked", remove the view (calls continueCollapse when surfaces can be cleaned-up)
+ * 4. Then re-dispatch the transition animation so that the "receiver" can animate it.
+ *
+ * So, constructor -> startAnimation -> continueCollapse -> re-dispatch.
+ */
+ @VisibleForTesting
+ class ConvertFromBubble implements Transitions.TransitionHandler, BubbleTransition {
+ @NonNull final Bubble mBubble;
+ IBinder mTransition;
+ TaskInfo mTaskInfo;
+ SurfaceControl mTaskLeash;
+ SurfaceControl mRootLeash;
+
+ ConvertFromBubble(@NonNull Bubble bubble, TaskInfo taskInfo) {
+ mBubble = bubble;
+ mTaskInfo = taskInfo;
+
+ mBubble.setPreparingTransition(this);
+ WindowContainerTransaction wct = new WindowContainerTransaction();
+ WindowContainerToken token = mTaskInfo.getToken();
+ wct.setWindowingMode(token, WINDOWING_MODE_UNDEFINED);
+ wct.setAlwaysOnTop(token, false);
+ mTaskOrganizer.setInterceptBackPressedOnTaskRoot(token, false);
+ mTaskViewTransitions.enqueueExternal(
+ mBubble.getTaskView().getController(),
+ () -> {
+ mTransition = mTransitions.startTransition(TRANSIT_CHANGE, wct, this);
+ return mTransition;
+ });
+ }
+
+ @Override
+ public void skip() {
+ mBubble.setPreparingTransition(null);
+ final TaskViewTaskController tv =
+ mBubble.getTaskView().getController();
+ tv.notifyTaskRemovalStarted(tv.getTaskInfo());
+ mTaskLeash = null;
+ }
+
+ @Override
+ public WindowContainerTransaction handleRequest(@NonNull IBinder transition,
+ @android.annotation.Nullable TransitionRequestInfo request) {
+ return null;
+ }
+
+ @Override
+ public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
+ @NonNull Transitions.TransitionFinishCallback finishCallback) {
+ }
+
+ @Override
+ public void onTransitionConsumed(@NonNull IBinder transition, boolean aborted,
+ @NonNull SurfaceControl.Transaction finishTransaction) {
+ if (!aborted) return;
+ mTransition = null;
+ skip();
+ mTaskViewTransitions.onExternalDone(transition);
+ }
+
+ @Override
+ public boolean startAnimation(@NonNull IBinder transition,
+ @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction,
+ @NonNull Transitions.TransitionFinishCallback finishCallback) {
+ if (mTransition != transition) return false;
+
+ final TaskViewTaskController tv =
+ mBubble.getTaskView().getController();
+ if (tv == null) {
+ mTaskViewTransitions.onExternalDone(transition);
+ return false;
+ }
+
+ TransitionInfo.Change taskChg = null;
+
+ boolean found = false;
+ for (int i = 0; i < info.getChanges().size(); ++i) {
+ final TransitionInfo.Change chg = info.getChanges().get(i);
+ if (chg.getTaskInfo() == null) continue;
+ if (chg.getMode() != TRANSIT_CHANGE) continue;
+ if (!mTaskInfo.token.equals(chg.getTaskInfo().token)) continue;
+ found = true;
+ mRepository.remove(tv);
+ taskChg = chg;
+ break;
+ }
+
+ if (!found) {
+ Slog.w(TAG, "Expected a TaskView conversion in this transition but didn't get "
+ + "one, cleaning up the task view");
+ tv.setTaskNotFound();
+ skip();
+ mTaskViewTransitions.onExternalDone(transition);
+ return false;
+ }
+
+ mTaskLeash = taskChg.getLeash();
+ mRootLeash = info.getRoot(0).getLeash();
+
+ SurfaceControl dest =
+ mBubble.getBubbleBarExpandedView().getViewRootImpl().getSurfaceControl();
+ final Runnable onPlucked = () -> {
+ // Need to remove the taskview AFTER applying the startTransaction because
+ // it isn't synchronized.
+ tv.notifyTaskRemovalStarted(tv.getTaskInfo());
+ // Unset after removeView so it can be used to pick a different animation.
+ mBubble.setPreparingTransition(null);
+ mBubbleData.setExpanded(false /* expanded */);
+ };
+ if (dest != null) {
+ pluck(mTaskLeash, mBubble.getBubbleBarExpandedView(), dest,
+ taskChg.getStartAbsBounds().left - info.getRoot(0).getOffset().x,
+ taskChg.getStartAbsBounds().top - info.getRoot(0).getOffset().y,
+ mBubble.getBubbleBarExpandedView().getCornerRadius(), startTransaction,
+ onPlucked);
+ mBubble.getBubbleBarExpandedView().post(() -> mTransitions.dispatchTransition(
+ mTransition, info, startTransaction, finishTransaction, finishCallback,
+ null));
+ } else {
+ onPlucked.run();
+ mTransitions.dispatchTransition(mTransition, info, startTransaction,
+ finishTransaction, finishCallback, null);
+ }
+
+ mTaskViewTransitions.onExternalDone(transition);
+ return true;
+ }
+
+ @Override
+ public void continueCollapse() {
+ mBubble.cleanupTaskView();
+ if (mTaskLeash == null) return;
+ SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+ t.reparent(mTaskLeash, mRootLeash);
+ t.apply();
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
index 62895fe7c7cc..4297fac0f6a8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
@@ -36,6 +36,7 @@ import android.window.ScreenCapture.ScreenshotHardwareBuffer;
import android.window.ScreenCapture.SynchronousScreenCaptureListener;
import androidx.annotation.IntDef;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.wm.shell.shared.annotations.ExternalThread;
@@ -330,6 +331,18 @@ public interface Bubbles {
* Does not result in a state change.
*/
void animateBubbleBarLocation(BubbleBarLocation location);
+
+ /**
+ * Called when an application icon is being dragged over the Bubble Bar drop zone.
+ * The location of the Bubble Bar is provided as an argument.
+ */
+ void onDragItemOverBubbleBarDragZone(@NonNull BubbleBarLocation location);
+
+ /**
+ * Called when an application icon is being dragged outside the Bubble Bar drop zone.
+ * Always called after {@link #onDragItemOverBubbleBarDragZone(BubbleBarLocation)}
+ */
+ void onItemDraggedOutsideBubbleBarDropZone();
}
/** Listener to find out about stack expansion / collapse events. */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubblesListener.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubblesListener.aidl
index eb907dbb6597..9fc769f562a9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubblesListener.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubblesListener.aidl
@@ -33,4 +33,16 @@ oneway interface IBubblesListener {
* Does not result in a state change.
*/
void animateBubbleBarLocation(in BubbleBarLocation location);
+
+ /**
+ * Called when an application icon is being dragged over the Bubble Bar drop zone.
+ * The location of the Bubble Bar is provided as an argument.
+ */
+ void onDragItemOverBubbleBarDragZone(in BubbleBarLocation location);
+
+ /**
+ * Called when an application icon is being dragged outside the Bubble Bar drop zone.
+ * Always called after {@link #onDragItemOverBubbleBarDragZone(BubbleBarLocation)}
+ */
+ void onItemDraggedOutsideBubbleBarDropZone();
} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java
index de6d1f6c8852..52f20646fb4a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java
@@ -36,17 +36,21 @@ import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
+import android.annotation.NonNull;
import android.content.Context;
import android.graphics.Point;
import android.graphics.Rect;
import android.util.Log;
import android.util.Size;
+import android.view.SurfaceControl;
import android.widget.FrameLayout;
import androidx.annotation.Nullable;
import com.android.app.animation.Interpolators;
import com.android.wm.shell.R;
+import com.android.wm.shell.animation.SizeChangeAnimation;
+import com.android.wm.shell.bubbles.Bubble;
import com.android.wm.shell.bubbles.BubbleOverflow;
import com.android.wm.shell.bubbles.BubblePositioner;
import com.android.wm.shell.bubbles.BubbleViewProvider;
@@ -571,6 +575,49 @@ public class BubbleBarAnimationHelper {
}
/**
+ * Animates converting of a non-bubble task into an expanded bubble view.
+ */
+ public void animateConvert(BubbleViewProvider expandedBubble,
+ @NonNull SurfaceControl.Transaction startT,
+ @NonNull Rect origBounds,
+ @NonNull SurfaceControl snapshot,
+ @NonNull SurfaceControl taskLeash,
+ @Nullable Runnable afterAnimation) {
+ mExpandedBubble = expandedBubble;
+ final BubbleBarExpandedView bbev = getExpandedView();
+ if (bbev == null) {
+ return;
+ }
+
+ bbev.setTaskViewAlpha(1f);
+ SurfaceControl tvSf = ((Bubble) mExpandedBubble).getTaskView().getSurfaceControl();
+
+ final Size size = getExpandedViewSize();
+ Point position = getExpandedViewRestPosition(size);
+
+ final SizeChangeAnimation sca =
+ new SizeChangeAnimation(
+ new Rect(origBounds.left - position.x, origBounds.top - position.y,
+ origBounds.right - position.x, origBounds.bottom - position.y),
+ new Rect(0, 0, size.getWidth(), size.getHeight()));
+ sca.initialize(bbev, taskLeash, snapshot, startT);
+
+ Animator a = sca.buildViewAnimator(bbev, tvSf, snapshot, /* onFinish */ (va) -> {
+ updateExpandedView(bbev);
+ snapshot.release();
+ bbev.setSurfaceZOrderedOnTop(false);
+ bbev.setAnimating(false);
+ if (afterAnimation != null) {
+ afterAnimation.run();
+ }
+ });
+
+ bbev.setSurfaceZOrderedOnTop(true);
+ a.setDuration(EXPANDED_VIEW_ANIMATE_TO_REST_DURATION);
+ a.start();
+ }
+
+ /**
* Cancel current animations
*/
public void cancelAnimations() {
@@ -627,6 +674,13 @@ public class BubbleBarAnimationHelper {
bbev.maybeShowOverflow();
}
+ void getExpandedViewRestBounds(Rect out) {
+ final int width = mPositioner.getExpandedViewWidthForBubbleBar(false /* overflow */);
+ final int height = mPositioner.getExpandedViewHeightForBubbleBar(false /* overflow */);
+ Point position = getExpandedViewRestPosition(new Size(width, height));
+ out.set(position.x, position.y, position.x + width, position.y + height);
+ }
+
private Point getExpandedViewRestPosition(Size size) {
final int padding = mPositioner.getBubbleBarExpandedViewPadding();
Point point = new Point();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
index eaa0bd250fc4..f3f8d6f96a42 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
@@ -28,6 +28,7 @@ import android.graphics.Rect;
import android.graphics.Region;
import android.graphics.drawable.ColorDrawable;
import android.view.Gravity;
+import android.view.SurfaceControl;
import android.view.TouchDelegate;
import android.view.View;
import android.view.ViewTreeObserver;
@@ -174,14 +175,34 @@ public class BubbleBarLayerView extends FrameLayout
/** Shows the expanded view of the provided bubble. */
public void showExpandedView(BubbleViewProvider b) {
- BubbleBarExpandedView expandedView = b.getBubbleBarExpandedView();
- if (expandedView == null) {
- return;
- }
+ if (!canExpandView(b)) return;
+ animateExpand(prepareExpandedView(b));
+ }
+
+ /**
+ * @return whether it's possible to expand {@param b} right now. This is {@code false} if
+ * the bubble has no view or if the bubble is already showing.
+ */
+ public boolean canExpandView(BubbleViewProvider b) {
+ if (b.getBubbleBarExpandedView() == null) return false;
if (mExpandedBubble != null && mIsExpanded && b.getKey().equals(mExpandedBubble.getKey())) {
- // Already showing this bubble, skip animating
- return;
+ // Already showing this bubble so can't expand it.
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Prepares the expanded view of the provided bubble to be shown. This includes removing any
+ * stale content and cancelling any related animations.
+ *
+ * @return previous open bubble if there was one.
+ */
+ private BubbleViewProvider prepareExpandedView(BubbleViewProvider b) {
+ if (!canExpandView(b)) {
+ throw new IllegalStateException("Can't prepare expand. Check canExpandView(b) first.");
}
+ BubbleBarExpandedView expandedView = b.getBubbleBarExpandedView();
BubbleViewProvider previousBubble = null;
if (mExpandedBubble != null && !b.getKey().equals(mExpandedBubble.getKey())) {
if (mIsExpanded && mExpandedBubble.getBubbleBarExpandedView() != null) {
@@ -251,7 +272,20 @@ public class BubbleBarLayerView extends FrameLayout
mIsExpanded = true;
mBubbleController.getSysuiProxy().onStackExpandChanged(true);
+ showScrim(true);
+ return previousBubble;
+ }
+ /**
+ * Performs an animation to open a bubble with content that is not already visible.
+ *
+ * @param previousBubble If non-null, this is a bubble that is already showing before the new
+ * bubble is expanded.
+ */
+ public void animateExpand(BubbleViewProvider previousBubble) {
+ if (!mIsExpanded || mExpandedBubble == null) {
+ throw new IllegalStateException("Can't animateExpand without expnaded state");
+ }
final Runnable afterAnimation = () -> {
if (mExpandedView == null) return;
// Touch delegate for the menu
@@ -274,14 +308,57 @@ public class BubbleBarLayerView extends FrameLayout
} else {
mAnimationHelper.animateExpansion(mExpandedBubble, afterAnimation);
}
+ }
- showScrim(true);
+ /**
+ * Like {@link #prepareExpandedView} but also makes the current expanded bubble visible
+ * immediately so it gets a surface that can be animated. Since the surface may not be ready
+ * yet, this keeps the TaskView alpha=0.
+ */
+ public BubbleViewProvider prepareConvertedView(BubbleViewProvider b) {
+ final BubbleViewProvider prior = prepareExpandedView(b);
+
+ final BubbleBarExpandedView bbev = mExpandedBubble.getBubbleBarExpandedView();
+ if (bbev != null) {
+ updateExpandedView();
+ bbev.setAnimating(true);
+ bbev.setContentVisibility(true);
+ bbev.setSurfaceZOrderedOnTop(true);
+ bbev.setTaskViewAlpha(0.f);
+ bbev.setVisibility(VISIBLE);
+ }
+
+ return prior;
+ }
+
+ /**
+ * Starts and animates a conversion-from transition.
+ *
+ * @param startT A transaction with first-frame work. this *will* be applied here!
+ */
+ public void animateConvert(@NonNull SurfaceControl.Transaction startT,
+ @NonNull Rect startBounds, @NonNull SurfaceControl snapshot, SurfaceControl taskLeash,
+ Runnable animFinish) {
+ if (!mIsExpanded || mExpandedBubble == null) {
+ throw new IllegalStateException("Can't animateExpand without expanded state");
+ }
+ mAnimationHelper.animateConvert(mExpandedBubble, startT, startBounds, snapshot, taskLeash,
+ animFinish);
+ }
+
+ /**
+ * Populates {@param out} with the rest bounds of an expanded bubble.
+ */
+ public void getExpandedViewRestBounds(Rect out) {
+ mAnimationHelper.getExpandedViewRestBounds(out);
}
/** Removes the given {@code bubble}. */
public void removeBubble(Bubble bubble, Runnable endAction) {
+ final boolean inTransition = bubble.getPreparingTransition() != null;
Runnable cleanUp = () -> {
- bubble.cleanupViews();
+ // The transition is already managing the task/wm state.
+ bubble.cleanupViews(!inTransition);
endAction.run();
};
if (mBubbleData.getBubbles().isEmpty()) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/MultiInstanceHelper.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/MultiInstanceHelper.kt
index 4cd2fd04d3cf..ff3e65a247ae 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/MultiInstanceHelper.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/MultiInstanceHelper.kt
@@ -15,16 +15,21 @@
*/
package com.android.wm.shell.common
+import android.annotation.UserIdInt
import android.app.PendingIntent
import android.content.ComponentName
import android.content.Context
import android.content.pm.LauncherApps
import android.content.pm.PackageManager
+import android.content.pm.PackageManager.Property
import android.os.UserHandle
import android.view.WindowManager.PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI
import com.android.internal.protolog.ProtoLog
import com.android.wm.shell.R
import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL
+import com.android.wm.shell.sysui.ShellCommandHandler
+import com.android.wm.shell.sysui.ShellInit
+import java.io.PrintWriter
import java.util.Arrays
/**
@@ -35,12 +40,23 @@ class MultiInstanceHelper @JvmOverloads constructor(
private val packageManager: PackageManager,
private val staticAppsSupportingMultiInstance: Array<String> = context.resources
.getStringArray(R.array.config_appsSupportMultiInstancesSplit),
- private val supportsMultiInstanceProperty: Boolean) {
+ shellInit: ShellInit,
+ private val shellCommandHandler: ShellCommandHandler,
+ private val supportsMultiInstanceProperty: Boolean
+) : ShellCommandHandler.ShellCommandActionHandler {
+
+ init {
+ shellInit.addInitCallback(this::onInit, this)
+ }
+
+ private fun onInit() {
+ shellCommandHandler.addCommandCallback("multi-instance", this, this)
+ }
/**
* Returns whether a specific component desires to be launched in multiple instances.
*/
- fun supportsMultiInstanceSplit(componentName: ComponentName?): Boolean {
+ fun supportsMultiInstanceSplit(componentName: ComponentName?, @UserIdInt userId: Int): Boolean {
if (componentName == null || componentName.packageName == null) {
// TODO(b/262864589): Handle empty component case
return false
@@ -63,8 +79,9 @@ class MultiInstanceHelper @JvmOverloads constructor(
// Check the activity property first
try {
- val activityProp = packageManager.getProperty(
- PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI, componentName)
+ val activityProp = packageManager.getPropertyAsUser(
+ PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI, componentName.packageName,
+ componentName.className, userId)
// If the above call doesn't throw a NameNotFoundException, then the activity property
// should override the application property value
if (activityProp.isBoolean) {
@@ -80,8 +97,9 @@ class MultiInstanceHelper @JvmOverloads constructor(
// Check the application property otherwise
try {
- val appProp = packageManager.getProperty(
- PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI, packageName)
+ val appProp = packageManager.getPropertyAsUser(
+ PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI, packageName, null /* className */,
+ userId)
if (appProp.isBoolean) {
ProtoLog.v(WM_SHELL, "application=%s supports multi-instance", packageName)
return appProp.boolean
@@ -96,6 +114,66 @@ class MultiInstanceHelper @JvmOverloads constructor(
return false
}
+ override fun onShellCommand(args: Array<out String>?, pw: PrintWriter?): Boolean {
+ if (pw == null || args == null || args.isEmpty()) {
+ return false
+ }
+ when (args[0]) {
+ "list" -> return dumpSupportedApps(pw)
+ }
+ return false
+ }
+
+ override fun printShellCommandHelp(pw: PrintWriter, prefix: String) {
+ pw.println("${prefix}list")
+ pw.println("$prefix Lists all the packages that support the multiinstance property")
+ }
+
+ /**
+ * Dumps the static allowlist and list of apps that have the declared property in the manifest.
+ */
+ private fun dumpSupportedApps(pw: PrintWriter): Boolean {
+ pw.println("Static allow list (for all users):")
+ staticAppsSupportingMultiInstance.forEach { pkg ->
+ pw.println(" $pkg")
+ }
+
+ // TODO(b/391693747): Dump this per-user once PM allows us to query properties
+ // for non-calling users
+ val apps = packageManager.queryApplicationProperty(
+ PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI)
+ val activities = packageManager.queryActivityProperty(
+ PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI)
+ val appsWithProperty = (apps + activities)
+ .sortedWith(object : Comparator<Property?> {
+ override fun compare(o1: Property?, o2: Property?): Int {
+ if (o1?.packageName != o2?.packageName) {
+ return o1?.packageName!!.compareTo(o2?.packageName!!)
+ } else {
+ if (o1?.className != null) {
+ return o1.className!!.compareTo(o2?.className!!)
+ } else if (o2?.className != null) {
+ return -o2.className!!.compareTo(o1?.className!!)
+ }
+ return 0
+ }
+ }
+ })
+ if (appsWithProperty.isNotEmpty()) {
+ pw.println("Apps (User ${context.userId}):")
+ appsWithProperty.forEach { prop ->
+ if (prop.isBoolean && prop.boolean) {
+ if (prop.className != null) {
+ pw.println(" ${prop.packageName}/${prop.className}")
+ } else {
+ pw.println(" ${prop.packageName}")
+ }
+ }
+ }
+ }
+ return true
+ }
+
companion object {
/** Returns the component from a PendingIntent */
@JvmStatic
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/UserProfileContexts.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/UserProfileContexts.kt
new file mode 100644
index 000000000000..0577f9e625ca
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/UserProfileContexts.kt
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.common
+
+import android.app.ActivityManager
+import android.content.Context
+import android.content.pm.UserInfo
+import android.os.UserHandle
+import android.os.UserManager
+import android.util.SparseArray
+import com.android.wm.shell.sysui.ShellController
+import com.android.wm.shell.sysui.ShellInit
+import com.android.wm.shell.sysui.UserChangeListener
+
+/** Creates and manages contexts for all the profiles of the current user. */
+class UserProfileContexts(
+ private val baseContext: Context,
+ private val shellController: ShellController,
+ shellInit: ShellInit,
+) {
+ // Contexts for all the profiles of the current user.
+ private val currentProfilesContext = SparseArray<Context>()
+
+ lateinit var userContext: Context
+ private set
+
+ init {
+ shellInit.addInitCallback(this::onInit, this)
+ }
+
+ private fun onInit() {
+ shellController.addUserChangeListener(
+ object : UserChangeListener {
+ override fun onUserChanged(newUserId: Int, userContext: Context) {
+ currentProfilesContext.clear()
+ this@UserProfileContexts.userContext = userContext
+ currentProfilesContext.put(newUserId, userContext)
+ }
+
+ override fun onUserProfilesChanged(profiles: List<UserInfo>) {
+ updateProfilesContexts(profiles)
+ }
+ }
+ )
+ val defaultUserId = ActivityManager.getCurrentUser()
+ val userManager = baseContext.getSystemService(UserManager::class.java)
+ userContext = baseContext.createContextAsUser(UserHandle.of(defaultUserId), /* flags= */ 0)
+ updateProfilesContexts(userManager.getProfiles(defaultUserId))
+ }
+
+ private fun updateProfilesContexts(profiles: List<UserInfo>) {
+ for (profile in profiles) {
+ if (profile.id in currentProfilesContext) continue
+ val profileContext = baseContext.createContextAsUser(profile.userHandle, /* flags= */ 0)
+ currentProfilesContext.put(profile.id, profileContext)
+ }
+ val profilesToRemove = buildList<Int> {
+ for (i in 0..<currentProfilesContext.size()) {
+ val userId = currentProfilesContext.keyAt(i)
+ if (profiles.none { it.id == userId }) {
+ add(userId)
+ }
+ }
+ }
+ profilesToRemove.forEach { currentProfilesContext.remove(it) }
+ }
+
+ operator fun get(userId: Int): Context? = currentProfilesContext.get(userId)
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/AppCompatUtils.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/AppCompatUtils.kt
deleted file mode 100644
index d1dcc9b1d591..000000000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/AppCompatUtils.kt
+++ /dev/null
@@ -1,47 +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:JvmName("AppCompatUtils")
-
-package com.android.wm.shell.compatui
-
-import android.app.ActivityManager.RunningTaskInfo
-import android.content.Context
-import com.android.internal.R
-
-// TODO(b/347289970): Consider replacing with API
-/**
- * If the top activity should be exempt from desktop windowing and forced back to fullscreen.
- * Currently includes all system ui activities and modal dialogs. However if the top activity is not
- * being displayed, regardless of its configuration, we will not exempt it as to remain in the
- * desktop windowing environment.
- */
-fun isTopActivityExemptFromDesktopWindowing(context: Context, task: RunningTaskInfo) =
- (isSystemUiTask(context, task) || isTransparentTask(task))
- && !task.isTopActivityNoDisplay
-
-/**
- * Returns true if all activities in a tasks stack are transparent. If there are no activities will
- * return false.
- */
-fun isTransparentTask(task: RunningTaskInfo): Boolean = task.isActivityStackTransparent
- && task.numActivities > 0
-
-private fun isSystemUiTask(context: Context, task: RunningTaskInfo): Boolean {
- val sysUiPackageName: String =
- context.resources.getString(R.string.config_systemUi)
- return task.baseActivity?.packageName == sysUiPackageName
-}
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 84042591ad1b..e0a829df79ad 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
@@ -43,6 +43,8 @@ import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.WindowManagerShellWrapper;
import com.android.wm.shell.activityembedding.ActivityEmbeddingController;
+import com.android.wm.shell.appzoomout.AppZoomOut;
+import com.android.wm.shell.appzoomout.AppZoomOutController;
import com.android.wm.shell.back.BackAnimation;
import com.android.wm.shell.back.BackAnimationBackground;
import com.android.wm.shell.back.BackAnimationController;
@@ -112,9 +114,8 @@ import com.android.wm.shell.shared.TransactionPool;
import com.android.wm.shell.shared.annotations.ShellAnimationThread;
import com.android.wm.shell.shared.annotations.ShellMainThread;
import com.android.wm.shell.shared.annotations.ShellSplashscreenThread;
+import com.android.wm.shell.shared.desktopmode.DesktopModeCompatPolicy;
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
-import com.android.wm.shell.appzoomout.AppZoomOut;
-import com.android.wm.shell.appzoomout.AppZoomOutController;
import com.android.wm.shell.splitscreen.SplitScreen;
import com.android.wm.shell.splitscreen.SplitScreenController;
import com.android.wm.shell.startingsurface.StartingSurface;
@@ -258,6 +259,12 @@ public abstract class WMShellBaseModule {
@WMSingleton
@Provides
+ static DesktopModeCompatPolicy provideDesktopModeCompatPolicy(Context context) {
+ return new DesktopModeCompatPolicy(context);
+ }
+
+ @WMSingleton
+ @Provides
static Optional<CompatUIHandler> provideCompatUIController(
Context context,
ShellInit shellInit,
@@ -410,9 +417,13 @@ public abstract class WMShellBaseModule {
@WMSingleton
@Provides
- static MultiInstanceHelper provideMultiInstanceHelper(Context context) {
+ static MultiInstanceHelper provideMultiInstanceHelper(
+ Context context,
+ ShellInit shellInit,
+ ShellCommandHandler shellCommandHandler
+ ) {
return new MultiInstanceHelper(context, context.getPackageManager(),
- Flags.supportsMultiInstanceSystemUi());
+ shellInit, shellCommandHandler, Flags.supportsMultiInstanceSystemUi());
}
//
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index fdfaa90ac8b9..6657c9e4b9a9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -16,6 +16,7 @@
package com.android.wm.shell.dagger;
+import static android.window.DesktopModeFlags.ENABLE_DESKTOP_SYSTEM_DIALOGS_TRANSITIONS;
import static android.window.DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_ENTER_TRANSITIONS;
import static android.window.DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_ENTER_TRANSITIONS_BUGFIX;
import static android.window.DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_MODALS_POLICY;
@@ -37,6 +38,7 @@ import android.os.UserManager;
import android.view.Choreographer;
import android.view.IWindowManager;
import android.view.WindowManager;
+import android.window.DesktopModeFlags;
import androidx.annotation.OptIn;
@@ -70,6 +72,7 @@ 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.TaskStackListenerImpl;
+import com.android.wm.shell.common.UserProfileContexts;
import com.android.wm.shell.common.split.SplitState;
import com.android.wm.shell.compatui.letterbox.LetterboxCommandHandler;
import com.android.wm.shell.compatui.letterbox.LetterboxTransitionObserver;
@@ -108,6 +111,8 @@ import com.android.wm.shell.desktopmode.education.AppToWebEducationController;
import com.android.wm.shell.desktopmode.education.AppToWebEducationFilter;
import com.android.wm.shell.desktopmode.education.data.AppHandleEducationDatastoreRepository;
import com.android.wm.shell.desktopmode.education.data.AppToWebEducationDatastoreRepository;
+import com.android.wm.shell.desktopmode.multidesks.DesksOrganizer;
+import com.android.wm.shell.desktopmode.multidesks.RootTaskDesksOrganizer;
import com.android.wm.shell.desktopmode.persistence.DesktopPersistentRepository;
import com.android.wm.shell.desktopmode.persistence.DesktopRepositoryInitializer;
import com.android.wm.shell.desktopmode.persistence.DesktopRepositoryInitializerImpl;
@@ -129,6 +134,7 @@ import com.android.wm.shell.shared.TransactionPool;
import com.android.wm.shell.shared.annotations.ShellAnimationThread;
import com.android.wm.shell.shared.annotations.ShellBackgroundThread;
import com.android.wm.shell.shared.annotations.ShellMainThread;
+import com.android.wm.shell.shared.desktopmode.DesktopModeCompatPolicy;
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
import com.android.wm.shell.splitscreen.SplitScreenController;
import com.android.wm.shell.sysui.ShellCommandHandler;
@@ -395,6 +401,7 @@ public abstract class WMShellModule {
ShellTaskOrganizer shellTaskOrganizer,
Optional<DesktopUserRepositories> desktopUserRepositories,
Optional<DesktopTasksController> desktopTasksController,
+ DesktopModeLoggerTransitionObserver desktopModeLoggerTransitionObserver,
LaunchAdjacentController launchAdjacentController,
WindowDecorViewModel windowDecorViewModel,
Optional<TaskChangeListener> taskChangeListener) {
@@ -407,6 +414,7 @@ public abstract class WMShellModule {
shellTaskOrganizer,
desktopUserRepositories,
desktopTasksController,
+ desktopModeLoggerTransitionObserver,
launchAdjacentController,
windowDecorViewModel,
taskChangeListener);
@@ -703,6 +711,16 @@ public abstract class WMShellModule {
@WMSingleton
@Provides
+ static DesksOrganizer provideDesksOrganizer(
+ @NonNull ShellInit shellInit,
+ @NonNull ShellCommandHandler shellCommandHandler,
+ @NonNull ShellTaskOrganizer shellTaskOrganizer
+ ) {
+ return new RootTaskDesksOrganizer(shellInit, shellCommandHandler, shellTaskOrganizer);
+ }
+
+ @WMSingleton
+ @Provides
@DynamicOverride
static DesktopTasksController provideDesktopTasksController(
Context context,
@@ -741,7 +759,10 @@ public abstract class WMShellModule {
DesktopTilingDecorViewModel desktopTilingDecorViewModel,
DesktopWallpaperActivityTokenProvider desktopWallpaperActivityTokenProvider,
Optional<BubbleController> bubbleController,
- OverviewToDesktopTransitionObserver overviewToDesktopTransitionObserver) {
+ OverviewToDesktopTransitionObserver overviewToDesktopTransitionObserver,
+ DesksOrganizer desksOrganizer,
+ UserProfileContexts userProfileContexts,
+ DesktopModeCompatPolicy desktopModeCompatPolicy) {
return new DesktopTasksController(
context,
shellInit,
@@ -775,7 +796,10 @@ public abstract class WMShellModule {
desktopTilingDecorViewModel,
desktopWallpaperActivityTokenProvider,
bubbleController,
- overviewToDesktopTransitionObserver);
+ overviewToDesktopTransitionObserver,
+ desksOrganizer,
+ userProfileContexts,
+ desktopModeCompatPolicy);
}
@WMSingleton
@@ -792,7 +816,9 @@ public abstract class WMShellModule {
ReturnToDragStartAnimator returnToDragStartAnimator,
@DynamicOverride DesktopUserRepositories desktopUserRepositories,
DesktopModeEventLogger desktopModeEventLogger,
- WindowDecorTaskResourceLoader windowDecorTaskResourceLoader) {
+ WindowDecorTaskResourceLoader windowDecorTaskResourceLoader,
+ FocusTransitionObserver focusTransitionObserver,
+ @ShellMainThread ShellExecutor mainExecutor) {
return new DesktopTilingDecorViewModel(
context,
mainDispatcher,
@@ -806,7 +832,9 @@ public abstract class WMShellModule {
returnToDragStartAnimator,
desktopUserRepositories,
desktopModeEventLogger,
- windowDecorTaskResourceLoader
+ windowDecorTaskResourceLoader,
+ focusTransitionObserver,
+ mainExecutor
);
}
@@ -907,7 +935,7 @@ public abstract class WMShellModule {
if (DesktopModeStatus.canEnterDesktopMode(context) && useKeyGestureEventHandler()
&& manageKeyGestures()
&& (Flags.enableMoveToNextDisplayShortcut()
- || Flags.enableTaskResizingKeyboardShortcuts())) {
+ || DesktopModeFlags.ENABLE_TASK_RESIZING_KEYBOARD_SHORTCUTS.isTrue())) {
return Optional.of(new DesktopModeKeyGestureHandler(context,
desktopModeWindowDecorViewModel, desktopTasksController,
inputManager, shellTaskOrganizer, focusTransitionObserver,
@@ -953,7 +981,8 @@ public abstract class WMShellModule {
DesktopModeEventLogger desktopModeEventLogger,
DesktopModeUiEventLogger desktopModeUiEventLogger,
WindowDecorTaskResourceLoader taskResourceLoader,
- RecentsTransitionHandler recentsTransitionHandler
+ RecentsTransitionHandler recentsTransitionHandler,
+ DesktopModeCompatPolicy desktopModeCompatPolicy
) {
if (!DesktopModeStatus.canEnterDesktopModeOrShowAppHandle(context)) {
return Optional.empty();
@@ -969,7 +998,7 @@ public abstract class WMShellModule {
desktopTasksLimiter, appHandleEducationController, appToWebEducationController,
windowDecorCaptionHandleRepository, activityOrientationChangeHandler,
focusTransitionObserver, desktopModeEventLogger, desktopModeUiEventLogger,
- taskResourceLoader, recentsTransitionHandler));
+ taskResourceLoader, recentsTransitionHandler, desktopModeCompatPolicy));
}
@WMSingleton
@@ -977,9 +1006,10 @@ public abstract class WMShellModule {
static WindowDecorTaskResourceLoader provideWindowDecorTaskResourceLoader(
@NonNull Context context, @NonNull ShellInit shellInit,
@NonNull ShellController shellController,
- @NonNull ShellCommandHandler shellCommandHandler) {
+ @NonNull ShellCommandHandler shellCommandHandler,
+ @NonNull UserProfileContexts userProfileContexts) {
return new WindowDecorTaskResourceLoader(context, shellInit, shellController,
- shellCommandHandler);
+ shellCommandHandler, userProfileContexts);
}
@WMSingleton
@@ -990,16 +1020,17 @@ public abstract class WMShellModule {
@ShellAnimationThread ShellExecutor animExecutor,
ShellInit shellInit,
Transitions transitions,
- @DynamicOverride DesktopUserRepositories desktopUserRepositories) {
+ @DynamicOverride DesktopUserRepositories desktopUserRepositories,
+ DesktopModeCompatPolicy desktopModeCompatPolicy) {
if (!DesktopModeStatus.canEnterDesktopMode(context)
|| !ENABLE_DESKTOP_WINDOWING_MODALS_POLICY.isTrue()
- || !Flags.enableDesktopSystemDialogsTransitions()) {
+ || !ENABLE_DESKTOP_SYSTEM_DIALOGS_TRANSITIONS.isTrue()) {
return Optional.empty();
}
return Optional.of(
new SystemModalsTransitionHandler(
context, mainExecutor, animExecutor, shellInit, transitions,
- desktopUserRepositories));
+ desktopUserRepositories, desktopModeCompatPolicy));
}
@WMSingleton
@@ -1183,10 +1214,12 @@ public abstract class WMShellModule {
Transitions transitions,
DisplayController displayController,
RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
- IWindowManager windowManager
+ IWindowManager windowManager,
+ Optional<DesktopUserRepositories> desktopUserRepositories,
+ Optional<DesktopTasksController> desktopTasksController,
+ ShellTaskOrganizer shellTaskOrganizer
) {
- if (!DesktopModeStatus.canEnterDesktopMode(context)
- || !Flags.enableDisplayWindowingModeSwitching()) {
+ if (!DesktopModeStatus.canEnterDesktopMode(context)) {
return Optional.empty();
}
return Optional.of(
@@ -1196,7 +1229,10 @@ public abstract class WMShellModule {
transitions,
displayController,
rootTaskDisplayAreaOrganizer,
- windowManager));
+ windowManager,
+ desktopUserRepositories.get(),
+ desktopTasksController.get(),
+ shellTaskOrganizer));
}
@WMSingleton
@@ -1406,4 +1442,14 @@ public abstract class WMShellModule {
Transitions transitions, ShellInit shellInit) {
return new OverviewToDesktopTransitionObserver(transitions, shellInit);
}
+
+ @WMSingleton
+ @Provides
+ static UserProfileContexts provideUserProfilesContexts(
+ Context context,
+ ShellController shellController,
+ ShellInit shellInit) {
+ return new UserProfileContexts(context, shellController, shellInit);
+ }
+
}
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 793bdf0b5614..413300612f7d 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
@@ -159,11 +159,12 @@ public abstract class Pip2Module {
PipUiEventLogger pipUiEventLogger,
PipTaskListener pipTaskListener,
@NonNull PipTransitionState pipTransitionState,
+ @NonNull PipDisplayLayoutState pipDisplayLayoutState,
@ShellMainThread ShellExecutor mainExecutor,
@ShellMainThread Handler mainHandler) {
return new PhonePipMenuController(context, pipBoundsState, pipMediaController,
- systemWindows, pipUiEventLogger, pipTaskListener, pipTransitionState, mainExecutor,
- mainHandler);
+ systemWindows, pipUiEventLogger, pipTaskListener, pipTransitionState,
+ pipDisplayLayoutState, mainExecutor, mainHandler);
}
@@ -178,6 +179,8 @@ public abstract class Pip2Module {
@NonNull PipTransitionState pipTransitionState,
@NonNull PipScheduler pipScheduler,
@NonNull SizeSpecSource sizeSpecSource,
+ @NonNull PipDisplayLayoutState pipDisplayLayoutState,
+ DisplayController displayController,
PipMotionHelper pipMotionHelper,
FloatingContentCoordinator floatingContentCoordinator,
PipUiEventLogger pipUiEventLogger,
@@ -185,8 +188,9 @@ public abstract class Pip2Module {
Optional<PipPerfHintController> pipPerfHintControllerOptional) {
return new PipTouchHandler(context, shellInit, shellCommandHandler, menuPhoneController,
pipBoundsAlgorithm, pipBoundsState, pipTransitionState, pipScheduler,
- sizeSpecSource, pipMotionHelper, floatingContentCoordinator, pipUiEventLogger,
- mainExecutor, pipPerfHintControllerOptional);
+ sizeSpecSource, pipDisplayLayoutState, displayController, pipMotionHelper,
+ floatingContentCoordinator, pipUiEventLogger, mainExecutor,
+ pipPerfHintControllerOptional);
}
@WMSingleton
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandler.kt
index 43e8d2a30930..6f455df6cfec 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandler.kt
@@ -16,7 +16,10 @@
package com.android.wm.shell.desktopmode
+import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD
import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
+import android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED
+import android.app.WindowConfiguration.windowingModeToString
import android.content.Context
import android.provider.Settings
import android.provider.Settings.Global.DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS
@@ -24,9 +27,15 @@ import android.view.Display.DEFAULT_DISPLAY
import android.view.IWindowManager
import android.view.WindowManager.TRANSIT_CHANGE
import android.window.WindowContainerTransaction
+import com.android.internal.protolog.ProtoLog
+import com.android.window.flags.Flags
import com.android.wm.shell.RootTaskDisplayAreaOrganizer
+import com.android.wm.shell.ShellTaskOrganizer
import com.android.wm.shell.common.DisplayController
import com.android.wm.shell.common.DisplayController.OnDisplaysChangedListener
+import com.android.wm.shell.desktopmode.multidesks.OnDeskRemovedListener
+import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
+import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
import com.android.wm.shell.sysui.ShellInit
import com.android.wm.shell.transition.Transitions
@@ -38,7 +47,13 @@ class DesktopDisplayEventHandler(
private val displayController: DisplayController,
private val rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer,
private val windowManager: IWindowManager,
-) : OnDisplaysChangedListener {
+ private val desktopUserRepositories: DesktopUserRepositories,
+ private val desktopTasksController: DesktopTasksController,
+ private val shellTaskOrganizer: ShellTaskOrganizer,
+) : OnDisplaysChangedListener, OnDeskRemovedListener {
+
+ private val desktopRepository: DesktopRepository
+ get() = desktopUserRepositories.current
init {
shellInit.addInitCallback({ onInit() }, this)
@@ -46,23 +61,48 @@ class DesktopDisplayEventHandler(
private fun onInit() {
displayController.addDisplayWindowListener(this)
+
+ if (Flags.enableMultipleDesktopsBackend()) {
+ desktopTasksController.onDeskRemovedListener = this
+ }
}
override fun onDisplayAdded(displayId: Int) {
- if (displayId == DEFAULT_DISPLAY) {
+ if (displayId != DEFAULT_DISPLAY) {
+ refreshDisplayWindowingMode()
+ }
+
+ if (!supportsDesks(displayId)) {
+ logV("Display #$displayId does not support desks")
return
}
- refreshDisplayWindowingMode()
+ logV("Creating new desk in new display#$displayId")
+ // TODO: b/362720497 - when SystemUI crashes with a freeform task open for any reason, the
+ // task is recreated and received in [FreeformTaskListener] before this display callback
+ // is invoked, which results in the repository trying to add the task to a desk before the
+ // desk has been recreated here, which may result in a crash-loop if the repository is
+ // checking that the desk exists before adding a task to it. See b/391984373.
+ desktopTasksController.createDesk(displayId)
}
override fun onDisplayRemoved(displayId: Int) {
- if (displayId == DEFAULT_DISPLAY) {
- return
+ if (displayId != DEFAULT_DISPLAY) {
+ refreshDisplayWindowingMode()
+ }
+
+ // TODO: b/362720497 - move desks in closing display to the remaining desk.
+ }
+
+ override fun onDeskRemoved(lastDisplayId: Int, deskId: Int) {
+ val remainingDesks = desktopRepository.getNumberOfDesks(lastDisplayId)
+ if (remainingDesks == 0) {
+ logV("All desks removed from display#$lastDisplayId, creating empty desk")
+ desktopTasksController.createDesk(lastDisplayId)
}
- refreshDisplayWindowingMode()
}
private fun refreshDisplayWindowingMode() {
+ if (!Flags.enableDisplayWindowingModeSwitching()) return
// TODO: b/375319538 - Replace the check with a DisplayManager API once it's available.
val isExtendedDisplayEnabled =
0 !=
@@ -89,13 +129,46 @@ class DesktopDisplayEventHandler(
}
val tdaInfo = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)
requireNotNull(tdaInfo) { "DisplayAreaInfo of DEFAULT_DISPLAY must be non-null." }
- if (tdaInfo.configuration.windowConfiguration.windowingMode == targetDisplayWindowingMode) {
+ val currentDisplayWindowingMode = tdaInfo.configuration.windowConfiguration.windowingMode
+ if (currentDisplayWindowingMode == targetDisplayWindowingMode) {
// Already in the target mode.
return
}
+ logV(
+ "As an external display is connected, changing default display's windowing mode from" +
+ " ${windowingModeToString(currentDisplayWindowingMode)}" +
+ " to ${windowingModeToString(targetDisplayWindowingMode)}"
+ )
+
val wct = WindowContainerTransaction()
wct.setWindowingMode(tdaInfo.token, targetDisplayWindowingMode)
+ shellTaskOrganizer
+ .getRunningTasks(DEFAULT_DISPLAY)
+ .filter { it.activityType == ACTIVITY_TYPE_STANDARD }
+ .forEach {
+ // TODO: b/391965153 - Reconsider the logic under multi-desk window hierarchy
+ when (it.windowingMode) {
+ currentDisplayWindowingMode -> {
+ wct.setWindowingMode(it.token, currentDisplayWindowingMode)
+ }
+ targetDisplayWindowingMode -> {
+ wct.setWindowingMode(it.token, WINDOWING_MODE_UNDEFINED)
+ }
+ }
+ }
transitions.startTransition(TRANSIT_CHANGE, wct, /* handler= */ null)
}
+
+ // TODO: b/362720497 - connected/projected display considerations.
+ private fun supportsDesks(displayId: Int): Boolean =
+ DesktopModeStatus.canEnterDesktopMode(context)
+
+ private fun logV(msg: String, vararg arguments: Any?) {
+ ProtoLog.v(WM_SHELL_DESKTOP_MODE, "%s: $msg", TAG, *arguments)
+ }
+
+ companion object {
+ private const val TAG = "DesktopDisplayEventHandler"
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt
index 7897c0aa35bc..f5a95a670036 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt
@@ -24,10 +24,10 @@ import android.view.MotionEvent
import android.view.MotionEvent.TOOL_TYPE_FINGER
import android.view.MotionEvent.TOOL_TYPE_MOUSE
import android.view.MotionEvent.TOOL_TYPE_STYLUS
+import android.window.DesktopModeFlags
import com.android.internal.annotations.VisibleForTesting
import com.android.internal.protolog.ProtoLog
import com.android.internal.util.FrameworkStatsLog
-import com.android.window.flags.Flags
import com.android.wm.shell.EventLogTags
import com.android.wm.shell.common.DisplayController
import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
@@ -185,7 +185,7 @@ class DesktopModeEventLogger {
displayController: DisplayController? = null,
displayLayoutSize: Size? = null,
) {
- if (!Flags.enableResizingMetrics()) return
+ if (!DesktopModeFlags.ENABLE_RESIZING_METRICS.isTrue) return
val sessionId = currentSessionId.get()
if (sessionId == NO_SESSION_ID) {
@@ -232,7 +232,7 @@ class DesktopModeEventLogger {
displayController: DisplayController? = null,
displayLayoutSize: Size? = null,
) {
- if (!Flags.enableResizingMetrics()) return
+ if (!DesktopModeFlags.ENABLE_RESIZING_METRICS.isTrue) return
val sessionId = currentSessionId.get()
if (sessionId == NO_SESSION_ID) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandler.kt
index 9334898fdb93..5269318943d9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandler.kt
@@ -23,10 +23,10 @@ import android.hardware.input.InputManager
import android.hardware.input.InputManager.KeyGestureEventHandler
import android.hardware.input.KeyGestureEvent
import android.os.IBinder
+import android.window.DesktopModeFlags
import com.android.hardware.input.Flags.manageKeyGestures
import com.android.internal.protolog.ProtoLog
import com.android.window.flags.Flags.enableMoveToNextDisplayShortcut
-import com.android.window.flags.Flags.enableTaskResizingKeyboardShortcuts
import com.android.wm.shell.ShellTaskOrganizer
import com.android.wm.shell.common.DisplayController
import com.android.wm.shell.common.ShellExecutor
@@ -144,7 +144,8 @@ class DesktopModeKeyGestureHandler(
KeyGestureEvent.KEY_GESTURE_TYPE_SNAP_RIGHT_FREEFORM_WINDOW,
KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_MAXIMIZE_FREEFORM_WINDOW,
KeyGestureEvent.KEY_GESTURE_TYPE_MINIMIZE_FREEFORM_WINDOW ->
- enableTaskResizingKeyboardShortcuts() && manageKeyGestures()
+ DesktopModeFlags.ENABLE_TASK_RESIZING_KEYBOARD_SHORTCUTS.isTrue &&
+ manageKeyGestures()
else -> false
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt
index a43358603bc3..3b051694ae81 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt
@@ -167,6 +167,29 @@ class DesktopModeLoggerTransitionObserver(
override fun onTransitionFinished(transition: IBinder, aborted: Boolean) {}
+ fun onTaskVanished(taskInfo: RunningTaskInfo) {
+ // At this point the task should have been cleared up due to transition. If it's not yet
+ // cleared up, it might be one of the edge cases where transitions don't give the correct
+ // signal.
+ if (visibleFreeformTaskInfos.containsKey(taskInfo.taskId)) {
+ val postTransitionFreeformTasks: SparseArray<TaskInfo> = SparseArray()
+ postTransitionFreeformTasks.putAll(visibleFreeformTaskInfos)
+ postTransitionFreeformTasks.remove(taskInfo.taskId)
+ ProtoLog.v(
+ WM_SHELL_DESKTOP_MODE,
+ "DesktopModeLogger: processing tasks after task vanished %s",
+ postTransitionFreeformTasks.size(),
+ )
+ identifyLogEventAndUpdateState(
+ transition = null,
+ transitionInfo = null,
+ preTransitionVisibleFreeformTasks = visibleFreeformTaskInfos,
+ postTransitionVisibleFreeformTasks = postTransitionFreeformTasks,
+ newFocusedFreeformTask = null,
+ )
+ }
+ }
+
// Returns null if there was no change in focused task
private fun getNewFocusedFreeformTask(info: TransitionInfo): TaskInfo? {
val freeformWindowChanges =
@@ -253,8 +276,8 @@ class DesktopModeLoggerTransitionObserver(
* state and update it
*/
private fun identifyLogEventAndUpdateState(
- transition: IBinder,
- transitionInfo: TransitionInfo,
+ transition: IBinder?,
+ transitionInfo: TransitionInfo?,
preTransitionVisibleFreeformTasks: SparseArray<TaskInfo>,
postTransitionVisibleFreeformTasks: SparseArray<TaskInfo>,
newFocusedFreeformTask: TaskInfo?,
@@ -310,8 +333,8 @@ class DesktopModeLoggerTransitionObserver(
/** Compare the old and new state of taskInfos and identify and log the changes */
private fun identifyAndLogTaskUpdates(
- transition: IBinder,
- transitionInfo: TransitionInfo,
+ transition: IBinder?,
+ transitionInfo: TransitionInfo?,
preTransitionVisibleFreeformTasks: SparseArray<TaskInfo>,
postTransitionVisibleFreeformTasks: SparseArray<TaskInfo>,
newFocusedFreeformTask: TaskInfo?,
@@ -384,22 +407,24 @@ class DesktopModeLoggerTransitionObserver(
}
private fun getMinimizeReason(
- transition: IBinder,
- transitionInfo: TransitionInfo,
+ transition: IBinder?,
+ transitionInfo: TransitionInfo?,
taskInfo: TaskInfo,
): MinimizeReason? {
- if (transitionInfo.type == Transitions.TRANSIT_MINIMIZE) {
+ if (transitionInfo?.type == Transitions.TRANSIT_MINIMIZE) {
return MinimizeReason.MINIMIZE_BUTTON
}
- val minimizingTask = desktopTasksLimiter.getOrNull()?.getMinimizingTask(transition)
+ val minimizingTask =
+ transition?.let { desktopTasksLimiter.getOrNull()?.getMinimizingTask(transition) }
if (minimizingTask?.taskId == taskInfo.taskId) {
return minimizingTask.minimizeReason
}
return null
}
- private fun getUnminimizeReason(transition: IBinder, taskInfo: TaskInfo): UnminimizeReason? {
- val unminimizingTask = desktopTasksLimiter.getOrNull()?.getUnminimizingTask(transition)
+ private fun getUnminimizeReason(transition: IBinder?, taskInfo: TaskInfo): UnminimizeReason? {
+ val unminimizingTask =
+ transition?.let { desktopTasksLimiter.getOrNull()?.getUnminimizingTask(transition) }
if (unminimizingTask?.taskId == taskInfo.taskId) {
return unminimizingTask.unminimizeReason
}
@@ -441,24 +466,24 @@ class DesktopModeLoggerTransitionObserver(
}
/** Get [EnterReason] for this session enter */
- private fun getEnterReason(transitionInfo: TransitionInfo): EnterReason {
+ private fun getEnterReason(transitionInfo: TransitionInfo?): EnterReason {
val enterReason =
when {
- transitionInfo.type == WindowManager.TRANSIT_WAKE
+ transitionInfo?.type == WindowManager.TRANSIT_WAKE
// If there is a screen lock, desktop window entry is after dismissing keyguard
||
- (transitionInfo.type == WindowManager.TRANSIT_TO_BACK &&
+ (transitionInfo?.type == WindowManager.TRANSIT_TO_BACK &&
wasPreviousTransitionExitByScreenOff) -> EnterReason.SCREEN_ON
- transitionInfo.type == TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP ->
+ transitionInfo?.type == TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP ->
EnterReason.APP_HANDLE_DRAG
- transitionInfo.type == TRANSIT_ENTER_DESKTOP_FROM_APP_HANDLE_MENU_BUTTON ->
+ transitionInfo?.type == TRANSIT_ENTER_DESKTOP_FROM_APP_HANDLE_MENU_BUTTON ->
EnterReason.APP_HANDLE_MENU_BUTTON
- transitionInfo.type == TRANSIT_ENTER_DESKTOP_FROM_APP_FROM_OVERVIEW ->
+ transitionInfo?.type == TRANSIT_ENTER_DESKTOP_FROM_APP_FROM_OVERVIEW ->
EnterReason.APP_FROM_OVERVIEW
- transitionInfo.type == TRANSIT_ENTER_DESKTOP_FROM_KEYBOARD_SHORTCUT ->
+ transitionInfo?.type == TRANSIT_ENTER_DESKTOP_FROM_KEYBOARD_SHORTCUT ->
EnterReason.KEYBOARD_SHORTCUT_ENTER
// NOTE: the below condition also applies for EnterReason quickswitch
- transitionInfo.type == WindowManager.TRANSIT_TO_FRONT -> EnterReason.OVERVIEW
+ transitionInfo?.type == WindowManager.TRANSIT_TO_FRONT -> EnterReason.OVERVIEW
// Enter desktop mode from cancelled recents has no transition. Enter is detected on
// the
// next transition involving freeform windows.
@@ -469,12 +494,13 @@ class DesktopModeLoggerTransitionObserver(
// after
// a cancelled recents.
wasPreviousTransitionExitToOverview -> EnterReason.OVERVIEW
- transitionInfo.type == WindowManager.TRANSIT_OPEN -> EnterReason.APP_FREEFORM_INTENT
+ transitionInfo?.type == WindowManager.TRANSIT_OPEN ->
+ EnterReason.APP_FREEFORM_INTENT
else -> {
ProtoLog.w(
WM_SHELL_DESKTOP_MODE,
"Unknown enter reason for transition type: %s",
- transitionInfo.type,
+ transitionInfo?.type,
)
EnterReason.UNKNOWN_ENTER
}
@@ -484,30 +510,31 @@ class DesktopModeLoggerTransitionObserver(
}
/** Get [ExitReason] for this session exit */
- private fun getExitReason(transitionInfo: TransitionInfo): ExitReason =
+ private fun getExitReason(transitionInfo: TransitionInfo?): ExitReason =
when {
- transitionInfo.type == WindowManager.TRANSIT_SLEEP -> {
+ transitionInfo?.type == WindowManager.TRANSIT_SLEEP -> {
wasPreviousTransitionExitByScreenOff = true
ExitReason.SCREEN_OFF
}
// TODO(b/384490301): differentiate back gesture / button exit from clicking the close
// button located in the window top corner.
- transitionInfo.type == WindowManager.TRANSIT_TO_BACK -> ExitReason.TASK_MOVED_TO_BACK
- transitionInfo.type == WindowManager.TRANSIT_CLOSE -> ExitReason.TASK_FINISHED
- transitionInfo.type == TRANSIT_EXIT_DESKTOP_MODE_TASK_DRAG -> ExitReason.DRAG_TO_EXIT
- transitionInfo.type == TRANSIT_EXIT_DESKTOP_MODE_HANDLE_MENU_BUTTON ->
+ transitionInfo?.type == WindowManager.TRANSIT_TO_BACK -> ExitReason.TASK_MOVED_TO_BACK
+ transitionInfo?.type == WindowManager.TRANSIT_CLOSE -> ExitReason.TASK_FINISHED
+ transitionInfo?.type == TRANSIT_EXIT_DESKTOP_MODE_TASK_DRAG -> ExitReason.DRAG_TO_EXIT
+ transitionInfo?.type == TRANSIT_EXIT_DESKTOP_MODE_HANDLE_MENU_BUTTON ->
ExitReason.APP_HANDLE_MENU_BUTTON_EXIT
- transitionInfo.type == TRANSIT_EXIT_DESKTOP_MODE_KEYBOARD_SHORTCUT ->
+ transitionInfo?.type == TRANSIT_EXIT_DESKTOP_MODE_KEYBOARD_SHORTCUT ->
ExitReason.KEYBOARD_SHORTCUT_EXIT
- transitionInfo.isExitToRecentsTransition() -> ExitReason.RETURN_HOME_OR_OVERVIEW
- transitionInfo.type == Transitions.TRANSIT_MINIMIZE -> ExitReason.TASK_MINIMIZED
+ transitionInfo?.isExitToRecentsTransition() == true ->
+ ExitReason.RETURN_HOME_OR_OVERVIEW
+ transitionInfo?.type == Transitions.TRANSIT_MINIMIZE -> ExitReason.TASK_MINIMIZED
else -> {
ProtoLog.w(
WM_SHELL_DESKTOP_MODE,
"Unknown exit reason for transition type: %s",
- transitionInfo.type,
+ transitionInfo?.type,
)
ExitReason.UNKNOWN_EXIT
}
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 9b9988457808..164d04bbde65 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
@@ -110,8 +110,8 @@ class DesktopModeShellCommandHandler(private val controller: DesktopTasksControl
pw.println("Error: display id should be an integer")
return false
}
- pw.println("Not implemented.")
- return false
+ controller.createDesk(displayId)
+ return true
}
private fun runActivateDesk(args: Array<String>, pw: PrintWriter): Boolean {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt
index fa696682de28..4ff1a5f1be31 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt
@@ -171,6 +171,9 @@ class DesktopRepository(
/** Returns a list of all [Desk]s in the repository. */
private fun desksSequence(): Sequence<Desk> = desktopData.desksSequence()
+ /** Returns the number of desks in the given display. */
+ fun getNumberOfDesks(displayId: Int) = desktopData.getNumberOfDesks(displayId)
+
/** Adds [regionListener] to inform about changes to exclusion regions for all Desktop tasks. */
fun setExclusionRegionListener(regionListener: Consumer<Region>, executor: Executor) {
desktopGestureExclusionListener = regionListener
@@ -201,11 +204,11 @@ class DesktopRepository(
/** Adds the given desk under the given display. */
fun addDesk(displayId: Int, deskId: Int) {
- desktopData.getOrCreateDesk(displayId, deskId)
+ desktopData.createDesk(displayId, deskId)
}
/** Returns the default desk in the given display. */
- fun getDefaultDesk(displayId: Int): Int? = desktopData.getDefaultDesk(displayId)?.deskId
+ private fun getDefaultDesk(displayId: Int): Desk? = desktopData.getDefaultDesk(displayId)
/** Sets the given desk as the active one in the given display. */
fun setActiveDesk(displayId: Int, deskId: Int) {
@@ -229,15 +232,14 @@ class DesktopRepository(
* TODO: b/389960283 - add explicit [deskId] argument.
*/
private fun addActiveTask(displayId: Int, taskId: Int) {
- val activeDeskId =
- desktopData.getActiveDesk(displayId)?.deskId
- ?: error("Expected active desk in display: $displayId")
+ val activeDesk = desktopData.getDefaultDesk(displayId)
+ checkNotNull(activeDesk) { "Expected desk in display: $displayId" }
// Removes task if it is active on another desk excluding [activeDesk].
- removeActiveTask(taskId, excludedDeskId = activeDeskId)
+ removeActiveTask(taskId, excludedDeskId = activeDesk.deskId)
- if (desktopData.getOrCreateDesk(displayId, activeDeskId).activeTasks.add(taskId)) {
- logD("Adds active task=%d displayId=%d deskId=%d", taskId, displayId, activeDeskId)
+ if (activeDesk.activeTasks.add(taskId)) {
+ logD("Adds active task=%d displayId=%d deskId=%d", taskId, displayId, activeDesk.deskId)
updateActiveTasksListeners(displayId)
}
}
@@ -266,18 +268,23 @@ class DesktopRepository(
* TODO: b/389960283 - add explicit [deskId] argument.
*/
fun addClosingTask(displayId: Int, taskId: Int) {
- val activeDeskId =
- desktopData.getActiveDesk(displayId)?.deskId
+ val activeDesk =
+ desktopData.getActiveDesk(displayId)
?: error("Expected active desk in display: $displayId")
- if (desktopData.getOrCreateDesk(displayId, activeDeskId).closingTasks.add(taskId)) {
- logD("Added closing task=%d displayId=%d deskId=%d", taskId, displayId, activeDeskId)
+ if (activeDesk.closingTasks.add(taskId)) {
+ logD(
+ "Added closing task=%d displayId=%d deskId=%d",
+ taskId,
+ displayId,
+ activeDesk.deskId,
+ )
} else {
// If the task hasn't been removed from closing list after it disappeared.
logW(
"Task with taskId=%d displayId=%d deskId=%d is already closing",
taskId,
displayId,
- activeDeskId,
+ activeDesk.deskId,
)
}
}
@@ -323,7 +330,7 @@ class DesktopRepository(
/**
* Returns the active tasks in the given display's active desk.
*
- * TODO: b/389960283 - add explicit [deskId] argument.
+ * TODO: b/389960283 - migrate callers to [getActiveTaskIdsInDesk].
*/
@VisibleForTesting
fun getActiveTasks(displayId: Int): ArraySet<Int> =
@@ -332,19 +339,27 @@ class DesktopRepository(
/**
* Returns the minimized tasks in the given display's active desk.
*
- * TODO: b/389960283 - add explicit [deskId] argument.
+ * TODO: b/389960283 - migrate callers to [getMinimizedTaskIdsInDesk].
*/
fun getMinimizedTasks(displayId: Int): ArraySet<Int> =
ArraySet(desktopData.getActiveDesk(displayId)?.minimizedTasks)
+ @VisibleForTesting
+ fun getMinimizedTaskIdsInDesk(deskId: Int): ArraySet<Int> =
+ ArraySet(desktopData.getDesk(deskId)?.minimizedTasks)
+
/**
* Returns all active non-minimized tasks for [displayId] ordered from top to bottom.
*
- * TODO: b/389960283 - add explicit [deskId] argument.
+ * TODO: b/389960283 - migrate callers to [getExpandedTasksIdsInDeskOrdered].
*/
fun getExpandedTasksOrdered(displayId: Int): List<Int> =
getFreeformTasksInZOrder(displayId).filter { !isMinimizedTask(it) }
+ @VisibleForTesting
+ fun getExpandedTasksIdsInDeskOrdered(deskId: Int): List<Int> =
+ getFreeformTasksIdsInDeskInZOrder(deskId).filter { !isMinimizedTask(it) }
+
/**
* Returns the count of active non-minimized tasks for [displayId].
*
@@ -357,11 +372,15 @@ class DesktopRepository(
/**
* Returns a list of freeform tasks, ordered from top-bottom (top at index 0).
*
- * TODO: b/389960283 - add explicit [deskId] argument.
+ * TODO: b/389960283 - migrate callers to [getFreeformTasksIdsInDeskInZOrder].
*/
@VisibleForTesting
fun getFreeformTasksInZOrder(displayId: Int): ArrayList<Int> =
- ArrayList(desktopData.getActiveDesk(displayId)?.freeformTasksInZOrder ?: emptyList())
+ ArrayList(desktopData.getDefaultDesk(displayId)?.freeformTasksInZOrder ?: emptyList())
+
+ @VisibleForTesting
+ fun getFreeformTasksIdsInDeskInZOrder(deskId: Int): ArrayList<Int> =
+ ArrayList(desktopData.getDesk(deskId)?.freeformTasksInZOrder ?: emptyList())
/** Returns the tasks inside the given desk. */
fun getActiveTaskIdsInDesk(deskId: Int): Set<Int> =
@@ -401,8 +420,8 @@ class DesktopRepository(
}
val prevCount = getVisibleTaskCount(displayId)
if (isVisible) {
- desktopData.getActiveDesk(displayId)?.visibleTasks?.add(taskId)
- ?: error("Expected non-null active desk in display $displayId")
+ desktopData.getDefaultDesk(displayId)?.visibleTasks?.add(taskId)
+ ?: error("Expected non-null desk in display $displayId")
unminimizeTask(displayId, taskId)
} else {
desktopData.getActiveDesk(displayId)?.visibleTasks?.remove(taskId)
@@ -587,17 +606,15 @@ class DesktopRepository(
* TODO: b/389960283 - add explicit [deskId] argument.
*/
private fun addOrMoveFreeformTaskToTop(displayId: Int, taskId: Int) {
- val activeDesk =
- desktopData.getActiveDesk(displayId)
- ?: error("Expected a desk to be active in display: $displayId")
+ val desk = getDefaultDesk(displayId) ?: error("Expected a desk in display: $displayId")
logD(
"Add or move task to top: display=%d taskId=%d deskId=%d",
taskId,
displayId,
- activeDesk.deskId,
+ desk.deskId,
)
- desktopData.forAllDesks { _, desk -> desk.freeformTasksInZOrder.remove(taskId) }
- activeDesk.freeformTasksInZOrder.add(0, taskId)
+ desktopData.forAllDesks { _, desk1 -> desk1.freeformTasksInZOrder.remove(taskId) }
+ desk.freeformTasksInZOrder.add(0, taskId)
// Unminimize the task if it is minimized.
unminimizeTask(displayId, taskId)
if (DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_PERSISTENCE.isTrue()) {
@@ -835,13 +852,8 @@ class DesktopRepository(
/** An interface for the desktop hierarchy's data managed by this repository. */
private interface DesktopData {
- /**
- * Returns the existing desk or creates a new entry if needed.
- *
- * TODO: 389787966 - consider removing this as it cannot be assumed a desk can be created in
- * all devices / form-factors.
- */
- fun getOrCreateDesk(displayId: Int, deskId: Int): Desk
+ /** Creates a desk record. */
+ fun createDesk(displayId: Int, deskId: Int)
/** Returns the desk with the given id, or null if it does not exist. */
fun getDesk(deskId: Int): Desk?
@@ -894,7 +906,8 @@ class DesktopRepository(
/**
* A [DesktopData] implementation that only supports one desk per display.
*
- * Internally, it reuses the displayId as that display's single desk's id.
+ * Internally, it reuses the displayId as that display's single desk's id. It also never truly
+ * "removes" a desk, it just clears its content.
*/
private class SingleDesktopData : DesktopData {
private val deskByDisplayId =
@@ -907,12 +920,16 @@ class DesktopRepository(
}
}
- override fun getOrCreateDesk(displayId: Int, deskId: Int): Desk {
- check(displayId == deskId)
- return deskByDisplayId.getOrCreate(displayId)
+ override fun createDesk(displayId: Int, deskId: Int) {
+ check(displayId == deskId) { "Display and desk ids must match" }
+ deskByDisplayId.getOrCreate(displayId)
}
- override fun getDesk(deskId: Int): Desk = getOrCreateDesk(deskId, deskId)
+ override fun getDesk(deskId: Int): Desk =
+ // TODO: b/362720497 - consider enforcing that the desk has been created before trying
+ // to use it. As of now, there are cases where a task may be created faster than a
+ // desk is, so just create it here if needed. See b/391984373.
+ deskByDisplayId.getOrCreate(deskId)
override fun getActiveDesk(displayId: Int): Desk {
// TODO: 389787966 - consider migrating to an "active" state instead of checking the
@@ -927,7 +944,7 @@ class DesktopRepository(
// existence of visible desktop windows, among other factors.
}
- override fun getDefaultDesk(displayId: Int): Desk = getOrCreateDesk(displayId, displayId)
+ override fun getDefaultDesk(displayId: Int): Desk = getDesk(deskId = displayId)
override fun getAllActiveDesks(): Set<Desk> =
deskByDisplayId.valueIterator().asSequence().toSet()
@@ -943,7 +960,7 @@ class DesktopRepository(
}
override fun forAllDesks(displayId: Int, consumer: (Desk) -> Unit) {
- consumer(getOrCreateDesk(displayId, displayId))
+ consumer(getDesk(deskId = displayId))
}
override fun desksSequence(): Sequence<Desk> = deskByDisplayId.valueIterator().asSequence()
@@ -962,16 +979,14 @@ class DesktopRepository(
private class MultiDesktopData : DesktopData {
private val desktopDisplays = SparseArray<DesktopDisplay>()
- override fun getOrCreateDesk(displayId: Int, deskId: Int): Desk {
+ override fun createDesk(displayId: Int, deskId: Int) {
val display =
desktopDisplays[displayId]
?: DesktopDisplay(displayId).also { desktopDisplays[displayId] = it }
- val desk =
- display.orderedDesks.find { desk -> desk.deskId == deskId }
- ?: Desk(deskId = deskId, displayId = displayId).also {
- display.orderedDesks.add(it)
- }
- return desk
+ check(display.orderedDesks.none { desk -> desk.deskId == deskId }) {
+ "Attempting to create desk#$deskId that already exists in display#$displayId"
+ }
+ display.orderedDesks.add(Desk(deskId = deskId, displayId = displayId))
}
override fun getDesk(deskId: Int): Desk? {
@@ -999,7 +1014,8 @@ class DesktopRepository(
override fun getDefaultDesk(displayId: Int): Desk? {
val display = desktopDisplays[displayId] ?: return null
- return display.orderedDesks.firstOrNull()
+ return display.orderedDesks.find { it.deskId == display.activeDeskId }
+ ?: display.orderedDesks.firstOrNull()
}
override fun getAllActiveDesks(): Set<Desk> {
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 3ae553596631..5b206dedee49 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
@@ -16,6 +16,7 @@
package com.android.wm.shell.desktopmode
+import android.annotation.UserIdInt
import android.app.ActivityManager
import android.app.ActivityManager.RunningTaskInfo
import android.app.ActivityOptions
@@ -55,6 +56,7 @@ import android.view.WindowManager.TRANSIT_TO_FRONT
import android.widget.Toast
import android.window.DesktopModeFlags
import android.window.DesktopModeFlags.DISABLE_NON_RESIZABLE_APP_SNAP_RESIZE
+import android.window.DesktopModeFlags.ENABLE_DESKTOP_WALLPAPER_ACTIVITY_FOR_SYSTEM_USER
import android.window.DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY
import android.window.DesktopModeFlags.ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS
import android.window.RemoteTransition
@@ -85,8 +87,7 @@ 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.compatui.isTopActivityExemptFromDesktopWindowing
-import com.android.wm.shell.compatui.isTransparentTask
+import com.android.wm.shell.common.UserProfileContexts
import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.InputMethod
import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.MinimizeReason
import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ResizeTrigger
@@ -102,6 +103,8 @@ import com.android.wm.shell.desktopmode.ExitDesktopTaskTransitionHandler.FULLSCR
import com.android.wm.shell.desktopmode.common.ToggleTaskSizeInteraction
import com.android.wm.shell.desktopmode.desktopwallpaperactivity.DesktopWallpaperActivityTokenProvider
import com.android.wm.shell.desktopmode.minimize.DesktopWindowLimitRemoteHandler
+import com.android.wm.shell.desktopmode.multidesks.DesksOrganizer
+import com.android.wm.shell.desktopmode.multidesks.OnDeskRemovedListener
import com.android.wm.shell.draganddrop.DragAndDropController
import com.android.wm.shell.freeform.FreeformTaskTransitionStarter
import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
@@ -113,6 +116,7 @@ import com.android.wm.shell.recents.RecentsTransitionStateListener.TRANSITION_ST
import com.android.wm.shell.shared.TransitionUtil
import com.android.wm.shell.shared.annotations.ExternalThread
import com.android.wm.shell.shared.annotations.ShellMainThread
+import com.android.wm.shell.shared.desktopmode.DesktopModeCompatPolicy
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
@@ -180,6 +184,9 @@ class DesktopTasksController(
private val desktopWallpaperActivityTokenProvider: DesktopWallpaperActivityTokenProvider,
private val bubbleController: Optional<BubbleController>,
private val overviewToDesktopTransitionObserver: OverviewToDesktopTransitionObserver,
+ private val desksOrganizer: DesksOrganizer,
+ private val userProfileContexts: UserProfileContexts,
+ private val desktopModeCompatPolicy: DesktopModeCompatPolicy,
) :
RemoteCallable<DesktopTasksController>,
Transitions.TransitionHandler,
@@ -232,6 +239,9 @@ class DesktopTasksController(
// Used to prevent handleRequest from moving the new fullscreen task to freeform.
private var dragAndDropFullscreenCookie: Binder? = null
+ // A listener that is invoked after a desk has been remove from the system. */
+ var onDeskRemovedListener: OnDeskRemovedListener? = null
+
init {
desktopMode = DesktopModeImpl()
if (DesktopModeStatus.canEnterDesktopMode(context)) {
@@ -415,6 +425,18 @@ class DesktopTasksController(
return isFreeformDisplay
}
+ /** Creates a new desk in the given display. */
+ fun createDesk(displayId: Int) {
+ if (Flags.enableMultipleDesktopsBackend()) {
+ desksOrganizer.createDesk(displayId) { deskId ->
+ taskRepository.addDesk(displayId = displayId, deskId = deskId)
+ }
+ } else {
+ // In single-desk, the desk reuses the display id.
+ taskRepository.addDesk(displayId = displayId, deskId = displayId)
+ }
+ }
+
/** Moves task to desktop mode if task is running, else launches it in desktop mode. */
@JvmOverloads
fun moveTaskToDesktop(
@@ -496,10 +518,7 @@ class DesktopTasksController(
remoteTransition: RemoteTransition? = null,
callback: IMoveToDesktopCallback? = null,
) {
- if (
- DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_MODALS_POLICY.isTrue() &&
- isTopActivityExemptFromDesktopWindowing(context, task)
- ) {
+ if (desktopModeCompatPolicy.isTopActivityExemptFromDesktopWindowing(task)) {
logW("Cannot enter desktop for taskId %d, ineligible top activity found", task.taskId)
return
}
@@ -1468,6 +1487,7 @@ class DesktopTasksController(
}
private fun addLaunchHomePendingIntent(wct: WindowContainerTransaction, displayId: Int) {
+ val userHandle = UserHandle.of(userId)
val launchHomeIntent =
Intent(Intent.ACTION_MAIN).apply {
if (displayId != DEFAULT_DISPLAY) {
@@ -1483,18 +1503,20 @@ class DesktopTasksController(
ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS
}
val pendingIntent =
- PendingIntent.getActivity(
+ PendingIntent.getActivityAsUser(
context,
- /* requestCode = */ 0,
+ /* requestCode= */ 0,
launchHomeIntent,
PendingIntent.FLAG_IMMUTABLE,
+ /* options= */ null,
+ userHandle,
)
wct.sendPendingIntent(pendingIntent, launchHomeIntent, options.toBundle())
}
private fun addWallpaperActivity(displayId: Int, wct: WindowContainerTransaction) {
logV("addWallpaperActivity")
- if (Flags.enableDesktopWallpaperActivityForSystemUser()) {
+ if (ENABLE_DESKTOP_WALLPAPER_ACTIVITY_FOR_SYSTEM_USER.isTrue()) {
val intent = Intent(context, DesktopWallpaperActivity::class.java)
if (
desktopWallpaperActivityTokenProvider.getToken(displayId) == null &&
@@ -1557,7 +1579,7 @@ class DesktopTasksController(
private fun removeWallpaperActivity(wct: WindowContainerTransaction, displayId: Int) {
desktopWallpaperActivityTokenProvider.getToken(displayId)?.let { token ->
logV("removeWallpaperActivity")
- if (Flags.enableDesktopWallpaperActivityForSystemUser()) {
+ if (ENABLE_DESKTOP_WALLPAPER_ACTIVITY_FOR_SYSTEM_USER.isTrue()) {
wct.reorder(token, /* onTop= */ false)
} else {
wct.removeTask(token)
@@ -1798,8 +1820,7 @@ class DesktopTasksController(
taskRepository.isActiveTask(triggerTask.taskId))
private fun isIncompatibleTask(task: RunningTaskInfo) =
- DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_MODALS_POLICY.isTrue() &&
- isTopActivityExemptFromDesktopWindowing(context, task)
+ desktopModeCompatPolicy.isTopActivityExemptFromDesktopWindowing(task)
private fun shouldHandleTaskClosing(request: TransitionRequestInfo): Boolean =
ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY.isTrue() &&
@@ -1841,7 +1862,9 @@ class DesktopTasksController(
// need updates in some cases.
val baseActivity = callingTaskInfo.baseActivity ?: return
val fillIn: Intent =
- context.packageManager.getLaunchIntentForPackage(baseActivity.packageName) ?: return
+ userProfileContexts[callingTaskInfo.userId]
+ ?.packageManager
+ ?.getLaunchIntentForPackage(baseActivity.packageName) ?: return
fillIn.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_MULTIPLE_TASK)
val launchIntent =
PendingIntent.getActivity(
@@ -2068,11 +2091,11 @@ class DesktopTasksController(
*/
private fun handleIncompatibleTaskLaunch(task: RunningTaskInfo): WindowContainerTransaction? {
logV("handleIncompatibleTaskLaunch")
- if (!isDesktopModeShowing(task.displayId)) return null
+ if (!isDesktopModeShowing(task.displayId) && !forceEnterDesktop(task.displayId)) return null
// Only update task repository for transparent task.
if (
DesktopModeFlags.INCLUDE_TOP_TRANSPARENT_FULLSCREEN_TASK_IN_DESKTOP_HEURISTIC
- .isTrue() && isTransparentTask(task)
+ .isTrue() && desktopModeCompatPolicy.isTransparentTask(task)
) {
taskRepository.setTopTransparentFullscreenTaskId(task.displayId, task.taskId)
}
@@ -2720,6 +2743,7 @@ class DesktopTasksController(
// TODO(b/358114479): Move this implementation into a separate class.
override fun onUnhandledDrag(
launchIntent: PendingIntent,
+ @UserIdInt userId: Int,
dragEvent: DragEvent,
onFinishCallback: Consumer<Boolean>,
): Boolean {
@@ -2728,8 +2752,10 @@ class DesktopTasksController(
// Not currently in desktop mode, ignore the drop
return false
}
+
+ // TODO:
val launchComponent = getComponent(launchIntent)
- if (!multiInstanceHelper.supportsMultiInstanceSplit(launchComponent)) {
+ if (!multiInstanceHelper.supportsMultiInstanceSplit(launchComponent, userId)) {
// TODO(b/320797628): Should only return early if there is an existing running task, and
// notify the user as well. But for now, just ignore the drop.
logV("Dropped intent does not support multi-instance")
@@ -2989,6 +3015,14 @@ class DesktopTasksController(
controller = null
}
+ override fun createDesk(displayId: Int) {
+ // TODO: b/362720497 - Implement this API.
+ }
+
+ override fun activateDesk(deskId: Int, remoteTransition: RemoteTransition?) {
+ // TODO: b/362720497 - Implement this API.
+ }
+
override fun showDesktopApps(displayId: Int, remoteTransition: RemoteTransition?) {
executeRemoteCallWithTaskPermission(controller, "showDesktopApps") { c ->
c.showDesktopApps(displayId, remoteTransition)
@@ -3016,17 +3050,6 @@ class DesktopTasksController(
)
}
- override fun getVisibleTaskCount(displayId: Int): Int {
- val result = IntArray(1)
- executeRemoteCallWithTaskPermission(
- controller,
- "visibleTaskCount",
- { controller -> result[0] = controller.visibleTaskCount(displayId) },
- /* blocking= */ true,
- )
- return result[0]
- }
-
override fun onDesktopSplitSelectAnimComplete(taskInfo: RunningTaskInfo) {
executeRemoteCallWithTaskPermission(controller, "onDesktopSplitSelectAnimComplete") { c
->
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt
index 14c8429766cc..b3648699ed0b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt
@@ -27,6 +27,7 @@ import android.view.WindowManager.TRANSIT_PIP
import android.view.WindowManager.TRANSIT_TO_BACK
import android.view.WindowManager.TRANSIT_TO_FRONT
import android.window.DesktopModeFlags
+import android.window.DesktopModeFlags.ENABLE_DESKTOP_WALLPAPER_ACTIVITY_FOR_SYSTEM_USER
import android.window.DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY
import android.window.TransitionInfo
import android.window.WindowContainerTransaction
@@ -275,7 +276,7 @@ class DesktopTasksTransitionObserver(
desktopWallpaperActivityTokenProvider
.getToken(lastSeenTransitionToCloseWallpaper.displayId)
?.let { wallpaperActivityToken ->
- if (Flags.enableDesktopWallpaperActivityForSystemUser()) {
+ if (ENABLE_DESKTOP_WALLPAPER_ACTIVITY_FOR_SYSTEM_USER.isTrue()) {
transitions.startTransition(
TRANSIT_TO_BACK,
WindowContainerTransaction()
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopUserRepositories.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopUserRepositories.kt
index 13576aa42737..a5ba6612bb1a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopUserRepositories.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopUserRepositories.kt
@@ -21,9 +21,9 @@ import android.content.Context
import android.content.pm.UserInfo
import android.os.UserManager
import android.util.SparseArray
+import android.window.DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_HSUM
import androidx.core.util.forEach
import com.android.internal.protolog.ProtoLog
-import com.android.window.flags.Flags
import com.android.wm.shell.desktopmode.persistence.DesktopPersistentRepository
import com.android.wm.shell.desktopmode.persistence.DesktopRepositoryInitializer
import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
@@ -68,7 +68,7 @@ class DesktopUserRepositories(
if (DesktopModeStatus.canEnterDesktopMode(context)) {
shellInit.addInitCallback(::onInit, this)
}
- if (Flags.enableDesktopWindowingHsum()) {
+ if (ENABLE_DESKTOP_WINDOWING_HSUM.isTrue()) {
userIdToProfileIdsMap[userId] = userManager.getProfiles(userId).map { it.id }
}
}
@@ -80,7 +80,7 @@ class DesktopUserRepositories(
/** Returns [DesktopRepository] for the parent user id. */
fun getProfile(profileId: Int): DesktopRepository {
- if (Flags.enableDesktopWindowingHsum()) {
+ if (ENABLE_DESKTOP_WINDOWING_HSUM.isTrue()) {
for ((uid, profileIds) in userIdToProfileIdsMap) {
if (profileId in profileIds) {
return desktopRepoByUserId.getOrCreate(uid)
@@ -101,14 +101,14 @@ class DesktopUserRepositories(
override fun onUserChanged(newUserId: Int, userContext: Context) {
logD("onUserChanged previousUserId=%d, newUserId=%d", userId, newUserId)
userId = newUserId
- if (Flags.enableDesktopWindowingHsum()) {
+ if (ENABLE_DESKTOP_WINDOWING_HSUM.isTrue()) {
sanitizeUsers()
}
}
override fun onUserProfilesChanged(profiles: MutableList<UserInfo>) {
logD("onUserProfilesChanged profiles=%s", profiles.toString())
- if (Flags.enableDesktopWindowingHsum()) {
+ if (ENABLE_DESKTOP_WINDOWING_HSUM.isTrue()) {
// TODO(b/366397912): Remove all persisted profile data when the profile changes.
userIdToProfileIdsMap[userId] = profiles.map { it.id }
}
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 a135e4462150..44f7e16e98c3 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
@@ -29,6 +29,11 @@ import com.android.wm.shell.desktopmode.IMoveToDesktopCallback;
* Interface that is exposed to remote callers to manipulate desktop mode features.
*/
interface IDesktopMode {
+ /** If possible, creates a new desk on the display whose ID is `displayId`. */
+ oneway void createDesk(int displayId);
+
+ /** Activates the desk whose ID is `deskId` on whatever display it currently exists on. */
+ oneway void activateDesk(int deskId, in RemoteTransition remoteTransition);
/** Show apps on the desktop on the given display */
void showDesktopApps(int displayId, in RemoteTransition remoteTransition);
@@ -48,9 +53,6 @@ interface IDesktopMode {
oneway void showDesktopApp(int taskId, in @nullable RemoteTransition remoteTransition,
in DesktopTaskToFrontReason toFrontReason);
- /** Get count of visible desktop tasks on the given display */
- int getVisibleTaskCount(int displayId);
-
/** Perform cleanup transactions after the animation to split select is complete */
oneway void onDesktopSplitSelectAnimComplete(in RunningTaskInfo taskInfo);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/compatui/SystemModalsTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/compatui/SystemModalsTransitionHandler.kt
index a428ce18a49e..224ff37a1dca 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/compatui/SystemModalsTransitionHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/compatui/SystemModalsTransitionHandler.kt
@@ -17,6 +17,7 @@
package com.android.wm.shell.desktopmode.compatui
import android.animation.ValueAnimator
+import android.app.ActivityManager.RunningTaskInfo
import android.content.Context
import android.os.IBinder
import android.view.Display.DEFAULT_DISPLAY
@@ -28,13 +29,14 @@ import androidx.core.animation.addListener
import com.android.app.animation.Interpolators
import com.android.internal.protolog.ProtoLog
import com.android.wm.shell.common.ShellExecutor
-import com.android.wm.shell.compatui.isTopActivityExemptFromDesktopWindowing
import com.android.wm.shell.desktopmode.DesktopUserRepositories
+import com.android.wm.shell.desktopmode.DesktopWallpaperActivity
import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
import com.android.wm.shell.shared.TransitionUtil.isClosingMode
import com.android.wm.shell.shared.TransitionUtil.isClosingType
import com.android.wm.shell.shared.TransitionUtil.isOpeningMode
import com.android.wm.shell.shared.TransitionUtil.isOpeningType
+import com.android.wm.shell.shared.desktopmode.DesktopModeCompatPolicy
import com.android.wm.shell.sysui.ShellInit
import com.android.wm.shell.transition.Transitions
import com.android.wm.shell.transition.Transitions.TransitionHandler
@@ -47,6 +49,7 @@ class SystemModalsTransitionHandler(
private val shellInit: ShellInit,
private val transitions: Transitions,
private val desktopUserRepositories: DesktopUserRepositories,
+ private val desktopModeCompatPolicy: DesktopModeCompatPolicy,
) : TransitionHandler {
private val showingSystemModalsIds = mutableSetOf<Int>()
@@ -128,7 +131,7 @@ class SystemModalsTransitionHandler(
return@find false
}
val taskInfo = change.taskInfo ?: return@find false
- return@find isTopActivityExemptFromDesktopWindowing(context, taskInfo)
+ return@find isSystemModal(taskInfo)
}
private fun getClosingSystemModal(info: TransitionInfo): TransitionInfo.Change? =
@@ -137,10 +140,13 @@ class SystemModalsTransitionHandler(
return@find false
}
val taskInfo = change.taskInfo ?: return@find false
- return@find isTopActivityExemptFromDesktopWindowing(context, taskInfo) ||
- showingSystemModalsIds.contains(taskInfo.taskId)
+ return@find isSystemModal(taskInfo) || showingSystemModalsIds.contains(taskInfo.taskId)
}
+ private fun isSystemModal(taskInfo: RunningTaskInfo): Boolean =
+ !DesktopWallpaperActivity.isWallpaperTask(taskInfo) &&
+ desktopModeCompatPolicy.isTopActivityExemptFromDesktopWindowing(taskInfo)
+
private fun createAlphaAnimator(
transaction: SurfaceControl.Transaction,
leash: SurfaceControl,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/AppHandleEducationController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/AppHandleEducationController.kt
index 5757c6afd196..b614b3f4d025 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/AppHandleEducationController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/AppHandleEducationController.kt
@@ -22,7 +22,6 @@ import android.content.Context
import android.content.res.Resources
import android.graphics.Point
import android.os.SystemProperties
-import android.util.Slog
import com.android.window.flags.Flags
import com.android.wm.shell.R
import com.android.wm.shell.desktopmode.CaptionState
@@ -32,27 +31,17 @@ import com.android.wm.shell.shared.annotations.ShellBackgroundThread
import com.android.wm.shell.shared.annotations.ShellMainThread
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus.canEnterDesktopMode
import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource
-import com.android.wm.shell.windowdecor.common.DecorThemeUtil
import com.android.wm.shell.windowdecor.education.DesktopWindowingEducationTooltipController
import com.android.wm.shell.windowdecor.education.DesktopWindowingEducationTooltipController.TooltipColorScheme
import com.android.wm.shell.windowdecor.education.DesktopWindowingEducationTooltipController.TooltipEducationViewConfig
-import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.MainCoroutineDispatcher
-import kotlinx.coroutines.TimeoutCancellationException
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.debounce
-import kotlinx.coroutines.flow.distinctUntilChanged
-import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.first
-import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOn
-import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.take
-import kotlinx.coroutines.flow.timeout
import kotlinx.coroutines.launch
/**
@@ -72,59 +61,75 @@ class AppHandleEducationController(
@ShellMainThread private val applicationCoroutineScope: CoroutineScope,
@ShellBackgroundThread private val backgroundDispatcher: MainCoroutineDispatcher,
) {
- private val decorThemeUtil = DecorThemeUtil(context)
private lateinit var openHandleMenuCallback: (Int) -> Unit
private lateinit var toDesktopModeCallback: (Int, DesktopModeTransitionSource) -> Unit
+ private val onTertiaryFixedColor =
+ context.getColor(com.android.internal.R.color.materialColorOnTertiaryFixed)
+ private val tertiaryFixedColor =
+ context.getColor(com.android.internal.R.color.materialColorTertiaryFixed)
init {
runIfEducationFeatureEnabled {
+ // Coroutine block for the first hint that appears on a full-screen app's app handle to
+ // encourage users to open the app handle menu.
applicationCoroutineScope.launch {
- // Central block handling the app handle's educational flow end-to-end.
- isAppHandleHintViewedFlow()
- .flatMapLatest { isAppHandleHintViewed ->
- if (isAppHandleHintViewed) {
- // If the education is viewed then return emptyFlow() that completes
- // immediately.
- // This will help us to not listen to [captionHandleStateFlow] after the
- // education
- // has been viewed already.
- emptyFlow()
- } else {
- // Listen for changes to window decor's caption handle.
- windowDecorCaptionHandleRepository.captionStateFlow
- // Wait for few seconds before emitting the latest state.
- .debounce(APP_HANDLE_EDUCATION_DELAY_MILLIS)
- .filter { captionState ->
- captionState is CaptionState.AppHandle &&
- appHandleEducationFilter.shouldShowAppHandleEducation(
- captionState
- )
- }
- }
+ if (isAppHandleHintViewed()) return@launch
+ windowDecorCaptionHandleRepository.captionStateFlow
+ .debounce(APP_HANDLE_EDUCATION_DELAY_MILLIS)
+ .filter { captionState ->
+ captionState is CaptionState.AppHandle &&
+ !captionState.isHandleMenuExpanded &&
+ !isAppHandleHintViewed() &&
+ appHandleEducationFilter.shouldShowDesktopModeEducation(captionState)
}
+ .take(1)
.flowOn(backgroundDispatcher)
.collectLatest { captionState ->
- val tooltipColorScheme = tooltipColorScheme(captionState)
-
- showEducation(captionState, tooltipColorScheme)
- // After showing first tooltip, mark education as viewed
+ showEducation(captionState)
appHandleEducationDatastoreRepository
.updateAppHandleHintViewedTimestampMillis(true)
}
}
+ // Coroutine block for the hint that appears when an app handle is expanded to
+ // encourage users to enter desktop mode.
applicationCoroutineScope.launch {
- if (isAppHandleHintUsed()) return@launch
+ if (isEnterDesktopModeHintViewed()) return@launch
windowDecorCaptionHandleRepository.captionStateFlow
+ .debounce(ENTER_DESKTOP_MODE_EDUCATION_DELAY_MILLIS)
.filter { captionState ->
- captionState is CaptionState.AppHandle && captionState.isHandleMenuExpanded
+ captionState is CaptionState.AppHandle &&
+ captionState.isHandleMenuExpanded &&
+ !isEnterDesktopModeHintViewed() &&
+ appHandleEducationFilter.shouldShowDesktopModeEducation(captionState)
}
.take(1)
.flowOn(backgroundDispatcher)
- .collect {
- // If user expands app handle, mark user has used the app handle hint
+ .collectLatest { captionState ->
+ showWindowingImageButtonTooltip(captionState as CaptionState.AppHandle)
appHandleEducationDatastoreRepository
- .updateAppHandleHintUsedTimestampMillis(true)
+ .updateEnterDesktopModeHintViewedTimestampMillis(true)
+ }
+ }
+
+ // Coroutine block for the hint that appears on the window app header in freeform mode
+ // to let users know how to exit desktop mode.
+ applicationCoroutineScope.launch {
+ if (isExitDesktopModeHintViewed()) return@launch
+ windowDecorCaptionHandleRepository.captionStateFlow
+ .debounce(APP_HANDLE_EDUCATION_DELAY_MILLIS)
+ .filter { captionState ->
+ captionState is CaptionState.AppHeader &&
+ !captionState.isHeaderMenuExpanded &&
+ !isExitDesktopModeHintViewed() &&
+ appHandleEducationFilter.shouldShowDesktopModeEducation(captionState)
+ }
+ .take(1)
+ .flowOn(backgroundDispatcher)
+ .collectLatest { captionState ->
+ showExitWindowingTooltip(captionState as CaptionState.AppHeader)
+ appHandleEducationDatastoreRepository
+ .updateExitDesktopModeHintViewedTimestampMillis(true)
}
}
}
@@ -135,7 +140,7 @@ class AppHandleEducationController(
block()
}
- private fun showEducation(captionState: CaptionState, tooltipColorScheme: TooltipColorScheme) {
+ private fun showEducation(captionState: CaptionState) {
val appHandleBounds = (captionState as CaptionState.AppHandle).globalAppHandleBounds
val tooltipGlobalCoordinates =
Point(appHandleBounds.left + appHandleBounds.width() / 2, appHandleBounds.bottom)
@@ -145,21 +150,21 @@ class AppHandleEducationController(
val appHandleTooltipConfig =
TooltipEducationViewConfig(
tooltipViewLayout = R.layout.desktop_windowing_education_top_arrow_tooltip,
- tooltipColorScheme = tooltipColorScheme,
+ tooltipColorScheme =
+ TooltipColorScheme(
+ tertiaryFixedColor,
+ onTertiaryFixedColor,
+ onTertiaryFixedColor,
+ ),
tooltipViewGlobalCoordinates = tooltipGlobalCoordinates,
tooltipText = getString(R.string.windowing_app_handle_education_tooltip),
arrowDirection =
DesktopWindowingEducationTooltipController.TooltipArrowDirection.UP,
onEducationClickAction = {
- launchWithExceptionHandling {
- showWindowingImageButtonTooltip(tooltipColorScheme)
- }
openHandleMenuCallback(captionState.runningTaskInfo.taskId)
},
onDismissAction = {
- launchWithExceptionHandling {
- showWindowingImageButtonTooltip(tooltipColorScheme)
- }
+ // TODO: b/341320146 - Log previous tooltip was dismissed
},
)
@@ -170,7 +175,7 @@ class AppHandleEducationController(
}
/** Show tooltip that points to windowing image button in app handle menu */
- private suspend fun showWindowingImageButtonTooltip(tooltipColorScheme: TooltipColorScheme) {
+ private suspend fun showWindowingImageButtonTooltip(captionState: CaptionState.AppHandle) {
val appInfoPillHeight = getSize(R.dimen.desktop_mode_handle_menu_app_info_pill_height)
val windowingOptionPillHeight =
getSize(R.dimen.desktop_mode_handle_menu_windowing_pill_height)
@@ -181,128 +186,81 @@ class AppHandleEducationController(
getSize(R.dimen.desktop_mode_handle_menu_margin_top) +
getSize(R.dimen.desktop_mode_handle_menu_pill_spacing_margin)
- windowDecorCaptionHandleRepository.captionStateFlow
- // After the first tooltip was dismissed, wait for 400 ms and see if the app handle menu
- // has been expanded.
- .timeout(APP_HANDLE_EDUCATION_TIMEOUT_MILLIS.milliseconds)
- .catchTimeoutAndLog {
- // TODO: b/341320146 - Log previous tooltip was dismissed
- }
- // Wait for few milliseconds before emitting the latest state.
- .debounce(APP_HANDLE_EDUCATION_DELAY_MILLIS)
- .filter { captionState ->
- // Filter out states when app handle is not visible or not expanded.
- captionState is CaptionState.AppHandle && captionState.isHandleMenuExpanded
- }
- // Before showing this tooltip, stop listening to further emissions to avoid
- // accidentally
- // showing the same tooltip on future emissions.
- .take(1)
- .flowOn(backgroundDispatcher)
- .collectLatest { captionState ->
- captionState as CaptionState.AppHandle
- val appHandleBounds = captionState.globalAppHandleBounds
- val tooltipGlobalCoordinates =
- Point(
- appHandleBounds.left + appHandleBounds.width() / 2 + appHandleMenuWidth / 2,
- appHandleBounds.top +
- appHandleMenuMargins +
- appInfoPillHeight +
- windowingOptionPillHeight / 2,
- )
- // Populate information important to inflate windowing image button education
- // tooltip.
- val windowingImageButtonTooltipConfig =
- TooltipEducationViewConfig(
- tooltipViewLayout = R.layout.desktop_windowing_education_left_arrow_tooltip,
- tooltipColorScheme = tooltipColorScheme,
- tooltipViewGlobalCoordinates = tooltipGlobalCoordinates,
- tooltipText =
- getString(
- R.string.windowing_desktop_mode_image_button_education_tooltip
- ),
- arrowDirection =
- DesktopWindowingEducationTooltipController.TooltipArrowDirection.LEFT,
- onEducationClickAction = {
- launchWithExceptionHandling {
- showExitWindowingTooltip(tooltipColorScheme)
- }
- toDesktopModeCallback(
- captionState.runningTaskInfo.taskId,
- DesktopModeTransitionSource.APP_HANDLE_MENU_BUTTON,
- )
- },
- onDismissAction = {
- launchWithExceptionHandling {
- showExitWindowingTooltip(tooltipColorScheme)
- }
- },
+ val appHandleBounds = captionState.globalAppHandleBounds
+ val tooltipGlobalCoordinates =
+ Point(
+ appHandleBounds.left + appHandleBounds.width() / 2 + appHandleMenuWidth / 2,
+ appHandleBounds.top +
+ appHandleMenuMargins +
+ appInfoPillHeight +
+ windowingOptionPillHeight / 2,
+ )
+ // Populate information important to inflate windowing image button education
+ // tooltip.
+ val windowingImageButtonTooltipConfig =
+ TooltipEducationViewConfig(
+ tooltipViewLayout = R.layout.desktop_windowing_education_left_arrow_tooltip,
+ tooltipColorScheme =
+ TooltipColorScheme(
+ tertiaryFixedColor,
+ onTertiaryFixedColor,
+ onTertiaryFixedColor,
+ ),
+ tooltipViewGlobalCoordinates = tooltipGlobalCoordinates,
+ tooltipText =
+ getString(R.string.windowing_desktop_mode_image_button_education_tooltip),
+ arrowDirection =
+ DesktopWindowingEducationTooltipController.TooltipArrowDirection.LEFT,
+ onEducationClickAction = {
+ toDesktopModeCallback(
+ captionState.runningTaskInfo.taskId,
+ DesktopModeTransitionSource.APP_HANDLE_MENU_BUTTON,
)
+ },
+ onDismissAction = {
+ // TODO: b/341320146 - Log previous tooltip was dismissed
+ },
+ )
- windowingEducationViewController.showEducationTooltip(
- taskId = captionState.runningTaskInfo.taskId,
- tooltipViewConfig = windowingImageButtonTooltipConfig,
- )
- }
+ windowingEducationViewController.showEducationTooltip(
+ taskId = captionState.runningTaskInfo.taskId,
+ tooltipViewConfig = windowingImageButtonTooltipConfig,
+ )
}
/** Show tooltip that points to app chip button and educates user on how to exit desktop mode */
- private suspend fun showExitWindowingTooltip(tooltipColorScheme: TooltipColorScheme) {
- windowDecorCaptionHandleRepository.captionStateFlow
- // After the previous tooltip was dismissed, wait for 400 ms and see if the user entered
- // desktop mode.
- .timeout(APP_HANDLE_EDUCATION_TIMEOUT_MILLIS.milliseconds)
- .catchTimeoutAndLog {
- // TODO: b/341320146 - Log previous tooltip was dismissed
- }
- // Wait for few milliseconds before emitting the latest state.
- .debounce(APP_HANDLE_EDUCATION_DELAY_MILLIS)
- .filter { captionState ->
- // Filter out states when app header is not visible or expanded.
- captionState is CaptionState.AppHeader && !captionState.isHeaderMenuExpanded
- }
- // Before showing this tooltip, stop listening to further emissions to avoid
- // accidentally
- // showing the same tooltip on future emissions.
- .take(1)
- .flowOn(backgroundDispatcher)
- .collectLatest { captionState ->
- captionState as CaptionState.AppHeader
- val globalAppChipBounds = captionState.globalAppChipBounds
- val tooltipGlobalCoordinates =
- Point(
- globalAppChipBounds.right,
- globalAppChipBounds.top + globalAppChipBounds.height() / 2,
- )
- // Populate information important to inflate exit desktop mode education tooltip.
- val exitWindowingTooltipConfig =
- TooltipEducationViewConfig(
- tooltipViewLayout = R.layout.desktop_windowing_education_left_arrow_tooltip,
- tooltipColorScheme = tooltipColorScheme,
- tooltipViewGlobalCoordinates = tooltipGlobalCoordinates,
- tooltipText =
- getString(R.string.windowing_desktop_mode_exit_education_tooltip),
- arrowDirection =
- DesktopWindowingEducationTooltipController.TooltipArrowDirection.LEFT,
- onDismissAction = {},
- onEducationClickAction = {
- openHandleMenuCallback(captionState.runningTaskInfo.taskId)
- },
- )
- windowingEducationViewController.showEducationTooltip(
- taskId = captionState.runningTaskInfo.taskId,
- tooltipViewConfig = exitWindowingTooltipConfig,
- )
- }
- }
-
- private fun tooltipColorScheme(captionState: CaptionState): TooltipColorScheme {
- val onTertiaryFixed =
- context.getColor(com.android.internal.R.color.materialColorOnTertiaryFixed)
- val tertiaryFixed =
- context.getColor(com.android.internal.R.color.materialColorTertiaryFixed)
-
- return TooltipColorScheme(tertiaryFixed, onTertiaryFixed, onTertiaryFixed)
+ private suspend fun showExitWindowingTooltip(captionState: CaptionState.AppHeader) {
+ val globalAppChipBounds = captionState.globalAppChipBounds
+ val tooltipGlobalCoordinates =
+ Point(
+ globalAppChipBounds.right,
+ globalAppChipBounds.top + globalAppChipBounds.height() / 2,
+ )
+ // Populate information important to inflate exit desktop mode education tooltip.
+ val exitWindowingTooltipConfig =
+ TooltipEducationViewConfig(
+ tooltipViewLayout = R.layout.desktop_windowing_education_left_arrow_tooltip,
+ tooltipColorScheme =
+ TooltipColorScheme(
+ tertiaryFixedColor,
+ onTertiaryFixedColor,
+ onTertiaryFixedColor,
+ ),
+ tooltipViewGlobalCoordinates = tooltipGlobalCoordinates,
+ tooltipText = getString(R.string.windowing_desktop_mode_exit_education_tooltip),
+ arrowDirection =
+ DesktopWindowingEducationTooltipController.TooltipArrowDirection.LEFT,
+ onDismissAction = {
+ // TODO: b/341320146 - Log previous tooltip was dismissed
+ },
+ onEducationClickAction = {
+ openHandleMenuCallback(captionState.runningTaskInfo.taskId)
+ },
+ )
+ windowingEducationViewController.showEducationTooltip(
+ taskId = captionState.runningTaskInfo.taskId,
+ tooltipViewConfig = exitWindowingTooltipConfig,
+ )
}
/**
@@ -319,43 +277,20 @@ class AppHandleEducationController(
this.toDesktopModeCallback = toDesktopModeCallback
}
- private inline fun <T> Flow<T>.catchTimeoutAndLog(crossinline block: () -> Unit) =
- catch { exception ->
- if (exception is TimeoutCancellationException) block() else throw exception
- }
-
- private fun launchWithExceptionHandling(block: suspend () -> Unit) =
- applicationCoroutineScope.launch {
- try {
- block()
- } catch (e: Throwable) {
- Slog.e(TAG, "Error: ", e)
- }
- }
+ private suspend fun isAppHandleHintViewed(): Boolean =
+ appHandleEducationDatastoreRepository.dataStoreFlow
+ .first()
+ .hasAppHandleHintViewedTimestampMillis() && !FORCE_SHOW_DESKTOP_MODE_EDUCATION
- /**
- * Listens to the changes to [WindowingEducationProto#hasAppHandleHintViewedTimestampMillis()]
- * in datastore proto object.
- *
- * If [SHOULD_OVERRIDE_EDUCATION_CONDITIONS] is true, this flow will always emit false. That
- * means it will always emit app handle hint has not been viewed yet.
- */
- private fun isAppHandleHintViewedFlow(): Flow<Boolean> =
+ private suspend fun isEnterDesktopModeHintViewed(): Boolean =
appHandleEducationDatastoreRepository.dataStoreFlow
- .map { preferences ->
- preferences.hasAppHandleHintViewedTimestampMillis() &&
- !SHOULD_OVERRIDE_EDUCATION_CONDITIONS
- }
- .distinctUntilChanged()
+ .first()
+ .hasEnterDesktopModeHintViewedTimestampMillis() && !FORCE_SHOW_DESKTOP_MODE_EDUCATION
- /**
- * Listens to the changes to [WindowingEducationProto#hasAppHandleHintUsedTimestampMillis()] in
- * datastore proto object.
- */
- private suspend fun isAppHandleHintUsed(): Boolean =
+ private suspend fun isExitDesktopModeHintViewed(): Boolean =
appHandleEducationDatastoreRepository.dataStoreFlow
.first()
- .hasAppHandleHintUsedTimestampMillis()
+ .hasExitDesktopModeHintViewedTimestampMillis() && !FORCE_SHOW_DESKTOP_MODE_EDUCATION
private fun getSize(@DimenRes resourceId: Int): Int {
if (resourceId == Resources.ID_NULL) return 0
@@ -369,13 +304,17 @@ class AppHandleEducationController(
val APP_HANDLE_EDUCATION_DELAY_MILLIS: Long
get() = SystemProperties.getLong("persist.windowing_app_handle_education_delay", 3000L)
- val APP_HANDLE_EDUCATION_TIMEOUT_MILLIS: Long
- get() = SystemProperties.getLong("persist.windowing_app_handle_education_timeout", 400L)
+ val ENTER_DESKTOP_MODE_EDUCATION_DELAY_MILLIS: Long
+ get() =
+ SystemProperties.getLong(
+ "persist.windowing_enter_desktop_mode_education_timeout",
+ 400L,
+ )
- val SHOULD_OVERRIDE_EDUCATION_CONDITIONS: Boolean
+ val FORCE_SHOW_DESKTOP_MODE_EDUCATION: Boolean
get() =
SystemProperties.getBoolean(
- "persist.desktop_windowing_app_handle_education_override_conditions",
+ "persist.windowing_force_show_desktop_mode_education",
false,
)
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/AppHandleEducationFilter.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/AppHandleEducationFilter.kt
index 9990846fc92e..4d219b5544aa 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/AppHandleEducationFilter.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/AppHandleEducationFilter.kt
@@ -17,13 +17,14 @@
package com.android.wm.shell.desktopmode.education
import android.annotation.IntegerRes
+import android.app.ActivityManager.RunningTaskInfo
import android.app.usage.UsageStatsManager
import android.content.Context
import android.os.SystemClock
import android.provider.Settings.Secure
import com.android.wm.shell.R
import com.android.wm.shell.desktopmode.CaptionState
-import com.android.wm.shell.desktopmode.education.AppHandleEducationController.Companion.SHOULD_OVERRIDE_EDUCATION_CONDITIONS
+import com.android.wm.shell.desktopmode.education.AppHandleEducationController.Companion.FORCE_SHOW_DESKTOP_MODE_EDUCATION
import com.android.wm.shell.desktopmode.education.data.AppHandleEducationDatastoreRepository
import com.android.wm.shell.desktopmode.education.data.WindowingEducationProto
import java.time.Duration
@@ -37,26 +38,28 @@ class AppHandleEducationFilter(
private val usageStatsManager =
context.getSystemService(Context.USAGE_STATS_SERVICE) as UsageStatsManager
+ suspend fun shouldShowDesktopModeEducation(captionState: CaptionState.AppHeader): Boolean =
+ shouldShowDesktopModeEducation(captionState.runningTaskInfo)
+
+ suspend fun shouldShowDesktopModeEducation(captionState: CaptionState.AppHandle): Boolean =
+ shouldShowDesktopModeEducation(captionState.runningTaskInfo)
+
/**
- * Returns true if conditions to show app handle education are met, returns false otherwise.
+ * Returns true if conditions to show app handle, enter desktop mode and exit desktop mode
+ * education are met based on the app info and usage, returns false otherwise.
*
- * If [SHOULD_OVERRIDE_EDUCATION_CONDITIONS] is true, this method will always return
- * ![captionState.isHandleMenuExpanded].
+ * If [FORCE_SHOW_DESKTOP_MODE_EDUCATION] is true, this method will always return true.
*/
- suspend fun shouldShowAppHandleEducation(captionState: CaptionState): Boolean {
- if ((captionState as CaptionState.AppHandle).isHandleMenuExpanded) return false
- if (SHOULD_OVERRIDE_EDUCATION_CONDITIONS) return true
+ private suspend fun shouldShowDesktopModeEducation(taskInfo: RunningTaskInfo): Boolean {
+ if (FORCE_SHOW_DESKTOP_MODE_EDUCATION) return true
- val focusAppPackageName =
- captionState.runningTaskInfo.topActivityInfo?.packageName ?: return false
+ val focusAppPackageName = taskInfo.topActivityInfo?.packageName ?: return false
val windowingEducationProto =
appHandleEducationDatastoreRepository.windowingEducationProto()
return isFocusAppInAllowlist(focusAppPackageName) &&
!isOtherEducationShowing() &&
hasSufficientTimeSinceSetup() &&
- !isAppHandleHintViewedBefore(windowingEducationProto) &&
- !isAppHandleHintUsedBefore(windowingEducationProto) &&
hasMinAppUsage(windowingEducationProto, focusAppPackageName)
}
@@ -79,14 +82,6 @@ class AppHandleEducationFilter(
R.integer.desktop_windowing_education_required_time_since_setup_seconds
)
- private fun isAppHandleHintViewedBefore(
- windowingEducationProto: WindowingEducationProto
- ): Boolean = windowingEducationProto.hasAppHandleHintViewedTimestampMillis()
-
- private fun isAppHandleHintUsedBefore(
- windowingEducationProto: WindowingEducationProto
- ): Boolean = windowingEducationProto.hasAppHandleHintUsedTimestampMillis()
-
private suspend fun hasMinAppUsage(
windowingEducationProto: WindowingEducationProto,
focusAppPackageName: String,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/data/AppHandleEducationDatastoreRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/data/AppHandleEducationDatastoreRepository.kt
index 3e120b09a0b6..d061e03b9be5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/data/AppHandleEducationDatastoreRepository.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/data/AppHandleEducationDatastoreRepository.kt
@@ -91,6 +91,40 @@ constructor(private val dataStore: DataStore<WindowingEducationProto>) {
}
/**
+ * Updates [WindowingEducationProto.enterDesktopModeHintViewedTimestampMillis_] field in
+ * datastore with current timestamp if [isViewed] is true, if not then clears the field.
+ */
+ suspend fun updateEnterDesktopModeHintViewedTimestampMillis(isViewed: Boolean) {
+ dataStore.updateData { preferences ->
+ if (isViewed) {
+ preferences
+ .toBuilder()
+ .setEnterDesktopModeHintViewedTimestampMillis(System.currentTimeMillis())
+ .build()
+ } else {
+ preferences.toBuilder().clearEnterDesktopModeHintViewedTimestampMillis().build()
+ }
+ }
+ }
+
+ /**
+ * Updates [WindowingEducationProto.exitDesktopModeHintViewedTimestampMillis_] field in
+ * datastore with current timestamp if [isViewed] is true, if not then clears the field.
+ */
+ suspend fun updateExitDesktopModeHintViewedTimestampMillis(isViewed: Boolean) {
+ dataStore.updateData { preferences ->
+ if (isViewed) {
+ preferences
+ .toBuilder()
+ .setExitDesktopModeHintViewedTimestampMillis(System.currentTimeMillis())
+ .build()
+ } else {
+ preferences.toBuilder().clearExitDesktopModeHintViewedTimestampMillis().build()
+ }
+ }
+ }
+
+ /**
* Updates [WindowingEducationProto.appHandleHintUsedTimestampMillis_] field in datastore with
* current timestamp if [isViewed] is true, if not then clears the field.
*/
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/DesksOrganizer.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/DesksOrganizer.kt
new file mode 100644
index 000000000000..5cbb59fbf323
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/DesksOrganizer.kt
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.wm.shell.desktopmode.multidesks
+
+import android.app.ActivityManager
+import android.window.TransitionInfo
+import android.window.WindowContainerTransaction
+
+/** An organizer of desk containers in which to host child desktop windows. */
+interface DesksOrganizer {
+ /** Creates a new desk container in the given display. */
+ fun createDesk(displayId: Int, callback: OnCreateCallback)
+
+ /** Activates the given desk, making it visible in its display. */
+ fun activateDesk(wct: WindowContainerTransaction, deskId: Int)
+
+ /** Removes the given desk and its desktop windows. */
+ fun removeDesk(wct: WindowContainerTransaction, deskId: Int)
+
+ /** Moves the given task to the given desk. */
+ fun moveTaskToDesk(
+ wct: WindowContainerTransaction,
+ deskId: Int,
+ task: ActivityManager.RunningTaskInfo,
+ )
+
+ /**
+ * Returns the desk id in which the task in the given change is located at the end of a
+ * transition, if any.
+ */
+ fun getDeskAtEnd(change: TransitionInfo.Change): Int?
+
+ /** A callback that is invoked when the desk container is created. */
+ fun interface OnCreateCallback {
+ /** Calls back when the [deskId] has been created. */
+ fun onCreated(deskId: Int)
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/OnDeskRemovedListener.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/OnDeskRemovedListener.kt
new file mode 100644
index 000000000000..452ddb1ff8fb
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/OnDeskRemovedListener.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.wm.shell.desktopmode.multidesks
+
+/** A listener for removals of desks. */
+fun interface OnDeskRemovedListener {
+ /** Called when a desk has been removed from the system. */
+ fun onDeskRemoved(lastDisplayId: Int, deskId: Int)
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/RootTaskDesksOrganizer.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/RootTaskDesksOrganizer.kt
new file mode 100644
index 000000000000..79c48c5e9594
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/RootTaskDesksOrganizer.kt
@@ -0,0 +1,182 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.wm.shell.desktopmode.multidesks
+
+import android.app.ActivityManager.RunningTaskInfo
+import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD
+import android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED
+import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
+import android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED
+import android.util.SparseArray
+import android.view.SurfaceControl
+import android.window.TransitionInfo
+import android.window.WindowContainerTransaction
+import androidx.core.util.forEach
+import com.android.internal.annotations.VisibleForTesting
+import com.android.internal.protolog.ProtoLog
+import com.android.window.flags.Flags
+import com.android.wm.shell.ShellTaskOrganizer
+import com.android.wm.shell.desktopmode.multidesks.DesksOrganizer.OnCreateCallback
+import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
+import com.android.wm.shell.sysui.ShellCommandHandler
+import com.android.wm.shell.sysui.ShellInit
+import java.io.PrintWriter
+
+/** A [DesksOrganizer] that uses root tasks as the container of each desk. */
+class RootTaskDesksOrganizer(
+ shellInit: ShellInit,
+ shellCommandHandler: ShellCommandHandler,
+ private val shellTaskOrganizer: ShellTaskOrganizer,
+) : DesksOrganizer, ShellTaskOrganizer.TaskListener {
+
+ private val deskCreateRequests = mutableListOf<CreateRequest>()
+ @VisibleForTesting val roots = SparseArray<DeskRoot>()
+
+ init {
+ if (Flags.enableMultipleDesktopsBackend()) {
+ shellInit.addInitCallback(
+ { shellCommandHandler.addDumpCallback(this::dump, this) },
+ this,
+ )
+ }
+ }
+
+ override fun createDesk(displayId: Int, callback: OnCreateCallback) {
+ logV("createDesk in display: %d", displayId)
+ deskCreateRequests += CreateRequest(displayId, callback)
+ shellTaskOrganizer.createRootTask(
+ displayId,
+ WINDOWING_MODE_FREEFORM,
+ /* listener = */ this,
+ /* removeWithTaskOrganizer = */ true,
+ )
+ }
+
+ override fun removeDesk(wct: WindowContainerTransaction, deskId: Int) {
+ logV("removeDesk %d", deskId)
+ val desk = checkNotNull(roots[deskId]) { "Root not found for desk: $deskId" }
+ wct.removeRootTask(desk.taskInfo.token)
+ }
+
+ override fun activateDesk(wct: WindowContainerTransaction, deskId: Int) {
+ logV("activateDesk %d", deskId)
+ val root = checkNotNull(roots[deskId]) { "Root not found for desk: $deskId" }
+ wct.reorder(root.taskInfo.token, /* onTop= */ true)
+ wct.setLaunchRoot(
+ /* container= */ root.taskInfo.token,
+ /* windowingModes= */ intArrayOf(WINDOWING_MODE_FREEFORM, WINDOWING_MODE_UNDEFINED),
+ /* activityTypes= */ intArrayOf(ACTIVITY_TYPE_UNDEFINED, ACTIVITY_TYPE_STANDARD),
+ )
+ }
+
+ override fun moveTaskToDesk(
+ wct: WindowContainerTransaction,
+ deskId: Int,
+ task: RunningTaskInfo,
+ ) {
+ val root = roots[deskId] ?: error("Root not found for desk: $deskId")
+ wct.reparent(task.token, root.taskInfo.token, /* onTop= */ true)
+ }
+
+ override fun getDeskAtEnd(change: TransitionInfo.Change): Int? =
+ change.taskInfo?.parentTaskId?.takeIf { it in roots }
+
+ override fun onTaskAppeared(taskInfo: RunningTaskInfo, leash: SurfaceControl) {
+ if (taskInfo.parentTaskId in roots) {
+ val deskId = taskInfo.parentTaskId
+ val taskId = taskInfo.taskId
+ logV("Task #$taskId appeared in desk #$deskId")
+ addChildToDesk(taskId = taskId, deskId = deskId)
+ return
+ }
+ val deskId = taskInfo.taskId
+ check(deskId !in roots) { "A root already exists for desk: $deskId" }
+ val request =
+ checkNotNull(deskCreateRequests.firstOrNull { it.displayId == taskInfo.displayId }) {
+ "Task ${taskInfo.taskId} appeared without pending create request"
+ }
+ logV("Desk #$deskId appeared")
+ roots[deskId] = DeskRoot(deskId, taskInfo, leash)
+ deskCreateRequests.remove(request)
+ request.onCreateCallback.onCreated(deskId)
+ }
+
+ override fun onTaskInfoChanged(taskInfo: RunningTaskInfo) {
+ if (roots.contains(taskInfo.taskId)) {
+ val deskId = taskInfo.taskId
+ roots[deskId] = roots[deskId].copy(taskInfo = taskInfo)
+ }
+ }
+
+ override fun onTaskVanished(taskInfo: RunningTaskInfo) {
+ if (roots.contains(taskInfo.taskId)) {
+ val deskId = taskInfo.taskId
+ val deskRoot = roots[deskId]
+ // Use the last saved taskInfo to obtain the displayId. Using the local one here will
+ // return -1 since the task is not unassociated with a display.
+ val displayId = deskRoot.taskInfo.displayId
+ logV("Desk #$deskId vanished from display #$displayId")
+ roots.remove(deskId)
+ return
+ }
+ // At this point, [parentTaskId] may be unset even if this is a task vanishing from a desk,
+ // so search through each root to remove this if it's a child.
+ roots.forEach { deskId, deskRoot ->
+ if (deskRoot.children.remove(taskInfo.taskId)) {
+ logV("Task #${taskInfo.taskId} vanished from desk #$deskId")
+ return
+ }
+ }
+ }
+
+ @VisibleForTesting
+ data class DeskRoot(
+ val deskId: Int,
+ val taskInfo: RunningTaskInfo,
+ val leash: SurfaceControl,
+ val children: MutableSet<Int> = mutableSetOf(),
+ )
+
+ override fun dump(pw: PrintWriter, prefix: String) {
+ val innerPrefix = "$prefix "
+ pw.println("$prefix$TAG")
+ pw.println("${innerPrefix}Desk Roots:")
+ roots.forEach { deskId, root ->
+ pw.println("$innerPrefix #$deskId visible=${root.taskInfo.isVisible}")
+ pw.println("$innerPrefix children=${root.children}")
+ }
+ }
+
+ private fun addChildToDesk(taskId: Int, deskId: Int) {
+ roots.forEach { _, deskRoot ->
+ if (deskRoot.deskId == deskId) {
+ deskRoot.children.add(taskId)
+ } else {
+ deskRoot.children.remove(taskId)
+ }
+ }
+ }
+
+ private data class CreateRequest(val displayId: Int, val onCreateCallback: OnCreateCallback)
+
+ private fun logV(msg: String, vararg arguments: Any?) {
+ ProtoLog.v(WM_SHELL_DESKTOP_MODE, "%s: $msg", TAG, *arguments)
+ }
+
+ companion object {
+ private const val TAG = "RootTaskDesksOrganizer"
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/persistence/DesktopRepositoryInitializerImpl.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/persistence/DesktopRepositoryInitializerImpl.kt
index 58a49a035bb6..5a89451ffdbc 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/persistence/DesktopRepositoryInitializerImpl.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/persistence/DesktopRepositoryInitializerImpl.kt
@@ -18,6 +18,7 @@ package com.android.wm.shell.desktopmode.persistence
import android.content.Context
import android.window.DesktopModeFlags
+import com.android.window.flags.Flags
import com.android.wm.shell.desktopmode.DesktopRepository
import com.android.wm.shell.desktopmode.DesktopUserRepositories
import com.android.wm.shell.shared.annotations.ShellMainThread
@@ -54,10 +55,22 @@ class DesktopRepositoryInitializerImpl(
DesktopModeStatus.getMaxTaskLimit(context).takeIf { it > 0 }
?: persistentDesktop.zOrderedTasksCount
var visibleTasksCount = 0
+ repository.addDesk(
+ displayId = persistentDesktop.displayId,
+ deskId =
+ if (Flags.enableMultipleDesktopsBackend()) {
+ persistentDesktop.desktopId
+ } else {
+ // When disabled, desk ids are always the display id.
+ persistentDesktop.displayId
+ },
+ )
persistentDesktop.zOrderedTasksList
// Reverse it so we initialize the repo from bottom to top.
.reversed()
.mapNotNull { taskId -> persistentDesktop.tasksByTaskIdMap[taskId] }
+ // TODO: b/362720497 - add tasks to their respective desk when multi-desk
+ // persistence is implemented.
.forEach { task ->
if (
task.desktopTaskState == DesktopTaskState.VISIBLE &&
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java
index e24b2c5f0134..e8996bc03eeb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java
@@ -31,6 +31,7 @@ import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMA
import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
+import android.annotation.UserIdInt;
import android.app.ActivityManager;
import android.app.ActivityTaskManager;
import android.app.PendingIntent;
@@ -125,6 +126,7 @@ public class DragAndDropController implements RemoteCallable<DragAndDropControll
* drag.
*/
default boolean onUnhandledDrag(@NonNull PendingIntent launchIntent,
+ @UserIdInt int userId,
@NonNull DragEvent dragEvent,
@NonNull Consumer<Boolean> onFinishCallback) {
return false;
@@ -444,8 +446,10 @@ public class DragAndDropController implements RemoteCallable<DragAndDropControll
return;
}
+ // TODO(b/391624027): Consider piping through launch intent user if needed later
+ final int userId = launchIntent.getCreatorUserHandle().getIdentifier();
final boolean handled = notifyListeners(
- l -> l.onUnhandledDrag(launchIntent, dragEvent, onFinishCallback));
+ l -> l.onUnhandledDrag(launchIntent, userId, dragEvent, onFinishCallback));
if (!handled) {
// Nobody handled this, we still have to notify WM
onFinishCallback.accept(false);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java
index b38a853321a7..897e2d1601a5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java
@@ -29,6 +29,7 @@ import android.window.DesktopModeFlags;
import com.android.internal.protolog.ProtoLog;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.LaunchAdjacentController;
+import com.android.wm.shell.desktopmode.DesktopModeLoggerTransitionObserver;
import com.android.wm.shell.desktopmode.DesktopRepository;
import com.android.wm.shell.desktopmode.DesktopTasksController;
import com.android.wm.shell.desktopmode.DesktopUserRepositories;
@@ -52,6 +53,7 @@ public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener,
private final ShellTaskOrganizer mShellTaskOrganizer;
private final Optional<DesktopUserRepositories> mDesktopUserRepositories;
private final Optional<DesktopTasksController> mDesktopTasksController;
+ private final DesktopModeLoggerTransitionObserver mDesktopModeLoggerTransitionObserver;
private final WindowDecorViewModel mWindowDecorationViewModel;
private final LaunchAdjacentController mLaunchAdjacentController;
private final Optional<TaskChangeListener> mTaskChangeListener;
@@ -64,6 +66,7 @@ public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener,
ShellTaskOrganizer shellTaskOrganizer,
Optional<DesktopUserRepositories> desktopUserRepositories,
Optional<DesktopTasksController> desktopTasksController,
+ DesktopModeLoggerTransitionObserver desktopModeLoggerTransitionObserver,
LaunchAdjacentController launchAdjacentController,
WindowDecorViewModel windowDecorationViewModel,
Optional<TaskChangeListener> taskChangeListener) {
@@ -72,6 +75,7 @@ public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener,
mWindowDecorationViewModel = windowDecorationViewModel;
mDesktopUserRepositories = desktopUserRepositories;
mDesktopTasksController = desktopTasksController;
+ mDesktopModeLoggerTransitionObserver = desktopModeLoggerTransitionObserver;
mLaunchAdjacentController = launchAdjacentController;
mTaskChangeListener = taskChangeListener;
if (shellInit != null) {
@@ -130,6 +134,9 @@ public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener,
repository.removeTask(taskInfo.displayId, taskInfo.taskId);
}
}
+ // TODO: b/367268649 - This listener shouldn't need to call the transition observer directly
+ // for logging once the logic in the observer is moved.
+ mDesktopModeLoggerTransitionObserver.onTaskVanished(taskInfo);
mWindowDecorationViewModel.onTaskVanished(taskInfo);
updateLaunchAdjacentController();
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PhonePipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PhonePipMenuController.java
index 44900ce1db8a..65099c2dfb9d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PhonePipMenuController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PhonePipMenuController.java
@@ -38,6 +38,7 @@ import com.android.internal.protolog.ProtoLog;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SystemWindows;
import com.android.wm.shell.common.pip.PipBoundsState;
+import com.android.wm.shell.common.pip.PipDisplayLayoutState;
import com.android.wm.shell.common.pip.PipMediaController;
import com.android.wm.shell.common.pip.PipMediaController.ActionListener;
import com.android.wm.shell.common.pip.PipMenuController;
@@ -121,6 +122,9 @@ public class PhonePipMenuController implements PipMenuController,
@NonNull
private final PipTransitionState mPipTransitionState;
+ @NonNull
+ private final PipDisplayLayoutState mPipDisplayLayoutState;
+
private SurfaceControl mLeash;
private ActionListener mMediaActionListener = new ActionListener() {
@@ -134,7 +138,8 @@ public class PhonePipMenuController implements PipMenuController,
public PhonePipMenuController(Context context, PipBoundsState pipBoundsState,
PipMediaController mediaController, SystemWindows systemWindows,
PipUiEventLogger pipUiEventLogger, PipTaskListener pipTaskListener,
- @NonNull PipTransitionState pipTransitionState, ShellExecutor mainExecutor,
+ @NonNull PipTransitionState pipTransitionState,
+ @NonNull PipDisplayLayoutState pipDisplayLayoutState, ShellExecutor mainExecutor,
Handler mainHandler) {
mContext = context;
mPipBoundsState = pipBoundsState;
@@ -142,6 +147,7 @@ public class PhonePipMenuController implements PipMenuController,
mSystemWindows = systemWindows;
mPipTaskListener = pipTaskListener;
mPipTransitionState = pipTransitionState;
+ mPipDisplayLayoutState = pipDisplayLayoutState;
mMainExecutor = mainExecutor;
mMainHandler = mainHandler;
mPipUiEventLogger = pipUiEventLogger;
@@ -218,7 +224,7 @@ public class PhonePipMenuController implements PipMenuController,
mSystemWindows.addView(mPipMenuView,
getPipMenuLayoutParams(mContext, MENU_WINDOW_TITLE, 0 /* width */, 0 /* height */),
- 0, SHELL_ROOT_LAYER_PIP);
+ mPipDisplayLayoutState.getDisplayId(), SHELL_ROOT_LAYER_PIP);
setShellRootAccessibilityWindow();
// Make sure the initial actions are set
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
index b1984ccef4cb..99c9302edb75 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
@@ -19,6 +19,7 @@ package com.android.wm.shell.pip2.phone;
import static android.app.WindowConfiguration.ROTATION_UNDEFINED;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.content.pm.PackageManager.FEATURE_PICTURE_IN_PICTURE;
+import static android.view.Display.DEFAULT_DISPLAY;
import android.annotation.NonNull;
import android.app.ActivityManager;
@@ -219,6 +220,7 @@ public class PipController implements ConfigurationChangeListener,
mPipDisplayLayoutState.setDisplayLayout(layout);
mDisplayController.addDisplayChangingController(this);
+ mDisplayController.addDisplayWindowListener(this);
mDisplayInsetsController.addInsetsChangedListener(mPipDisplayLayoutState.getDisplayId(),
new ImeListener(mDisplayController, mPipDisplayLayoutState.getDisplayId()) {
@Override
@@ -297,6 +299,22 @@ public class PipController implements ConfigurationChangeListener,
setDisplayLayout(mDisplayController.getDisplayLayout(displayId));
}
+ @Override
+ public void onDisplayRemoved(int displayId) {
+ // If PiP was active on an external display that is removed, clean up states and set
+ // {@link PipDisplayLayoutState} to DEFAULT_DISPLAY.
+ if (Flags.enableConnectedDisplaysPip() && mPipTransitionState.isInPip()
+ && displayId == mPipDisplayLayoutState.getDisplayId()
+ && displayId != DEFAULT_DISPLAY) {
+ mPipTransitionState.setState(PipTransitionState.EXITING_PIP);
+ mPipTransitionState.setState(PipTransitionState.EXITED_PIP);
+
+ mPipDisplayLayoutState.setDisplayId(DEFAULT_DISPLAY);
+ mPipDisplayLayoutState.setDisplayLayout(
+ mDisplayController.getDisplayLayout(DEFAULT_DISPLAY));
+ }
+ }
+
/**
* A callback for any observed transition that contains a display change in its
* {@link android.window.TransitionRequestInfo},
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipDismissTargetHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipDismissTargetHandler.java
index b3070f29c6e2..71697596afd3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipDismissTargetHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipDismissTargetHandler.java
@@ -23,6 +23,7 @@ import android.content.res.Resources;
import android.graphics.PixelFormat;
import android.graphics.Point;
import android.graphics.Rect;
+import android.view.Display;
import android.view.MotionEvent;
import android.view.SurfaceControl;
import android.view.View;
@@ -34,7 +35,9 @@ import androidx.annotation.NonNull;
import com.android.wm.shell.R;
import com.android.wm.shell.bubbles.DismissViewUtils;
+import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.common.pip.PipDisplayLayoutState;
import com.android.wm.shell.common.pip.PipUiEventLogger;
import com.android.wm.shell.shared.bubbles.DismissCircleView;
import com.android.wm.shell.shared.bubbles.DismissView;
@@ -50,6 +53,9 @@ public class PipDismissTargetHandler implements ViewTreeObserver.OnPreDrawListen
/* The multiplier to apply scale the target size by when applying the magnetic field radius */
private static final float MAGNETIC_FIELD_RADIUS_MULTIPLIER = 1.25f;
+ /* The window type to apply to the display */
+ private static final int WINDOW_TYPE = WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL;
+
/**
* MagnetizedObject wrapper for PIP. This allows the magnetic target library to locate and move
* PIP.
@@ -84,16 +90,22 @@ public class PipDismissTargetHandler implements ViewTreeObserver.OnPreDrawListen
private final Context mContext;
private final PipMotionHelper mMotionHelper;
private final PipUiEventLogger mPipUiEventLogger;
- private final WindowManager mWindowManager;
+ private WindowManager mWindowManager;
+ /** The display id for the display that is associated with mWindowManager. */
+ private int mWindowManagerDisplayId = -1;
+ private final PipDisplayLayoutState mPipDisplayLayoutState;
+ private final DisplayController mDisplayController;
private final ShellExecutor mMainExecutor;
public PipDismissTargetHandler(Context context, PipUiEventLogger pipUiEventLogger,
- PipMotionHelper motionHelper, ShellExecutor mainExecutor) {
+ PipMotionHelper motionHelper, PipDisplayLayoutState pipDisplayLayoutState,
+ DisplayController displayController, ShellExecutor mainExecutor) {
mContext = context;
mPipUiEventLogger = pipUiEventLogger;
mMotionHelper = motionHelper;
+ mPipDisplayLayoutState = pipDisplayLayoutState;
+ mDisplayController = displayController;
mMainExecutor = mainExecutor;
- mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
}
void init() {
@@ -240,6 +252,8 @@ public class PipDismissTargetHandler implements ViewTreeObserver.OnPreDrawListen
/** Adds the magnetic target view to the WindowManager so it's ready to be animated in. */
public void createOrUpdateDismissTarget() {
+ getWindowManager();
+
if (mTargetViewContainer.getParent() == null) {
mTargetViewContainer.cancelAnimators();
@@ -262,7 +276,7 @@ public class PipDismissTargetHandler implements ViewTreeObserver.OnPreDrawListen
WindowManager.LayoutParams.MATCH_PARENT,
height,
0, windowSize.y - height,
- WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL,
+ WINDOW_TYPE,
WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
| WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
| WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
@@ -308,4 +322,16 @@ public class PipDismissTargetHandler implements ViewTreeObserver.OnPreDrawListen
mWindowManager.removeViewImmediate(mTargetViewContainer);
}
}
+
+ /** Sets mWindowManager to WindowManager associated with the display where PiP is active on. */
+ private void getWindowManager() {
+ final int pipDisplayId = mPipDisplayLayoutState.getDisplayId();
+ if (mWindowManager != null && pipDisplayId == mWindowManagerDisplayId) {
+ return;
+ }
+ mWindowManagerDisplayId = pipDisplayId;
+ Display display = mDisplayController.getDisplay(mWindowManagerDisplayId);
+ Context uiContext = mContext.createWindowContext(display, WINDOW_TYPE, null);
+ mWindowManager = uiContext.getSystemService(WindowManager.class);
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipInputConsumer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipInputConsumer.java
index ffda56d89276..0a0ecffbea1f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipInputConsumer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipInputConsumer.java
@@ -16,8 +16,6 @@
package com.android.wm.shell.pip2.phone;
-import static android.view.Display.DEFAULT_DISPLAY;
-
import android.os.Binder;
import android.os.IBinder;
import android.os.Looper;
@@ -30,6 +28,7 @@ import android.view.InputEvent;
import com.android.internal.protolog.ProtoLog;
import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.common.pip.PipDisplayLayoutState;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import java.io.PrintWriter;
@@ -84,6 +83,7 @@ public class PipInputConsumer {
private final IWindowManager mWindowManager;
private final IBinder mToken;
private final String mName;
+ private final PipDisplayLayoutState mPipDisplayLayoutState;
private final ShellExecutor mMainExecutor;
private InputEventReceiver mInputEventReceiver;
@@ -94,10 +94,11 @@ public class PipInputConsumer {
* @param name the name corresponding to the input consumer that is defined in the system.
*/
public PipInputConsumer(IWindowManager windowManager, String name,
- ShellExecutor mainExecutor) {
+ PipDisplayLayoutState pipDisplayLayoutState, ShellExecutor mainExecutor) {
mWindowManager = windowManager;
mToken = new Binder();
mName = name;
+ mPipDisplayLayoutState = pipDisplayLayoutState;
mMainExecutor = mainExecutor;
}
@@ -138,9 +139,9 @@ public class PipInputConsumer {
}
final InputChannel inputChannel = new InputChannel();
try {
- // TODO(b/113087003): Support Picture-in-picture in multi-display.
- mWindowManager.destroyInputConsumer(mToken, DEFAULT_DISPLAY);
- mWindowManager.createInputConsumer(mToken, mName, DEFAULT_DISPLAY, inputChannel);
+ final int displayId = mPipDisplayLayoutState.getDisplayId();
+ mWindowManager.destroyInputConsumer(mToken, displayId);
+ mWindowManager.createInputConsumer(mToken, mName, displayId, inputChannel);
} catch (RemoteException e) {
ProtoLog.e(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"%s: Failed to create input consumer, %s", TAG, e);
@@ -162,8 +163,7 @@ public class PipInputConsumer {
return;
}
try {
- // TODO(b/113087003): Support Picture-in-picture in multi-display.
- mWindowManager.destroyInputConsumer(mToken, DEFAULT_DISPLAY);
+ mWindowManager.destroyInputConsumer(mToken, mPipDisplayLayoutState.getDisplayId());
} catch (RemoteException e) {
ProtoLog.e(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"%s: Failed to destroy input consumer, %s", TAG, e);
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 d98be55f28e1..e4be3f60f86e 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
@@ -44,6 +44,7 @@ import com.android.wm.shell.R;
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.PipDisplayLayoutState;
import com.android.wm.shell.common.pip.PipPerfHintController;
import com.android.wm.shell.common.pip.PipPinchResizingAlgorithm;
import com.android.wm.shell.common.pip.PipUiEventLogger;
@@ -70,9 +71,9 @@ public class PipResizeGestureHandler implements
private final PipScheduler mPipScheduler;
private final PipTransitionState mPipTransitionState;
private final PhonePipMenuController mPhonePipMenuController;
+ private final PipDisplayLayoutState mPipDisplayLayoutState;
private final PipUiEventLogger mPipUiEventLogger;
private final PipPinchResizingAlgorithm mPinchResizingAlgorithm;
- private final int mDisplayId;
private final ShellExecutor mMainExecutor;
private final PointF mDownPoint = new PointF();
@@ -120,10 +121,10 @@ public class PipResizeGestureHandler implements
PipTransitionState pipTransitionState,
PipUiEventLogger pipUiEventLogger,
PhonePipMenuController menuActivityController,
+ PipDisplayLayoutState pipDisplayLayoutState,
ShellExecutor mainExecutor,
@Nullable PipPerfHintController pipPerfHintController) {
mContext = context;
- mDisplayId = context.getDisplayId();
mMainExecutor = mainExecutor;
mPipPerfHintController = pipPerfHintController;
mPipBoundsAlgorithm = pipBoundsAlgorithm;
@@ -135,6 +136,7 @@ public class PipResizeGestureHandler implements
mPipTransitionState.addPipTransitionStateChangedListener(this);
mPhonePipMenuController = menuActivityController;
+ mPipDisplayLayoutState = pipDisplayLayoutState;
mPipUiEventLogger = pipUiEventLogger;
mPinchResizingAlgorithm = new PipPinchResizingAlgorithm();
}
@@ -197,7 +199,7 @@ public class PipResizeGestureHandler implements
if (mIsEnabled) {
// Register input event receiver
mInputMonitor = mContext.getSystemService(InputManager.class).monitorGestureInput(
- "pip-resize", mDisplayId);
+ "pip-resize", mPipDisplayLayoutState.getDisplayId());
try {
mMainExecutor.executeBlocking(() -> {
mInputEventReceiver = new PipResizeInputEventReceiver(
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 fc3fbe299605..35cd1a2e681f 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
@@ -54,10 +54,12 @@ import android.view.accessibility.AccessibilityWindowInfo;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.ProtoLog;
import com.android.wm.shell.R;
+import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.FloatingContentCoordinator;
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.PipDisplayLayoutState;
import com.android.wm.shell.common.pip.PipDoubleTapHelper;
import com.android.wm.shell.common.pip.PipPerfHintController;
import com.android.wm.shell.common.pip.PipUiEventLogger;
@@ -91,6 +93,7 @@ public class PipTouchHandler implements PipTransitionState.PipTransitionStateCha
@NonNull private final PipTransitionState mPipTransitionState;
@NonNull private final PipScheduler mPipScheduler;
@NonNull private final SizeSpecSource mSizeSpecSource;
+ @NonNull private final PipDisplayLayoutState mPipDisplayLayoutState;
private final PipUiEventLogger mPipUiEventLogger;
private final PipDismissTargetHandler mPipDismissTargetHandler;
private final ShellExecutor mMainExecutor;
@@ -183,6 +186,8 @@ public class PipTouchHandler implements PipTransitionState.PipTransitionStateCha
@NonNull PipTransitionState pipTransitionState,
@NonNull PipScheduler pipScheduler,
@NonNull SizeSpecSource sizeSpecSource,
+ @NonNull PipDisplayLayoutState pipDisplayLayoutState,
+ DisplayController displayController,
PipMotionHelper pipMotionHelper,
FloatingContentCoordinator floatingContentCoordinator,
PipUiEventLogger pipUiEventLogger,
@@ -200,6 +205,7 @@ public class PipTouchHandler implements PipTransitionState.PipTransitionStateCha
mPipTransitionState.addPipTransitionStateChangedListener(this::onPipTransitionStateChanged);
mPipScheduler = pipScheduler;
mSizeSpecSource = sizeSpecSource;
+ mPipDisplayLayoutState = pipDisplayLayoutState;
mMenuController = menuController;
mPipUiEventLogger = pipUiEventLogger;
mFloatingContentCoordinator = floatingContentCoordinator;
@@ -208,7 +214,7 @@ public class PipTouchHandler implements PipTransitionState.PipTransitionStateCha
mMotionHelper = pipMotionHelper;
mPipScheduler.setUpdateMovementBoundsRunnable(this::updateMovementBounds);
mPipDismissTargetHandler = new PipDismissTargetHandler(context, pipUiEventLogger,
- mMotionHelper, mainExecutor);
+ mMotionHelper, mPipDisplayLayoutState, displayController, mainExecutor);
mTouchState = new PipTouchState(ViewConfiguration.get(context),
() -> {
mMenuController.showMenuWithPossibleDelay(MENU_STATE_FULL,
@@ -220,8 +226,7 @@ public class PipTouchHandler implements PipTransitionState.PipTransitionStateCha
mainExecutor);
mPipResizeGestureHandler = new PipResizeGestureHandler(context, pipBoundsAlgorithm,
pipBoundsState, mTouchState, mPipScheduler, mPipTransitionState, pipUiEventLogger,
- menuController, mainExecutor,
- mPipPerfHintController);
+ menuController, mPipDisplayLayoutState, mainExecutor, mPipPerfHintController);
mPipBoundsState.addOnAspectRatioChangedCallback(aspectRatio -> {
updateMinMaxSize(aspectRatio);
onAspectRatioChanged();
@@ -264,7 +269,7 @@ public class PipTouchHandler implements PipTransitionState.PipTransitionStateCha
mPipDismissTargetHandler.init();
mPipInputConsumer = new PipInputConsumer(WindowManagerGlobal.getWindowManagerService(),
- INPUT_CONSUMER_PIP, mMainExecutor);
+ INPUT_CONSUMER_PIP, mPipDisplayLayoutState, mMainExecutor);
mPipInputConsumer.setInputListener(this::handleTouchEvent);
mPipInputConsumer.setRegistrationListener(this::onRegistrationChanged);
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 272cb4372acf..03327bf463e3 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
@@ -70,6 +70,7 @@ import com.android.wm.shell.desktopmode.DesktopRepository;
import com.android.wm.shell.desktopmode.DesktopUserRepositories;
import com.android.wm.shell.desktopmode.desktopwallpaperactivity.DesktopWallpaperActivityTokenProvider;
import com.android.wm.shell.pip.PipTransitionController;
+import com.android.wm.shell.pip2.PipSurfaceTransactionHelper;
import com.android.wm.shell.pip2.animation.PipAlphaAnimator;
import com.android.wm.shell.pip2.animation.PipEnterAnimator;
import com.android.wm.shell.pip2.animation.PipExpandAnimator;
@@ -115,6 +116,7 @@ public class PipTransition extends PipTransitionController implements
private final PipTransitionState mPipTransitionState;
private final PipDisplayLayoutState mPipDisplayLayoutState;
private final DisplayController mDisplayController;
+ private final PipSurfaceTransactionHelper mPipSurfaceTransactionHelper;
private final Optional<DesktopUserRepositories> mDesktopUserRepositoriesOptional;
private final Optional<DesktopWallpaperActivityTokenProvider>
mDesktopWallpaperActivityTokenProviderOptional;
@@ -171,6 +173,7 @@ public class PipTransition extends PipTransitionController implements
mPipTransitionState.addPipTransitionStateChangedListener(this);
mPipDisplayLayoutState = pipDisplayLayoutState;
mDisplayController = displayController;
+ mPipSurfaceTransactionHelper = new PipSurfaceTransactionHelper(mContext);
mDesktopUserRepositoriesOptional = desktopUserRepositoriesOptional;
mDesktopWallpaperActivityTokenProviderOptional =
desktopWallpaperActivityTokenProviderOptional;
@@ -343,6 +346,25 @@ public class PipTransition extends PipTransitionController implements
}
}
+ @Override
+ public boolean syncPipSurfaceState(@NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction) {
+ final TransitionInfo.Change pipChange = getPipChange(info);
+ if (pipChange == null) return false;
+
+ // add shadow and corner radii
+ final SurfaceControl leash = pipChange.getLeash();
+ final boolean isInPip = mPipTransitionState.isInPip();
+
+ mPipSurfaceTransactionHelper.round(startTransaction, leash, isInPip)
+ .shadow(startTransaction, leash, isInPip);
+ mPipSurfaceTransactionHelper.round(finishTransaction, leash, isInPip)
+ .shadow(finishTransaction, leash, isInPip);
+
+ return true;
+ }
+
//
// Animation schedulers and entry points
//
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 2d4d458292ea..4f2e028a1df0 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
@@ -311,6 +311,9 @@ public class RecentTasksController implements TaskStackListenerCallback,
public void onTaskAdded(RunningTaskInfo taskInfo) {
notifyRunningTaskAppeared(taskInfo);
+ if (!enableShellTopTaskTracking()) {
+ notifyRecentTasksChanged();
+ }
}
public void onTaskRemoved(RunningTaskInfo taskInfo) {
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 afc6fee2eca3..55133780f517 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
@@ -222,7 +222,7 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler,
RecentsMixedHandler mixer = null;
Consumer<IBinder> setTransitionForMixer = null;
for (int i = 0; i < mMixers.size(); ++i) {
- setTransitionForMixer = mMixers.get(i).handleRecentsRequest(wct);
+ setTransitionForMixer = mMixers.get(i).handleRecentsRequest();
if (setTransitionForMixer != null) {
mixer = mMixers.get(i);
break;
@@ -1455,6 +1455,11 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler,
}
}
+ // Notify the mixers of the pending finish
+ for (int i = 0; i < mMixers.size(); ++i) {
+ mMixers.get(i).handleFinishRecents(returningToApp, wct, t);
+ }
+
if (Flags.enableRecentsBookendTransition()) {
if (!wct.isEmpty()) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
@@ -1653,15 +1658,22 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler,
*/
public interface RecentsMixedHandler extends Transitions.TransitionHandler {
/**
- * Called when a recents request comes in. The handler can add operations to outWCT. If
- * the handler wants to "accept" the transition, it should return a Consumer accepting the
- * IBinder for the transition. If not, it should return `null`.
+ * Called when a recents request comes in. If the handler wants to "accept" the transition,
+ * it should return a Consumer accepting the IBinder for the transition. If not, it should
+ * return `null`.
*
* If a mixed-handler accepts this recents, it will be the de-facto handler for this
* transition and is required to call the associated {@link #startAnimation},
* {@link #mergeAnimation}, and {@link #onTransitionConsumed} methods.
*/
@Nullable
- Consumer<IBinder> handleRecentsRequest(WindowContainerTransaction outWCT);
+ Consumer<IBinder> handleRecentsRequest();
+
+ /**
+ * Called when a recents transition has finished, with a WCT and SurfaceControl Transaction
+ * that can be used to add to any changes needed to restore the state.
+ */
+ void handleFinishRecents(boolean returnToApp, @NonNull WindowContainerTransaction finishWct,
+ @NonNull SurfaceControl.Transaction finishT);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
index 99a89a6b884f..ae0159263364 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
@@ -649,11 +649,12 @@ public class SplitScreenController implements SplitDragPolicy.Starter,
@Nullable Bundle options, UserHandle user) {
if (options == null) options = new Bundle();
final ActivityOptions activityOptions = ActivityOptions.fromBundle(options);
+ final int userId = user.getIdentifier();
if (samePackage(packageName, getPackageName(reverseSplitPosition(position), null),
- user.getIdentifier(), getUserId(reverseSplitPosition(position), null))) {
+ userId, getUserId(reverseSplitPosition(position), null))) {
if (mMultiInstanceHelpher.supportsMultiInstanceSplit(
- getShortcutComponent(packageName, shortcutId, user, mLauncherApps))) {
+ getShortcutComponent(packageName, shortcutId, user, mLauncherApps), userId)) {
activityOptions.setApplyMultipleTaskFlagForShortcut(true);
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Adding MULTIPLE_TASK");
} else if (isSplitScreenVisible()) {
@@ -687,7 +688,8 @@ public class SplitScreenController implements SplitDragPolicy.Starter,
final int userId1 = shortcutInfo.getUserId();
final int userId2 = SplitScreenUtils.getUserId(taskId, mTaskOrganizer);
if (samePackage(packageName1, packageName2, userId1, userId2)) {
- if (mMultiInstanceHelpher.supportsMultiInstanceSplit(shortcutInfo.getActivity())) {
+ if (mMultiInstanceHelpher.supportsMultiInstanceSplit(shortcutInfo.getActivity(),
+ userId1)) {
activityOptions.setApplyMultipleTaskFlagForShortcut(true);
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Adding MULTIPLE_TASK");
} else {
@@ -735,7 +737,8 @@ public class SplitScreenController implements SplitDragPolicy.Starter,
final int userId2 = SplitScreenUtils.getUserId(taskId, mTaskOrganizer);
boolean setSecondIntentMultipleTask = false;
if (samePackage(packageName1, packageName2, userId1, userId2)) {
- if (mMultiInstanceHelpher.supportsMultiInstanceSplit(getComponent(pendingIntent))) {
+ if (mMultiInstanceHelpher.supportsMultiInstanceSplit(getComponent(pendingIntent),
+ userId1)) {
setSecondIntentMultipleTask = true;
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Adding MULTIPLE_TASK");
} else {
@@ -775,7 +778,8 @@ public class SplitScreenController implements SplitDragPolicy.Starter,
? ActivityOptions.fromBundle(options2) : ActivityOptions.makeBasic();
boolean setSecondIntentMultipleTask = false;
if (samePackage(packageName1, packageName2, userId1, userId2)) {
- if (mMultiInstanceHelpher.supportsMultiInstanceSplit(getComponent(pendingIntent1))) {
+ if (mMultiInstanceHelpher.supportsMultiInstanceSplit(getComponent(pendingIntent1),
+ userId1)) {
fillInIntent1 = new Intent();
fillInIntent1.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
setSecondIntentMultipleTask = true;
@@ -858,7 +862,7 @@ public class SplitScreenController implements SplitDragPolicy.Starter,
return;
}
if (samePackage(packageName1, packageName2, userId1, userId2)) {
- if (mMultiInstanceHelpher.supportsMultiInstanceSplit(getComponent(intent))) {
+ if (mMultiInstanceHelpher.supportsMultiInstanceSplit(getComponent(intent), userId1)) {
// Flag with MULTIPLE_TASK if this is launching the same activity into both sides of
// the split and there is no reusable background task.
fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
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 511e426cc681..722494c05e32 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
@@ -129,6 +129,7 @@ import com.android.internal.logging.InstanceId;
import com.android.internal.policy.FoldLockSettingsObserver;
import com.android.internal.protolog.ProtoLog;
import com.android.launcher3.icons.IconProvider;
+import com.android.wm.shell.Flags;
import com.android.wm.shell.R;
import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
import com.android.wm.shell.ShellTaskOrganizer;
@@ -3766,13 +3767,31 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mTaskOrganizer.applyTransaction(wct);
}
+ public void onRecentsInSplitAnimationFinishing(boolean returnToApp,
+ @NonNull WindowContainerTransaction finishWct,
+ @NonNull SurfaceControl.Transaction finishT) {
+ if (!Flags.enableRecentsBookendTransition()) {
+ // The non-bookend recents transition case will be handled by
+ // RecentsMixedTransition wrapping the finish callback and calling
+ // onRecentsInSplitAnimationFinish()
+ return;
+ }
+
+ onRecentsInSplitAnimationFinishInner(returnToApp, finishWct, finishT);
+ }
+
/** Call this when the recents animation during split-screen finishes. */
- public void onRecentsInSplitAnimationFinish(WindowContainerTransaction finishWct,
- SurfaceControl.Transaction finishT) {
- ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onRecentsInSplitAnimationFinish");
- mPausingTasks.clear();
+ public void onRecentsInSplitAnimationFinish(@NonNull WindowContainerTransaction finishWct,
+ @NonNull SurfaceControl.Transaction finishT) {
+ if (Flags.enableRecentsBookendTransition()) {
+ // The bookend recents transition case will be handled by
+ // onRecentsInSplitAnimationFinishing above
+ return;
+ }
+
// Check if the recent transition is finished by returning to the current
// split, so we can restore the divider bar.
+ boolean returnToApp = false;
for (int i = 0; i < finishWct.getHierarchyOps().size(); ++i) {
final WindowContainerTransaction.HierarchyOp op =
finishWct.getHierarchyOps().get(i);
@@ -3787,13 +3806,26 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
}
if (op.getType() == HIERARCHY_OP_TYPE_REORDER && op.getToTop()
&& anyStageContainsContainer) {
- updateSurfaceBounds(mSplitLayout, finishT,
- false /* applyResizingOffset */);
- finishT.reparent(mSplitLayout.getDividerLeash(), mRootTaskLeash);
- setDividerVisibility(true, finishT);
- return;
+ returnToApp = true;
}
}
+ onRecentsInSplitAnimationFinishInner(returnToApp, finishWct, finishT);
+ }
+
+ /** Call this when the recents animation during split-screen finishes. */
+ public void onRecentsInSplitAnimationFinishInner(boolean returnToApp,
+ @NonNull WindowContainerTransaction finishWct,
+ @NonNull SurfaceControl.Transaction finishT) {
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onRecentsInSplitAnimationFinish: returnToApp=%b",
+ returnToApp);
+ mPausingTasks.clear();
+ if (returnToApp) {
+ updateSurfaceBounds(mSplitLayout, finishT,
+ false /* applyResizingOffset */);
+ finishT.reparent(mSplitLayout.getDividerLeash(), mRootTaskLeash);
+ setDividerVisibility(true, finishT);
+ return;
+ }
setSplitsVisible(false);
finishWct.setReparentLeafTaskIfRelaunch(mRootTaskInfo.token,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskView.java
index 0445add9cba9..13d87eda085b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskView.java
@@ -92,6 +92,10 @@ public class TaskView extends SurfaceView implements SurfaceHolder.Callback,
getHolder().addCallback(this);
}
+ public TaskViewTaskController getController() {
+ return mTaskViewTaskController;
+ }
+
/**
* Launch a new activity.
*
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java
index d19a7eac6ad2..a0cc2bc8887b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java
@@ -417,7 +417,8 @@ public class TaskViewTaskController implements ShellTaskOrganizer.TaskListener {
}
}
- void notifyTaskRemovalStarted(@NonNull ActivityManager.RunningTaskInfo taskInfo) {
+ /** Notifies listeners of a task being removed. */
+ public void notifyTaskRemovalStarted(@NonNull ActivityManager.RunningTaskInfo taskInfo) {
if (mListener == null) return;
final int taskId = taskInfo.taskId;
mListenerExecutor.execute(() -> mListener.onTaskRemovalStarted(taskId));
@@ -448,7 +449,7 @@ public class TaskViewTaskController implements ShellTaskOrganizer.TaskListener {
* have the pending info, we'll do it when we receive it in
* {@link #onTaskAppeared(ActivityManager.RunningTaskInfo, SurfaceControl)}.
*/
- void setTaskNotFound() {
+ public void setTaskNotFound() {
mTaskNotFound = true;
if (mPendingInfo != null) {
cleanUpPendingTask();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java
index 6c90a9060523..1eaae7ec83d9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java
@@ -20,6 +20,7 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_CLOSE;
+import static android.view.WindowManager.TRANSIT_NONE;
import static android.view.WindowManager.TRANSIT_OPEN;
import static android.view.WindowManager.TRANSIT_TO_BACK;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
@@ -66,7 +67,7 @@ public class TaskViewTransitions implements Transitions.TransitionHandler, TaskV
static final String TAG = "TaskViewTransitions";
/**
- * Map of {@link TaskViewTaskController} to {@link TaskViewRequestedState}.
+ * Map of {@link TaskViewTaskController} to {@link TaskViewRepository.TaskViewState}.
* <p>
* {@link TaskView} keeps a reference to the {@link TaskViewTaskController} instance and
* manages its lifecycle.
@@ -95,6 +96,7 @@ public class TaskViewTransitions implements Transitions.TransitionHandler, TaskV
final @WindowManager.TransitionType int mType;
final WindowContainerTransaction mWct;
final @NonNull TaskViewTaskController mTaskView;
+ ExternalTransition mExternalTransition;
IBinder mClaimed;
/**
@@ -182,6 +184,32 @@ public class TaskViewTransitions implements Transitions.TransitionHandler, TaskV
}
/**
+ * Starts or queues an "external" runnable into the pending queue. This means it will run
+ * in order relative to the local transitions.
+ *
+ * The external operation *must* call {@link #onExternalDone} once it has finished.
+ *
+ * In practice, the external is usually another transition on a different handler.
+ */
+ public void enqueueExternal(@NonNull TaskViewTaskController taskView, ExternalTransition ext) {
+ final PendingTransition pending = new PendingTransition(
+ TRANSIT_NONE, null /* wct */, taskView, null /* cookie */);
+ pending.mExternalTransition = ext;
+ mPending.add(pending);
+ startNextTransition();
+ }
+
+ /**
+ * An external transition run in this "queue" is required to call this once it becomes ready.
+ */
+ public void onExternalDone(IBinder key) {
+ final PendingTransition pending = findPending(key);
+ if (pending == null) return;
+ mPending.remove(pending);
+ startNextTransition();
+ }
+
+ /**
* Looks through the pending transitions for a opening transaction that matches the provided
* `taskView`.
*
@@ -191,6 +219,7 @@ public class TaskViewTransitions implements Transitions.TransitionHandler, TaskV
PendingTransition findPendingOpeningTransition(TaskViewTaskController taskView) {
for (int i = mPending.size() - 1; i >= 0; --i) {
if (mPending.get(i).mTaskView != taskView) continue;
+ if (mPending.get(i).mExternalTransition != null) continue;
if (TransitionUtil.isOpeningType(mPending.get(i).mType)) {
return mPending.get(i);
}
@@ -207,6 +236,7 @@ public class TaskViewTransitions implements Transitions.TransitionHandler, TaskV
PendingTransition findPending(TaskViewTaskController taskView, int type) {
for (int i = mPending.size() - 1; i >= 0; --i) {
if (mPending.get(i).mTaskView != taskView) continue;
+ if (mPending.get(i).mExternalTransition != null) continue;
if (mPending.get(i).mType == type) {
return mPending.get(i);
}
@@ -518,7 +548,11 @@ public class TaskViewTransitions implements Transitions.TransitionHandler, TaskV
// Wait for this to start animating.
return;
}
- pending.mClaimed = mTransitions.startTransition(pending.mType, pending.mWct, this);
+ if (pending.mExternalTransition != null) {
+ pending.mClaimed = pending.mExternalTransition.start();
+ } else {
+ pending.mClaimed = mTransitions.startTransition(pending.mType, pending.mWct, this);
+ }
}
@Override
@@ -641,7 +675,7 @@ public class TaskViewTransitions implements Transitions.TransitionHandler, TaskV
}
@VisibleForTesting
- void prepareOpenAnimation(TaskViewTaskController taskView,
+ public void prepareOpenAnimation(TaskViewTaskController taskView,
final boolean newTask,
SurfaceControl.Transaction startTransaction,
SurfaceControl.Transaction finishTransaction,
@@ -695,4 +729,10 @@ public class TaskViewTransitions implements Transitions.TransitionHandler, TaskV
taskView.notifyAppeared(newTask);
}
+
+ /** Interface for running an external transition in this object's pending queue. */
+ public interface ExternalTransition {
+ /** Starts a transition and returns an identifying key for lookup. */
+ IBinder start();
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
index 2177986bccd5..d8e7c2ccb15f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
@@ -367,7 +367,7 @@ public class DefaultMixedHandler implements MixedTransitionHandler,
}
@Override
- public Consumer<IBinder> handleRecentsRequest(WindowContainerTransaction outWCT) {
+ public Consumer<IBinder> handleRecentsRequest() {
if (mRecentsHandler != null) {
if (mSplitHandler.isSplitScreenVisible()) {
return this::setRecentsTransitionDuringSplit;
@@ -383,6 +383,21 @@ public class DefaultMixedHandler implements MixedTransitionHandler,
return null;
}
+ @Override
+ public void handleFinishRecents(boolean returnToApp,
+ @NonNull WindowContainerTransaction finishWct,
+ @NonNull SurfaceControl.Transaction finishT) {
+ if (mRecentsHandler != null) {
+ for (int i = mActiveTransitions.size() - 1; i >= 0; --i) {
+ final MixedTransition mixed = mActiveTransitions.get(i);
+ if (mixed.mType == MixedTransition.TYPE_RECENTS_DURING_SPLIT) {
+ ((RecentsMixedTransition) mixed).onAnimateRecentsDuringSplitFinishing(
+ returnToApp, finishWct, finishT);
+ }
+ }
+ }
+ }
+
private void setRecentsTransitionDuringSplit(IBinder transition) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Got a recents request while "
+ "Split-Screen is foreground, so treat it as Mixed.");
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedTransition.java
index b0547a2a47b1..29a58d7f75dc 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedTransition.java
@@ -407,8 +407,6 @@ class DefaultMixedTransition extends DefaultMixedHandler.MixedTransition {
mLeftoversHandler.mergeAnimation(
transition, info, t, mergeTarget, finishCallback);
}
- } else {
- mPipHandler.end();
}
return;
case TYPE_KEYGUARD:
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 36c3e9711f5c..ac6e4c5cd69e 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
@@ -306,10 +306,10 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
}
// Early check if the transition doesn't warrant an animation.
- if (Transitions.isAllNoAnimation(info) || Transitions.isAllOrderOnly(info)
+ if (TransitionUtil.isAllNoAnimation(info) || TransitionUtil.isAllOrderOnly(info)
|| (info.getFlags() & WindowManager.TRANSIT_FLAG_INVISIBLE) != 0) {
startTransaction.apply();
- finishTransaction.apply();
+ // As a contract, finishTransaction should only be applied in Transitions#onFinish
finishCallback.onTransitionFinished(null /* wct */);
return true;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/MixedTransitionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/MixedTransitionHelper.java
index 30ffdac5cbba..357861cc3ca7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/MixedTransitionHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/MixedTransitionHelper.java
@@ -161,13 +161,10 @@ public class MixedTransitionHelper {
pipHandler.startEnterAnimation(pipChange, startTransaction, finishTransaction,
finishCB);
}
- // make a new finishTransaction because pip's startEnterAnimation "consumes" it so
- // we need a separate one to send over to launcher.
- SurfaceControl.Transaction otherFinishT = new SurfaceControl.Transaction();
// Dispatch the rest of the transition normally. This will most-likely be taken by
// recents or default handler.
mixed.mLeftoversHandler = player.dispatchTransition(mixed.mTransition, everythingElse,
- otherStartT, otherFinishT, finishCB, mixedHandler);
+ otherStartT, finishTransaction, finishCB, mixedHandler);
} else {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Not leaving split, so just "
+ "forward animation to Pip-Handler.");
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RecentsMixedTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RecentsMixedTransition.java
index 8cdbe26a2c76..1847af07f275 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RecentsMixedTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RecentsMixedTransition.java
@@ -159,6 +159,8 @@ class RecentsMixedTransition extends DefaultMixedHandler.MixedTransition {
// If pair-to-pair switching, the post-recents clean-up isn't needed.
wct = wct != null ? wct : new WindowContainerTransaction();
if (mAnimType != ANIM_TYPE_PAIR_TO_PAIR) {
+ // TODO(b/346588978): Only called if !enableRecentsBookendTransition(), can remove
+ // once that rolls out
mSplitHandler.onRecentsInSplitAnimationFinish(wct, finishTransaction);
} else {
// notify pair-to-pair recents animation finish
@@ -177,6 +179,17 @@ class RecentsMixedTransition extends DefaultMixedHandler.MixedTransition {
return handled;
}
+ /**
+ * Called when the recents animation during split is about to finish.
+ */
+ void onAnimateRecentsDuringSplitFinishing(boolean returnToApp,
+ @NonNull WindowContainerTransaction finishWct,
+ @NonNull SurfaceControl.Transaction finishT) {
+ if (mAnimType != ANIM_TYPE_PAIR_TO_PAIR) {
+ mSplitHandler.onRecentsInSplitAnimationFinishing(returnToApp, finishWct, finishT);
+ }
+ }
+
@Override
void mergeAnimation(
@NonNull IBinder transition, @NonNull TransitionInfo info,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
index 8c9407b38d9e..b83b7e2f07a3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
@@ -672,46 +672,6 @@ public class Transitions implements RemoteCallable<Transitions>,
return -1;
}
- /**
- * Look through a transition and see if all non-closing changes are no-animation. If so, no
- * animation should play.
- */
- static boolean isAllNoAnimation(TransitionInfo info) {
- if (isClosingType(info.getType())) {
- // no-animation is only relevant for launching (open) activities.
- return false;
- }
- boolean hasNoAnimation = false;
- final int changeSize = info.getChanges().size();
- for (int i = changeSize - 1; i >= 0; --i) {
- final TransitionInfo.Change change = info.getChanges().get(i);
- if (isClosingType(change.getMode())) {
- // ignore closing apps since they are a side-effect of the transition and don't
- // animate.
- continue;
- }
- if (change.hasFlags(FLAG_NO_ANIMATION)) {
- hasNoAnimation = true;
- } else if (!TransitionUtil.isOrderOnly(change) && !change.hasFlags(FLAG_IS_OCCLUDED)) {
- // Ignore the order only or occluded changes since they shouldn't be visible during
- // animation. For anything else, we need to animate if at-least one relevant
- // participant *is* animated,
- return false;
- }
- }
- return hasNoAnimation;
- }
-
- /**
- * Check if all changes in this transition are only ordering changes. If so, we won't animate.
- */
- static boolean isAllOrderOnly(TransitionInfo info) {
- for (int i = info.getChanges().size() - 1; i >= 0; --i) {
- if (!TransitionUtil.isOrderOnly(info.getChanges().get(i))) return false;
- }
- return true;
- }
-
private Track getOrCreateTrack(int trackId) {
while (trackId >= mTracks.size()) {
mTracks.add(new Track());
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CarWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CarWindowDecorViewModel.java
index 7948eadb28f4..2b2cdf84005c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CarWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CarWindowDecorViewModel.java
@@ -16,13 +16,17 @@
package com.android.wm.shell.windowdecor;
import android.app.ActivityManager.RunningTaskInfo;
+import android.app.ActivityTaskManager;
+import android.app.IActivityTaskManager;
import android.content.Context;
import android.hardware.input.InputManager;
+import android.os.RemoteException;
import android.os.SystemClock;
import android.os.UserHandle;
import android.util.Log;
import android.util.SparseArray;
import android.view.InputDevice;
+import android.view.InsetsState;
import android.view.KeyCharacterMap;
import android.view.KeyEvent;
import android.view.SurfaceControl;
@@ -33,6 +37,7 @@ import android.window.WindowContainerTransaction;
import com.android.wm.shell.R;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.common.DisplayInsetsController;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.freeform.FreeformTaskTransitionStarter;
@@ -49,7 +54,8 @@ import com.android.wm.shell.windowdecor.common.viewhost.WindowDecorViewHostSuppl
* Works with decorations that extend {@link CarWindowDecoration}.
*/
public abstract class CarWindowDecorViewModel
- implements WindowDecorViewModel, FocusTransitionListener {
+ implements WindowDecorViewModel, FocusTransitionListener,
+ DisplayInsetsController.OnInsetsChangedListener {
private static final String TAG = "CarWindowDecorViewModel";
private final ShellTaskOrganizer mTaskOrganizer;
@@ -57,31 +63,37 @@ public abstract class CarWindowDecorViewModel
private final @ShellBackgroundThread ShellExecutor mBgExecutor;
private final ShellExecutor mMainExecutor;
private final DisplayController mDisplayController;
+ private final DisplayInsetsController mDisplayInsetsController;
private final FocusTransitionObserver mFocusTransitionObserver;
private final SyncTransactionQueue mSyncQueue;
private final SparseArray<CarWindowDecoration> mWindowDecorByTaskId = new SparseArray<>();
private final WindowDecorViewHostSupplier<WindowDecorViewHost> mWindowDecorViewHostSupplier;
+ private final IActivityTaskManager mActivityTaskManager;
public CarWindowDecorViewModel(
Context context,
+ @ShellMainThread ShellExecutor mainExecutor,
@ShellBackgroundThread ShellExecutor bgExecutor,
- @ShellMainThread ShellExecutor shellExecutor,
ShellInit shellInit,
ShellTaskOrganizer taskOrganizer,
DisplayController displayController,
+ DisplayInsetsController displayInsetsController,
SyncTransactionQueue syncQueue,
FocusTransitionObserver focusTransitionObserver,
WindowDecorViewHostSupplier<WindowDecorViewHost> windowDecorViewHostSupplier) {
mContext = context;
- mMainExecutor = shellExecutor;
+ mMainExecutor = mainExecutor;
mBgExecutor = bgExecutor;
mTaskOrganizer = taskOrganizer;
mDisplayController = displayController;
+ mDisplayInsetsController = displayInsetsController;
mFocusTransitionObserver = focusTransitionObserver;
mSyncQueue = syncQueue;
mWindowDecorViewHostSupplier = windowDecorViewHostSupplier;
+ mActivityTaskManager = ActivityTaskManager.getService();
shellInit.addInitCallback(this::onInit, this);
+ displayInsetsController.addGlobalInsetsChangedListener(this);
}
private void onInit() {
@@ -187,6 +199,26 @@ public abstract class CarWindowDecorViewModel
decoration.close();
}
+ @Override
+ public void insetsChanged(int displayId, InsetsState insetsState) {
+ final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+ try {
+ mActivityTaskManager.getTasks(/* maxNum= */ Integer.MAX_VALUE,
+ /* filterOnlyVisibleRecents= */ false, /* keepIntentExtra= */ false,
+ displayId)
+ .stream().filter(taskInfo -> taskInfo.isVisible && taskInfo.isRunning)
+ .forEach(taskInfo -> {
+ final CarWindowDecoration decoration = mWindowDecorByTaskId.get(
+ taskInfo.taskId);
+ if (decoration != null) {
+ decoration.relayout(taskInfo, t, t);
+ }
+ });
+ } catch (RemoteException e) {
+ Log.e(TAG, "Cannot update decoration on inset change on displayId: " + displayId);
+ }
+ }
+
/**
* @return {@code true} if the task/activity associated with {@code taskInfo} should show
* window decoration.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CarWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CarWindowDecoration.java
index 39437845301e..3182745d813e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CarWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CarWindowDecoration.java
@@ -30,7 +30,6 @@ import android.view.WindowInsets;
import android.window.WindowContainerTransaction;
import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
import com.android.wm.shell.R;
import com.android.wm.shell.ShellTaskOrganizer;
@@ -47,6 +46,7 @@ public class CarWindowDecoration extends WindowDecoration<WindowDecorLinearLayou
private WindowDecorLinearLayout mRootView;
private @ShellBackgroundThread final ShellExecutor mBgExecutor;
private final View.OnClickListener mClickListener;
+ private final RelayoutParams mRelayoutParams = new RelayoutParams();
private final RelayoutResult<WindowDecorLinearLayout> mResult = new RelayoutResult<>();
CarWindowDecoration(
@@ -75,7 +75,8 @@ public class CarWindowDecoration extends WindowDecoration<WindowDecorLinearLayou
@SuppressLint("MissingPermission")
void relayout(ActivityManager.RunningTaskInfo taskInfo,
SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT) {
- relayout(taskInfo, startT, finishT, /* isCaptionVisible= */ true);
+ relayout(taskInfo, startT, finishT,
+ /* isCaptionVisible= */ mRelayoutParams.mIsCaptionVisible);
}
@SuppressLint("MissingPermission")
@@ -84,12 +85,9 @@ public class CarWindowDecoration extends WindowDecoration<WindowDecorLinearLayou
boolean isCaptionVisible) {
final WindowContainerTransaction wct = new WindowContainerTransaction();
- RelayoutParams relayoutParams = new RelayoutParams();
+ updateRelayoutParams(mRelayoutParams, taskInfo, isCaptionVisible);
- updateRelayoutParams(relayoutParams, taskInfo,
- mDisplayController.getInsetsState(taskInfo.displayId), isCaptionVisible);
-
- relayout(relayoutParams, startT, finishT, wct, mRootView, mResult);
+ relayout(mRelayoutParams, startT, finishT, wct, mRootView, mResult);
// After this line, mTaskInfo is up-to-date and should be used instead of taskInfo
mBgExecutor.execute(() -> mTaskOrganizer.applyTransaction(wct));
@@ -118,7 +116,6 @@ public class CarWindowDecoration extends WindowDecoration<WindowDecorLinearLayou
private void updateRelayoutParams(
RelayoutParams relayoutParams,
ActivityManager.RunningTaskInfo taskInfo,
- @Nullable InsetsState displayInsetsState,
boolean isCaptionVisible) {
relayoutParams.reset();
relayoutParams.mRunningTaskInfo = taskInfo;
@@ -127,16 +124,19 @@ public class CarWindowDecoration extends WindowDecoration<WindowDecorLinearLayou
relayoutParams.mCaptionHeightId = R.dimen.freeform_decor_caption_height;
relayoutParams.mIsCaptionVisible =
isCaptionVisible && mIsStatusBarVisible && !mIsKeyguardVisibleAndOccluded;
- if (displayInsetsState != null) {
- relayoutParams.mCaptionTopPadding = getTopPadding(
- taskInfo.getConfiguration().windowConfiguration.getBounds(),
- displayInsetsState);
- }
+ relayoutParams.mCaptionTopPadding = getTopPadding(taskInfo, relayoutParams);
+
relayoutParams.mInsetSourceFlags |= FLAG_FORCE_CONSUMING_OPAQUE_CAPTION_BAR;
relayoutParams.mApplyStartTransactionOnDraw = true;
}
- private static int getTopPadding(Rect taskBounds, @NonNull InsetsState insetsState) {
+ private int getTopPadding(ActivityManager.RunningTaskInfo taskInfo,
+ RelayoutParams relayoutParams) {
+ Rect taskBounds = taskInfo.getConfiguration().windowConfiguration.getBounds();
+ InsetsState insetsState = mDisplayController.getInsetsState(taskInfo.displayId);
+ if (insetsState == null) {
+ return relayoutParams.mCaptionTopPadding;
+ }
Insets systemDecor = insetsState.calculateInsets(taskBounds,
WindowInsets.Type.systemBars() & ~WindowInsets.Type.captionBar(),
false /* ignoreVisibility */);
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 fad1c9f848ea..7e7a793298e2 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
@@ -32,7 +32,6 @@ import static android.view.WindowInsets.Type.statusBars;
import static com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_ENTER_MODE_APP_HANDLE_MENU;
import static com.android.window.flags.Flags.enableDisplayFocusInShellTransitions;
-import static com.android.wm.shell.compatui.AppCompatUtils.isTopActivityExemptFromDesktopWindowing;
import static com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.InputMethod;
import static com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.MinimizeReason;
import static com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ResizeTrigger;
@@ -133,6 +132,7 @@ import com.android.wm.shell.recents.RecentsTransitionStateListener;
import com.android.wm.shell.shared.FocusTransitionListener;
import com.android.wm.shell.shared.annotations.ShellBackgroundThread;
import com.android.wm.shell.shared.annotations.ShellMainThread;
+import com.android.wm.shell.shared.desktopmode.DesktopModeCompatPolicy;
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;
@@ -254,6 +254,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
private final DesktopModeUiEventLogger mDesktopModeUiEventLogger;
private final WindowDecorTaskResourceLoader mTaskResourceLoader;
private final RecentsTransitionHandler mRecentsTransitionHandler;
+ private final DesktopModeCompatPolicy mDesktopModeCompatPolicy;
public DesktopModeWindowDecorViewModel(
Context context,
@@ -290,7 +291,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
DesktopModeEventLogger desktopModeEventLogger,
DesktopModeUiEventLogger desktopModeUiEventLogger,
WindowDecorTaskResourceLoader taskResourceLoader,
- RecentsTransitionHandler recentsTransitionHandler) {
+ RecentsTransitionHandler recentsTransitionHandler,
+ DesktopModeCompatPolicy desktopModeCompatPolicy) {
this(
context,
shellExecutor,
@@ -332,7 +334,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
desktopModeEventLogger,
desktopModeUiEventLogger,
taskResourceLoader,
- recentsTransitionHandler);
+ recentsTransitionHandler,
+ desktopModeCompatPolicy);
}
@VisibleForTesting
@@ -377,7 +380,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
DesktopModeEventLogger desktopModeEventLogger,
DesktopModeUiEventLogger desktopModeUiEventLogger,
WindowDecorTaskResourceLoader taskResourceLoader,
- RecentsTransitionHandler recentsTransitionHandler) {
+ RecentsTransitionHandler recentsTransitionHandler,
+ DesktopModeCompatPolicy desktopModeCompatPolicy) {
mContext = context;
mMainExecutor = shellExecutor;
mMainHandler = mainHandler;
@@ -447,6 +451,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
mDesktopModeUiEventLogger = desktopModeUiEventLogger;
mTaskResourceLoader = taskResourceLoader;
mRecentsTransitionHandler = recentsTransitionHandler;
+ mDesktopModeCompatPolicy = desktopModeCompatPolicy;
shellInit.addInitCallback(this::onInit, this);
}
@@ -461,7 +466,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
new DesktopModeOnTaskResizeAnimationListener());
mDesktopTasksController.setOnTaskRepositionAnimationListener(
new DesktopModeOnTaskRepositionAnimationListener());
- if (Flags.enableDesktopRecentsTransitionsCornersBugfix()) {
+ if (DesktopModeFlags.ENABLE_DESKTOP_RECENTS_TRANSITIONS_CORNERS_BUGFIX.isTrue()) {
mRecentsTransitionHandler.addTransitionStateListener(
new DesktopModeRecentsTransitionStateListener());
}
@@ -1652,8 +1657,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
&& mSplitScreenController.isTaskRootOrStageRoot(taskInfo.taskId)) {
return false;
}
- if (DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_MODALS_POLICY.isTrue()
- && isTopActivityExemptFromDesktopWindowing(mContext, taskInfo)) {
+ if (mDesktopModeCompatPolicy.isTopActivityExemptFromDesktopWindowing(taskInfo)) {
return false;
}
if (isPartOfDefaultHomePackage(taskInfo)) {
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 b179741b1259..4e125d001076 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
@@ -542,6 +542,9 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
if (appHeader != null) {
appHeader.setAppName(name);
appHeader.setAppIcon(icon);
+ if (canEnterDesktopMode(mContext) && isEducationEnabled()) {
+ notifyCaptionStateChanged();
+ }
}
});
}
@@ -969,7 +972,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
final RelayoutParams.OccludingCaptionElement controlsElement =
new RelayoutParams.OccludingCaptionElement();
controlsElement.mWidthResId = R.dimen.desktop_mode_customizable_caption_margin_end;
- if (Flags.enableMinimizeButton()) {
+ if (DesktopModeFlags.ENABLE_MINIMIZE_BUTTON.isTrue()) {
controlsElement.mWidthResId =
R.dimen.desktop_mode_customizable_caption_with_minimize_button_margin_end;
}
@@ -1355,7 +1358,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
mWebUri = assistContent == null ? null : AppToWebUtils.getSessionWebUri(assistContent);
updateGenericLink();
final boolean supportsMultiInstance = mMultiInstanceHelper
- .supportsMultiInstanceSplit(mTaskInfo.baseActivity)
+ .supportsMultiInstanceSplit(mTaskInfo.baseActivity, mTaskInfo.userId)
&& Flags.enableDesktopWindowingMultiInstanceFeatures();
final boolean shouldShowManageWindowsButton = supportsMultiInstance
&& mMinimumInstancesFound;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt
index e5c989ed5f97..053850480ecc 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt
@@ -49,6 +49,7 @@ import com.android.window.flags.Flags
import com.android.wm.shell.R
import com.android.wm.shell.shared.annotations.ShellBackgroundThread
import com.android.wm.shell.shared.annotations.ShellMainThread
+import com.android.wm.shell.shared.bubbles.BubbleAnythingFlagHelper
import com.android.wm.shell.shared.split.SplitScreenConstants
import com.android.wm.shell.splitscreen.SplitScreenController
import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalSystemViewContainer
@@ -645,7 +646,7 @@ class HandleMenu(
private fun bindWindowingPill(style: MenuStyle) {
windowingPill.background.setTint(style.backgroundColor)
- if (!com.android.wm.shell.Flags.enableBubbleAnything()) {
+ if (!BubbleAnythingFlagHelper.enableBubbleToFullscreen()) {
floatingBtn.visibility = View.GONE
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MoveToDesktopAnimator.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MoveToDesktopAnimator.kt
index 70c0b54462e3..22bc9782170b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MoveToDesktopAnimator.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MoveToDesktopAnimator.kt
@@ -5,6 +5,7 @@ import android.app.ActivityManager.RunningTaskInfo
import android.content.Context
import android.graphics.PointF
import android.graphics.Rect
+import android.view.Choreographer
import android.view.MotionEvent
import android.view.SurfaceControl
import android.view.VelocityTracker
@@ -48,7 +49,7 @@ class MoveToDesktopAnimator @JvmOverloads constructor(
t.setScale(taskSurface, scale, scale)
.setCornerRadius(taskSurface, cornerRadius)
.setScale(taskSurface, scale, scale)
- .setCornerRadius(taskSurface, cornerRadius)
+ .setFrameTimeline(Choreographer.getInstance().vsyncId)
.setPosition(taskSurface, position.x, position.y)
.apply()
}
@@ -96,6 +97,7 @@ class MoveToDesktopAnimator @JvmOverloads constructor(
setTaskPosition(ev.rawX, ev.rawY)
val t = transactionFactory()
t.setPosition(taskSurface, position.x, position.y)
+ t.setFrameTimeline(Choreographer.getInstance().vsyncId)
t.apply()
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/WindowDecorTaskResourceLoader.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/WindowDecorTaskResourceLoader.kt
index d87da092cccf..1bc48f89ea6d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/WindowDecorTaskResourceLoader.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/WindowDecorTaskResourceLoader.kt
@@ -16,7 +16,6 @@
package com.android.wm.shell.windowdecor.common
import android.annotation.DimenRes
-import android.app.ActivityManager
import android.app.ActivityManager.RunningTaskInfo
import android.content.Context
import android.content.pm.ActivityInfo
@@ -29,6 +28,7 @@ import com.android.launcher3.icons.BaseIconFactory
import com.android.launcher3.icons.BaseIconFactory.MODE_DEFAULT
import com.android.launcher3.icons.IconProvider
import com.android.wm.shell.R
+import com.android.wm.shell.common.UserProfileContexts
import com.android.wm.shell.shared.annotations.ShellBackgroundThread
import com.android.wm.shell.sysui.ShellCommandHandler
import com.android.wm.shell.sysui.ShellController
@@ -41,10 +41,10 @@ import java.util.concurrent.ConcurrentHashMap
* A utility and cache for window decoration UI resources.
*/
class WindowDecorTaskResourceLoader(
- private val context: Context,
shellInit: ShellInit,
private val shellController: ShellController,
private val shellCommandHandler: ShellCommandHandler,
+ private val userProfilesContexts: UserProfileContexts,
private val iconProvider: IconProvider,
private val headerIconFactory: BaseIconFactory,
private val veilIconFactory: BaseIconFactory,
@@ -54,11 +54,12 @@ class WindowDecorTaskResourceLoader(
shellInit: ShellInit,
shellController: ShellController,
shellCommandHandler: ShellCommandHandler,
+ userProfileContexts: UserProfileContexts,
) : this(
- context,
shellInit,
shellController,
shellCommandHandler,
+ userProfileContexts,
IconProvider(context),
headerIconFactory = context.createIconFactory(R.dimen.desktop_mode_caption_icon_radius),
veilIconFactory = context.createIconFactory(R.dimen.desktop_mode_resize_veil_icon_size),
@@ -79,9 +80,6 @@ class WindowDecorTaskResourceLoader(
*/
private val existingTasks = mutableSetOf<Int>()
- @VisibleForTesting
- lateinit var currentUserContext: Context
-
init {
shellInit.addInitCallback(this::onInit, this)
}
@@ -90,14 +88,10 @@ class WindowDecorTaskResourceLoader(
shellCommandHandler.addDumpCallback(this::dump, this)
shellController.addUserChangeListener(object : UserChangeListener {
override fun onUserChanged(newUserId: Int, userContext: Context) {
- currentUserContext = userContext
// No need to hold on to resources for tasks of another profile.
taskToResourceCache.clear()
}
})
- currentUserContext = context.createContextAsUser(
- UserHandle.of(ActivityManager.getCurrentUser()), /* flags= */ 0
- )
}
/** Returns the user readable name for this task. */
@@ -158,15 +152,20 @@ class WindowDecorTaskResourceLoader(
private fun loadAppResources(taskInfo: RunningTaskInfo): AppResources {
Trace.beginSection("$TAG#loadAppResources")
- val pm = currentUserContext.packageManager
- val activityInfo = getActivityInfo(taskInfo, pm)
- val appName = pm.getApplicationLabel(activityInfo.applicationInfo)
- val appIconDrawable = iconProvider.getIcon(activityInfo)
- val badgedAppIconDrawable = pm.getUserBadgedIcon(appIconDrawable, taskInfo.userHandle())
- val appIcon = headerIconFactory.createIconBitmap(badgedAppIconDrawable, /* scale= */ 1f)
- val veilIcon = veilIconFactory.createScaledBitmap(appIconDrawable, MODE_DEFAULT)
- Trace.endSection()
- return AppResources(appName = appName, appIcon = appIcon, veilIcon = veilIcon)
+ try {
+ val pm = checkNotNull(userProfilesContexts[taskInfo.userId]?.packageManager) {
+ "Could not get context for user ${taskInfo.userId}"
+ }
+ val activityInfo = getActivityInfo(taskInfo, pm)
+ val appName = pm.getApplicationLabel(activityInfo.applicationInfo)
+ val appIconDrawable = iconProvider.getIcon(activityInfo)
+ val badgedAppIconDrawable = pm.getUserBadgedIcon(appIconDrawable, taskInfo.userHandle())
+ val appIcon = headerIconFactory.createIconBitmap(badgedAppIconDrawable, /* scale= */ 1f)
+ val veilIcon = veilIconFactory.createScaledBitmap(appIconDrawable, MODE_DEFAULT)
+ return AppResources(appName = appName, appIcon = appIcon, veilIcon = veilIcon)
+ } finally {
+ Trace.endSection()
+ }
}
private fun getActivityInfo(taskInfo: RunningTaskInfo, pm: PackageManager): ActivityInfo {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDecorViewModel.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDecorViewModel.kt
index d72da3a08de5..8747f63e789f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDecorViewModel.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDecorViewModel.kt
@@ -29,6 +29,7 @@ import com.android.wm.shell.RootTaskDisplayAreaOrganizer
import com.android.wm.shell.ShellTaskOrganizer
import com.android.wm.shell.common.DisplayChangeController
import com.android.wm.shell.common.DisplayController
+import com.android.wm.shell.common.ShellExecutor
import com.android.wm.shell.common.SyncTransactionQueue
import com.android.wm.shell.desktopmode.DesktopModeEventLogger
import com.android.wm.shell.desktopmode.DesktopTasksController
@@ -37,6 +38,7 @@ import com.android.wm.shell.desktopmode.ReturnToDragStartAnimator
import com.android.wm.shell.desktopmode.ToggleResizeDesktopTaskTransitionHandler
import com.android.wm.shell.shared.annotations.ShellBackgroundThread
import com.android.wm.shell.shared.annotations.ShellMainThread
+import com.android.wm.shell.transition.FocusTransitionObserver
import com.android.wm.shell.transition.Transitions
import com.android.wm.shell.windowdecor.DesktopModeWindowDecoration
import com.android.wm.shell.windowdecor.common.WindowDecorTaskResourceLoader
@@ -58,6 +60,8 @@ class DesktopTilingDecorViewModel(
private val desktopUserRepositories: DesktopUserRepositories,
private val desktopModeEventLogger: DesktopModeEventLogger,
private val taskResourceLoader: WindowDecorTaskResourceLoader,
+ private val focusTransitionObserver: FocusTransitionObserver,
+ private val mainExecutor: ShellExecutor,
) : DisplayChangeController.OnDisplayChangingListener {
@VisibleForTesting
var tilingTransitionHandlerByDisplayId = SparseArray<DesktopTilingWindowDecoration>()
@@ -94,6 +98,8 @@ class DesktopTilingDecorViewModel(
returnToDragStartAnimator,
desktopUserRepositories,
desktopModeEventLogger,
+ focusTransitionObserver,
+ mainExecutor,
)
tilingTransitionHandlerByDisplayId.put(displayId, newHandler)
newHandler
@@ -112,9 +118,10 @@ class DesktopTilingDecorViewModel(
}
fun moveTaskToFrontIfTiled(taskInfo: RunningTaskInfo): Boolean {
+ // Always pass focus=true because taskInfo.isFocused is not updated yet.
return tilingTransitionHandlerByDisplayId
.get(taskInfo.displayId)
- ?.moveTiledPairToFront(taskInfo, isTaskFocused = true) ?: false
+ ?.moveTiledPairToFront(taskInfo.taskId, isFocusedOnDisplay = true) ?: false
}
fun onOverviewAnimationStateChange(isRunning: Boolean) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecoration.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecoration.kt
index 6f2323347468..666d4bd046dc 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecoration.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecoration.kt
@@ -35,11 +35,14 @@ import android.window.TransitionRequestInfo
import android.window.WindowContainerTransaction
import com.android.internal.annotations.VisibleForTesting
import com.android.launcher3.icons.BaseIconFactory
+import com.android.window.flags.Flags
+import com.android.wm.shell.shared.FocusTransitionListener
import com.android.wm.shell.R
import com.android.wm.shell.RootTaskDisplayAreaOrganizer
import com.android.wm.shell.ShellTaskOrganizer
import com.android.wm.shell.common.DisplayController
import com.android.wm.shell.common.DisplayLayout
+import com.android.wm.shell.common.ShellExecutor
import com.android.wm.shell.common.SyncTransactionQueue
import com.android.wm.shell.desktopmode.DesktopModeEventLogger
import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ResizeTrigger
@@ -49,6 +52,7 @@ import com.android.wm.shell.desktopmode.ReturnToDragStartAnimator
import com.android.wm.shell.desktopmode.ToggleResizeDesktopTaskTransitionHandler
import com.android.wm.shell.shared.annotations.ShellBackgroundThread
import com.android.wm.shell.shared.annotations.ShellMainThread
+import com.android.wm.shell.transition.FocusTransitionObserver
import com.android.wm.shell.transition.Transitions
import com.android.wm.shell.transition.Transitions.TRANSIT_MINIMIZE
import com.android.wm.shell.windowdecor.DesktopModeWindowDecoration
@@ -78,13 +82,16 @@ class DesktopTilingWindowDecoration(
private val returnToDragStartAnimator: ReturnToDragStartAnimator,
private val desktopUserRepositories: DesktopUserRepositories,
private val desktopModeEventLogger: DesktopModeEventLogger,
+ private val focusTransitionObserver: FocusTransitionObserver,
+ @ShellMainThread private val mainExecutor: ShellExecutor,
private val transactionSupplier: Supplier<Transaction> = Supplier { Transaction() },
) :
Transitions.TransitionHandler,
ShellTaskOrganizer.FocusListener,
ShellTaskOrganizer.TaskVanishedListener,
DragEventListener,
- Transitions.TransitionObserver {
+ Transitions.TransitionObserver,
+ FocusTransitionListener {
companion object {
private val TAG: String = DesktopTilingWindowDecoration::class.java.simpleName
private const val TILING_DIVIDER_TAG = "Tiling Divider"
@@ -176,8 +183,13 @@ class DesktopTilingWindowDecoration(
if (!isTilingManagerInitialised) {
desktopTilingDividerWindowManager = initTilingManagerForDisplay(displayId, config)
isTilingManagerInitialised = true
- shellTaskOrganizer.addFocusListener(this)
- isTilingFocused = true
+
+ if (Flags.enableDisplayFocusInShellTransitions()) {
+ focusTransitionObserver.setLocalFocusTransitionListener(this, mainExecutor)
+ } else {
+ shellTaskOrganizer.addFocusListener(this)
+ isTilingFocused = true
+ }
}
leftTaskResizingHelper?.initIfNeeded()
rightTaskResizingHelper?.initIfNeeded()
@@ -474,23 +486,33 @@ class DesktopTilingWindowDecoration(
}
}
- // Only called if [taskInfo] relates to a focused task
- private fun isTilingFocusRemoved(taskInfo: RunningTaskInfo): Boolean {
+ // Only called if [taskId] relates to a focused task
+ private fun isTilingFocusRemoved(taskId: Int): Boolean {
return isTilingFocused &&
- taskInfo.taskId != leftTaskResizingHelper?.taskInfo?.taskId &&
- taskInfo.taskId != rightTaskResizingHelper?.taskInfo?.taskId
+ taskId != leftTaskResizingHelper?.taskInfo?.taskId &&
+ taskId != rightTaskResizingHelper?.taskInfo?.taskId
}
+ // Overriding ShellTaskOrganizer.FocusListener
override fun onFocusTaskChanged(taskInfo: RunningTaskInfo?) {
+ if (Flags.enableDisplayFocusInShellTransitions()) return
if (taskInfo != null) {
- moveTiledPairToFront(taskInfo)
+ moveTiledPairToFront(taskInfo.taskId, taskInfo.isFocused)
}
}
+ // Overriding FocusTransitionListener
+ override fun onFocusedTaskChanged(taskId: Int,
+ isFocusedOnDisplay: Boolean,
+ isFocusedGlobally: Boolean) {
+ if (!Flags.enableDisplayFocusInShellTransitions()) return
+ moveTiledPairToFront(taskId, isFocusedOnDisplay)
+ }
+
// Only called if [taskInfo] relates to a focused task
- private fun isTilingRefocused(taskInfo: RunningTaskInfo): Boolean {
- return taskInfo.taskId == leftTaskResizingHelper?.taskInfo?.taskId ||
- taskInfo.taskId == rightTaskResizingHelper?.taskInfo?.taskId
+ private fun isTilingRefocused(taskId: Int): Boolean {
+ return taskId == leftTaskResizingHelper?.taskInfo?.taskId ||
+ taskId == rightTaskResizingHelper?.taskInfo?.taskId
}
private fun buildTiledTasksMoveToFront(leftOnTop: Boolean): WindowContainerTransaction {
@@ -582,14 +604,13 @@ class DesktopTilingWindowDecoration(
* If specified, [isTaskFocused] will override [RunningTaskInfo.isFocused]. This is to be used
* when called when the task will be focused, but the [taskInfo] hasn't been updated yet.
*/
- fun moveTiledPairToFront(taskInfo: RunningTaskInfo, isTaskFocused: Boolean? = null): Boolean {
+ fun moveTiledPairToFront(taskId: Int, isFocusedOnDisplay: Boolean): Boolean {
if (!isTilingManagerInitialised) return false
- val isFocused = isTaskFocused ?: taskInfo.isFocused
- if (!isFocused) return false
+ if (!isFocusedOnDisplay) return false
// If a task that isn't tiled is being focused, let the generic handler do the work.
- if (isTilingFocusRemoved(taskInfo)) {
+ if (!Flags.enableDisplayFocusInShellTransitions() && isTilingFocusRemoved(taskId)) {
isTilingFocused = false
return false
}
@@ -597,31 +618,29 @@ class DesktopTilingWindowDecoration(
val leftTiledTask = leftTaskResizingHelper ?: return false
val rightTiledTask = rightTaskResizingHelper ?: return false
if (!allTiledTasksVisible()) return false
- val isLeftOnTop = taskInfo.taskId == leftTiledTask.taskInfo.taskId
- if (isTilingRefocused(taskInfo)) {
- val t = transactionSupplier.get()
- isTilingFocused = true
- if (taskInfo.taskId == leftTaskResizingHelper?.taskInfo?.taskId) {
- desktopTilingDividerWindowManager?.onRelativeLeashChanged(
- leftTiledTask.getLeash(),
- t,
- )
- }
- if (taskInfo.taskId == rightTaskResizingHelper?.taskInfo?.taskId) {
- desktopTilingDividerWindowManager?.onRelativeLeashChanged(
- rightTiledTask.getLeash(),
- t,
- )
- }
- transitions.startTransition(
- TRANSIT_TO_FRONT,
- buildTiledTasksMoveToFront(isLeftOnTop),
- null,
- )
- t.apply()
- return true
+ val isLeftOnTop = taskId == leftTiledTask.taskInfo.taskId
+ if (!isTilingRefocused(taskId)) return false
+ val t = transactionSupplier.get()
+ if (!Flags.enableDisplayFocusInShellTransitions()) isTilingFocused = true
+ if (taskId == leftTaskResizingHelper?.taskInfo?.taskId) {
+ desktopTilingDividerWindowManager?.onRelativeLeashChanged(
+ leftTiledTask.getLeash(),
+ t,
+ )
}
- return false
+ if (taskId == rightTaskResizingHelper?.taskInfo?.taskId) {
+ desktopTilingDividerWindowManager?.onRelativeLeashChanged(
+ rightTiledTask.getLeash(),
+ t,
+ )
+ }
+ transitions.startTransition(
+ TRANSIT_TO_FRONT,
+ buildTiledTasksMoveToFront(isLeftOnTop),
+ null,
+ )
+ t.apply()
+ return true
}
private fun allTiledTasksVisible(): Boolean {
@@ -706,7 +725,13 @@ class DesktopTilingWindowDecoration(
}
private fun tearDownTiling() {
- if (isTilingManagerInitialised) shellTaskOrganizer.removeFocusListener(this)
+ if (isTilingManagerInitialised) {
+ if (Flags.enableDisplayFocusInShellTransitions()) {
+ focusTransitionObserver.unsetLocalFocusTransitionListener(this)
+ } else {
+ shellTaskOrganizer.removeFocusListener(this)
+ }
+ }
if (leftTaskResizingHelper == null && rightTaskResizingHelper == null) {
shellTaskOrganizer.removeTaskVanishedListener(this)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHeaderViewHolder.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHeaderViewHolder.kt
index dc4fa3788778..9f8ca7740182 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHeaderViewHolder.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHeaderViewHolder.kt
@@ -47,7 +47,6 @@ import com.android.internal.R.color.materialColorSurfaceContainerHigh
import com.android.internal.R.color.materialColorSurfaceContainerLow
import com.android.internal.R.color.materialColorSurfaceDim
import com.android.window.flags.Flags
-import com.android.window.flags.Flags.enableMinimizeButton
import com.android.wm.shell.R
import android.window.DesktopModeFlags
import com.android.wm.shell.windowdecor.MaximizeButtonView
@@ -226,7 +225,7 @@ class AppHeaderViewHolder(
minimizeWindowButton.background = getDrawable(1)
}
maximizeButtonView.setAnimationTints(isDarkMode())
- minimizeWindowButton.isGone = !enableMinimizeButton()
+ minimizeWindowButton.isGone = !DesktopModeFlags.ENABLE_MINIMIZE_BUTTON.isTrue()
}
private fun bindDataWithThemedHeaders(
@@ -276,7 +275,7 @@ class AppHeaderViewHolder(
drawableInsets = minimizeDrawableInsets
)
}
- minimizeWindowButton.isGone = !enableMinimizeButton()
+ minimizeWindowButton.isGone = !DesktopModeFlags.ENABLE_MINIMIZE_BUTTON.isTrue()
// Maximize button.
maximizeButtonView.apply {
setAnimationTints(
@@ -329,11 +328,6 @@ class AppHeaderViewHolder(
}
fun runOnAppChipGlobalLayout(runnable: () -> Unit) {
- if (openMenuButton.isAttachedToWindow) {
- // App chip is already inflated.
- runnable()
- return
- }
// Wait for app chip to be inflated before notifying repository.
openMenuButton.viewTreeObserver.addOnGlobalLayoutListener(object :
OnGlobalLayoutListener {
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/OpenAppFromAllAppsLandscape.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/OpenAppFromAllAppsLandscape.kt
new file mode 100644
index 000000000000..6b159a4152ac
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/OpenAppFromAllAppsLandscape.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker
+
+import android.tools.Rotation.ROTATION_90
+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.CASCADE_APP
+import com.android.wm.shell.scenarios.OpenAppFromAllApps
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class OpenAppFromAllAppsLandscape : OpenAppFromAllApps(rotation = ROTATION_90) {
+
+ @ExpectedScenarios(["CASCADE_APP"])
+ @Test
+ override fun openApp() = super.openApp()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT).use(CASCADE_APP)
+ }
+} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/OpenAppFromAllAppsPortrait.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/OpenAppFromAllAppsPortrait.kt
new file mode 100644
index 000000000000..07b439284680
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/OpenAppFromAllAppsPortrait.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker
+
+import android.tools.Rotation.ROTATION_0
+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.CASCADE_APP
+import com.android.wm.shell.scenarios.OpenAppFromAllApps
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class OpenAppFromAllAppsPortrait : OpenAppFromAllApps(rotation = ROTATION_0) {
+
+ @ExpectedScenarios(["CASCADE_APP"])
+ @Test
+ override fun openApp() = super.openApp()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT).use(CASCADE_APP)
+ }
+} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/OpenAppFromTaskbarLandscape.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/OpenAppFromTaskbarLandscape.kt
new file mode 100644
index 000000000000..caadd3be0b9c
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/OpenAppFromTaskbarLandscape.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker
+
+import android.tools.Rotation.ROTATION_90
+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.CASCADE_APP
+import com.android.wm.shell.scenarios.OpenAppFromTaskbar
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class OpenAppFromTaskbarLandscape : OpenAppFromTaskbar(rotation = ROTATION_90) {
+
+ @ExpectedScenarios(["CASCADE_APP"])
+ @Test
+ override fun openApp() = super.openApp()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT).use(CASCADE_APP)
+ }
+} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/OpenAppFromTaskbarPortrait.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/OpenAppFromTaskbarPortrait.kt
new file mode 100644
index 000000000000..77f5ab290e20
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/OpenAppFromTaskbarPortrait.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker
+
+import android.tools.Rotation.ROTATION_0
+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.CASCADE_APP
+import com.android.wm.shell.scenarios.OpenAppFromTaskbar
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class OpenAppFromTaskbarPortrait : OpenAppFromTaskbar(rotation = ROTATION_0) {
+
+ @ExpectedScenarios(["CASCADE_APP"])
+ @Test
+ override fun openApp() = super.openApp()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT).use(CASCADE_APP)
+ }
+} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MaximizeAppWindow.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MaximizeAppWindow.kt
index 966aea3088c4..7855698d0151 100644
--- a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MaximizeAppWindow.kt
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MaximizeAppWindow.kt
@@ -21,6 +21,7 @@ import android.tools.NavBar
import android.tools.Rotation
import android.tools.flicker.rules.ChangeDisplayOrientationRule
import android.tools.traces.parsers.WindowManagerStateHelper
+import android.window.DesktopModeFlags
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
import com.android.launcher3.tapl.LauncherInstrumentation
@@ -59,7 +60,7 @@ constructor(
fun setup() {
Assume.assumeTrue(Flags.enableDesktopWindowingMode() && tapl.isTablet)
if (usingKeyboard) {
- Assume.assumeTrue(Flags.enableTaskResizingKeyboardShortcuts())
+ Assume.assumeTrue(DesktopModeFlags.ENABLE_TASK_RESIZING_KEYBOARD_SHORTCUTS.isTrue)
}
tapl.setEnableRotation(true)
tapl.setExpectedRotation(rotation.value)
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MinimizeAppWindows.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MinimizeAppWindows.kt
index 46c97b0a1397..2f99fbaba078 100644
--- a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MinimizeAppWindows.kt
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MinimizeAppWindows.kt
@@ -21,6 +21,7 @@ import android.tools.NavBar
import android.tools.Rotation
import android.tools.flicker.rules.ChangeDisplayOrientationRule
import android.tools.traces.parsers.WindowManagerStateHelper
+import android.window.DesktopModeFlags
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
import com.android.launcher3.tapl.LauncherInstrumentation
@@ -62,7 +63,7 @@ constructor(
Assume.assumeTrue(Flags.enableDesktopWindowingMode() && tapl.isTablet)
Assume.assumeTrue(Flags.enableMinimizeButton())
if (usingKeyboard) {
- Assume.assumeTrue(Flags.enableTaskResizingKeyboardShortcuts())
+ Assume.assumeTrue(DesktopModeFlags.ENABLE_TASK_RESIZING_KEYBOARD_SHORTCUTS.isTrue)
}
tapl.setEnableRotation(true)
tapl.setExpectedRotation(rotation.value)
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/OpenAppFromAllApps.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/OpenAppFromAllApps.kt
index 36cdd5b26992..348219631245 100644
--- a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/OpenAppFromAllApps.kt
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/OpenAppFromAllApps.kt
@@ -20,12 +20,12 @@ import android.app.Instrumentation
import android.tools.NavBar
import android.tools.flicker.rules.ChangeDisplayOrientationRule
import android.tools.Rotation
+import android.tools.device.apphelpers.CalculatorAppHelper
import android.tools.traces.parsers.WindowManagerStateHelper
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
import com.android.launcher3.tapl.LauncherInstrumentation
import com.android.server.wm.flicker.helpers.DesktopModeAppHelper
-import com.android.server.wm.flicker.helpers.MailAppHelper
import com.android.server.wm.flicker.helpers.SimpleAppHelper
import com.android.window.flags.Flags
import com.android.wm.shell.Utils
@@ -44,7 +44,7 @@ abstract class OpenAppFromAllApps(val rotation: Rotation = Rotation.ROTATION_0)
private val wmHelper = WindowManagerStateHelper(instrumentation)
private val device = UiDevice.getInstance(instrumentation)
private val testApp = DesktopModeAppHelper(SimpleAppHelper(instrumentation))
- private val mailApp = MailAppHelper(instrumentation)
+ private val calculatorApp = CalculatorAppHelper(instrumentation)
@Rule
@JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation)
@@ -64,13 +64,13 @@ abstract class OpenAppFromAllApps(val rotation: Rotation = Rotation.ROTATION_0)
open fun openApp() {
tapl.launchedAppState.taskbar
.openAllApps()
- .getAppIcon(mailApp.appName)
- .launch(mailApp.packageName)
+ .getAppIcon(calculatorApp.appName)
+ .launch(calculatorApp.packageName)
}
@After
fun teardown() {
- mailApp.exit(wmHelper)
+ calculatorApp.exit(wmHelper)
testApp.exit(wmHelper)
}
} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/SnapResizeAppWindowWithKeyboardShortcuts.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/SnapResizeAppWindowWithKeyboardShortcuts.kt
index 068064402ee5..59d15ca4fa6b 100644
--- a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/SnapResizeAppWindowWithKeyboardShortcuts.kt
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/SnapResizeAppWindowWithKeyboardShortcuts.kt
@@ -20,6 +20,7 @@ import android.app.Instrumentation
import android.tools.NavBar
import android.tools.Rotation
import android.tools.traces.parsers.WindowManagerStateHelper
+import android.window.DesktopModeFlags
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
import com.android.launcher3.tapl.LauncherInstrumentation
@@ -59,7 +60,7 @@ abstract class SnapResizeAppWindowWithKeyboardShortcuts(
@Before
fun setup() {
Assume.assumeTrue(Flags.enableDesktopWindowingMode() &&
- Flags.enableTaskResizingKeyboardShortcuts() && tapl.isTablet)
+ DesktopModeFlags.ENABLE_TASK_RESIZING_KEYBOARD_SHORTCUTS.isTrue && tapl.isTablet)
testApp.enterDesktopMode(wmHelper, device)
}
diff --git a/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/CopyContentInSplit.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/CopyContentInSplit.kt
index 31d89f92f744..9d501d32fbc7 100644
--- a/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/CopyContentInSplit.kt
+++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/CopyContentInSplit.kt
@@ -23,8 +23,8 @@ import android.tools.traces.parsers.WindowManagerStateHelper
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.server.wm.flicker.helpers.RecentTasksUtils
import com.android.wm.shell.Utils
-import com.android.wm.shell.flicker.utils.RecentTasksUtils
import com.android.wm.shell.flicker.utils.SplitScreenUtils
import org.junit.After
import org.junit.Before
diff --git a/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/DismissSplitScreenByDivider.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/DismissSplitScreenByDivider.kt
index 1af6cac39085..f574f02ac3b3 100644
--- a/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/DismissSplitScreenByDivider.kt
+++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/DismissSplitScreenByDivider.kt
@@ -23,8 +23,8 @@ import android.tools.traces.parsers.WindowManagerStateHelper
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.server.wm.flicker.helpers.RecentTasksUtils
import com.android.wm.shell.Utils
-import com.android.wm.shell.flicker.utils.RecentTasksUtils
import com.android.wm.shell.flicker.utils.SplitScreenUtils
import org.junit.After
import org.junit.Before
diff --git a/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/DismissSplitScreenByGoHome.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/DismissSplitScreenByGoHome.kt
index 8ad8c7bd7a7f..60fcce2fbf18 100644
--- a/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/DismissSplitScreenByGoHome.kt
+++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/DismissSplitScreenByGoHome.kt
@@ -23,8 +23,8 @@ import android.tools.traces.parsers.WindowManagerStateHelper
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.server.wm.flicker.helpers.RecentTasksUtils
import com.android.wm.shell.Utils
-import com.android.wm.shell.flicker.utils.RecentTasksUtils
import com.android.wm.shell.flicker.utils.SplitScreenUtils
import org.junit.After
import org.junit.Before
diff --git a/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/DragDividerToResize.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/DragDividerToResize.kt
index da0ace472153..e6a080b2d258 100644
--- a/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/DragDividerToResize.kt
+++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/DragDividerToResize.kt
@@ -23,8 +23,8 @@ import android.tools.traces.parsers.WindowManagerStateHelper
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.server.wm.flicker.helpers.RecentTasksUtils
import com.android.wm.shell.Utils
-import com.android.wm.shell.flicker.utils.RecentTasksUtils
import com.android.wm.shell.flicker.utils.SplitScreenUtils
import org.junit.After
import org.junit.Before
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/FromSplitScreenAutoEnterPipOnGoToHomeTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/FromSplitScreenAutoEnterPipOnGoToHomeTest.kt
index f9c60ad14fae..aa893ed65e7c 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/FromSplitScreenAutoEnterPipOnGoToHomeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/FromSplitScreenAutoEnterPipOnGoToHomeTest.kt
@@ -19,7 +19,6 @@ package com.android.wm.shell.flicker.pip
import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.Presubmit
import android.platform.test.annotations.RequiresDevice
-import android.platform.test.annotations.RequiresFlagsDisabled
import android.tools.Rotation
import android.tools.flicker.junit.FlickerParametersRunnerFactory
import android.tools.flicker.legacy.FlickerBuilder
@@ -29,7 +28,6 @@ import android.tools.helpers.WindowUtils
import android.tools.traces.parsers.toFlickerComponent
import com.android.server.wm.flicker.helpers.SimpleAppHelper
import com.android.server.wm.flicker.testapp.ActivityOptions
-import com.android.wm.shell.Flags
import com.android.wm.shell.flicker.utils.SplitScreenUtils
import org.junit.Assume
import org.junit.FixMethodOrder
@@ -64,7 +62,6 @@ import org.junit.runners.Parameterized
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-@RequiresFlagsDisabled(Flags.FLAG_ENABLE_PIP2)
open class FromSplitScreenAutoEnterPipOnGoToHomeTest(flicker: LegacyFlickerTest) :
AutoEnterPipOnGoToHomeTest(flicker) {
private val portraitDisplayBounds = WindowUtils.getDisplayBounds(Rotation.ROTATION_0)
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/FromSplitScreenEnterPipOnUserLeaveHintTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/FromSplitScreenEnterPipOnUserLeaveHintTest.kt
index 805f4c2fe7f8..8e7cb56c0f07 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/FromSplitScreenEnterPipOnUserLeaveHintTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/FromSplitScreenEnterPipOnUserLeaveHintTest.kt
@@ -19,7 +19,6 @@ package com.android.wm.shell.flicker.pip
import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.Presubmit
import android.platform.test.annotations.RequiresDevice
-import android.platform.test.annotations.RequiresFlagsDisabled
import android.tools.Rotation
import android.tools.flicker.junit.FlickerParametersRunnerFactory
import android.tools.flicker.legacy.FlickerBuilder
@@ -30,7 +29,6 @@ import android.tools.traces.parsers.toFlickerComponent
import com.android.server.wm.flicker.helpers.PipAppHelper
import com.android.server.wm.flicker.helpers.SimpleAppHelper
import com.android.server.wm.flicker.testapp.ActivityOptions
-import com.android.wm.shell.Flags
import com.android.wm.shell.flicker.pip.common.EnterPipTransition
import com.android.wm.shell.flicker.utils.SplitScreenUtils
import org.junit.Assume
@@ -66,7 +64,6 @@ import org.junit.runners.Parameterized
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-@RequiresFlagsDisabled(Flags.FLAG_ENABLE_PIP2)
@FlakyTest(bugId = 386333280)
open class FromSplitScreenEnterPipOnUserLeaveHintTest(flicker: LegacyFlickerTest) :
EnterPipTransition(flicker) {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleTransitionsTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleTransitionsTest.java
new file mode 100644
index 000000000000..9d0ddbc6de12
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleTransitionsTest.java
@@ -0,0 +1,211 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.wm.shell.bubbles;
+
+import static android.view.WindowManager.TRANSIT_CHANGE;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+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.ArgumentMatchers.isNull;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.ActivityManager;
+import android.os.IBinder;
+import android.view.SurfaceControl;
+import android.view.ViewRootImpl;
+import android.window.IWindowContainerToken;
+import android.window.TransitionInfo;
+import android.window.WindowContainerToken;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.launcher3.icons.BubbleIconFactory;
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.TestSyncExecutor;
+import com.android.wm.shell.bubbles.bar.BubbleBarExpandedView;
+import com.android.wm.shell.bubbles.bar.BubbleBarLayerView;
+import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.common.SyncTransactionQueue;
+import com.android.wm.shell.taskview.TaskView;
+import com.android.wm.shell.taskview.TaskViewRepository;
+import com.android.wm.shell.taskview.TaskViewTaskController;
+import com.android.wm.shell.taskview.TaskViewTransitions;
+import com.android.wm.shell.transition.Transitions;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Tests of {@link BubbleTransitions}.
+ */
+@SmallTest
+public class BubbleTransitionsTest extends ShellTestCase {
+ @Mock
+ private BubbleData mBubbleData;
+ @Mock
+ private Bubble mBubble;
+ @Mock
+ private Transitions mTransitions;
+ @Mock
+ private SyncTransactionQueue mSyncQueue;
+ @Mock
+ private BubbleExpandedViewManager mExpandedViewManager;
+ @Mock
+ private BubblePositioner mBubblePositioner;
+ @Mock
+ private BubbleLogger mBubbleLogger;
+ @Mock
+ private BubbleStackView mStackView;
+ @Mock
+ private BubbleBarLayerView mLayerView;
+ @Mock
+ private BubbleIconFactory mIconFactory;
+
+ @Mock private ShellTaskOrganizer mTaskOrganizer;
+ private TaskViewTransitions mTaskViewTransitions;
+ private TaskViewRepository mRepository;
+ private BubbleTransitions mBubbleTransitions;
+ private BubbleTaskViewFactory mTaskViewFactory;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mRepository = new TaskViewRepository();
+ ShellExecutor syncExecutor = new TestSyncExecutor();
+
+ when(mTransitions.getMainExecutor()).thenReturn(syncExecutor);
+ when(mTransitions.isRegistered()).thenReturn(true);
+ mTaskViewTransitions = new TaskViewTransitions(mTransitions, mRepository, mTaskOrganizer,
+ mSyncQueue);
+ mBubbleTransitions = new BubbleTransitions(mTransitions, mTaskOrganizer, mRepository,
+ mBubbleData, mTaskViewTransitions, mContext);
+ mTaskViewFactory = () -> {
+ TaskViewTaskController taskViewTaskController = new TaskViewTaskController(
+ mContext, mTaskOrganizer, mTaskViewTransitions, mSyncQueue);
+ TaskView taskView = new TaskView(mContext, mTaskViewTransitions,
+ taskViewTaskController);
+ return new BubbleTaskView(taskView, syncExecutor);
+ };
+ final BubbleBarExpandedView bbev = mock(BubbleBarExpandedView.class);
+ final ViewRootImpl vri = mock(ViewRootImpl.class);
+ when(bbev.getViewRootImpl()).thenReturn(vri);
+ when(mBubble.getBubbleBarExpandedView()).thenReturn(bbev);
+ }
+
+ private ActivityManager.RunningTaskInfo setupBubble() {
+ ActivityManager.RunningTaskInfo taskInfo = new ActivityManager.RunningTaskInfo();
+ final IWindowContainerToken itoken = mock(IWindowContainerToken.class);
+ final IBinder asBinder = mock(IBinder.class);
+ when(itoken.asBinder()).thenReturn(asBinder);
+ WindowContainerToken token = new WindowContainerToken(itoken);
+ taskInfo.token = token;
+ final TaskView tv = mock(TaskView.class);
+ final TaskViewTaskController tvtc = mock(TaskViewTaskController.class);
+ when(tvtc.getTaskInfo()).thenReturn(taskInfo);
+ when(tv.getController()).thenReturn(tvtc);
+ when(mBubble.getTaskView()).thenReturn(tv);
+ mRepository.add(tvtc);
+ return taskInfo;
+ }
+
+ @Test
+ public void testConvertToBubble() {
+ // Basic walk-through of convert-to-bubble transition stages
+ ActivityManager.RunningTaskInfo taskInfo = setupBubble();
+ final BubbleTransitions.BubbleTransition bt = mBubbleTransitions.startConvertToBubble(
+ mBubble, taskInfo, mExpandedViewManager, mTaskViewFactory, mBubblePositioner,
+ mBubbleLogger, mStackView, mLayerView, mIconFactory, false);
+ final BubbleTransitions.ConvertToBubble ctb = (BubbleTransitions.ConvertToBubble) bt;
+ ctb.onInflated(mBubble);
+ when(mLayerView.canExpandView(any())).thenReturn(true);
+ verify(mTransitions).startTransition(anyInt(), any(), eq(ctb));
+ verify(mBubble).setPreparingTransition(eq(bt));
+ // Ensure we are communicating with the taskviewtransitions queue
+ assertTrue(mTaskViewTransitions.hasPending());
+
+ final TransitionInfo info = new TransitionInfo(TRANSIT_CHANGE, 0);
+ final TransitionInfo.Change chg = new TransitionInfo.Change(taskInfo.token,
+ mock(SurfaceControl.class));
+ chg.setTaskInfo(taskInfo);
+ chg.setMode(TRANSIT_CHANGE);
+ info.addChange(chg);
+ info.addRoot(new TransitionInfo.Root(0, mock(SurfaceControl.class), 0, 0));
+ SurfaceControl.Transaction startT = mock(SurfaceControl.Transaction.class);
+ SurfaceControl.Transaction finishT = mock(SurfaceControl.Transaction.class);
+ final boolean[] finishCalled = new boolean[]{false};
+ Transitions.TransitionFinishCallback finishCb = wct -> {
+ assertFalse(finishCalled[0]);
+ finishCalled[0] = true;
+ };
+ ctb.startAnimation(ctb.mTransition, info, startT, finishT, finishCb);
+ assertFalse(mTaskViewTransitions.hasPending());
+
+ verify(mBubbleData).notificationEntryUpdated(eq(mBubble), anyBoolean(), anyBoolean());
+ ctb.continueExpand();
+
+ clearInvocations(mBubble);
+ verify(mBubble, never()).setPreparingTransition(any());
+
+ ctb.surfaceCreated();
+ verify(mBubble).setPreparingTransition(isNull());
+ ArgumentCaptor<Runnable> animCb = ArgumentCaptor.forClass(Runnable.class);
+ verify(mLayerView).animateConvert(any(), any(), any(), any(), animCb.capture());
+ assertFalse(finishCalled[0]);
+ animCb.getValue().run();
+ assertTrue(finishCalled[0]);
+ }
+
+ @Test
+ public void testConvertFromBubble() {
+ ActivityManager.RunningTaskInfo taskInfo = setupBubble();
+ final BubbleTransitions.BubbleTransition bt = mBubbleTransitions.startConvertFromBubble(
+ mBubble, taskInfo);
+ final BubbleTransitions.ConvertFromBubble cfb = (BubbleTransitions.ConvertFromBubble) bt;
+ verify(mTransitions).startTransition(anyInt(), any(), eq(cfb));
+ verify(mBubble).setPreparingTransition(eq(bt));
+ assertTrue(mTaskViewTransitions.hasPending());
+
+ final TransitionInfo info = new TransitionInfo(TRANSIT_CHANGE, 0);
+ final TransitionInfo.Change chg = new TransitionInfo.Change(taskInfo.token,
+ mock(SurfaceControl.class));
+ chg.setMode(TRANSIT_CHANGE);
+ chg.setTaskInfo(taskInfo);
+ info.addChange(chg);
+ info.addRoot(new TransitionInfo.Root(0, mock(SurfaceControl.class), 0, 0));
+ SurfaceControl.Transaction startT = mock(SurfaceControl.Transaction.class);
+ SurfaceControl.Transaction finishT = mock(SurfaceControl.Transaction.class);
+ Transitions.TransitionFinishCallback finishCb = wct -> {};
+ cfb.startAnimation(cfb.mTransition, info, startT, finishT, finishCb);
+
+ // Can really only verify that it interfaces with the taskViewTransitions queue.
+ // The actual functioning of this is tightly-coupled with SurfaceFlinger and renderthread
+ // in order to properly synchronize surface manipulation with drawing and thus can't be
+ // directly tested.
+ assertFalse(mTaskViewTransitions.hasPending());
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/MultiInstanceHelperTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/MultiInstanceHelperTest.kt
index bec91e910cf7..6b0c390ac239 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/MultiInstanceHelperTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/MultiInstanceHelperTest.kt
@@ -33,8 +33,6 @@ import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers
import org.mockito.ArgumentMatchers.eq
import org.mockito.kotlin.any
-import org.mockito.kotlin.doReturn
-import org.mockito.kotlin.doThrow
import org.mockito.kotlin.mock
import org.mockito.kotlin.never
import org.mockito.kotlin.verify
@@ -80,17 +78,19 @@ class MultiInstanceHelperTest : ShellTestCase() {
@Test
fun supportsMultiInstanceSplit_inStaticAllowList() {
val allowList = arrayOf(TEST_PACKAGE)
- val helper = MultiInstanceHelper(mContext, context.packageManager, allowList, true)
+ val helper = MultiInstanceHelper(mContext, context.packageManager, allowList,
+ mock(), mock(), true)
val component = ComponentName(TEST_PACKAGE, TEST_ACTIVITY)
- assertEquals(true, helper.supportsMultiInstanceSplit(component))
+ assertEquals(true, helper.supportsMultiInstanceSplit(component, TEST_OTHER_USER_ID))
}
@Test
fun supportsMultiInstanceSplit_notInStaticAllowList() {
val allowList = arrayOf(TEST_PACKAGE)
- val helper = MultiInstanceHelper(mContext, context.packageManager, allowList, true)
+ val helper = MultiInstanceHelper(mContext, context.packageManager, allowList,
+ mock(), mock(), true)
val component = ComponentName(TEST_NOT_ALLOWED_PACKAGE, TEST_ACTIVITY)
- assertEquals(false, helper.supportsMultiInstanceSplit(component))
+ assertEquals(false, helper.supportsMultiInstanceSplit(component, TEST_OTHER_USER_ID))
}
@Test
@@ -99,17 +99,17 @@ class MultiInstanceHelperTest : ShellTestCase() {
val component = ComponentName(TEST_PACKAGE, TEST_ACTIVITY)
val pm = mock<PackageManager>()
val activityProp = PackageManager.Property("", true, "", "")
- whenever(pm.getProperty(eq(PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI),
- eq(component)))
+ whenever(pm.getPropertyAsUser(eq(PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI),
+ eq(component.packageName), eq(component.className), eq(TEST_OTHER_USER_ID)))
.thenReturn(activityProp)
val appProp = PackageManager.Property("", false, "", "")
- whenever(pm.getProperty(eq(PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI),
- eq(component.packageName)))
+ whenever(pm.getPropertyAsUser(eq(PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI),
+ eq(component.packageName), eq(null), eq(TEST_OTHER_USER_ID)))
.thenReturn(appProp)
- val helper = MultiInstanceHelper(mContext, pm, emptyArray(), true)
+ val helper = MultiInstanceHelper(mContext, pm, emptyArray(), mock(), mock(), true)
// Expect activity property to override application property
- assertEquals(true, helper.supportsMultiInstanceSplit(component))
+ assertEquals(true, helper.supportsMultiInstanceSplit(component, TEST_OTHER_USER_ID))
}
@Test
@@ -118,17 +118,17 @@ class MultiInstanceHelperTest : ShellTestCase() {
val component = ComponentName(TEST_PACKAGE, TEST_ACTIVITY)
val pm = mock<PackageManager>()
val activityProp = PackageManager.Property("", false, "", "")
- whenever(pm.getProperty(eq(PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI),
- eq(component)))
+ whenever(pm.getPropertyAsUser(eq(PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI),
+ eq(component.packageName), eq(component.className), eq(TEST_OTHER_USER_ID)))
.thenReturn(activityProp)
val appProp = PackageManager.Property("", true, "", "")
- whenever(pm.getProperty(eq(PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI),
- eq(component.packageName)))
+ whenever(pm.getPropertyAsUser(eq(PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI),
+ eq(component.packageName), eq(null), eq(TEST_OTHER_USER_ID)))
.thenReturn(appProp)
- val helper = MultiInstanceHelper(mContext, pm, emptyArray(), true)
+ val helper = MultiInstanceHelper(mContext, pm, emptyArray(), mock(), mock(), true)
// Expect activity property to override application property
- assertEquals(false, helper.supportsMultiInstanceSplit(component))
+ assertEquals(false, helper.supportsMultiInstanceSplit(component, TEST_OTHER_USER_ID))
}
@Test
@@ -136,17 +136,17 @@ class MultiInstanceHelperTest : ShellTestCase() {
fun supportsMultiInstanceSplit_noActivityPropertyApplicationPropertyTrue() {
val component = ComponentName(TEST_PACKAGE, TEST_ACTIVITY)
val pm = mock<PackageManager>()
- whenever(pm.getProperty(eq(PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI),
- eq(component)))
+ whenever(pm.getPropertyAsUser(eq(PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI),
+ eq(component.packageName), eq(component.className), eq(TEST_OTHER_USER_ID)))
.thenThrow(PackageManager.NameNotFoundException())
val appProp = PackageManager.Property("", true, "", "")
- whenever(pm.getProperty(eq(PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI),
- eq(component.packageName)))
+ whenever(pm.getPropertyAsUser(eq(PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI),
+ eq(component.packageName), eq(null), eq(TEST_OTHER_USER_ID)))
.thenReturn(appProp)
- val helper = MultiInstanceHelper(mContext, pm, emptyArray(), true)
+ val helper = MultiInstanceHelper(mContext, pm, emptyArray(), mock(), mock(), true)
// Expect fall through to app property
- assertEquals(true, helper.supportsMultiInstanceSplit(component))
+ assertEquals(true, helper.supportsMultiInstanceSplit(component, TEST_OTHER_USER_ID))
}
@Test
@@ -154,15 +154,15 @@ class MultiInstanceHelperTest : ShellTestCase() {
fun supportsMultiInstanceSplit_noActivityOrAppProperty() {
val component = ComponentName(TEST_PACKAGE, TEST_ACTIVITY)
val pm = mock<PackageManager>()
- whenever(pm.getProperty(eq(PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI),
- eq(component)))
+ whenever(pm.getPropertyAsUser(eq(PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI),
+ eq(component.packageName), eq(component.className), eq(TEST_OTHER_USER_ID)))
.thenThrow(PackageManager.NameNotFoundException())
- whenever(pm.getProperty(eq(PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI),
- eq(component.packageName)))
+ whenever(pm.getPropertyAsUser(eq(PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI),
+ eq(component.packageName), eq(null), eq(TEST_OTHER_USER_ID)))
.thenThrow(PackageManager.NameNotFoundException())
- val helper = MultiInstanceHelper(mContext, pm, emptyArray(), true)
- assertEquals(false, helper.supportsMultiInstanceSplit(component))
+ val helper = MultiInstanceHelper(mContext, pm, emptyArray(), mock(), mock(), true)
+ assertEquals(false, helper.supportsMultiInstanceSplit(component, TEST_OTHER_USER_ID))
}
@Test
@@ -171,24 +171,25 @@ class MultiInstanceHelperTest : ShellTestCase() {
val component = ComponentName(TEST_PACKAGE, TEST_ACTIVITY)
val pm = mock<PackageManager>()
val activityProp = PackageManager.Property("", true, "", "")
- whenever(pm.getProperty(eq(PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI),
- eq(component)))
+ whenever(pm.getPropertyAsUser(eq(PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI),
+ eq(component.packageName), eq(component.className), eq(TEST_OTHER_USER_ID)))
.thenReturn(activityProp)
val appProp = PackageManager.Property("", true, "", "")
- whenever(pm.getProperty(eq(PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI),
- eq(component.packageName)))
+ whenever(pm.getPropertyAsUser(eq(PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI),
+ eq(component.packageName), eq(null), eq(TEST_OTHER_USER_ID)))
.thenReturn(appProp)
- val helper = MultiInstanceHelper(mContext, pm, emptyArray(), false)
+ val helper = MultiInstanceHelper(mContext, pm, emptyArray(), mock(), mock(), false)
// Expect we only check the static list and not the property
- assertEquals(false, helper.supportsMultiInstanceSplit(component))
+ assertEquals(false, helper.supportsMultiInstanceSplit(component, TEST_OTHER_USER_ID))
verify(pm, never()).getProperty(any(), any<ComponentName>())
}
companion object {
val TEST_PACKAGE = "com.android.wm.shell.common"
- val TEST_NOT_ALLOWED_PACKAGE = "com.android.wm.shell.common.fake";
- val TEST_ACTIVITY = "TestActivity";
+ val TEST_NOT_ALLOWED_PACKAGE = "com.android.wm.shell.common.fake"
+ val TEST_ACTIVITY = "TestActivity"
val TEST_SHORTCUT_ID = "test_shortcut_1"
+ val TEST_OTHER_USER_ID = 1234
}
} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/UserProfileContextsTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/UserProfileContextsTest.kt
new file mode 100644
index 000000000000..ef0b8ab14c81
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/UserProfileContextsTest.kt
@@ -0,0 +1,166 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.common
+
+import android.app.ActivityManager
+import android.content.Context
+import android.content.pm.UserInfo
+import android.os.UserHandle
+import android.os.UserManager
+import android.testing.AndroidTestingRunner
+import com.android.wm.shell.ShellTestCase
+import com.android.wm.shell.TestShellExecutor
+import com.android.wm.shell.sysui.ShellController
+import com.android.wm.shell.sysui.ShellInit
+import com.android.wm.shell.sysui.UserChangeListener
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.anyInt
+import org.mockito.kotlin.any
+import org.mockito.kotlin.argumentCaptor
+import org.mockito.kotlin.doAnswer
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.times
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
+
+/**
+ * Tests for [UserProfileContexts].
+ */
+@RunWith(AndroidTestingRunner::class)
+class UserProfileContextsTest : ShellTestCase() {
+
+ private val testExecutor = TestShellExecutor()
+ private val shellInit = ShellInit(testExecutor)
+ private val activityManager = mock<ActivityManager>()
+ private val userManager = mock<UserManager>()
+ private val shellController = mock<ShellController>()
+ private val baseContext = mock<Context>()
+
+ private lateinit var userProfilesContexts: UserProfileContexts
+
+ @Before
+ fun setUp() {
+ doReturn(activityManager)
+ .whenever(baseContext)
+ .getSystemService(eq(ActivityManager::class.java))
+ doReturn(userManager).whenever(baseContext).getSystemService(eq(UserManager::class.java))
+ doAnswer { invocation ->
+ val userHandle = invocation.getArgument<UserHandle>(0)
+ createContextForUser(userHandle.identifier)
+ }
+ .whenever(baseContext)
+ .createContextAsUser(any<UserHandle>(), anyInt())
+ // Define users and profiles
+ val currentUser = ActivityManager.getCurrentUser()
+ whenever(userManager.getProfiles(eq(currentUser)))
+ .thenReturn(
+ listOf(UserInfo(currentUser, "Current", 0), UserInfo(MAIN_PROFILE, "Work", 0))
+ )
+ whenever(userManager.getProfiles(eq(SECOND_USER))).thenReturn(SECOND_PROFILES)
+ userProfilesContexts = UserProfileContexts(baseContext, shellController, shellInit)
+ shellInit.init()
+ }
+
+ @Test
+ fun onInit_registerUserChangeAndInit() {
+ val currentUser = ActivityManager.getCurrentUser()
+
+ verify(shellController, times(1)).addUserChangeListener(any())
+ assertThat(userProfilesContexts.userContext.userId).isEqualTo(currentUser)
+ assertThat(userProfilesContexts[currentUser]?.userId).isEqualTo(currentUser)
+ assertThat(userProfilesContexts[MAIN_PROFILE]?.userId).isEqualTo(MAIN_PROFILE)
+ assertThat(userProfilesContexts[SECOND_USER]).isNull()
+ }
+
+ @Test
+ fun onUserChanged_updateUserContext() {
+ val userChangeListener = retrieveUserChangeListener()
+ val newUserContext = createContextForUser(SECOND_USER)
+
+ userChangeListener.onUserChanged(SECOND_USER, newUserContext)
+
+ assertThat(userProfilesContexts.userContext).isEqualTo(newUserContext)
+ assertThat(userProfilesContexts[SECOND_USER]).isEqualTo(newUserContext)
+ }
+
+ @Test
+ fun onUserProfilesChanged_updateAllContexts() {
+ val userChangeListener = retrieveUserChangeListener()
+ val newUserContext = createContextForUser(SECOND_USER)
+ userChangeListener.onUserChanged(SECOND_USER, newUserContext)
+
+ userChangeListener.onUserProfilesChanged(SECOND_PROFILES)
+
+ assertThat(userProfilesContexts.userContext).isEqualTo(newUserContext)
+ assertThat(userProfilesContexts[SECOND_USER]).isEqualTo(newUserContext)
+ assertThat(userProfilesContexts[SECOND_PROFILE]?.userId).isEqualTo(SECOND_PROFILE)
+ assertThat(userProfilesContexts[SECOND_PROFILE_2]?.userId).isEqualTo(SECOND_PROFILE_2)
+ }
+
+ @Test
+ fun onUserProfilesChanged_keepOnlyNewProfiles() {
+ val userChangeListener = retrieveUserChangeListener()
+ val newUserContext = createContextForUser(SECOND_USER)
+ userChangeListener.onUserChanged(SECOND_USER, newUserContext)
+ userChangeListener.onUserProfilesChanged(SECOND_PROFILES)
+ val newProfiles = listOf(
+ UserInfo(SECOND_USER, "Second", 0),
+ UserInfo(SECOND_PROFILE, "Second Profile", 0),
+ UserInfo(MAIN_PROFILE, "Main profile", 0),
+ )
+
+ userChangeListener.onUserProfilesChanged(newProfiles)
+
+ assertThat(userProfilesContexts[SECOND_PROFILE_2]).isNull()
+ assertThat(userProfilesContexts[MAIN_PROFILE]?.userId).isEqualTo(MAIN_PROFILE)
+ assertThat(userProfilesContexts[SECOND_USER]?.userId).isEqualTo(SECOND_USER)
+ assertThat(userProfilesContexts[SECOND_PROFILE]?.userId).isEqualTo(SECOND_PROFILE)
+ }
+
+ private fun retrieveUserChangeListener(): UserChangeListener {
+ val captor = argumentCaptor<UserChangeListener>()
+
+ verify(shellController, times(1)).addUserChangeListener(captor.capture())
+
+ return captor.firstValue
+ }
+
+ private fun createContextForUser(userId: Int): Context {
+ val newContext = mock<Context>()
+ whenever(newContext.userId).thenReturn(userId)
+ return newContext
+ }
+
+ private companion object {
+ const val SECOND_USER = 3
+ const val MAIN_PROFILE = 11
+ const val SECOND_PROFILE = 15
+ const val SECOND_PROFILE_2 = 17
+
+ val SECOND_PROFILES =
+ listOf(
+ UserInfo(SECOND_USER, "Second", 0),
+ UserInfo(SECOND_PROFILE, "Second Profile", 0),
+ UserInfo(SECOND_PROFILE_2, "Second Profile 2", 0),
+ )
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopActivityOrientationChangeHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopActivityOrientationChangeHandlerTest.kt
index ecad5217b87f..957fdf995776 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopActivityOrientationChangeHandlerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopActivityOrientationChangeHandlerTest.kt
@@ -183,6 +183,8 @@ class DesktopActivityOrientationChangeHandlerTest : ShellTestCase() {
@Test
fun handleActivityOrientationChange_resizeable_doNothing() {
+ userRepositories.current.addDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
+ userRepositories.current.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
val task = setUpFreeformTask()
taskStackListener.onActivityRequestedOrientationChanged(
@@ -195,6 +197,8 @@ class DesktopActivityOrientationChangeHandlerTest : ShellTestCase() {
@Test
fun handleActivityOrientationChange_nonResizeableFullscreen_doNothing() {
+ userRepositories.current.addDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
+ userRepositories.current.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
val task = createFullscreenTask()
task.isResizeable = false
val activityInfo = ActivityInfo()
@@ -214,6 +218,8 @@ class DesktopActivityOrientationChangeHandlerTest : ShellTestCase() {
@Test
fun handleActivityOrientationChange_nonResizeablePortrait_requestSameOrientation_doNothing() {
+ userRepositories.current.addDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
+ userRepositories.current.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
val task = setUpFreeformTask(isResizeable = false)
val newTask =
setUpFreeformTask(
@@ -228,6 +234,8 @@ class DesktopActivityOrientationChangeHandlerTest : ShellTestCase() {
@Test
fun handleActivityOrientationChange_notInDesktopMode_doNothing() {
+ userRepositories.current.addDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
+ userRepositories.current.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
val task = setUpFreeformTask(isResizeable = false)
userRepositories.current.updateTask(task.displayId, task.taskId, isVisible = false)
@@ -241,6 +249,8 @@ class DesktopActivityOrientationChangeHandlerTest : ShellTestCase() {
@Test
fun handleActivityOrientationChange_nonResizeablePortrait_respectLandscapeRequest() {
+ userRepositories.current.addDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
+ userRepositories.current.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
val task = setUpFreeformTask(isResizeable = false)
val oldBounds = task.configuration.windowConfiguration.bounds
val newTask =
@@ -263,6 +273,8 @@ class DesktopActivityOrientationChangeHandlerTest : ShellTestCase() {
@Test
fun handleActivityOrientationChange_nonResizeableLandscape_respectPortraitRequest() {
+ userRepositories.current.addDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
+ userRepositories.current.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
val oldBounds = Rect(0, 0, 500, 200)
val task =
setUpFreeformTask(
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandlerTest.kt
index 6a3717427e93..fae7363e0676 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandlerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandlerTest.kt
@@ -16,10 +16,14 @@
package com.android.wm.shell.desktopmode
+import android.app.ActivityManager.RunningTaskInfo
import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
+import android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED
import android.content.ContentResolver
import android.os.Binder
+import android.platform.test.annotations.EnableFlags
+import android.platform.test.flag.junit.SetFlagsRule
import android.provider.Settings
import android.provider.Settings.Global.DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS
import android.testing.AndroidTestingRunner
@@ -29,20 +33,27 @@ import android.view.WindowManager.TRANSIT_CHANGE
import android.window.DisplayAreaInfo
import android.window.WindowContainerTransaction
import androidx.test.filters.SmallTest
+import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession
import com.android.dx.mockito.inline.extended.ExtendedMockito.never
+import com.android.dx.mockito.inline.extended.StaticMockitoSession
+import com.android.window.flags.Flags
import com.android.wm.shell.MockToken
import com.android.wm.shell.RootTaskDisplayAreaOrganizer
+import com.android.wm.shell.ShellTaskOrganizer
import com.android.wm.shell.ShellTestCase
+import com.android.wm.shell.TestRunningTaskInfoBuilder
import com.android.wm.shell.common.DisplayController
import com.android.wm.shell.common.DisplayController.OnDisplaysChangedListener
import com.android.wm.shell.common.ShellExecutor
+import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
import com.android.wm.shell.sysui.ShellInit
import com.android.wm.shell.transition.Transitions
import com.google.common.truth.Truth.assertThat
+import org.junit.After
import org.junit.Before
+import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
-import org.mockito.ArgumentCaptor
import org.mockito.ArgumentMatchers.isNull
import org.mockito.Mock
import org.mockito.Mockito.anyInt
@@ -53,6 +64,7 @@ import org.mockito.kotlin.any
import org.mockito.kotlin.argumentCaptor
import org.mockito.kotlin.eq
import org.mockito.kotlin.whenever
+import org.mockito.quality.Strictness
/**
* Test class for [DesktopDisplayEventHandler]
@@ -63,21 +75,44 @@ import org.mockito.kotlin.whenever
@RunWith(AndroidTestingRunner::class)
class DesktopDisplayEventHandlerTest : ShellTestCase() {
+ @JvmField @Rule val setFlagsRule = SetFlagsRule()
+
@Mock lateinit var testExecutor: ShellExecutor
@Mock lateinit var transitions: Transitions
@Mock lateinit var displayController: DisplayController
@Mock lateinit var rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer
@Mock private lateinit var mockWindowManager: IWindowManager
+ @Mock private lateinit var mockDesktopUserRepositories: DesktopUserRepositories
+ @Mock private lateinit var mockDesktopRepository: DesktopRepository
+ @Mock private lateinit var mockDesktopTasksController: DesktopTasksController
+ @Mock private lateinit var shellTaskOrganizer: ShellTaskOrganizer
+ private lateinit var mockitoSession: StaticMockitoSession
private lateinit var shellInit: ShellInit
private lateinit var handler: DesktopDisplayEventHandler
+ private val onDisplaysChangedListenerCaptor = argumentCaptor<OnDisplaysChangedListener>()
+ private val runningTasks = mutableListOf<RunningTaskInfo>()
+ private val externalDisplayId = 100
+ private val freeformTask =
+ TestRunningTaskInfoBuilder().setWindowingMode(WINDOWING_MODE_FREEFORM).build()
+ private val fullscreenTask =
+ TestRunningTaskInfoBuilder().setWindowingMode(WINDOWING_MODE_FULLSCREEN).build()
+ private val defaultTDA = DisplayAreaInfo(MockToken().token(), DEFAULT_DISPLAY, 0)
+
@Before
fun setUp() {
+ mockitoSession =
+ mockitoSession()
+ .strictness(Strictness.LENIENT)
+ .spyStatic(DesktopModeStatus::class.java)
+ .startMocking()
+
shellInit = spy(ShellInit(testExecutor))
+ whenever(mockDesktopUserRepositories.current).thenReturn(mockDesktopRepository)
whenever(transitions.startTransition(anyInt(), any(), isNull())).thenAnswer { Binder() }
- val tda = DisplayAreaInfo(MockToken().token(), DEFAULT_DISPLAY, 0)
- whenever(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)).thenReturn(tda)
+ whenever(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY))
+ .thenReturn(defaultTDA)
handler =
DesktopDisplayEventHandler(
context,
@@ -86,8 +121,21 @@ class DesktopDisplayEventHandlerTest : ShellTestCase() {
displayController,
rootTaskDisplayAreaOrganizer,
mockWindowManager,
+ mockDesktopUserRepositories,
+ mockDesktopTasksController,
+ shellTaskOrganizer,
)
+ whenever(shellTaskOrganizer.getRunningTasks(anyInt())).thenAnswer { runningTasks }
+ runningTasks.add(freeformTask)
+ runningTasks.add(fullscreenTask)
shellInit.init()
+ verify(displayController)
+ .addDisplayWindowListener(onDisplaysChangedListenerCaptor.capture())
+ }
+
+ @After
+ fun tearDown() {
+ mockitoSession.finishMocking()
}
private fun testDisplayWindowingModeSwitch(
@@ -95,11 +143,7 @@ class DesktopDisplayEventHandlerTest : ShellTestCase() {
extendedDisplayEnabled: Boolean,
expectTransition: Boolean,
) {
- val externalDisplayId = 100
- val captor = ArgumentCaptor.forClass(OnDisplaysChangedListener::class.java)
- verify(displayController).addDisplayWindowListener(captor.capture())
- val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!!
- tda.configuration.windowConfiguration.windowingMode = defaultWindowingMode
+ defaultTDA.configuration.windowConfiguration.windowingMode = defaultWindowingMode
whenever(mockWindowManager.getWindowingMode(anyInt())).thenAnswer { defaultWindowingMode }
val settingsSession =
ExtendedDisplaySettingsSession(
@@ -108,23 +152,17 @@ class DesktopDisplayEventHandlerTest : ShellTestCase() {
)
settingsSession.use {
- // The external display connected.
- whenever(rootTaskDisplayAreaOrganizer.getDisplayIds())
- .thenReturn(intArrayOf(DEFAULT_DISPLAY, externalDisplayId))
- captor.value.onDisplayAdded(externalDisplayId)
- tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FREEFORM
- // The external display disconnected.
- whenever(rootTaskDisplayAreaOrganizer.getDisplayIds())
- .thenReturn(intArrayOf(DEFAULT_DISPLAY))
- captor.value.onDisplayRemoved(externalDisplayId)
+ connectExternalDisplay()
+ defaultTDA.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FREEFORM
+ disconnectExternalDisplay()
if (expectTransition) {
val arg = argumentCaptor<WindowContainerTransaction>()
verify(transitions, times(2))
.startTransition(eq(TRANSIT_CHANGE), arg.capture(), isNull())
- assertThat(arg.firstValue.changes[tda.token.asBinder()]?.windowingMode)
+ assertThat(arg.firstValue.changes[defaultTDA.token.asBinder()]?.windowingMode)
.isEqualTo(WINDOWING_MODE_FREEFORM)
- assertThat(arg.secondValue.changes[tda.token.asBinder()]?.windowingMode)
+ assertThat(arg.secondValue.changes[defaultTDA.token.asBinder()]?.windowingMode)
.isEqualTo(defaultWindowingMode)
} else {
verify(transitions, never()).startTransition(eq(TRANSIT_CHANGE), any(), isNull())
@@ -159,6 +197,96 @@ class DesktopDisplayEventHandlerTest : ShellTestCase() {
)
}
+ @Test
+ fun testDisplayAdded_supportsDesks_createsDesk() {
+ whenever(DesktopModeStatus.canEnterDesktopMode(context)).thenReturn(true)
+
+ onDisplaysChangedListenerCaptor.lastValue.onDisplayAdded(DEFAULT_DISPLAY)
+
+ verify(mockDesktopTasksController).createDesk(DEFAULT_DISPLAY)
+ }
+
+ @Test
+ fun testDisplayAdded_cannotEnterDesktopMode_doesNotCreateDesk() {
+ whenever(DesktopModeStatus.canEnterDesktopMode(context)).thenReturn(false)
+
+ onDisplaysChangedListenerCaptor.lastValue.onDisplayAdded(DEFAULT_DISPLAY)
+
+ verify(mockDesktopTasksController, never()).createDesk(DEFAULT_DISPLAY)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun testDeskRemoved_noDesksRemain_createsDesk() {
+ whenever(mockDesktopRepository.getNumberOfDesks(DEFAULT_DISPLAY)).thenReturn(0)
+
+ handler.onDeskRemoved(DEFAULT_DISPLAY, deskId = 1)
+
+ verify(mockDesktopTasksController).createDesk(DEFAULT_DISPLAY)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun testDeskRemoved_desksRemain_doesNotCreateDesk() {
+ whenever(mockDesktopRepository.getNumberOfDesks(DEFAULT_DISPLAY)).thenReturn(1)
+
+ handler.onDeskRemoved(DEFAULT_DISPLAY, deskId = 1)
+
+ verify(mockDesktopTasksController, never()).createDesk(DEFAULT_DISPLAY)
+ }
+
+ @Test
+ fun displayWindowingModeSwitch_existingTasksOnConnected() {
+ defaultTDA.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FULLSCREEN
+ whenever(mockWindowManager.getWindowingMode(anyInt())).thenAnswer {
+ WINDOWING_MODE_FULLSCREEN
+ }
+
+ ExtendedDisplaySettingsSession(context.contentResolver, 1).use {
+ connectExternalDisplay()
+
+ val arg = argumentCaptor<WindowContainerTransaction>()
+ verify(transitions, times(1))
+ .startTransition(eq(TRANSIT_CHANGE), arg.capture(), isNull())
+ assertThat(arg.firstValue.changes[freeformTask.token.asBinder()]?.windowingMode)
+ .isEqualTo(WINDOWING_MODE_UNDEFINED)
+ assertThat(arg.firstValue.changes[fullscreenTask.token.asBinder()]?.windowingMode)
+ .isEqualTo(WINDOWING_MODE_FULLSCREEN)
+ }
+ }
+
+ @Test
+ fun displayWindowingModeSwitch_existingTasksOnDisconnected() {
+ defaultTDA.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FREEFORM
+ whenever(mockWindowManager.getWindowingMode(anyInt())).thenAnswer {
+ WINDOWING_MODE_FULLSCREEN
+ }
+
+ ExtendedDisplaySettingsSession(context.contentResolver, 1).use {
+ disconnectExternalDisplay()
+
+ val arg = argumentCaptor<WindowContainerTransaction>()
+ verify(transitions, times(1))
+ .startTransition(eq(TRANSIT_CHANGE), arg.capture(), isNull())
+ assertThat(arg.firstValue.changes[freeformTask.token.asBinder()]?.windowingMode)
+ .isEqualTo(WINDOWING_MODE_FREEFORM)
+ assertThat(arg.firstValue.changes[fullscreenTask.token.asBinder()]?.windowingMode)
+ .isEqualTo(WINDOWING_MODE_UNDEFINED)
+ }
+ }
+
+ private fun connectExternalDisplay() {
+ whenever(rootTaskDisplayAreaOrganizer.getDisplayIds())
+ .thenReturn(intArrayOf(DEFAULT_DISPLAY, externalDisplayId))
+ onDisplaysChangedListenerCaptor.lastValue.onDisplayAdded(externalDisplayId)
+ }
+
+ private fun disconnectExternalDisplay() {
+ whenever(rootTaskDisplayAreaOrganizer.getDisplayIds())
+ .thenReturn(intArrayOf(DEFAULT_DISPLAY))
+ onDisplaysChangedListenerCaptor.lastValue.onDisplayRemoved(externalDisplayId)
+ }
+
private class ExtendedDisplaySettingsSession(
private val contentResolver: ContentResolver,
private val overrideValue: Int,
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt
index 8d73f3f59afd..f5c93ee8ffe4 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt
@@ -17,13 +17,15 @@
package com.android.wm.shell.desktopmode
import android.graphics.Rect
+import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
+import android.platform.test.flag.junit.FlagsParameterization
import android.platform.test.flag.junit.SetFlagsRule
-import android.testing.AndroidTestingRunner
import android.util.ArraySet
import android.view.Display.DEFAULT_DISPLAY
import android.view.Display.INVALID_DISPLAY
import androidx.test.filters.SmallTest
+import com.android.window.flags.Flags
import com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_PERSISTENCE
import com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_PIP
import com.android.wm.shell.ShellTestCase
@@ -56,6 +58,8 @@ import org.mockito.kotlin.never
import org.mockito.kotlin.times
import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
/**
* Tests for [@link DesktopRepository].
@@ -63,11 +67,11 @@ import org.mockito.kotlin.whenever
* Build/Install/Run: atest WMShellUnitTests:DesktopRepositoryTest
*/
@SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RunWith(ParameterizedAndroidJunit4::class)
@ExperimentalCoroutinesApi
-class DesktopRepositoryTest : ShellTestCase() {
+class DesktopRepositoryTest(flags: FlagsParameterization) : ShellTestCase() {
- @JvmField @Rule val setFlagsRule = SetFlagsRule()
+ @JvmField @Rule val setFlagsRule = SetFlagsRule(flags)
private lateinit var repo: DesktopRepository
private lateinit var shellInit: ShellInit
@@ -86,6 +90,8 @@ class DesktopRepositoryTest : ShellTestCase() {
whenever(runBlocking { persistentRepository.readDesktop(any(), any()) })
.thenReturn(Desktop.getDefaultInstance())
shellInit.init()
+ repo.addDesk(displayId = DEFAULT_DISPLAY, deskId = DEFAULT_DESKTOP_ID)
+ repo.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = DEFAULT_DESKTOP_ID)
}
@After
@@ -137,6 +143,7 @@ class DesktopRepositoryTest : ShellTestCase() {
@Test
fun addTask_multipleDisplays_notifiesCorrectListener() {
+ repo.addDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY)
val listener = TestListener()
repo.addActiveTaskListener(listener)
@@ -150,6 +157,7 @@ class DesktopRepositoryTest : ShellTestCase() {
@Test
fun addTask_multipleDisplays_moveToAnotherDisplay() {
+ repo.addDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY)
repo.addTask(DEFAULT_DISPLAY, taskId = 1, isVisible = true)
repo.addTask(SECOND_DISPLAY, taskId = 1, isVisible = true)
assertThat(repo.getFreeformTasksInZOrder(DEFAULT_DISPLAY)).isEmpty()
@@ -310,19 +318,21 @@ class DesktopRepositoryTest : ShellTestCase() {
@Test
fun isOnlyVisibleNonClosingTask_multipleDisplays() {
+ repo.addDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY)
+ repo.setActiveDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY)
repo.updateTask(DEFAULT_DISPLAY, taskId = 1, isVisible = true)
repo.updateTask(DEFAULT_DISPLAY, taskId = 2, isVisible = true)
repo.updateTask(SECOND_DISPLAY, taskId = 3, isVisible = true)
// Not the only task on DEFAULT_DISPLAY
assertThat(repo.isVisibleTask(1)).isTrue()
- assertThat(repo.isOnlyVisibleNonClosingTask(1)).isFalse()
+ assertThat(repo.isOnlyVisibleNonClosingTask(1, DEFAULT_DISPLAY)).isFalse()
// Not the only task on DEFAULT_DISPLAY
assertThat(repo.isVisibleTask(2)).isTrue()
- assertThat(repo.isOnlyVisibleNonClosingTask(2)).isFalse()
+ assertThat(repo.isOnlyVisibleNonClosingTask(2, DEFAULT_DISPLAY)).isFalse()
// The only visible task on SECOND_DISPLAY
assertThat(repo.isVisibleTask(3)).isTrue()
- assertThat(repo.isOnlyVisibleNonClosingTask(3)).isTrue()
+ assertThat(repo.isOnlyVisibleNonClosingTask(3, SECOND_DISPLAY)).isTrue()
// Not a visible task
assertThat(repo.isVisibleTask(99)).isFalse()
assertThat(repo.isOnlyVisibleNonClosingTask(99)).isFalse()
@@ -343,6 +353,7 @@ class DesktopRepositoryTest : ShellTestCase() {
@Test
fun addListener_tasksOnDifferentDisplay_doesNotNotify() {
+ repo.addDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY)
repo.updateTask(SECOND_DISPLAY, taskId = 1, isVisible = true)
val listener = TestVisibilityListener()
val executor = TestShellExecutor()
@@ -351,7 +362,7 @@ class DesktopRepositoryTest : ShellTestCase() {
assertThat(listener.visibleTasksCountOnDefaultDisplay).isEqualTo(0)
// One call as adding listener notifies it
- assertThat(listener.visibleChangesOnDefaultDisplay).isEqualTo(0)
+ assertThat(listener.visibleChangesOnDefaultDisplay).isEqualTo(1)
}
@Test
@@ -365,11 +376,14 @@ class DesktopRepositoryTest : ShellTestCase() {
executor.flushAll()
assertThat(listener.visibleTasksCountOnDefaultDisplay).isEqualTo(2)
- assertThat(listener.visibleChangesOnDefaultDisplay).isEqualTo(2)
+ // 1 from registration, 2 for the updates.
+ assertThat(listener.visibleChangesOnDefaultDisplay).isEqualTo(3)
}
@Test
fun updateTask_visibleTask_addVisibleTaskNotifiesListenerForThatDisplay() {
+ repo.addDesk(displayId = 1, deskId = 1)
+ repo.setActiveDesk(displayId = 1, deskId = 1)
val listener = TestVisibilityListener()
val executor = TestShellExecutor()
repo.addVisibleTasksListener(listener, executor)
@@ -378,22 +392,27 @@ class DesktopRepositoryTest : ShellTestCase() {
executor.flushAll()
assertThat(listener.visibleTasksCountOnDefaultDisplay).isEqualTo(1)
- assertThat(listener.visibleChangesOnDefaultDisplay).isEqualTo(1)
+ // 1 for the registration, 1 for the update.
+ assertThat(listener.visibleChangesOnDefaultDisplay).isEqualTo(2)
assertThat(listener.visibleTasksCountOnSecondaryDisplay).isEqualTo(0)
- assertThat(listener.visibleChangesOnSecondaryDisplay).isEqualTo(0)
+ // 1 for the registration.
+ assertThat(listener.visibleChangesOnSecondaryDisplay).isEqualTo(1)
repo.updateTask(displayId = 1, taskId = 2, isVisible = true)
executor.flushAll()
// Listener for secondary display is notified
assertThat(listener.visibleTasksCountOnSecondaryDisplay).isEqualTo(1)
- assertThat(listener.visibleChangesOnSecondaryDisplay).isEqualTo(1)
+ // 1 for the registration, 1 for the update.
+ assertThat(listener.visibleChangesOnSecondaryDisplay).isEqualTo(2)
// No changes to listener for default display
- assertThat(listener.visibleChangesOnDefaultDisplay).isEqualTo(1)
+ assertThat(listener.visibleChangesOnDefaultDisplay).isEqualTo(2)
}
@Test
fun updateTask_taskOnDefaultBecomesVisibleOnSecondDisplay_listenersNotified() {
+ repo.addDesk(displayId = 1, deskId = 1)
+ repo.setActiveDesk(displayId = 1, deskId = 1)
val listener = TestVisibilityListener()
val executor = TestShellExecutor()
repo.addVisibleTasksListener(listener, executor)
@@ -406,14 +425,15 @@ class DesktopRepositoryTest : ShellTestCase() {
repo.updateTask(displayId = 1, taskId = 1, isVisible = true)
executor.flushAll()
- // Default display should have 2 calls
- // 1 - visible task added
- // 2 - visible task removed
- assertThat(listener.visibleChangesOnDefaultDisplay).isEqualTo(2)
+ // Default display should have 3 calls
+ // 1 - listener registered
+ // 2 - visible task added
+ // 3 - visible task removed
+ assertThat(listener.visibleChangesOnDefaultDisplay).isEqualTo(3)
assertThat(listener.visibleTasksCountOnDefaultDisplay).isEqualTo(0)
- // Secondary display should have 1 call for visible task added
- assertThat(listener.visibleChangesOnSecondaryDisplay).isEqualTo(1)
+ // Secondary display should have 2 calls for registration + visible task added
+ assertThat(listener.visibleChangesOnSecondaryDisplay).isEqualTo(2)
assertThat(listener.visibleTasksCountOnSecondaryDisplay).isEqualTo(1)
}
@@ -431,13 +451,13 @@ class DesktopRepositoryTest : ShellTestCase() {
repo.updateTask(DEFAULT_DISPLAY, taskId = 1, isVisible = false)
executor.flushAll()
- assertThat(listener.visibleChangesOnDefaultDisplay).isEqualTo(3)
+ assertThat(listener.visibleChangesOnDefaultDisplay).isEqualTo(4)
repo.updateTask(DEFAULT_DISPLAY, taskId = 2, isVisible = false)
executor.flushAll()
assertThat(listener.visibleTasksCountOnDefaultDisplay).isEqualTo(0)
- assertThat(listener.visibleChangesOnDefaultDisplay).isEqualTo(4)
+ assertThat(listener.visibleChangesOnDefaultDisplay).isEqualTo(5)
}
/**
@@ -458,7 +478,8 @@ class DesktopRepositoryTest : ShellTestCase() {
repo.updateTask(INVALID_DISPLAY, taskId = 1, isVisible = false)
executor.flushAll()
- assertThat(listener.visibleChangesOnDefaultDisplay).isEqualTo(3)
+ // 1 from registering, 1x3 for each update including the one to the invalid display.
+ assertThat(listener.visibleChangesOnDefaultDisplay).isEqualTo(4)
assertThat(listener.visibleTasksCountOnDefaultDisplay).isEqualTo(1)
}
@@ -497,6 +518,8 @@ class DesktopRepositoryTest : ShellTestCase() {
@Test
fun getVisibleTaskCount_multipleDisplays_returnsCorrectCount() {
+ repo.addDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY)
+ repo.setActiveDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY)
assertThat(repo.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(0)
assertThat(repo.getVisibleTaskCount(SECOND_DISPLAY)).isEqualTo(0)
@@ -674,8 +697,6 @@ class DesktopRepositoryTest : ShellTestCase() {
repo.removeTask(INVALID_DISPLAY, taskId = 1)
- val invalidDisplayTasks = repo.getFreeformTasksInZOrder(INVALID_DISPLAY)
- assertThat(invalidDisplayTasks).isEmpty()
val validDisplayTasks = repo.getFreeformTasksInZOrder(DEFAULT_DISPLAY)
assertThat(validDisplayTasks).isEmpty()
}
@@ -746,6 +767,7 @@ class DesktopRepositoryTest : ShellTestCase() {
@Test
fun removeTask_validDisplay_differentDisplay_doesNotRemovesTask() {
+ repo.addDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY)
repo.addTask(DEFAULT_DISPLAY, taskId = 1, isVisible = true)
repo.removeTask(SECOND_DISPLAY, taskId = 1)
@@ -758,6 +780,7 @@ class DesktopRepositoryTest : ShellTestCase() {
@EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_PERSISTENCE)
fun removeTask_validDisplayButDifferentDisplay_persistenceEnabled_doesNotRemoveTask() {
runTest(StandardTestDispatcher()) {
+ repo.addDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY)
repo.addTask(DEFAULT_DISPLAY, taskId = 1, isVisible = true)
repo.removeTask(SECOND_DISPLAY, taskId = 1)
@@ -784,10 +807,10 @@ class DesktopRepositoryTest : ShellTestCase() {
@Test
fun removeTask_removesTaskBoundsBeforeMaximize() {
val taskId = 1
- repo.addTask(THIRD_DISPLAY, taskId, isVisible = true)
+ repo.addTask(DEFAULT_DISPLAY, taskId, isVisible = true)
repo.saveBoundsBeforeMaximize(taskId, Rect(0, 0, 200, 200))
- repo.removeTask(THIRD_DISPLAY, taskId)
+ repo.removeTask(DEFAULT_DISPLAY, taskId)
assertThat(repo.removeBoundsBeforeMaximize(taskId)).isNull()
}
@@ -795,16 +818,17 @@ class DesktopRepositoryTest : ShellTestCase() {
@Test
fun removeTask_removesTaskBoundsBeforeImmersive() {
val taskId = 1
- repo.addTask(THIRD_DISPLAY, taskId, isVisible = true)
+ repo.addTask(DEFAULT_DISPLAY, taskId, isVisible = true)
repo.saveBoundsBeforeFullImmersive(taskId, Rect(0, 0, 200, 200))
- repo.removeTask(THIRD_DISPLAY, taskId)
+ repo.removeTask(DEFAULT_DISPLAY, taskId)
assertThat(repo.removeBoundsBeforeFullImmersive(taskId)).isNull()
}
@Test
fun removeTask_removesActiveTask() {
+ repo.addDesk(THIRD_DISPLAY, THIRD_DISPLAY)
val taskId = 1
val listener = TestListener()
repo.addActiveTaskListener(listener)
@@ -829,6 +853,7 @@ class DesktopRepositoryTest : ShellTestCase() {
@Test
fun removeTask_updatesTaskVisibility() {
+ repo.addDesk(displayId = THIRD_DISPLAY, deskId = THIRD_DISPLAY)
val taskId = 1
repo.addTask(DEFAULT_DISPLAY, taskId, isVisible = true)
@@ -930,8 +955,8 @@ class DesktopRepositoryTest : ShellTestCase() {
@Test
fun updateTask_minimizedTaskBecomesVisible_unminimizesTask() {
- repo.minimizeTask(displayId = 10, taskId = 2)
- repo.updateTask(displayId = 10, taskId = 2, isVisible = true)
+ repo.minimizeTask(displayId = DEFAULT_DISPLAY, taskId = 2)
+ repo.updateTask(displayId = DEFAULT_DISPLAY, taskId = 2, isVisible = true)
val isMinimizedTask = repo.isMinimizedTask(taskId = 2)
@@ -1003,34 +1028,34 @@ class DesktopRepositoryTest : ShellTestCase() {
fun setTaskInFullImmersiveState_savedAsInImmersiveState() {
assertThat(repo.isTaskInFullImmersiveState(taskId = 1)).isFalse()
- repo.setTaskInFullImmersiveState(DEFAULT_DESKTOP_ID, taskId = 1, immersive = true)
+ repo.setTaskInFullImmersiveState(DEFAULT_DISPLAY, taskId = 1, immersive = true)
assertThat(repo.isTaskInFullImmersiveState(taskId = 1)).isTrue()
}
@Test
fun removeTaskInFullImmersiveState_removedAsInImmersiveState() {
- repo.setTaskInFullImmersiveState(DEFAULT_DESKTOP_ID, taskId = 1, immersive = true)
+ repo.setTaskInFullImmersiveState(DEFAULT_DISPLAY, taskId = 1, immersive = true)
assertThat(repo.isTaskInFullImmersiveState(taskId = 1)).isTrue()
- repo.setTaskInFullImmersiveState(DEFAULT_DESKTOP_ID, taskId = 1, immersive = false)
+ repo.setTaskInFullImmersiveState(DEFAULT_DISPLAY, taskId = 1, immersive = false)
assertThat(repo.isTaskInFullImmersiveState(taskId = 1)).isFalse()
}
@Test
fun removeTaskInFullImmersiveState_otherWasImmersive_otherRemainsImmersive() {
- repo.setTaskInFullImmersiveState(DEFAULT_DESKTOP_ID, taskId = 1, immersive = true)
+ repo.setTaskInFullImmersiveState(DEFAULT_DISPLAY, taskId = 1, immersive = true)
- repo.setTaskInFullImmersiveState(DEFAULT_DESKTOP_ID, taskId = 2, immersive = false)
+ repo.setTaskInFullImmersiveState(DEFAULT_DISPLAY, taskId = 2, immersive = false)
assertThat(repo.isTaskInFullImmersiveState(taskId = 1)).isTrue()
}
@Test
fun setTaskInFullImmersiveState_sameDisplay_overridesExistingFullImmersiveTask() {
- repo.setTaskInFullImmersiveState(DEFAULT_DESKTOP_ID, taskId = 1, immersive = true)
- repo.setTaskInFullImmersiveState(DEFAULT_DESKTOP_ID, taskId = 2, immersive = true)
+ repo.setTaskInFullImmersiveState(DEFAULT_DISPLAY, taskId = 1, immersive = true)
+ repo.setTaskInFullImmersiveState(DEFAULT_DISPLAY, taskId = 2, immersive = true)
assertThat(repo.isTaskInFullImmersiveState(taskId = 1)).isFalse()
assertThat(repo.isTaskInFullImmersiveState(taskId = 2)).isTrue()
@@ -1038,8 +1063,10 @@ class DesktopRepositoryTest : ShellTestCase() {
@Test
fun setTaskInFullImmersiveState_differentDisplay_bothAreImmersive() {
- repo.setTaskInFullImmersiveState(DEFAULT_DESKTOP_ID, taskId = 1, immersive = true)
- repo.setTaskInFullImmersiveState(DEFAULT_DESKTOP_ID + 1, taskId = 2, immersive = true)
+ repo.addDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY)
+ repo.setActiveDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY)
+ repo.setTaskInFullImmersiveState(DEFAULT_DISPLAY, taskId = 1, immersive = true)
+ repo.setTaskInFullImmersiveState(SECOND_DISPLAY, taskId = 2, immersive = true)
assertThat(repo.isTaskInFullImmersiveState(taskId = 1)).isTrue()
assertThat(repo.isTaskInFullImmersiveState(taskId = 2)).isTrue()
@@ -1061,11 +1088,13 @@ class DesktopRepositoryTest : ShellTestCase() {
@Test
fun getTaskInFullImmersiveState_byDisplay() {
+ repo.addDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY)
+ repo.setActiveDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY)
repo.setTaskInFullImmersiveState(DEFAULT_DESKTOP_ID, taskId = 1, immersive = true)
- repo.setTaskInFullImmersiveState(DEFAULT_DESKTOP_ID + 1, taskId = 2, immersive = true)
+ repo.setTaskInFullImmersiveState(SECOND_DISPLAY, taskId = 2, immersive = true)
assertThat(repo.getTaskInFullImmersiveState(DEFAULT_DESKTOP_ID)).isEqualTo(1)
- assertThat(repo.getTaskInFullImmersiveState(DEFAULT_DESKTOP_ID + 1)).isEqualTo(2)
+ assertThat(repo.getTaskInFullImmersiveState(SECOND_DISPLAY)).isEqualTo(2)
}
@Test
@@ -1089,11 +1118,13 @@ class DesktopRepositoryTest : ShellTestCase() {
@Test
fun setTaskInPip_multipleDisplays_bothAreInPip() {
+ repo.addDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY)
+ repo.setActiveDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY)
repo.setTaskInPip(DEFAULT_DESKTOP_ID, taskId = 1, enterPip = true)
- repo.setTaskInPip(DEFAULT_DESKTOP_ID + 1, taskId = 2, enterPip = true)
+ repo.setTaskInPip(SECOND_DISPLAY, taskId = 2, enterPip = true)
assertThat(repo.isTaskMinimizedPipInDisplay(DEFAULT_DESKTOP_ID, taskId = 1)).isTrue()
- assertThat(repo.isTaskMinimizedPipInDisplay(DEFAULT_DESKTOP_ID + 1, taskId = 2)).isTrue()
+ assertThat(repo.isTaskMinimizedPipInDisplay(SECOND_DISPLAY, taskId = 2)).isTrue()
}
@Test
@@ -1129,6 +1160,14 @@ class DesktopRepositoryTest : ShellTestCase() {
assertThat(repo.shouldDesktopBeActiveForPip(DEFAULT_DESKTOP_ID)).isFalse()
}
+ @Test
+ @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun addTask_deskDoesNotExists_createsDesk() {
+ repo.addTask(displayId = 999, taskId = 6, isVisible = true)
+
+ assertThat(repo.getActiveTaskIdsInDesk(999)).contains(6)
+ }
+
class TestListener : DesktopRepository.ActiveTasksListener {
var activeChangesOnDefaultDisplay = 0
var activeChangesOnSecondaryDisplay = 0
@@ -1169,5 +1208,10 @@ class DesktopRepositoryTest : ShellTestCase() {
const val THIRD_DISPLAY = 345
private const val DEFAULT_USER_ID = 1000
private const val DEFAULT_DESKTOP_ID = 0
+
+ @JvmStatic
+ @Parameters(name = "{0}")
+ fun getParams(): List<FlagsParameterization> =
+ FlagsParameterization.allCombinationsOf(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
}
}
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 fffaab36c9ad..40c0e3610da2 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
@@ -48,8 +48,8 @@ import android.os.IBinder
import android.os.UserManager
import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
+import android.platform.test.flag.junit.FlagsParameterization
import android.platform.test.flag.junit.SetFlagsRule
-import android.testing.AndroidTestingRunner
import android.view.Display.DEFAULT_DISPLAY
import android.view.DragEvent
import android.view.Gravity
@@ -100,6 +100,7 @@ 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.UserProfileContexts
import com.android.wm.shell.desktopmode.DesktopImmersiveController.ExitResult
import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.InputMethod
import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.MinimizeReason
@@ -117,6 +118,7 @@ import com.android.wm.shell.desktopmode.ExitDesktopTaskTransitionHandler.FULLSCR
import com.android.wm.shell.desktopmode.common.ToggleTaskSizeInteraction
import com.android.wm.shell.desktopmode.desktopwallpaperactivity.DesktopWallpaperActivityTokenProvider
import com.android.wm.shell.desktopmode.minimize.DesktopWindowLimitRemoteHandler
+import com.android.wm.shell.desktopmode.multidesks.DesksOrganizer
import com.android.wm.shell.desktopmode.persistence.Desktop
import com.android.wm.shell.desktopmode.persistence.DesktopPersistentRepository
import com.android.wm.shell.desktopmode.persistence.DesktopRepositoryInitializer
@@ -127,6 +129,7 @@ import com.android.wm.shell.recents.RecentsTransitionHandler
import com.android.wm.shell.recents.RecentsTransitionStateListener
import com.android.wm.shell.recents.RecentsTransitionStateListener.TRANSITION_STATE_ANIMATING
import com.android.wm.shell.recents.RecentsTransitionStateListener.TRANSITION_STATE_REQUESTED
+import com.android.wm.shell.shared.desktopmode.DesktopModeCompatPolicy
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
@@ -184,6 +187,8 @@ import org.mockito.kotlin.capture
import org.mockito.kotlin.eq
import org.mockito.kotlin.whenever
import org.mockito.quality.Strictness
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
/**
* Test class for {@link DesktopTasksController}
@@ -191,12 +196,12 @@ import org.mockito.quality.Strictness
* Usage: atest WMShellUnitTests:DesktopTasksControllerTest
*/
@SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RunWith(ParameterizedAndroidJunit4::class)
@ExperimentalCoroutinesApi
@EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
-class DesktopTasksControllerTest : ShellTestCase() {
+class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() {
- @JvmField @Rule val setFlagsRule = SetFlagsRule()
+ @JvmField @Rule val setFlagsRule = SetFlagsRule(flags)
@Mock lateinit var testExecutor: ShellExecutor
@Mock lateinit var shellCommandHandler: ShellCommandHandler
@@ -247,6 +252,8 @@ class DesktopTasksControllerTest : ShellTestCase() {
DesktopWallpaperActivityTokenProvider
@Mock
private lateinit var overviewToDesktopTransitionObserver: OverviewToDesktopTransitionObserver
+ @Mock private lateinit var desksOrganizer: DesksOrganizer
+ @Mock private lateinit var userProfileContexts: UserProfileContexts
private lateinit var controller: DesktopTasksController
private lateinit var shellInit: ShellInit
@@ -255,6 +262,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
private lateinit var desktopTasksLimiter: DesktopTasksLimiter
private lateinit var recentsTransitionStateListener: RecentsTransitionStateListener
private lateinit var testScope: CoroutineScope
+ private lateinit var desktopModeCompatPolicy: DesktopModeCompatPolicy
private val shellExecutor = TestShellExecutor()
@@ -305,6 +313,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
mContext,
mockHandler,
)
+ desktopModeCompatPolicy = DesktopModeCompatPolicy(context)
whenever(shellTaskOrganizer.getRunningTasks(anyInt())).thenAnswer { runningTasks }
whenever(transitions.startTransition(anyInt(), any(), isNull())).thenAnswer { Binder() }
@@ -341,6 +350,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
)
.thenReturn(ExitResult.NoExit)
whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(wallpaperToken)
+ whenever(userProfileContexts[anyInt()]).thenReturn(context)
controller = createController()
controller.setSplitScreenController(splitScreenController)
@@ -358,6 +368,8 @@ class DesktopTasksControllerTest : ShellTestCase() {
assumeTrue(ENABLE_SHELL_TRANSITIONS)
taskRepository = userRepositories.current
+ taskRepository.addDesk(displayId = DEFAULT_DISPLAY, deskId = DEFAULT_DISPLAY)
+ taskRepository.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = DEFAULT_DISPLAY)
}
private fun createController() =
@@ -395,6 +407,9 @@ class DesktopTasksControllerTest : ShellTestCase() {
desktopWallpaperActivityTokenProvider,
Optional.of(bubbleController),
overviewToDesktopTransitionObserver,
+ desksOrganizer,
+ userProfileContexts,
+ desktopModeCompatPolicy,
)
@After
@@ -613,7 +628,12 @@ class DesktopTasksControllerTest : ShellTestCase() {
Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
Flags.FLAG_ENABLE_PER_DISPLAY_DESKTOP_WALLPAPER_ACTIVITY,
)
+ @DisableFlags(
+ /** TODO: b/362720497 - re-enable when activation is implemented. */
+ Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND
+ )
fun showDesktopApps_onSecondaryDisplay_desktopWallpaperEnabled_perDisplayWallpaperEnabled_shouldShowWallpaper() {
+ taskRepository.addDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY)
val homeTask = setUpHomeTask(SECOND_DISPLAY)
val task1 = setUpFreeformTask(SECOND_DISPLAY)
val task2 = setUpFreeformTask(SECOND_DISPLAY)
@@ -634,8 +654,13 @@ class DesktopTasksControllerTest : ShellTestCase() {
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- @DisableFlags(Flags.FLAG_ENABLE_PER_DISPLAY_DESKTOP_WALLPAPER_ACTIVITY)
+ @DisableFlags(
+ Flags.FLAG_ENABLE_PER_DISPLAY_DESKTOP_WALLPAPER_ACTIVITY,
+ /** TODO: b/362720497 - re-enable when activation is implemented. */
+ Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
+ )
fun showDesktopApps_onSecondaryDisplay_desktopWallpaperEnabled_shouldNotShowWallpaper() {
+ taskRepository.addDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY)
val homeTask = setUpHomeTask(SECOND_DISPLAY)
val task1 = setUpFreeformTask(SECOND_DISPLAY)
val task2 = setUpFreeformTask(SECOND_DISPLAY)
@@ -674,8 +699,13 @@ class DesktopTasksControllerTest : ShellTestCase() {
}
@Test
- @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ @DisableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
+ /** TODO: b/362720497 - re-enable when activation is implemented. */
+ Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
+ )
fun showDesktopApps_onSecondaryDisplay_desktopWallpaperDisabled_shouldNotMoveLauncher() {
+ taskRepository.addDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY)
val homeTask = setUpHomeTask(SECOND_DISPLAY)
val task1 = setUpFreeformTask(SECOND_DISPLAY)
val task2 = setUpFreeformTask(SECOND_DISPLAY)
@@ -777,6 +807,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
@Test
@DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
fun showDesktopApps_twoDisplays_bringsToFrontOnlyOneDisplay_desktopWallpaperDisabled() {
+ taskRepository.addDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY)
val homeTaskDefaultDisplay = setUpHomeTask(DEFAULT_DISPLAY)
val taskDefaultDisplay = setUpFreeformTask(DEFAULT_DISPLAY)
setUpHomeTask(SECOND_DISPLAY)
@@ -797,6 +828,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
fun showDesktopApps_twoDisplays_bringsToFrontOnlyOneDisplay_desktopWallpaperEnabled() {
+ taskRepository.addDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY)
val homeTaskDefaultDisplay = setUpHomeTask(DEFAULT_DISPLAY)
val taskDefaultDisplay = setUpFreeformTask(DEFAULT_DISPLAY)
setUpHomeTask(SECOND_DISPLAY)
@@ -883,6 +915,8 @@ class DesktopTasksControllerTest : ShellTestCase() {
@Test
fun visibleTaskCount_twoTasksVisibleOnDifferentDisplays_returnsOne() {
+ taskRepository.addDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY)
+ taskRepository.setActiveDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY)
setUpHomeTask()
setUpFreeformTask(DEFAULT_DISPLAY).also(::markTaskVisible)
setUpFreeformTask(SECOND_DISPLAY).also(::markTaskVisible)
@@ -1476,6 +1510,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
val fullscreenTaskDefault = setUpFullscreenTask(displayId = DEFAULT_DISPLAY)
markTaskHidden(freeformTaskDefault)
+ taskRepository.addDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY)
val homeTaskSecond = setUpHomeTask(displayId = SECOND_DISPLAY)
val freeformTaskSecond = setUpFreeformTask(displayId = SECOND_DISPLAY)
markTaskHidden(freeformTaskSecond)
@@ -1673,6 +1708,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
@Test
fun moveToFullscreen_secondDisplayTaskHasFreeform_secondDisplayNotAffected() {
val taskDefaultDisplay = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
+ taskRepository.addDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY)
val taskSecondDisplay = setUpFreeformTask(displayId = SECOND_DISPLAY)
controller.moveToFullscreen(taskDefaultDisplay.taskId, transitionSource = UNKNOWN)
@@ -1853,6 +1889,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
@Test
fun moveToNextDisplay_moveFromFirstToSecondDisplay() {
// Set up two display ids
+ taskRepository.addDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY)
whenever(rootTaskDisplayAreaOrganizer.displayIds)
.thenReturn(intArrayOf(DEFAULT_DISPLAY, SECOND_DISPLAY))
// Create a mock for the target display area: second display
@@ -1882,6 +1919,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
whenever(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY))
.thenReturn(defaultDisplayArea)
+ taskRepository.addDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY)
val task = setUpFreeformTask(displayId = SECOND_DISPLAY)
controller.moveToNextDisplay(task.taskId)
@@ -1901,6 +1939,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
)
fun moveToNextDisplay_wallpaperOnSystemUser_reorderWallpaperToBack() {
// Set up two display ids
+ taskRepository.addDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY)
whenever(rootTaskDisplayAreaOrganizer.displayIds)
.thenReturn(intArrayOf(DEFAULT_DISPLAY, SECOND_DISPLAY))
// Create a mock for the target display area: second display
@@ -1925,6 +1964,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
@DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WALLPAPER_ACTIVITY_FOR_SYSTEM_USER)
fun moveToNextDisplay_wallpaperNotOnSystemUser_removeWallpaper() {
// Set up two display ids
+ taskRepository.addDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY)
whenever(rootTaskDisplayAreaOrganizer.displayIds)
.thenReturn(intArrayOf(DEFAULT_DISPLAY, SECOND_DISPLAY))
// Create a mock for the target display area: second display
@@ -2049,6 +2089,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
@Test
@EnableFlags(FLAG_ENABLE_MOVE_TO_NEXT_DISPLAY_SHORTCUT)
fun moveToNextDisplay_defaultBoundsWhenDestinationTooSmall() {
+ taskRepository.addDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY)
// Set up two display ids
whenever(rootTaskDisplayAreaOrganizer.displayIds)
.thenReturn(intArrayOf(DEFAULT_DISPLAY, SECOND_DISPLAY))
@@ -2090,6 +2131,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
FLAG_ENABLE_MOVE_TO_NEXT_DISPLAY_SHORTCUT,
)
fun moveToNextDisplay_destinationGainGlobalFocus() {
+ taskRepository.addDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY)
// Set up two display ids
whenever(rootTaskDisplayAreaOrganizer.displayIds)
.thenReturn(intArrayOf(DEFAULT_DISPLAY, SECOND_DISPLAY))
@@ -2977,6 +3019,27 @@ class DesktopTasksControllerTest : ShellTestCase() {
}
@Test
+ fun handleRequest_systemUIActivityWithDisplay_returnSwitchToFullscreenWCT_enforcedDesktop() {
+ whenever(DesktopModeStatus.enterDesktopByDefaultOnFreeformDisplay(context)).thenReturn(true)
+ val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!!
+ tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FREEFORM
+ // Set task as systemUI package
+ val systemUIPackageName =
+ context.resources.getString(com.android.internal.R.string.config_systemUi)
+ val baseComponent = ComponentName(systemUIPackageName, /* cls= */ "")
+ val task =
+ createFreeformTask().apply {
+ baseActivity = baseComponent
+ isTopActivityNoDisplay = false
+ }
+
+ assertThat(controller.isDesktopModeShowing(DEFAULT_DISPLAY)).isFalse()
+ val result = controller.handleRequest(Binder(), createTransition(task))
+ assertThat(result?.changes?.get(task.token.asBinder())?.windowingMode)
+ .isEqualTo(WINDOWING_MODE_FULLSCREEN)
+ }
+
+ @Test
@DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
fun handleRequest_backTransition_singleTaskNoToken_noWallpaper_doesNotHandle() {
val task = setUpFreeformTask()
@@ -3158,6 +3221,8 @@ class DesktopTasksControllerTest : ShellTestCase() {
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
fun handleRequest_closeTransition_singleTaskNoToken_secondaryDisplay_launchesHome() {
+ taskRepository.addDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY)
+ taskRepository.setActiveDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY)
val task = setUpFreeformTask(displayId = SECOND_DISPLAY)
whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(null)
@@ -4933,7 +4998,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
@Test
@EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP)
fun shouldPlayDesktopAnimation_notShowingDesktop_doesNotPlay() {
- val triggerTask = setUpFullscreenTask(displayId = 5)
+ val triggerTask = setUpFullscreenTask(displayId = DEFAULT_DISPLAY)
taskRepository.setTaskInFullImmersiveState(
displayId = triggerTask.displayId,
taskId = triggerTask.taskId,
@@ -4951,7 +5016,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
@Test
@EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP)
fun shouldPlayDesktopAnimation_notOpening_doesNotPlay() {
- val triggerTask = setUpFreeformTask(displayId = 5)
+ val triggerTask = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
taskRepository.setTaskInFullImmersiveState(
displayId = triggerTask.displayId,
taskId = triggerTask.taskId,
@@ -4969,7 +5034,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
@Test
@EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP)
fun shouldPlayDesktopAnimation_notImmersive_doesNotPlay() {
- val triggerTask = setUpFreeformTask(displayId = 5)
+ val triggerTask = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
taskRepository.setTaskInFullImmersiveState(
displayId = triggerTask.displayId,
taskId = triggerTask.taskId,
@@ -4988,8 +5053,8 @@ class DesktopTasksControllerTest : ShellTestCase() {
@EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP)
fun shouldPlayDesktopAnimation_fullscreenEntersDesktop_plays() {
// At least one freeform task to be in a desktop.
- val existingTask = setUpFreeformTask(displayId = 5)
- val triggerTask = setUpFullscreenTask(displayId = 5)
+ val existingTask = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
+ val triggerTask = setUpFullscreenTask(displayId = DEFAULT_DISPLAY)
assertThat(controller.isDesktopModeShowing(triggerTask.displayId)).isTrue()
taskRepository.setTaskInFullImmersiveState(
displayId = existingTask.displayId,
@@ -5008,7 +5073,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
@Test
@EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP)
fun shouldPlayDesktopAnimation_fullscreenStaysFullscreen_doesNotPlay() {
- val triggerTask = setUpFullscreenTask(displayId = 5)
+ val triggerTask = setUpFullscreenTask(displayId = DEFAULT_DISPLAY)
assertThat(controller.isDesktopModeShowing(triggerTask.displayId)).isFalse()
assertThat(
@@ -5023,8 +5088,8 @@ class DesktopTasksControllerTest : ShellTestCase() {
@EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP)
fun shouldPlayDesktopAnimation_freeformStaysInDesktop_plays() {
// At least one freeform task to be in a desktop.
- val existingTask = setUpFreeformTask(displayId = 5)
- val triggerTask = setUpFreeformTask(displayId = 5, active = false)
+ val existingTask = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
+ val triggerTask = setUpFreeformTask(displayId = DEFAULT_DISPLAY, active = false)
assertThat(controller.isDesktopModeShowing(triggerTask.displayId)).isTrue()
taskRepository.setTaskInFullImmersiveState(
displayId = existingTask.displayId,
@@ -5043,7 +5108,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
@Test
@EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP)
fun shouldPlayDesktopAnimation_freeformExitsDesktop_doesNotPlay() {
- val triggerTask = setUpFreeformTask(displayId = 5, active = false)
+ val triggerTask = setUpFreeformTask(displayId = DEFAULT_DISPLAY, active = false)
assertThat(controller.isDesktopModeShowing(triggerTask.displayId)).isFalse()
assertThat(
@@ -5054,6 +5119,19 @@ class DesktopTasksControllerTest : ShellTestCase() {
.isFalse()
}
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun testCreateDesk() {
+ val currentDeskCount = taskRepository.getNumberOfDesks(DEFAULT_DISPLAY)
+ whenever(desksOrganizer.createDesk(eq(DEFAULT_DISPLAY), any())).thenAnswer { invocation ->
+ (invocation.arguments[1] as DesksOrganizer.OnCreateCallback).onCreated(deskId = 5)
+ }
+
+ controller.createDesk(DEFAULT_DISPLAY)
+
+ assertThat(taskRepository.getNumberOfDesks(DEFAULT_DISPLAY)).isEqualTo(currentDeskCount + 1)
+ }
+
private class RunOnStartTransitionCallback : ((IBinder) -> Unit) {
var invocations = 0
private set
@@ -5098,7 +5176,8 @@ class DesktopTasksControllerTest : ShellTestCase() {
whenever(mockDragEvent.dragSurface).thenReturn(dragSurface)
whenever(mockDragEvent.x).thenReturn(inputCoordinate.x)
whenever(mockDragEvent.y).thenReturn(inputCoordinate.y)
- whenever(multiInstanceHelper.supportsMultiInstanceSplit(anyOrNull())).thenReturn(true)
+ whenever(multiInstanceHelper.supportsMultiInstanceSplit(anyOrNull(), anyInt()))
+ .thenReturn(true)
whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator)
doReturn(indicatorType)
.whenever(spyController)
@@ -5112,6 +5191,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
spyController.onUnhandledDrag(
mockPendingIntent,
+ context.userId,
mockDragEvent,
mockCallback as Consumer<Boolean>,
)
@@ -5387,6 +5467,11 @@ class DesktopTasksControllerTest : ShellTestCase() {
val STABLE_BOUNDS = Rect(0, 0, 1000, 1000)
const val MAX_TASK_LIMIT = 6
private const val TASKBAR_FRAME_HEIGHT = 200
+
+ @JvmStatic
+ @Parameters(name = "{0}")
+ fun getParams(): List<FlagsParameterization> =
+ FlagsParameterization.allCombinationsOf(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt
index e85901bbd9d4..554b09f130bd 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt
@@ -180,6 +180,8 @@ class DesktopTasksLimiterTest : ShellTestCase() {
@Test
fun addPendingMinimizeTransition_taskIsNotMinimized() {
+ desktopTaskRepo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
+ desktopTaskRepo.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
val task = setUpFreeformTask()
markTaskHidden(task)
@@ -190,6 +192,8 @@ class DesktopTasksLimiterTest : ShellTestCase() {
@Test
fun onTransitionReady_noPendingTransition_taskIsNotMinimized() {
+ desktopTaskRepo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
+ desktopTaskRepo.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
val task = setUpFreeformTask()
markTaskHidden(task)
@@ -203,6 +207,8 @@ class DesktopTasksLimiterTest : ShellTestCase() {
@Test
fun onTransitionReady_differentPendingTransition_taskIsNotMinimized() {
+ desktopTaskRepo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
+ desktopTaskRepo.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
val pendingTransition = Binder()
val taskTransition = Binder()
val task = setUpFreeformTask()
@@ -219,6 +225,8 @@ class DesktopTasksLimiterTest : ShellTestCase() {
@Test
fun onTransitionReady_pendingTransition_noTaskChange_taskVisible_taskIsNotMinimized() {
+ desktopTaskRepo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
+ desktopTaskRepo.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
val transition = Binder()
val task = setUpFreeformTask()
markTaskVisible(task)
@@ -232,6 +240,8 @@ class DesktopTasksLimiterTest : ShellTestCase() {
@Test
fun onTransitionReady_pendingTransition_noTaskChange_taskInvisible_taskIsMinimized() {
val transition = Binder()
+ desktopTaskRepo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
+ desktopTaskRepo.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
val task = setUpFreeformTask()
markTaskHidden(task)
addPendingMinimizeChange(transition, taskId = task.taskId)
@@ -243,6 +253,8 @@ class DesktopTasksLimiterTest : ShellTestCase() {
@Test
fun onTransitionReady_pendingTransition_changeTaskToBack_taskIsMinimized() {
+ desktopTaskRepo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
+ desktopTaskRepo.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
val transition = Binder()
val task = setUpFreeformTask()
addPendingMinimizeChange(transition, taskId = task.taskId)
@@ -257,6 +269,8 @@ class DesktopTasksLimiterTest : ShellTestCase() {
@Test
fun onTransitionReady_pendingTransition_changeTaskToBack_boundsSaved() {
+ desktopTaskRepo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
+ desktopTaskRepo.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
val bounds = Rect(0, 0, 200, 200)
val transition = Binder()
val task = setUpFreeformTask()
@@ -280,6 +294,8 @@ class DesktopTasksLimiterTest : ShellTestCase() {
@Test
fun onTransitionReady_transitionMergedFromPending_taskIsMinimized() {
+ desktopTaskRepo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
+ desktopTaskRepo.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
val mergedTransition = Binder()
val newTransition = Binder()
val task = setUpFreeformTask()
@@ -302,6 +318,8 @@ class DesktopTasksLimiterTest : ShellTestCase() {
@Test
@DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION)
fun removeLeftoverMinimizedTasks_activeNonMinimizedTasksStillAround_doesNothing() {
+ desktopTaskRepo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
+ desktopTaskRepo.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
desktopTaskRepo.addTask(displayId = DEFAULT_DISPLAY, taskId = 1, isVisible = true)
desktopTaskRepo.addTask(displayId = DEFAULT_DISPLAY, taskId = 2, isVisible = true)
desktopTaskRepo.minimizeTask(displayId = DEFAULT_DISPLAY, taskId = 2)
@@ -318,6 +336,8 @@ class DesktopTasksLimiterTest : ShellTestCase() {
@Test
@DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION)
fun removeLeftoverMinimizedTasks_noMinimizedTasks_doesNothing() {
+ desktopTaskRepo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
+ desktopTaskRepo.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
val wct = WindowContainerTransaction()
desktopTasksLimiter.leftoverMinimizedTasksRemover.removeLeftoverMinimizedTasks(
DEFAULT_DISPLAY,
@@ -330,6 +350,8 @@ class DesktopTasksLimiterTest : ShellTestCase() {
@Test
@DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION)
fun removeLeftoverMinimizedTasks_onlyMinimizedTasksLeft_removesAllMinimizedTasks() {
+ desktopTaskRepo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
+ desktopTaskRepo.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
val task2 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
desktopTaskRepo.minimizeTask(displayId = DEFAULT_DISPLAY, taskId = task1.taskId)
@@ -351,6 +373,8 @@ class DesktopTasksLimiterTest : ShellTestCase() {
@Test
@EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION)
fun removeLeftoverMinimizedTasks_onlyMinimizedTasksLeft_backNavEnabled_doesNothing() {
+ desktopTaskRepo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
+ desktopTaskRepo.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
val task2 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
desktopTaskRepo.minimizeTask(displayId = DEFAULT_DISPLAY, taskId = task1.taskId)
@@ -364,6 +388,8 @@ class DesktopTasksLimiterTest : ShellTestCase() {
@Test
fun addAndGetMinimizeTaskChanges_tasksWithinLimit_noTaskMinimized() {
+ desktopTaskRepo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
+ desktopTaskRepo.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
(1..<MAX_TASK_LIMIT).forEach { _ -> setUpFreeformTask() }
val wct = WindowContainerTransaction()
@@ -380,6 +406,8 @@ class DesktopTasksLimiterTest : ShellTestCase() {
@Test
fun addAndGetMinimizeTaskChanges_tasksAboveLimit_backTaskMinimized() {
+ desktopTaskRepo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
+ desktopTaskRepo.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
// The following list will be ordered bottom -> top, as the last task is moved to top last.
val tasks = (1..MAX_TASK_LIMIT).map { setUpFreeformTask() }
@@ -399,6 +427,8 @@ class DesktopTasksLimiterTest : ShellTestCase() {
@Test
fun addAndGetMinimizeTaskChanges_nonMinimizedTasksWithinLimit_noTaskMinimized() {
+ desktopTaskRepo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
+ desktopTaskRepo.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
val tasks = (1..MAX_TASK_LIMIT).map { setUpFreeformTask() }
desktopTaskRepo.minimizeTask(displayId = DEFAULT_DISPLAY, taskId = tasks[0].taskId)
@@ -416,6 +446,8 @@ class DesktopTasksLimiterTest : ShellTestCase() {
@Test
fun getTaskToMinimize_tasksWithinLimit_returnsNull() {
+ desktopTaskRepo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
+ desktopTaskRepo.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
val tasks = (1..MAX_TASK_LIMIT).map { setUpFreeformTask() }
val minimizedTask =
@@ -426,6 +458,8 @@ class DesktopTasksLimiterTest : ShellTestCase() {
@Test
fun getTaskToMinimize_tasksAboveLimit_returnsBackTask() {
+ desktopTaskRepo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
+ desktopTaskRepo.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
val tasks = (1..MAX_TASK_LIMIT + 1).map { setUpFreeformTask() }
val minimizedTask =
@@ -437,6 +471,8 @@ class DesktopTasksLimiterTest : ShellTestCase() {
@Test
fun getTaskToMinimize_tasksAboveLimit_otherLimit_returnsBackTask() {
+ desktopTaskRepo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
+ desktopTaskRepo.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
desktopTasksLimiter =
DesktopTasksLimiter(
transitions,
@@ -458,6 +494,8 @@ class DesktopTasksLimiterTest : ShellTestCase() {
@Test
fun getTaskToMinimize_withNewTask_tasksAboveLimit_returnsBackTask() {
+ desktopTaskRepo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
+ desktopTaskRepo.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
val tasks = (1..MAX_TASK_LIMIT).map { setUpFreeformTask() }
val minimizedTask =
@@ -472,6 +510,8 @@ class DesktopTasksLimiterTest : ShellTestCase() {
@Test
fun getTaskToMinimize_tasksAtLimit_newIntentReturnsBackTask() {
+ desktopTaskRepo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
+ desktopTaskRepo.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
val tasks = (1..MAX_TASK_LIMIT).map { setUpFreeformTask() }
val minimizedTask =
desktopTasksLimiter.getTaskIdToMinimize(
@@ -486,6 +526,8 @@ class DesktopTasksLimiterTest : ShellTestCase() {
@Test
fun minimizeTransitionReadyAndFinished_logsJankInstrumentationBeginAndEnd() {
+ desktopTaskRepo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
+ desktopTaskRepo.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
(1..<MAX_TASK_LIMIT).forEach { _ -> setUpFreeformTask() }
val transition = Binder()
val task = setUpFreeformTask()
@@ -510,6 +552,8 @@ class DesktopTasksLimiterTest : ShellTestCase() {
@Test
fun minimizeTransitionReadyAndAborted_logsJankInstrumentationBeginAndCancel() {
+ desktopTaskRepo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
+ desktopTaskRepo.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
(1..<MAX_TASK_LIMIT).forEach { _ -> setUpFreeformTask() }
val transition = Binder()
val task = setUpFreeformTask()
@@ -534,6 +578,8 @@ class DesktopTasksLimiterTest : ShellTestCase() {
@Test
fun minimizeTransitionReadyAndMerged_logsJankInstrumentationBeginAndEnd() {
+ desktopTaskRepo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
+ desktopTaskRepo.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
(1..<MAX_TASK_LIMIT).forEach { _ -> setUpFreeformTask() }
val mergedTransition = Binder()
val newTransition = Binder()
@@ -566,6 +612,8 @@ class DesktopTasksLimiterTest : ShellTestCase() {
@Test
fun getMinimizingTask_pendingTaskTransition_returnsTask() {
+ desktopTaskRepo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
+ desktopTaskRepo.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
val transition = Binder()
val task = setUpFreeformTask()
addPendingMinimizeChange(
@@ -582,6 +630,8 @@ class DesktopTasksLimiterTest : ShellTestCase() {
@Test
fun getMinimizingTask_activeTaskTransition_returnsTask() {
+ desktopTaskRepo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
+ desktopTaskRepo.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
val transition = Binder()
val task = setUpFreeformTask()
addPendingMinimizeChange(
@@ -613,6 +663,8 @@ class DesktopTasksLimiterTest : ShellTestCase() {
@Test
fun getUnminimizingTask_pendingTaskTransition_returnsTask() {
+ desktopTaskRepo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
+ desktopTaskRepo.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
val transition = Binder()
val task = setUpFreeformTask()
addPendingUnminimizeChange(
@@ -632,6 +684,8 @@ class DesktopTasksLimiterTest : ShellTestCase() {
@Test
fun getUnminimizingTask_activeTaskTransition_returnsTask() {
+ desktopTaskRepo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
+ desktopTaskRepo.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
val transition = Binder()
val task = setUpFreeformTask()
addPendingMinimizeChange(
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTestHelpers.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTestHelpers.kt
index aee8821a63f6..8b6cafb10df4 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTestHelpers.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTestHelpers.kt
@@ -35,6 +35,7 @@ object DesktopTestHelpers {
): RunningTaskInfo =
TestRunningTaskInfoBuilder()
.setDisplayId(displayId)
+ .setParentTaskId(displayId)
.setToken(MockToken().token())
.setActivityType(ACTIVITY_TYPE_STANDARD)
.setWindowingMode(WINDOWING_MODE_FREEFORM)
@@ -73,10 +74,14 @@ object DesktopTestHelpers {
.setLastActiveTime(100)
.build()
+ /**
+ * Create a new System Modal task builder, i.e. a builder for a task with only transparent
+ * activities.
+ */
+ fun createSystemModalTaskBuilder(displayId: Int = DEFAULT_DISPLAY): TestRunningTaskInfoBuilder =
+ createFullscreenTaskBuilder(displayId).setActivityStackTransparent(true).setNumActivities(1)
+
/** Create a new System Modal task, i.e. a task with only transparent activities. */
fun createSystemModalTask(displayId: Int = DEFAULT_DISPLAY): RunningTaskInfo =
- createFullscreenTaskBuilder(displayId)
- .setActivityStackTransparent(true)
- .setNumActivities(1)
- .build()
+ createSystemModalTaskBuilder(displayId).build()
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/compatui/SystemModalsTransitionHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/compatui/SystemModalsTransitionHandlerTest.kt
index 1569f9dc9b10..dfb1b0c8c642 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/compatui/SystemModalsTransitionHandlerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/compatui/SystemModalsTransitionHandlerTest.kt
@@ -16,6 +16,7 @@
package com.android.wm.shell.desktopmode.compatui
+import android.content.Intent
import android.os.Binder
import android.testing.AndroidTestingRunner
import android.view.SurfaceControl
@@ -29,7 +30,10 @@ import com.android.wm.shell.desktopmode.DesktopRepository
import com.android.wm.shell.desktopmode.DesktopTestHelpers.createFullscreenTask
import com.android.wm.shell.desktopmode.DesktopTestHelpers.createFullscreenTaskBuilder
import com.android.wm.shell.desktopmode.DesktopTestHelpers.createSystemModalTask
+import com.android.wm.shell.desktopmode.DesktopTestHelpers.createSystemModalTaskBuilder
import com.android.wm.shell.desktopmode.DesktopUserRepositories
+import com.android.wm.shell.desktopmode.DesktopWallpaperActivity
+import com.android.wm.shell.shared.desktopmode.DesktopModeCompatPolicy
import com.android.wm.shell.sysui.ShellInit
import com.android.wm.shell.transition.TransitionInfoBuilder
import com.android.wm.shell.transition.Transitions
@@ -60,12 +64,14 @@ class SystemModalsTransitionHandlerTest : ShellTestCase() {
private val finishT = mock<SurfaceControl.Transaction>()
private lateinit var transitionHandler: SystemModalsTransitionHandler
+ private lateinit var desktopModeCompatPolicy: DesktopModeCompatPolicy
@Before
fun setUp() {
// Simulate having one Desktop task so that we see Desktop Mode as active
whenever(desktopUserRepositories.current).thenReturn(desktopRepository)
whenever(desktopRepository.getVisibleTaskCount(anyInt())).thenReturn(1)
+ desktopModeCompatPolicy = DesktopModeCompatPolicy(context)
transitionHandler = createTransitionHandler()
}
@@ -77,6 +83,7 @@ class SystemModalsTransitionHandlerTest : ShellTestCase() {
shellInit,
transitions,
desktopUserRepositories,
+ desktopModeCompatPolicy,
)
@Test
@@ -116,6 +123,19 @@ class SystemModalsTransitionHandlerTest : ShellTestCase() {
}
@Test
+ fun startAnimation_launchingWallpaperTask_doesNotAnimate() {
+ val wallpaperTask =
+ createSystemModalTaskBuilder().setBaseIntent(createWallpaperIntent()).build()
+ val info =
+ TransitionInfoBuilder(TRANSIT_OPEN).addChange(TRANSIT_OPEN, wallpaperTask).build()
+
+ assertThat(transitionHandler.startAnimation(Binder(), info, startT, finishT) {}).isFalse()
+ }
+
+ private fun createWallpaperIntent() =
+ Intent().apply { setComponent(DesktopWallpaperActivity.wallpaperActivityComponent) }
+
+ @Test
fun startAnimation_launchingFullscreenTask_doesNotAnimate() {
val info =
TransitionInfoBuilder(TRANSIT_OPEN)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/education/AppHandleEducationControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/education/AppHandleEducationControllerTest.kt
index 5475032f35a9..493a8c83c48e 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/education/AppHandleEducationControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/education/AppHandleEducationControllerTest.kt
@@ -29,7 +29,6 @@ import com.android.wm.shell.ShellTestCase
import com.android.wm.shell.desktopmode.CaptionState
import com.android.wm.shell.desktopmode.WindowDecorCaptionHandleRepository
import com.android.wm.shell.desktopmode.education.AppHandleEducationController.Companion.APP_HANDLE_EDUCATION_DELAY_MILLIS
-import com.android.wm.shell.desktopmode.education.AppHandleEducationController.Companion.APP_HANDLE_EDUCATION_TIMEOUT_MILLIS
import com.android.wm.shell.desktopmode.education.data.AppHandleEducationDatastoreRepository
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource
@@ -47,7 +46,6 @@ import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import kotlinx.coroutines.test.setMain
import org.junit.Before
-import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@@ -113,10 +111,10 @@ class AppHandleEducationControllerTest : ShellTestCase() {
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION)
- fun init_appHandleVisible_shouldCallShowEducationTooltip() =
+ fun init_appHandleVisible_shouldCallShowEducationTooltipAndMarkAsViewed() =
testScope.runTest {
// App handle is visible. Should show education tooltip.
- setShouldShowAppHandleEducation(true)
+ setShouldShowDesktopModeEducation(true)
// Simulate app handle visible.
testCaptionStateFlow.value = createAppHandleState()
@@ -124,6 +122,38 @@ class AppHandleEducationControllerTest : ShellTestCase() {
waitForBufferDelay()
verify(mockTooltipController, times(1)).showEducationTooltip(any(), any())
+ verify(mockDataStoreRepository, times(1))
+ .updateAppHandleHintViewedTimestampMillis(eq(true))
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION)
+ fun init_appHandleVisibleAndMenuExpanded_shouldCallShowEducationTooltipAndMarkAsViewed() =
+ testScope.runTest {
+ setShouldShowDesktopModeEducation(true)
+
+ // Simulate app handle visible and handle menu is expanded.
+ testCaptionStateFlow.value = createAppHandleState(isHandleMenuExpanded = true)
+ waitForBufferDelay()
+
+ verify(mockTooltipController, times(1)).showEducationTooltip(any(), any())
+ verify(mockDataStoreRepository, times(1))
+ .updateEnterDesktopModeHintViewedTimestampMillis(eq(true))
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION)
+ fun init_appHeaderVisible_shouldCallShowEducationTooltipAndMarkAsViewed() =
+ testScope.runTest {
+ setShouldShowDesktopModeEducation(true)
+
+ // Simulate app header visible.
+ testCaptionStateFlow.value = createAppHeaderState()
+ waitForBufferDelay()
+
+ verify(mockTooltipController, times(1)).showEducationTooltip(any(), any())
+ verify(mockDataStoreRepository, times(1))
+ .updateExitDesktopModeHintViewedTimestampMillis(eq(true))
}
@Test
@@ -133,7 +163,7 @@ class AppHandleEducationControllerTest : ShellTestCase() {
// App handle visible but education aconfig flag disabled, should not show education
// tooltip.
whenever(DesktopModeStatus.canEnterDesktopMode(any())).thenReturn(false)
- setShouldShowAppHandleEducation(true)
+ setShouldShowDesktopModeEducation(true)
// Simulate app handle visible.
testCaptionStateFlow.value = createAppHandleState()
@@ -145,12 +175,11 @@ class AppHandleEducationControllerTest : ShellTestCase() {
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION)
- fun init_shouldShowAppHandleEducationReturnsFalse_shouldNotCallShowEducationTooltip() =
+ fun init_shouldShowDesktopModeEducationReturnsFalse_shouldNotCallShowEducationTooltip() =
testScope.runTest {
- // App handle is visible but [shouldShowAppHandleEducation] api returns false, should
- // not
- // show education tooltip.
- setShouldShowAppHandleEducation(false)
+ // App handle is visible but [shouldShowDesktopModeEducation] api returns false, should
+ // not show education tooltip.
+ setShouldShowDesktopModeEducation(false)
// Simulate app handle visible.
testCaptionStateFlow.value = createAppHandleState()
@@ -165,7 +194,7 @@ class AppHandleEducationControllerTest : ShellTestCase() {
fun init_appHandleNotVisible_shouldNotCallShowEducationTooltip() =
testScope.runTest {
// App handle is not visible, should not show education tooltip.
- setShouldShowAppHandleEducation(true)
+ setShouldShowDesktopModeEducation(true)
// Simulate app handle is not visible.
testCaptionStateFlow.value = CaptionState.NoCaption
@@ -184,7 +213,7 @@ class AppHandleEducationControllerTest : ShellTestCase() {
// Mark app handle hint viewed.
testDataStoreFlow.value =
createWindowingEducationProto(appHandleHintViewedTimestampMillis = 123L)
- setShouldShowAppHandleEducation(true)
+ setShouldShowDesktopModeEducation(true)
// Simulate app handle visible.
testCaptionStateFlow.value = createAppHandleState(isHandleMenuExpanded = false)
@@ -196,231 +225,95 @@ class AppHandleEducationControllerTest : ShellTestCase() {
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION)
- fun overridePrerequisite_appHandleHintViewedAlready_shouldCallShowEducationTooltip() =
+ fun init_enterDesktopModeHintViewedAlready_shouldNotCallShowEducationTooltip() =
testScope.runTest {
- // App handle is visible but app handle hint has been viewed before.
- // But as we are overriding prerequisite conditions, we should show app
- // handle tooltip.
+ // App handle is visible but app handle hint has been viewed before,
+ // should not show education tooltip.
// Mark app handle hint viewed.
testDataStoreFlow.value =
- createWindowingEducationProto(appHandleHintViewedTimestampMillis = 123L)
- val systemPropertiesKey =
- "persist.desktop_windowing_app_handle_education_override_conditions"
- whenever(SystemProperties.getBoolean(eq(systemPropertiesKey), anyBoolean()))
- .thenReturn(true)
- setShouldShowAppHandleEducation(true)
+ createWindowingEducationProto(enterDesktopModeHintViewedTimestampMillis = 123L)
+ setShouldShowDesktopModeEducation(true)
// Simulate app handle visible.
- testCaptionStateFlow.value = createAppHandleState(isHandleMenuExpanded = false)
- // Wait for first tooltip to showup.
- waitForBufferDelay()
-
- verify(mockTooltipController, times(1)).showEducationTooltip(any(), any())
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION)
- fun init_appHandleExpanded_shouldMarkAppHandleHintUsed() =
- testScope.runTest {
- setShouldShowAppHandleEducation(false)
-
- // Simulate app handle visible and expanded.
testCaptionStateFlow.value = createAppHandleState(isHandleMenuExpanded = true)
- // Wait for some time before verifying
+ // Wait for first tooltip to showup.
waitForBufferDelay()
- verify(mockDataStoreRepository, times(1))
- .updateAppHandleHintUsedTimestampMillis(eq(true))
+ verify(mockTooltipController, never()).showEducationTooltip(any(), any())
}
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION)
- fun init_showFirstTooltip_shouldMarkAppHandleHintViewed() =
+ fun init_exitDesktopModeHintViewedAlready_shouldNotCallShowEducationTooltip() =
testScope.runTest {
- // App handle is visible. Should show education tooltip.
- setShouldShowAppHandleEducation(true)
+ // App handle is visible but app handle hint has been viewed before,
+ // should not show education tooltip.
+ // Mark app handle hint viewed.
+ testDataStoreFlow.value =
+ createWindowingEducationProto(exitDesktopModeHintViewedTimestampMillis = 123L)
+ setShouldShowDesktopModeEducation(true)
// Simulate app handle visible.
- testCaptionStateFlow.value = createAppHandleState()
+ testCaptionStateFlow.value = createAppHeaderState()
// Wait for first tooltip to showup.
waitForBufferDelay()
- verify(mockDataStoreRepository, times(1))
- .updateAppHandleHintViewedTimestampMillis(eq(true))
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION)
- @Ignore("b/371527084: revisit testcase after refactoring original logic")
- fun showWindowingImageButtonTooltip_appHandleExpanded_shouldCallShowEducationTooltipTwice() =
- testScope.runTest {
- // After first tooltip is dismissed, app handle is expanded. Should show second
- // education
- // tooltip.
- showAndDismissFirstTooltip()
-
- // Simulate app handle expanded.
- testCaptionStateFlow.value = createAppHandleState(isHandleMenuExpanded = true)
- // Wait for next tooltip to showup.
- waitForBufferDelay()
-
- // [showEducationTooltip] should be called twice, once for each tooltip.
- verify(mockTooltipController, times(2)).showEducationTooltip(any(), any())
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION)
- @Ignore("b/371527084: revisit testcase after refactoring original logic")
- fun showWindowingImageButtonTooltip_appHandleExpandedAfterTimeout_shouldCallShowEducationTooltipOnce() =
- testScope.runTest {
- // After first tooltip is dismissed, app handle is expanded after timeout. Should not
- // show
- // second education tooltip.
- showAndDismissFirstTooltip()
-
- // Wait for timeout to occur, after this timeout we should not listen for further
- // triggers
- // anymore.
- advanceTimeBy(APP_HANDLE_EDUCATION_TIMEOUT_BUFFER_MILLIS)
- runCurrent()
- // Simulate app handle expanded.
- testCaptionStateFlow.value = createAppHandleState(isHandleMenuExpanded = true)
- // Wait for next tooltip to showup.
- waitForBufferDelay()
-
- // [showEducationTooltip] should be called once, just for the first tooltip.
- verify(mockTooltipController, times(1)).showEducationTooltip(any(), any())
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION)
- @Ignore("b/371527084: revisit testcase after refactoring original logic")
- fun showWindowingImageButtonTooltip_appHandleExpandedTwice_shouldCallShowEducationTooltipTwice() =
- testScope.runTest {
- // After first tooltip is dismissed, app handle is expanded twice. Should show second
- // education tooltip only once.
- showAndDismissFirstTooltip()
-
- // Simulate app handle expanded.
- testCaptionStateFlow.value = createAppHandleState(isHandleMenuExpanded = true)
- // Wait for next tooltip to showup.
- waitForBufferDelay()
- // Simulate app handle being expanded twice.
- testCaptionStateFlow.value = createAppHandleState(isHandleMenuExpanded = true)
- waitForBufferDelay()
-
- // [showEducationTooltip] should not be called thrice, even if app handle was expanded
- // twice. Should be called twice, once for each tooltip.
- verify(mockTooltipController, times(2)).showEducationTooltip(any(), any())
+ verify(mockTooltipController, never()).showEducationTooltip(any(), any())
}
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION)
- @Ignore("b/371527084: revisit testcase after refactoring original logic")
- fun showWindowingImageButtonTooltip_appHandleNotExpanded_shouldCallShowEducationTooltipOnce() =
+ fun overridePrerequisite_appHandleHintViewedAlready_shouldCallShowEducationTooltip() =
testScope.runTest {
- // After first tooltip is dismissed, app handle is not expanded. Should not show second
- // education tooltip.
- showAndDismissFirstTooltip()
+ // App handle is visible but app handle hint has been viewed before.
+ // But as we are overriding prerequisite conditions, we should show app
+ // handle tooltip.
+ // Mark app handle hint viewed.
+ testDataStoreFlow.value =
+ createWindowingEducationProto(appHandleHintViewedTimestampMillis = 123L)
+ val systemPropertiesKey = "persist.windowing_force_show_desktop_mode_education"
+ whenever(SystemProperties.getBoolean(eq(systemPropertiesKey), anyBoolean()))
+ .thenReturn(true)
+ setShouldShowDesktopModeEducation(true)
- // Simulate app handle visible but not expanded.
+ // Simulate app handle visible.
testCaptionStateFlow.value = createAppHandleState(isHandleMenuExpanded = false)
- // Wait for next tooltip to showup.
+ // Wait for first tooltip to showup.
waitForBufferDelay()
- // [showEducationTooltip] should be called once, just for the first tooltip.
verify(mockTooltipController, times(1)).showEducationTooltip(any(), any())
}
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION)
- @Ignore("b/371527084: revisit testcase after refactoring original logic")
- fun showExitWindowingButtonTooltip_appHeaderVisible_shouldCallShowEducationTooltipThrice() =
- testScope.runTest {
- // After first two tooltips are dismissed, app header is visible. Should show third
- // education tooltip.
- showAndDismissFirstTooltip()
- showAndDismissSecondTooltip()
-
- // Simulate app header visible.
- testCaptionStateFlow.value = createAppHeaderState()
- // Wait for next tooltip to showup.
- waitForBufferDelay()
-
- verify(mockTooltipController, times(3)).showEducationTooltip(any(), any())
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION)
- @Ignore("b/371527084: revisit testcase after refactoring original logic")
- fun showExitWindowingButtonTooltip_appHeaderVisibleAfterTimeout_shouldCallShowEducationTooltipTwice() =
- testScope.runTest {
- // After first two tooltips are dismissed, app header is visible after timeout. Should
- // not
- // show third education tooltip.
- showAndDismissFirstTooltip()
- showAndDismissSecondTooltip()
-
- // Wait for timeout to occur, after this timeout we should not listen for further
- // triggers
- // anymore.
- advanceTimeBy(APP_HANDLE_EDUCATION_TIMEOUT_BUFFER_MILLIS)
- runCurrent()
- // Simulate app header visible.
- testCaptionStateFlow.value = createAppHeaderState()
- // Wait for next tooltip to showup.
- waitForBufferDelay()
-
- verify(mockTooltipController, times(2)).showEducationTooltip(any(), any())
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION)
- @Ignore("b/371527084: revisit testcase after refactoring original logic")
- fun showExitWindowingButtonTooltip_appHeaderVisibleTwice_shouldCallShowEducationTooltipThrice() =
+ fun clickAppHandleHint_openHandleMenuCallbackInvoked() =
testScope.runTest {
- // After first two tooltips are dismissed, app header is visible twice. Should show
- // third
- // education tooltip only once.
- showAndDismissFirstTooltip()
- showAndDismissSecondTooltip()
-
- // Simulate app header visible.
- testCaptionStateFlow.value = createAppHeaderState()
- // Wait for next tooltip to showup.
- waitForBufferDelay()
- testCaptionStateFlow.value = createAppHeaderState()
- // Wait for next tooltip to showup.
+ // App handle is visible. Should show education tooltip.
+ setShouldShowDesktopModeEducation(true)
+ val mockOpenHandleMenuCallback: (Int) -> Unit = mock()
+ val mockToDesktopModeCallback: (Int, DesktopModeTransitionSource) -> Unit = mock()
+ educationController.setAppHandleEducationTooltipCallbacks(
+ mockOpenHandleMenuCallback,
+ mockToDesktopModeCallback,
+ )
+ // Simulate app handle visible.
+ testCaptionStateFlow.value = createAppHandleState()
+ // Wait for first tooltip to showup.
waitForBufferDelay()
- verify(mockTooltipController, times(3)).showEducationTooltip(any(), any())
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION)
- @Ignore("b/371527084: revisit testcase after refactoring original logic")
- fun showExitWindowingButtonTooltip_appHeaderExpanded_shouldCallShowEducationTooltipTwice() =
- testScope.runTest {
- // After first two tooltips are dismissed, app header is visible but expanded. Should
- // not
- // show third education tooltip.
- showAndDismissFirstTooltip()
- showAndDismissSecondTooltip()
-
- // Simulate app header visible.
- testCaptionStateFlow.value = createAppHeaderState(isHeaderMenuExpanded = true)
- // Wait for next tooltip to showup.
- waitForBufferDelay()
+ verify(mockTooltipController, atLeastOnce())
+ .showEducationTooltip(educationConfigCaptor.capture(), any())
+ educationConfigCaptor.lastValue.onEducationClickAction.invoke()
- verify(mockTooltipController, times(2)).showEducationTooltip(any(), any())
+ verify(mockOpenHandleMenuCallback, times(1)).invoke(any())
}
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION)
- fun setAppHandleEducationTooltipCallbacks_onAppHandleTooltipClicked_callbackInvoked() =
+ fun clickEnterDesktopModeHint_toDesktopModeCallbackInvoked() =
testScope.runTest {
// App handle is visible. Should show education tooltip.
- setShouldShowAppHandleEducation(true)
+ setShouldShowDesktopModeEducation(true)
val mockOpenHandleMenuCallback: (Int) -> Unit = mock()
val mockToDesktopModeCallback: (Int, DesktopModeTransitionSource) -> Unit = mock()
educationController.setAppHandleEducationTooltipCallbacks(
@@ -428,7 +321,7 @@ class AppHandleEducationControllerTest : ShellTestCase() {
mockToDesktopModeCallback,
)
// Simulate app handle visible.
- testCaptionStateFlow.value = createAppHandleState()
+ testCaptionStateFlow.value = createAppHandleState(isHandleMenuExpanded = true)
// Wait for first tooltip to showup.
waitForBufferDelay()
@@ -436,68 +329,41 @@ class AppHandleEducationControllerTest : ShellTestCase() {
.showEducationTooltip(educationConfigCaptor.capture(), any())
educationConfigCaptor.lastValue.onEducationClickAction.invoke()
- verify(mockOpenHandleMenuCallback, times(1)).invoke(any())
+ verify(mockToDesktopModeCallback, times(1))
+ .invoke(any(), eq(DesktopModeTransitionSource.APP_HANDLE_MENU_BUTTON))
}
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION)
- @Ignore("b/371527084: revisit testcase after refactoring original logic")
- fun setAppHandleEducationTooltipCallbacks_onWindowingImageButtonTooltipClicked_callbackInvoked() =
+ fun clickExitDesktopModeHint_openHandleMenuCallbackInvoked() =
testScope.runTest {
- // After first tooltip is dismissed, app handle is expanded. Should show second
- // education
- // tooltip.
- showAndDismissFirstTooltip()
+ // App handle is visible. Should show education tooltip.
+ setShouldShowDesktopModeEducation(true)
val mockOpenHandleMenuCallback: (Int) -> Unit = mock()
val mockToDesktopModeCallback: (Int, DesktopModeTransitionSource) -> Unit = mock()
educationController.setAppHandleEducationTooltipCallbacks(
mockOpenHandleMenuCallback,
mockToDesktopModeCallback,
)
- // Simulate app handle expanded.
- testCaptionStateFlow.value = createAppHandleState(isHandleMenuExpanded = true)
- // Wait for next tooltip to showup.
+ // Simulate app handle visible.
+ testCaptionStateFlow.value = createAppHeaderState()
+ // Wait for first tooltip to showup.
waitForBufferDelay()
verify(mockTooltipController, atLeastOnce())
.showEducationTooltip(educationConfigCaptor.capture(), any())
educationConfigCaptor.lastValue.onEducationClickAction.invoke()
- verify(mockToDesktopModeCallback, times(1)).invoke(any(), any())
+ verify(mockOpenHandleMenuCallback, times(1)).invoke(any())
}
- private suspend fun TestScope.showAndDismissFirstTooltip() {
- setShouldShowAppHandleEducation(true)
- // Simulate app handle visible.
- testCaptionStateFlow.value = createAppHandleState(isHandleMenuExpanded = false)
- // Wait for first tooltip to showup.
- waitForBufferDelay()
- // [shouldShowAppHandleEducation] should return false as education has been viewed
- // before.
- setShouldShowAppHandleEducation(false)
- // Dismiss previous tooltip, after this we should listen for next tooltip's trigger.
- captureAndInvokeOnDismissAction()
+ private suspend fun setShouldShowDesktopModeEducation(shouldShowDesktopModeEducation: Boolean) {
+ whenever(mockEducationFilter.shouldShowDesktopModeEducation(any<CaptionState.AppHandle>()))
+ .thenReturn(shouldShowDesktopModeEducation)
+ whenever(mockEducationFilter.shouldShowDesktopModeEducation(any<CaptionState.AppHeader>()))
+ .thenReturn(shouldShowDesktopModeEducation)
}
- private fun TestScope.showAndDismissSecondTooltip() {
- // Simulate app handle expanded.
- testCaptionStateFlow.value = createAppHandleState(isHandleMenuExpanded = true)
- // Wait for next tooltip to showup.
- waitForBufferDelay()
- // Dismiss previous tooltip, after this we should listen for next tooltip's trigger.
- captureAndInvokeOnDismissAction()
- }
-
- private fun captureAndInvokeOnDismissAction() {
- verify(mockTooltipController, atLeastOnce())
- .showEducationTooltip(educationConfigCaptor.capture(), any())
- educationConfigCaptor.lastValue.onDismissAction.invoke()
- }
-
- private suspend fun setShouldShowAppHandleEducation(shouldShowAppHandleEducation: Boolean) =
- whenever(mockEducationFilter.shouldShowAppHandleEducation(any()))
- .thenReturn(shouldShowAppHandleEducation)
-
/**
* Class under test waits for some time before showing education, simulate advance time before
* verifying or moving forward
@@ -510,7 +376,5 @@ class AppHandleEducationControllerTest : ShellTestCase() {
private companion object {
val APP_HANDLE_EDUCATION_DELAY_BUFFER_MILLIS: Long =
APP_HANDLE_EDUCATION_DELAY_MILLIS + 1000L
- val APP_HANDLE_EDUCATION_TIMEOUT_BUFFER_MILLIS: Long =
- APP_HANDLE_EDUCATION_TIMEOUT_MILLIS + 1000L
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/education/AppHandleEducationDatastoreRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/education/AppHandleEducationDatastoreRepositoryTest.kt
index 4db883d13551..31dfc78902b2 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/education/AppHandleEducationDatastoreRepositoryTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/education/AppHandleEducationDatastoreRepositoryTest.kt
@@ -123,6 +123,24 @@ class AppHandleEducationDatastoreRepositoryTest {
}
@Test
+ fun updateEnterDesktopModeHintViewedTimestampMillis_updatesDatastoreProto() =
+ runTest(StandardTestDispatcher()) {
+ datastoreRepository.updateEnterDesktopModeHintViewedTimestampMillis(true)
+
+ val result = testDatastore.data.first().hasEnterDesktopModeHintViewedTimestampMillis()
+ assertThat(result).isEqualTo(true)
+ }
+
+ @Test
+ fun updateExitDesktopModeHintViewedTimestampMillis_updatesDatastoreProto() =
+ runTest(StandardTestDispatcher()) {
+ datastoreRepository.updateExitDesktopModeHintViewedTimestampMillis(true)
+
+ val result = testDatastore.data.first().hasExitDesktopModeHintViewedTimestampMillis()
+ assertThat(result).isEqualTo(true)
+ }
+
+ @Test
fun updateAppHandleHintUsedTimestampMillis_updatesDatastoreProto() =
runTest(StandardTestDispatcher()) {
datastoreRepository.updateAppHandleHintUsedTimestampMillis(true)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/education/AppHandleEducationFilterTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/education/AppHandleEducationFilterTest.kt
index 2fc36efb1a41..218226240c0f 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/education/AppHandleEducationFilterTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/education/AppHandleEducationFilterTest.kt
@@ -89,9 +89,9 @@ class AppHandleEducationFilterTest : ShellTestCase() {
}
@Test
- fun shouldShowAppHandleEducation_isTriggerValid_returnsTrue() = runTest {
- // setup() makes sure that all of the conditions satisfy and #shouldShowAppHandleEducation
- // should return true
+ fun shouldShowDesktopModeEducation_isTriggerValid_returnsTrue() = runTest {
+ // setup() makes sure that all of the conditions satisfy and
+ // [shouldShowDesktopModeEducation] should return true
val windowingEducationProto =
createWindowingEducationProto(
appUsageStats = mapOf(GMAIL_PACKAGE_NAME to 4),
@@ -99,16 +99,15 @@ class AppHandleEducationFilterTest : ShellTestCase() {
)
`when`(datastoreRepository.windowingEducationProto()).thenReturn(windowingEducationProto)
- val result = educationFilter.shouldShowAppHandleEducation(createAppHandleState())
+ val result = educationFilter.shouldShowDesktopModeEducation(createAppHandleState())
assertThat(result).isTrue()
}
@Test
- fun shouldShowAppHandleEducation_focusAppNotInAllowlist_returnsFalse() = runTest {
+ fun shouldShowDesktopModeEducation_focusAppNotInAllowlist_returnsFalse() = runTest {
// Pass Youtube as current focus app, it is not in allowlist hence
- // #shouldShowAppHandleEducation
- // should return false
+ // [shouldShowDesktopModeEducation] should return false
testableResources.addOverride(
R.array.desktop_windowing_app_handle_education_allowlist_apps,
arrayOf(GMAIL_PACKAGE_NAME),
@@ -122,16 +121,15 @@ class AppHandleEducationFilterTest : ShellTestCase() {
createAppHandleState(createTaskInfo(runningTaskPackageName = YOUTUBE_PACKAGE_NAME))
`when`(datastoreRepository.windowingEducationProto()).thenReturn(windowingEducationProto)
- val result = educationFilter.shouldShowAppHandleEducation(captionState)
+ val result = educationFilter.shouldShowDesktopModeEducation(captionState)
assertThat(result).isFalse()
}
@Test
- fun shouldShowAppHandleEducation_timeSinceSetupIsNotSufficient_returnsFalse() = runTest {
- // Time required to have passed setup is > 100 years, hence #shouldShowAppHandleEducation
- // should
- // return false
+ fun shouldShowDesktopModeEducation_timeSinceSetupIsNotSufficient_returnsFalse() = runTest {
+ // Time required to have passed setup is > 100 years, hence [shouldShowDesktopModeEducation]
+ // should return false
testableResources.addOverride(
R.integer.desktop_windowing_education_required_time_since_setup_seconds,
MAX_VALUE,
@@ -143,50 +141,15 @@ class AppHandleEducationFilterTest : ShellTestCase() {
)
`when`(datastoreRepository.windowingEducationProto()).thenReturn(windowingEducationProto)
- val result = educationFilter.shouldShowAppHandleEducation(createAppHandleState())
-
- assertThat(result).isFalse()
- }
-
- @Test
- fun shouldShowAppHandleEducation_appHandleHintViewedBefore_returnsFalse() = runTest {
- // App handle hint has been viewed before, hence #shouldShowAppHandleEducation should return
- // false
- val windowingEducationProto =
- createWindowingEducationProto(
- appUsageStats = mapOf(GMAIL_PACKAGE_NAME to 4),
- appHandleHintViewedTimestampMillis = 123L,
- appUsageStatsLastUpdateTimestampMillis = Long.MAX_VALUE,
- )
- `when`(datastoreRepository.windowingEducationProto()).thenReturn(windowingEducationProto)
-
- val result = educationFilter.shouldShowAppHandleEducation(createAppHandleState())
-
- assertThat(result).isFalse()
- }
-
- @Test
- fun shouldShowAppHandleEducation_appHandleHintUsedBefore_returnsFalse() = runTest {
- // App handle hint has been used before, hence #shouldShowAppHandleEducation should return
- // false
- val windowingEducationProto =
- createWindowingEducationProto(
- appUsageStats = mapOf(GMAIL_PACKAGE_NAME to 4),
- appHandleHintUsedTimestampMillis = 123L,
- appUsageStatsLastUpdateTimestampMillis = Long.MAX_VALUE,
- )
- `when`(datastoreRepository.windowingEducationProto()).thenReturn(windowingEducationProto)
-
- val result = educationFilter.shouldShowAppHandleEducation(createAppHandleState())
+ val result = educationFilter.shouldShowDesktopModeEducation(createAppHandleState())
assertThat(result).isFalse()
}
@Test
- fun shouldShowAppHandleEducation_doesNotHaveMinAppUsage_returnsFalse() = runTest {
+ fun shouldShowDesktopModeEducation_doesNotHaveMinAppUsage_returnsFalse() = runTest {
// Simulate that gmail app has been launched twice before, minimum app launch count is 3,
- // hence
- // #shouldShowAppHandleEducation should return false
+ // hence [shouldShowDesktopModeEducation] should return false
testableResources.addOverride(R.integer.desktop_windowing_education_min_app_launch_count, 3)
val windowingEducationProto =
createWindowingEducationProto(
@@ -195,13 +158,13 @@ class AppHandleEducationFilterTest : ShellTestCase() {
)
`when`(datastoreRepository.windowingEducationProto()).thenReturn(windowingEducationProto)
- val result = educationFilter.shouldShowAppHandleEducation(createAppHandleState())
+ val result = educationFilter.shouldShowDesktopModeEducation(createAppHandleState())
assertThat(result).isFalse()
}
@Test
- fun shouldShowAppHandleEducation_appUsageStatsStale_queryAppUsageStats() = runTest {
+ fun shouldShowDesktopModeEducation_appUsageStatsStale_queryAppUsageStats() = runTest {
// UsageStats caching interval is set to 0ms, that means caching should happen very
// frequently
testableResources.addOverride(
@@ -209,8 +172,7 @@ class AppHandleEducationFilterTest : ShellTestCase() {
0,
)
// The DataStore currently holds a proto object where Gmail's app launch count is recorded
- // as 4.
- // This value exceeds the minimum required count of 3.
+ // as 4. This value exceeds the minimum required count of 3.
testableResources.addOverride(R.integer.desktop_windowing_education_min_app_launch_count, 3)
val windowingEducationProto =
createWindowingEducationProto(
@@ -223,40 +185,20 @@ class AppHandleEducationFilterTest : ShellTestCase() {
.thenReturn(mapOf(GMAIL_PACKAGE_NAME to UsageStats().apply { mAppLaunchCount = 2 }))
`when`(datastoreRepository.windowingEducationProto()).thenReturn(windowingEducationProto)
- val result = educationFilter.shouldShowAppHandleEducation(createAppHandleState())
+ val result = educationFilter.shouldShowDesktopModeEducation(createAppHandleState())
// Result should be false as queried usage stats should be considered to determine the
- // result
- // instead of cached stats
- assertThat(result).isFalse()
- }
-
- @Test
- fun shouldShowAppHandleEducation_appHandleMenuExpanded_returnsFalse() = runTest {
- val windowingEducationProto =
- createWindowingEducationProto(
- appUsageStats = mapOf(GMAIL_PACKAGE_NAME to 4),
- appUsageStatsLastUpdateTimestampMillis = Long.MAX_VALUE,
- )
- // Simulate app handle menu is expanded
- val captionState = createAppHandleState(isHandleMenuExpanded = true)
- `when`(datastoreRepository.windowingEducationProto()).thenReturn(windowingEducationProto)
-
- val result = educationFilter.shouldShowAppHandleEducation(captionState)
-
- // We should not show app handle education if app menu is expanded
+ // result instead of cached stats
assertThat(result).isFalse()
}
@Test
- fun shouldShowAppHandleEducation_overridePrerequisite_returnsTrue() = runTest {
+ fun shouldShowDesktopModeEducation_overridePrerequisite_returnsTrue() = runTest {
// Simulate that gmail app has been launched twice before, minimum app launch count is 3,
- // hence
- // #shouldShowAppHandleEducation should return false. But as we are overriding prerequisite
- // conditions, #shouldShowAppHandleEducation should return true.
+ // hence [shouldShowDesktopModeEducation] should return false. But as we are overriding
+ // prerequisite conditions, [shouldShowDesktopModeEducation] should return true.
testableResources.addOverride(R.integer.desktop_windowing_education_min_app_launch_count, 3)
- val systemPropertiesKey =
- "persist.desktop_windowing_app_handle_education_override_conditions"
+ val systemPropertiesKey = "persist.windowing_force_show_desktop_mode_education"
whenever(SystemProperties.getBoolean(eq(systemPropertiesKey), anyBoolean()))
.thenReturn(true)
val windowingEducationProto =
@@ -266,7 +208,7 @@ class AppHandleEducationFilterTest : ShellTestCase() {
)
`when`(datastoreRepository.windowingEducationProto()).thenReturn(windowingEducationProto)
- val result = educationFilter.shouldShowAppHandleEducation(createAppHandleState())
+ val result = educationFilter.shouldShowDesktopModeEducation(createAppHandleState())
assertThat(result).isTrue()
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/multidesks/RootTaskDesksOrganizerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/multidesks/RootTaskDesksOrganizerTest.kt
new file mode 100644
index 000000000000..a07203d86b75
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/multidesks/RootTaskDesksOrganizerTest.kt
@@ -0,0 +1,256 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.wm.shell.desktopmode.multidesks
+
+import android.testing.AndroidTestingRunner
+import android.view.Display
+import android.view.SurfaceControl
+import android.window.TransitionInfo
+import android.window.WindowContainerTransaction
+import android.window.WindowContainerTransaction.HierarchyOp
+import androidx.test.filters.SmallTest
+import com.android.wm.shell.ShellTaskOrganizer
+import com.android.wm.shell.ShellTestCase
+import com.android.wm.shell.TestShellExecutor
+import com.android.wm.shell.desktopmode.DesktopTestHelpers.createFreeformTask
+import com.android.wm.shell.sysui.ShellCommandHandler
+import com.android.wm.shell.sysui.ShellInit
+import com.google.common.truth.Truth.assertThat
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertThrows
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.mock
+
+/**
+ * Tests for [RootTaskDesksOrganizer].
+ *
+ * Usage: atest WMShellUnitTests:RootTaskDesksOrganizerTest
+ */
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class RootTaskDesksOrganizerTest : ShellTestCase() {
+
+ private val testExecutor = TestShellExecutor()
+ private val testShellInit = ShellInit(testExecutor)
+ private val mockShellCommandHandler = mock<ShellCommandHandler>()
+ private val mockShellTaskOrganizer = mock<ShellTaskOrganizer>()
+
+ private lateinit var organizer: RootTaskDesksOrganizer
+
+ @Before
+ fun setUp() {
+ organizer =
+ RootTaskDesksOrganizer(testShellInit, mockShellCommandHandler, mockShellTaskOrganizer)
+ }
+
+ @Test
+ fun testCreateDesk_callsBack() {
+ val callback = FakeOnCreateCallback()
+ organizer.createDesk(Display.DEFAULT_DISPLAY, callback)
+
+ val freeformRoot = createFreeformTask().apply { parentTaskId = -1 }
+ organizer.onTaskAppeared(freeformRoot, SurfaceControl())
+
+ assertThat(callback.created).isTrue()
+ assertEquals(freeformRoot.taskId, callback.deskId)
+ }
+
+ @Test
+ fun testOnTaskAppeared_withoutRequest_throws() {
+ val freeformRoot = createFreeformTask().apply { parentTaskId = -1 }
+
+ assertThrows(Exception::class.java) {
+ organizer.onTaskAppeared(freeformRoot, SurfaceControl())
+ }
+ }
+
+ @Test
+ fun testOnTaskAppeared_withRequestOnlyInAnotherDisplay_throws() {
+ organizer.createDesk(displayId = 2, FakeOnCreateCallback())
+ val freeformRoot = createFreeformTask(Display.DEFAULT_DISPLAY).apply { parentTaskId = -1 }
+
+ assertThrows(Exception::class.java) {
+ organizer.onTaskAppeared(freeformRoot, SurfaceControl())
+ }
+ }
+
+ @Test
+ fun testOnTaskAppeared_duplicateRoot_throws() {
+ organizer.createDesk(Display.DEFAULT_DISPLAY, FakeOnCreateCallback())
+ val freeformRoot = createFreeformTask().apply { parentTaskId = -1 }
+ organizer.onTaskAppeared(freeformRoot, SurfaceControl())
+
+ assertThrows(Exception::class.java) {
+ organizer.onTaskAppeared(freeformRoot, SurfaceControl())
+ }
+ }
+
+ @Test
+ fun testOnTaskVanished_removesRoot() {
+ val callback = FakeOnCreateCallback()
+ organizer.createDesk(Display.DEFAULT_DISPLAY, callback)
+ val freeformRoot = createFreeformTask().apply { parentTaskId = -1 }
+ organizer.onTaskAppeared(freeformRoot, SurfaceControl())
+
+ organizer.onTaskVanished(freeformRoot)
+
+ assertThat(organizer.roots.contains(freeformRoot.taskId)).isFalse()
+ }
+
+ @Test
+ fun testDesktopWindowAppearsInDesk() {
+ organizer.createDesk(Display.DEFAULT_DISPLAY, FakeOnCreateCallback())
+ val freeformRoot = createFreeformTask().apply { parentTaskId = -1 }
+ organizer.onTaskAppeared(freeformRoot, SurfaceControl())
+ val child = createFreeformTask().apply { parentTaskId = freeformRoot.taskId }
+
+ organizer.onTaskAppeared(child, SurfaceControl())
+
+ assertThat(organizer.roots[freeformRoot.taskId].children).contains(child.taskId)
+ }
+
+ @Test
+ fun testDesktopWindowDisappearsFromDesk() {
+ organizer.createDesk(Display.DEFAULT_DISPLAY, FakeOnCreateCallback())
+ val freeformRoot = createFreeformTask().apply { parentTaskId = -1 }
+ organizer.onTaskAppeared(freeformRoot, SurfaceControl())
+ val child = createFreeformTask().apply { parentTaskId = freeformRoot.taskId }
+
+ organizer.onTaskAppeared(child, SurfaceControl())
+ organizer.onTaskVanished(child)
+
+ assertThat(organizer.roots[freeformRoot.taskId].children).doesNotContain(child.taskId)
+ }
+
+ @Test
+ fun testRemoveDesk() {
+ organizer.createDesk(Display.DEFAULT_DISPLAY, FakeOnCreateCallback())
+ val freeformRoot = createFreeformTask().apply { parentTaskId = -1 }
+ organizer.onTaskAppeared(freeformRoot, SurfaceControl())
+
+ val wct = WindowContainerTransaction()
+ organizer.removeDesk(wct, freeformRoot.taskId)
+
+ assertThat(
+ wct.hierarchyOps.any { hop ->
+ hop.type == HierarchyOp.HIERARCHY_OP_TYPE_REMOVE_ROOT_TASK &&
+ hop.container == freeformRoot.token.asBinder()
+ }
+ )
+ .isTrue()
+ }
+
+ @Test
+ fun testRemoveDesk_didNotExist_throws() {
+ val freeformRoot = createFreeformTask().apply { parentTaskId = -1 }
+
+ val wct = WindowContainerTransaction()
+ assertThrows(Exception::class.java) { organizer.removeDesk(wct, freeformRoot.taskId) }
+ }
+
+ @Test
+ fun testActivateDesk() {
+ organizer.createDesk(Display.DEFAULT_DISPLAY, FakeOnCreateCallback())
+ val freeformRoot = createFreeformTask().apply { parentTaskId = -1 }
+ organizer.onTaskAppeared(freeformRoot, SurfaceControl())
+
+ val wct = WindowContainerTransaction()
+ organizer.activateDesk(wct, freeformRoot.taskId)
+
+ assertThat(
+ wct.hierarchyOps.any { hop ->
+ hop.type == HierarchyOp.HIERARCHY_OP_TYPE_REORDER &&
+ hop.toTop &&
+ hop.container == freeformRoot.token.asBinder()
+ }
+ )
+ .isTrue()
+ assertThat(
+ wct.hierarchyOps.any { hop ->
+ hop.type == HierarchyOp.HIERARCHY_OP_TYPE_SET_LAUNCH_ROOT &&
+ hop.container == freeformRoot.token.asBinder()
+ }
+ )
+ .isTrue()
+ }
+
+ @Test
+ fun testActivateDesk_didNotExist_throws() {
+ val freeformRoot = createFreeformTask().apply { parentTaskId = -1 }
+
+ val wct = WindowContainerTransaction()
+ assertThrows(Exception::class.java) { organizer.activateDesk(wct, freeformRoot.taskId) }
+ }
+
+ @Test
+ fun testMoveTaskToDesk() {
+ organizer.createDesk(Display.DEFAULT_DISPLAY, FakeOnCreateCallback())
+ val freeformRoot = createFreeformTask().apply { parentTaskId = -1 }
+ organizer.onTaskAppeared(freeformRoot, SurfaceControl())
+
+ val desktopTask = createFreeformTask().apply { parentTaskId = -1 }
+ val wct = WindowContainerTransaction()
+ organizer.moveTaskToDesk(wct, freeformRoot.taskId, desktopTask)
+
+ assertThat(
+ wct.hierarchyOps.any { hop ->
+ hop.isReparent &&
+ hop.toTop &&
+ hop.container == desktopTask.token.asBinder() &&
+ hop.newParent == freeformRoot.token.asBinder()
+ }
+ )
+ .isTrue()
+ }
+
+ @Test
+ fun testMoveTaskToDesk_didNotExist_throws() {
+ val freeformRoot = createFreeformTask().apply { parentTaskId = -1 }
+
+ val desktopTask = createFreeformTask().apply { parentTaskId = -1 }
+ val wct = WindowContainerTransaction()
+ assertThrows(Exception::class.java) {
+ organizer.moveTaskToDesk(wct, freeformRoot.taskId, desktopTask)
+ }
+ }
+
+ @Test
+ fun testGetDeskAtEnd() {
+ organizer.createDesk(Display.DEFAULT_DISPLAY, FakeOnCreateCallback())
+ val freeformRoot = createFreeformTask().apply { parentTaskId = -1 }
+ organizer.onTaskAppeared(freeformRoot, SurfaceControl())
+
+ val task = createFreeformTask().apply { parentTaskId = freeformRoot.taskId }
+ val endDesk =
+ organizer.getDeskAtEnd(
+ TransitionInfo.Change(task.token, SurfaceControl()).apply { taskInfo = task }
+ )
+
+ assertThat(endDesk).isEqualTo(freeformRoot.taskId)
+ }
+
+ private class FakeOnCreateCallback : DesksOrganizer.OnCreateCallback {
+ var deskId: Int? = null
+ val created: Boolean
+ get() = deskId != null
+
+ override fun onCreated(deskId: Int) {
+ this.deskId = deskId
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/persistence/DesktopRepositoryInitializerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/persistence/DesktopRepositoryInitializerTest.kt
index a3c441698905..9a8f264e98a4 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/persistence/DesktopRepositoryInitializerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/persistence/DesktopRepositoryInitializerTest.kt
@@ -17,6 +17,7 @@
package com.android.wm.shell.desktopmode.persistence
import android.os.UserManager
+import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
import android.platform.test.flag.junit.SetFlagsRule
import android.testing.AndroidTestingRunner
@@ -24,6 +25,7 @@ import android.view.Display.DEFAULT_DISPLAY
import androidx.test.filters.SmallTest
import com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_HSUM
import com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_PERSISTENCE
+import com.android.window.flags.Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND
import com.android.wm.shell.ShellTestCase
import com.android.wm.shell.common.ShellExecutor
import com.android.wm.shell.desktopmode.DesktopUserRepositories
@@ -85,7 +87,9 @@ class DesktopRepositoryInitializerTest : ShellTestCase() {
@Test
@EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_PERSISTENCE, FLAG_ENABLE_DESKTOP_WINDOWING_HSUM)
- fun initWithPersistence_multipleUsers_addedCorrectly() =
+ /** TODO: b/362720497 - add multi-desk version when implemented. */
+ @DisableFlags(FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun initWithPersistence_multipleUsers_addedCorrectly_multiDesksDisabled() =
runTest(StandardTestDispatcher()) {
whenever(persistentRepository.getUserDesktopRepositoryMap())
.thenReturn(
@@ -145,7 +149,9 @@ class DesktopRepositoryInitializerTest : ShellTestCase() {
@Test
@EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_PERSISTENCE)
- fun initWithPersistence_singleUser_addedCorrectly() =
+ /** TODO: b/362720497 - add multi-desk version when implemented. */
+ @DisableFlags(FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun initWithPersistence_singleUser_addedCorrectly_multiDesksDisabled() =
runTest(StandardTestDispatcher()) {
whenever(persistentRepository.getUserDesktopRepositoryMap())
.thenReturn(mapOf(USER_ID_1 to desktopRepositoryState1))
@@ -156,24 +162,24 @@ class DesktopRepositoryInitializerTest : ShellTestCase() {
repositoryInitializer.initialize(desktopUserRepositories)
- // Desktop Repository currently returns all tasks across desktops for a specific user
- // since the repository currently doesn't handle desktops. This test logic should be
- // updated
- // once the repository handles multiple desktops.
assertThat(
- desktopUserRepositories.getProfile(USER_ID_1).getActiveTasks(DEFAULT_DISPLAY)
+ desktopUserRepositories
+ .getProfile(USER_ID_1)
+ .getActiveTaskIdsInDesk(deskId = DEFAULT_DISPLAY)
)
.containsExactly(1, 3, 4, 5)
.inOrder()
assertThat(
desktopUserRepositories
.getProfile(USER_ID_1)
- .getExpandedTasksOrdered(DEFAULT_DISPLAY)
+ .getExpandedTasksIdsInDeskOrdered(deskId = DEFAULT_DISPLAY)
)
.containsExactly(5, 1)
.inOrder()
assertThat(
- desktopUserRepositories.getProfile(USER_ID_1).getMinimizedTasks(DEFAULT_DISPLAY)
+ desktopUserRepositories
+ .getProfile(USER_ID_1)
+ .getMinimizedTaskIdsInDesk(deskId = DEFAULT_DISPLAY)
)
.containsExactly(3, 4)
.inOrder()
@@ -195,6 +201,7 @@ class DesktopRepositoryInitializerTest : ShellTestCase() {
val desktop1: Desktop =
Desktop.newBuilder()
.setDesktopId(DESKTOP_ID_1)
+ .setDisplayId(DEFAULT_DISPLAY)
.addAllZOrderedTasks(freeformTasksInZOrder1)
.putTasksByTaskId(
1,
@@ -216,6 +223,7 @@ class DesktopRepositoryInitializerTest : ShellTestCase() {
val desktop2: Desktop =
Desktop.newBuilder()
.setDesktopId(DESKTOP_ID_2)
+ .setDisplayId(DEFAULT_DISPLAY)
.addAllZOrderedTasks(freeformTasksInZOrder2)
.putTasksByTaskId(
4,
@@ -237,6 +245,7 @@ class DesktopRepositoryInitializerTest : ShellTestCase() {
val desktop3: Desktop =
Desktop.newBuilder()
.setDesktopId(DESKTOP_ID_3)
+ .setDisplayId(DEFAULT_DISPLAY)
.addAllZOrderedTasks(freeformTasksInZOrder3)
.putTasksByTaskId(
7,
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskListenerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskListenerTests.java
index 6c16b3220a07..4174bbd89b76 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskListenerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskListenerTests.java
@@ -46,6 +46,7 @@ import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.TestRunningTaskInfoBuilder;
import com.android.wm.shell.common.LaunchAdjacentController;
+import com.android.wm.shell.desktopmode.DesktopModeLoggerTransitionObserver;
import com.android.wm.shell.desktopmode.DesktopRepository;
import com.android.wm.shell.desktopmode.DesktopTasksController;
import com.android.wm.shell.desktopmode.DesktopUserRepositories;
@@ -89,6 +90,8 @@ public final class FreeformTaskListenerTests extends ShellTestCase {
@Mock
private DesktopTasksController mDesktopTasksController;
@Mock
+ private DesktopModeLoggerTransitionObserver mDesktopModeLoggerTransitionObserver;
+ @Mock
private LaunchAdjacentController mLaunchAdjacentController;
@Mock
private TaskChangeListener mTaskChangeListener;
@@ -114,6 +117,7 @@ public final class FreeformTaskListenerTests extends ShellTestCase {
mTaskOrganizer,
Optional.of(mDesktopUserRepositories),
Optional.of(mDesktopTasksController),
+ mDesktopModeLoggerTransitionObserver,
mLaunchAdjacentController,
mWindowDecorViewModel,
Optional.of(mTaskChangeListener));
@@ -283,6 +287,19 @@ public final class FreeformTaskListenerTests extends ShellTestCase {
}
@Test
+ public void onTaskVanished_withDesktopModeLogger_forwards() {
+ ActivityManager.RunningTaskInfo task =
+ new TestRunningTaskInfoBuilder().setWindowingMode(WINDOWING_MODE_FREEFORM).build();
+ task.isVisible = true;
+ mFreeformTaskListener.onTaskAppeared(task, mMockSurfaceControl);
+
+ mFreeformTaskListener.onTaskVanished(task);
+
+ verify(mDesktopModeLoggerTransitionObserver).onTaskVanished(task);
+ }
+
+
+ @Test
public void onTaskInfoChanged_withDesktopController_forwards() {
ActivityManager.RunningTaskInfo task =
new TestRunningTaskInfoBuilder().setWindowingMode(WINDOWING_MODE_FREEFORM).build();
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 065fa219e8d0..542289db6cfc 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
@@ -211,6 +211,7 @@ public class RecentTasksControllerTest extends ShellTestCase {
@Test
public void testAddRemoveSplitNotifyChange() {
+ reset(mRecentTasksController);
RecentTaskInfo t1 = makeTaskInfo(1);
RecentTaskInfo t2 = makeTaskInfo(2);
setRawList(t1, t2);
@@ -225,6 +226,7 @@ public class RecentTasksControllerTest extends ShellTestCase {
@Test
public void testAddSameSplitBoundsInfoSkipNotifyChange() {
+ reset(mRecentTasksController);
RecentTaskInfo t1 = makeTaskInfo(1);
RecentTaskInfo t2 = makeTaskInfo(2);
setRawList(t1, t2);
@@ -535,6 +537,7 @@ public class RecentTasksControllerTest extends ShellTestCase {
@Test
public void testTaskWindowingModeChangedNotifiesChange() {
+ reset(mRecentTasksController);
RecentTaskInfo t1 = makeTaskInfo(1);
setRawList(t1);
@@ -551,7 +554,8 @@ public class RecentTasksControllerTest extends ShellTestCase {
WINDOWING_MODE_MULTI_WINDOW);
mShellTaskOrganizer.onTaskInfoChanged(rt2MultiWIndow);
- verify(mRecentTasksController).notifyRecentTasksChanged();
+ // One for onTaskAppeared and one for onTaskInfoChanged
+ verify(mRecentTasksController, times(2)).notifyRecentTasksChanged();
}
@Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/AppCompatUtilsTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeCompatPolicyTest.kt
index 7157a7f0b38f..8c78debdc19f 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/AppCompatUtilsTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeCompatPolicyTest.kt
@@ -14,29 +14,38 @@
* limitations under the License.
*/
-package com.android.wm.shell.compatui
+package com.android.wm.shell.shared.desktopmode
import android.content.ComponentName
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.internal.R
+import com.android.wm.shell.compatui.CompatUIShellTestCase
import com.android.wm.shell.desktopmode.DesktopTestHelpers.createFreeformTask
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
+import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
/**
- * Tests for [@link AppCompatUtils].
+ * Tests for [@link DesktopModeCompatPolicy].
*
- * Build/Install/Run: atest WMShellUnitTests:AppCompatUtilsTest
+ * Build/Install/Run: atest WMShellUnitTests:DesktopModeCompatPolicyTest
*/
@RunWith(AndroidTestingRunner::class)
@SmallTest
-class AppCompatUtilsTest : CompatUIShellTestCase() {
+class DesktopModeCompatPolicyTest : CompatUIShellTestCase() {
+ private lateinit var desktopModeCompatPolicy: DesktopModeCompatPolicy
+
+ @Before
+ fun setUp() {
+ desktopModeCompatPolicy = DesktopModeCompatPolicy(mContext)
+ }
+
@Test
fun testIsTopActivityExemptFromDesktopWindowing_onlyTransparentActivitiesInStack() {
- assertTrue(isTopActivityExemptFromDesktopWindowing(mContext,
+ assertTrue(desktopModeCompatPolicy.isTopActivityExemptFromDesktopWindowing(
createFreeformTask(/* displayId */ 0)
.apply {
isActivityStackTransparent = true
@@ -47,7 +56,7 @@ class AppCompatUtilsTest : CompatUIShellTestCase() {
@Test
fun testIsTopActivityExemptFromDesktopWindowing_noActivitiesInStack() {
- assertFalse(isTopActivityExemptFromDesktopWindowing(mContext,
+ assertFalse(desktopModeCompatPolicy.isTopActivityExemptFromDesktopWindowing(
createFreeformTask(/* displayId */ 0)
.apply {
isActivityStackTransparent = true
@@ -58,7 +67,7 @@ class AppCompatUtilsTest : CompatUIShellTestCase() {
@Test
fun testIsTopActivityExemptFromDesktopWindowing_nonTransparentActivitiesInStack() {
- assertFalse(isTopActivityExemptFromDesktopWindowing(mContext,
+ assertFalse(desktopModeCompatPolicy.isTopActivityExemptFromDesktopWindowing(
createFreeformTask(/* displayId */ 0)
.apply {
isActivityStackTransparent = false
@@ -69,7 +78,7 @@ class AppCompatUtilsTest : CompatUIShellTestCase() {
@Test
fun testIsTopActivityExemptFromDesktopWindowing_transparentActivityStack_notDisplayed() {
- assertFalse(isTopActivityExemptFromDesktopWindowing(mContext,
+ assertFalse(desktopModeCompatPolicy.isTopActivityExemptFromDesktopWindowing(
createFreeformTask(/* displayId */ 0)
.apply {
isActivityStackTransparent = true
@@ -82,7 +91,7 @@ class AppCompatUtilsTest : CompatUIShellTestCase() {
fun testIsTopActivityExemptFromDesktopWindowing_systemUiTask() {
val systemUIPackageName = context.resources.getString(R.string.config_systemUi)
val baseComponent = ComponentName(systemUIPackageName, /* class */ "")
- assertTrue(isTopActivityExemptFromDesktopWindowing(mContext,
+ assertTrue(desktopModeCompatPolicy.isTopActivityExemptFromDesktopWindowing(
createFreeformTask(/* displayId */ 0)
.apply {
baseActivity = baseComponent
@@ -94,7 +103,7 @@ class AppCompatUtilsTest : CompatUIShellTestCase() {
fun testIsTopActivityExemptFromDesktopWindowing_systemUiTask_notDisplayed() {
val systemUIPackageName = context.resources.getString(R.string.config_systemUi)
val baseComponent = ComponentName(systemUIPackageName, /* class */ "")
- assertFalse(isTopActivityExemptFromDesktopWindowing(mContext,
+ assertFalse(desktopModeCompatPolicy.isTopActivityExemptFromDesktopWindowing(
createFreeformTask(/* displayId */ 0)
.apply {
baseActivity = baseComponent
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
index bb9703fce2e3..7f6b06d46abf 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
@@ -213,7 +213,7 @@ public class SplitScreenControllerTests extends ShellTestCase {
@Test
public void startIntent_multiInstancesSupported_appendsMultipleTaskFag() {
- doReturn(true).when(mMultiInstanceHelper).supportsMultiInstanceSplit(any());
+ doReturn(true).when(mMultiInstanceHelper).supportsMultiInstanceSplit(any(), anyInt());
Intent startIntent = createStartIntent("startActivity");
PendingIntent pendingIntent =
PendingIntent.getActivity(mContext, 0, startIntent, FLAG_IMMUTABLE);
@@ -252,13 +252,13 @@ public class SplitScreenControllerTests extends ShellTestCase {
verify(mStageCoordinator).startTask(anyInt(), eq(SPLIT_POSITION_TOP_OR_LEFT),
isNull(), isNull(), eq(SPLIT_INDEX_0));
- verify(mMultiInstanceHelper, never()).supportsMultiInstanceSplit(any());
+ verify(mMultiInstanceHelper, never()).supportsMultiInstanceSplit(any(), anyInt());
verify(mStageCoordinator, never()).switchSplitPosition(any());
}
@Test
public void startIntent_multiInstancesSupported_startTaskInBackgroundAfterSplitActivated() {
- doReturn(true).when(mMultiInstanceHelper).supportsMultiInstanceSplit(any());
+ doReturn(true).when(mMultiInstanceHelper).supportsMultiInstanceSplit(any(), anyInt());
doNothing().when(mSplitScreenController).startTask(anyInt(), anyInt(), any(), any());
Intent startIntent = createStartIntent("startActivity");
PendingIntent pendingIntent =
@@ -276,14 +276,14 @@ public class SplitScreenControllerTests extends ShellTestCase {
mSplitScreenController.startIntent(pendingIntent, mContext.getUserId(), null,
SPLIT_POSITION_TOP_OR_LEFT, null /* options */, null /* hideTaskToken */,
SPLIT_INDEX_0);
- verify(mMultiInstanceHelper, never()).supportsMultiInstanceSplit(any());
+ verify(mMultiInstanceHelper, never()).supportsMultiInstanceSplit(any(), anyInt());
verify(mStageCoordinator).startTask(anyInt(), eq(SPLIT_POSITION_TOP_OR_LEFT),
isNull(), isNull(), eq(SPLIT_INDEX_0));
}
@Test
public void startIntent_multiInstancesNotSupported_switchesPositionAfterSplitActivated() {
- doReturn(false).when(mMultiInstanceHelper).supportsMultiInstanceSplit(any());
+ doReturn(false).when(mMultiInstanceHelper).supportsMultiInstanceSplit(any(), anyInt());
Intent startIntent = createStartIntent("startActivity");
PendingIntent pendingIntent =
PendingIntent.getActivity(mContext, 0, startIntent, FLAG_IMMUTABLE);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
index 4211e4682810..b9d6a454694d 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
@@ -67,6 +67,7 @@ import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import com.android.launcher3.icons.IconProvider;
+import com.android.wm.shell.Flags;
import com.android.wm.shell.MockToken;
import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
import com.android.wm.shell.ShellTaskOrganizer;
@@ -355,8 +356,13 @@ public class SplitTransitionTests extends ShellTestCase {
// Make sure it cleans-up if recents doesn't restore
WindowContainerTransaction commitWCT = new WindowContainerTransaction();
- mStageCoordinator.onRecentsInSplitAnimationFinish(commitWCT,
- mock(SurfaceControl.Transaction.class));
+ if (Flags.enableRecentsBookendTransition()) {
+ mStageCoordinator.onRecentsInSplitAnimationFinishing(false /* returnToApp */, commitWCT,
+ mock(SurfaceControl.Transaction.class));
+ } else {
+ mStageCoordinator.onRecentsInSplitAnimationFinish(commitWCT,
+ mock(SurfaceControl.Transaction.class));
+ }
assertFalse(mStageCoordinator.isSplitScreenVisible());
}
@@ -420,8 +426,13 @@ public class SplitTransitionTests extends ShellTestCase {
// simulate the restoreWCT being applied:
mMainStage.onTaskAppeared(mMainChild, mock(SurfaceControl.class));
mSideStage.onTaskAppeared(mSideChild, mock(SurfaceControl.class));
- mStageCoordinator.onRecentsInSplitAnimationFinish(restoreWCT,
- mock(SurfaceControl.Transaction.class));
+ if (Flags.enableRecentsBookendTransition()) {
+ mStageCoordinator.onRecentsInSplitAnimationFinishing(true /* returnToApp */, restoreWCT,
+ mock(SurfaceControl.Transaction.class));
+ } else {
+ mStageCoordinator.onRecentsInSplitAnimationFinish(restoreWCT,
+ mock(SurfaceControl.Transaction.class));
+ }
assertTrue(mStageCoordinator.isSplitScreenVisible());
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/util/WindowingEducationTestUtils.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/util/WindowingEducationTestUtils.kt
index b9d91e7895db..546848421302 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/util/WindowingEducationTestUtils.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/util/WindowingEducationTestUtils.kt
@@ -81,7 +81,9 @@ fun createWindowingEducationProto(
appHandleHintViewedTimestampMillis: Long? = null,
appHandleHintUsedTimestampMillis: Long? = null,
appUsageStats: Map<String, Int>? = null,
- appUsageStatsLastUpdateTimestampMillis: Long? = null
+ appUsageStatsLastUpdateTimestampMillis: Long? = null,
+ enterDesktopModeHintViewedTimestampMillis: Long? = null,
+ exitDesktopModeHintViewedTimestampMillis: Long? = null,
): WindowingEducationProto =
WindowingEducationProto.newBuilder()
.apply {
@@ -91,6 +93,12 @@ fun createWindowingEducationProto(
if (appHandleHintUsedTimestampMillis != null) {
setAppHandleHintUsedTimestampMillis(appHandleHintUsedTimestampMillis)
}
+ if (enterDesktopModeHintViewedTimestampMillis != null) {
+ setEnterDesktopModeHintViewedTimestampMillis(enterDesktopModeHintViewedTimestampMillis)
+ }
+ if (exitDesktopModeHintViewedTimestampMillis != null) {
+ setExitDesktopModeHintViewedTimestampMillis(exitDesktopModeHintViewedTimestampMillis)
+ }
setAppHandleEducation(
createAppHandleEducationProto(appUsageStats, appUsageStatsLastUpdateTimestampMillis))
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTestsBase.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTestsBase.kt
index 908bc9952e99..c5c827467c75 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTestsBase.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTestsBase.kt
@@ -69,6 +69,7 @@ import com.android.wm.shell.desktopmode.education.AppToWebEducationController
import com.android.wm.shell.freeform.FreeformTaskTransitionStarter
import com.android.wm.shell.recents.RecentsTransitionHandler
import com.android.wm.shell.recents.RecentsTransitionStateListener
+import com.android.wm.shell.shared.desktopmode.DesktopModeCompatPolicy
import com.android.wm.shell.splitscreen.SplitScreenController
import com.android.wm.shell.sysui.ShellCommandHandler
import com.android.wm.shell.sysui.ShellController
@@ -161,6 +162,7 @@ open class DesktopModeWindowDecorViewModelTestsBase : ShellTestCase() {
val display = mock<Display>()
protected lateinit var spyContext: TestableContext
private lateinit var desktopModeEventLogger: DesktopModeEventLogger
+ private lateinit var desktopModeCompatPolicy: DesktopModeCompatPolicy
private val transactionFactory = Supplier<SurfaceControl.Transaction> {
SurfaceControl.Transaction()
@@ -188,6 +190,7 @@ open class DesktopModeWindowDecorViewModelTestsBase : ShellTestCase() {
whenever(mockDisplayController.getDisplay(any())).thenReturn(display)
whenever(mockDesktopUserRepositories.getProfile(anyInt()))
.thenReturn(mockDesktopRepository)
+ desktopModeCompatPolicy = DesktopModeCompatPolicy(context)
desktopModeWindowDecorViewModel = DesktopModeWindowDecorViewModel(
spyContext,
testShellExecutor,
@@ -230,6 +233,7 @@ open class DesktopModeWindowDecorViewModelTestsBase : ShellTestCase() {
mock<DesktopModeUiEventLogger>(),
mock<WindowDecorTaskResourceLoader>(),
mockRecentsTransitionHandler,
+ desktopModeCompatPolicy,
)
desktopModeWindowDecorViewModel.setSplitScreenController(mockSplitScreenController)
whenever(mockDisplayController.getDisplayLayout(any())).thenReturn(mockDisplayLayout)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
index 9ea5fd6e1abe..87198d14c839 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
@@ -280,7 +280,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
mTestableContext = new TestableContext(mContext);
mTestableContext.ensureTestableResources();
mContext.setMockPackageManager(mMockPackageManager);
- when(mMockMultiInstanceHelper.supportsMultiInstanceSplit(any()))
+ when(mMockMultiInstanceHelper.supportsMultiInstanceSplit(any(), anyInt()))
.thenReturn(false);
when(mMockPackageManager.getApplicationLabel(any())).thenReturn("applicationLabel");
final ActivityInfo activityInfo = createActivityInfo();
@@ -295,7 +295,8 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
anyBoolean(), anyBoolean(), anyBoolean(), anyBoolean(), anyBoolean(), anyBoolean(),
any(), anyInt(), anyInt(), anyInt(), anyInt()))
.thenReturn(mMockHandleMenu);
- when(mMockMultiInstanceHelper.supportsMultiInstanceSplit(any())).thenReturn(false);
+ when(mMockMultiInstanceHelper.supportsMultiInstanceSplit(any(), anyInt()))
+ .thenReturn(false);
when(mMockAppHeaderViewHolderFactory.create(any(), any(), any(), any(), any(), any()))
.thenReturn(mMockAppHeaderViewHolder);
when(mMockDesktopUserRepositories.getCurrent()).thenReturn(mDesktopRepository);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositionerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositionerTest.kt
index 2207c705d7dc..0615c1d677ba 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositionerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositionerTest.kt
@@ -51,6 +51,7 @@ import com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_UNDEFI
import java.util.function.Supplier
import junit.framework.Assert
import org.junit.Before
+import org.junit.Ignore
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
@@ -206,6 +207,7 @@ class MultiDisplayVeiledResizeTaskPositionerTest : ShellTestCase() {
}
@Test
+ @Ignore("Causing presubmit failure b/391717499")
fun testDragResize_movesTask_doesNotShowResizeVeil() = runOnUiThread {
taskPositioner.onDragPositioningStart(
CTRL_TYPE_UNDEFINED,
@@ -245,6 +247,7 @@ class MultiDisplayVeiledResizeTaskPositionerTest : ShellTestCase() {
}
@Test
+ @Ignore("Causing presubmit failure b/391717499")
fun testDragResize_movesTaskToNewDisplay() = runOnUiThread {
taskPositioner.onDragPositioningStart(
CTRL_TYPE_UNDEFINED,
@@ -370,6 +373,7 @@ class MultiDisplayVeiledResizeTaskPositionerTest : ShellTestCase() {
}
@Test
+ @Ignore("Causing presubmit failure b/391717499")
fun testDragResize_drag_setBoundsNotRunIfDragEndsInDisallowedEndArea() = runOnUiThread {
taskPositioner.onDragPositioningStart(
CTRL_TYPE_UNDEFINED, // drag
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/common/WindowDecorTaskResourceLoaderTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/common/WindowDecorTaskResourceLoaderTest.kt
index 1ec0fe794d0a..431de896f433 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/common/WindowDecorTaskResourceLoaderTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/common/WindowDecorTaskResourceLoaderTest.kt
@@ -33,6 +33,7 @@ import com.android.launcher3.icons.IconProvider
import com.android.wm.shell.ShellTestCase
import com.android.wm.shell.TestRunningTaskInfoBuilder
import com.android.wm.shell.TestShellExecutor
+import com.android.wm.shell.common.UserProfileContexts
import com.android.wm.shell.sysui.ShellController
import com.android.wm.shell.sysui.ShellInit
import com.android.wm.shell.sysui.UserChangeListener
@@ -69,6 +70,7 @@ class WindowDecorTaskResourceLoaderTest : ShellTestCase() {
private val mockIconProvider = mock<IconProvider>()
private val mockHeaderIconFactory = mock<BaseIconFactory>()
private val mockVeilIconFactory = mock<BaseIconFactory>()
+ private val mMockUserProfileContexts = mock<UserProfileContexts>()
private lateinit var spyContext: TestableContext
private lateinit var loader: WindowDecorTaskResourceLoader
@@ -83,12 +85,13 @@ class WindowDecorTaskResourceLoaderTest : ShellTestCase() {
spyContext = spy(mContext)
spyContext.setMockPackageManager(mockPackageManager)
doReturn(spyContext).whenever(spyContext).createContextAsUser(any(), anyInt())
+ doReturn(spyContext).whenever(mMockUserProfileContexts)[anyInt()]
loader =
WindowDecorTaskResourceLoader(
- context = spyContext,
shellInit = shellInit,
shellController = mockShellController,
shellCommandHandler = mock(),
+ userProfilesContexts = mMockUserProfileContexts,
iconProvider = mockIconProvider,
headerIconFactory = mockHeaderIconFactory,
veilIconFactory = mockVeilIconFactory,
@@ -170,16 +173,6 @@ class WindowDecorTaskResourceLoaderTest : ShellTestCase() {
}
@Test
- fun testUserChange_updatesContext() {
- val newUser = 5000
- val newContext = mock<Context>()
-
- userChangeListener.onUserChanged(newUser, newContext)
-
- assertThat(loader.currentUserContext).isEqualTo(newContext)
- }
-
- @Test
fun testUserChange_clearsCache() {
val newUser = 5000
val newContext = mock<Context>()
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDecorViewModelTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDecorViewModelTest.kt
index 997ece6ecadc..2cabb9a33b86 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDecorViewModelTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDecorViewModelTest.kt
@@ -23,6 +23,7 @@ import com.android.wm.shell.RootTaskDisplayAreaOrganizer
import com.android.wm.shell.ShellTaskOrganizer
import com.android.wm.shell.ShellTestCase
import com.android.wm.shell.common.DisplayController
+import com.android.wm.shell.common.ShellExecutor
import com.android.wm.shell.common.SyncTransactionQueue
import com.android.wm.shell.desktopmode.DesktopModeEventLogger
import com.android.wm.shell.desktopmode.DesktopUserRepositories
@@ -30,6 +31,7 @@ import com.android.wm.shell.desktopmode.DesktopTasksController
import com.android.wm.shell.desktopmode.DesktopTestHelpers.createFreeformTask
import com.android.wm.shell.desktopmode.ReturnToDragStartAnimator
import com.android.wm.shell.desktopmode.ToggleResizeDesktopTaskTransitionHandler
+import com.android.wm.shell.transition.FocusTransitionObserver
import com.android.wm.shell.transition.Transitions
import com.android.wm.shell.windowdecor.DesktopModeWindowDecoration
import com.android.wm.shell.windowdecor.common.WindowDecorTaskResourceLoader
@@ -67,6 +69,8 @@ class DesktopTilingDecorViewModelTest : ShellTestCase() {
private val desktopModeWindowDecorationMock: DesktopModeWindowDecoration = mock()
private val desktopTilingDecoration: DesktopTilingWindowDecoration = mock()
private val taskResourceLoader: WindowDecorTaskResourceLoader = mock()
+ private val focusTransitionObserver: FocusTransitionObserver = mock()
+ private val mainExecutor: ShellExecutor = mock()
private lateinit var desktopTilingDecorViewModel: DesktopTilingDecorViewModel
@Before
@@ -86,6 +90,8 @@ class DesktopTilingDecorViewModelTest : ShellTestCase() {
userRepositories,
desktopModeEventLogger,
taskResourceLoader,
+ focusTransitionObserver,
+ mainExecutor
)
whenever(contextMock.createContextAsUser(any(), any())).thenReturn(contextMock)
}
@@ -140,7 +146,7 @@ class DesktopTilingDecorViewModelTest : ShellTestCase() {
desktopTilingDecorViewModel.moveTaskToFrontIfTiled(task1)
verify(desktopTilingDecoration, times(1))
- .moveTiledPairToFront(any(), isTaskFocused = eq(true))
+ .moveTiledPairToFront(any(), isFocusedOnDisplay = eq(true))
}
@Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecorationTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecorationTest.kt
index 2f15c2e38855..399a51e1ed08 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecorationTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecorationTest.kt
@@ -33,6 +33,7 @@ import com.android.wm.shell.ShellTaskOrganizer
import com.android.wm.shell.ShellTestCase
import com.android.wm.shell.common.DisplayController
import com.android.wm.shell.common.DisplayLayout
+import com.android.wm.shell.common.ShellExecutor
import com.android.wm.shell.common.SyncTransactionQueue
import com.android.wm.shell.desktopmode.DesktopModeEventLogger
import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ResizeTrigger
@@ -42,6 +43,7 @@ import com.android.wm.shell.desktopmode.DesktopTestHelpers.createFreeformTask
import com.android.wm.shell.desktopmode.DesktopUserRepositories
import com.android.wm.shell.desktopmode.ReturnToDragStartAnimator
import com.android.wm.shell.desktopmode.ToggleResizeDesktopTaskTransitionHandler
+import com.android.wm.shell.transition.FocusTransitionObserver
import com.android.wm.shell.transition.Transitions
import com.android.wm.shell.windowdecor.DesktopModeWindowDecoration
import com.android.wm.shell.windowdecor.DragResizeWindowGeometry
@@ -105,6 +107,8 @@ class DesktopTilingWindowDecorationTest : ShellTestCase() {
private val mainDispatcher: MainCoroutineDispatcher = mock()
private val bgScope: CoroutineScope = mock()
private val taskResourceLoader: WindowDecorTaskResourceLoader = mock()
+ private val focusTransitionObserver: FocusTransitionObserver = mock()
+ private val mainExecutor: ShellExecutor = mock()
private lateinit var tilingDecoration: DesktopTilingWindowDecoration
private val split_divider_width = 10
@@ -129,6 +133,8 @@ class DesktopTilingWindowDecorationTest : ShellTestCase() {
returnToDragStartAnimator,
userRepositories,
desktopModeEventLogger,
+ focusTransitionObserver,
+ mainExecutor
)
whenever(context.createContextAsUser(any(), any())).thenReturn(context)
whenever(userRepositories.current).thenReturn(desktopRepository)
@@ -242,7 +248,7 @@ class DesktopTilingWindowDecorationTest : ShellTestCase() {
BOUNDS,
)
- assertThat(tilingDecoration.moveTiledPairToFront(task2)).isFalse()
+ assertThat(tilingDecoration.moveTiledPairToFront(task2.taskId, false)).isFalse()
verify(transitions, never()).startTransition(any(), any(), any())
}
@@ -272,7 +278,7 @@ class DesktopTilingWindowDecorationTest : ShellTestCase() {
BOUNDS,
)
- assertThat(tilingDecoration.moveTiledPairToFront(task3)).isFalse()
+ assertThat(tilingDecoration.moveTiledPairToFront(task3.taskId, false)).isFalse()
verify(transitions, never()).startTransition(any(), any(), any())
}
@@ -304,7 +310,7 @@ class DesktopTilingWindowDecorationTest : ShellTestCase() {
)
task1.isFocused = true
- assertThat(tilingDecoration.moveTiledPairToFront(task1, isTaskFocused = true)).isTrue()
+ assertThat(tilingDecoration.moveTiledPairToFront(task1.taskId, isFocusedOnDisplay = true)).isTrue()
verify(transitions, times(1)).startTransition(eq(TRANSIT_TO_FRONT), any(), eq(null))
}
@@ -336,8 +342,8 @@ class DesktopTilingWindowDecorationTest : ShellTestCase() {
task1.isFocused = true
task3.isFocused = true
- assertThat(tilingDecoration.moveTiledPairToFront(task3)).isFalse()
- assertThat(tilingDecoration.moveTiledPairToFront(task1)).isTrue()
+ assertThat(tilingDecoration.moveTiledPairToFront(task3.taskId, true)).isFalse()
+ assertThat(tilingDecoration.moveTiledPairToFront(task1.taskId, true)).isTrue()
verify(transitions, times(1)).startTransition(eq(TRANSIT_TO_FRONT), any(), eq(null))
}
@@ -367,8 +373,8 @@ class DesktopTilingWindowDecorationTest : ShellTestCase() {
BOUNDS,
)
- assertThat(tilingDecoration.moveTiledPairToFront(task3, isTaskFocused = true)).isFalse()
- assertThat(tilingDecoration.moveTiledPairToFront(task1, isTaskFocused = true)).isTrue()
+ assertThat(tilingDecoration.moveTiledPairToFront(task3.taskId, isFocusedOnDisplay = true)).isFalse()
+ assertThat(tilingDecoration.moveTiledPairToFront(task1.taskId, isFocusedOnDisplay = true)).isTrue()
verify(transitions, times(1)).startTransition(eq(TRANSIT_TO_FRONT), any(), eq(null))
}
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index 677fd86aca9c..53d3b77f1ba2 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -206,6 +206,9 @@ java_sdk_library {
visibility: [
"//frameworks/base", // Framework
],
+ impl_library_visibility: [
+ "//frameworks/base/ravenwood",
+ ],
srcs: [
":framework-graphics-srcs",
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 52eae43f7db9..71013f7f4e34 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -21,6 +21,8 @@ import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_AUDIO;
import static android.content.Context.DEVICE_ID_DEFAULT;
import static android.media.audio.Flags.autoPublicVolumeApiHardening;
import static android.media.audio.Flags.automaticBtDeviceType;
+import static android.media.audio.Flags.cacheGetStreamMinMaxVolume;
+import static android.media.audio.Flags.cacheGetStreamVolume;
import static android.media.audio.Flags.FLAG_DEPRECATE_STREAM_BT_SCO;
import static android.media.audio.Flags.FLAG_FOCUS_EXCLUSIVE_WITH_RECORDING;
import static android.media.audio.Flags.FLAG_FOCUS_FREEZE_TEST_API;
@@ -58,7 +60,6 @@ import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.media.AudioAttributes.AttributeSystemUsage;
-import android.media.AudioDeviceInfo;
import android.media.CallbackUtil.ListenerInfo;
import android.media.audiopolicy.AudioPolicy;
import android.media.audiopolicy.AudioPolicy.AudioPolicyFocusListener;
@@ -75,6 +76,7 @@ import android.os.Binder;
import android.os.Build;
import android.os.Handler;
import android.os.IBinder;
+import android.os.IpcDataCache;
import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
@@ -1231,6 +1233,102 @@ public class AudioManager {
}
/**
+ * API string for caching the min volume for each stream
+ * @hide
+ **/
+ public static final String VOLUME_MIN_CACHING_API = "getStreamMinVolume";
+ /**
+ * API string for caching the max volume for each stream
+ * @hide
+ **/
+ public static final String VOLUME_MAX_CACHING_API = "getStreamMaxVolume";
+ /**
+ * API string for caching the volume for each stream
+ * @hide
+ **/
+ public static final String VOLUME_CACHING_API = "getStreamVolume";
+ private static final int VOLUME_CACHING_SIZE = 16;
+
+ private final IpcDataCache.QueryHandler<VolumeCacheQuery, Integer> mVolQuery =
+ new IpcDataCache.QueryHandler<>() {
+ @Override
+ public Integer apply(VolumeCacheQuery query) {
+ final IAudioService service = getService();
+ try {
+ return switch (query.queryCommand) {
+ case QUERY_VOL_MIN -> service.getStreamMinVolume(query.stream);
+ case QUERY_VOL_MAX -> service.getStreamMaxVolume(query.stream);
+ case QUERY_VOL -> service.getStreamVolume(query.stream);
+ default -> {
+ Log.w(TAG, "Not a valid volume cache query: " + query);
+ yield null;
+ }
+ };
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ };
+
+ private final IpcDataCache<VolumeCacheQuery, Integer> mVolMinCache =
+ new IpcDataCache<>(VOLUME_CACHING_SIZE, IpcDataCache.MODULE_SYSTEM,
+ VOLUME_MIN_CACHING_API, VOLUME_MIN_CACHING_API, mVolQuery);
+
+ private final IpcDataCache<VolumeCacheQuery, Integer> mVolMaxCache =
+ new IpcDataCache<>(VOLUME_CACHING_SIZE, IpcDataCache.MODULE_SYSTEM,
+ VOLUME_MAX_CACHING_API, VOLUME_MAX_CACHING_API, mVolQuery);
+
+ private final IpcDataCache<VolumeCacheQuery, Integer> mVolCache =
+ new IpcDataCache<>(VOLUME_CACHING_SIZE, IpcDataCache.MODULE_SYSTEM,
+ VOLUME_CACHING_API, VOLUME_CACHING_API, mVolQuery);
+
+ /**
+ * Used to invalidate the cache for the given API
+ * @hide
+ **/
+ public static void clearVolumeCache(String api) {
+ if (cacheGetStreamMinMaxVolume() && (VOLUME_MAX_CACHING_API.equals(api)
+ || VOLUME_MIN_CACHING_API.equals(api))) {
+ IpcDataCache.invalidateCache(IpcDataCache.MODULE_SYSTEM, api);
+ } else if (cacheGetStreamVolume() && VOLUME_CACHING_API.equals(api)) {
+ IpcDataCache.invalidateCache(IpcDataCache.MODULE_SYSTEM, api);
+ } else {
+ Log.w(TAG, "invalid clearVolumeCache for api " + api);
+ }
+ }
+
+ private static final int QUERY_VOL_MIN = 1;
+ private static final int QUERY_VOL_MAX = 2;
+ private static final int QUERY_VOL = 3;
+ /** @hide */
+ @IntDef(prefix = "QUERY_VOL", value = {
+ QUERY_VOL_MIN,
+ QUERY_VOL_MAX,
+ QUERY_VOL}
+ )
+ @Retention(RetentionPolicy.SOURCE)
+ private @interface QueryVolCommand {}
+
+ private record VolumeCacheQuery(int stream, @QueryVolCommand int queryCommand) {
+ private String queryVolCommandToString() {
+ return switch (queryCommand) {
+ case QUERY_VOL_MIN -> "getStreamMinVolume";
+ case QUERY_VOL_MAX -> "getStreamMaxVolume";
+ case QUERY_VOL -> "getStreamVolume";
+ default -> "invalid command";
+ };
+ }
+
+ @NonNull
+ @Override
+ public String toString() {
+ return TextUtils.formatSimple("VolumeCacheQuery(stream=%d, queryCommand=%s)", stream,
+ queryVolCommandToString());
+ }
+ }
+
+ /**
* Returns the maximum volume index for a particular stream.
*
* @param streamType The stream type whose maximum volume index is returned.
@@ -1238,6 +1336,9 @@ public class AudioManager {
* @see #getStreamVolume(int)
*/
public int getStreamMaxVolume(int streamType) {
+ if (cacheGetStreamMinMaxVolume()) {
+ return mVolMaxCache.query(new VolumeCacheQuery(streamType, QUERY_VOL_MAX));
+ }
final IAudioService service = getService();
try {
return service.getStreamMaxVolume(streamType);
@@ -1271,6 +1372,9 @@ public class AudioManager {
*/
@TestApi
public int getStreamMinVolumeInt(int streamType) {
+ if (cacheGetStreamMinMaxVolume()) {
+ return mVolMinCache.query(new VolumeCacheQuery(streamType, QUERY_VOL_MIN));
+ }
final IAudioService service = getService();
try {
return service.getStreamMinVolume(streamType);
@@ -1288,6 +1392,9 @@ public class AudioManager {
* @see #setStreamVolume(int, int, int)
*/
public int getStreamVolume(int streamType) {
+ if (cacheGetStreamVolume()) {
+ return mVolCache.query(new VolumeCacheQuery(streamType, QUERY_VOL));
+ }
final IAudioService service = getService();
try {
return service.getStreamVolume(streamType);
diff --git a/media/java/android/media/MediaCodec.java b/media/java/android/media/MediaCodec.java
index c9625c405faa..c4886836f451 100644
--- a/media/java/android/media/MediaCodec.java
+++ b/media/java/android/media/MediaCodec.java
@@ -20,6 +20,8 @@ import static android.media.codec.Flags.FLAG_CODEC_AVAILABILITY;
import static android.media.codec.Flags.FLAG_NULL_OUTPUT_SURFACE;
import static android.media.codec.Flags.FLAG_REGION_OF_INTEREST;
import static android.media.codec.Flags.FLAG_SUBSESSION_METRICS;
+import static android.media.tv.flags.Flags.applyPictureProfiles;
+import static android.media.tv.flags.Flags.mediaQualityFw;
import static com.android.media.codec.flags.Flags.FLAG_LARGE_AUDIO_FRAME;
@@ -37,6 +39,8 @@ import android.graphics.Rect;
import android.graphics.SurfaceTexture;
import android.hardware.HardwareBuffer;
import android.media.MediaCodecInfo.CodecCapabilities;
+import android.media.quality.PictureProfile;
+import android.media.quality.PictureProfileHandle;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
@@ -5370,6 +5374,9 @@ final public class MediaCodec {
* @param params The bundle of parameters to set.
* @throws IllegalStateException if in the Released state.
*/
+
+ private static final String PARAMETER_KEY_PICTURE_PROFILE_HANDLE = "picture-profile-handle";
+
public final void setParameters(@Nullable Bundle params) {
if (params == null) {
return;
@@ -5383,19 +5390,41 @@ final public class MediaCodec {
if (key.equals(MediaFormat.KEY_AUDIO_SESSION_ID)) {
int sessionId = 0;
try {
- sessionId = (Integer)params.get(key);
+ sessionId = (Integer) params.get(key);
} catch (Exception e) {
throw new IllegalArgumentException("Wrong Session ID Parameter!");
}
keys[i] = "audio-hw-sync";
values[i] = AudioSystem.getAudioHwSyncForSession(sessionId);
+ } else if (applyPictureProfiles() && mediaQualityFw()
+ && key.equals(MediaFormat.KEY_PICTURE_PROFILE_INSTANCE)) {
+ PictureProfile pictureProfile = null;
+ try {
+ pictureProfile = (PictureProfile) params.get(key);
+ } catch (ClassCastException e) {
+ throw new IllegalArgumentException(
+ "Cannot cast the instance parameter to PictureProfile!");
+ } catch (Exception e) {
+ android.util.Log.getStackTraceString(e);
+ throw new IllegalArgumentException("Unexpected exception when casting the "
+ + "instance parameter to PictureProfile!");
+ }
+ if (pictureProfile == null) {
+ throw new IllegalArgumentException(
+ "Picture profile instance parameter is null!");
+ }
+ PictureProfileHandle handle = pictureProfile.getHandle();
+ if (handle != PictureProfileHandle.NONE) {
+ keys[i] = PARAMETER_KEY_PICTURE_PROFILE_HANDLE;
+ values[i] = Long.valueOf(handle.getId());
+ }
} else {
keys[i] = key;
Object value = params.get(key);
// Bundle's byte array is a byte[], JNI layer only takes ByteBuffer
if (value instanceof byte[]) {
- values[i] = ByteBuffer.wrap((byte[])value);
+ values[i] = ByteBuffer.wrap((byte[]) value);
} else {
values[i] = value;
}
diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java
index e57148fe5a6a..3af36a404c30 100644
--- a/media/java/android/media/MediaRouter2.java
+++ b/media/java/android/media/MediaRouter2.java
@@ -281,7 +281,7 @@ public final class MediaRouter2 {
/* executor */ null,
/* onInstanceInvalidatedListener */ null);
} catch (IllegalArgumentException ex) {
- Log.e(TAG, "Package " + clientPackageName + " not found. Ignoring.");
+ Log.e(TAG, "Failed to create proxy router for package '" + clientPackageName + "'", ex);
return null;
}
}
diff --git a/media/java/android/media/flags/projection.aconfig b/media/java/android/media/flags/projection.aconfig
index fa1349c61c4c..6d4f0b4f47d5 100644
--- a/media/java/android/media/flags/projection.aconfig
+++ b/media/java/android/media/flags/projection.aconfig
@@ -29,3 +29,13 @@ flag {
is_exported: true
}
+flag {
+ namespace: "media_projection"
+ name: "show_stop_dialog_post_call_end"
+ description: "Shows a stop dialog for MediaProjection sessions that started during call and remain active after a call ends"
+ bug: "390343524"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+ is_exported: true
+}
diff --git a/media/java/android/media/projection/IMediaProjectionWatcherCallback.aidl b/media/java/android/media/projection/IMediaProjectionWatcherCallback.aidl
index e46d34e81483..3baf4d7efd65 100644
--- a/media/java/android/media/projection/IMediaProjectionWatcherCallback.aidl
+++ b/media/java/android/media/projection/IMediaProjectionWatcherCallback.aidl
@@ -18,6 +18,7 @@ package android.media.projection;
import android.media.projection.MediaProjectionInfo;
import android.view.ContentRecordingSession;
+import android.media.projection.MediaProjectionEvent;
/** {@hide} */
oneway interface IMediaProjectionWatcherCallback {
@@ -35,4 +36,19 @@ oneway interface IMediaProjectionWatcherCallback {
in MediaProjectionInfo info,
in @nullable ContentRecordingSession session
);
+
+ /**
+ * Called when a specific {@link MediaProjectionEvent} occurs during the media projection session.
+ *
+ * @param event contains the event type, which describes the nature/context of the event.
+ * @param info optional {@link MediaProjectionInfo} containing details about the media
+ projection host.
+ * @param session the recording session for the current media projection. Can be
+ * {@code null} when the recording will stop.
+ */
+ void onMediaProjectionEvent(
+ in MediaProjectionEvent event,
+ in @nullable MediaProjectionInfo info,
+ in @nullable ContentRecordingSession session
+ );
}
diff --git a/media/java/android/media/projection/MediaProjectionEvent.aidl b/media/java/android/media/projection/MediaProjectionEvent.aidl
new file mode 100644
index 000000000000..34359900ce81
--- /dev/null
+++ b/media/java/android/media/projection/MediaProjectionEvent.aidl
@@ -0,0 +1,3 @@
+package android.media.projection;
+
+parcelable MediaProjectionEvent; \ No newline at end of file
diff --git a/media/java/android/media/projection/MediaProjectionEvent.java b/media/java/android/media/projection/MediaProjectionEvent.java
new file mode 100644
index 000000000000..6922560c8abe
--- /dev/null
+++ b/media/java/android/media/projection/MediaProjectionEvent.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.projection;
+
+import android.annotation.IntDef;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
+
+/** @hide */
+public final class MediaProjectionEvent implements Parcelable {
+
+ /**
+ * Represents various media projection events.
+ */
+ @IntDef({PROJECTION_STARTED_DURING_CALL_AND_ACTIVE_POST_CALL})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface EventType {}
+
+ /** Event type for when a call ends but the session is still active. */
+ public static final int PROJECTION_STARTED_DURING_CALL_AND_ACTIVE_POST_CALL = 0;
+
+ private final @EventType int mEventType;
+ private final long mTimestampMillis;
+
+ public MediaProjectionEvent(@EventType int eventType, long timestampMillis) {
+ mEventType = eventType;
+ mTimestampMillis = timestampMillis;
+ }
+
+ private MediaProjectionEvent(Parcel in) {
+ mEventType = in.readInt();
+ mTimestampMillis = in.readLong();
+ }
+
+ public @EventType int getEventType() {
+ return mEventType;
+ }
+
+ public long getTimestampMillis() {
+ return mTimestampMillis;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o instanceof MediaProjectionEvent other) {
+ return mEventType == other.mEventType && mTimestampMillis == other.mTimestampMillis;
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mEventType, mTimestampMillis);
+ }
+
+ @Override
+ public String toString() {
+ return "MediaProjectionEvent{mEventType=" + mEventType + ", mTimestampMillis="
+ + mTimestampMillis + "}";
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeInt(mEventType);
+ out.writeLong(mTimestampMillis);
+ }
+
+ public static final Parcelable.Creator<MediaProjectionEvent> CREATOR =
+ new Parcelable.Creator<>() {
+ @Override
+ public MediaProjectionEvent createFromParcel(Parcel in) {
+ return new MediaProjectionEvent(in);
+ }
+
+ @Override
+ public MediaProjectionEvent[] newArray(int size) {
+ return new MediaProjectionEvent[size];
+ }
+ };
+}
diff --git a/media/java/android/media/projection/MediaProjectionManager.java b/media/java/android/media/projection/MediaProjectionManager.java
index 9cc2cca441a4..9036bf385d96 100644
--- a/media/java/android/media/projection/MediaProjectionManager.java
+++ b/media/java/android/media/projection/MediaProjectionManager.java
@@ -363,6 +363,19 @@ public final class MediaProjectionManager {
@Nullable ContentRecordingSession session
) {
}
+
+ /**
+ * Called when a specific {@link MediaProjectionEvent} occurs during the media projection
+ * session.
+ *
+ * @param event the media projection event details.
+ * @param info optional details about the media projection host.
+ * @param session optional associated recording session details.
+ */
+ public void onMediaProjectionEvent(
+ final MediaProjectionEvent event,
+ @Nullable MediaProjectionInfo info,
+ @Nullable final ContentRecordingSession session) {}
}
/** @hide */
@@ -405,5 +418,13 @@ public final class MediaProjectionManager {
) {
mHandler.post(() -> mCallback.onRecordingSessionSet(info, session));
}
+
+ @Override
+ public void onMediaProjectionEvent(
+ final MediaProjectionEvent event,
+ @Nullable MediaProjectionInfo info,
+ @Nullable final ContentRecordingSession session) {
+ mHandler.post(() -> mCallback.onMediaProjectionEvent(event, info, session));
+ }
}
}
diff --git a/media/java/android/media/quality/MediaQualityContract.java b/media/java/android/media/quality/MediaQualityContract.java
index d1f63404dbff..e558209420e0 100644
--- a/media/java/android/media/quality/MediaQualityContract.java
+++ b/media/java/android/media/quality/MediaQualityContract.java
@@ -385,6 +385,332 @@ public class MediaQualityContract {
public static final String PARAMETER_AUTO_SUPER_RESOLUTION_ENABLED =
"auto_super_resolution_enabled";
+ /**
+ * @hide
+ *
+ */
+ public static final String PARAMETER_LEVEL_RANGE = "level_range";
+
+ /**
+ * @hide
+ *
+ */
+ public static final String PARAMETER_GAMUT_MAPPING = "gamut_mapping";
+
+ /**
+ * @hide
+ *
+ */
+ public static final String PARAMETER_PC_MODE = "pc_mode";
+
+ /**
+ * @hide
+ *
+ */
+ public static final String PARAMETER_LOW_LATENCY = "low_latency";
+
+ /**
+ * @hide
+ *
+ */
+ public static final String PARAMETER_VRR = "vrr";
+
+ /**
+ * @hide
+ *
+ */
+ public static final String PARAMETER_CVRR = "cvrr";
+
+ /**
+ * @hide
+ *
+ */
+ public static final String PARAMETER_HDMI_RGB_RANGE = "hdmi_rgb_range";
+
+ /**
+ * @hide
+ *
+ */
+ public static final String PARAMETER_COLOR_SPACE = "color_space";
+
+ /**
+ * @hide
+ *
+ */
+ public static final String PARAMETER_PANEL_INIT_MAX_LUMINCE_NITS =
+ "panel_init_max_lumince_nits";
+
+ /**
+ * @hide
+ *
+ */
+ public static final String PARAMETER_PANEL_INIT_MAX_LUMINCE_VALID =
+ "panel_init_max_lumince_valid";
+
+ /**
+ * @hide
+ *
+ */
+ public static final String PARAMETER_GAMMA = "gamma";
+
+ /**
+ * @hide
+ *
+ */
+ public static final String PARAMETER_COLOR_TEMPERATURE_RED_OFFSET =
+ "color_temperature_red_offset";
+
+ /**
+ * @hide
+ *
+ */
+ public static final String PARAMETER_COLOR_TEMPERATURE_GREEN_OFFSET =
+ "color_temperature_green_offset";
+
+ /**
+ * @hide
+ *
+ */
+ public static final String PARAMETER_COLOR_TEMPERATURE_BLUE_OFFSET =
+ "color_temperature_blue_offset";
+
+ /**
+ * @hide
+ *
+ */
+ public static final String PARAMETER_ELEVEN_POINT_RED = "eleven_point_red";
+
+ /**
+ * @hide
+ *
+ */
+ public static final String PARAMETER_ELEVEN_POINT_GREEN = "eleven_point_green";
+
+ /**
+ * @hide
+ *
+ */
+ public static final String PARAMETER_ELEVEN_POINT_BLUE = "eleven_point_blue";
+
+ /**
+ * @hide
+ *
+ */
+ public static final String PARAMETER_LOW_BLUE_LIGHT = "low_blue_light";
+
+ /**
+ * @hide
+ *
+ */
+ public static final String PARAMETER_LD_MODE = "ld_mode";
+
+ /**
+ * @hide
+ *
+ */
+ public static final String PARAMETER_OSD_RED_GAIN = "osd_red_gain";
+
+ /**
+ * @hide
+ *
+ */
+ public static final String PARAMETER_OSD_GREEN_GAIN = "osd_green_gain";
+
+ /**
+ * @hide
+ *
+ */
+ public static final String PARAMETER_OSD_BLUE_GAIN = "osd_blue_gain";
+
+ /**
+ * @hide
+ *
+ */
+ public static final String PARAMETER_OSD_RED_OFFSET = "osd_red_offset";
+
+ /**
+ * @hide
+ *
+ */
+ public static final String PARAMETER_OSD_GREEN_OFFSET = "osd_green_offset";
+
+ /**
+ * @hide
+ *
+ */
+ public static final String PARAMETER_OSD_BLUE_OFFSET = "osd_blue_offset";
+
+ /**
+ * @hide
+ *
+ */
+ public static final String PARAMETER_OSD_HUE = "osd_hue";
+
+ /**
+ * @hide
+ *
+ */
+ public static final String PARAMETER_OSD_SATURATION = "osd_saturation";
+
+ /**
+ * @hide
+ *
+ */
+ public static final String PARAMETER_OSD_CONTRAST = "osd_contrast";
+
+ /**
+ * @hide
+ *
+ */
+ public static final String PARAMETER_COLOR_TUNER_SWITCH = "color_tuner_switch";
+
+ /**
+ * @hide
+ *
+ */
+ public static final String PARAMETER_COLOR_TUNER_HUE_RED = "color_tuner_hue_red";
+
+ /**
+ * @hide
+ *
+ */
+ public static final String PARAMETER_COLOR_TUNER_HUE_GREEN = "color_tuner_hue_green";
+
+ /**
+ * @hide
+ *
+ */
+ public static final String PARAMETER_COLOR_TUNER_HUE_BLUE = "color_tuner_hue_blue";
+
+ /**
+ * @hide
+ *
+ */
+ public static final String PARAMETER_COLOR_TUNER_HUE_CYAN = "color_tuner_hue_cyan";
+
+ /**
+ * @hide
+ *
+ */
+ public static final String PARAMETER_COLOR_TUNER_HUE_MAGENTA = "color_tuner_hue_magenta";
+
+ /**
+ * @hide
+ *
+ */
+ public static final String PARAMETER_COLOR_TUNER_HUE_YELLOW = "color_tuner_hue_yellow";
+
+ /**
+ * @hide
+ *
+ */
+ public static final String PARAMETER_COLOR_TUNER_HUE_FLESH = "color_tuner_hue_flesh";
+
+ /**
+ * @hide
+ *
+ */
+ public static final String PARAMETER_COLOR_TUNER_SATURATION_RED =
+ "color_tuner_saturation_red";
+
+ /**
+ * @hide
+ *
+ */
+ public static final String PARAMETER_COLOR_TUNER_SATURATION_GREEN =
+ "color_tuner_saturation_green";
+
+ /**
+ * @hide
+ *
+ */
+ public static final String PARAMETER_COLOR_TUNER_SATURATION_BLUE =
+ "color_tuner_saturation_blue";
+
+ /**
+ * @hide
+ *
+ */
+ public static final String PARAMETER_COLOR_TUNER_SATURATION_CYAN =
+ "color_tuner_saturation_cyan";
+
+ /**
+ * @hide
+ *
+ */
+ public static final String PARAMETER_COLOR_TUNER_SATURATION_MAGENTA =
+ "color_tuner_saturation_magenta";
+
+ /**
+ * @hide
+ *
+ */
+ public static final String PARAMETER_COLOR_TUNER_SATURATION_YELLOW =
+ "color_tuner_saturation_yellow";
+
+ /**
+ * @hide
+ *
+ */
+ public static final String PARAMETER_COLOR_TUNER_SATURATION_FLESH =
+ "color_tuner_saturation_flesh";
+
+ /**
+ * @hide
+ *
+ */
+ public static final String PARAMETER_COLOR_TUNER_LUMINANCE_RED =
+ "color_tuner_luminance_red";
+
+ /**
+ * @hide
+ *
+ */
+ public static final String PARAMETER_COLOR_TUNER_LUMINANCE_GREEN =
+ "color_tuner_luminance_green";
+
+ /**
+ * @hide
+ *
+ */
+ public static final String PARAMETER_COLOR_TUNER_LUMINANCE_BLUE =
+ "color_tuner_luminance_blue";
+
+ /**
+ * @hide
+ *
+ */
+ public static final String PARAMETER_COLOR_TUNER_LUMINANCE_CYAN =
+ "color_tuner_luminance_cyan";
+
+ /**
+ * @hide
+ *
+ */
+ public static final String PARAMETER_COLOR_TUNER_LUMINANCE_MAGENTA =
+ "color_tuner_luminance_magenta";
+
+ /**
+ * @hide
+ *
+ */
+ public static final String PARAMETER_COLOR_TUNER_LUMINANCE_YELLOW =
+ "color_tuner_luminance_yellow";
+
+ /**
+ * @hide
+ *
+ */
+ public static final String PARAMETER_COLOR_TUNER_LUMINANCE_FLESH =
+ "color_tuner_luminance_flesh";
+
+ /**
+ * @hide
+ *
+ */
+ public static final String PARAMETER_PICTURE_QUALITY_EVENT_TYPE =
+ "picture_quality_event_type";
+
private PictureQuality() {
}
}
@@ -641,6 +967,12 @@ public class MediaQualityContract {
*/
public static final String PARAMETER_DIGITAL_OUTPUT_MODE = "digital_output_mode";
+ /**
+ * @hide
+ */
+ public static final String PARAMETER_SOUND_STYLE = "sound_style";
+
+
private SoundQuality() {
}
diff --git a/media/java/android/media/quality/MediaQualityManager.java b/media/java/android/media/quality/MediaQualityManager.java
index b7269256a449..0d6d32a22dae 100644
--- a/media/java/android/media/quality/MediaQualityManager.java
+++ b/media/java/android/media/quality/MediaQualityManager.java
@@ -51,7 +51,6 @@ import java.util.function.Consumer;
@FlaggedApi(Flags.FLAG_MEDIA_QUALITY_FW)
@SystemService(Context.MEDIA_QUALITY_SERVICE)
public final class MediaQualityManager {
- // TODO: unhide the APIs for api review
private static final String TAG = "MediaQualityManager";
private final IMediaQualityManager mService;
@@ -123,7 +122,6 @@ public final class MediaQualityManager {
public void onPictureProfileAdded(String profileId, PictureProfile profile) {
synchronized (mPpLock) {
for (PictureProfileCallbackRecord record : mPpCallbackRecords) {
- // TODO: filter callback record
record.postPictureProfileAdded(profileId, profile);
}
}
@@ -132,7 +130,6 @@ public final class MediaQualityManager {
public void onPictureProfileUpdated(String profileId, PictureProfile profile) {
synchronized (mPpLock) {
for (PictureProfileCallbackRecord record : mPpCallbackRecords) {
- // TODO: filter callback record
record.postPictureProfileUpdated(profileId, profile);
}
}
@@ -141,7 +138,6 @@ public final class MediaQualityManager {
public void onPictureProfileRemoved(String profileId, PictureProfile profile) {
synchronized (mPpLock) {
for (PictureProfileCallbackRecord record : mPpCallbackRecords) {
- // TODO: filter callback record
record.postPictureProfileRemoved(profileId, profile);
}
}
@@ -151,7 +147,6 @@ public final class MediaQualityManager {
String profileId, List<ParameterCapability> caps) {
synchronized (mPpLock) {
for (PictureProfileCallbackRecord record : mPpCallbackRecords) {
- // TODO: filter callback record
record.postParameterCapabilitiesChanged(profileId, caps);
}
}
@@ -160,7 +155,6 @@ public final class MediaQualityManager {
public void onError(String profileId, int err) {
synchronized (mPpLock) {
for (PictureProfileCallbackRecord record : mPpCallbackRecords) {
- // TODO: filter callback record
record.postError(profileId, err);
}
}
@@ -171,7 +165,6 @@ public final class MediaQualityManager {
public void onSoundProfileAdded(String profileId, SoundProfile profile) {
synchronized (mSpLock) {
for (SoundProfileCallbackRecord record : mSpCallbackRecords) {
- // TODO: filter callback record
record.postSoundProfileAdded(profileId, profile);
}
}
@@ -180,7 +173,6 @@ public final class MediaQualityManager {
public void onSoundProfileUpdated(String profileId, SoundProfile profile) {
synchronized (mSpLock) {
for (SoundProfileCallbackRecord record : mSpCallbackRecords) {
- // TODO: filter callback record
record.postSoundProfileUpdated(profileId, profile);
}
}
@@ -189,7 +181,6 @@ public final class MediaQualityManager {
public void onSoundProfileRemoved(String profileId, SoundProfile profile) {
synchronized (mSpLock) {
for (SoundProfileCallbackRecord record : mSpCallbackRecords) {
- // TODO: filter callback record
record.postSoundProfileRemoved(profileId, profile);
}
}
@@ -199,7 +190,6 @@ public final class MediaQualityManager {
String profileId, List<ParameterCapability> caps) {
synchronized (mSpLock) {
for (SoundProfileCallbackRecord record : mSpCallbackRecords) {
- // TODO: filter callback record
record.postParameterCapabilitiesChanged(profileId, caps);
}
}
@@ -208,7 +198,6 @@ public final class MediaQualityManager {
public void onError(String profileId, int err) {
synchronized (mSpLock) {
for (SoundProfileCallbackRecord record : mSpCallbackRecords) {
- // TODO: filter callback record
record.postError(profileId, err);
}
}
diff --git a/media/java/android/mtp/OWNERS b/media/java/android/mtp/OWNERS
index 77ed08b1f9a5..c57265a91eff 100644
--- a/media/java/android/mtp/OWNERS
+++ b/media/java/android/mtp/OWNERS
@@ -1,9 +1,9 @@
set noparent
-anothermark@google.com
+vmartensson@google.com
+nkapron@google.com
febinthattil@google.com
-aprasath@google.com
+shubhankarm@google.com
jsharkey@android.com
jameswei@google.com
rmojumder@google.com
-kumarashishg@google.com
diff --git a/media/jni/android_mtp_MtpDatabase.cpp b/media/jni/android_mtp_MtpDatabase.cpp
index a77bc9fe0570..a1ce495fe33d 100644
--- a/media/jni/android_mtp_MtpDatabase.cpp
+++ b/media/jni/android_mtp_MtpDatabase.cpp
@@ -910,7 +910,7 @@ MtpResponseCode MtpDatabase::getObjectInfo(MtpObjectHandle handle,
case MTP_FORMAT_TIFF:
case MTP_FORMAT_TIFF_EP:
case MTP_FORMAT_DEFINED: {
- String8 temp(path);
+ String8 temp {static_cast<std::string_view>(path)};
std::unique_ptr<FileStream> stream(new FileStream(temp));
piex::PreviewImageData image_data;
if (!GetExifFromRawImage(stream.get(), temp, image_data)) {
@@ -967,7 +967,7 @@ void* MtpDatabase::getThumbnail(MtpObjectHandle handle, size_t& outThumbSize) {
case MTP_FORMAT_TIFF:
case MTP_FORMAT_TIFF_EP:
case MTP_FORMAT_DEFINED: {
- String8 temp(path);
+ String8 temp {static_cast<std::string_view>(path)};
std::unique_ptr<FileStream> stream(new FileStream(temp));
piex::PreviewImageData image_data;
if (!GetExifFromRawImage(stream.get(), temp, image_data)) {
diff --git a/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioManagerTest.java b/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioManagerTest.java
index e9a0d3eceba3..209734ca4a53 100644
--- a/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioManagerTest.java
+++ b/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioManagerTest.java
@@ -16,6 +16,15 @@
package com.android.audiopolicytest;
+import static android.media.AudioManager.STREAM_ACCESSIBILITY;
+import static android.media.AudioManager.STREAM_ALARM;
+import static android.media.AudioManager.STREAM_DTMF;
+import static android.media.AudioManager.STREAM_MUSIC;
+import static android.media.AudioManager.STREAM_NOTIFICATION;
+import static android.media.AudioManager.STREAM_RING;
+import static android.media.AudioManager.STREAM_SYSTEM;
+import static android.media.AudioManager.STREAM_VOICE_CALL;
+
import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
import static com.android.audiopolicytest.AudioVolumeTestUtil.DEFAULT_ATTRIBUTES;
@@ -28,11 +37,15 @@ import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
+import android.content.Context;
import android.media.AudioAttributes;
import android.media.AudioManager;
import android.media.AudioSystem;
+import android.media.IAudioService;
import android.media.audiopolicy.AudioProductStrategy;
import android.media.audiopolicy.AudioVolumeGroup;
+import android.os.IBinder;
+import android.os.ServiceManager;
import android.platform.test.annotations.Presubmit;
import android.util.Log;
@@ -54,6 +67,17 @@ public class AudioManagerTest {
private AudioManager mAudioManager;
+ private static final int[] PUBLIC_STREAM_TYPES = {
+ STREAM_VOICE_CALL,
+ STREAM_SYSTEM,
+ STREAM_RING,
+ STREAM_MUSIC,
+ STREAM_ALARM,
+ STREAM_NOTIFICATION,
+ STREAM_DTMF,
+ STREAM_ACCESSIBILITY,
+ };
+
@Rule
public final AudioVolumesTestRule rule = new AudioVolumesTestRule();
@@ -207,6 +231,33 @@ public class AudioManagerTest {
}
//-----------------------------------------------------------------
+ // Test getStreamVolume consistency with AudioService
+ //-----------------------------------------------------------------
+ @Test
+ public void getStreamMinMaxVolume_consistentWithAs() throws Exception {
+ IBinder b = ServiceManager.getService(Context.AUDIO_SERVICE);
+ IAudioService service = IAudioService.Stub.asInterface(b);
+
+ for (int streamType : PUBLIC_STREAM_TYPES) {
+ assertEquals(service.getStreamMinVolume(streamType),
+ mAudioManager.getStreamMinVolume(streamType));
+ assertEquals(service.getStreamMaxVolume(streamType),
+ mAudioManager.getStreamMaxVolume(streamType));
+ }
+ }
+
+ @Test
+ public void getStreamVolume_consistentWithAs() throws Exception {
+ IBinder b = ServiceManager.getService(Context.AUDIO_SERVICE);
+ IAudioService service = IAudioService.Stub.asInterface(b);
+
+ for (int streamType : PUBLIC_STREAM_TYPES) {
+ assertEquals(service.getStreamVolume(streamType),
+ mAudioManager.getStreamVolume(streamType));
+ }
+ }
+
+ //-----------------------------------------------------------------
// Test Volume per Attributes setter/getters
//-----------------------------------------------------------------
@Test
diff --git a/media/tests/MtpTests/OWNERS b/media/tests/MtpTests/OWNERS
index bdb6cdbea332..c57265a91eff 100644
--- a/media/tests/MtpTests/OWNERS
+++ b/media/tests/MtpTests/OWNERS
@@ -1,9 +1,9 @@
set noparent
-anothermark@google.com
+vmartensson@google.com
+nkapron@google.com
febinthattil@google.com
-aprasath@google.com
+shubhankarm@google.com
jsharkey@android.com
jameswei@google.com
rmojumder@google.com
-kumarashishg@google.com \ No newline at end of file
diff --git a/packages/PrintSpooler/src/com/android/printspooler/model/PageContentRepository.java b/packages/PrintSpooler/src/com/android/printspooler/model/PageContentRepository.java
index fe27cee7ba2d..acead8e2a0eb 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/model/PageContentRepository.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/model/PageContentRepository.java
@@ -510,7 +510,10 @@ public final class PageContentRepository {
protected Void doInBackground(Void... params) {
synchronized (mLock) {
try {
- if (mRenderer != null) {
+ // A page count < 0 indicates there was an error
+ // opening the document, in which case it doesn't
+ // need to be closed.
+ if (mRenderer != null && mPageCount >= 0) {
mRenderer.closeDocument();
}
} catch (RemoteException re) {
diff --git a/packages/PrintSpooler/src/com/android/printspooler/model/RemotePrintDocument.java b/packages/PrintSpooler/src/com/android/printspooler/model/RemotePrintDocument.java
index b48c55ddfef0..a9d00e9a77eb 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/model/RemotePrintDocument.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/model/RemotePrintDocument.java
@@ -70,6 +70,7 @@ public final class RemotePrintDocument {
private static final int STATE_CANCELING = 6;
private static final int STATE_CANCELED = 7;
private static final int STATE_DESTROYED = 8;
+ private static final int STATE_INVALID = 9;
private final Context mContext;
@@ -287,7 +288,8 @@ public final class RemotePrintDocument {
}
if (mState != STATE_STARTED && mState != STATE_UPDATED
&& mState != STATE_FAILED && mState != STATE_CANCELING
- && mState != STATE_CANCELED && mState != STATE_DESTROYED) {
+ && mState != STATE_CANCELED && mState != STATE_DESTROYED
+ && mState != STATE_INVALID) {
throw new IllegalStateException("Cannot finish in state:"
+ stateToString(mState));
}
@@ -300,6 +302,16 @@ public final class RemotePrintDocument {
}
}
+ /**
+ * Mark this document as invalid.
+ */
+ public void invalid() {
+ if (DEBUG) {
+ Log.i(LOG_TAG, "[CALLED] invalid()");
+ }
+ mState = STATE_INVALID;
+ }
+
public void cancel(boolean force) {
if (DEBUG) {
Log.i(LOG_TAG, "[CALLED] cancel(" + force + ")");
@@ -491,6 +503,9 @@ public final class RemotePrintDocument {
case STATE_DESTROYED: {
return "STATE_DESTROYED";
}
+ case STATE_INVALID: {
+ return "STATE_INVALID";
+ }
default: {
return "STATE_UNKNOWN";
}
diff --git a/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java b/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java
index 4a3a6d248254..2e3234e6e622 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java
@@ -167,6 +167,7 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat
private static final int STATE_PRINTER_UNAVAILABLE = 6;
private static final int STATE_UPDATE_SLOW = 7;
private static final int STATE_PRINT_COMPLETED = 8;
+ private static final int STATE_FILE_INVALID = 9;
private static final int UI_STATE_PREVIEW = 0;
private static final int UI_STATE_ERROR = 1;
@@ -404,6 +405,11 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat
public void onPause() {
PrintSpoolerService spooler = mSpoolerProvider.getSpooler();
+ if (isInvalid()) {
+ super.onPause();
+ return;
+ }
+
if (mState == STATE_INITIALIZING) {
if (isFinishing()) {
if (spooler != null) {
@@ -478,7 +484,8 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat
}
if (mState == STATE_PRINT_CANCELED || mState == STATE_PRINT_CONFIRMED
- || mState == STATE_PRINT_COMPLETED) {
+ || mState == STATE_PRINT_COMPLETED
+ || mState == STATE_FILE_INVALID) {
return true;
}
@@ -509,23 +516,32 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat
@Override
public void onMalformedPdfFile() {
onPrintDocumentError("Cannot print a malformed PDF file");
+ mPrintedDocument.invalid();
+ setState(STATE_FILE_INVALID);
}
@Override
public void onSecurePdfFile() {
onPrintDocumentError("Cannot print a password protected PDF file");
+ mPrintedDocument.invalid();
+ setState(STATE_FILE_INVALID);
}
private void onPrintDocumentError(String message) {
setState(mProgressMessageController.cancel());
- ensureErrorUiShown(null, PrintErrorFragment.ACTION_RETRY);
+ ensureErrorUiShown(
+ getString(R.string.print_cannot_load_page), PrintErrorFragment.ACTION_NONE);
setState(STATE_UPDATE_FAILED);
if (DEBUG) {
Log.i(LOG_TAG, "PrintJob state[" + PrintJobInfo.STATE_FAILED + "] reason: " + message);
}
PrintSpoolerService spooler = mSpoolerProvider.getSpooler();
- spooler.setPrintJobState(mPrintJob.getId(), PrintJobInfo.STATE_FAILED, message);
+ // Use a cancel state for the spooler. This will prevent the notification from getting
+ // displayed and will remove the job. The notification (which displays the cancel and
+ // restart options) doesn't make sense for an invalid document since it will just fail
+ // again.
+ spooler.setPrintJobState(mPrintJob.getId(), PrintJobInfo.STATE_CANCELED, message);
mPrintedDocument.finish();
}
@@ -995,6 +1011,9 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat
}
private void setState(int state) {
+ if (isInvalid()) {
+ return;
+ }
if (isFinalState(mState)) {
if (isFinalState(state)) {
if (DEBUG) {
@@ -1015,7 +1034,12 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat
private static boolean isFinalState(int state) {
return state == STATE_PRINT_CANCELED
|| state == STATE_PRINT_COMPLETED
- || state == STATE_CREATE_FILE_FAILED;
+ || state == STATE_CREATE_FILE_FAILED
+ || state == STATE_FILE_INVALID;
+ }
+
+ private boolean isInvalid() {
+ return mState == STATE_FILE_INVALID;
}
private void updateSelectedPagesFromPreview() {
@@ -1100,7 +1124,7 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat
}
private void ensurePreviewUiShown() {
- if (isFinishing() || isDestroyed()) {
+ if (isFinishing() || isDestroyed() || isInvalid()) {
return;
}
if (mUiState != UI_STATE_PREVIEW) {
@@ -1257,6 +1281,9 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat
}
private boolean updateDocument(boolean clearLastError) {
+ if (isInvalid()) {
+ return false;
+ }
if (!clearLastError && mPrintedDocument.hasUpdateError()) {
return false;
}
@@ -1676,7 +1703,8 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat
|| mState == STATE_UPDATE_FAILED
|| mState == STATE_CREATE_FILE_FAILED
|| mState == STATE_PRINTER_UNAVAILABLE
- || mState == STATE_UPDATE_SLOW) {
+ || mState == STATE_UPDATE_SLOW
+ || mState == STATE_FILE_INVALID) {
disableOptionsUi(isFinalState(mState));
return;
}
@@ -2100,6 +2128,9 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat
}
private boolean canUpdateDocument() {
+ if (isInvalid()) {
+ return false;
+ }
if (mPrintedDocument.isDestroyed()) {
return false;
}
diff --git a/packages/SettingsLib/BannerMessagePreference/Android.bp b/packages/SettingsLib/BannerMessagePreference/Android.bp
index 77e2cc735895..182daeb45d7c 100644
--- a/packages/SettingsLib/BannerMessagePreference/Android.bp
+++ b/packages/SettingsLib/BannerMessagePreference/Android.bp
@@ -28,4 +28,8 @@ android_library {
sdk_version: "system_current",
min_sdk_version: "28",
+ apex_available: [
+ "//apex_available:platform",
+ "com.android.healthfitness",
+ ],
}
diff --git a/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_filled.xml b/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_filled.xml
index f55b320269a8..ff22b2e7f86f 100644
--- a/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_filled.xml
+++ b/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_filled.xml
@@ -20,7 +20,8 @@
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
- android:paddingEnd="?android:attr/listPreferredItemPaddingEnd">
+ android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+ android:filterTouchesWhenObscured="true">
<com.google.android.material.button.MaterialButton
android:id="@+id/settingslib_button"
diff --git a/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_filled_extra.xml b/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_filled_extra.xml
index b663b6ccc5bf..d878ba0d310b 100644
--- a/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_filled_extra.xml
+++ b/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_filled_extra.xml
@@ -20,7 +20,8 @@
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
- android:paddingEnd="?android:attr/listPreferredItemPaddingEnd">
+ android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+ android:filterTouchesWhenObscured="true">
<com.google.android.material.button.MaterialButton
android:id="@+id/settingslib_button"
diff --git a/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_filled_large.xml b/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_filled_large.xml
index 784e6ad6a9f8..8f0a158c55eb 100644
--- a/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_filled_large.xml
+++ b/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_filled_large.xml
@@ -20,7 +20,8 @@
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
- android:paddingEnd="?android:attr/listPreferredItemPaddingEnd">
+ android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+ android:filterTouchesWhenObscured="true">
<com.google.android.material.button.MaterialButton
android:id="@+id/settingslib_button"
diff --git a/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_outline.xml b/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_outline.xml
index 8b44a6539801..0c8996063e9f 100644
--- a/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_outline.xml
+++ b/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_outline.xml
@@ -20,7 +20,8 @@
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
- android:paddingEnd="?android:attr/listPreferredItemPaddingEnd">
+ android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+ android:filterTouchesWhenObscured="true">
<com.google.android.material.button.MaterialButton
android:id="@+id/settingslib_button"
diff --git a/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_outline_extra.xml b/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_outline_extra.xml
index f8a2d8fbd975..41d8490feeb3 100644
--- a/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_outline_extra.xml
+++ b/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_outline_extra.xml
@@ -20,7 +20,8 @@
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
- android:paddingEnd="?android:attr/listPreferredItemPaddingEnd">
+ android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+ android:filterTouchesWhenObscured="true">
<com.google.android.material.button.MaterialButton
android:id="@+id/settingslib_button"
diff --git a/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_outline_large.xml b/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_outline_large.xml
index 781a5a136164..958552064c98 100644
--- a/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_outline_large.xml
+++ b/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_outline_large.xml
@@ -20,7 +20,8 @@
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
- android:paddingEnd="?android:attr/listPreferredItemPaddingEnd">
+ android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+ android:filterTouchesWhenObscured="true">
<com.google.android.material.button.MaterialButton
android:id="@+id/settingslib_button"
diff --git a/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_tonal.xml b/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_tonal.xml
index 5b568f870ea4..03ca1f0a1033 100644
--- a/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_tonal.xml
+++ b/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_tonal.xml
@@ -20,7 +20,8 @@
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
- android:paddingEnd="?android:attr/listPreferredItemPaddingEnd">
+ android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+ android:filterTouchesWhenObscured="true">
<com.google.android.material.button.MaterialButton
android:id="@+id/settingslib_button"
diff --git a/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_tonal_extra.xml b/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_tonal_extra.xml
index 1e7a08b714f1..030ee66fef3f 100644
--- a/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_tonal_extra.xml
+++ b/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_tonal_extra.xml
@@ -20,7 +20,8 @@
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
- android:paddingEnd="?android:attr/listPreferredItemPaddingEnd">
+ android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+ android:filterTouchesWhenObscured="true">
<com.google.android.material.button.MaterialButton
android:id="@+id/settingslib_button"
diff --git a/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_tonal_large.xml b/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_tonal_large.xml
index 42116be07041..5c16723f7a63 100644
--- a/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_tonal_large.xml
+++ b/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_tonal_large.xml
@@ -20,7 +20,8 @@
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
- android:paddingEnd="?android:attr/listPreferredItemPaddingEnd">
+ android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+ android:filterTouchesWhenObscured="true">
<com.google.android.material.button.MaterialButton
android:id="@+id/settingslib_button"
diff --git a/packages/SettingsLib/ButtonPreference/res/layout/settingslib_button_layout.xml b/packages/SettingsLib/ButtonPreference/res/layout/settingslib_button_layout.xml
index 1ff09901ffaf..5405045a013d 100644
--- a/packages/SettingsLib/ButtonPreference/res/layout/settingslib_button_layout.xml
+++ b/packages/SettingsLib/ButtonPreference/res/layout/settingslib_button_layout.xml
@@ -20,7 +20,8 @@
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
- android:paddingEnd="?android:attr/listPreferredItemPaddingEnd">
+ android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+ android:filterTouchesWhenObscured="true">
<Button
android:id="@+id/settingslib_button"
diff --git a/packages/SettingsLib/ButtonPreference/res/layout/settingslib_number_button.xml b/packages/SettingsLib/ButtonPreference/res/layout/settingslib_number_button.xml
index fa13b4125065..b23c5a510745 100644
--- a/packages/SettingsLib/ButtonPreference/res/layout/settingslib_number_button.xml
+++ b/packages/SettingsLib/ButtonPreference/res/layout/settingslib_number_button.xml
@@ -29,7 +29,8 @@
android:layout_height="wrap_content"
android:paddingVertical="@dimen/settingslib_expressive_space_small1"
android:paddingHorizontal="@dimen/settingslib_expressive_space_small4"
- android:background="@drawable/settingslib_number_button_background">
+ android:background="@drawable/settingslib_number_button_background"
+ android:filterTouchesWhenObscured="true">
<TextView
android:id="@+id/settingslib_number_count"
android:layout_width="wrap_content"
diff --git a/packages/SettingsLib/ButtonPreference/res/layout/settingslib_section_button.xml b/packages/SettingsLib/ButtonPreference/res/layout/settingslib_section_button.xml
index e7fb572d06dc..66a4c2e25c77 100644
--- a/packages/SettingsLib/ButtonPreference/res/layout/settingslib_section_button.xml
+++ b/packages/SettingsLib/ButtonPreference/res/layout/settingslib_section_button.xml
@@ -21,7 +21,8 @@
android:orientation="vertical"
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
- android:paddingVertical="@dimen/settingslib_expressive_space_extrasmall4">
+ android:paddingVertical="@dimen/settingslib_expressive_space_extrasmall4"
+ android:filterTouchesWhenObscured="true">
<com.google.android.material.button.MaterialButton
android:id="@+id/settingslib_section_button"
diff --git a/packages/SettingsLib/DisplayUtils/Android.bp b/packages/SettingsLib/DisplayUtils/Android.bp
index 279bb70d81bf..62630b5a9331 100644
--- a/packages/SettingsLib/DisplayUtils/Android.bp
+++ b/packages/SettingsLib/DisplayUtils/Android.bp
@@ -15,6 +15,4 @@ android_library {
],
srcs: ["src/**/*.java"],
-
- min_sdk_version: "21",
}
diff --git a/packages/SettingsLib/DisplayUtils/src/com/android/settingslib/display/DisplayDensityConfiguration.java b/packages/SettingsLib/DisplayUtils/src/com/android/settingslib/display/DisplayDensityConfiguration.java
index 284a9025de64..127e628fbd2f 100644
--- a/packages/SettingsLib/DisplayUtils/src/com/android/settingslib/display/DisplayDensityConfiguration.java
+++ b/packages/SettingsLib/DisplayUtils/src/com/android/settingslib/display/DisplayDensityConfiguration.java
@@ -16,13 +16,20 @@
package com.android.settingslib.display;
+import android.annotation.NonNull;
+import android.content.Context;
+import android.hardware.display.DisplayManager;
import android.os.AsyncTask;
import android.os.RemoteException;
import android.os.UserHandle;
import android.util.Log;
+import android.view.Display;
+import android.view.DisplayInfo;
import android.view.IWindowManager;
import android.view.WindowManagerGlobal;
+import java.util.function.Predicate;
+
/** Utility methods for controlling the display density. */
public class DisplayDensityConfiguration {
private static final String LOG_TAG = "DisplayDensityConfig";
@@ -85,4 +92,42 @@ public class DisplayDensityConfiguration {
}
});
}
+
+ /**
+ * Asynchronously applies display density changes to all displays that satisfy the predicate.
+ *
+ * <p>The change will be applied to the user specified by the value of
+ * {@link UserHandle#myUserId()} at the time the method is called.
+ *
+ * @param context The context
+ * @param predicate Determines which displays to set the density to
+ * @param density The density to force
+ */
+ public static void setForcedDisplayDensity(@NonNull Context context,
+ @NonNull Predicate<DisplayInfo> predicate, final int density) {
+ final int userId = UserHandle.myUserId();
+ DisplayManager dm = context.getSystemService(DisplayManager.class);
+ AsyncTask.execute(() -> {
+ try {
+ for (Display display : dm.getDisplays(
+ DisplayManager.DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED)) {
+ int displayId = display.getDisplayId();
+ DisplayInfo info = new DisplayInfo();
+ if (!display.getDisplayInfo(info)) {
+ Log.w(LOG_TAG, "Unable to save forced display density setting "
+ + "for display " + displayId);
+ continue;
+ }
+ if (!predicate.test(info)) {
+ continue;
+ }
+
+ final IWindowManager wm = WindowManagerGlobal.getWindowManagerService();
+ wm.setForcedDisplayDensityForUser(displayId, density, userId);
+ }
+ } catch (RemoteException exc) {
+ Log.w(LOG_TAG, "Unable to save forced display density setting");
+ }
+ });
+ }
}
diff --git a/packages/SettingsLib/Graph/graph.proto b/packages/SettingsLib/Graph/graph.proto
index 33a7df4c6ba8..a834947144a0 100644
--- a/packages/SettingsLib/Graph/graph.proto
+++ b/packages/SettingsLib/Graph/graph.proto
@@ -26,6 +26,14 @@ message PreferenceScreenProto {
optional PreferenceGroupProto root = 2;
// If the preference screen provides complete hierarchy by source code.
optional bool complete_hierarchy = 3;
+ // Parameterized screens (not recursive, provided on the top level only)
+ repeated ParameterizedPreferenceScreenProto parameterized_screens = 4;
+}
+
+// Proto of parameterized preference screen
+message ParameterizedPreferenceScreenProto {
+ optional BundleProto args = 1;
+ optional PreferenceScreenProto screen = 2;
}
// Proto of PreferenceGroup.
diff --git a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/GetPreferenceGraphApiHandler.kt b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/GetPreferenceGraphApiHandler.kt
index adffd206d552..27ce1c7246e6 100644
--- a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/GetPreferenceGraphApiHandler.kt
+++ b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/GetPreferenceGraphApiHandler.kt
@@ -18,12 +18,14 @@ package com.android.settingslib.graph
import android.app.Application
import android.os.Bundle
+import android.os.Parcelable
import android.os.SystemClock
import com.android.settingslib.graph.proto.PreferenceGraphProto
import com.android.settingslib.ipc.ApiHandler
import com.android.settingslib.ipc.ApiPermissionChecker
import com.android.settingslib.ipc.MessageCodec
import com.android.settingslib.metadata.PreferenceRemoteOpMetricsLogger
+import com.android.settingslib.metadata.PreferenceScreenCoordinate
import com.android.settingslib.metadata.PreferenceScreenRegistry
import com.android.settingslib.preference.PreferenceScreenProvider
import java.util.Locale
@@ -59,10 +61,9 @@ class GetPreferenceGraphApiHandler(
var success = false
try {
val builder = PreferenceGraphBuilder.of(application, callingPid, callingUid, request)
- if (request.screenKeys.isEmpty()) {
- PreferenceScreenRegistry.preferenceScreenMetadataFactories.forEachKeyAsync {
- builder.addPreferenceScreenFromRegistry(it)
- }
+ if (request.screens.isEmpty()) {
+ val factories = PreferenceScreenRegistry.preferenceScreenMetadataFactories
+ factories.forEachAsync { _, factory -> builder.addPreferenceScreen(factory) }
for (provider in preferenceScreenProviders) {
builder.addPreferenceScreenProvider(provider)
}
@@ -84,15 +85,15 @@ class GetPreferenceGraphApiHandler(
/**
* Request of [GetPreferenceGraphApiHandler].
*
- * @param screenKeys screen keys of the preference graph
- * @param visitedScreens keys of the visited preference screen
+ * @param screens screens of the preference graph
+ * @param visitedScreens visited preference screens
* @param locale locale of the preference graph
*/
data class GetPreferenceGraphRequest
@JvmOverloads
constructor(
- val screenKeys: Set<String> = setOf(),
- val visitedScreens: Set<String> = setOf(),
+ val screens: Set<PreferenceScreenCoordinate> = setOf(),
+ val visitedScreens: Set<PreferenceScreenCoordinate> = setOf(),
val locale: Locale? = null,
val flags: Int = PreferenceGetterFlags.ALL,
val includeValueDescriptor: Boolean = true,
@@ -101,26 +102,32 @@ constructor(
object GetPreferenceGraphRequestCodec : MessageCodec<GetPreferenceGraphRequest> {
override fun encode(data: GetPreferenceGraphRequest): Bundle =
Bundle(4).apply {
- putStringArray(KEY_SCREEN_KEYS, data.screenKeys.toTypedArray())
- putStringArray(KEY_VISITED_KEYS, data.visitedScreens.toTypedArray())
+ putParcelableArray(KEY_SCREENS, data.screens.toTypedArray())
+ putParcelableArray(KEY_VISITED_SCREENS, data.visitedScreens.toTypedArray())
putString(KEY_LOCALE, data.locale?.toLanguageTag())
putInt(KEY_FLAGS, data.flags)
}
+ @Suppress("DEPRECATION")
override fun decode(data: Bundle): GetPreferenceGraphRequest {
- val screenKeys = data.getStringArray(KEY_SCREEN_KEYS) ?: arrayOf()
- val visitedScreens = data.getStringArray(KEY_VISITED_KEYS) ?: arrayOf()
+ data.classLoader = PreferenceScreenCoordinate::class.java.classLoader
+ val screens = data.getParcelableArray(KEY_SCREENS) ?: arrayOf()
+ val visitedScreens = data.getParcelableArray(KEY_VISITED_SCREENS) ?: arrayOf()
fun String?.toLocale() = if (this != null) Locale.forLanguageTag(this) else null
+ fun Array<Parcelable>.toScreenCoordinates() =
+ buildSet(size) {
+ for (element in this@toScreenCoordinates) add(element as PreferenceScreenCoordinate)
+ }
return GetPreferenceGraphRequest(
- screenKeys.toSet(),
- visitedScreens.toSet(),
+ screens.toScreenCoordinates(),
+ visitedScreens.toScreenCoordinates(),
data.getString(KEY_LOCALE).toLocale(),
data.getInt(KEY_FLAGS),
)
}
- private const val KEY_SCREEN_KEYS = "k"
- private const val KEY_VISITED_KEYS = "v"
+ private const val KEY_SCREENS = "s"
+ private const val KEY_VISITED_SCREENS = "v"
private const val KEY_LOCALE = "l"
private const val KEY_FLAGS = "f"
}
diff --git a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGetterApi.kt b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGetterApi.kt
index a9958b975fc6..1d4e2c9e1bef 100644
--- a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGetterApi.kt
+++ b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGetterApi.kt
@@ -26,6 +26,7 @@ import com.android.settingslib.ipc.ApiPermissionChecker
import com.android.settingslib.metadata.PreferenceCoordinate
import com.android.settingslib.metadata.PreferenceHierarchyNode
import com.android.settingslib.metadata.PreferenceRemoteOpMetricsLogger
+import com.android.settingslib.metadata.PreferenceScreenCoordinate
import com.android.settingslib.metadata.PreferenceScreenRegistry
/**
@@ -105,8 +106,10 @@ class PreferenceGetterApiHandler(
val errors = mutableMapOf<PreferenceCoordinate, Int>()
val preferences = mutableMapOf<PreferenceCoordinate, PreferenceProto>()
val flags = request.flags
- for ((screenKey, coordinates) in request.preferences.groupBy { it.screenKey }) {
- val screenMetadata = PreferenceScreenRegistry.create(application, screenKey)
+ val groups =
+ request.preferences.groupBy { PreferenceScreenCoordinate(it.screenKey, it.args) }
+ for ((screen, coordinates) in groups) {
+ val screenMetadata = PreferenceScreenRegistry.create(application, screen)
if (screenMetadata == null) {
val latencyMs = SystemClock.elapsedRealtime() - elapsedRealtime
for (coordinate in coordinates) {
diff --git a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGraphBuilder.kt b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGraphBuilder.kt
index c0d244989044..4290437b0d02 100644
--- a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGraphBuilder.kt
+++ b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGraphBuilder.kt
@@ -40,6 +40,7 @@ import com.android.settingslib.graph.proto.PreferenceProto
import com.android.settingslib.graph.proto.PreferenceProto.ActionTarget
import com.android.settingslib.graph.proto.PreferenceScreenProto
import com.android.settingslib.graph.proto.TextProto
+import com.android.settingslib.metadata.EXTRA_BINDING_SCREEN_ARGS
import com.android.settingslib.metadata.IntRangeValuePreference
import com.android.settingslib.metadata.PersistentPreference
import com.android.settingslib.metadata.PreferenceAvailabilityProvider
@@ -47,7 +48,10 @@ import com.android.settingslib.metadata.PreferenceHierarchy
import com.android.settingslib.metadata.PreferenceMetadata
import com.android.settingslib.metadata.PreferenceRestrictionProvider
import com.android.settingslib.metadata.PreferenceScreenBindingKeyProvider
+import com.android.settingslib.metadata.PreferenceScreenCoordinate
import com.android.settingslib.metadata.PreferenceScreenMetadata
+import com.android.settingslib.metadata.PreferenceScreenMetadataFactory
+import com.android.settingslib.metadata.PreferenceScreenMetadataParameterizedFactory
import com.android.settingslib.metadata.PreferenceScreenRegistry
import com.android.settingslib.metadata.PreferenceSummaryProvider
import com.android.settingslib.metadata.PreferenceTitleProvider
@@ -72,15 +76,19 @@ private constructor(
PreferenceScreenFactory(context.ofLocale(request.locale))
}
private val builder by lazy { PreferenceGraphProto.newBuilder() }
- private val visitedScreens = mutableSetOf<String>().apply { addAll(request.visitedScreens) }
+ private val visitedScreens = request.visitedScreens.toMutableSet()
+ private val screens = mutableMapOf<String, PreferenceScreenProto.Builder>()
private suspend fun init() {
- for (key in request.screenKeys) {
- addPreferenceScreenFromRegistry(key)
+ for (screen in request.screens) {
+ PreferenceScreenRegistry.create(context, screen)?.let { addPreferenceScreen(it) }
}
}
- fun build(): PreferenceGraphProto = builder.build()
+ fun build(): PreferenceGraphProto {
+ for ((key, screenBuilder) in screens) builder.putScreens(key, screenBuilder.build())
+ return builder.build()
+ }
/**
* Adds an activity to the graph.
@@ -138,19 +146,12 @@ private constructor(
null
}
- suspend fun addPreferenceScreenFromRegistry(key: String): Boolean {
- val metadata = PreferenceScreenRegistry.create(context, key) ?: return false
- return addPreferenceScreenMetadata(metadata)
+ private suspend fun addPreferenceScreenFromRegistry(key: String): Boolean {
+ val factory =
+ PreferenceScreenRegistry.preferenceScreenMetadataFactories[key] ?: return false
+ return addPreferenceScreen(factory)
}
- private suspend fun addPreferenceScreenMetadata(metadata: PreferenceScreenMetadata): Boolean =
- addPreferenceScreen(metadata.key) {
- preferenceScreenProto {
- completeHierarchy = metadata.hasCompleteHierarchy()
- root = metadata.getPreferenceHierarchy(context).toProto(metadata, true)
- }
- }
-
suspend fun addPreferenceScreenProvider(activityClass: Class<*>) {
Log.d(TAG, "add $activityClass")
createPreferenceScreen { activityClass.newInstance() }
@@ -188,26 +189,52 @@ private constructor(
Log.e(TAG, "\"$preferenceScreen\" has no key")
return
}
- @Suppress("CheckReturnValue") addPreferenceScreen(key) { preferenceScreen.toProto(intent) }
+ val args = preferenceScreen.peekExtras()?.getBundle(EXTRA_BINDING_SCREEN_ARGS)
+ @Suppress("CheckReturnValue")
+ addPreferenceScreen(key, args) {
+ this.intent = intent.toProto()
+ root = preferenceScreen.toProto()
+ }
+ }
+
+ suspend fun addPreferenceScreen(factory: PreferenceScreenMetadataFactory): Boolean {
+ if (factory is PreferenceScreenMetadataParameterizedFactory) {
+ factory.parameters(context).collect { addPreferenceScreen(factory.create(context, it)) }
+ return true
+ }
+ return addPreferenceScreen(factory.create(context))
}
+ private suspend fun addPreferenceScreen(metadata: PreferenceScreenMetadata): Boolean =
+ addPreferenceScreen(metadata.key, metadata.arguments) {
+ completeHierarchy = metadata.hasCompleteHierarchy()
+ root = metadata.getPreferenceHierarchy(context).toProto(metadata, true)
+ }
+
private suspend fun addPreferenceScreen(
key: String,
- preferenceScreenProvider: suspend () -> PreferenceScreenProto,
- ): Boolean =
- if (visitedScreens.add(key)) {
- builder.putScreens(key, preferenceScreenProvider())
- true
- } else {
- Log.w(TAG, "$key visited")
- false
+ args: Bundle?,
+ init: suspend PreferenceScreenProto.Builder.() -> Unit,
+ ): Boolean {
+ if (!visitedScreens.add(PreferenceScreenCoordinate(key, args))) {
+ Log.w(TAG, "$key $args visited")
+ return false
}
-
- private suspend fun PreferenceScreen.toProto(intent: Intent?): PreferenceScreenProto =
- preferenceScreenProto {
- intent?.let { this.intent = it.toProto() }
- root = (this@toProto as PreferenceGroup).toProto()
+ if (args == null) { // normal screen
+ screens[key] = PreferenceScreenProto.newBuilder().also { init(it) }
+ } else if (args.isEmpty) { // parameterized screen with backward compatibility
+ val builder = screens.getOrPut(key) { PreferenceScreenProto.newBuilder() }
+ init(builder)
+ } else { // parameterized screen with non-empty arguments
+ val builder = screens.getOrPut(key) { PreferenceScreenProto.newBuilder() }
+ val parameterizedScreen = parameterizedPreferenceScreenProto {
+ setArgs(args.toProto())
+ setScreen(PreferenceScreenProto.newBuilder().also { init(it) })
+ }
+ builder.addParameterizedScreens(parameterizedScreen)
}
+ return true
+ }
private suspend fun PreferenceGroup.toProto(): PreferenceGroupProto = preferenceGroupProto {
preference = (this@toProto as Preference).toProto()
@@ -271,7 +298,7 @@ private constructor(
.toProto(context, callingPid, callingUid, screenMetadata, isRoot, request.flags)
.also {
if (metadata is PreferenceScreenMetadata) {
- @Suppress("CheckReturnValue") addPreferenceScreenMetadata(metadata)
+ @Suppress("CheckReturnValue") addPreferenceScreen(metadata)
}
metadata.intent(context)?.resolveActivity(context.packageManager)?.let {
if (it.packageName == context.packageName) {
@@ -322,7 +349,7 @@ private constructor(
val screenKey = screen?.key
if (!screenKey.isNullOrEmpty()) {
@Suppress("CheckReturnValue")
- addPreferenceScreen(screenKey) { screen.toProto(null) }
+ addPreferenceScreen(screenKey, null) { root = screen.toProto() }
return actionTargetProto { key = screenKey }
}
} catch (e: Exception) {
diff --git a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceSetterApi.kt b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceSetterApi.kt
index a595f42a573d..60f9c6bb92a3 100644
--- a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceSetterApi.kt
+++ b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceSetterApi.kt
@@ -40,11 +40,12 @@ import com.android.settingslib.metadata.SensitivityLevel.Companion.HIGH_SENSITIV
import com.android.settingslib.metadata.SensitivityLevel.Companion.UNKNOWN_SENSITIVITY
/** Request to set preference value. */
-data class PreferenceSetterRequest(
- val screenKey: String,
- val key: String,
+class PreferenceSetterRequest(
+ screenKey: String,
+ args: Bundle?,
+ key: String,
val value: PreferenceValueProto,
-)
+) : PreferenceCoordinate(screenKey, args, key)
/** Result of preference setter request. */
@IntDef(
@@ -121,7 +122,7 @@ class PreferenceSetterApiHandler(
metricsLogger?.logSetterApi(
application,
callingUid,
- PreferenceCoordinate(request.screenKey, request.key),
+ request,
null,
null,
PreferenceSetterResult.UNSUPPORTED,
@@ -130,7 +131,7 @@ class PreferenceSetterApiHandler(
return PreferenceSetterResult.UNSUPPORTED
}
val screenMetadata =
- PreferenceScreenRegistry.create(application, request.screenKey) ?: return notFound()
+ PreferenceScreenRegistry.create(application, request) ?: return notFound()
val key = request.key
val metadata =
screenMetadata.getPreferenceHierarchy(application).find(key) ?: return notFound()
@@ -199,7 +200,7 @@ class PreferenceSetterApiHandler(
metricsLogger?.logSetterApi(
application,
callingUid,
- PreferenceCoordinate(request.screenKey, request.key),
+ request,
screenMetadata,
metadata,
result,
@@ -235,6 +236,7 @@ object PreferenceSetterRequestCodec : MessageCodec<PreferenceSetterRequest> {
override fun encode(data: PreferenceSetterRequest) =
Bundle(3).apply {
putString(SCREEN_KEY, data.screenKey)
+ putBundle(ARGS, data.args)
putString(KEY, data.key)
putByteArray(null, data.value.toByteArray())
}
@@ -242,10 +244,12 @@ object PreferenceSetterRequestCodec : MessageCodec<PreferenceSetterRequest> {
override fun decode(data: Bundle) =
PreferenceSetterRequest(
data.getString(SCREEN_KEY)!!,
+ data.getBundle(ARGS),
data.getString(KEY)!!,
PreferenceValueProto.parseFrom(data.getByteArray(null)!!),
)
private const val SCREEN_KEY = "s"
private const val KEY = "k"
+ private const val ARGS = "a"
}
diff --git a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/ProtoDsl.kt b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/ProtoDsl.kt
index adbe77318353..5f2a0d826407 100644
--- a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/ProtoDsl.kt
+++ b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/ProtoDsl.kt
@@ -19,6 +19,7 @@ package com.android.settingslib.graph
import com.android.settingslib.graph.proto.BundleProto
import com.android.settingslib.graph.proto.BundleProto.BundleValue
import com.android.settingslib.graph.proto.IntentProto
+import com.android.settingslib.graph.proto.ParameterizedPreferenceScreenProto
import com.android.settingslib.graph.proto.PreferenceGroupProto
import com.android.settingslib.graph.proto.PreferenceOrGroupProto
import com.android.settingslib.graph.proto.PreferenceProto
@@ -39,6 +40,12 @@ inline fun preferenceScreenProto(
init: PreferenceScreenProto.Builder.() -> Unit
): PreferenceScreenProto = PreferenceScreenProto.newBuilder().also(init).build()
+/** Kotlin DSL-style builder for [PreferenceScreenProto]. */
+inline fun parameterizedPreferenceScreenProto(
+ init: ParameterizedPreferenceScreenProto.Builder.() -> Unit
+): ParameterizedPreferenceScreenProto =
+ ParameterizedPreferenceScreenProto.newBuilder().also(init).build()
+
/** Returns preference or null. */
val PreferenceOrGroupProto.preferenceOrNull
get() = if (hasPreference()) preference else null
diff --git a/packages/SettingsLib/IntroPreference/res/layout/settingslib_expressive_preference_intro.xml b/packages/SettingsLib/IntroPreference/res/layout/settingslib_expressive_preference_intro.xml
index 43cf6aa09109..7adcbf6c6601 100644
--- a/packages/SettingsLib/IntroPreference/res/layout/settingslib_expressive_preference_intro.xml
+++ b/packages/SettingsLib/IntroPreference/res/layout/settingslib_expressive_preference_intro.xml
@@ -18,6 +18,8 @@
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/entity_header"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
style="@style/SettingsLibEntityHeader">
<LinearLayout
diff --git a/packages/SettingsLib/Metadata/processor/src/com/android/settingslib/metadata/PreferenceScreenAnnotationProcessor.kt b/packages/SettingsLib/Metadata/processor/src/com/android/settingslib/metadata/PreferenceScreenAnnotationProcessor.kt
index 38b641336547..69b75adea9d3 100644
--- a/packages/SettingsLib/Metadata/processor/src/com/android/settingslib/metadata/PreferenceScreenAnnotationProcessor.kt
+++ b/packages/SettingsLib/Metadata/processor/src/com/android/settingslib/metadata/PreferenceScreenAnnotationProcessor.kt
@@ -33,6 +33,9 @@ import javax.tools.Diagnostic
/** Processor to gather preference screens annotated with `@ProvidePreferenceScreen`. */
class PreferenceScreenAnnotationProcessor : AbstractProcessor() {
private val screens = mutableListOf<Screen>()
+ private val bundleType: TypeMirror by lazy {
+ processingEnv.elementUtils.getTypeElement("android.os.Bundle").asType()
+ }
private val contextType: TypeMirror by lazy {
processingEnv.elementUtils.getTypeElement("android.content.Context").asType()
}
@@ -83,19 +86,57 @@ class PreferenceScreenAnnotationProcessor : AbstractProcessor() {
error("@$ANNOTATION_NAME must be added to $PREFERENCE_SCREEN_METADATA subclass", this)
return
}
- val constructorType = getConstructorType()
- if (constructorType == null) {
+ fun reportConstructorError() =
error(
- "Class must be an object, or has single public constructor that " +
- "accepts no parameter or a Context parameter",
+ "Must have only one public constructor: constructor(), " +
+ "constructor(Context), constructor(Bundle) or constructor(Context, Bundle)",
this,
)
+ val constructor = findConstructor()
+ if (constructor == null || constructor.parameters.size > 2) {
+ reportConstructorError()
return
}
+ val constructorHasContextParameter = constructor.hasParameter(0, contextType)
+ var index = if (constructorHasContextParameter) 1 else 0
val annotation = annotationMirrors.single { it.isElement(annotationElement) }
val key = annotation.fieldValue<String>("value")!!
val overlay = annotation.fieldValue<Boolean>("overlay") == true
- screens.add(Screen(key, overlay, qualifiedName.toString(), constructorType))
+ val parameterized = annotation.fieldValue<Boolean>("parameterized") == true
+ var parametersHasContextParameter = false
+ if (parameterized) {
+ val parameters = findParameters()
+ if (parameters == null) {
+ error("require a static 'parameters()' or 'parameters(Context)' method", this)
+ return
+ }
+ parametersHasContextParameter = parameters
+ if (constructor.hasParameter(index, bundleType)) {
+ index++
+ } else {
+ error(
+ "Parameterized screen constructor must be" +
+ "constructor(Bundle) or constructor(Context, Bundle)",
+ this,
+ )
+ return
+ }
+ }
+ if (index == constructor.parameters.size) {
+ screens.add(
+ Screen(
+ key,
+ overlay,
+ parameterized,
+ annotation.fieldValue<Boolean>("parameterizedMigration") == true,
+ qualifiedName.toString(),
+ constructorHasContextParameter,
+ parametersHasContextParameter,
+ )
+ )
+ } else {
+ reportConstructorError()
+ }
}
private fun codegen() {
@@ -116,10 +157,15 @@ class PreferenceScreenAnnotationProcessor : AbstractProcessor() {
screens.sort()
processingEnv.filer.createSourceFile("$outputPkg.$outputClass").openWriter().use {
it.write("package $outputPkg;\n\n")
+ it.write("import android.content.Context;\n")
+ it.write("import android.os.Bundle;\n")
it.write("import $PACKAGE.FixedArrayMap;\n")
it.write("import $PACKAGE.FixedArrayMap.OrderedInitializer;\n")
- it.write("import $PACKAGE.$FACTORY;\n\n")
- it.write("// Generated by annotation processor for @$ANNOTATION_NAME\n")
+ it.write("import $PACKAGE.$PREFERENCE_SCREEN_METADATA;\n")
+ it.write("import $PACKAGE.$FACTORY;\n")
+ it.write("import $PACKAGE.$PARAMETERIZED_FACTORY;\n")
+ it.write("import kotlinx.coroutines.flow.Flow;\n")
+ it.write("\n// Generated by annotation processor for @$ANNOTATION_NAME\n")
it.write("public final class $outputClass {\n")
it.write(" private $outputClass() {}\n\n")
it.write(" public static FixedArrayMap<String, $FACTORY> $outputFun() {\n")
@@ -127,10 +173,29 @@ class PreferenceScreenAnnotationProcessor : AbstractProcessor() {
it.write(" return new FixedArrayMap<>($size, $outputClass::init);\n")
it.write(" }\n\n")
fun Screen.write() {
- it.write(" screens.put(\"$key\", context -> new $klass(")
- when (constructorType) {
- ConstructorType.DEFAULT -> it.write("));")
- ConstructorType.CONTEXT -> it.write("context));")
+ it.write(" screens.put(\"$key\", ")
+ if (parameterized) {
+ it.write("new $PARAMETERIZED_FACTORY() {\n")
+ it.write(" @Override public PreferenceScreenMetadata create")
+ it.write("(Context context, Bundle args) {\n")
+ it.write(" return new $klass(")
+ if (constructorHasContextParameter) it.write("context, ")
+ it.write("args);\n")
+ it.write(" }\n\n")
+ it.write(" @Override public Flow<Bundle> parameters(Context context) {\n")
+ it.write(" return $klass.parameters(")
+ if (parametersHasContextParameter) it.write("context")
+ it.write(");\n")
+ it.write(" }\n")
+ if (parameterizedMigration) {
+ it.write("\n @Override public boolean acceptEmptyArguments()")
+ it.write(" { return true; }\n")
+ }
+ it.write(" });")
+ } else {
+ it.write("context -> new $klass(")
+ if (constructorHasContextParameter) it.write("context")
+ it.write("));")
}
if (overlay) it.write(" // overlay")
it.write("\n")
@@ -159,7 +224,7 @@ class PreferenceScreenAnnotationProcessor : AbstractProcessor() {
}
private fun AnnotationMirror.isElement(element: TypeElement) =
- processingEnv.typeUtils.isSameType(annotationType.asElement().asType(), element.asType())
+ annotationType.asElement().asType().isSameType(element.asType())
@Suppress("UNCHECKED_CAST")
private fun <T> AnnotationMirror.fieldValue(name: String): T? = field(name)?.value as? T
@@ -171,7 +236,7 @@ class PreferenceScreenAnnotationProcessor : AbstractProcessor() {
return null
}
- private fun TypeElement.getConstructorType(): ConstructorType? {
+ private fun TypeElement.findConstructor(): ExecutableElement? {
var constructor: ExecutableElement? = null
for (element in enclosedElements) {
if (element.kind != ElementKind.CONSTRUCTOR) continue
@@ -179,16 +244,30 @@ class PreferenceScreenAnnotationProcessor : AbstractProcessor() {
if (constructor != null) return null
constructor = element as ExecutableElement
}
- return constructor?.parameters?.run {
- when {
- isEmpty() -> ConstructorType.DEFAULT
- size == 1 && processingEnv.typeUtils.isSameType(this[0].asType(), contextType) ->
- ConstructorType.CONTEXT
- else -> null
- }
+ return constructor
+ }
+
+ private fun TypeElement.findParameters(): Boolean? {
+ for (element in enclosedElements) {
+ if (element.kind != ElementKind.METHOD) continue
+ if (!element.modifiers.contains(Modifier.PUBLIC)) continue
+ if (!element.modifiers.contains(Modifier.STATIC)) continue
+ if (!element.simpleName.contentEquals("parameters")) return null
+ val parameters = (element as ExecutableElement).parameters
+ if (parameters.isEmpty()) return false
+ if (parameters.size == 1 && parameters[0].asType().isSameType(contextType)) return true
+ error("parameters method should have no parameter or a Context parameter", element)
+ return null
}
+ return null
}
+ private fun ExecutableElement.hasParameter(index: Int, typeMirror: TypeMirror) =
+ index < parameters.size && parameters[index].asType().isSameType(typeMirror)
+
+ private fun TypeMirror.isSameType(typeMirror: TypeMirror) =
+ processingEnv.typeUtils.isSameType(this, typeMirror)
+
private fun warn(msg: CharSequence) =
processingEnv.messager.printMessage(Diagnostic.Kind.WARNING, msg)
@@ -198,8 +277,11 @@ class PreferenceScreenAnnotationProcessor : AbstractProcessor() {
private data class Screen(
val key: String,
val overlay: Boolean,
+ val parameterized: Boolean,
+ val parameterizedMigration: Boolean,
val klass: String,
- val constructorType: ConstructorType,
+ val constructorHasContextParameter: Boolean,
+ val parametersHasContextParameter: Boolean,
) : Comparable<Screen> {
override fun compareTo(other: Screen): Int {
val diff = key.compareTo(other.key)
@@ -207,17 +289,13 @@ class PreferenceScreenAnnotationProcessor : AbstractProcessor() {
}
}
- private enum class ConstructorType {
- DEFAULT, // default constructor with no parameter
- CONTEXT, // constructor with a Context parameter
- }
-
companion object {
private const val PACKAGE = "com.android.settingslib.metadata"
private const val ANNOTATION_NAME = "ProvidePreferenceScreen"
private const val ANNOTATION = "$PACKAGE.$ANNOTATION_NAME"
private const val PREFERENCE_SCREEN_METADATA = "PreferenceScreenMetadata"
private const val FACTORY = "PreferenceScreenMetadataFactory"
+ private const val PARAMETERIZED_FACTORY = "PreferenceScreenMetadataParameterizedFactory"
private const val OPTIONS_NAME = "ProvidePreferenceScreenOptions"
private const val OPTIONS = "$PACKAGE.$OPTIONS_NAME"
diff --git a/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/Annotations.kt b/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/Annotations.kt
index 4bed795ea760..449c78ce8965 100644
--- a/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/Annotations.kt
+++ b/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/Annotations.kt
@@ -22,14 +22,27 @@ package com.android.settingslib.metadata
* The annotated class must satisfy either condition:
* - the primary constructor has no parameter
* - the primary constructor has a single [android.content.Context] parameter
+ * - (parameterized) the primary constructor has a single [android.os.Bundle] parameter to override
+ * [PreferenceScreenMetadata.arguments]
+ * - (parameterized) the primary constructor has a [android.content.Context] and a
+ * [android.os.Bundle] parameter to override [PreferenceScreenMetadata.arguments]
*
* @param value unique preference screen key
* @param overlay if true, current annotated screen will overlay the screen that has identical key
+ * @param parameterized if true, the screen relies on additional arguments to build its content
+ * @param parameterizedMigration whether the parameterized screen was a normal screen, in which case
+ * `Bundle.EMPTY` will be passed as arguments to take care of backward compatibility
+ * @see PreferenceScreenMetadata
*/
@Retention(AnnotationRetention.SOURCE)
@Target(AnnotationTarget.CLASS)
@MustBeDocumented
-annotation class ProvidePreferenceScreen(val value: String, val overlay: Boolean = false)
+annotation class ProvidePreferenceScreen(
+ val value: String,
+ val overlay: Boolean = false,
+ val parameterized: Boolean = false,
+ val parameterizedMigration: Boolean = false, // effective only when parameterized is true
+)
/**
* Provides options for [ProvidePreferenceScreen] annotation processor.
diff --git a/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/Bundles.kt b/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/Bundles.kt
new file mode 100644
index 000000000000..a63576510aec
--- /dev/null
+++ b/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/Bundles.kt
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.metadata
+
+import android.content.Intent
+import android.os.Bundle
+
+@Suppress("DEPRECATION")
+fun Bundle?.contentEquals(other: Bundle?): Boolean {
+ if (this == null) return other == null
+ if (other == null) return false
+ if (keySet() != other.keySet()) return false
+ fun Any?.valueEquals(other: Any?) =
+ when (this) {
+ is Bundle -> other is Bundle && this.contentEquals(other)
+ is Intent -> other is Intent && this.filterEquals(other)
+ is BooleanArray -> other is BooleanArray && this contentEquals other
+ is ByteArray -> other is ByteArray && this contentEquals other
+ is CharArray -> other is CharArray && this contentEquals other
+ is DoubleArray -> other is DoubleArray && this contentEquals other
+ is FloatArray -> other is FloatArray && this contentEquals other
+ is IntArray -> other is IntArray && this contentEquals other
+ is LongArray -> other is LongArray && this contentEquals other
+ is ShortArray -> other is ShortArray && this contentEquals other
+ is Array<*> -> other is Array<*> && this contentDeepEquals other
+ else -> this == other
+ }
+ for (key in keySet()) {
+ if (!get(key).valueEquals(other.get(key))) return false
+ }
+ return true
+}
diff --git a/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PersistentPreference.kt b/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PersistentPreference.kt
index 63f1050df94e..e456a7f1aa1c 100644
--- a/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PersistentPreference.kt
+++ b/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PersistentPreference.kt
@@ -78,13 +78,8 @@ annotation class SensitivityLevel {
/** Preference metadata that has a value persisted in datastore. */
interface PersistentPreference<T> : PreferenceMetadata {
- /**
- * The value type the preference is associated with.
- *
- * TODO(b/388167302): Remove the default implementation once all subclasses are migrated.
- */
- val valueType: Class<T>?
- get() = null
+ /** The value type the preference is associated with. */
+ val valueType: Class<T>
/**
* Returns the key-value storage of the preference.
diff --git a/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceCoordinate.kt b/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceCoordinate.kt
index 2dd736ae6083..ac08847b6002 100644
--- a/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceCoordinate.kt
+++ b/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceCoordinate.kt
@@ -16,26 +16,41 @@
package com.android.settingslib.metadata
+import android.os.Bundle
import android.os.Parcel
import android.os.Parcelable
/**
* Coordinate to locate a preference.
*
- * Within an app, the preference screen key (unique among screens) plus preference key (unique on
- * the screen) is used to locate a preference.
+ * Within an app, the preference screen coordinate (unique among screens) plus preference key
+ * (unique on the screen) is used to locate a preference.
*/
-data class PreferenceCoordinate(val screenKey: String, val key: String) : Parcelable {
+open class PreferenceCoordinate : PreferenceScreenCoordinate {
+ val key: String
- constructor(parcel: Parcel) : this(parcel.readString()!!, parcel.readString()!!)
+ constructor(screenKey: String, key: String) : this(screenKey, null, key)
+
+ constructor(screenKey: String, args: Bundle?, key: String) : super(screenKey, args) {
+ this.key = key
+ }
+
+ constructor(parcel: Parcel) : super(parcel) {
+ this.key = parcel.readString()!!
+ }
override fun writeToParcel(parcel: Parcel, flags: Int) {
- parcel.writeString(screenKey)
+ super.writeToParcel(parcel, flags)
parcel.writeString(key)
}
override fun describeContents() = 0
+ override fun equals(other: Any?) =
+ super.equals(other) && key == (other as PreferenceCoordinate).key
+
+ override fun hashCode() = super.hashCode() xor key.hashCode()
+
companion object CREATOR : Parcelable.Creator<PreferenceCoordinate> {
override fun createFromParcel(parcel: Parcel) = PreferenceCoordinate(parcel)
@@ -43,3 +58,46 @@ data class PreferenceCoordinate(val screenKey: String, val key: String) : Parcel
override fun newArray(size: Int) = arrayOfNulls<PreferenceCoordinate>(size)
}
}
+
+/** Coordinate to locate a preference screen. */
+open class PreferenceScreenCoordinate : Parcelable {
+ /** Unique preference screen key. */
+ val screenKey: String
+
+ /** Arguments to create parameterized preference screen. */
+ val args: Bundle?
+
+ constructor(screenKey: String, args: Bundle?) {
+ this.screenKey = screenKey
+ this.args = args
+ }
+
+ constructor(parcel: Parcel) {
+ screenKey = parcel.readString()!!
+ args = parcel.readBundle(javaClass.classLoader)
+ }
+
+ override fun writeToParcel(parcel: Parcel, flags: Int) {
+ parcel.writeString(screenKey)
+ parcel.writeBundle(args)
+ }
+
+ override fun describeContents() = 0
+
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (javaClass != other?.javaClass) return false
+ other as PreferenceScreenCoordinate
+ return screenKey == other.screenKey && args.contentEquals(other.args)
+ }
+
+ // "args" is not included intentionally, otherwise we need to take care of array, etc.
+ override fun hashCode() = screenKey.hashCode()
+
+ companion object CREATOR : Parcelable.Creator<PreferenceScreenCoordinate> {
+
+ override fun createFromParcel(parcel: Parcel) = PreferenceScreenCoordinate(parcel)
+
+ override fun newArray(size: Int) = arrayOfNulls<PreferenceScreenCoordinate>(size)
+ }
+}
diff --git a/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceHierarchy.kt b/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceHierarchy.kt
index 876f6152cccd..3bd051dee41d 100644
--- a/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceHierarchy.kt
+++ b/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceHierarchy.kt
@@ -17,6 +17,7 @@
package com.android.settingslib.metadata
import android.content.Context
+import android.os.Bundle
/** A node in preference hierarchy that is associated with [PreferenceMetadata]. */
open class PreferenceHierarchyNode internal constructor(val metadata: PreferenceMetadata) {
@@ -54,8 +55,14 @@ internal constructor(private val context: Context, metadata: PreferenceMetadata)
*
* @throws NullPointerException if screen is not registered to [PreferenceScreenRegistry]
*/
- operator fun String.unaryPlus() =
- +PreferenceHierarchyNode(PreferenceScreenRegistry.create(context, this)!!)
+ operator fun String.unaryPlus() = addPreferenceScreen(this, null)
+
+ /**
+ * Adds parameterized preference screen with given key (as a placeholder) to the hierarchy.
+ *
+ * @see String.unaryPlus
+ */
+ infix fun String.args(args: Bundle) = createPreferenceScreenHierarchy(this, args)
operator fun PreferenceHierarchyNode.unaryPlus() = also { children.add(it) }
@@ -122,6 +129,14 @@ internal constructor(private val context: Context, metadata: PreferenceMetadata)
}
/**
+ * Adds parameterized preference screen with given key (as a placeholder) to the hierarchy.
+ *
+ * @see addPreferenceScreen
+ */
+ fun addParameterizedScreen(screenKey: String, args: Bundle) =
+ addPreferenceScreen(screenKey, args)
+
+ /**
* Adds preference screen with given key (as a placeholder) to the hierarchy.
*
* This is mainly to support Android Settings overlays. OEMs might want to custom some of the
@@ -132,11 +147,13 @@ internal constructor(private val context: Context, metadata: PreferenceMetadata)
*
* @throws NullPointerException if screen is not registered to [PreferenceScreenRegistry]
*/
- fun addPreferenceScreen(screenKey: String) {
- children.add(
- PreferenceHierarchy(context, PreferenceScreenRegistry.create(context, screenKey)!!)
- )
- }
+ fun addPreferenceScreen(screenKey: String) = addPreferenceScreen(screenKey, null)
+
+ private fun addPreferenceScreen(screenKey: String, args: Bundle?): PreferenceHierarchyNode =
+ createPreferenceScreenHierarchy(screenKey, args).also { children.add(it) }
+
+ private fun createPreferenceScreenHierarchy(screenKey: String, args: Bundle?) =
+ PreferenceHierarchyNode(PreferenceScreenRegistry.create(context, screenKey, args)!!)
/** Extensions to add more preferences to the hierarchy. */
operator fun PreferenceHierarchy.plusAssign(init: PreferenceHierarchy.() -> Unit) = init(this)
diff --git a/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceScreenBindingKeyProvider.kt b/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceScreenBindingKeyProvider.kt
index 84014f191f68..4fd13ede6803 100644
--- a/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceScreenBindingKeyProvider.kt
+++ b/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceScreenBindingKeyProvider.kt
@@ -17,13 +17,20 @@
package com.android.settingslib.metadata
import android.content.Context
+import android.os.Bundle
/** Provides the associated preference screen key for binding. */
interface PreferenceScreenBindingKeyProvider {
/** Returns the associated preference screen key. */
fun getPreferenceScreenBindingKey(context: Context): String?
+
+ /** Returns the arguments to build preference screen. */
+ fun getPreferenceScreenBindingArgs(context: Context): Bundle?
}
/** Extra key to provide the preference screen key for binding. */
const val EXTRA_BINDING_SCREEN_KEY = "settingslib:binding_screen_key"
+
+/** Extra key to provide arguments for preference screen binding. */
+const val EXTRA_BINDING_SCREEN_ARGS = "settingslib:binding_screen_args"
diff --git a/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceScreenMetadata.kt b/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceScreenMetadata.kt
index 850d4523e96e..7f1ded71e30a 100644
--- a/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceScreenMetadata.kt
+++ b/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceScreenMetadata.kt
@@ -18,12 +18,25 @@ package com.android.settingslib.metadata
import android.content.Context
import android.content.Intent
+import android.os.Bundle
import androidx.annotation.AnyThread
import androidx.fragment.app.Fragment
+import kotlinx.coroutines.flow.Flow
-/** Metadata of preference screen. */
+/**
+ * Metadata of preference screen.
+ *
+ * For parameterized preference screen that relies on additional information (e.g. package name,
+ * language code) to build its content, the subclass must:
+ * - override [arguments] in constructor
+ * - add a static method `fun parameters(context: Context): List<Bundle>` (context is optional) to
+ * provide all possible arguments
+ */
@AnyThread
interface PreferenceScreenMetadata : PreferenceMetadata {
+ /** Arguments to build the screen content. */
+ val arguments: Bundle?
+ get() = null
/**
* The screen title resource, which precedes [getScreenTitle] if provided.
@@ -65,7 +78,12 @@ interface PreferenceScreenMetadata : PreferenceMetadata {
fun getLaunchIntent(context: Context, metadata: PreferenceMetadata?): Intent? = null
}
-/** Factory of [PreferenceScreenMetadata]. */
+/**
+ * Factory of [PreferenceScreenMetadata].
+ *
+ * Annotation processor generates implementation of this interface based on
+ * [ProvidePreferenceScreen] when [ProvidePreferenceScreen.parameterized] is `false`.
+ */
fun interface PreferenceScreenMetadataFactory {
/**
@@ -75,3 +93,44 @@ fun interface PreferenceScreenMetadataFactory {
*/
fun create(context: Context): PreferenceScreenMetadata
}
+
+/**
+ * Parameterized factory of [PreferenceScreenMetadata].
+ *
+ * Annotation processor generates implementation of this interface based on
+ * [ProvidePreferenceScreen] when [ProvidePreferenceScreen.parameterized] is `true`.
+ */
+interface PreferenceScreenMetadataParameterizedFactory : PreferenceScreenMetadataFactory {
+ override fun create(context: Context) = create(context, Bundle.EMPTY)
+
+ /**
+ * Creates a new [PreferenceScreenMetadata] with given arguments.
+ *
+ * @param context application context to create the PreferenceScreenMetadata
+ * @param args arguments to create the screen metadata, [Bundle.EMPTY] is reserved for the
+ * default case when screen is migrated from normal to parameterized
+ */
+ fun create(context: Context, args: Bundle): PreferenceScreenMetadata
+
+ /**
+ * Returns all possible arguments to create [PreferenceScreenMetadata].
+ *
+ * Note that [Bundle.EMPTY] is a special arguments reserved for backward compatibility when a
+ * preference screen was a normal screen but migrated to parameterized screen later:
+ * 1. Set [ProvidePreferenceScreen.parameterizedMigration] to `true`, so that the generated
+ * [acceptEmptyArguments] will be `true`.
+ * 1. In the original [parameters] implementation, produce a [Bundle.EMPTY] for the default
+ * case.
+ *
+ * Do not use [Bundle.EMPTY] for other purpose.
+ */
+ fun parameters(context: Context): Flow<Bundle>
+
+ /**
+ * Returns true when the parameterized screen was a normal screen.
+ *
+ * The [PreferenceScreenMetadata] is expected to accept an empty arguments ([Bundle.EMPTY]) and
+ * take care of backward compatibility.
+ */
+ fun acceptEmptyArguments(): Boolean = false
+}
diff --git a/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceScreenRegistry.kt b/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceScreenRegistry.kt
index c74b3151abb2..246310984db9 100644
--- a/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceScreenRegistry.kt
+++ b/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceScreenRegistry.kt
@@ -17,10 +17,13 @@
package com.android.settingslib.metadata
import android.content.Context
+import android.os.Bundle
+import android.util.Log
import com.android.settingslib.datastore.KeyValueStore
/** Registry of all available preference screens in the app. */
object PreferenceScreenRegistry : ReadWritePermitProvider {
+ private const val TAG = "ScreenRegistry"
/** Provider of key-value store. */
private lateinit var keyValueStoreProvider: KeyValueStoreProvider
@@ -52,9 +55,28 @@ object PreferenceScreenRegistry : ReadWritePermitProvider {
fun getKeyValueStore(context: Context, preference: PreferenceMetadata): KeyValueStore? =
keyValueStoreProvider.getKeyValueStore(context, preference)
- /** Creates [PreferenceScreenMetadata] of particular screen key. */
- fun create(context: Context, screenKey: String?): PreferenceScreenMetadata? =
- screenKey?.let { preferenceScreenMetadataFactories[it]?.create(context.applicationContext) }
+ /** Creates [PreferenceScreenMetadata] of particular screen. */
+ fun create(context: Context, screenCoordinate: PreferenceScreenCoordinate) =
+ create(context, screenCoordinate.screenKey, screenCoordinate.args)
+
+ /** Creates [PreferenceScreenMetadata] of particular screen key with given arguments. */
+ fun create(context: Context, screenKey: String?, args: Bundle?): PreferenceScreenMetadata? {
+ if (screenKey == null) return null
+ val factory = preferenceScreenMetadataFactories[screenKey] ?: return null
+ val appContext = context.applicationContext
+ if (factory is PreferenceScreenMetadataParameterizedFactory) {
+ if (args != null) return factory.create(appContext, args)
+ // In case the parameterized screen was a normal scree, it is expected to accept
+ // Bundle.EMPTY arguments and take care of backward compatibility.
+ if (factory.acceptEmptyArguments()) return factory.create(appContext)
+ Log.e(TAG, "screen $screenKey is parameterized but args is not provided")
+ return null
+ } else {
+ if (args == null) return factory.create(appContext)
+ Log.e(TAG, "screen $screenKey is not parameterized but args is provided")
+ return null
+ }
+ }
/**
* Sets the provider to check read write permit. Read and write requests are denied by default.
diff --git a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBindings.kt b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBindings.kt
index 65fbe2b66e77..dbac17d4e8b8 100644
--- a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBindings.kt
+++ b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBindings.kt
@@ -22,6 +22,7 @@ import androidx.preference.PreferenceCategory
import androidx.preference.PreferenceScreen
import androidx.preference.SwitchPreferenceCompat
import androidx.preference.TwoStatePreference
+import com.android.settingslib.metadata.EXTRA_BINDING_SCREEN_ARGS
import com.android.settingslib.metadata.EXTRA_BINDING_SCREEN_KEY
import com.android.settingslib.metadata.PreferenceMetadata
import com.android.settingslib.metadata.PreferenceScreenMetadata
@@ -35,9 +36,11 @@ interface PreferenceScreenBinding : PreferenceBinding {
super.bind(preference, metadata)
val context = preference.context
val screenMetadata = metadata as PreferenceScreenMetadata
+ val extras = preference.extras
// Pass the preference key to fragment, so that the fragment could find associated
// preference screen registered in PreferenceScreenRegistry
- preference.extras.putString(EXTRA_BINDING_SCREEN_KEY, preference.key)
+ extras.putString(EXTRA_BINDING_SCREEN_KEY, preference.key)
+ screenMetadata.arguments?.let { extras.putBundle(EXTRA_BINDING_SCREEN_ARGS, it) }
if (preference is PreferenceScreen) {
val screenTitle = screenMetadata.screenTitle
preference.title =
diff --git a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceFragment.kt b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceFragment.kt
index ffe181d0c350..02f91c1bb50b 100644
--- a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceFragment.kt
+++ b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceFragment.kt
@@ -23,6 +23,7 @@ import android.util.Log
import androidx.annotation.XmlRes
import androidx.lifecycle.Lifecycle
import androidx.preference.PreferenceScreen
+import com.android.settingslib.metadata.EXTRA_BINDING_SCREEN_ARGS
import com.android.settingslib.metadata.EXTRA_BINDING_SCREEN_KEY
import com.android.settingslib.metadata.PreferenceScreenBindingKeyProvider
import com.android.settingslib.metadata.PreferenceScreenRegistry
@@ -89,13 +90,19 @@ open class PreferenceFragment :
@XmlRes protected open fun getPreferenceScreenResId(context: Context): Int = 0
protected fun getPreferenceScreenCreator(context: Context): PreferenceScreenCreator? =
- (PreferenceScreenRegistry.create(context, getPreferenceScreenBindingKey(context))
- as? PreferenceScreenCreator)
+ (PreferenceScreenRegistry.create(
+ context,
+ getPreferenceScreenBindingKey(context),
+ getPreferenceScreenBindingArgs(context),
+ ) as? PreferenceScreenCreator)
?.run { if (isFlagEnabled(context)) this else null }
override fun getPreferenceScreenBindingKey(context: Context): String? =
arguments?.getString(EXTRA_BINDING_SCREEN_KEY)
+ override fun getPreferenceScreenBindingArgs(context: Context): Bundle? =
+ arguments?.getBundle(EXTRA_BINDING_SCREEN_ARGS)
+
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
preferenceScreenBindingHelper?.onCreate()
diff --git a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceScreenBindingHelper.kt b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceScreenBindingHelper.kt
index 4a6a589cd3c9..1cb8005ddae0 100644
--- a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceScreenBindingHelper.kt
+++ b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceScreenBindingHelper.kt
@@ -31,6 +31,7 @@ import com.android.settingslib.datastore.KeyValueStore
import com.android.settingslib.datastore.KeyedDataObservable
import com.android.settingslib.datastore.KeyedObservable
import com.android.settingslib.datastore.KeyedObserver
+import com.android.settingslib.metadata.EXTRA_BINDING_SCREEN_ARGS
import com.android.settingslib.metadata.PersistentPreference
import com.android.settingslib.metadata.PreferenceChangeReason
import com.android.settingslib.metadata.PreferenceHierarchy
@@ -227,14 +228,16 @@ class PreferenceScreenBindingHelper(
/** Updates preference screen that has incomplete hierarchy. */
@JvmStatic
fun bind(preferenceScreen: PreferenceScreen) {
- PreferenceScreenRegistry.create(preferenceScreen.context, preferenceScreen.key)?.run {
+ val context = preferenceScreen.context
+ val args = preferenceScreen.peekExtras()?.getBundle(EXTRA_BINDING_SCREEN_ARGS)
+ PreferenceScreenRegistry.create(context, preferenceScreen.key, args)?.run {
if (!hasCompleteHierarchy()) {
val preferenceBindingFactory =
(this as? PreferenceScreenCreator)?.preferenceBindingFactory ?: return
bindRecursively(
preferenceScreen,
preferenceBindingFactory,
- getPreferenceHierarchy(preferenceScreen.context),
+ getPreferenceHierarchy(context),
)
}
}
diff --git a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceScreenFactory.kt b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceScreenFactory.kt
index 211b3bdaea70..88c4fe6bf188 100644
--- a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceScreenFactory.kt
+++ b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceScreenFactory.kt
@@ -17,10 +17,12 @@
package com.android.settingslib.preference
import android.content.Context
+import android.os.Bundle
import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat
import androidx.preference.PreferenceManager
import androidx.preference.PreferenceScreen
+import com.android.settingslib.metadata.EXTRA_BINDING_SCREEN_ARGS
import com.android.settingslib.metadata.PreferenceScreenRegistry
/** Factory to create preference screen. */
@@ -81,8 +83,12 @@ class PreferenceScreenFactory {
*
* The screen must be registered in [PreferenceScreenFactory] and provide a complete hierarchy.
*/
- fun createBindingScreen(context: Context, screenKey: String?): PreferenceScreen? {
- val metadata = PreferenceScreenRegistry.create(context, screenKey) ?: return null
+ fun createBindingScreen(
+ context: Context,
+ screenKey: String?,
+ args: Bundle?,
+ ): PreferenceScreen? {
+ val metadata = PreferenceScreenRegistry.create(context, screenKey, args) ?: return null
if (metadata is PreferenceScreenCreator && metadata.hasCompleteHierarchy()) {
return metadata.createPreferenceScreen(this)
}
@@ -94,8 +100,9 @@ class PreferenceScreenFactory {
@JvmStatic
fun createBindingScreen(preference: Preference): PreferenceScreen? {
val context = preference.context
+ val args = preference.peekExtras()?.getBundle(EXTRA_BINDING_SCREEN_ARGS)
val preferenceScreenCreator =
- (PreferenceScreenRegistry.create(context, preference.key)
+ (PreferenceScreenRegistry.create(context, preference.key, args)
as? PreferenceScreenCreator) ?: return null
if (!preferenceScreenCreator.hasCompleteHierarchy()) return null
val factory = PreferenceScreenFactory(context)
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictionsProvider.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictionsProvider.kt
index 3309faaa8db2..3a6327962dc2 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictionsProvider.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictionsProvider.kt
@@ -32,6 +32,7 @@ import kotlinx.coroutines.flow.flowOn
data class EnhancedConfirmation(
val key: String,
val packageName: String,
+ val isRestrictedSettingAllowed: Boolean?
)
data class Restrictions(
val userId: Int = UserHandle.myUserId(),
@@ -92,6 +93,9 @@ internal class RestrictionsProviderImpl(
}
restrictions.enhancedConfirmation?.let { ec ->
+ if (ec.isRestrictedSettingAllowed == true) {
+ return NoRestricted
+ }
RestrictedLockUtilsInternal
.checkIfRequiresEnhancedConfirmation(context, ec.key,
ec.packageName)
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt
index 7466f95e3fb8..5580d2e3211b 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt
@@ -155,7 +155,7 @@ internal fun <T : AppRecord> TogglePermissionAppListModel<T>.TogglePermissionApp
}
RestrictedSwitchPreference(
model = switchModel,
- restrictions = getRestrictions(userId, packageName),
+ restrictions = getRestrictions(userId, packageName, isAllowed()),
ifBlockedByAdminOverrideCheckedValueTo = switchifBlockedByAdminOverrideCheckedValueTo,
restrictionsProviderFactory = restrictionsProviderFactory,
)
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppList.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppList.kt
index d2867af1eda6..771eb85ee21a 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppList.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppList.kt
@@ -116,12 +116,15 @@ fun <T : AppRecord> TogglePermissionAppListModel<T>.isChangeableWithSystemUidChe
fun <T : AppRecord> TogglePermissionAppListModel<T>.getRestrictions(
userId: Int,
packageName: String,
+ isRestrictedSettingAllowed: Boolean?
) =
Restrictions(
userId = userId,
keys = switchRestrictionKeys,
enhancedConfirmation =
- enhancedConfirmationKey?.let { key -> EnhancedConfirmation(key, packageName) },
+ enhancedConfirmationKey?.let {
+ key -> EnhancedConfirmation(key, packageName, isRestrictedSettingAllowed)
+ },
)
interface TogglePermissionAppListProvider {
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt
index ec44d2af4ffa..bef2bdaaefaf 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt
@@ -149,13 +149,14 @@ internal class TogglePermissionInternalAppListModel<T : AppRecord>(
@Composable
fun getSummary(record: T): () -> String {
+ val allowed = listModel.isAllowed(record)
val restrictions =
listModel.getRestrictions(
userId = record.app.userId,
packageName = record.app.packageName,
+ allowed()
)
val restrictedMode by restrictionsProviderFactory.rememberRestrictedMode(restrictions)
- val allowed = listModel.isAllowed(record)
return RestrictedSwitchPreferenceModel.getSummary(
context = context,
summaryIfNoRestricted = { getSummaryIfNoRestricted(allowed()) },
diff --git a/packages/SettingsLib/TopIntroPreference/res/layout/settingslib_expressive_top_intro.xml b/packages/SettingsLib/TopIntroPreference/res/layout/settingslib_expressive_top_intro.xml
index 834814ccbc6b..1c6b1ebd1535 100644
--- a/packages/SettingsLib/TopIntroPreference/res/layout/settingslib_expressive_top_intro.xml
+++ b/packages/SettingsLib/TopIntroPreference/res/layout/settingslib_expressive_top_intro.xml
@@ -18,7 +18,8 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
- android:paddingStart="?android:attr/listPreferredItemPaddingStart">
+ android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+ android:filterTouchesWhenObscured="true">
<com.android.settingslib.widget.CollapsableTextView
android:id="@+id/collapsable_text_view"
diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedDropDownPreference.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedDropDownPreference.java
index c36ade979d47..d3219c287f4b 100644
--- a/packages/SettingsLib/src/com/android/settingslib/RestrictedDropDownPreference.java
+++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedDropDownPreference.java
@@ -41,10 +41,23 @@ public class RestrictedDropDownPreference extends DropDownPreference implements
* package. Marks the preference as disabled if so.
* @param settingIdentifier The key identifying the setting
* @param packageName the package to check the settingIdentifier for
+ * @param settingEnabled Whether the setting in question is enabled
+ */
+ public void checkEcmRestrictionAndSetDisabled(@NonNull String settingIdentifier,
+ @NonNull String packageName, boolean settingEnabled) {
+ mHelper.checkEcmRestrictionAndSetDisabled(settingIdentifier, packageName, settingEnabled);
+ }
+
+ /**
+ * Checks if the given setting is subject to Enhanced Confirmation Mode restrictions for this
+ * package. Marks the preference as disabled if so.
+ * TODO b/390196024: remove this and update all callers to use the "settingEnabled" version
+ * @param settingIdentifier The key identifying the setting
+ * @param packageName the package to check the settingIdentifier for
*/
public void checkEcmRestrictionAndSetDisabled(@NonNull String settingIdentifier,
@NonNull String packageName) {
- mHelper.checkEcmRestrictionAndSetDisabled(settingIdentifier, packageName);
+ mHelper.checkEcmRestrictionAndSetDisabled(settingIdentifier, packageName, false);
}
@Override
diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedPreference.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedPreference.java
index 332042a5c4f9..ec1b2b3e2589 100644
--- a/packages/SettingsLib/src/com/android/settingslib/RestrictedPreference.java
+++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedPreference.java
@@ -107,12 +107,25 @@ public class RestrictedPreference extends TwoTargetPreference implements
/**
* Checks if the given setting is subject to Enhanced Confirmation Mode restrictions for this
* package. Marks the preference as disabled if so.
+ * TODO b/390196024: remove this and update all callers to use the "settingEnabled" version
* @param settingIdentifier The key identifying the setting
* @param packageName the package to check the settingIdentifier for
*/
public void checkEcmRestrictionAndSetDisabled(@NonNull String settingIdentifier,
@NonNull String packageName) {
- mHelper.checkEcmRestrictionAndSetDisabled(settingIdentifier, packageName);
+ mHelper.checkEcmRestrictionAndSetDisabled(settingIdentifier, packageName, false);
+ }
+
+ /**
+ * Checks if the given setting is subject to Enhanced Confirmation Mode restrictions for this
+ * package. Marks the preference as disabled if so.
+ * @param settingIdentifier The key identifying the setting
+ * @param packageName the package to check the settingIdentifier for
+ * @param settingEnabled Whether the setting in question is enabled
+ */
+ public void checkEcmRestrictionAndSetDisabled(@NonNull String settingIdentifier,
+ @NonNull String packageName, boolean settingEnabled) {
+ mHelper.checkEcmRestrictionAndSetDisabled(settingIdentifier, packageName, settingEnabled);
}
@Override
diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java
index 25628fba1b66..212e43aa4044 100644
--- a/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java
+++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java
@@ -204,16 +204,33 @@ public class RestrictedPreferenceHelper {
* package. Marks the preference as disabled if so.
* @param settingIdentifier The key identifying the setting
* @param packageName the package to check the settingIdentifier for
+ * @param settingEnabled Whether the setting in question is enabled
*/
public void checkEcmRestrictionAndSetDisabled(@NonNull String settingIdentifier,
- @NonNull String packageName) {
+ @NonNull String packageName, boolean settingEnabled) {
updatePackageDetails(packageName, android.os.Process.INVALID_UID);
+ if (settingEnabled) {
+ setDisabledByEcm(null);
+ return;
+ }
Intent intent = RestrictedLockUtilsInternal.checkIfRequiresEnhancedConfirmation(
mContext, settingIdentifier, packageName);
setDisabledByEcm(intent);
}
/**
+ * Checks if the given setting is subject to Enhanced Confirmation Mode restrictions for this
+ * package. Marks the preference as disabled if so.
+ * TODO b/390196024: remove this and update all callers to use the "settingEnabled" version
+ * @param settingIdentifier The key identifying the setting
+ * @param packageName the package to check the settingIdentifier for
+ */
+ public void checkEcmRestrictionAndSetDisabled(@NonNull String settingIdentifier,
+ @NonNull String packageName) {
+ checkEcmRestrictionAndSetDisabled(settingIdentifier, packageName, false);
+ }
+
+ /**
* @return EnforcedAdmin if we have been passed the restriction in the xml.
*/
public EnforcedAdmin checkRestrictionEnforced() {
diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedSelectorWithWidgetPreference.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedSelectorWithWidgetPreference.java
index 573869db5073..f8f16a9dd63b 100644
--- a/packages/SettingsLib/src/com/android/settingslib/RestrictedSelectorWithWidgetPreference.java
+++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedSelectorWithWidgetPreference.java
@@ -134,10 +134,11 @@ public class RestrictedSelectorWithWidgetPreference extends SelectorWithWidgetPr
*
* @param settingIdentifier The key identifying the setting
* @param packageName the package to check the settingIdentifier for
+ * @param settingEnabled Whether the setting in question is enabled
*/
- public void checkEcmRestrictionAndSetDisabled(
- @NonNull String settingIdentifier, @NonNull String packageName) {
- mHelper.checkEcmRestrictionAndSetDisabled(settingIdentifier, packageName);
+ public void checkEcmRestrictionAndSetDisabled(@NonNull String settingIdentifier,
+ @NonNull String packageName, boolean settingEnabled) {
+ mHelper.checkEcmRestrictionAndSetDisabled(settingIdentifier, packageName, settingEnabled);
}
@Override
diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java
index 0aac9a1104e9..a5fa6a854e97 100644
--- a/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java
+++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java
@@ -220,10 +220,23 @@ public class RestrictedSwitchPreference extends SwitchPreferenceCompat implement
* package. Marks the preference as disabled if so.
* @param settingIdentifier The key identifying the setting
* @param packageName the package to check the settingIdentifier for
+ * @param settingEnabled Whether the setting in question is enabled
*/
public void checkEcmRestrictionAndSetDisabled(@NonNull String settingIdentifier,
- @NonNull String packageName) {
- mHelper.checkEcmRestrictionAndSetDisabled(settingIdentifier, packageName);
+ @NonNull String packageName, boolean settingEnabled) {
+ mHelper.checkEcmRestrictionAndSetDisabled(settingIdentifier, packageName, settingEnabled);
+ }
+
+ /**
+ * Checks if the given setting is subject to Enhanced Confirmation Mode restrictions for this
+ * package. Marks the preference as disabled if so.
+ * TODO b/390196024: remove this and update all callers to use the "settingEnabled" version
+ * @param settingIdentifier The key identifying the setting
+ * @param packageName the package to check the settingIdentifier for
+ */
+ public void checkEcmRestrictionAndSetDisabled(@NonNull String settingIdentifier,
+ @NonNull String packageName) {
+ mHelper.checkEcmRestrictionAndSetDisabled(settingIdentifier, packageName, false);
}
@Override
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
index 68e9fe703090..a00484ac28ab 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
@@ -1169,4 +1169,38 @@ public class BluetoothUtils {
String metadataValue = getFastPairCustomizedField(bluetoothDevice, TEMP_BOND_TYPE);
return Objects.equals(metadataValue, TEMP_BOND_DEVICE_METADATA_VALUE);
}
+
+ /**
+ * Set temp bond metadata to device
+ *
+ * @param device the BluetoothDevice to be marked as temp bond
+ *
+ * Note: It is a workaround since Bluetooth API is not ready.
+ * Avoid using this method if possible
+ */
+ public static void setTemporaryBondMetadata(@Nullable BluetoothDevice device) {
+ if (device == null) return;
+ if (!Flags.enableTemporaryBondDevicesUi()) {
+ Log.d(TAG, "Skip setTemporaryBondMetadata, flag is disabled");
+ return;
+ }
+ String fastPairCustomizedMeta = getStringMetaData(device,
+ METADATA_FAST_PAIR_CUSTOMIZED_FIELDS);
+ String fullContentWithTag = generateExpressionWithTag(TEMP_BOND_TYPE,
+ TEMP_BOND_DEVICE_METADATA_VALUE);
+ if (TextUtils.isEmpty(fastPairCustomizedMeta)) {
+ fastPairCustomizedMeta = fullContentWithTag;
+ } else {
+ String oldValue = extraTagValue(TEMP_BOND_TYPE, fastPairCustomizedMeta);
+ if (TextUtils.isEmpty(oldValue)) {
+ fastPairCustomizedMeta += fullContentWithTag;
+ } else {
+ fastPairCustomizedMeta =
+ fastPairCustomizedMeta.replace(
+ generateExpressionWithTag(TEMP_BOND_TYPE, oldValue),
+ fullContentWithTag);
+ }
+ }
+ device.setMetadata(METADATA_FAST_PAIR_CUSTOMIZED_FIELDS, fastPairCustomizedMeta.getBytes());
+ }
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java
index 7d5eece6c30e..84156429809b 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java
@@ -1087,8 +1087,9 @@ public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile {
private String generateRandomPassword() {
String randomUUID = UUID.randomUUID().toString();
- // first 12 chars from xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx
- return randomUUID.substring(0, 8) + randomUUID.substring(9, 13);
+ // first 16 chars from xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx
+ return randomUUID.substring(0, 8) + randomUUID.substring(9, 13) + randomUUID.substring(14,
+ 18);
}
private void registerContentObserver() {
diff --git a/packages/SettingsLib/src/com/android/settingslib/enterprise/ActionDisabledByAdminControllerFactory.java b/packages/SettingsLib/src/com/android/settingslib/enterprise/ActionDisabledByAdminControllerFactory.java
index e3d7902f34b2..00973811dbf0 100644
--- a/packages/SettingsLib/src/com/android/settingslib/enterprise/ActionDisabledByAdminControllerFactory.java
+++ b/packages/SettingsLib/src/com/android/settingslib/enterprise/ActionDisabledByAdminControllerFactory.java
@@ -84,7 +84,11 @@ public final class ActionDisabledByAdminControllerFactory {
return false;
}
DevicePolicyManager dpm = context.getSystemService(DevicePolicyManager.class);
- return ParentalControlsUtilsInternal.parentConsentRequired(context, dpm,
+ final SupervisionManager sm =
+ android.app.supervision.flags.Flags.deprecateDpmSupervisionApis()
+ ? context.getSystemService(SupervisionManager.class)
+ : null;
+ return ParentalControlsUtilsInternal.parentConsentRequired(context, dpm, sm,
BiometricAuthenticator.TYPE_ANY_BIOMETRIC, new UserHandle(UserHandle.myUserId()));
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java
index cafe19ff9a9b..7c46db96595f 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java
@@ -22,9 +22,11 @@ import static com.android.settingslib.flags.Flags.FLAG_ENABLE_DETERMINING_ADVANC
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -44,6 +46,8 @@ import android.media.AudioDeviceAttributes;
import android.media.AudioDeviceInfo;
import android.media.AudioManager;
import android.net.Uri;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
import android.platform.test.flag.junit.SetFlagsRule;
import android.provider.Settings;
import android.util.Pair;
@@ -109,6 +113,7 @@ public class BluetoothUtilsTest {
+ "</HEARABLE_CONTROL_SLICE_WITH_WIDTH>";
private static final String TEMP_BOND_METADATA =
"<TEMP_BOND_TYPE>" + LE_AUDIO_SHARING_METADATA + "</TEMP_BOND_TYPE>";
+ private static final String FAKE_TEMP_BOND_METADATA = "<TEMP_BOND_TYPE>fake</TEMP_BOND_TYPE>";
private static final String TEST_EXCLUSIVE_MANAGER_PACKAGE = "com.test.manager";
private static final String TEST_EXCLUSIVE_MANAGER_COMPONENT = "com.test.manager/.component";
private static final int TEST_BROADCAST_ID = 25;
@@ -1348,4 +1353,34 @@ public class BluetoothUtilsTest {
assertThat(BluetoothUtils.isTemporaryBondDevice(mBluetoothDevice)).isTrue();
}
+
+ @Test
+ @DisableFlags(Flags.FLAG_ENABLE_TEMPORARY_BOND_DEVICES_UI)
+ public void setTemporaryBondDevice_flagOff_doNothing() {
+ when(mBluetoothDevice.getMetadata(METADATA_FAST_PAIR_CUSTOMIZED_FIELDS))
+ .thenReturn(new byte[]{});
+ BluetoothUtils.setTemporaryBondMetadata(mBluetoothDevice);
+ verify(mBluetoothDevice, never()).setMetadata(eq(METADATA_FAST_PAIR_CUSTOMIZED_FIELDS),
+ any());
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_TEMPORARY_BOND_DEVICES_UI)
+ public void setTemporaryBondDevice_flagOn_setCorrectValue() {
+ when(mBluetoothDevice.getMetadata(METADATA_FAST_PAIR_CUSTOMIZED_FIELDS))
+ .thenReturn(new byte[]{});
+ BluetoothUtils.setTemporaryBondMetadata(mBluetoothDevice);
+ verify(mBluetoothDevice).setMetadata(METADATA_FAST_PAIR_CUSTOMIZED_FIELDS,
+ TEMP_BOND_METADATA.getBytes());
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_TEMPORARY_BOND_DEVICES_UI)
+ public void setTemporaryBondDevice_flagOff_replaceAndSetCorrectValue() {
+ when(mBluetoothDevice.getMetadata(METADATA_FAST_PAIR_CUSTOMIZED_FIELDS))
+ .thenReturn(FAKE_TEMP_BOND_METADATA.getBytes());
+ BluetoothUtils.setTemporaryBondMetadata(mBluetoothDevice);
+ verify(mBluetoothDevice).setMetadata(METADATA_FAST_PAIR_CUSTOMIZED_FIELDS,
+ TEMP_BOND_METADATA.getBytes());
+ }
}
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
index d936a5c699c7..27a3cf1198b2 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
@@ -291,5 +291,6 @@ public class SecureSettings {
Settings.Secure.FACE_KEYGUARD_ENABLED,
Settings.Secure.FINGERPRINT_APP_ENABLED,
Settings.Secure.FINGERPRINT_KEYGUARD_ENABLED,
+ Settings.Secure.DUAL_SHADE,
};
}
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
index 919c3c4721f2..8dca39fdc107 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
@@ -460,5 +460,6 @@ public class SecureSettingsValidators {
VALIDATORS.put(Secure.FACE_KEYGUARD_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.FINGERPRINT_APP_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.FINGERPRINT_KEYGUARD_ENABLED, BOOLEAN_VALIDATOR);
+ VALIDATORS.put(Secure.DUAL_SHADE, BOOLEAN_VALIDATOR);
}
}
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
index 14b2dfe414a4..fc402d45c3ec 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
@@ -89,6 +89,7 @@ import java.io.OutputStream;
import java.time.DateTimeException;
import java.util.Arrays;
import java.util.Collections;
+import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
@@ -97,7 +98,6 @@ import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
-import java.util.HashMap;
import java.util.zip.CRC32;
/**
@@ -1753,8 +1753,8 @@ public class SettingsBackupAgent extends BackupAgentHelper {
if (previousDensity == null || previousDensity != newDensity) {
// From nothing to something is a change.
- DisplayDensityConfiguration.setForcedDisplayDensity(
- Display.DEFAULT_DISPLAY, newDensity);
+ DisplayDensityConfiguration.setForcedDisplayDensity(getBaseContext(),
+ info -> info.type == Display.TYPE_INTERNAL, newDensity);
}
}
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index c47bf628002d..7c588b3834a5 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -4072,7 +4072,7 @@ public class SettingsProvider extends ContentProvider {
@VisibleForTesting
final class UpgradeController {
- private static final int SETTINGS_VERSION = 226;
+ private static final int SETTINGS_VERSION = 227;
private final int mUserId;
@@ -6266,6 +6266,51 @@ public class SettingsProvider extends ContentProvider {
currentVersion = 226;
}
+ // Version 226: Introduces dreaming while postured setting and migrates user from
+ // docked dream trigger to postured dream trigger.
+ if (currentVersion == 226) {
+ final SettingsState secureSettings = getSecureSettingsLocked(userId);
+ final Setting dreamOnDock = secureSettings.getSettingLocked(
+ Secure.SCREENSAVER_ACTIVATE_ON_DOCK);
+ final Setting dreamsEnabled = secureSettings.getSettingLocked(
+ Secure.SCREENSAVER_ENABLED);
+ final boolean dreamOnPosturedDefault = getContext().getResources().getBoolean(
+ com.android.internal.R.bool.config_dreamsActivatedOnPosturedByDefault);
+ final boolean dreamsEnabledByDefault = getContext().getResources().getBoolean(
+ com.android.internal.R.bool.config_dreamsEnabledByDefault);
+
+ if (dreamOnPosturedDefault && !dreamOnDock.isNull()
+ && dreamOnDock.getValue().equals("1")) {
+ // Disable dock activation and enable postured.
+ secureSettings.insertSettingOverrideableByRestoreLocked(
+ Secure.SCREENSAVER_ACTIVATE_ON_DOCK,
+ "0",
+ null,
+ true,
+ SettingsState.SYSTEM_PACKAGE_NAME);
+ secureSettings.insertSettingOverrideableByRestoreLocked(
+ Secure.SCREENSAVER_ACTIVATE_ON_POSTURED,
+ "1",
+ null,
+ true,
+ SettingsState.SYSTEM_PACKAGE_NAME);
+
+ // Disable dreams overall, so user doesn't start to unexpectedly see dreams
+ // enabled when postured.
+ if (!dreamsEnabledByDefault && !dreamsEnabled.isNull()
+ && dreamsEnabled.getValue().equals("1")) {
+ secureSettings.insertSettingOverrideableByRestoreLocked(
+ Secure.SCREENSAVER_ENABLED,
+ "0",
+ null,
+ true,
+ SettingsState.SYSTEM_PACKAGE_NAME);
+ }
+ }
+
+ currentVersion = 227;
+ }
+
// vXXX: Add new settings above this point.
if (currentVersion != newVersion) {
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index 9aad5d5f8367..246aa7158cab 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -178,6 +178,7 @@ public class SettingsBackupTest {
Settings.Global.DEVELOPMENT_ENABLE_FREEFORM_WINDOWS_SUPPORT,
Settings.Global.DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS,
Settings.Global.DEVELOPMENT_OVERRIDE_DESKTOP_MODE_FEATURES,
+ Settings.Global.DEVELOPMENT_OVERRIDE_DESKTOP_EXPERIENCE_FEATURES,
Settings.Global.DEVELOPMENT_FORCE_RESIZABLE_ACTIVITIES,
Settings.Global.DEVELOPMENT_FORCE_RTL,
Settings.Global.DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW,
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index b53198d8ae98..72ae76a45cac 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -392,6 +392,9 @@
<!-- To be able to decipher default applications for certain roles in shortcut helper -->
<uses-permission android:name="android.permission.MANAGE_DEFAULT_APPLICATIONS" />
+ <!-- To be able to set unrestricted system gesture exclusion rects -->
+ <uses-permission android:name="android.permission.SET_UNRESTRICTED_GESTURE_EXCLUSION"/>
+
<protected-broadcast android:name="com.android.settingslib.action.REGISTER_SLICE_RECEIVER" />
<protected-broadcast android:name="com.android.settingslib.action.UNREGISTER_SLICE_RECEIVER" />
<protected-broadcast android:name="com.android.settings.flashlight.action.FLASHLIGHT_CHANGED" />
@@ -548,10 +551,13 @@
android:exported="true" />
<service android:name=".wallpapers.GradientColorWallpaper"
- android:featureFlag="android.app.enable_connected_displays_wallpaper"
android:singleUser="true"
android:permission="android.permission.BIND_WALLPAPER"
- android:exported="true" />
+ android:exported="true">
+ <meta-data android:name="android.service.wallpaper"
+ android:resource="@xml/gradient_color_wallpaper">
+ </meta-data>
+ </service>
<activity android:name=".tuner.TunerActivity"
android:enabled="false"
@@ -1165,5 +1171,16 @@
android:exported="false"
/>
+ <service
+ android:name="com.google.android.systemui.lowlightclock.LowLightClockDreamService"
+ android:enabled="false"
+ android:exported="false"
+ android:directBootAware="true"
+ android:permission="android.permission.BIND_DREAM_SERVICE">
+ <intent-filter>
+ <action android:name="android.service.dreams.DreamService" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ </service>
</application>
</manifest>
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/aconfig/Android.bp b/packages/SystemUI/accessibility/accessibilitymenu/aconfig/Android.bp
index 0ff856e0b91e..1d74774c7c11 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/aconfig/Android.bp
+++ b/packages/SystemUI/accessibility/accessibilitymenu/aconfig/Android.bp
@@ -5,7 +5,7 @@ package {
aconfig_declarations {
name: "com_android_a11y_menu_flags",
package: "com.android.systemui.accessibility.accessibilitymenu",
- container: "system",
+ container: "system_ext",
srcs: [
"accessibility.aconfig",
],
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/aconfig/accessibility.aconfig b/packages/SystemUI/accessibility/accessibilitymenu/aconfig/accessibility.aconfig
index 6d790114803a..bdf6d4242e68 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/aconfig/accessibility.aconfig
+++ b/packages/SystemUI/accessibility/accessibilitymenu/aconfig/accessibility.aconfig
@@ -1,5 +1,5 @@
package: "com.android.systemui.accessibility.accessibilitymenu"
-container: "system"
+container: "system_ext"
# NOTE: Keep alphabetized to help limit merge conflicts from multiple simultaneous editors.
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 903c05526122..f0c855753292 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -186,6 +186,16 @@ flag {
}
flag {
+ name: "notifications_pinned_hun_in_shade"
+ namespace: "systemui"
+ description: "Fixes displaying pinned HUNs in the Shade, making sure that their y and z positions are correct."
+ bug: "385041854"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "pss_app_selector_recents_split_screen"
namespace: "systemui"
description: "Allows recent apps selected for partial screenshare to be launched in split screen mode"
@@ -227,6 +237,13 @@ flag {
}
flag {
+ name: "notification_bundle_ui"
+ namespace: "systemui"
+ description: "Three-level group UI for notification bundles"
+ bug: "367996732"
+}
+
+flag {
name: "notification_background_tint_optimization"
namespace: "systemui"
description: "Re-enable the codepath that removed tinting of notifications when the"
@@ -524,6 +541,26 @@ flag {
}
flag {
+ name: "face_scanning_animation_npe_fix"
+ namespace: "systemui"
+ description: "Fix for the face scanning animation NPE"
+ bug: "392032258"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
+ name: "indication_text_a11y_fix"
+ namespace: "systemui"
+ description: "add double shadow to the indication text at the bottom of the lock screen"
+ bug: "349297241"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "rest_to_unlock"
namespace: "systemui"
description: "Require prolonged touch for fingerprint authentication"
@@ -596,13 +633,6 @@ flag {
}
flag {
- name: "quick_settings_visual_haptics_longpress"
- namespace: "systemui"
- description: "Enable special visual and haptic effects for quick settings tiles with long-press actions"
- bug: "229856884"
-}
-
-flag {
name: "switch_user_on_bg"
namespace: "systemui"
description: "Does user switching on a background thread"
@@ -1832,6 +1862,13 @@ flag {
}
flag {
+ name: "double_tap_to_sleep"
+ namespace: "systemui"
+ description: "Enable Double Tap to Sleep"
+ bug: "385194612"
+}
+
+flag{
name: "gsf_bouncer"
namespace: "systemui"
description: "Applies GSF font styles to Bouncer surfaces."
@@ -1915,16 +1952,6 @@ flag {
}
flag {
- name: "stabilize_heads_up_group"
- namespace: "systemui"
- description: "Stabilize heads up groups in VisualStabilityCoordinator"
- bug: "381864715"
- metadata {
- purpose: PURPOSE_BUGFIX
- }
-}
-
-flag {
name: "magnetic_notification_horizontal_swipe"
namespace: "systemui"
description: "Add support for magnetic behavior on horizontal notification swipes."
@@ -1942,6 +1969,13 @@ flag {
}
flag {
+ name: "permission_helper_ui_rich_ongoing"
+ namespace: "systemui"
+ description: "[RONs] Guards inline permission helper for demoting RONs"
+ bug: "379186372"
+}
+
+flag {
name: "aod_ui_rich_ongoing"
namespace: "systemui"
description: "Show a rich ongoing notification on the always-on display (depends on ui_rich_ongoing)"
@@ -1956,4 +1990,14 @@ flag {
metadata {
purpose: PURPOSE_BUGFIX
}
-} \ No newline at end of file
+}
+
+flag {
+ name: "shade_launch_accessibility"
+ namespace: "systemui"
+ description: "Intercept accessibility focus events for the Shade during launch animations to avoid stray TalkBack events."
+ bug: "379222226"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/FontVariationUtils.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/FontVariationUtils.kt
index 78ae4af258fc..9545bda80b2d 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/FontVariationUtils.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/FontVariationUtils.kt
@@ -1,9 +1,12 @@
package com.android.systemui.animation
-private const val TAG_WGHT = "wght"
-private const val TAG_WDTH = "wdth"
-private const val TAG_OPSZ = "opsz"
-private const val TAG_ROND = "ROND"
+object GSFAxes {
+ const val WEIGHT = "wght"
+ const val WIDTH = "wdth"
+ const val SLANT = "slnt"
+ const val ROUND = "ROND"
+ const val OPTICAL_SIZE = "opsz"
+}
class FontVariationUtils {
private var mWeight = -1
@@ -21,7 +24,7 @@ class FontVariationUtils {
weight: Int = -1,
width: Int = -1,
opticalSize: Int = -1,
- roundness: Int = -1
+ roundness: Int = -1,
): String {
isUpdated = false
if (weight >= 0 && mWeight != weight) {
@@ -43,16 +46,20 @@ class FontVariationUtils {
}
var resultString = ""
if (mWeight >= 0) {
- resultString += "'$TAG_WGHT' $mWeight"
+ resultString += "'${GSFAxes.WEIGHT}' $mWeight"
}
if (mWidth >= 0) {
- resultString += (if (resultString.isBlank()) "" else ", ") + "'$TAG_WDTH' $mWidth"
+ resultString +=
+ (if (resultString.isBlank()) "" else ", ") + "'${GSFAxes.WIDTH}' $mWidth"
}
if (mOpticalSize >= 0) {
- resultString += (if (resultString.isBlank()) "" else ", ") + "'$TAG_OPSZ' $mOpticalSize"
+ resultString +=
+ (if (resultString.isBlank()) "" else ", ") +
+ "'${GSFAxes.OPTICAL_SIZE}' $mOpticalSize"
}
if (mRoundness >= 0) {
- resultString += (if (resultString.isBlank()) "" else ", ") + "'$TAG_ROND' $mRoundness"
+ resultString +=
+ (if (resultString.isBlank()) "" else ", ") + "'${GSFAxes.ROUND}' $mRoundness"
}
return if (isUpdated) resultString else ""
}
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/gesture/effect/OffsetOverscrollEffect.kt b/packages/SystemUI/compose/core/src/com/android/compose/gesture/effect/OffsetOverscrollEffect.kt
index e74b185851c4..1bb8ae5019fb 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/gesture/effect/OffsetOverscrollEffect.kt
+++ b/packages/SystemUI/compose/core/src/com/android/compose/gesture/effect/OffsetOverscrollEffect.kt
@@ -40,7 +40,7 @@ import kotlinx.coroutines.CoroutineScope
@Composable
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
fun rememberOffsetOverscrollEffect(
- animationSpec: AnimationSpec<Float> = MaterialTheme.motionScheme.defaultSpatialSpec()
+ animationSpec: AnimationSpec<Float> = MaterialTheme.motionScheme.slowSpatialSpec()
): OffsetOverscrollEffect {
val animationScope = rememberCoroutineScope()
return remember(animationScope, animationSpec) {
@@ -51,7 +51,7 @@ fun rememberOffsetOverscrollEffect(
@Composable
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
fun rememberOffsetOverscrollEffectFactory(
- animationSpec: AnimationSpec<Float> = MaterialTheme.motionScheme.defaultSpatialSpec()
+ animationSpec: AnimationSpec<Float> = MaterialTheme.motionScheme.slowSpatialSpec()
): OverscrollFactory {
val animationScope = rememberCoroutineScope()
return remember(animationScope, animationSpec) {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ResponsiveLazyHorizontalGrid.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ResponsiveLazyHorizontalGrid.kt
index c7930549abe8..44c375d6ac5e 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ResponsiveLazyHorizontalGrid.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ResponsiveLazyHorizontalGrid.kt
@@ -272,9 +272,8 @@ private fun calculateNumCellsWidth(width: Dp) =
}
private fun calculateNumCellsHeight(height: Dp) =
- // See https://developer.android.com/develop/ui/views/layout/use-window-size-classes
when {
- height >= 900.dp -> 3
+ height >= 1000.dp -> 3
height >= 480.dp -> 2
else -> 1
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/MediaCarouselSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/MediaCarouselSection.kt
index 0ff567bf90ad..d8b3f742b447 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/MediaCarouselSection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/MediaCarouselSection.kt
@@ -19,12 +19,11 @@ package com.android.systemui.keyguard.ui.composable.section
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.res.dimensionResource
-import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.compose.animation.scene.ContentScope
import com.android.systemui.keyguard.ui.viewmodel.KeyguardMediaViewModel
+import com.android.systemui.lifecycle.rememberViewModel
import com.android.systemui.media.controls.ui.composable.MediaCarousel
import com.android.systemui.media.controls.ui.controller.MediaCarouselController
import com.android.systemui.media.controls.ui.view.MediaHost
@@ -38,7 +37,7 @@ class MediaCarouselSection
constructor(
private val mediaCarouselController: MediaCarouselController,
@param:Named(MediaModule.KEYGUARD) private val mediaHost: MediaHost,
- private val keyguardMediaViewModel: KeyguardMediaViewModel,
+ private val keyguardMediaViewModelFactory: KeyguardMediaViewModel.Factory,
) {
@Composable
@@ -46,7 +45,10 @@ constructor(
isShadeLayoutWide: Boolean,
modifier: Modifier = Modifier,
) {
- val isMediaVisible by keyguardMediaViewModel.isMediaVisible.collectAsStateWithLifecycle()
+ val viewModel =
+ rememberViewModel(traceName = "KeyguardMediaCarousel") {
+ keyguardMediaViewModelFactory.create()
+ }
val horizontalPadding =
if (isShadeLayoutWide) {
dimensionResource(id = R.dimen.notification_side_paddings)
@@ -55,7 +57,7 @@ constructor(
dimensionResource(id = R.dimen.notification_panel_margin_horizontal)
}
MediaCarousel(
- isVisible = isMediaVisible,
+ isVisible = viewModel.isMediaVisible,
mediaHost = mediaHost,
modifier = modifier.fillMaxWidth().padding(horizontal = horizontalPadding),
carouselController = mediaCarouselController,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt
index d7545cb07849..22556777c40f 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt
@@ -26,7 +26,6 @@ import com.android.systemui.scene.ui.composable.transitions.lockscreenToGoneTran
import com.android.systemui.scene.ui.composable.transitions.lockscreenToQuickSettingsTransition
import com.android.systemui.scene.ui.composable.transitions.lockscreenToShadeTransition
import com.android.systemui.scene.ui.composable.transitions.lockscreenToSplitShadeTransition
-import com.android.systemui.scene.ui.composable.transitions.notificationsShadeToQuickSettingsShadeTransition
import com.android.systemui.scene.ui.composable.transitions.shadeToQuickSettingsTransition
import com.android.systemui.scene.ui.composable.transitions.toNotificationsShadeTransition
import com.android.systemui.scene.ui.composable.transitions.toQuickSettingsShadeTransition
@@ -172,13 +171,6 @@ val SceneContainerTransitions = transitions {
toQuickSettingsShadeTransition()
}
from(
- Overlays.NotificationsShade,
- to = Overlays.QuickSettingsShade,
- cuj = Cuj.CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE,
- ) {
- notificationsShadeToQuickSettingsShadeTransition()
- }
- from(
Scenes.Gone,
to = Overlays.NotificationsShade,
key = SlightlyFasterShadeCollapse,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt
index bdd0da9ce4a4..4e10ff689b19 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt
@@ -67,11 +67,10 @@ import com.android.systemui.Flags
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.common.ui.compose.Icon
import com.android.systemui.compose.modifiers.sysuiResTag
-import com.android.systemui.haptics.slider.SeekableSliderTrackerConfig
-import com.android.systemui.haptics.slider.SliderHapticFeedbackConfig
import com.android.systemui.haptics.slider.compose.ui.SliderHapticsViewModel
import com.android.systemui.lifecycle.rememberViewModel
import com.android.systemui.res.R
+import com.android.systemui.volume.haptics.ui.VolumeHapticsConfigsProvider
import com.android.systemui.volume.panel.component.volume.slider.ui.viewmodel.SliderState
import kotlin.math.round
import kotlinx.coroutines.flow.distinctUntilChanged
@@ -103,6 +102,10 @@ fun VolumeSlider(
}
val value by valueState(state)
+ val interactionSource = remember { MutableInteractionSource() }
+ val hapticsViewModel: SliderHapticsViewModel? =
+ setUpHapticsViewModel(value, state.valueRange, interactionSource, hapticsViewModelFactory)
+
Column(modifier = modifier.animateContentSize(), verticalArrangement = Arrangement.Top) {
Row(
horizontalArrangement = Arrangement.spacedBy(12.dp),
@@ -127,8 +130,14 @@ fun VolumeSlider(
Slider(
value = value,
valueRange = state.valueRange,
- onValueChange = onValueChange,
- onValueChangeFinished = onValueChangeFinished,
+ onValueChange = { newValue ->
+ hapticsViewModel?.addVelocityDataPoint(newValue)
+ onValueChange(newValue)
+ },
+ onValueChangeFinished = {
+ hapticsViewModel?.onValueChangeEnded()
+ onValueChangeFinished?.invoke()
+ },
enabled = state.isEnabled,
modifier =
Modifier.height(40.dp)
@@ -210,41 +219,8 @@ private fun LegacyVolumeSlider(
) {
val value by valueState(state)
val interactionSource = remember { MutableInteractionSource() }
- val sliderStepSize = 1f / (state.valueRange.endInclusive - state.valueRange.start)
val hapticsViewModel: SliderHapticsViewModel? =
- hapticsViewModelFactory?.let {
- rememberViewModel(traceName = "SliderHapticsViewModel") {
- it.create(
- interactionSource,
- state.valueRange,
- Orientation.Horizontal,
- SliderHapticFeedbackConfig(
- lowerBookendScale = 0.2f,
- progressBasedDragMinScale = 0.2f,
- progressBasedDragMaxScale = 0.5f,
- deltaProgressForDragThreshold = 0f,
- additionalVelocityMaxBump = 0.2f,
- maxVelocityToScale = 0.1f, /* slider progress(from 0 to 1) per sec */
- sliderStepSize = sliderStepSize,
- ),
- SeekableSliderTrackerConfig(
- lowerBookendThreshold = 0f,
- upperBookendThreshold = 1f,
- ),
- )
- }
- }
- var lastDiscreteStep by remember { mutableFloatStateOf(round(value)) }
- LaunchedEffect(value) {
- snapshotFlow { value }
- .map { round(it) }
- .filter { it != lastDiscreteStep }
- .distinctUntilChanged()
- .collect { discreteStep ->
- lastDiscreteStep = discreteStep
- hapticsViewModel?.onValueChange(discreteStep)
- }
- }
+ setUpHapticsViewModel(value, state.valueRange, interactionSource, hapticsViewModelFactory)
PlatformSlider(
modifier =
@@ -357,3 +333,36 @@ private fun SliderIcon(
content = { Icon(modifier = Modifier.size(24.dp), icon = icon) },
)
}
+
+@Composable
+fun setUpHapticsViewModel(
+ value: Float,
+ valueRange: ClosedFloatingPointRange<Float>,
+ interactionSource: MutableInteractionSource,
+ hapticsViewModelFactory: SliderHapticsViewModel.Factory?,
+): SliderHapticsViewModel? {
+ return hapticsViewModelFactory?.let {
+ rememberViewModel(traceName = "SliderHapticsViewModel") {
+ it.create(
+ interactionSource,
+ valueRange,
+ Orientation.Horizontal,
+ VolumeHapticsConfigsProvider.sliderHapticFeedbackConfig(valueRange),
+ VolumeHapticsConfigsProvider.seekableSliderTrackerConfig,
+ )
+ }
+ .also { hapticsViewModel ->
+ var lastDiscreteStep by remember { mutableFloatStateOf(round(value)) }
+ LaunchedEffect(value) {
+ snapshotFlow { value }
+ .map { round(it) }
+ .filter { it != lastDiscreteStep }
+ .distinctUntilChanged()
+ .collect { discreteStep ->
+ lastDiscreteStep = discreteStep
+ hapticsViewModel.onValueChange(discreteStep)
+ }
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt
index ab3f6396e5c0..70ff47baa7a9 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt
@@ -127,7 +127,14 @@ internal class DraggableHandler(
directionChangeSlop = layoutImpl.directionChangeSlop,
)
- return createSwipeAnimation(layoutImpl, result, isUpOrLeft, orientation, gestureContext)
+ return createSwipeAnimation(
+ layoutImpl,
+ result,
+ isUpOrLeft,
+ orientation,
+ gestureContext,
+ layoutImpl.decayAnimationSpec,
+ )
}
private fun resolveSwipeSource(startedPosition: Offset): SwipeSource.Resolved? {
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/PredictiveBackHandler.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/PredictiveBackHandler.kt
index 96d68ff03acd..41279d338896 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/PredictiveBackHandler.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/PredictiveBackHandler.kt
@@ -18,19 +18,26 @@ package com.android.compose.animation.scene
import androidx.activity.BackEventCompat
import androidx.activity.compose.PredictiveBackHandler
+import androidx.compose.animation.core.AnimationSpec
import androidx.compose.animation.core.snap
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.runtime.Composable
+import androidx.compose.ui.util.fastCoerceIn
import com.android.compose.animation.scene.UserActionResult.ChangeScene
import com.android.compose.animation.scene.UserActionResult.HideOverlay
import com.android.compose.animation.scene.UserActionResult.ReplaceByOverlay
import com.android.compose.animation.scene.UserActionResult.ShowOverlay
-import com.android.compose.animation.scene.transition.animateProgress
import com.android.mechanics.ProvidedGestureContext
import com.android.mechanics.spec.InputDirection
+import kotlin.coroutines.cancellation.CancellationException
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.launch
@Composable
internal fun PredictiveBackHandler(
@@ -63,6 +70,7 @@ internal fun PredictiveBackHandler(
distance = 1f,
gestureContext =
ProvidedGestureContext(dragOffset = 0f, direction = InputDirection.Max),
+ decayAnimationSpec = layoutImpl.decayAnimationSpec,
)
animateProgress(
@@ -93,3 +101,63 @@ private fun UserActionResult.copy(
is ReplaceByOverlay -> copy(transitionKey = transitionKey)
}
}
+
+private suspend fun <T : ContentKey> animateProgress(
+ state: MutableSceneTransitionLayoutStateImpl,
+ animation: SwipeAnimation<T>,
+ progress: Flow<Float>,
+ commitSpec: AnimationSpec<Float>?,
+ cancelSpec: AnimationSpec<Float>?,
+ animationScope: CoroutineScope? = null,
+) {
+ suspend fun animateOffset(targetContent: T, spec: AnimationSpec<Float>?) {
+ if (state.transitionState != animation.contentTransition || animation.isAnimatingOffset()) {
+ return
+ }
+
+ animation.animateOffset(
+ initialVelocity = 0f,
+ targetContent = targetContent,
+
+ // Important: we have to specify a spec that correctly animates *progress* (low
+ // visibility threshold) and not *offset* (higher visibility threshold).
+ spec = spec ?: animation.contentTransition.transformationSpec.progressSpec,
+ )
+ }
+
+ coroutineScope {
+ val collectionJob = launch {
+ try {
+ progress.collectLatest { progress ->
+ // Progress based animation should never overscroll given that the
+ // absoluteDistance exposed to overscroll builders is always 1f and will not
+ // lead to any noticeable transformation.
+ animation.dragOffset = progress.fastCoerceIn(0f, 1f)
+ }
+
+ // Transition committed.
+ animateOffset(animation.toContent, commitSpec)
+ } catch (e: CancellationException) {
+ // Transition cancelled.
+ animateOffset(animation.fromContent, cancelSpec)
+ }
+ }
+
+ // Start the transition.
+ animationScope?.launch { startTransition(state, animation, collectionJob) }
+ ?: startTransition(state, animation, collectionJob)
+ }
+}
+
+private suspend fun <T : ContentKey> startTransition(
+ state: MutableSceneTransitionLayoutStateImpl,
+ animation: SwipeAnimation<T>,
+ progressCollectionJob: Job,
+) {
+ state.startTransition(animation.contentTransition)
+ // The transition is done. Cancel the collection in case the transition was finished
+ // because it was interrupted by another transition.
+ if (progressCollectionJob.isActive) {
+ progressCollectionJob.cancel()
+ }
+}
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 f6d40eef53a3..72bb82bd41bb 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
@@ -17,6 +17,7 @@
package com.android.compose.animation.scene
import androidx.annotation.FloatRange
+import androidx.compose.animation.rememberSplineBasedDecay
import androidx.compose.foundation.LocalOverscrollFactory
import androidx.compose.foundation.OverscrollEffect
import androidx.compose.foundation.OverscrollFactory
@@ -747,6 +748,7 @@ internal fun SceneTransitionLayoutForTesting(
val layoutDirection = LocalLayoutDirection.current
val defaultEffectFactory = checkNotNull(LocalOverscrollFactory.current)
val animationScope = rememberCoroutineScope()
+ val decayAnimationSpec = rememberSplineBasedDecay<Float>()
val layoutImpl = remember {
SceneTransitionLayoutImpl(
state = state as MutableSceneTransitionLayoutStateImpl,
@@ -762,6 +764,7 @@ internal fun SceneTransitionLayoutForTesting(
lookaheadScope = lookaheadScope,
directionChangeSlop = directionChangeSlop,
defaultEffectFactory = defaultEffectFactory,
+ decayAnimationSpec = decayAnimationSpec,
)
.also { onLayoutImpl?.invoke(it) }
}
@@ -801,6 +804,7 @@ internal fun SceneTransitionLayoutForTesting(
layoutImpl.swipeSourceDetector = swipeSourceDetector
layoutImpl.swipeDetector = swipeDetector
layoutImpl.transitionInterceptionThreshold = transitionInterceptionThreshold
+ layoutImpl.decayAnimationSpec = decayAnimationSpec
}
layoutImpl.Content(modifier)
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 585da0633131..53996d25afdb 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
@@ -17,6 +17,7 @@
package com.android.compose.animation.scene
import androidx.annotation.VisibleForTesting
+import androidx.compose.animation.core.DecayAnimationSpec
import androidx.compose.foundation.OverscrollFactory
import androidx.compose.foundation.clickable
import androidx.compose.foundation.gestures.Orientation
@@ -82,6 +83,7 @@ internal class SceneTransitionLayoutImpl(
internal var swipeSourceDetector: SwipeSourceDetector,
internal var swipeDetector: SwipeDetector,
internal var transitionInterceptionThreshold: Float,
+ internal var decayAnimationSpec: DecayAnimationSpec<Float>,
builder: SceneTransitionLayoutScope<InternalContentScope>.() -> Unit,
/**
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeAnimation.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeAnimation.kt
index 3bd37ad018b0..cb0d33cf5205 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeAnimation.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeAnimation.kt
@@ -21,6 +21,8 @@ package com.android.compose.animation.scene
import androidx.compose.animation.core.Animatable
import androidx.compose.animation.core.AnimationSpec
import androidx.compose.animation.core.AnimationVector1D
+import androidx.compose.animation.core.DecayAnimationSpec
+import androidx.compose.animation.core.calculateTargetValue
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.runtime.getValue
@@ -42,6 +44,7 @@ internal fun createSwipeAnimation(
orientation: Orientation,
distance: Float,
gestureContext: MutableDragOffsetGestureContext,
+ decayAnimationSpec: DecayAnimationSpec<Float>,
): SwipeAnimation<*> {
return createSwipeAnimation(
layoutState,
@@ -53,6 +56,7 @@ internal fun createSwipeAnimation(
error("Computing contentForUserActions requires a SceneTransitionLayoutImpl")
},
gestureContext = gestureContext,
+ decayAnimationSpec = decayAnimationSpec,
)
}
@@ -62,6 +66,7 @@ internal fun createSwipeAnimation(
isUpOrLeft: Boolean,
orientation: Orientation,
gestureContext: MutableDragOffsetGestureContext,
+ decayAnimationSpec: DecayAnimationSpec<Float>,
distance: Float = DistanceUnspecified,
): SwipeAnimation<*> {
var lastDistance = distance
@@ -106,6 +111,7 @@ internal fun createSwipeAnimation(
distance = ::distance,
contentForUserActions = { layoutImpl.contentForUserActions().key },
gestureContext = gestureContext,
+ decayAnimationSpec = decayAnimationSpec,
)
}
@@ -117,6 +123,7 @@ private fun createSwipeAnimation(
distance: (SwipeAnimation<*>) -> Float,
contentForUserActions: () -> ContentKey,
gestureContext: MutableDragOffsetGestureContext,
+ decayAnimationSpec: DecayAnimationSpec<Float>,
): SwipeAnimation<*> {
fun <T : ContentKey> swipeAnimation(fromContent: T, toContent: T): SwipeAnimation<T> {
return SwipeAnimation(
@@ -128,6 +135,7 @@ private fun createSwipeAnimation(
requiresFullDistanceSwipe = result.requiresFullDistanceSwipe,
distance = distance,
gestureContext = gestureContext,
+ decayAnimationSpec = decayAnimationSpec,
)
}
@@ -201,6 +209,7 @@ internal class SwipeAnimation<T : ContentKey>(
private val distance: (SwipeAnimation<T>) -> Float,
currentContent: T = fromContent,
private val gestureContext: MutableDragOffsetGestureContext,
+ private val decayAnimationSpec: DecayAnimationSpec<Float>,
) : MutableDragOffsetGestureContext by gestureContext {
/** The [TransitionState.Transition] whose implementation delegates to this [SwipeAnimation]. */
lateinit var contentTransition: TransitionState.Transition
@@ -367,20 +376,10 @@ internal class SwipeAnimation<T : ContentKey>(
check(isAnimatingOffset())
- val motionSpatialSpec = spec ?: layoutState.motionScheme.defaultSpatialSpec()
-
val velocityConsumed = CompletableDeferred<Float>()
offsetAnimationRunnable.complete {
- val result =
- animatable.animateTo(
- targetValue = targetOffset,
- animationSpec = motionSpatialSpec,
- initialVelocity = initialVelocity,
- )
-
- // We are no longer able to consume the velocity, the rest can be consumed by another
- // component in the hierarchy.
- velocityConsumed.complete(initialVelocity - result.endState.velocity)
+ val consumed = animateOffset(animatable, targetOffset, initialVelocity, spec)
+ velocityConsumed.complete(consumed)
// Wait for overscroll to finish so that the transition is removed from the STLState
// only after the overscroll is done, to avoid dropping frame right when the user lifts
@@ -391,6 +390,53 @@ internal class SwipeAnimation<T : ContentKey>(
return velocityConsumed.await()
}
+ private suspend fun animateOffset(
+ animatable: Animatable<Float, AnimationVector1D>,
+ targetOffset: Float,
+ initialVelocity: Float,
+ spec: AnimationSpec<Float>?,
+ ): Float {
+ val initialOffset = animatable.value
+ val decayOffset =
+ decayAnimationSpec.calculateTargetValue(
+ initialVelocity = initialVelocity,
+ initialValue = initialOffset,
+ )
+
+ val willDecayReachBounds =
+ when {
+ targetOffset > initialOffset -> decayOffset >= targetOffset
+ targetOffset < initialOffset -> decayOffset <= targetOffset
+ else -> true
+ }
+
+ if (willDecayReachBounds) {
+ val result = animatable.animateDecay(initialVelocity, decayAnimationSpec)
+ check(animatable.value == targetOffset) {
+ buildString {
+ appendLine(
+ "animatable.value = ${animatable.value} != $targetOffset = targetOffset"
+ )
+ appendLine(" initialOffset=$initialOffset")
+ appendLine(" targetOffset=$targetOffset")
+ appendLine(" initialVelocity=$initialVelocity")
+ appendLine(" decayOffset=$decayOffset")
+ }
+ }
+ return initialVelocity - result.endState.velocity
+ }
+
+ val motionSpatialSpec = spec ?: layoutState.motionScheme.defaultSpatialSpec()
+ animatable.animateTo(
+ targetValue = targetOffset,
+ animationSpec = motionSpatialSpec,
+ initialVelocity = initialVelocity,
+ )
+
+ // We consumed the whole velocity.
+ return initialVelocity
+ }
+
private fun canChangeContent(targetContent: ContentKey): Boolean {
return when (val transition = contentTransition) {
is TransitionState.Transition.ChangeScene ->
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/Seek.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/Seek.kt
deleted file mode 100644
index 819cec712808..000000000000
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/Seek.kt
+++ /dev/null
@@ -1,203 +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.compose.animation.scene.transition
-
-import androidx.annotation.FloatRange
-import androidx.compose.animation.core.AnimationSpec
-import androidx.compose.foundation.gestures.Orientation
-import androidx.compose.ui.util.fastCoerceIn
-import com.android.compose.animation.scene.ContentKey
-import com.android.compose.animation.scene.MutableSceneTransitionLayoutState
-import com.android.compose.animation.scene.MutableSceneTransitionLayoutStateImpl
-import com.android.compose.animation.scene.OverlayKey
-import com.android.compose.animation.scene.SceneKey
-import com.android.compose.animation.scene.SwipeAnimation
-import com.android.compose.animation.scene.TransitionKey
-import com.android.compose.animation.scene.UserActionResult
-import com.android.compose.animation.scene.createSwipeAnimation
-import com.android.mechanics.ProvidedGestureContext
-import com.android.mechanics.spec.InputDirection
-import kotlin.coroutines.cancellation.CancellationException
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Job
-import kotlinx.coroutines.coroutineScope
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.collectLatest
-import kotlinx.coroutines.launch
-
-/**
- * Seek to the given [scene] using [progress].
- *
- * This will start a transition from the
- * [current scene][MutableSceneTransitionLayoutState.currentScene] to [scene], driven by the
- * progress in [progress]. Once [progress] stops emitting, we will animate progress to 1f (using
- * [animationSpec]) if it stopped normally or to 0f if it stopped with a
- * [kotlin.coroutines.cancellation.CancellationException].
- */
-suspend fun MutableSceneTransitionLayoutState.seekToScene(
- scene: SceneKey,
- @FloatRange(0.0, 1.0) progress: Flow<Float>,
- transitionKey: TransitionKey? = null,
- animationSpec: AnimationSpec<Float>? = null,
-) {
- require(scene != currentScene) {
- "seekToScene($scene) has to be called with a different scene than the current scene"
- }
-
- seek(UserActionResult.ChangeScene(scene, transitionKey), progress, animationSpec)
-}
-
-/**
- * Seek to show the given [overlay] using [progress].
- *
- * This will start a transition to show [overlay] from the
- * [current scene][MutableSceneTransitionLayoutState.currentScene], driven by the progress in
- * [progress]. Once [progress] stops emitting, we will animate progress to 1f (using
- * [animationSpec]) if it stopped normally or to 0f if it stopped with a
- * [kotlin.coroutines.cancellation.CancellationException].
- */
-suspend fun MutableSceneTransitionLayoutState.seekToShowOverlay(
- overlay: OverlayKey,
- @FloatRange(0.0, 1.0) progress: Flow<Float>,
- transitionKey: TransitionKey? = null,
- animationSpec: AnimationSpec<Float>? = null,
-) {
- require(overlay in currentOverlays) {
- "seekToShowOverlay($overlay) can be called only when the overlay is in currentOverlays"
- }
-
- seek(UserActionResult.ShowOverlay(overlay, transitionKey), progress, animationSpec)
-}
-
-/**
- * Seek to hide the given [overlay] using [progress].
- *
- * This will start a transition to hide [overlay] to the
- * [current scene][MutableSceneTransitionLayoutState.currentScene], driven by the progress in
- * [progress]. Once [progress] stops emitting, we will animate progress to 1f (using
- * [animationSpec]) if it stopped normally or to 0f if it stopped with a
- * [kotlin.coroutines.cancellation.CancellationException].
- */
-suspend fun MutableSceneTransitionLayoutState.seekToHideOverlay(
- overlay: OverlayKey,
- @FloatRange(0.0, 1.0) progress: Flow<Float>,
- transitionKey: TransitionKey? = null,
- animationSpec: AnimationSpec<Float>? = null,
-) {
- require(overlay !in currentOverlays) {
- "seekToHideOverlay($overlay) can be called only when the overlay is *not* in " +
- "currentOverlays"
- }
-
- seek(UserActionResult.HideOverlay(overlay, transitionKey), progress, animationSpec)
-}
-
-private suspend fun MutableSceneTransitionLayoutState.seek(
- result: UserActionResult,
- progress: Flow<Float>,
- animationSpec: AnimationSpec<Float>?,
-) {
- val layoutState =
- when (this) {
- is MutableSceneTransitionLayoutStateImpl -> this
- }
-
- val swipeAnimation =
- createSwipeAnimation(
- layoutState = layoutState,
- result = result,
-
- // We are animating progress, so distance is always 1f.
- distance = 1f,
-
- // The orientation and isUpOrLeft don't matter here given that they are only used during
- // overscroll, which is disabled for progress-based transitions.
- orientation = Orientation.Horizontal,
- isUpOrLeft = false,
- // There is no gesture information available here - animateProgress
- // will set the progress as the dragOffset.
- gestureContext = ProvidedGestureContext(0f, InputDirection.Max),
- )
-
- animateProgress(
- state = layoutState,
- animation = swipeAnimation,
- progress = progress,
- commitSpec = animationSpec,
- cancelSpec = animationSpec,
- )
-}
-
-internal suspend fun <T : ContentKey> animateProgress(
- state: MutableSceneTransitionLayoutStateImpl,
- animation: SwipeAnimation<T>,
- progress: Flow<Float>,
- commitSpec: AnimationSpec<Float>?,
- cancelSpec: AnimationSpec<Float>?,
- animationScope: CoroutineScope? = null,
-) {
- suspend fun animateOffset(targetContent: T, spec: AnimationSpec<Float>?) {
- if (state.transitionState != animation.contentTransition || animation.isAnimatingOffset()) {
- return
- }
-
- animation.animateOffset(
- initialVelocity = 0f,
- targetContent = targetContent,
-
- // Important: we have to specify a spec that correctly animates *progress* (low
- // visibility threshold) and not *offset* (higher visibility threshold).
- spec = spec ?: animation.contentTransition.transformationSpec.progressSpec,
- )
- }
-
- coroutineScope {
- val collectionJob = launch {
- try {
- progress.collectLatest { progress ->
- // Progress based animation should never overscroll given that the
- // absoluteDistance exposed to overscroll builders is always 1f and will not
- // lead to any noticeable transformation.
- animation.dragOffset = progress.fastCoerceIn(0f, 1f)
- }
-
- // Transition committed.
- animateOffset(animation.toContent, commitSpec)
- } catch (e: CancellationException) {
- // Transition cancelled.
- animateOffset(animation.fromContent, cancelSpec)
- }
- }
-
- // Start the transition.
- animationScope?.launch { startTransition(state, animation, collectionJob) }
- ?: startTransition(state, animation, collectionJob)
- }
-}
-
-private suspend fun <T : ContentKey> startTransition(
- state: MutableSceneTransitionLayoutStateImpl,
- animation: SwipeAnimation<T>,
- progressCollectionJob: Job,
-) {
- state.startTransition(animation.contentTransition)
- // The transition is done. Cancel the collection in case the transition was finished
- // because it was interrupted by another transition.
- if (progressCollectionJob.isActive) {
- progressCollectionJob.cancel()
- }
-}
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 6b439980cc68..969003cb92f3 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
@@ -18,7 +18,9 @@
package com.android.compose.animation.scene
+import androidx.compose.animation.SplineBasedFloatDecayAnimationSpec
import androidx.compose.animation.core.Spring
+import androidx.compose.animation.core.generateDecayAnimationSpec
import androidx.compose.animation.core.spring
import androidx.compose.foundation.overscroll
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
@@ -119,10 +121,11 @@ class DraggableHandlerTest {
val transitionInterceptionThreshold = 0.05f
val directionChangeSlop = 10f
+ private val density = Density(1f)
private val layoutImpl =
SceneTransitionLayoutImpl(
state = layoutState,
- density = Density(1f),
+ density = density,
layoutDirection = LayoutDirection.Ltr,
swipeSourceDetector = DefaultEdgeDetector,
swipeDetector = DefaultSwipeDetector,
@@ -134,6 +137,8 @@ class DraggableHandlerTest {
animationScope = testScope,
directionChangeSlop = directionChangeSlop,
defaultEffectFactory = defaultEffectFactory,
+ decayAnimationSpec =
+ SplineBasedFloatDecayAnimationSpec(density).generateDecayAnimationSpec(),
)
.apply { setContentsAndLayoutTargetSizeForTest(LAYOUT_SIZE) }
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 c5e4061e834a..26f3c259dca9 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
@@ -24,18 +24,14 @@ import com.android.compose.animation.scene.TestScenes.SceneB
import com.android.compose.animation.scene.TestScenes.SceneC
import com.android.compose.animation.scene.content.state.TransitionState
import com.android.compose.animation.scene.subjects.assertThat
-import com.android.compose.animation.scene.transition.seekToScene
import com.android.compose.test.TestSceneTransition
import com.android.compose.test.runMonotonicClockTest
import com.android.compose.test.transition
import com.google.common.truth.Truth.assertThat
-import kotlin.coroutines.cancellation.CancellationException
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.CoroutineStart
import kotlinx.coroutines.awaitCancellation
import kotlinx.coroutines.cancelAndJoin
-import kotlinx.coroutines.channels.Channel
-import kotlinx.coroutines.flow.consumeAsFlow
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.test.runCurrent
@@ -282,77 +278,6 @@ class SceneTransitionLayoutStateTest {
}
@Test
- fun seekToScene() = runMonotonicClockTest {
- val state = MutableSceneTransitionLayoutStateForTests(SceneA)
- val progress = Channel<Float>()
-
- val job =
- launch(start = CoroutineStart.UNDISPATCHED) {
- state.seekToScene(SceneB, progress.consumeAsFlow())
- }
-
- val transition = assertThat(state.transitionState).isSceneTransition()
- assertThat(transition).hasFromScene(SceneA)
- assertThat(transition).hasToScene(SceneB)
- assertThat(transition).hasProgress(0f)
-
- // Change progress.
- progress.send(0.4f)
- assertThat(transition).hasProgress(0.4f)
-
- // Close the channel normally to confirm the transition.
- progress.close()
- job.join()
- assertThat(state.transitionState).isIdle()
- assertThat(state.transitionState).hasCurrentScene(SceneB)
- }
-
- @Test
- fun seekToScene_cancelled() = runMonotonicClockTest {
- val state = MutableSceneTransitionLayoutStateForTests(SceneA)
- val progress = Channel<Float>()
-
- val job =
- launch(start = CoroutineStart.UNDISPATCHED) {
- state.seekToScene(SceneB, progress.consumeAsFlow())
- }
-
- val transition = assertThat(state.transitionState).isSceneTransition()
- assertThat(transition).hasFromScene(SceneA)
- assertThat(transition).hasToScene(SceneB)
- assertThat(transition).hasProgress(0f)
-
- // Change progress.
- progress.send(0.4f)
- assertThat(transition).hasProgress(0.4f)
-
- // Close the channel with a CancellationException to cancel the transition.
- progress.close(CancellationException())
- job.join()
- assertThat(state.transitionState).isIdle()
- assertThat(state.transitionState).hasCurrentScene(SceneA)
- }
-
- @Test
- fun seekToScene_interrupted() = runMonotonicClockTest {
- val state = MutableSceneTransitionLayoutStateForTests(SceneA)
- val progress = Channel<Float>()
-
- val job =
- launch(start = CoroutineStart.UNDISPATCHED) {
- state.seekToScene(SceneB, progress.consumeAsFlow())
- }
-
- assertThat(state.transitionState).isSceneTransition()
-
- // Start a new transition, interrupting the seek transition.
- state.setTargetScene(SceneB, animationScope = this)
-
- // The previous job is cancelled and does not infinitely collect the progress.
- job.join()
- }
-
- @Test
fun replacedTransitionIsRemovedFromFinishedTransitions() = runTest {
val state = MutableSceneTransitionLayoutStateForTests(SceneA)
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
index f6ff3268fca4..36029177d4f6 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
@@ -17,6 +17,7 @@ import android.content.Context
import android.content.res.Resources
import android.graphics.Typeface
import android.view.LayoutInflater
+import com.android.systemui.animation.GSFAxes
import com.android.systemui.customization.R
import com.android.systemui.log.core.MessageBuffer
import com.android.systemui.plugins.clocks.ClockController
@@ -107,18 +108,18 @@ class DefaultClockProvider(
// TODO(b/364681643): Variations for retargetted DIGITAL_CLOCK_FLEX
val LEGACY_FLEX_LS_VARIATION =
listOf(
- ClockFontAxisSetting("wght", 600f),
- ClockFontAxisSetting("wdth", 100f),
- ClockFontAxisSetting("ROND", 100f),
- ClockFontAxisSetting("slnt", 0f),
+ ClockFontAxisSetting(GSFAxes.WEIGHT, 600f),
+ ClockFontAxisSetting(GSFAxes.WIDTH, 100f),
+ ClockFontAxisSetting(GSFAxes.ROUND, 100f),
+ ClockFontAxisSetting(GSFAxes.SLANT, 0f),
)
val LEGACY_FLEX_AOD_VARIATION =
listOf(
- ClockFontAxisSetting("wght", 74f),
- ClockFontAxisSetting("wdth", 43f),
- ClockFontAxisSetting("ROND", 100f),
- ClockFontAxisSetting("slnt", 0f),
+ ClockFontAxisSetting(GSFAxes.WEIGHT, 74f),
+ ClockFontAxisSetting(GSFAxes.WIDTH, 43f),
+ ClockFontAxisSetting(GSFAxes.ROUND, 100f),
+ ClockFontAxisSetting(GSFAxes.SLANT, 0f),
)
val FLEX_TYPEFACE by lazy {
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockController.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockController.kt
index e69fa994931d..cc3769e0a568 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockController.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockController.kt
@@ -16,6 +16,7 @@
package com.android.systemui.shared.clocks
+import com.android.systemui.animation.GSFAxes
import com.android.systemui.customization.R
import com.android.systemui.plugins.clocks.AlarmData
import com.android.systemui.plugins.clocks.AxisType
@@ -122,16 +123,16 @@ class FlexClockController(private val clockCtx: ClockContext) : ClockController
val FONT_AXES =
listOf(
ClockFontAxis(
- key = "wght",
+ key = GSFAxes.WEIGHT,
type = AxisType.Float,
- minValue = 1f,
+ minValue = 25f,
currentValue = 400f,
maxValue = 1000f,
name = "Weight",
description = "Glyph Weight",
),
ClockFontAxis(
- key = "wdth",
+ key = GSFAxes.WIDTH,
type = AxisType.Float,
minValue = 25f,
currentValue = 100f,
@@ -140,7 +141,7 @@ class FlexClockController(private val clockCtx: ClockContext) : ClockController
description = "Glyph Width",
),
ClockFontAxis(
- key = "ROND",
+ key = GSFAxes.ROUND,
type = AxisType.Boolean,
minValue = 0f,
currentValue = 0f,
@@ -149,7 +150,7 @@ class FlexClockController(private val clockCtx: ClockContext) : ClockController
description = "Glyph Roundness",
),
ClockFontAxis(
- key = "slnt",
+ key = GSFAxes.SLANT,
type = AxisType.Boolean,
minValue = 0f,
currentValue = 0f,
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockFaceController.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockFaceController.kt
index 827bd6898310..e2bbe0fef3c0 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockFaceController.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockFaceController.kt
@@ -21,6 +21,7 @@ import android.view.Gravity
import android.view.View
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
import android.widget.FrameLayout
+import com.android.systemui.animation.GSFAxes
import com.android.systemui.customization.R
import com.android.systemui.plugins.clocks.AlarmData
import com.android.systemui.plugins.clocks.ClockAnimations
@@ -125,7 +126,19 @@ class FlexClockFaceController(clockCtx: ClockContext, private val isLargeClock:
layerController.faceEvents.onThemeChanged(theme)
}
- override fun onFontAxesChanged(axes: List<ClockFontAxisSetting>) {
+ override fun onFontAxesChanged(settings: List<ClockFontAxisSetting>) {
+ var axes = settings
+ if (!isLargeClock) {
+ axes =
+ axes.map { axis ->
+ if (axis.key == GSFAxes.WIDTH && axis.value > SMALL_CLOCK_MAX_WDTH) {
+ axis.copy(value = SMALL_CLOCK_MAX_WDTH)
+ } else {
+ axis
+ }
+ }
+ }
+
layerController.events.onFontAxesChanged(axes)
}
@@ -236,6 +249,7 @@ class FlexClockFaceController(clockCtx: ClockContext, private val isLargeClock:
}
companion object {
+ val SMALL_CLOCK_MAX_WDTH = 120f
val SMALL_LAYER_CONFIG =
LayerConfig(
timespec = DigitalTimespec.TIME_FULL_FORMAT,
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/FlexClockView.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/FlexClockView.kt
index c40bb9a5ebea..3eb519186a3e 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/FlexClockView.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/FlexClockView.kt
@@ -139,13 +139,49 @@ class FlexClockView(clockCtx: ClockContext) : FrameLayout(clockCtx.context) {
digitalClockTextViewMap.forEach { (_, textView) -> textView.refreshText() }
}
+ override fun setVisibility(visibility: Int) {
+ if (visibility != this.visibility) {
+ logger.d({ "setVisibility(${str1 ?: int1})" }) {
+ int1 = visibility
+ str1 =
+ when (visibility) {
+ VISIBLE -> "VISIBLE"
+ INVISIBLE -> "INVISIBLE"
+ GONE -> "GONE"
+ else -> null
+ }
+ }
+ }
+
+ super.setVisibility(visibility)
+ }
+
+ private var loggedAlpha = 1000f
+
+ override fun setAlpha(alpha: Float) {
+ val delta = if (alpha <= 0f || alpha >= 1f) 0.001f else 0.5f
+ if (abs(loggedAlpha - alpha) >= delta) {
+ loggedAlpha = alpha
+ logger.d({ "setAlpha($double1)" }) { double1 = alpha.toDouble() }
+ }
+ super.setAlpha(alpha)
+ }
+
+ private val isDrawn: Boolean
+ get() = (mPrivateFlags and 0x20 /* PFLAG_DRAWN */) > 0
+
override fun invalidate() {
- logger.d("invalidate()")
+ if (isDrawn && visibility == VISIBLE) {
+ logger.d("invalidate()")
+ }
+
super.invalidate()
}
override fun requestLayout() {
- logger.d("requestLayout()")
+ if (!isLayoutRequested()) {
+ logger.d("requestLayout()")
+ }
super.requestLayout()
}
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockTextView.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockTextView.kt
index 2b0825f39243..fbd5887c5b54 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockTextView.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockTextView.kt
@@ -34,6 +34,7 @@ import android.view.View.MeasureSpec.EXACTLY
import android.view.animation.Interpolator
import android.widget.TextView
import com.android.internal.annotations.VisibleForTesting
+import com.android.systemui.animation.GSFAxes
import com.android.systemui.animation.TextAnimator
import com.android.systemui.customization.R
import com.android.systemui.log.core.Logger
@@ -44,6 +45,7 @@ import com.android.systemui.shared.clocks.DimensionParser
import com.android.systemui.shared.clocks.FontTextStyle
import com.android.systemui.shared.clocks.LogUtil
import java.lang.Thread
+import kotlin.math.abs
import kotlin.math.max
import kotlin.math.min
@@ -206,7 +208,10 @@ open class SimpleDigitalClockTextView(clockCtx: ClockContext, attrs: AttributeSe
}
override fun onDraw(canvas: Canvas) {
- logger.d({ "onDraw(); ls: $str1" }) { str1 = textAnimator.textInterpolator.shapedText }
+ logger.d({ "onDraw(${str1?.replace("\n", "\\n")})" }) {
+ str1 = textAnimator.textInterpolator.shapedText
+ }
+
val translation = getLocalTranslation()
canvas.translate(translation.x.toFloat(), translation.y.toFloat())
digitTranslateAnimator?.let {
@@ -221,8 +226,42 @@ open class SimpleDigitalClockTextView(clockCtx: ClockContext, attrs: AttributeSe
canvas.translate(-translation.x.toFloat(), -translation.y.toFloat())
}
+ override fun setVisibility(visibility: Int) {
+ if (visibility != this.visibility) {
+ logger.d({ "setVisibility(${str1 ?: int1})" }) {
+ int1 = visibility
+ str1 =
+ when (visibility) {
+ VISIBLE -> "VISIBLE"
+ INVISIBLE -> "INVISIBLE"
+ GONE -> "GONE"
+ else -> null
+ }
+ }
+ }
+
+ super.setVisibility(visibility)
+ }
+
+ private var loggedAlpha = 1000f
+
+ override fun setAlpha(alpha: Float) {
+ val delta = if (alpha <= 0f || alpha >= 1f) 0.001f else 0.5f
+ if (abs(loggedAlpha - alpha) >= delta) {
+ loggedAlpha = alpha
+ logger.d({ "setAlpha($double1)" }) { double1 = alpha.toDouble() }
+ }
+ super.setAlpha(alpha)
+ }
+
+ private val isDrawn: Boolean
+ get() = (mPrivateFlags and 0x20 /* PFLAG_DRAWN */) > 0
+
override fun invalidate() {
- logger.d("invalidate()")
+ if (isDrawn && visibility == VISIBLE) {
+ logger.d("invalidate()")
+ }
+
super.invalidate()
(parent as? FlexClockView)?.invalidate()
}
@@ -490,22 +529,22 @@ open class SimpleDigitalClockTextView(clockCtx: ClockContext, attrs: AttributeSe
Paint().also { it.xfermode = PorterDuffXfermode(PorterDuff.Mode.DST_OUT) }
val AOD_COLOR = Color.WHITE
- val OPTICAL_SIZE_AXIS = ClockFontAxisSetting("opsz", 144f)
+ val OPTICAL_SIZE_AXIS = ClockFontAxisSetting(GSFAxes.OPTICAL_SIZE, 144f)
val DEFAULT_LS_VARIATION =
listOf(
OPTICAL_SIZE_AXIS,
- ClockFontAxisSetting("wght", 400f),
- ClockFontAxisSetting("wdth", 100f),
- ClockFontAxisSetting("ROND", 0f),
- ClockFontAxisSetting("slnt", 0f),
+ ClockFontAxisSetting(GSFAxes.WEIGHT, 400f),
+ ClockFontAxisSetting(GSFAxes.WIDTH, 100f),
+ ClockFontAxisSetting(GSFAxes.ROUND, 0f),
+ ClockFontAxisSetting(GSFAxes.SLANT, 0f),
)
val DEFAULT_AOD_VARIATION =
listOf(
OPTICAL_SIZE_AXIS,
- ClockFontAxisSetting("wght", 200f),
- ClockFontAxisSetting("wdth", 100f),
- ClockFontAxisSetting("ROND", 0f),
- ClockFontAxisSetting("slnt", 0f),
+ ClockFontAxisSetting(GSFAxes.WEIGHT, 200f),
+ ClockFontAxisSetting(GSFAxes.WIDTH, 100f),
+ ClockFontAxisSetting(GSFAxes.ROUND, 0f),
+ ClockFontAxisSetting(GSFAxes.SLANT, 0f),
)
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/IMagnificationConnectionTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/IMagnificationConnectionTest.java
index b087ecf1a488..6d75c4ca3a38 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/IMagnificationConnectionTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/IMagnificationConnectionTest.java
@@ -46,7 +46,7 @@ import androidx.test.filters.SmallTest;
import com.android.app.viewcapture.ViewCaptureAwareWindowManager;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.model.SysUiState;
-import com.android.systemui.recents.OverviewProxyService;
+import com.android.systemui.recents.LauncherProxyService;
import com.android.systemui.settings.FakeDisplayTracker;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.util.settings.SecureSettings;
@@ -86,7 +86,7 @@ public class IMagnificationConnectionTest extends SysuiTestCase {
@Mock
private IRemoteMagnificationAnimationCallback mAnimationCallback;
@Mock
- private OverviewProxyService mOverviewProxyService;
+ private LauncherProxyService mLauncherProxyService;
@Mock
private SecureSettings mSecureSettings;
@Mock
@@ -114,7 +114,7 @@ public class IMagnificationConnectionTest extends SysuiTestCase {
assertNotNull(mTestableLooper);
mMagnification = new MagnificationImpl(getContext(),
mTestableLooper.getLooper(), mContext.getMainExecutor(), mCommandQueue,
- mModeSwitchesController, mSysUiState, mOverviewProxyService, mSecureSettings,
+ mModeSwitchesController, mSysUiState, mLauncherProxyService, mSecureSettings,
mDisplayTracker, getContext().getSystemService(DisplayManager.class),
mA11yLogger, mIWindowManager, mAccessibilityManager,
mViewCaptureAwareWindowManager);
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/animation/FontVariationUtilsTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/animation/FontVariationUtilsTest.kt
index b0f81c012cca..f44769d522eb 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/animation/FontVariationUtilsTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/animation/FontVariationUtilsTest.kt
@@ -7,11 +7,6 @@ import junit.framework.Assert
import org.junit.Test
import org.junit.runner.RunWith
-private const val TAG_WGHT = "wght"
-private const val TAG_WDTH = "wdth"
-private const val TAG_OPSZ = "opsz"
-private const val TAG_ROND = "ROND"
-
@RunWith(AndroidJUnit4::class)
@SmallTest
class FontVariationUtilsTest : SysuiTestCase() {
@@ -23,19 +18,22 @@ class FontVariationUtilsTest : SysuiTestCase() {
weight = 100,
width = 100,
opticalSize = -1,
- roundness = 100
+ roundness = 100,
)
- Assert.assertEquals("'$TAG_WGHT' 100, '$TAG_WDTH' 100, '$TAG_ROND' 100", initFvar)
+ Assert.assertEquals(
+ "'${GSFAxes.WEIGHT}' 100, '${GSFAxes.WIDTH}' 100, '${GSFAxes.ROUND}' 100",
+ initFvar,
+ )
val updatedFvar =
fontVariationUtils.updateFontVariation(
weight = 200,
width = 100,
opticalSize = 0,
- roundness = 100
+ roundness = 100,
)
Assert.assertEquals(
- "'$TAG_WGHT' 200, '$TAG_WDTH' 100, '$TAG_OPSZ' 0, '$TAG_ROND' 100",
- updatedFvar
+ "'${GSFAxes.WEIGHT}' 200, '${GSFAxes.WIDTH}' 100, '${GSFAxes.OPTICAL_SIZE}' 0, '${GSFAxes.ROUND}' 100",
+ updatedFvar,
)
}
@@ -46,14 +44,14 @@ class FontVariationUtilsTest : SysuiTestCase() {
weight = 100,
width = 100,
opticalSize = 0,
- roundness = 100
+ roundness = 100,
)
val updatedFvar1 =
fontVariationUtils.updateFontVariation(
weight = 100,
width = 100,
opticalSize = 0,
- roundness = 100
+ roundness = 100,
)
Assert.assertEquals("", updatedFvar1)
val updatedFvar2 = fontVariationUtils.updateFontVariation()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorParameterizedTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorParameterizedTest.kt
index 5e023a203267..6899d23bcfd7 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorParameterizedTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorParameterizedTest.kt
@@ -34,7 +34,7 @@ import com.android.systemui.inputdevice.tutorial.data.repository.DeviceType
import com.android.systemui.inputdevice.tutorial.tutorialSchedulerRepository
import com.android.systemui.keyboard.data.repository.keyboardRepository
import com.android.systemui.kosmos.testScope
-import com.android.systemui.recents.OverviewProxyService.OverviewProxyListener
+import com.android.systemui.recents.LauncherProxyService.LauncherProxyListener
import com.android.systemui.testKosmos
import com.android.systemui.touchpad.data.repository.touchpadRepository
import com.android.systemui.user.data.repository.fakeUserRepository
@@ -68,7 +68,7 @@ class KeyboardTouchpadEduInteractorParameterizedTest(private val gestureType: Ge
private val keyboardRepository = kosmos.keyboardRepository
private val tutorialSchedulerRepository = kosmos.tutorialSchedulerRepository
private val userRepository = kosmos.fakeUserRepository
- private val overviewProxyService = kosmos.mockOverviewProxyService
+ private val launcherProxyService = kosmos.mockLauncherProxyService
private val underTest: KeyboardTouchpadEduInteractor = kosmos.keyboardTouchpadEduInteractor
private val eduClock = kosmos.fakeEduClock
@@ -519,8 +519,8 @@ class KeyboardTouchpadEduInteractorParameterizedTest(private val gestureType: Ge
}
private fun updateContextualEduStats(isTrackpadGesture: Boolean, gestureType: GestureType) {
- val listenerCaptor = argumentCaptor<OverviewProxyListener>()
- verify(overviewProxyService).addCallback(listenerCaptor.capture())
+ val listenerCaptor = argumentCaptor<LauncherProxyListener>()
+ verify(launcherProxyService).addCallback(listenerCaptor.capture())
listenerCaptor.firstValue.updateContextualEduStats(isTrackpadGesture, gestureType)
}
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 692b9c67f322..dc393c01dce1 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
@@ -32,7 +32,7 @@ import com.android.systemui.inputdevice.tutorial.data.repository.DeviceType
import com.android.systemui.inputdevice.tutorial.tutorialSchedulerRepository
import com.android.systemui.keyboard.data.repository.keyboardRepository
import com.android.systemui.kosmos.testScope
-import com.android.systemui.recents.OverviewProxyService.OverviewProxyListener
+import com.android.systemui.recents.LauncherProxyService.LauncherProxyListener
import com.android.systemui.testKosmos
import com.android.systemui.touchpad.data.repository.touchpadRepository
import com.google.common.truth.Truth.assertThat
@@ -58,7 +58,7 @@ class KeyboardTouchpadEduInteractorTest : SysuiTestCase() {
private val touchpadRepository = kosmos.touchpadRepository
private val keyboardRepository = kosmos.keyboardRepository
private val tutorialSchedulerRepository = kosmos.tutorialSchedulerRepository
- private val overviewProxyService = kosmos.mockOverviewProxyService
+ private val launcherProxyService = kosmos.mockLauncherProxyService
private val underTest: KeyboardTouchpadEduInteractor = kosmos.keyboardTouchpadEduInteractor
private val eduClock = kosmos.fakeEduClock
@@ -167,16 +167,16 @@ class KeyboardTouchpadEduInteractorTest : SysuiTestCase() {
keyboardRepository.setIsAnyKeyboardConnected(true)
}
- private fun getOverviewProxyListener(): OverviewProxyListener {
- val listenerCaptor = argumentCaptor<OverviewProxyListener>()
- verify(overviewProxyService).addCallback(listenerCaptor.capture())
+ private fun getLauncherProxyListener(): LauncherProxyListener {
+ val listenerCaptor = argumentCaptor<LauncherProxyListener>()
+ verify(launcherProxyService).addCallback(listenerCaptor.capture())
return listenerCaptor.firstValue
}
private fun TestScope.triggerEducation(gestureType: GestureType) {
// Increment max number of signal to try triggering education
for (i in 1..KeyboardTouchpadEduInteractor.MAX_SIGNAL_COUNT) {
- val listener = getOverviewProxyListener()
+ val listener = getLauncherProxyListener()
listener.updateContextualEduStats(/* isTrackpadGesture= */ false, gestureType)
}
runCurrent()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/CustomInputGesturesRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/CustomInputGesturesRepositoryTest.kt
index 698fac107a1d..4d81cb0ce726 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/CustomInputGesturesRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/CustomInputGesturesRepositoryTest.kt
@@ -19,6 +19,7 @@ package com.android.systemui.keyboard.shortcut.data.repository
import android.content.Context
import android.content.Context.INPUT_SERVICE
import android.content.Intent
+import android.hardware.input.FakeInputManager
import android.hardware.input.InputGestureData
import android.hardware.input.InputManager
import android.hardware.input.InputManager.CUSTOM_INPUT_GESTURE_RESULT_SUCCESS
@@ -57,13 +58,14 @@ class CustomInputGesturesRepositoryTest : SysuiTestCase() {
private val secondaryUserContext: Context = mock()
private var activeUserContext: Context = primaryUserContext
- private val kosmos = testKosmos().also {
- it.userTracker = FakeUserTracker(onCreateCurrentUserContext = { activeUserContext })
- }
+ private val kosmos =
+ testKosmos().also {
+ it.userTracker = FakeUserTracker(onCreateCurrentUserContext = { activeUserContext })
+ }
private val inputManager = kosmos.fakeInputManager.inputManager
private val broadcastDispatcher = kosmos.broadcastDispatcher
- private val inputManagerForSecondaryUser: InputManager = mock()
+ private val inputManagerForSecondaryUser: InputManager = FakeInputManager().inputManager
private val testScope = kosmos.testScope
private val testHelper = kosmos.shortcutHelperTestHelper
private val customInputGesturesRepository = kosmos.customInputGesturesRepository
@@ -94,9 +96,10 @@ class CustomInputGesturesRepositoryTest : SysuiTestCase() {
fun customInputGestures_initialValueReturnsDataFromAPI() {
testScope.runTest {
val customInputGestures = listOf(allAppsInputGestureData)
- whenever(
- inputManager.getCustomInputGestures(/* filter= */ InputGestureData.Filter.KEY)
- ).then { return@then customInputGestures }
+ whenever(inputManager.getCustomInputGestures(/* filter= */ InputGestureData.Filter.KEY))
+ .then {
+ return@then customInputGestures
+ }
val inputGestures by collectLastValue(customInputGesturesRepository.customInputGestures)
@@ -108,9 +111,10 @@ class CustomInputGesturesRepositoryTest : SysuiTestCase() {
fun customInputGestures_isUpdatedToMostRecentDataAfterNewGestureIsAdded() {
testScope.runTest {
var customInputGestures = listOf<InputGestureData>()
- whenever(
- inputManager.getCustomInputGestures(/* filter= */ InputGestureData.Filter.KEY)
- ).then { return@then customInputGestures }
+ whenever(inputManager.getCustomInputGestures(/* filter= */ InputGestureData.Filter.KEY))
+ .then {
+ return@then customInputGestures
+ }
whenever(inputManager.addCustomInputGesture(any())).then { invocation ->
val inputGesture = invocation.getArgument<InputGestureData>(0)
customInputGestures = customInputGestures + inputGesture
@@ -129,10 +133,10 @@ class CustomInputGesturesRepositoryTest : SysuiTestCase() {
fun retrieveCustomInputGestures_retrievesMostRecentData() {
testScope.runTest {
var customInputGestures = listOf<InputGestureData>()
- whenever(
- inputManager.getCustomInputGestures(/* filter= */ InputGestureData.Filter.KEY)
- ).then { return@then customInputGestures }
-
+ whenever(inputManager.getCustomInputGestures(/* filter= */ InputGestureData.Filter.KEY))
+ .then {
+ return@then customInputGestures
+ }
assertThat(customInputGesturesRepository.retrieveCustomInputGestures()).isEmpty()
@@ -143,24 +147,38 @@ class CustomInputGesturesRepositoryTest : SysuiTestCase() {
}
}
+ @Test
+ fun getInputGestureByTrigger_returnsInputGestureFromInputManager() =
+ testScope.runTest {
+ inputManager.addCustomInputGesture(allAppsInputGestureData)
+
+ val inputGestureData =
+ customInputGesturesRepository.getInputGestureByTrigger(
+ allAppsInputGestureData.trigger
+ )
+
+ assertThat(inputGestureData).isEqualTo(allAppsInputGestureData)
+ }
+
private fun setCustomInputGesturesForPrimaryUser(vararg inputGesture: InputGestureData) {
- whenever(
- inputManager.getCustomInputGestures(/* filter= */ InputGestureData.Filter.KEY)
- ).thenReturn(inputGesture.toList())
+ whenever(inputManager.getCustomInputGestures(/* filter= */ InputGestureData.Filter.KEY))
+ .thenReturn(inputGesture.toList())
}
private fun setCustomInputGesturesForSecondaryUser(vararg inputGesture: InputGestureData) {
whenever(
- inputManagerForSecondaryUser.getCustomInputGestures(/* filter= */ InputGestureData.Filter.KEY)
- ).thenReturn(inputGesture.toList())
+ inputManagerForSecondaryUser.getCustomInputGestures(
+ /* filter= */ InputGestureData.Filter.KEY
+ )
+ )
+ .thenReturn(inputGesture.toList())
}
private fun switchToSecondaryUser() {
activeUserContext = secondaryUserContext
broadcastDispatcher.sendIntentToMatchingReceiversOnly(
context,
- Intent(Intent.ACTION_USER_SWITCHED)
+ Intent(Intent.ACTION_USER_SWITCHED),
)
}
-
-} \ No newline at end of file
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/CustomShortcutCategoriesRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/CustomShortcutCategoriesRepositoryTest.kt
index 4cfb26e6555b..522572dcffb7 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/CustomShortcutCategoriesRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/CustomShortcutCategoriesRepositoryTest.kt
@@ -24,6 +24,7 @@ import android.hardware.input.InputGestureData
import android.hardware.input.InputGestureData.createKeyTrigger
import android.hardware.input.InputManager.CUSTOM_INPUT_GESTURE_RESULT_ERROR_DOES_NOT_EXIST
import android.hardware.input.InputManager.CUSTOM_INPUT_GESTURE_RESULT_SUCCESS
+import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_HOME
import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION
import android.hardware.input.fakeInputManager
import android.platform.test.annotations.DisableFlags
@@ -336,28 +337,6 @@ class CustomShortcutCategoriesRepositoryTest : SysuiTestCase() {
}
}
- private suspend fun customizeShortcut(
- customizationRequest: ShortcutCustomizationRequestInfo,
- keyCombination: KeyCombination? = null,
- ): ShortcutCustomizationRequestResult {
- repo.onCustomizationRequested(customizationRequest)
- repo.updateUserKeyCombination(keyCombination)
-
- return when (customizationRequest) {
- is SingleShortcutCustomization.Add -> {
- repo.confirmAndSetShortcutCurrentlyBeingCustomized()
- }
-
- is SingleShortcutCustomization.Delete -> {
- repo.deleteShortcutCurrentlyBeingCustomized()
- }
-
- else -> {
- ShortcutCustomizationRequestResult.ERROR_OTHER
- }
- }
- }
-
@Test
@EnableFlags(FLAG_ENABLE_CUSTOMIZABLE_INPUT_GESTURES, FLAG_USE_KEY_GESTURE_EVENT_HANDLER)
fun categories_isUpdatedAfterCustomShortcutsAreReset() {
@@ -387,10 +366,66 @@ class CustomShortcutCategoriesRepositoryTest : SysuiTestCase() {
}
}
+ @Test
+ fun selectedKeyCombinationIsAvailable_whenTriggerIsNotRegisteredInInputManager() =
+ testScope.runTest {
+ helper.toggle(deviceId = 123)
+ repo.onCustomizationRequested(allAppsShortcutAddRequest)
+ repo.updateUserKeyCombination(standardKeyCombination)
+
+ assertThat(repo.isSelectedKeyCombinationAvailable()).isTrue()
+ }
+
+ @Test
+ fun selectedKeyCombinationIsNotAvailable_whenTriggerIsRegisteredInInputManager() =
+ testScope.runTest {
+ inputManager.addCustomInputGesture(buildInputGestureWithStandardKeyCombination())
+
+ helper.toggle(deviceId = 123)
+ repo.onCustomizationRequested(allAppsShortcutAddRequest)
+ repo.updateUserKeyCombination(standardKeyCombination)
+
+ assertThat(repo.isSelectedKeyCombinationAvailable()).isFalse()
+ }
+
private fun setApiAppLaunchBookmarks(appLaunchBookmarks: List<InputGestureData>) {
whenever(inputManager.appLaunchBookmarks).thenReturn(appLaunchBookmarks)
}
+ private suspend fun customizeShortcut(
+ customizationRequest: ShortcutCustomizationRequestInfo,
+ keyCombination: KeyCombination? = null,
+ ): ShortcutCustomizationRequestResult {
+ repo.onCustomizationRequested(customizationRequest)
+ repo.updateUserKeyCombination(keyCombination)
+
+ return when (customizationRequest) {
+ is SingleShortcutCustomization.Add -> {
+ repo.confirmAndSetShortcutCurrentlyBeingCustomized()
+ }
+
+ is SingleShortcutCustomization.Delete -> {
+ repo.deleteShortcutCurrentlyBeingCustomized()
+ }
+
+ else -> {
+ ShortcutCustomizationRequestResult.ERROR_OTHER
+ }
+ }
+ }
+
+ private fun buildInputGestureWithStandardKeyCombination() =
+ InputGestureData.Builder()
+ .setKeyGestureType(KEY_GESTURE_TYPE_HOME)
+ .setTrigger(
+ createKeyTrigger(
+ /* keycode= */ standardKeyCombination.keyCode!!,
+ /* modifierState= */ standardKeyCombination.modifiers and
+ ALL_SUPPORTED_MODIFIERS,
+ )
+ )
+ .build()
+
private fun simpleInputGestureDataForAppLaunchShortcut(
keyCode: Int = KEYCODE_A,
modifiers: Int = META_CTRL_ON or META_ALT_ON,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/DefaultShortcutCategoriesRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/DefaultShortcutCategoriesRepositoryTest.kt
index 3bf59f34db76..cd05980385e0 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/DefaultShortcutCategoriesRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/DefaultShortcutCategoriesRepositoryTest.kt
@@ -50,6 +50,7 @@ import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCommand
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutKey
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutSubCategory
+import com.android.systemui.keyboard.shortcut.shortcutHelperAccessibilityShortcutsSource
import com.android.systemui.keyboard.shortcut.shortcutHelperAppCategoriesShortcutsSource
import com.android.systemui.keyboard.shortcut.shortcutHelperCurrentAppShortcutsSource
import com.android.systemui.keyboard.shortcut.shortcutHelperInputShortcutsSource
@@ -88,6 +89,7 @@ class DefaultShortcutCategoriesRepositoryTest : SysuiTestCase() {
it.shortcutHelperAppCategoriesShortcutsSource = fakeAppCategoriesSource
it.shortcutHelperInputShortcutsSource = FakeKeyboardShortcutGroupsSource()
it.shortcutHelperCurrentAppShortcutsSource = FakeKeyboardShortcutGroupsSource()
+ it.shortcutHelperAccessibilityShortcutsSource = FakeKeyboardShortcutGroupsSource()
}
private val repo = kosmos.defaultShortcutCategoriesRepository
@@ -284,14 +286,20 @@ class DefaultShortcutCategoriesRepositoryTest : SysuiTestCase() {
val categories by collectLastValue(repo.categories)
val cycleForwardThroughRecentAppsShortcut =
- categories?.first { it.type == ShortcutCategoryType.MultiTasking }
- ?.subCategories?.first { it.label == recentAppsGroup.label }
- ?.shortcuts?.first { it.label == CYCLE_FORWARD_THROUGH_RECENT_APPS_SHORTCUT_LABEL }
+ categories
+ ?.first { it.type == ShortcutCategoryType.MultiTasking }
+ ?.subCategories
+ ?.first { it.label == recentAppsGroup.label }
+ ?.shortcuts
+ ?.first { it.label == CYCLE_FORWARD_THROUGH_RECENT_APPS_SHORTCUT_LABEL }
val cycleBackThroughRecentAppsShortcut =
- categories?.first { it.type == ShortcutCategoryType.MultiTasking }
- ?.subCategories?.first { it.label == recentAppsGroup.label }
- ?.shortcuts?.first { it.label == CYCLE_BACK_THROUGH_RECENT_APPS_SHORTCUT_LABEL }
+ categories
+ ?.first { it.type == ShortcutCategoryType.MultiTasking }
+ ?.subCategories
+ ?.first { it.label == recentAppsGroup.label }
+ ?.shortcuts
+ ?.first { it.label == CYCLE_BACK_THROUGH_RECENT_APPS_SHORTCUT_LABEL }
assertThat(cycleForwardThroughRecentAppsShortcut?.isCustomizable).isFalse()
assertThat(cycleBackThroughRecentAppsShortcut?.isCustomizable).isFalse()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutHelperCategoriesInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutHelperCategoriesInteractorTest.kt
index 8f0bc640f0eb..61490986f4a9 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutHelperCategoriesInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutHelperCategoriesInteractorTest.kt
@@ -38,6 +38,7 @@ import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategory
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.InputMethodEditor
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.MultiTasking
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.System
+import com.android.systemui.keyboard.shortcut.shortcutHelperAccessibilityShortcutsSource
import com.android.systemui.keyboard.shortcut.shortcutHelperAppCategoriesShortcutsSource
import com.android.systemui.keyboard.shortcut.shortcutHelperCategoriesInteractor
import com.android.systemui.keyboard.shortcut.shortcutHelperCurrentAppShortcutsSource
@@ -76,6 +77,7 @@ class ShortcutHelperCategoriesInteractorTest : SysuiTestCase() {
it.shortcutHelperMultiTaskingShortcutsSource = multitaskingShortcutsSource
it.shortcutHelperAppCategoriesShortcutsSource = FakeKeyboardShortcutGroupsSource()
it.shortcutHelperCurrentAppShortcutsSource = FakeKeyboardShortcutGroupsSource()
+ it.shortcutHelperAccessibilityShortcutsSource = FakeKeyboardShortcutGroupsSource()
it.userTracker = FakeUserTracker(onCreateCurrentUserContext = { mockUserContext })
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/ui/ShortcutHelperDialogStarterTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/ui/ShortcutHelperDialogStarterTest.kt
index 000024f9b814..7a343351ef64 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/ui/ShortcutHelperDialogStarterTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/ui/ShortcutHelperDialogStarterTest.kt
@@ -26,6 +26,7 @@ import com.android.systemui.SysuiTestCase
import com.android.systemui.keyboard.shortcut.data.source.FakeKeyboardShortcutGroupsSource
import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts
import com.android.systemui.keyboard.shortcut.shortcutCustomizationDialogStarterFactory
+import com.android.systemui.keyboard.shortcut.shortcutHelperAccessibilityShortcutsSource
import com.android.systemui.keyboard.shortcut.shortcutHelperAppCategoriesShortcutsSource
import com.android.systemui.keyboard.shortcut.shortcutHelperCurrentAppShortcutsSource
import com.android.systemui.keyboard.shortcut.shortcutHelperInputShortcutsSource
@@ -66,6 +67,7 @@ class ShortcutHelperDialogStarterTest : SysuiTestCase() {
it.testDispatcher = UnconfinedTestDispatcher()
it.shortcutHelperSystemShortcutsSource = fakeSystemSource
it.shortcutHelperMultiTaskingShortcutsSource = fakeMultiTaskingSource
+ it.shortcutHelperAccessibilityShortcutsSource = FakeKeyboardShortcutGroupsSource()
it.shortcutHelperAppCategoriesShortcutsSource = FakeKeyboardShortcutGroupsSource()
it.shortcutHelperInputShortcutsSource = FakeKeyboardShortcutGroupsSource()
it.shortcutHelperCurrentAppShortcutsSource = FakeKeyboardShortcutGroupsSource()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutCustomizationViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutCustomizationViewModelTest.kt
index d9d34f5ace7b..6eef5eb09812 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutCustomizationViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutCustomizationViewModelTest.kt
@@ -18,11 +18,15 @@ package com.android.systemui.keyboard.shortcut.ui.viewmodel
import android.content.Context
import android.content.Context.INPUT_SERVICE
-import android.hardware.input.InputManager.CUSTOM_INPUT_GESTURE_RESULT_ERROR_ALREADY_EXISTS
+import android.hardware.input.InputGestureData
+import android.hardware.input.InputGestureData.createKeyTrigger
import android.hardware.input.InputManager.CUSTOM_INPUT_GESTURE_RESULT_ERROR_OTHER
import android.hardware.input.InputManager.CUSTOM_INPUT_GESTURE_RESULT_ERROR_RESERVED_GESTURE
-import android.hardware.input.InputManager.CUSTOM_INPUT_GESTURE_RESULT_SUCCESS
+import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_HOME
import android.hardware.input.fakeInputManager
+import android.view.KeyEvent.KEYCODE_A
+import android.view.KeyEvent.META_CTRL_ON
+import android.view.KeyEvent.META_META_ON
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
@@ -30,7 +34,6 @@ import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.allAppsInputGestureData
import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.allAppsShortcutAddRequest
import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.allAppsShortcutDeleteRequest
-import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.goHomeInputGestureData
import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.keyDownEventWithActionKeyPressed
import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.keyDownEventWithoutActionKeyPressed
import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.keyUpEventWithActionKeyPressed
@@ -44,16 +47,17 @@ import com.android.systemui.keyboard.shortcut.ui.model.ShortcutCustomizationUiSt
import com.android.systemui.keyboard.shortcut.ui.model.ShortcutCustomizationUiState.DeleteShortcutDialog
import com.android.systemui.keyboard.shortcut.ui.model.ShortcutCustomizationUiState.ResetShortcutDialog
import com.android.systemui.kosmos.testScope
+import com.android.systemui.kosmos.useUnconfinedTestDispatcher
import com.android.systemui.res.R
import com.android.systemui.settings.FakeUserTracker
import com.android.systemui.settings.userTracker
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.launch
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
@@ -63,7 +67,7 @@ class ShortcutCustomizationViewModelTest : SysuiTestCase() {
private val mockUserContext: Context = mock()
private val kosmos =
- testKosmos().also {
+ testKosmos().useUnconfinedTestDispatcher().also {
it.userTracker = FakeUserTracker(onCreateCurrentUserContext = { mockUserContext })
}
private val testScope = kosmos.testScope
@@ -75,6 +79,7 @@ class ShortcutCustomizationViewModelTest : SysuiTestCase() {
fun setup() {
helper.showFromActivity()
whenever(mockUserContext.getSystemService(INPUT_SERVICE)).thenReturn(inputManager)
+ testScope.backgroundScope.launch { viewModel.activate() }
}
@Test
@@ -146,8 +151,6 @@ class ShortcutCustomizationViewModelTest : SysuiTestCase() {
fun uiState_becomeInactiveAfterSuccessfullySettingShortcut() {
testScope.runTest {
val uiState by collectLastValue(viewModel.shortcutCustomizationUiState)
- whenever(inputManager.addCustomInputGesture(any()))
- .thenReturn(CUSTOM_INPUT_GESTURE_RESULT_SUCCESS)
openAddShortcutDialogAndSetShortcut()
@@ -166,11 +169,38 @@ class ShortcutCustomizationViewModelTest : SysuiTestCase() {
}
@Test
- fun uiState_errorMessage_isKeyCombinationInUse_whenKeyCombinationAlreadyExists() {
+ fun uiState_errorMessage_onKeyPressed_isKeyCombinationInUse_whenKeyCombinationAlreadyExists() {
testScope.runTest {
+ inputManager.addCustomInputGesture(buildSimpleInputGestureWithMetaCtrlATrigger())
+ val uiState by collectLastValue(viewModel.shortcutCustomizationUiState)
+
+ openAddShortcutDialogAndPressKeyCombination()
+
+ assertThat((uiState as AddShortcutDialog).errorMessage)
+ .isEqualTo(
+ context.getString(
+ R.string.shortcut_customizer_key_combination_in_use_error_message
+ )
+ )
+ }
+ }
+
+ @Test
+ fun uiState_errorMessage_onKeyPressed_isEmpty_whenKeyCombinationIsAvailable() {
+ testScope.runTest {
+ val uiState by collectLastValue(viewModel.shortcutCustomizationUiState)
+
+ openAddShortcutDialogAndPressKeyCombination()
+
+ assertThat((uiState as AddShortcutDialog).errorMessage).isEmpty()
+ }
+ }
+
+ @Test
+ fun uiState_errorMessage_onSetShortcut_isKeyCombinationInUse_whenKeyCombinationAlreadyExists() {
+ testScope.runTest {
+ inputManager.addCustomInputGesture(buildSimpleInputGestureWithMetaCtrlATrigger())
val uiState by collectLastValue(viewModel.shortcutCustomizationUiState)
- whenever(inputManager.addCustomInputGesture(any()))
- .thenReturn(CUSTOM_INPUT_GESTURE_RESULT_ERROR_ALREADY_EXISTS)
openAddShortcutDialogAndSetShortcut()
@@ -184,11 +214,12 @@ class ShortcutCustomizationViewModelTest : SysuiTestCase() {
}
@Test
- fun uiState_errorMessage_isKeyCombinationInUse_whenKeyCombinationIsReserved() {
+ fun uiState_errorMessage_onSetShortcut_isKeyCombinationInUse_whenKeyCombinationIsReserved() {
testScope.runTest {
+ inputManager.addCustomInputGesture(buildSimpleInputGestureWithMetaCtrlATrigger())
+ kosmos.fakeInputManager.addCustomInputGestureErrorCode =
+ CUSTOM_INPUT_GESTURE_RESULT_ERROR_RESERVED_GESTURE
val uiState by collectLastValue(viewModel.shortcutCustomizationUiState)
- whenever(inputManager.addCustomInputGesture(any()))
- .thenReturn(CUSTOM_INPUT_GESTURE_RESULT_ERROR_RESERVED_GESTURE)
openAddShortcutDialogAndSetShortcut()
@@ -202,11 +233,12 @@ class ShortcutCustomizationViewModelTest : SysuiTestCase() {
}
@Test
- fun uiState_errorMessage_isGenericError_whenErrorIsUnknown() {
+ fun uiState_errorMessage_onSetShortcut_isGenericError_whenErrorIsUnknown() {
testScope.runTest {
+ inputManager.addCustomInputGesture(buildSimpleInputGestureWithMetaCtrlATrigger())
+ kosmos.fakeInputManager.addCustomInputGestureErrorCode =
+ CUSTOM_INPUT_GESTURE_RESULT_ERROR_OTHER
val uiState by collectLastValue(viewModel.shortcutCustomizationUiState)
- whenever(inputManager.addCustomInputGesture(any()))
- .thenReturn(CUSTOM_INPUT_GESTURE_RESULT_ERROR_OTHER)
openAddShortcutDialogAndSetShortcut()
@@ -219,10 +251,7 @@ class ShortcutCustomizationViewModelTest : SysuiTestCase() {
fun uiState_becomesInactiveAfterSuccessfullyDeletingShortcut() {
testScope.runTest {
val uiState by collectLastValue(viewModel.shortcutCustomizationUiState)
- whenever(inputManager.getCustomInputGestures(any()))
- .thenReturn(listOf(goHomeInputGestureData, allAppsInputGestureData))
- whenever(inputManager.removeCustomInputGesture(any()))
- .thenReturn(CUSTOM_INPUT_GESTURE_RESULT_SUCCESS)
+ inputManager.addCustomInputGesture(allAppsInputGestureData)
openDeleteShortcutDialogAndDeleteShortcut()
@@ -234,7 +263,6 @@ class ShortcutCustomizationViewModelTest : SysuiTestCase() {
fun uiState_becomesInactiveAfterSuccessfullyResettingShortcuts() {
testScope.runTest {
val uiState by collectLastValue(viewModel.shortcutCustomizationUiState)
- whenever(inputManager.getCustomInputGestures(any())).thenReturn(emptyList())
openResetShortcutDialogAndResetAllCustomShortcuts()
@@ -297,24 +325,42 @@ class ShortcutCustomizationViewModelTest : SysuiTestCase() {
}
}
+ @Test
+ fun uiState_pressedKeys_resetsToEmpty_onClearSelectedShortcutKeyCombination() {
+ testScope.runTest {
+ val uiState by collectLastValue(viewModel.shortcutCustomizationUiState)
+ viewModel.onShortcutCustomizationRequested(standardAddShortcutRequest)
+ viewModel.onShortcutKeyCombinationSelected(keyDownEventWithActionKeyPressed)
+ viewModel.onShortcutKeyCombinationSelected(keyUpEventWithActionKeyPressed)
+ viewModel.clearSelectedKeyCombination()
+ assertThat((uiState as AddShortcutDialog).pressedKeys).isEmpty()
+ }
+ }
+
private suspend fun openAddShortcutDialogAndSetShortcut() {
- viewModel.onShortcutCustomizationRequested(allAppsShortcutAddRequest)
+ openAddShortcutDialogAndPressKeyCombination()
+ viewModel.onSetShortcut()
+ }
+ private fun openAddShortcutDialogAndPressKeyCombination() {
+ viewModel.onShortcutCustomizationRequested(allAppsShortcutAddRequest)
viewModel.onShortcutKeyCombinationSelected(keyDownEventWithActionKeyPressed)
viewModel.onShortcutKeyCombinationSelected(keyUpEventWithActionKeyPressed)
-
- viewModel.onSetShortcut()
}
private suspend fun openDeleteShortcutDialogAndDeleteShortcut() {
viewModel.onShortcutCustomizationRequested(allAppsShortcutDeleteRequest)
-
viewModel.deleteShortcutCurrentlyBeingCustomized()
}
private suspend fun openResetShortcutDialogAndResetAllCustomShortcuts() {
viewModel.onShortcutCustomizationRequested(ShortcutCustomizationRequestInfo.Reset)
-
viewModel.resetAllCustomShortcuts()
}
+
+ private fun buildSimpleInputGestureWithMetaCtrlATrigger() =
+ InputGestureData.Builder()
+ .setKeyGestureType(KEY_GESTURE_TYPE_HOME)
+ .setTrigger(createKeyTrigger(KEYCODE_A, META_CTRL_ON or META_META_ON))
+ .build()
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModelTest.kt
index 3fc46b973959..cf38072912e9 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModelTest.kt
@@ -45,6 +45,7 @@ import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.System
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutSubCategory
import com.android.systemui.keyboard.shortcut.shared.model.shortcut
+import com.android.systemui.keyboard.shortcut.shortcutHelperAccessibilityShortcutsSource
import com.android.systemui.keyboard.shortcut.shortcutHelperAppCategoriesShortcutsSource
import com.android.systemui.keyboard.shortcut.shortcutHelperCurrentAppShortcutsSource
import com.android.systemui.keyboard.shortcut.shortcutHelperInputShortcutsSource
@@ -95,6 +96,7 @@ class ShortcutHelperViewModelTest : SysuiTestCase() {
it.shortcutHelperMultiTaskingShortcutsSource = fakeMultiTaskingSource
it.shortcutHelperAppCategoriesShortcutsSource = FakeKeyboardShortcutGroupsSource()
it.shortcutHelperInputShortcutsSource = FakeKeyboardShortcutGroupsSource()
+ it.shortcutHelperAccessibilityShortcutsSource = FakeKeyboardShortcutGroupsSource()
it.shortcutHelperCurrentAppShortcutsSource = fakeCurrentAppsSource
it.userTracker = FakeUserTracker(onCreateCurrentUserContext = { mockUserContext })
}
@@ -112,9 +114,12 @@ class ShortcutHelperViewModelTest : SysuiTestCase() {
fakeSystemSource.setGroups(TestShortcuts.systemGroups)
fakeMultiTaskingSource.setGroups(TestShortcuts.multitaskingGroups)
fakeCurrentAppsSource.setGroups(TestShortcuts.currentAppGroups)
- whenever(mockPackageManager.getApplicationInfo(anyString(), eq(0))).thenReturn(mockApplicationInfo)
- whenever(mockPackageManager.getApplicationLabel(mockApplicationInfo)).thenReturn("Current App")
- whenever(mockPackageManager.getApplicationIcon(anyString())).thenThrow(NameNotFoundException())
+ whenever(mockPackageManager.getApplicationInfo(anyString(), eq(0)))
+ .thenReturn(mockApplicationInfo)
+ whenever(mockPackageManager.getApplicationLabel(mockApplicationInfo))
+ .thenReturn("Current App")
+ whenever(mockPackageManager.getApplicationIcon(anyString()))
+ .thenThrow(NameNotFoundException())
whenever(mockUserContext.packageManager).thenReturn(mockPackageManager)
whenever(mockUserContext.getSystemService(INPUT_SERVICE)).thenReturn(inputManager)
}
@@ -278,11 +283,11 @@ class ShortcutHelperViewModelTest : SysuiTestCase() {
fun shortcutsUiState_currentAppIsLauncher_defaultSelectedCategoryIsSystem() =
testScope.runTest {
whenever(
- mockRoleManager.getRoleHoldersAsUser(
- RoleManager.ROLE_HOME,
- fakeUserTracker.userHandle,
+ mockRoleManager.getRoleHoldersAsUser(
+ RoleManager.ROLE_HOME,
+ fakeUserTracker.userHandle,
+ )
)
- )
.thenReturn(listOf(TestShortcuts.currentAppPackageName))
val uiState by collectLastValue(viewModel.shortcutsUiState)
@@ -318,23 +323,23 @@ class ShortcutHelperViewModelTest : SysuiTestCase() {
label = "System",
iconSource = IconSource(imageVector = Icons.Default.Tv),
shortcutCategory =
- ShortcutCategory(
- System,
- subCategoryWithShortcutLabels("first Foo shortcut1"),
- subCategoryWithShortcutLabels(
- "second foO shortcut2",
- subCategoryLabel = SECOND_SIMPLE_GROUP_LABEL,
+ ShortcutCategory(
+ System,
+ subCategoryWithShortcutLabels("first Foo shortcut1"),
+ subCategoryWithShortcutLabels(
+ "second foO shortcut2",
+ subCategoryLabel = SECOND_SIMPLE_GROUP_LABEL,
+ ),
),
- ),
),
ShortcutCategoryUi(
label = "Multitasking",
iconSource = IconSource(imageVector = Icons.Default.VerticalSplit),
shortcutCategory =
- ShortcutCategory(
- MultiTasking,
- subCategoryWithShortcutLabels("third FoO shortcut1"),
- ),
+ ShortcutCategory(
+ MultiTasking,
+ subCategoryWithShortcutLabels("third FoO shortcut1"),
+ ),
),
)
}
@@ -420,9 +425,8 @@ class ShortcutHelperViewModelTest : SysuiTestCase() {
@Test
fun shortcutsUiState_shouldShowResetButton_isTrueWhenThereAreCustomShortcuts() =
testScope.runTest {
- whenever(
- inputManager.getCustomInputGestures(/* filter= */ InputGestureData.Filter.KEY)
- ).thenReturn(listOf(allAppsInputGestureData))
+ whenever(inputManager.getCustomInputGestures(/* filter= */ InputGestureData.Filter.KEY))
+ .thenReturn(listOf(allAppsInputGestureData))
val uiState by collectLastValue(viewModel.shortcutsUiState)
testHelper.showFromActivity()
@@ -433,7 +437,7 @@ class ShortcutHelperViewModelTest : SysuiTestCase() {
@Test
fun shortcutsUiState_searchQuery_isResetAfterHelperIsClosedAndReOpened() =
- testScope.runTest{
+ testScope.runTest {
val uiState by collectLastValue(viewModel.shortcutsUiState)
openHelperAndSearchForFooString()
@@ -443,7 +447,7 @@ class ShortcutHelperViewModelTest : SysuiTestCase() {
assertThat((uiState as? ShortcutsUiState.Active)?.searchQuery).isEqualTo("")
}
- private fun openHelperAndSearchForFooString(){
+ private fun openHelperAndSearchForFooString() {
testHelper.showFromActivity()
viewModel.onSearchQueryChanged("foo")
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
index c8a16483a00c..abcbdb153e80 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
@@ -27,6 +27,8 @@ import com.android.keyguard.KeyguardSecurityModel.SecurityMode.PIN
import com.android.systemui.Flags
import com.android.systemui.Flags.FLAG_COMMUNAL_HUB
import com.android.systemui.Flags.FLAG_COMMUNAL_SCENE_KTF_REFACTOR
+import com.android.systemui.Flags.FLAG_GLANCEABLE_HUB_V2
+import com.android.systemui.Flags.glanceableHubV2
import com.android.systemui.SysuiTestCase
import com.android.systemui.bouncer.data.repository.fakeKeyguardBouncerRepository
import com.android.systemui.communal.domain.interactor.CommunalSceneTransitionInteractor
@@ -34,6 +36,8 @@ import com.android.systemui.communal.domain.interactor.communalInteractor
import com.android.systemui.communal.domain.interactor.communalSceneInteractor
import com.android.systemui.communal.domain.interactor.communalSceneTransitionInteractor
import com.android.systemui.communal.domain.interactor.setCommunalAvailable
+import com.android.systemui.communal.domain.interactor.setCommunalV2ConfigEnabled
+import com.android.systemui.communal.domain.interactor.setCommunalV2Enabled
import com.android.systemui.communal.shared.model.CommunalScenes
import com.android.systemui.flags.BrokenWithSceneContainer
import com.android.systemui.flags.DisableSceneContainer
@@ -153,6 +157,9 @@ class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTest
if (!SceneContainerFlag.isEnabled) {
mSetFlagsRule.disableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
}
+ if (glanceableHubV2()) {
+ kosmos.setCommunalV2ConfigEnabled(true)
+ }
featureFlags = FakeFeatureFlags()
fromLockscreenTransitionInteractor.start()
@@ -1948,6 +1955,39 @@ class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTest
@Test
@DisableSceneContainer
+ @EnableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR, FLAG_GLANCEABLE_HUB_V2)
+ fun glanceableHubToDreaming_v2() =
+ testScope.runTest {
+ kosmos.setCommunalV2Enabled(true)
+
+ // GIVEN a device that is not dreaming or dozing
+ keyguardRepository.setDreamingWithOverlay(false)
+ keyguardRepository.setDozeTransitionModel(
+ DozeTransitionModel(from = DozeStateModel.DOZE, to = DozeStateModel.FINISH)
+ )
+ advanceTimeBy(600.milliseconds)
+
+ // GIVEN a prior transition has run to glanceable hub
+ communalSceneInteractor.changeScene(CommunalScenes.Communal, "test")
+ runCurrent()
+ clearInvocations(transitionRepository)
+
+ keyguardRepository.setDreamingWithOverlay(true)
+ advanceTimeBy(100.milliseconds)
+
+ assertThat(transitionRepository)
+ .startedTransition(
+ ownerName = CommunalSceneTransitionInteractor::class.simpleName,
+ from = KeyguardState.GLANCEABLE_HUB,
+ to = KeyguardState.DREAMING,
+ animatorAssertion = { it.isNull() },
+ )
+
+ coroutineContext.cancelChildren()
+ }
+
+ @Test
+ @DisableSceneContainer
@DisableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
fun glanceableHubToDreaming() =
testScope.runTest {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorTest.kt
index 9d5bf4dbdc3f..a276f514b779 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorTest.kt
@@ -1045,6 +1045,41 @@ class WindowManagerLockscreenVisibilityInteractorTest : SysuiTestCase() {
assertThat(usingKeyguardGoingAwayAnimation).isFalse()
}
+ @Test
+ fun aodVisibility_visibleFullyInAod_falseOtherwise() =
+ testScope.runTest {
+ val aodVisibility by collectValues(underTest.value.aodVisibility)
+
+ transitionRepository.sendTransitionStepsThroughRunning(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.AOD,
+ testScope,
+ throughValue = 0.5f,
+ )
+
+ assertEquals(listOf(false), aodVisibility)
+
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ transitionState = TransitionState.FINISHED,
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.AOD,
+ )
+ )
+ runCurrent()
+
+ assertEquals(listOf(false, true), aodVisibility)
+
+ transitionRepository.sendTransitionStepsThroughRunning(
+ from = KeyguardState.AOD,
+ to = KeyguardState.GONE,
+ testScope,
+ )
+ runCurrent()
+
+ assertEquals(listOf(false, true, false), aodVisibility)
+ }
+
companion object {
private val progress = MutableStateFlow(0f)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToPrimaryBouncerTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToPrimaryBouncerTransitionViewModelTest.kt
index 83b821619659..e93ed39274fb 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToPrimaryBouncerTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToPrimaryBouncerTransitionViewModelTest.kt
@@ -20,6 +20,7 @@ import android.platform.test.annotations.EnableFlags
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.Flags.FLAG_BOUNCER_UI_REVAMP
+import com.android.systemui.Flags.FLAG_NOTIFICATION_SHADE_BLUR
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectValues
import com.android.systemui.flags.BrokenWithSceneContainer
@@ -97,6 +98,7 @@ class AlternateBouncerToPrimaryBouncerTransitionViewModelTest : SysuiTestCase()
}
@Test
+ @EnableFlags(FLAG_NOTIFICATION_SHADE_BLUR)
fun blurRadiusGoesToMaximumWhenShadeIsExpanded() =
testScope.runTest {
val values by collectValues(underTest.windowBlurRadius)
@@ -123,8 +125,8 @@ class AlternateBouncerToPrimaryBouncerTransitionViewModelTest : SysuiTestCase()
kosmos.keyguardWindowBlurTestUtil.assertTransitionToBlurRadius(
transitionProgress = listOf(0f, 0f, 0.1f, 0.2f, 0.3f, 1f),
- startValue = kosmos.blurConfig.maxBlurRadiusPx / 3.0f,
- endValue = kosmos.blurConfig.maxBlurRadiusPx / 3.0f,
+ startValue = kosmos.blurConfig.maxBlurRadiusPx,
+ endValue = kosmos.blurConfig.maxBlurRadiusPx,
transitionFactory = ::step,
actualValuesProvider = { values },
checkInterpolatedValues = false,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardMediaViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardMediaViewModelTest.kt
new file mode 100644
index 000000000000..38829da69c28
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardMediaViewModelTest.kt
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.keyguard.data.repository.keyguardRepository
+import com.android.systemui.kosmos.runTest
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.kosmos.useUnconfinedTestDispatcher
+import com.android.systemui.lifecycle.activateIn
+import com.android.systemui.media.controls.data.repository.mediaFilterRepository
+import com.android.systemui.media.controls.shared.model.MediaData
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class KeyguardMediaViewModelTest : SysuiTestCase() {
+ private val kosmos = testKosmos().useUnconfinedTestDispatcher()
+
+ private val underTest = kosmos.keyguardMediaViewModelFactory.create()
+
+ @Before
+ fun setUp() {
+ underTest.activateIn(kosmos.testScope)
+ }
+
+ @Test
+ fun onDozing_noActiveMedia_mediaIsHidden() =
+ kosmos.runTest {
+ keyguardRepository.setIsDozing(true)
+
+ assertThat(underTest.isMediaVisible).isFalse()
+ }
+
+ @Test
+ fun onDozing_activeMediaExists_mediaIsHidden() =
+ kosmos.runTest {
+ val userMedia = MediaData(active = true)
+
+ mediaFilterRepository.addSelectedUserMediaEntry(userMedia)
+ keyguardRepository.setIsDozing(true)
+
+ assertThat(underTest.isMediaVisible).isFalse()
+ }
+
+ @Test
+ fun onDeviceAwake_activeMediaExists_mediaIsVisible() =
+ kosmos.runTest {
+ val userMedia = MediaData(active = true)
+
+ mediaFilterRepository.addSelectedUserMediaEntry(userMedia)
+ keyguardRepository.setIsDozing(false)
+
+ assertThat(underTest.isMediaVisible).isTrue()
+ }
+
+ @Test
+ fun onDeviceAwake_noActiveMedia_mediaIsHidden() =
+ kosmos.runTest {
+ keyguardRepository.setIsDozing(false)
+
+ assertThat(underTest.isMediaVisible).isFalse()
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModelTest.kt
index fdee8d0544f0..aaca603ecf77 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModelTest.kt
@@ -21,6 +21,7 @@ import android.platform.test.flag.junit.FlagsParameterization
import androidx.test.filters.SmallTest
import com.android.compose.animation.scene.ObservableTransitionState
import com.android.systemui.Flags.FLAG_BOUNCER_UI_REVAMP
+import com.android.systemui.Flags.FLAG_NOTIFICATION_SHADE_BLUR
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.coroutines.collectValues
@@ -155,6 +156,7 @@ class LockscreenToPrimaryBouncerTransitionViewModelTest(flags: FlagsParameteriza
}
@Test
+ @EnableFlags(FLAG_NOTIFICATION_SHADE_BLUR)
@BrokenWithSceneContainer(388068805)
fun blurRadiusIsMaxWhenShadeIsExpanded() =
testScope.runTest {
@@ -198,8 +200,8 @@ class LockscreenToPrimaryBouncerTransitionViewModelTest(flags: FlagsParameteriza
kosmos.keyguardWindowBlurTestUtil.assertTransitionToBlurRadius(
transitionProgress = listOf(0f, 0f, 0.1f, 0.2f, 0.3f, 1f),
- startValue = kosmos.blurConfig.maxBlurRadiusPx / 3.0f,
- endValue = kosmos.blurConfig.maxBlurRadiusPx / 3.0f,
+ startValue = kosmos.blurConfig.maxBlurRadiusPx,
+ endValue = kosmos.blurConfig.maxBlurRadiusPx,
transitionFactory = ::step,
actualValuesProvider = { values },
checkInterpolatedValues = false,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToPrimaryBouncerTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToPrimaryBouncerTransitionViewModelTest.kt
index 6db876756d3a..0951df24c56f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToPrimaryBouncerTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToPrimaryBouncerTransitionViewModelTest.kt
@@ -16,50 +16,96 @@
package com.android.systemui.keyguard.ui.viewmodel
-import androidx.test.ext.junit.runners.AndroidJUnit4
+import android.platform.test.annotations.EnableFlags
+import android.platform.test.flag.junit.FlagsParameterization
import androidx.test.filters.SmallTest
+import com.android.systemui.Flags.FLAG_NOTIFICATION_SHADE_BLUR
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectValues
-import com.android.systemui.flags.DisableSceneContainer
+import com.android.systemui.flags.BrokenWithSceneContainer
+import com.android.systemui.flags.andSceneContainer
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.keyguard.ui.transitions.blurConfig
import com.android.systemui.kosmos.testScope
import com.android.systemui.testKosmos
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runTest
+import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
@ExperimentalCoroutinesApi
@SmallTest
-@RunWith(AndroidJUnit4::class)
-class OccludedToPrimaryBouncerTransitionViewModelTest : SysuiTestCase() {
+@RunWith(ParameterizedAndroidJunit4::class)
+class OccludedToPrimaryBouncerTransitionViewModelTest(flags: FlagsParameterization) :
+ SysuiTestCase() {
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
- private val underTest by lazy { kosmos.occludedToPrimaryBouncerTransitionViewModel }
+ private lateinit var underTest: OccludedToPrimaryBouncerTransitionViewModel
+
+ companion object {
+ @JvmStatic
+ @Parameters(name = "{0}")
+ fun getParams(): List<FlagsParameterization> {
+ return FlagsParameterization.allCombinationsOf().andSceneContainer()
+ }
+ }
+
+ init {
+ mSetFlagsRule.setFlagsParameterization(flags)
+ }
+
+ @Before
+ fun setup() {
+ underTest = kosmos.occludedToPrimaryBouncerTransitionViewModel
+ }
@Test
- @DisableSceneContainer
- fun blurBecomesMaxValueImmediately() =
+ @BrokenWithSceneContainer(388068805)
+ fun notificationsAreBlurredImmediatelyWhenBouncerIsOpenedAndShadeIsExpanded() =
+ testScope.runTest {
+ val values by collectValues(underTest.notificationBlurRadius)
+ kosmos.keyguardWindowBlurTestUtil.shadeExpanded(true)
+
+ kosmos.keyguardWindowBlurTestUtil.assertTransitionToBlurRadius(
+ transitionProgress = listOf(0.0f, 0.2f, 0.3f, 0.65f, 0.7f, 1.0f),
+ startValue = kosmos.blurConfig.maxBlurRadiusPx,
+ endValue = kosmos.blurConfig.maxBlurRadiusPx,
+ actualValuesProvider = { values },
+ transitionFactory = ::step,
+ checkInterpolatedValues = false,
+ )
+ }
+
+ @Test
+ @EnableFlags(FLAG_NOTIFICATION_SHADE_BLUR)
+ @BrokenWithSceneContainer(388068805)
+ fun blurBecomesMaxValueImmediatelyWhenShadeIsAlreadyExpanded() =
testScope.runTest {
val values by collectValues(underTest.windowBlurRadius)
+ kosmos.keyguardWindowBlurTestUtil.shadeExpanded(true)
kosmos.keyguardWindowBlurTestUtil.assertTransitionToBlurRadius(
transitionProgress = listOf(0.0f, 0.2f, 0.3f, 0.65f, 0.7f, 1.0f),
startValue = kosmos.blurConfig.maxBlurRadiusPx,
endValue = kosmos.blurConfig.maxBlurRadiusPx,
actualValuesProvider = { values },
- transitionFactory = { step, transitionState ->
- TransitionStep(
- from = KeyguardState.OCCLUDED,
- to = KeyguardState.PRIMARY_BOUNCER,
- value = step,
- transitionState = transitionState,
- ownerName = "OccludedToPrimaryBouncerTransitionViewModelTest",
- )
- },
+ transitionFactory = ::step,
checkInterpolatedValues = false,
)
}
+
+ fun step(value: Float, state: TransitionState = TransitionState.RUNNING): TransitionStep {
+ return TransitionStep(
+ from = KeyguardState.OCCLUDED,
+ to = KeyguardState.PRIMARY_BOUNCER,
+ value = value,
+ transitionState = state,
+ ownerName = "OccludedToPrimaryBouncerTransitionViewModelTest",
+ )
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModelTest.kt
index 0db0c5fe8482..8fefb8d40b71 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModelTest.kt
@@ -16,12 +16,16 @@
package com.android.systemui.keyguard.ui.viewmodel
-import androidx.test.ext.junit.runners.AndroidJUnit4
+import android.platform.test.annotations.EnableFlags
+import android.platform.test.flag.junit.FlagsParameterization
import androidx.test.filters.SmallTest
+import com.android.systemui.Flags.FLAG_NOTIFICATION_SHADE_BLUR
import com.android.systemui.SysuiTestCase
import com.android.systemui.biometrics.data.repository.fingerprintPropertyRepository
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.coroutines.collectValues
+import com.android.systemui.flags.BrokenWithSceneContainer
+import com.android.systemui.flags.andSceneContainer
import com.android.systemui.keyguard.data.repository.biometricSettingsRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
@@ -35,13 +39,17 @@ import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
+import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
@ExperimentalCoroutinesApi
@SmallTest
-@RunWith(AndroidJUnit4::class)
-class PrimaryBouncerToLockscreenTransitionViewModelTest : SysuiTestCase() {
+@RunWith(ParameterizedAndroidJunit4::class)
+class PrimaryBouncerToLockscreenTransitionViewModelTest(flags: FlagsParameterization) :
+ SysuiTestCase() {
val kosmos = testKosmos()
val testScope = kosmos.testScope
@@ -49,9 +57,27 @@ class PrimaryBouncerToLockscreenTransitionViewModelTest : SysuiTestCase() {
val fingerprintPropertyRepository = kosmos.fingerprintPropertyRepository
val biometricSettingsRepository = kosmos.biometricSettingsRepository
- val underTest = kosmos.primaryBouncerToLockscreenTransitionViewModel
+ private lateinit var underTest: PrimaryBouncerToLockscreenTransitionViewModel
+
+ companion object {
+ @JvmStatic
+ @Parameters(name = "{0}")
+ fun getParams(): List<FlagsParameterization> {
+ return FlagsParameterization.allCombinationsOf().andSceneContainer()
+ }
+ }
+
+ init {
+ mSetFlagsRule.setFlagsParameterization(flags)
+ }
+
+ @Before
+ fun setup() {
+ underTest = kosmos.primaryBouncerToLockscreenTransitionViewModel
+ }
@Test
+ @BrokenWithSceneContainer(392346450)
fun lockscreenAlphaStartsFromViewStateAccessorAlpha() =
testScope.runTest {
val viewState = ViewStateAccessor(alpha = { 0.5f })
@@ -70,6 +96,7 @@ class PrimaryBouncerToLockscreenTransitionViewModelTest : SysuiTestCase() {
}
@Test
+ @BrokenWithSceneContainer(392346450)
fun deviceEntryParentViewAlpha() =
testScope.runTest {
val deviceEntryParentViewAlpha by collectLastValue(underTest.deviceEntryParentViewAlpha)
@@ -89,6 +116,7 @@ class PrimaryBouncerToLockscreenTransitionViewModelTest : SysuiTestCase() {
}
@Test
+ @BrokenWithSceneContainer(392346450)
fun deviceEntryBackgroundViewAlpha_udfpsEnrolled_show() =
testScope.runTest {
fingerprintPropertyRepository.supportsUdfps()
@@ -113,6 +141,7 @@ class PrimaryBouncerToLockscreenTransitionViewModelTest : SysuiTestCase() {
}
@Test
+ @BrokenWithSceneContainer(388068805)
fun blurRadiusGoesFromMaxToMinWhenShadeIsNotExpanded() =
testScope.runTest {
val values by collectValues(underTest.windowBlurRadius)
@@ -128,6 +157,8 @@ class PrimaryBouncerToLockscreenTransitionViewModelTest : SysuiTestCase() {
}
@Test
+ @EnableFlags(FLAG_NOTIFICATION_SHADE_BLUR)
+ @BrokenWithSceneContainer(388068805)
fun blurRadiusRemainsAtMaxWhenShadeIsExpanded() =
testScope.runTest {
val values by collectValues(underTest.windowBlurRadius)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToOccludedTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToOccludedTransitionViewModelTest.kt
index b0b4af5fea5b..fd7fb9f863c8 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToOccludedTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToOccludedTransitionViewModelTest.kt
@@ -16,11 +16,13 @@
package com.android.systemui.keyguard.ui.viewmodel
+import android.platform.test.annotations.EnableFlags
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectValues
-import com.android.systemui.flags.DisableSceneContainer
+import com.android.systemui.flags.BrokenWithSceneContainer
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionStep
import com.android.systemui.keyguard.ui.transitions.blurConfig
@@ -40,10 +42,37 @@ class PrimaryBouncerToOccludedTransitionViewModelTest : SysuiTestCase() {
private val underTest by lazy { kosmos.primaryBouncerToOccludedTransitionViewModel }
@Test
- @DisableSceneContainer
- fun blurBecomesMaxValueImmediately() =
+ @BrokenWithSceneContainer(388068805)
+ fun blurBecomesMinValueImmediatelyWhenShadeIsNotExpanded() =
testScope.runTest {
val values by collectValues(underTest.windowBlurRadius)
+ kosmos.keyguardWindowBlurTestUtil.shadeExpanded(false)
+
+ kosmos.keyguardWindowBlurTestUtil.assertTransitionToBlurRadius(
+ transitionProgress = listOf(0.0f, 0.2f, 0.3f, 0.65f, 0.7f, 1.0f),
+ startValue = kosmos.blurConfig.minBlurRadiusPx,
+ endValue = kosmos.blurConfig.minBlurRadiusPx,
+ actualValuesProvider = { values },
+ transitionFactory = { step, transitionState ->
+ TransitionStep(
+ from = KeyguardState.PRIMARY_BOUNCER,
+ to = KeyguardState.OCCLUDED,
+ value = step,
+ transitionState = transitionState,
+ ownerName = "PrimaryBouncerToOccludedTransitionViewModelTest",
+ )
+ },
+ checkInterpolatedValues = false,
+ )
+ }
+
+ @Test
+ @BrokenWithSceneContainer(388068805)
+ @EnableFlags(Flags.FLAG_NOTIFICATION_SHADE_BLUR)
+ fun blurBecomesMaxValueImmediatelyWhenShadeIsExpanded() =
+ testScope.runTest {
+ val values by collectValues(underTest.windowBlurRadius)
+ kosmos.keyguardWindowBlurTestUtil.shadeExpanded(false)
kosmos.keyguardWindowBlurTestUtil.assertTransitionToBlurRadius(
transitionProgress = listOf(0.0f, 0.2f, 0.3f, 0.65f, 0.7f, 1.0f),
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java
index 83cae49fca2f..7478464772a4 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java
@@ -259,7 +259,6 @@ public class MediaOutputAdapterTest extends SysuiTestCase {
mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0);
assertThat(mViewHolder.mSeekBar.getContentDescription()).isNotNull();
- assertThat(mViewHolder.mSeekBar.getAccessibilityDelegate()).isNotNull();
assertThat(mViewHolder.mContainerLayout.isFocusable()).isFalse();
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/dialog/OWNERS b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/dialog/OWNERS
new file mode 100644
index 000000000000..739d2ac2e87b
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/dialog/OWNERS
@@ -0,0 +1 @@
+file:/packages/SystemUI/src/com/android/systemui/media/dialog/OWNERS
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/NavBarHelperTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/NavBarHelperTest.java
index a770ee199ba6..c1872f05aa1d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/NavBarHelperTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/NavBarHelperTest.java
@@ -57,7 +57,7 @@ import com.android.systemui.accessibility.SystemActions;
import com.android.systemui.assist.AssistManager;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.navigationbar.gestural.EdgeBackGestureHandler;
-import com.android.systemui.recents.OverviewProxyService;
+import com.android.systemui.recents.LauncherProxyService;
import com.android.systemui.settings.DisplayTracker;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.CommandQueue;
@@ -104,7 +104,7 @@ public class NavBarHelperTest extends SysuiTestCase {
@Mock
SystemActions mSystemActions;
@Mock
- OverviewProxyService mOverviewProxyService;
+ LauncherProxyService mLauncherProxyService;
@Mock
Lazy<AssistManager> mAssistManagerLazy;
@Mock
@@ -161,7 +161,7 @@ public class NavBarHelperTest extends SysuiTestCase {
mNavBarHelper = new NavBarHelper(mContext, mAccessibilityManager,
mAccessibilityButtonModeObserver, mAccessibilityButtonTargetObserver,
mAccessibilityGestureTargetObserver,
- mSystemActions, mOverviewProxyService, mAssistManagerLazy,
+ mSystemActions, mLauncherProxyService, mAssistManagerLazy,
() -> Optional.of(mock(CentralSurfaces.class)), mock(KeyguardStateController.class),
mNavigationModeController, mEdgeBackGestureHandlerFactory, mWm, mUserTracker,
mDisplayTracker, mNotificationShadeWindowController, mConfigurationController,
@@ -171,7 +171,7 @@ public class NavBarHelperTest extends SysuiTestCase {
@Test
public void registerListenersInCtor() {
verify(mNavigationModeController, times(1)).addListener(mNavBarHelper);
- verify(mOverviewProxyService, times(1)).addCallback(mNavBarHelper);
+ verify(mLauncherProxyService, times(1)).addCallback(mNavBarHelper);
verify(mCommandQueue, times(1)).addCallback(any());
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/TaskbarDelegateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/TaskbarDelegateTest.kt
index 9bae7bd72f7d..cf0a25020d93 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/TaskbarDelegateTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/TaskbarDelegateTest.kt
@@ -9,7 +9,7 @@ import com.android.systemui.dump.DumpManager
import com.android.systemui.model.SysUiState
import com.android.systemui.navigationbar.gestural.EdgeBackGestureHandler
import com.android.systemui.plugins.statusbar.StatusBarStateController
-import com.android.systemui.recents.OverviewProxyService
+import com.android.systemui.recents.LauncherProxyService
import com.android.systemui.settings.DisplayTracker
import com.android.systemui.shared.system.QuickStepContract
import com.android.systemui.shared.system.TaskStackChangeListeners
@@ -49,7 +49,7 @@ class TaskbarDelegateTest : SysuiTestCase() {
@Mock lateinit var mLightBarControllerFactory: LightBarTransitionsController.Factory
@Mock lateinit var mLightBarTransitionController: LightBarTransitionsController
@Mock lateinit var mCommandQueue: CommandQueue
- @Mock lateinit var mOverviewProxyService: OverviewProxyService
+ @Mock lateinit var mLauncherProxyService: LauncherProxyService
@Mock lateinit var mNavBarHelper: NavBarHelper
@Mock lateinit var mNavigationModeController: NavigationModeController
@Mock lateinit var mSysUiState: SysUiState
@@ -87,7 +87,7 @@ class TaskbarDelegateTest : SysuiTestCase() {
)
mTaskbarDelegate.setDependencies(
mCommandQueue,
- mOverviewProxyService,
+ mLauncherProxyService,
mNavBarHelper,
mNavigationModeController,
mSysUiState,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/views/NavigationBarButtonTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/views/NavigationBarButtonTest.java
index 00e79f5a3ac2..fd4bb4bb8a37 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/views/NavigationBarButtonTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/views/NavigationBarButtonTest.java
@@ -40,7 +40,7 @@ import com.android.systemui.SysuiTestableContext;
import com.android.systemui.assist.AssistManager;
import com.android.systemui.navigationbar.NavigationBarController;
import com.android.systemui.navigationbar.gestural.EdgeBackGestureHandler;
-import com.android.systemui.recents.OverviewProxyService;
+import com.android.systemui.recents.LauncherProxyService;
import com.android.systemui.settings.FakeDisplayTracker;
import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -80,7 +80,7 @@ public class NavigationBarButtonTest extends SysuiTestCase {
.thenReturn(mEdgeBackGestureHandler);
mDependency.injectMockDependency(AssistManager.class);
- mDependency.injectMockDependency(OverviewProxyService.class);
+ mDependency.injectMockDependency(LauncherProxyService.class);
mDependency.injectMockDependency(KeyguardStateController.class);
mDependency.injectMockDependency(NavigationBarController.class);
mDependency.injectTestDependency(EdgeBackGestureHandler.Factory.class,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/views/NavigationBarInflaterViewTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/views/NavigationBarInflaterViewTest.java
index e58c8f281fc1..85c093c16d88 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/views/NavigationBarInflaterViewTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/views/NavigationBarInflaterViewTest.java
@@ -35,7 +35,7 @@ import com.android.systemui.assist.AssistManager;
import com.android.systemui.navigationbar.NavigationBarController;
import com.android.systemui.navigationbar.NavigationModeController;
import com.android.systemui.navigationbar.views.buttons.ButtonDispatcher;
-import com.android.systemui.recents.OverviewProxyService;
+import com.android.systemui.recents.LauncherProxyService;
import org.junit.After;
import org.junit.Before;
@@ -55,7 +55,7 @@ public class NavigationBarInflaterViewTest extends SysuiTestCase {
@Before
public void setUp() {
mDependency.injectMockDependency(AssistManager.class);
- mDependency.injectMockDependency(OverviewProxyService.class);
+ mDependency.injectMockDependency(LauncherProxyService.class);
mDependency.injectMockDependency(NavigationModeController.class);
mDependency.injectMockDependency(NavigationBarController.class);
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/views/NavigationBarTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/views/NavigationBarTest.java
index 7f9313cbeb5b..09e49eb217b0 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/views/NavigationBarTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/views/NavigationBarTest.java
@@ -16,9 +16,9 @@
package com.android.systemui.navigationbar.views;
-import static android.app.StatusBarManager.NAVIGATION_HINT_BACK_ALT;
-import static android.app.StatusBarManager.NAVIGATION_HINT_IME_SHOWN;
-import static android.app.StatusBarManager.NAVIGATION_HINT_IME_SWITCHER_SHOWN;
+import static android.app.StatusBarManager.NAVBAR_BACK_DISMISS_IME;
+import static android.app.StatusBarManager.NAVBAR_IME_SWITCHER_BUTTON_VISIBLE;
+import static android.app.StatusBarManager.NAVBAR_IME_VISIBLE;
import static android.inputmethodservice.InputMethodService.BACK_DISPOSITION_ADJUST_NOTHING;
import static android.inputmethodservice.InputMethodService.BACK_DISPOSITION_DEFAULT;
import static android.inputmethodservice.InputMethodService.IME_VISIBLE;
@@ -31,8 +31,9 @@ import static com.android.systemui.assist.AssistManager.INVOCATION_TYPE_HOME_BUT
import static com.android.systemui.navigationbar.views.NavigationBar.NavBarActionEvent.NAVBAR_ASSIST_LONGPRESS;
import static com.android.systemui.navigationbar.views.buttons.KeyButtonView.NavBarButtonEvent.NAVBAR_IME_SWITCHER_BUTTON_LONGPRESS;
import static com.android.systemui.navigationbar.views.buttons.KeyButtonView.NavBarButtonEvent.NAVBAR_IME_SWITCHER_BUTTON_TAP;
-import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SHOWING;
-import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SWITCHER_SHOWING;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BACK_DISMISS_IME;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SWITCHER_BUTTON_VISIBLE;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_VISIBLE;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SCREEN_PINNING;
import static com.google.common.truth.Truth.assertThat;
@@ -105,7 +106,7 @@ import com.android.systemui.navigationbar.views.buttons.KeyButtonView;
import com.android.systemui.navigationbar.views.buttons.NavBarButtonClickLogger;
import com.android.systemui.navigationbar.views.buttons.NavbarOrientationTrackingLogger;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.recents.OverviewProxyService;
+import com.android.systemui.recents.LauncherProxyService;
import com.android.systemui.recents.Recents;
import com.android.systemui.settings.DisplayTracker;
import com.android.systemui.settings.FakeDisplayTracker;
@@ -184,7 +185,7 @@ public class NavigationBarTest extends SysuiTestCase {
@Mock
private SystemActions mSystemActions;
@Mock
- private OverviewProxyService mOverviewProxyService;
+ private LauncherProxyService mLauncherProxyService;
@Mock
private StatusBarStateController mStatusBarStateController;
@Mock
@@ -284,14 +285,14 @@ public class NavigationBarTest extends SysuiTestCase {
mDependency.injectMockDependency(KeyguardStateController.class);
mDependency.injectTestDependency(StatusBarStateController.class, mStatusBarStateController);
mDependency.injectMockDependency(NavigationBarController.class);
- mDependency.injectTestDependency(OverviewProxyService.class, mOverviewProxyService);
+ mDependency.injectTestDependency(LauncherProxyService.class, mLauncherProxyService);
mDependency.injectTestDependency(NavigationModeController.class, mNavigationModeController);
TestableLooper.get(this).runWithLooper(() -> {
mNavBarHelper = spy(new NavBarHelper(mContext, mock(AccessibilityManager.class),
mock(AccessibilityButtonModeObserver.class),
mock(AccessibilityButtonTargetsObserver.class),
mock(AccessibilityGestureTargetsObserver.class),
- mSystemActions, mOverviewProxyService,
+ mSystemActions, mLauncherProxyService,
() -> mock(AssistManager.class), () -> Optional.of(mCentralSurfaces),
mKeyguardStateController, mock(NavigationModeController.class),
mEdgeBackGestureHandlerFactory, mock(IWindowManager.class),
@@ -500,8 +501,9 @@ public class NavigationBarTest extends SysuiTestCase {
mNavigationBar.setImeWindowStatus(DEFAULT_DISPLAY, IME_VISIBLE,
BACK_DISPOSITION_DEFAULT, true /* showImeSwitcher */);
- verify(mMockSysUiState).setFlag(eq(SYSUI_STATE_IME_SHOWING), eq(true));
- verify(mMockSysUiState).setFlag(eq(SYSUI_STATE_IME_SWITCHER_SHOWING), eq(true));
+ verify(mMockSysUiState).setFlag(eq(SYSUI_STATE_IME_VISIBLE), eq(true));
+ verify(mMockSysUiState).setFlag(eq(SYSUI_STATE_IME_SWITCHER_BUTTON_VISIBLE), eq(true));
+ verify(mMockSysUiState).setFlag(eq(SYSUI_STATE_BACK_DISMISS_IME), eq(true));
}
/**
@@ -514,8 +516,9 @@ public class NavigationBarTest extends SysuiTestCase {
mNavigationBar.setImeWindowStatus(DEFAULT_DISPLAY, IME_VISIBLE,
BACK_DISPOSITION_DEFAULT, false /* showImeSwitcher */);
- verify(mMockSysUiState).setFlag(eq(SYSUI_STATE_IME_SHOWING), eq(true));
- verify(mMockSysUiState).setFlag(eq(SYSUI_STATE_IME_SWITCHER_SHOWING), eq(false));
+ verify(mMockSysUiState).setFlag(eq(SYSUI_STATE_IME_VISIBLE), eq(true));
+ verify(mMockSysUiState).setFlag(eq(SYSUI_STATE_IME_SWITCHER_BUTTON_VISIBLE), eq(false));
+ verify(mMockSysUiState).setFlag(eq(SYSUI_STATE_BACK_DISMISS_IME), eq(true));
}
/**
@@ -531,8 +534,9 @@ public class NavigationBarTest extends SysuiTestCase {
mNavigationBar.setImeWindowStatus(DEFAULT_DISPLAY, 0 /* vis */,
BACK_DISPOSITION_DEFAULT, true /* showImeSwitcher */);
- verify(mMockSysUiState).setFlag(eq(SYSUI_STATE_IME_SHOWING), eq(false));
- verify(mMockSysUiState).setFlag(eq(SYSUI_STATE_IME_SWITCHER_SHOWING), eq(false));
+ verify(mMockSysUiState).setFlag(eq(SYSUI_STATE_IME_VISIBLE), eq(false));
+ verify(mMockSysUiState).setFlag(eq(SYSUI_STATE_IME_SWITCHER_BUTTON_VISIBLE), eq(false));
+ verify(mMockSysUiState).setFlag(eq(SYSUI_STATE_BACK_DISMISS_IME), eq(false));
}
/**
@@ -545,8 +549,9 @@ public class NavigationBarTest extends SysuiTestCase {
mNavigationBar.setImeWindowStatus(DEFAULT_DISPLAY, IME_VISIBLE,
BACK_DISPOSITION_ADJUST_NOTHING, true /* showImeSwitcher */);
- verify(mMockSysUiState).setFlag(eq(SYSUI_STATE_IME_SHOWING), eq(true));
- verify(mMockSysUiState).setFlag(eq(SYSUI_STATE_IME_SWITCHER_SHOWING), eq(true));
+ verify(mMockSysUiState).setFlag(eq(SYSUI_STATE_IME_VISIBLE), eq(true));
+ verify(mMockSysUiState).setFlag(eq(SYSUI_STATE_IME_SWITCHER_BUTTON_VISIBLE), eq(true));
+ verify(mMockSysUiState).setFlag(eq(SYSUI_STATE_BACK_DISMISS_IME), eq(false));
}
@Test
@@ -564,29 +569,27 @@ public class NavigationBarTest extends SysuiTestCase {
externalNavBar.init();
defaultNavBar.setImeWindowStatus(DEFAULT_DISPLAY, IME_VISIBLE,
- BACK_DISPOSITION_DEFAULT, true);
+ BACK_DISPOSITION_DEFAULT, true /* showImeSwitcher */);
// Verify IME window state will be updated in default NavBar & external NavBar state reset.
- assertEquals(NAVIGATION_HINT_BACK_ALT | NAVIGATION_HINT_IME_SHOWN
- | NAVIGATION_HINT_IME_SWITCHER_SHOWN,
- defaultNavBar.getNavigationIconHints());
- assertFalse((externalNavBar.getNavigationIconHints() & NAVIGATION_HINT_BACK_ALT) != 0);
- assertFalse((externalNavBar.getNavigationIconHints() & NAVIGATION_HINT_IME_SHOWN) != 0);
- assertFalse((externalNavBar.getNavigationIconHints() & NAVIGATION_HINT_IME_SWITCHER_SHOWN)
- != 0);
+ assertEquals(NAVBAR_BACK_DISMISS_IME | NAVBAR_IME_VISIBLE
+ | NAVBAR_IME_SWITCHER_BUTTON_VISIBLE,
+ defaultNavBar.getNavbarFlags());
+ assertFalse((externalNavBar.getNavbarFlags() & NAVBAR_BACK_DISMISS_IME) != 0);
+ assertFalse((externalNavBar.getNavbarFlags() & NAVBAR_IME_VISIBLE) != 0);
+ assertFalse((externalNavBar.getNavbarFlags() & NAVBAR_IME_SWITCHER_BUTTON_VISIBLE) != 0);
externalNavBar.setImeWindowStatus(EXTERNAL_DISPLAY_ID, IME_VISIBLE,
- BACK_DISPOSITION_DEFAULT, true);
+ BACK_DISPOSITION_DEFAULT, true /* showImeSwitcher */);
defaultNavBar.setImeWindowStatus(DEFAULT_DISPLAY, 0 /* vis */,
- BACK_DISPOSITION_DEFAULT, false);
+ BACK_DISPOSITION_DEFAULT, false /* showImeSwitcher */);
// Verify IME window state will be updated in external NavBar & default NavBar state reset.
- assertEquals(NAVIGATION_HINT_BACK_ALT | NAVIGATION_HINT_IME_SHOWN
- | NAVIGATION_HINT_IME_SWITCHER_SHOWN,
- externalNavBar.getNavigationIconHints());
- assertFalse((defaultNavBar.getNavigationIconHints() & NAVIGATION_HINT_BACK_ALT) != 0);
- assertFalse((defaultNavBar.getNavigationIconHints() & NAVIGATION_HINT_IME_SHOWN) != 0);
- assertFalse((defaultNavBar.getNavigationIconHints() & NAVIGATION_HINT_IME_SWITCHER_SHOWN)
- != 0);
+ assertEquals(NAVBAR_BACK_DISMISS_IME | NAVBAR_IME_VISIBLE
+ | NAVBAR_IME_SWITCHER_BUTTON_VISIBLE,
+ externalNavBar.getNavbarFlags());
+ assertFalse((defaultNavBar.getNavbarFlags() & NAVBAR_BACK_DISMISS_IME) != 0);
+ assertFalse((defaultNavBar.getNavbarFlags() & NAVBAR_IME_VISIBLE) != 0);
+ assertFalse((defaultNavBar.getNavbarFlags() & NAVBAR_IME_SWITCHER_BUTTON_VISIBLE) != 0);
}
@Test
@@ -601,32 +604,29 @@ public class NavigationBarTest extends SysuiTestCase {
// Verify navbar altered back icon when an app is showing IME
mNavigationBar.setImeWindowStatus(DEFAULT_DISPLAY, IME_VISIBLE,
- BACK_DISPOSITION_DEFAULT, true);
- assertTrue((mNavigationBar.getNavigationIconHints() & NAVIGATION_HINT_BACK_ALT) != 0);
- assertTrue((mNavigationBar.getNavigationIconHints() & NAVIGATION_HINT_IME_SHOWN) != 0);
- assertTrue((mNavigationBar.getNavigationIconHints() & NAVIGATION_HINT_IME_SWITCHER_SHOWN)
- != 0);
+ BACK_DISPOSITION_DEFAULT, true /* showImeSwitcher */);
+ assertTrue((mNavigationBar.getNavbarFlags() & NAVBAR_BACK_DISMISS_IME) != 0);
+ assertTrue((mNavigationBar.getNavbarFlags() & NAVBAR_IME_VISIBLE) != 0);
+ assertTrue((mNavigationBar.getNavbarFlags() & NAVBAR_IME_SWITCHER_BUTTON_VISIBLE) != 0);
// Verify navbar didn't alter and showing back icon when the keyguard is showing without
// requesting IME insets visible.
doReturn(true).when(mKeyguardStateController).isShowing();
mNavigationBar.setImeWindowStatus(DEFAULT_DISPLAY, IME_VISIBLE,
- BACK_DISPOSITION_DEFAULT, true);
- assertFalse((mNavigationBar.getNavigationIconHints() & NAVIGATION_HINT_BACK_ALT) != 0);
- assertFalse((mNavigationBar.getNavigationIconHints() & NAVIGATION_HINT_IME_SHOWN) != 0);
- assertFalse((mNavigationBar.getNavigationIconHints() & NAVIGATION_HINT_IME_SWITCHER_SHOWN)
- != 0);
+ BACK_DISPOSITION_DEFAULT, true /* showImeSwitcher */);
+ assertFalse((mNavigationBar.getNavbarFlags() & NAVBAR_BACK_DISMISS_IME) != 0);
+ assertFalse((mNavigationBar.getNavbarFlags() & NAVBAR_IME_VISIBLE) != 0);
+ assertFalse((mNavigationBar.getNavbarFlags() & NAVBAR_IME_SWITCHER_BUTTON_VISIBLE) != 0);
// Verify navbar altered and showing back icon when the keyguard is showing and
// requesting IME insets visible.
windowInsets = new WindowInsets.Builder().setVisible(ime(), true).build();
doReturn(windowInsets).when(mockShadeWindowView).getRootWindowInsets();
mNavigationBar.setImeWindowStatus(DEFAULT_DISPLAY, IME_VISIBLE,
- BACK_DISPOSITION_DEFAULT, true);
- assertTrue((mNavigationBar.getNavigationIconHints() & NAVIGATION_HINT_BACK_ALT) != 0);
- assertTrue((mNavigationBar.getNavigationIconHints() & NAVIGATION_HINT_IME_SHOWN) != 0);
- assertTrue((mNavigationBar.getNavigationIconHints() & NAVIGATION_HINT_IME_SWITCHER_SHOWN)
- != 0);
+ BACK_DISPOSITION_DEFAULT, true /* showImeSwitcher */);
+ assertTrue((mNavigationBar.getNavbarFlags() & NAVBAR_BACK_DISMISS_IME) != 0);
+ assertTrue((mNavigationBar.getNavbarFlags() & NAVBAR_IME_VISIBLE) != 0);
+ assertTrue((mNavigationBar.getNavbarFlags() & NAVBAR_IME_SWITCHER_BUTTON_VISIBLE) != 0);
}
@Test
@@ -690,7 +690,7 @@ public class NavigationBarTest extends SysuiTestCase {
mock(AccessibilityManager.class),
deviceProvisionedController,
new MetricsLogger(),
- mOverviewProxyService,
+ mLauncherProxyService,
mNavigationModeController,
mStatusBarStateController,
mStatusBarKeyguardViewManager,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/views/NavigationBarTransitionsTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/views/NavigationBarTransitionsTest.java
index 3621ab975daf..cff9beccc729 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/views/NavigationBarTransitionsTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/views/NavigationBarTransitionsTest.java
@@ -36,7 +36,7 @@ import com.android.systemui.navigationbar.NavigationBarController;
import com.android.systemui.navigationbar.NavigationModeController;
import com.android.systemui.navigationbar.gestural.EdgeBackGestureHandler;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.recents.OverviewProxyService;
+import com.android.systemui.recents.LauncherProxyService;
import com.android.systemui.settings.FakeDisplayTracker;
import com.android.systemui.shared.statusbar.phone.BarTransitions;
import com.android.systemui.statusbar.phone.LightBarTransitionsController;
@@ -72,7 +72,7 @@ public class NavigationBarTransitionsTest extends SysuiTestCase {
when(mEdgeBackGestureHandlerFactory.create(any(Context.class)))
.thenReturn(mEdgeBackGestureHandler);
mDependency.injectMockDependency(AssistManager.class);
- mDependency.injectMockDependency(OverviewProxyService.class);
+ mDependency.injectMockDependency(LauncherProxyService.class);
mDependency.injectMockDependency(StatusBarStateController.class);
mDependency.injectMockDependency(KeyguardStateController.class);
mDependency.injectMockDependency(NavigationBarController.class);
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/views/buttons/KeyButtonViewTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/views/buttons/KeyButtonViewTest.java
index 403a883e1760..58ec0c7a0e72 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/views/buttons/KeyButtonViewTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/views/buttons/KeyButtonViewTest.java
@@ -51,7 +51,7 @@ import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.UiEventLogger;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.assist.AssistManager;
-import com.android.systemui.recents.OverviewProxyService;
+import com.android.systemui.recents.LauncherProxyService;
import org.junit.Before;
import org.junit.Test;
@@ -76,7 +76,7 @@ public class KeyButtonViewTest extends SysuiTestCase {
public void setup() throws Exception {
MockitoAnnotations.initMocks(this);
mMetricsLogger = mDependency.injectMockDependency(MetricsLogger.class);
- mDependency.injectMockDependency(OverviewProxyService.class);
+ mDependency.injectMockDependency(LauncherProxyService.class);
mDependency.injectMockDependency(AssistManager.class);
mUiEventLogger = mDependency.injectMockDependency(UiEventLogger.class);
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayActionsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayActionsViewModelTest.kt
index 855931c32671..52b9e47e6d3d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayActionsViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayActionsViewModelTest.kt
@@ -21,7 +21,9 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.compose.animation.scene.Back
import com.android.compose.animation.scene.Swipe
-import com.android.compose.animation.scene.UserActionResult
+import com.android.compose.animation.scene.UserActionResult.HideOverlay
+import com.android.compose.animation.scene.UserActionResult.ShowOverlay
+import com.android.compose.animation.scene.UserActionResult.ShowOverlay.HideCurrentOverlays
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.flags.EnableSceneContainer
@@ -53,7 +55,7 @@ class NotificationsShadeOverlayActionsViewModelTest : SysuiTestCase() {
val actions by collectLastValue(underTest.actions)
underTest.activateIn(this)
- assertThat((actions?.get(Swipe.Up) as? UserActionResult.HideOverlay)?.overlay)
+ assertThat((actions?.get(Swipe.Up) as? HideOverlay)?.overlay)
.isEqualTo(Overlays.NotificationsShade)
assertThat(actions?.get(Swipe.Down)).isNull()
}
@@ -64,7 +66,7 @@ class NotificationsShadeOverlayActionsViewModelTest : SysuiTestCase() {
val actions by collectLastValue(underTest.actions)
underTest.activateIn(this)
- assertThat((actions?.get(Back) as? UserActionResult.HideOverlay)?.overlay)
+ assertThat((actions?.get(Back) as? HideOverlay)?.overlay)
.isEqualTo(Overlays.NotificationsShade)
}
@@ -74,11 +76,11 @@ class NotificationsShadeOverlayActionsViewModelTest : SysuiTestCase() {
val actions by collectLastValue(underTest.actions)
underTest.activateIn(this)
- assertThat(
- (actions?.get(Swipe.Down(fromSource = SceneContainerEdge.TopRight))
- as? UserActionResult.ReplaceByOverlay)
- ?.overlay
- )
- .isEqualTo(Overlays.QuickSettingsShade)
+ val action =
+ (actions?.get(Swipe.Down(fromSource = SceneContainerEdge.TopRight)) as? ShowOverlay)
+ assertThat(action?.overlay).isEqualTo(Overlays.QuickSettingsShade)
+ val overlaysToHide = action?.hideCurrentOverlays as? HideCurrentOverlays.Some
+ assertThat(overlaysToHide).isNotNull()
+ assertThat(overlaysToHide?.overlays).containsExactly(Overlays.NotificationsShade)
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeUserActionsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeUserActionsViewModelTest.kt
deleted file mode 100644
index 46b02e92a4f9..000000000000
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeUserActionsViewModelTest.kt
+++ /dev/null
@@ -1,161 +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.notifications.ui.viewmodel
-
-import android.testing.TestableLooper
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import com.android.compose.animation.scene.Swipe
-import com.android.compose.animation.scene.UserActionResult
-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.coroutines.collectLastValue
-import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
-import com.android.systemui.deviceentry.domain.interactor.deviceUnlockedInteractor
-import com.android.systemui.flags.EnableSceneContainer
-import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository
-import com.android.systemui.keyguard.domain.interactor.keyguardEnabledInteractor
-import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
-import com.android.systemui.kosmos.testScope
-import com.android.systemui.lifecycle.activateIn
-import com.android.systemui.scene.domain.interactor.sceneInteractor
-import com.android.systemui.scene.domain.resolver.homeSceneFamilyResolver
-import com.android.systemui.scene.shared.model.SceneFamilies
-import com.android.systemui.scene.shared.model.Scenes
-import com.android.systemui.shade.ui.viewmodel.notificationsShadeUserActionsViewModel
-import com.android.systemui.testKosmos
-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.Test
-import org.junit.runner.RunWith
-
-@OptIn(ExperimentalCoroutinesApi::class)
-@SmallTest
-@RunWith(AndroidJUnit4::class)
-@TestableLooper.RunWithLooper
-@EnableSceneContainer
-class NotificationsShadeUserActionsViewModelTest : SysuiTestCase() {
-
- private val kosmos = testKosmos()
- private val testScope = kosmos.testScope
- private val sceneInteractor by lazy { kosmos.sceneInteractor }
- private val deviceUnlockedInteractor by lazy { kosmos.deviceUnlockedInteractor }
-
- private val underTest by lazy { kosmos.notificationsShadeUserActionsViewModel }
-
- @Test
- fun upTransitionSceneKey_deviceLocked_lockscreen() =
- testScope.runTest {
- val actions by collectLastValue(underTest.actions)
- lockDevice()
- underTest.activateIn(this)
-
- assertThat((actions?.get(Swipe.Up) as? UserActionResult.ChangeScene)?.toScene)
- .isEqualTo(SceneFamilies.Home)
- assertThat(actions?.get(Swipe.Down)).isNull()
- assertThat(kosmos.homeSceneFamilyResolver.resolvedScene.value)
- .isEqualTo(Scenes.Lockscreen)
- }
-
- @Test
- fun upTransitionSceneKey_deviceLocked_keyguardDisabled_gone() =
- testScope.runTest {
- val actions by collectLastValue(underTest.actions)
- lockDevice()
- kosmos.keyguardEnabledInteractor.notifyKeyguardEnabled(false)
- underTest.activateIn(this)
-
- assertThat((actions?.get(Swipe.Up) as? UserActionResult.ChangeScene)?.toScene)
- .isEqualTo(SceneFamilies.Home)
- assertThat(kosmos.homeSceneFamilyResolver.resolvedScene.value).isEqualTo(Scenes.Gone)
- }
-
- @Test
- fun upTransitionSceneKey_deviceUnlocked_gone() =
- testScope.runTest {
- val actions by collectLastValue(underTest.actions)
- lockDevice()
- unlockDevice()
- underTest.activateIn(this)
-
- assertThat((actions?.get(Swipe.Up) as? UserActionResult.ChangeScene)?.toScene)
- .isEqualTo(SceneFamilies.Home)
- assertThat(actions?.get(Swipe.Down)).isNull()
- assertThat(sceneInteractor.currentScene.value).isEqualTo(Scenes.Gone)
- }
-
- @Test
- fun upTransitionSceneKey_authMethodSwipe_lockscreenNotDismissed_goesToLockscreen() =
- testScope.runTest {
- val actions by collectLastValue(underTest.actions)
- kosmos.fakeDeviceEntryRepository.setLockscreenEnabled(true)
- kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.None
- )
- sceneInteractor.changeScene(Scenes.Lockscreen, "reason")
- underTest.activateIn(this)
-
- assertThat((actions?.get(Swipe.Up) as? UserActionResult.ChangeScene)?.toScene)
- .isEqualTo(SceneFamilies.Home)
- assertThat(kosmos.homeSceneFamilyResolver.resolvedScene.value)
- .isEqualTo(Scenes.Lockscreen)
- }
-
- @Test
- fun upTransitionSceneKey_authMethodSwipe_lockscreenDismissed_goesToGone() =
- testScope.runTest {
- val deviceUnlockStatus by collectLastValue(deviceUnlockedInteractor.deviceUnlockStatus)
- val actions by collectLastValue(underTest.actions)
- kosmos.fakeDeviceEntryRepository.setLockscreenEnabled(true)
- kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.None
- )
- assertThat(deviceUnlockStatus?.isUnlocked).isTrue()
- sceneInteractor // force the lazy; this will kick off StateFlows
- runCurrent()
- sceneInteractor.changeScene(Scenes.Gone, "reason")
- underTest.activateIn(this)
-
- assertThat((actions?.get(Swipe.Up) as? UserActionResult.ChangeScene)?.toScene)
- .isEqualTo(SceneFamilies.Home)
- assertThat(kosmos.homeSceneFamilyResolver.resolvedScene.value).isEqualTo(Scenes.Gone)
- }
-
- private fun TestScope.lockDevice() {
- val deviceUnlockStatus by collectLastValue(deviceUnlockedInteractor.deviceUnlockStatus)
-
- kosmos.fakeAuthenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
- assertThat(deviceUnlockStatus?.isUnlocked).isFalse()
- sceneInteractor.changeScene(Scenes.Lockscreen, "reason")
- runCurrent()
- }
-
- private fun TestScope.unlockDevice() {
- val deviceUnlockStatus by collectLastValue(deviceUnlockedInteractor.deviceUnlockStatus)
-
- kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
- SuccessFingerprintAuthenticationStatus(0, true)
- )
- assertThat(deviceUnlockStatus?.isUnlocked).isTrue()
- sceneInteractor.changeScene(Scenes.Gone, "reason")
- runCurrent()
- }
-}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java
index e9633f49f76d..ff005c2b767a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java
@@ -16,7 +16,6 @@
package com.android.systemui.qs;
-import static com.android.systemui.Flags.FLAG_QUICK_SETTINGS_VISUAL_HAPTICS_LONGPRESS;
import static com.android.systemui.flags.SceneContainerFlagParameterizationKt.parameterizeSceneContainerFlag;
import static com.google.common.truth.Truth.assertThat;
@@ -41,8 +40,6 @@ import static kotlinx.coroutines.flow.StateFlowKt.MutableStateFlow;
import android.content.res.Configuration;
import android.content.res.Resources;
-import android.platform.test.annotations.DisableFlags;
-import android.platform.test.annotations.EnableFlags;
import android.platform.test.flag.junit.FlagsParameterization;
import android.testing.TestableLooper.RunWithLooper;
import android.view.ContextThemeWrapper;
@@ -87,6 +84,7 @@ import javax.inject.Provider;
import kotlinx.coroutines.flow.MutableStateFlow;
import kotlinx.coroutines.flow.StateFlow;
+
import platform.test.runner.parameterized.ParameterizedAndroidJunit4;
import platform.test.runner.parameterized.Parameters;
@@ -505,7 +503,6 @@ public class QSPanelControllerBaseTest extends SysuiTestCase {
}
@Test
- @EnableFlags(FLAG_QUICK_SETTINGS_VISUAL_HAPTICS_LONGPRESS)
public void setTiles_longPressEffectEnabled_nonNullLongPressEffectsAreProvided() {
mLongPressEffectProvider.mEffectsProvided = 0;
when(mQSHost.getTiles()).thenReturn(List.of(mQSTile, mOtherTile));
@@ -516,16 +513,6 @@ public class QSPanelControllerBaseTest extends SysuiTestCase {
}
@Test
- @DisableFlags(FLAG_QUICK_SETTINGS_VISUAL_HAPTICS_LONGPRESS)
- public void setTiles_longPressEffectDisabled_noLongPressEffectsAreProvided() {
- mLongPressEffectProvider.mEffectsProvided = 0;
- when(mQSHost.getTiles()).thenReturn(List.of(mQSTile, mOtherTile));
- mController.setTiles();
-
- assertThat(mLongPressEffectProvider.mEffectsProvided).isEqualTo(0);
- }
-
- @Test
public void setTiles_differentTiles_extraTileRemoved() {
when(mQSHost.getTiles()).thenReturn(List.of(mQSTile, mOtherTile));
mController.setTiles();
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/InternetTileNewImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/InternetTileNewImplTest.kt
index fecd8c3cacca..4c834b396df6 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/InternetTileNewImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/InternetTileNewImplTest.kt
@@ -37,6 +37,7 @@ import com.android.systemui.qs.QsEventLogger
import com.android.systemui.qs.flags.QSComposeFragment
import com.android.systemui.qs.flags.QsDetailedView
import com.android.systemui.qs.logging.QSLogger
+import com.android.systemui.qs.tiles.dialog.InternetDetailsViewModel
import com.android.systemui.qs.tiles.dialog.InternetDialogManager
import com.android.systemui.qs.tiles.dialog.WifiStateWorker
import com.android.systemui.res.R
@@ -109,6 +110,7 @@ class InternetTileNewImplTest(flags: FlagsParameterization) : SysuiTestCase() {
@Mock private lateinit var dialogManager: InternetDialogManager
@Mock private lateinit var wifiStateWorker: WifiStateWorker
@Mock private lateinit var accessPointController: AccessPointController
+ @Mock private lateinit var internetDetailsViewModelFactory: InternetDetailsViewModel.Factory
@Before
fun setUp() {
@@ -145,6 +147,7 @@ class InternetTileNewImplTest(flags: FlagsParameterization) : SysuiTestCase() {
dialogManager,
wifiStateWorker,
accessPointController,
+ internetDetailsViewModelFactory
)
underTest.initialize()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/ModesTileTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/ModesTileTest.kt
index f005375a2ef9..7bb28dbabd5e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/ModesTileTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/ModesTileTest.kt
@@ -49,6 +49,7 @@ import com.android.systemui.statusbar.policy.data.repository.zenModeRepository
import com.android.systemui.statusbar.policy.domain.interactor.zenModeInteractor
import com.android.systemui.statusbar.policy.ui.dialog.ModesDialogDelegate
import com.android.systemui.statusbar.policy.ui.dialog.modesDialogEventLogger
+import com.android.systemui.statusbar.policy.ui.dialog.viewmodel.modesDialogViewModel
import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.any
import com.android.systemui.util.settings.FakeSettings
@@ -146,6 +147,7 @@ class ModesTileTest : SysuiTestCase() {
tileDataInteractor,
mapper,
userActionInteractor,
+ kosmos.modesDialogViewModel,
)
underTest.initialize()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileUserActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileUserActionInteractorTest.kt
index e4a988860a6e..ce4a3432a5b4 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileUserActionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileUserActionInteractorTest.kt
@@ -26,13 +26,13 @@ import com.android.systemui.kosmos.testScope
import com.android.systemui.qs.tiles.base.actions.FakeQSTileIntentUserInputHandler
import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandlerSubject
import com.android.systemui.qs.tiles.base.interactor.QSTileInputTestKtx
+import com.android.systemui.qs.tiles.dialog.InternetDetailsContentManager
+import com.android.systemui.qs.tiles.dialog.InternetDetailsViewModel
import com.android.systemui.qs.tiles.dialog.InternetDialogManager
import com.android.systemui.qs.tiles.dialog.WifiStateWorker
import com.android.systemui.qs.tiles.impl.internet.domain.model.InternetTileModel
import com.android.systemui.statusbar.connectivity.AccessPointController
-import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.nullable
-import com.google.common.truth.Truth
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -40,9 +40,10 @@ import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.anyBoolean
import org.mockito.ArgumentMatchers.eq
-import org.mockito.Mock
-import org.mockito.Mockito.verify
+import org.mockito.kotlin.any
+import org.mockito.kotlin.mock
import org.mockito.kotlin.times
+import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever
@SmallTest
@@ -54,15 +55,27 @@ class InternetTileUserActionInteractorTest : SysuiTestCase() {
private lateinit var underTest: InternetTileUserActionInteractor
- @Mock private lateinit var internetDialogManager: InternetDialogManager
- @Mock private lateinit var wifiStateWorker: WifiStateWorker
- @Mock private lateinit var controller: AccessPointController
+ private lateinit var internetDialogManager: InternetDialogManager
+ private lateinit var wifiStateWorker: WifiStateWorker
+ private lateinit var controller: AccessPointController
+ private lateinit var internetDetailsViewModelFactory: InternetDetailsViewModel.Factory
+ private lateinit var internetDetailsContentManagerFactory: InternetDetailsContentManager.Factory
+ private lateinit var internetDetailsViewModel: InternetDetailsViewModel
@Before
fun setup() {
internetDialogManager = mock<InternetDialogManager>()
wifiStateWorker = mock<WifiStateWorker>()
controller = mock<AccessPointController>()
+ internetDetailsViewModelFactory = mock<InternetDetailsViewModel.Factory>()
+ internetDetailsContentManagerFactory = mock<InternetDetailsContentManager.Factory>()
+ internetDetailsViewModel =
+ InternetDetailsViewModel(
+ onLongClick = {},
+ accessPointController = mock<AccessPointController>(),
+ contentManagerFactory = internetDetailsContentManagerFactory,
+ )
+ whenever(internetDetailsViewModelFactory.create(any())).thenReturn(internetDetailsViewModel)
underTest =
InternetTileUserActionInteractor(
@@ -71,6 +84,7 @@ class InternetTileUserActionInteractorTest : SysuiTestCase() {
wifiStateWorker,
controller,
inputHandler,
+ internetDetailsViewModelFactory,
)
}
@@ -102,7 +116,7 @@ class InternetTileUserActionInteractorTest : SysuiTestCase() {
underTest.handleInput(QSTileInputTestKtx.longClick(input))
QSTileIntentUserInputHandlerSubject.assertThat(inputHandler).handledOneIntentInput {
- Truth.assertThat(it.intent.action).isEqualTo(Settings.ACTION_WIFI_SETTINGS)
+ assertThat(it.intent.action).isEqualTo(Settings.ACTION_WIFI_SETTINGS)
}
}
@@ -114,7 +128,7 @@ class InternetTileUserActionInteractorTest : SysuiTestCase() {
underTest.handleInput(QSTileInputTestKtx.longClick(input))
QSTileIntentUserInputHandlerSubject.assertThat(inputHandler).handledOneIntentInput {
- Truth.assertThat(it.intent.action).isEqualTo(Settings.ACTION_WIFI_SETTINGS)
+ assertThat(it.intent.action).isEqualTo(Settings.ACTION_WIFI_SETTINGS)
}
}
@@ -141,8 +155,7 @@ class InternetTileUserActionInteractorTest : SysuiTestCase() {
@Test
fun detailsViewModel() =
kosmos.testScope.runTest {
- assertThat(underTest.detailsViewModel.getTitle())
- .isEqualTo("Internet")
+ assertThat(underTest.detailsViewModel.getTitle()).isEqualTo("Internet")
assertThat(underTest.detailsViewModel.getSubTitle())
.isEqualTo("Tab a network to connect")
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayActionsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayActionsViewModelTest.kt
index 939644594d31..df2dd99c779e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayActionsViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayActionsViewModelTest.kt
@@ -21,7 +21,9 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.compose.animation.scene.Back
import com.android.compose.animation.scene.Swipe
-import com.android.compose.animation.scene.UserActionResult
+import com.android.compose.animation.scene.UserActionResult.HideOverlay
+import com.android.compose.animation.scene.UserActionResult.ShowOverlay
+import com.android.compose.animation.scene.UserActionResult.ShowOverlay.HideCurrentOverlays
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.flags.EnableSceneContainer
@@ -53,7 +55,7 @@ class QuickSettingsShadeOverlayActionsViewModelTest : SysuiTestCase() {
val actions by collectLastValue(underTest.actions)
underTest.activateIn(this)
- assertThat((actions?.get(Swipe.Up) as? UserActionResult.HideOverlay)?.overlay)
+ assertThat((actions?.get(Swipe.Up) as? HideOverlay)?.overlay)
.isEqualTo(Overlays.QuickSettingsShade)
assertThat(actions?.get(Swipe.Down)).isNull()
}
@@ -66,7 +68,7 @@ class QuickSettingsShadeOverlayActionsViewModelTest : SysuiTestCase() {
underTest.activateIn(this)
assertThat(isEditing).isFalse()
- assertThat((actions?.get(Back) as? UserActionResult.HideOverlay)?.overlay)
+ assertThat((actions?.get(Back) as? HideOverlay)?.overlay)
.isEqualTo(Overlays.QuickSettingsShade)
}
@@ -87,11 +89,11 @@ class QuickSettingsShadeOverlayActionsViewModelTest : SysuiTestCase() {
val actions by collectLastValue(underTest.actions)
underTest.activateIn(this)
- assertThat(
- (actions?.get(Swipe.Down(fromSource = SceneContainerEdge.TopLeft))
- as? UserActionResult.ReplaceByOverlay)
- ?.overlay
- )
- .isEqualTo(Overlays.NotificationsShade)
+ val action =
+ (actions?.get(Swipe.Down(fromSource = SceneContainerEdge.TopLeft)) as? ShowOverlay)
+ assertThat(action?.overlay).isEqualTo(Overlays.NotificationsShade)
+ val overlaysToHide = action?.hideCurrentOverlays as? HideCurrentOverlays.Some
+ assertThat(overlaysToHide).isNotNull()
+ assertThat(overlaysToHide?.overlays).containsExactly(Overlays.QuickSettingsShade)
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt
index 27e9f07af168..3d5daf6cf9c2 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt
@@ -318,7 +318,7 @@ class TakeScreenshotExecutorTest : SysuiTestCase() {
val displayId = 1
setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = displayId))
val onSaved = { _: Uri? -> }
- focusedDisplayRepository.emit(displayId)
+ focusedDisplayRepository.setDisplayId(displayId)
screenshotExecutor.executeScreenshots(
createScreenshotRequest(
@@ -345,7 +345,7 @@ class TakeScreenshotExecutorTest : SysuiTestCase() {
display(TYPE_INTERNAL, id = Display.DEFAULT_DISPLAY),
display(TYPE_EXTERNAL, id = 1),
)
- focusedDisplayRepository.emit(5) // invalid display
+ focusedDisplayRepository.setDisplayId(5) // invalid display
val onSaved = { _: Uri? -> }
screenshotExecutor.executeScreenshots(
createScreenshotRequest(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
index ee9cb141a700..555c717e1e65 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
@@ -20,7 +20,7 @@ import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
import android.testing.TestableLooper.RunWithLooper
import android.view.Choreographer
-import android.view.MotionEvent
+import android.view.accessibility.AccessibilityEvent
import android.widget.FrameLayout
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
@@ -45,7 +45,10 @@ import com.android.systemui.res.R
import com.android.systemui.settings.brightness.data.repository.BrightnessMirrorShowingRepository
import com.android.systemui.settings.brightness.domain.interactor.BrightnessMirrorShowingInteractor
import com.android.systemui.shade.NotificationShadeWindowView.InteractionEventHandler
+import com.android.systemui.shade.data.repository.ShadeAnimationRepository
+import com.android.systemui.shade.data.repository.ShadeRepositoryImpl
import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor
+import com.android.systemui.shade.domain.interactor.ShadeAnimationInteractorLegacyImpl
import com.android.systemui.statusbar.BlurUtils
import com.android.systemui.statusbar.DragDownHelper
import com.android.systemui.statusbar.LockscreenShadeTransitionController
@@ -185,6 +188,10 @@ class NotificationShadeWindowViewTest : SysuiTestCase() {
notificationShadeDepthController,
underTest,
shadeViewController,
+ ShadeAnimationInteractorLegacyImpl(
+ ShadeAnimationRepository(),
+ ShadeRepositoryImpl(testScope),
+ ),
panelExpansionInteractor,
ShadeExpansionStateManager(),
notificationStackScrollLayoutController,
@@ -259,6 +266,20 @@ class NotificationShadeWindowViewTest : SysuiTestCase() {
verify(configurationForwarder).dispatchOnMovedToDisplay(eq(1), eq(config))
}
+ @Test
+ @EnableFlags(AConfigFlags.FLAG_SHADE_LAUNCH_ACCESSIBILITY)
+ fun requestSendAccessibilityEvent_duringLaunchAnimation_blocksFocusEvent() {
+ underTest.setAnimatingContentLaunch(true)
+
+ assertThat(
+ underTest.requestSendAccessibilityEvent(
+ underTest.getChildAt(0),
+ AccessibilityEvent(AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED),
+ )
+ )
+ .isFalse()
+ }
+
private fun captureInteractionEventHandler() {
verify(underTest).setInteractionEventHandler(interactionEventHandlerCaptor.capture())
interactionEventHandler = interactionEventHandlerCaptor.value
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/QsBatteryModeControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/QsBatteryModeControllerTest.kt
index ab5fa8ef43fb..5566c10dc9e9 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/QsBatteryModeControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/QsBatteryModeControllerTest.kt
@@ -38,7 +38,7 @@ class QsBatteryModeControllerTest : SysuiTestCase() {
private val kosmos = testKosmos()
private val insetsProviderStore = kosmos.fakeStatusBarContentInsetsProviderStore
- private val insetsProvider = insetsProviderStore.defaultDisplay
+ private val insetsProvider = insetsProviderStore.forDisplay(context.displayId)
@JvmField @Rule val mockitoRule = MockitoJUnit.rule()!!
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/data/repository/ShadeDisplaysRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/data/repository/ShadeDisplaysRepositoryTest.kt
index 007a0fb87953..4f332d4bbed8 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/data/repository/ShadeDisplaysRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/data/repository/ShadeDisplaysRepositoryTest.kt
@@ -17,15 +17,21 @@
package com.android.systemui.shade.data.repository
import android.provider.Settings.Global.DEVELOPMENT_SHADE_DISPLAY_AWARENESS
+import android.view.Display
+import android.view.Display.TYPE_EXTERNAL
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.coroutines.collectValues
+import com.android.systemui.display.data.repository.display
import com.android.systemui.display.data.repository.displayRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
import com.android.systemui.kosmos.testScope
+import com.android.systemui.kosmos.useUnconfinedTestDispatcher
import com.android.systemui.shade.display.AnyExternalShadeDisplayPolicy
import com.android.systemui.shade.display.DefaultDisplayShadePolicy
+import com.android.systemui.shade.display.FakeShadeDisplayPolicy
import com.android.systemui.shade.display.StatusBarTouchShadeDisplayPolicy
import com.android.systemui.testKosmos
import com.android.systemui.util.settings.fakeGlobalSettings
@@ -37,24 +43,28 @@ import org.junit.runner.RunWith
@SmallTest
@RunWith(AndroidJUnit4::class)
class ShadeDisplaysRepositoryTest : SysuiTestCase() {
- private val kosmos = testKosmos()
+ private val kosmos = testKosmos().useUnconfinedTestDispatcher()
private val testScope = kosmos.testScope
private val globalSettings = kosmos.fakeGlobalSettings
private val displayRepository = kosmos.displayRepository
private val defaultPolicy = DefaultDisplayShadePolicy()
private val policies = kosmos.shadeDisplayPolicies
+ private val keyguardRepository = kosmos.fakeKeyguardRepository
- private val underTest =
+ private fun createUnderTest(shadeOnDefaultDisplayWhenLocked: Boolean = false) =
ShadeDisplaysRepositoryImpl(
globalSettings,
defaultPolicy,
testScope.backgroundScope,
policies,
+ shadeOnDefaultDisplayWhenLocked = shadeOnDefaultDisplayWhenLocked,
+ keyguardRepository,
)
@Test
fun policy_changing_propagatedFromTheLatestPolicy() =
testScope.runTest {
+ val underTest = createUnderTest()
val displayIds by collectValues(underTest.displayId)
assertThat(displayIds).containsExactly(0)
@@ -81,30 +91,54 @@ class ShadeDisplaysRepositoryTest : SysuiTestCase() {
@Test
fun policy_updatesBasedOnSettingValue_defaultDisplay() =
testScope.runTest {
- val policy by collectLastValue(underTest.policy)
-
+ val underTest = createUnderTest()
globalSettings.putString(DEVELOPMENT_SHADE_DISPLAY_AWARENESS, "default_display")
- assertThat(policy).isInstanceOf(DefaultDisplayShadePolicy::class.java)
+ assertThat(underTest.currentPolicy).isInstanceOf(DefaultDisplayShadePolicy::class.java)
}
@Test
fun policy_updatesBasedOnSettingValue_anyExternal() =
testScope.runTest {
- val policy by collectLastValue(underTest.policy)
-
+ val underTest = createUnderTest()
globalSettings.putString(DEVELOPMENT_SHADE_DISPLAY_AWARENESS, "any_external_display")
- assertThat(policy).isInstanceOf(AnyExternalShadeDisplayPolicy::class.java)
+ assertThat(underTest.currentPolicy)
+ .isInstanceOf(AnyExternalShadeDisplayPolicy::class.java)
}
@Test
fun policy_updatesBasedOnSettingValue_focusBased() =
testScope.runTest {
- val policy by collectLastValue(underTest.policy)
-
+ val underTest = createUnderTest()
globalSettings.putString(DEVELOPMENT_SHADE_DISPLAY_AWARENESS, "status_bar_latest_touch")
- assertThat(policy).isInstanceOf(StatusBarTouchShadeDisplayPolicy::class.java)
+ assertThat(underTest.currentPolicy)
+ .isInstanceOf(StatusBarTouchShadeDisplayPolicy::class.java)
+ }
+
+ @Test
+ fun displayId_afterKeyguardHides_goesBackToPreviousDisplay() =
+ testScope.runTest {
+ val underTest = createUnderTest(shadeOnDefaultDisplayWhenLocked = true)
+ globalSettings.putString(
+ DEVELOPMENT_SHADE_DISPLAY_AWARENESS,
+ FakeShadeDisplayPolicy.name,
+ )
+
+ val displayId by collectLastValue(underTest.displayId)
+
+ displayRepository.addDisplays(display(id = 2, type = TYPE_EXTERNAL))
+ FakeShadeDisplayPolicy.setDisplayId(2)
+
+ assertThat(displayId).isEqualTo(2)
+
+ keyguardRepository.setKeyguardShowing(true)
+
+ assertThat(displayId).isEqualTo(Display.DEFAULT_DISPLAY)
+
+ keyguardRepository.setKeyguardShowing(false)
+
+ assertThat(displayId).isEqualTo(2)
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/data/repository/ShadePrimaryDisplayCommandTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/data/repository/ShadePrimaryDisplayCommandTest.kt
index eeb3e6b31c69..fd6bc98b006c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/data/repository/ShadePrimaryDisplayCommandTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/data/repository/ShadePrimaryDisplayCommandTest.kt
@@ -25,7 +25,7 @@ import com.android.systemui.display.data.repository.displayRepository
import com.android.systemui.kosmos.testScope
import com.android.systemui.kosmos.useUnconfinedTestDispatcher
import com.android.systemui.shade.ShadePrimaryDisplayCommand
-import com.android.systemui.shade.display.ShadeDisplayPolicy
+import com.android.systemui.shade.display.FakeShadeDisplayPolicy
import com.android.systemui.statusbar.commandline.commandRegistry
import com.android.systemui.testKosmos
import com.android.systemui.util.settings.fakeGlobalSettings
@@ -33,8 +33,6 @@ import com.google.common.truth.StringSubject
import com.google.common.truth.Truth.assertThat
import java.io.PrintWriter
import java.io.StringWriter
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
@@ -118,23 +116,12 @@ class ShadePrimaryDisplayCommandTest : SysuiTestCase() {
@Test
fun policies_setsNewPolicy() =
testScope.runTest {
- val policy by collectLastValue(shadeDisplaysRepository.policy)
- val newPolicy = policies.last().name
+ val newPolicy = FakeShadeDisplayPolicy.name
commandRegistry.onShellCommand(pw, arrayOf("shade_display_override", newPolicy))
- assertThat(policy!!.name).isEqualTo(newPolicy)
+ assertThat(shadeDisplaysRepository.currentPolicy.name).isEqualTo(newPolicy)
}
-
- private fun makePolicy(policyName: String): ShadeDisplayPolicy {
- return object : ShadeDisplayPolicy {
- override val name: String
- get() = policyName
-
- override val displayId: StateFlow<Int>
- get() = MutableStateFlow(0)
- }
- }
}
private fun StringSubject.containsAllIn(strings: List<String>) {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/display/FocusShadeDisplayPolicyTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/display/FocusShadeDisplayPolicyTest.kt
new file mode 100644
index 000000000000..b4249ef72e62
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/display/FocusShadeDisplayPolicyTest.kt
@@ -0,0 +1,58 @@
+/*
+ * 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.shade.display
+
+import android.view.Display
+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.kosmos.useUnconfinedTestDispatcher
+import com.android.systemui.shade.data.repository.fakeFocusedDisplayRepository
+import com.android.systemui.shade.data.repository.focusShadeDisplayPolicy
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlin.test.Test
+import kotlinx.coroutines.test.runTest
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class FocusShadeDisplayPolicyTest : SysuiTestCase() {
+ private val kosmos = testKosmos().useUnconfinedTestDispatcher()
+ private val testScope = kosmos.testScope
+ private val focusedDisplayRepository = kosmos.fakeFocusedDisplayRepository
+
+ private val underTest = kosmos.focusShadeDisplayPolicy
+
+ @Test
+ fun displayId_propagatedFromRepository() =
+ testScope.runTest {
+ val displayId by collectLastValue(underTest.displayId)
+
+ assertThat(displayId).isEqualTo(Display.DEFAULT_DISPLAY)
+
+ focusedDisplayRepository.setDisplayId(2)
+
+ assertThat(displayId).isEqualTo(2)
+
+ focusedDisplayRepository.setDisplayId(3)
+
+ assertThat(displayId).isEqualTo(3)
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/display/StatusBarTouchShadeDisplayPolicyTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/display/StatusBarTouchShadeDisplayPolicyTest.kt
index 20dfd3e11947..e43c46b36a06 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/display/StatusBarTouchShadeDisplayPolicyTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/display/StatusBarTouchShadeDisplayPolicyTest.kt
@@ -26,12 +26,11 @@ import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.coroutines.collectValues
import com.android.systemui.display.data.repository.display
import com.android.systemui.display.data.repository.displayRepository
-import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
import com.android.systemui.kosmos.testScope
import com.android.systemui.kosmos.useUnconfinedTestDispatcher
+import com.android.systemui.shade.data.repository.statusBarTouchShadeDisplayPolicy
import com.android.systemui.shade.domain.interactor.notificationElement
import com.android.systemui.shade.domain.interactor.qsElement
-import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlin.test.Test
@@ -45,22 +44,9 @@ import org.mockito.kotlin.mock
class StatusBarTouchShadeDisplayPolicyTest : SysuiTestCase() {
private val kosmos = testKosmos().useUnconfinedTestDispatcher()
private val testScope = kosmos.testScope
- private val keyguardRepository = kosmos.fakeKeyguardRepository
private val displayRepository = kosmos.displayRepository
- private fun createUnderTest(
- shadeOnDefaultDisplayWhenLocked: Boolean = false
- ): StatusBarTouchShadeDisplayPolicy {
- return StatusBarTouchShadeDisplayPolicy(
- displayRepository,
- keyguardRepository,
- testScope.backgroundScope,
- shadeOnDefaultDisplayWhenLocked = shadeOnDefaultDisplayWhenLocked,
- shadeInteractor = { kosmos.shadeInteractor },
- { kosmos.qsElement },
- { kosmos.notificationElement },
- )
- }
+ private val underTest = kosmos.statusBarTouchShadeDisplayPolicy
private fun createMotionEventForDisplay(displayId: Int, xCoordinate: Float = 0f): MotionEvent {
return mock<MotionEvent> {
@@ -71,15 +57,12 @@ class StatusBarTouchShadeDisplayPolicyTest : SysuiTestCase() {
@Test
fun displayId_defaultToDefaultDisplay() {
- val underTest = createUnderTest()
-
assertThat(underTest.displayId.value).isEqualTo(Display.DEFAULT_DISPLAY)
}
@Test
fun onStatusBarTouched_called_updatesDisplayId() =
testScope.runTest {
- val underTest = createUnderTest()
val displayId by collectLastValue(underTest.displayId)
displayRepository.addDisplays(display(id = 2, type = TYPE_EXTERNAL))
@@ -91,7 +74,6 @@ class StatusBarTouchShadeDisplayPolicyTest : SysuiTestCase() {
@Test
fun onStatusBarTouched_notExistentDisplay_displayIdNotUpdated() =
testScope.runTest {
- val underTest = createUnderTest()
val displayIds by collectValues(underTest.displayId)
assertThat(displayIds).isEqualTo(listOf(Display.DEFAULT_DISPLAY))
@@ -104,7 +86,6 @@ class StatusBarTouchShadeDisplayPolicyTest : SysuiTestCase() {
@Test
fun onStatusBarTouched_afterDisplayRemoved_goesBackToDefaultDisplay() =
testScope.runTest {
- val underTest = createUnderTest()
val displayId by collectLastValue(underTest.displayId)
displayRepository.addDisplays(display(id = 2, type = TYPE_EXTERNAL))
@@ -118,46 +99,8 @@ class StatusBarTouchShadeDisplayPolicyTest : SysuiTestCase() {
}
@Test
- fun onStatusBarTouched_afterKeyguardVisible_goesBackToDefaultDisplay() =
- testScope.runTest {
- val underTest = createUnderTest(shadeOnDefaultDisplayWhenLocked = true)
- val displayId by collectLastValue(underTest.displayId)
-
- displayRepository.addDisplays(display(id = 2, type = TYPE_EXTERNAL))
- underTest.onStatusBarTouched(createMotionEventForDisplay(2), STATUS_BAR_WIDTH)
-
- assertThat(displayId).isEqualTo(2)
-
- keyguardRepository.setKeyguardShowing(true)
-
- assertThat(displayId).isEqualTo(Display.DEFAULT_DISPLAY)
- }
-
- @Test
- fun onStatusBarTouched_afterKeyguardHides_goesBackToPreviousDisplay() =
- testScope.runTest {
- val underTest = createUnderTest(shadeOnDefaultDisplayWhenLocked = true)
- val displayId by collectLastValue(underTest.displayId)
-
- displayRepository.addDisplays(display(id = 2, type = TYPE_EXTERNAL))
- underTest.onStatusBarTouched(createMotionEventForDisplay(2), STATUS_BAR_WIDTH)
-
- assertThat(displayId).isEqualTo(2)
-
- keyguardRepository.setKeyguardShowing(true)
-
- assertThat(displayId).isEqualTo(Display.DEFAULT_DISPLAY)
-
- keyguardRepository.setKeyguardShowing(false)
-
- assertThat(displayId).isEqualTo(2)
- }
-
- @Test
fun onStatusBarTouched_leftSide_intentSetToNotifications() =
testScope.runTest {
- val underTest = createUnderTest(shadeOnDefaultDisplayWhenLocked = true)
-
underTest.onStatusBarTouched(
createMotionEventForDisplay(2, STATUS_BAR_WIDTH * 0.1f),
STATUS_BAR_WIDTH,
@@ -169,8 +112,6 @@ class StatusBarTouchShadeDisplayPolicyTest : SysuiTestCase() {
@Test
fun onStatusBarTouched_rightSide_intentSetToQs() =
testScope.runTest {
- val underTest = createUnderTest(shadeOnDefaultDisplayWhenLocked = true)
-
underTest.onStatusBarTouched(
createMotionEventForDisplay(2, STATUS_BAR_WIDTH * 0.95f),
STATUS_BAR_WIDTH,
@@ -182,8 +123,6 @@ class StatusBarTouchShadeDisplayPolicyTest : SysuiTestCase() {
@Test
fun onStatusBarTouched_nullAfterConsumed() =
testScope.runTest {
- val underTest = createUnderTest(shadeOnDefaultDisplayWhenLocked = true)
-
underTest.onStatusBarTouched(
createMotionEventForDisplay(2, STATUS_BAR_WIDTH * 0.1f),
STATUS_BAR_WIDTH,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeModeInteractorImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeModeInteractorImplTest.kt
index a47db2ec728b..668f568d7f46 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeModeInteractorImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeModeInteractorImplTest.kt
@@ -16,15 +16,11 @@
package com.android.systemui.shade.domain.interactor
-import android.platform.test.annotations.DisableFlags
-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.shade.data.repository.shadeRepository
-import com.android.systemui.shade.shared.flag.DualShade
import com.android.systemui.shade.shared.model.ShadeMode
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
@@ -48,82 +44,79 @@ class ShadeModeInteractorImplTest : SysuiTestCase() {
}
@Test
- @DisableFlags(DualShade.FLAG_NAME)
fun legacyShadeMode_narrowScreen_singleShade() =
testScope.runTest {
val shadeMode by collectLastValue(underTest.shadeMode)
- kosmos.shadeRepository.setShadeLayoutWide(false)
+ kosmos.enableSingleShade()
assertThat(shadeMode).isEqualTo(ShadeMode.Single)
}
@Test
- @DisableFlags(DualShade.FLAG_NAME)
fun legacyShadeMode_wideScreen_splitShade() =
testScope.runTest {
val shadeMode by collectLastValue(underTest.shadeMode)
- kosmos.shadeRepository.setShadeLayoutWide(true)
+ kosmos.enableSplitShade()
assertThat(shadeMode).isEqualTo(ShadeMode.Split)
}
@Test
- @EnableFlags(DualShade.FLAG_NAME)
fun shadeMode_wideScreen_isDual() =
testScope.runTest {
val shadeMode by collectLastValue(underTest.shadeMode)
- kosmos.shadeRepository.setShadeLayoutWide(true)
+ kosmos.enableDualShade(wideLayout = true)
assertThat(shadeMode).isEqualTo(ShadeMode.Dual)
}
@Test
- @EnableFlags(DualShade.FLAG_NAME)
fun shadeMode_narrowScreen_isDual() =
testScope.runTest {
val shadeMode by collectLastValue(underTest.shadeMode)
- kosmos.shadeRepository.setShadeLayoutWide(false)
+ kosmos.enableDualShade(wideLayout = false)
assertThat(shadeMode).isEqualTo(ShadeMode.Dual)
}
@Test
- @EnableFlags(DualShade.FLAG_NAME)
- fun isDualShade_flagEnabled_true() =
+ fun isDualShade_settingEnabled_returnsTrue() =
testScope.runTest {
- // Initiate collection.
+ // TODO(b/391578667): Add a test case for user switching once the bug is fixed.
val shadeMode by collectLastValue(underTest.shadeMode)
+ kosmos.enableDualShade()
+ assertThat(shadeMode).isEqualTo(ShadeMode.Dual)
assertThat(underTest.isDualShade).isTrue()
}
@Test
- @DisableFlags(DualShade.FLAG_NAME)
- fun isDualShade_flagDisabled_false() =
+ fun isDualShade_settingDisabled_returnsFalse() =
testScope.runTest {
- // Initiate collection.
val shadeMode by collectLastValue(underTest.shadeMode)
+ kosmos.disableDualShade()
+ assertThat(shadeMode).isNotEqualTo(ShadeMode.Dual)
assertThat(underTest.isDualShade).isFalse()
}
@Test
fun getTopEdgeSplitFraction_narrowScreen_splitInHalf() =
testScope.runTest {
- // Ensure isShadeLayoutWide is collected.
- val isShadeLayoutWide by collectLastValue(underTest.isShadeLayoutWide)
- kosmos.shadeRepository.setShadeLayoutWide(false)
+ val shadeMode by collectLastValue(underTest.shadeMode)
+ kosmos.enableDualShade(wideLayout = false)
+ assertThat(shadeMode).isEqualTo(ShadeMode.Dual)
assertThat(underTest.getTopEdgeSplitFraction()).isEqualTo(0.5f)
}
@Test
fun getTopEdgeSplitFraction_wideScreen_splitInHalf() =
testScope.runTest {
- // Ensure isShadeLayoutWide is collected.
- val isShadeLayoutWide by collectLastValue(underTest.isShadeLayoutWide)
- kosmos.shadeRepository.setShadeLayoutWide(true)
+ val shadeMode by collectLastValue(underTest.shadeMode)
+ kosmos.enableDualShade(wideLayout = true)
+ assertThat(shadeMode).isEqualTo(ShadeMode.Dual)
assertThat(underTest.getTopEdgeSplitFraction()).isEqualTo(0.5f)
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
index 0713a247a4a3..baaf6c9a76ae 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
@@ -84,7 +84,7 @@ import com.android.systemui.flags.FakeFeatureFlagsClassic;
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
import com.android.systemui.log.LogWtfHandlerRule;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.recents.OverviewProxyService;
+import com.android.systemui.recents.LauncherProxyService;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.NotificationLockscreenUserManager.NotificationStateChangedListener;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
@@ -156,7 +156,7 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase {
@Mock
private NotificationClickNotifier mClickNotifier;
@Mock
- private OverviewProxyService mOverviewProxyService;
+ private LauncherProxyService mLauncherProxyService;
@Mock
private KeyguardManager mKeyguardManager;
@Mock
@@ -1142,7 +1142,7 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase {
(() -> mVisibilityProvider),
(() -> mNotifCollection),
mClickNotifier,
- (() -> mOverviewProxyService),
+ (() -> mLauncherProxyService),
NotificationLockscreenUserManagerTest.this.mKeyguardManager,
mStatusBarStateController,
mMainExecutor,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt
index eec23d3ffb1a..942e6554e5d9 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt
@@ -36,6 +36,7 @@ import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifCh
import com.android.systemui.statusbar.chips.ui.model.ColorsModel
import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
+import com.android.systemui.statusbar.core.StatusBarRootModernization
import com.android.systemui.statusbar.notification.data.model.activeNotificationModel
import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationsStore
import com.android.systemui.statusbar.notification.data.repository.UnconfinedFakeHeadsUpRowRepository
@@ -44,6 +45,7 @@ import com.android.systemui.statusbar.notification.headsup.PinnedStatus
import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel
import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel
import com.android.systemui.statusbar.notification.stack.data.repository.headsUpNotificationRepository
+import com.android.systemui.statusbar.phone.ongoingcall.StatusBarChipsModernization
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlin.test.Test
@@ -609,7 +611,7 @@ class NotifChipsViewModelTest : SysuiTestCase() {
listOf(
activeNotificationModel(
key = "notif",
- statusBarChipIcon = mock<StatusBarIconView>(),
+ statusBarChipIcon = createStatusBarIconViewOrNull(),
promotedContent = promotedContentBuilder.build(),
)
)
@@ -629,22 +631,26 @@ class NotifChipsViewModelTest : SysuiTestCase() {
}
@Test
- @DisableFlags(FLAG_PROMOTE_NOTIFICATIONS_AUTOMATICALLY)
- fun chips_clickingChipNotifiesInteractor() =
+ @DisableFlags(
+ FLAG_PROMOTE_NOTIFICATIONS_AUTOMATICALLY,
+ StatusBarRootModernization.FLAG_NAME,
+ StatusBarChipsModernization.FLAG_NAME,
+ )
+ fun chips_chipsModernizationDisabled_clickingChipNotifiesInteractor() =
kosmos.runTest {
val latest by collectLastValue(underTest.chips)
- val latestChipTap by
+ val latestChipTapKey by
collectLastValue(
kosmos.statusBarNotificationChipsInteractor.promotedNotificationChipTapEvent
)
+ val key = "clickTest"
setNotifs(
listOf(
activeNotificationModel(
- key = "clickTest",
+ key,
statusBarChipIcon = createStatusBarIconViewOrNull(),
- promotedContent =
- PromotedNotificationContentModel.Builder("clickTest").build(),
+ promotedContent = PromotedNotificationContentModel.Builder(key).build(),
)
)
)
@@ -652,7 +658,41 @@ class NotifChipsViewModelTest : SysuiTestCase() {
chip.onClickListenerLegacy!!.onClick(mock<View>())
- assertThat(latestChipTap).isEqualTo("clickTest")
+ assertThat(latestChipTapKey).isEqualTo(key)
+ }
+
+ @Test
+ @DisableFlags(FLAG_PROMOTE_NOTIFICATIONS_AUTOMATICALLY)
+ @EnableFlags(StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME)
+ fun chips_chipsModernizationEnabled_clickingChipNotifiesInteractor() =
+ kosmos.runTest {
+ val latest by collectLastValue(underTest.chips)
+ val latestChipTapKey by
+ collectLastValue(
+ kosmos.statusBarNotificationChipsInteractor.promotedNotificationChipTapEvent
+ )
+ val key = "clickTest"
+
+ setNotifs(
+ listOf(
+ activeNotificationModel(
+ key,
+ statusBarChipIcon = createStatusBarIconViewOrNull(),
+ promotedContent = PromotedNotificationContentModel.Builder(key).build(),
+ )
+ )
+ )
+ val chip = latest!![0]
+
+ assertThat(chip.clickBehavior)
+ .isInstanceOf(
+ OngoingActivityChipModel.ClickBehavior.ShowHeadsUpNotification::class.java
+ )
+
+ (chip.clickBehavior as OngoingActivityChipModel.ClickBehavior.ShowHeadsUpNotification)
+ .onClick()
+
+ assertThat(latestChipTapKey).isEqualTo(key)
}
private fun setNotifs(notifs: List<ActiveNotificationModel>) {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/TargetSdkResolverTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/TargetSdkResolverTest.kt
index 22906b8724b5..c515d940d2aa 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/TargetSdkResolverTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/TargetSdkResolverTest.kt
@@ -144,7 +144,8 @@ class TargetSdkResolverTest : SysuiTestCase() {
/* rankingAdjustment = */ 0,
/* isBubble = */ false,
/* proposedImportance = */ 0,
- /* sensitiveContent = */ false
+ /* sensitiveContent = */ false,
+ /* summarization = */ null
)
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java
index 7b120947b1d6..2aa1efaa429f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java
@@ -107,7 +107,7 @@ public class VisualStabilityCoordinatorTest extends SysuiTestCase {
@Parameters(name = "{0}")
public static List<FlagsParameterization> getParams() {
return SceneContainerFlagParameterizationKt
- .andSceneContainer(allCombinationsOf(Flags.FLAG_STABILIZE_HEADS_UP_GROUP));
+ .andSceneContainer(allCombinationsOf(Flags.FLAG_STABILIZE_HEADS_UP_GROUP_V2));
}
private VisualStabilityCoordinator mCoordinator;
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProviderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProviderTest.kt
index 3c772fdbe0b2..356eedbc9a45 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProviderTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProviderTest.kt
@@ -29,6 +29,7 @@ import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.NotificationLockscreenUserManager
import com.android.systemui.statusbar.NotificationLockscreenUserManager.REDACTION_TYPE_NONE
import com.android.systemui.statusbar.NotificationLockscreenUserManager.REDACTION_TYPE_PUBLIC
+import com.android.systemui.statusbar.RankingBuilder
import com.android.systemui.statusbar.notification.collection.GroupEntry
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
import com.android.systemui.statusbar.notification.collection.listbuilder.NotifSection
@@ -242,4 +243,42 @@ class NotifUiAdjustmentProviderTest : SysuiTestCase() {
// Then: need no re-inflation
assertFalse(NotifUiAdjustment.needReinflate(oldAdjustment, newAdjustment))
}
+
+ @Test
+ @EnableFlags(android.app.Flags.FLAG_NM_SUMMARIZATION_UI)
+ fun changeIsSummarization_needReInflation_newlySummarized() {
+ // Given: an Entry with no summarization
+ val oldAdjustment = adjustmentProvider.calculateAdjustment(entry)
+ assertThat(oldAdjustment.summarization).isNull()
+
+ // When: the Entry now has a summarization
+ val rb = RankingBuilder(entry.ranking)
+ rb.setSummarization("summary!")
+ entry.ranking = rb.build()
+ val newAdjustment = adjustmentProvider.calculateAdjustment(entry)
+ assertThat(newAdjustment).isNotEqualTo(oldAdjustment)
+
+ // Then: Need re-inflation
+ assertTrue(NotifUiAdjustment.needReinflate(oldAdjustment, newAdjustment))
+ }
+
+ @Test
+ @EnableFlags(android.app.Flags.FLAG_NM_SUMMARIZATION_UI)
+ fun changeIsSummarization_needReInflation_summarizationChanged() {
+ // Given: an Entry with no summarization
+ val rb = RankingBuilder(entry.ranking)
+ rb.setSummarization("summary!")
+ entry.ranking = rb.build()
+ val oldAdjustment = adjustmentProvider.calculateAdjustment(entry)
+
+ // When: the Entry now has a new summarization
+ val rb2 = RankingBuilder(entry.ranking)
+ rb2.setSummarization("summary new!")
+ entry.ranking = rb2.build()
+ val newAdjustment = adjustmentProvider.calculateAdjustment(entry)
+ assertThat(newAdjustment).isNotEqualTo(oldAdjustment)
+
+ // Then: Need re-inflation
+ assertTrue(NotifUiAdjustment.needReinflate(oldAdjustment, newAdjustment))
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/BundleNotificationInfoTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/BundleNotificationInfoTest.java
index b2962eeb9001..66277e2d13a6 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/BundleNotificationInfoTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/BundleNotificationInfoTest.java
@@ -198,7 +198,8 @@ public class BundleNotificationInfoTest extends SysuiTestCase {
false,
true,
mAssistantFeedbackController,
- mMetricsLogger);
+ mMetricsLogger,
+ null);
// and the feedback button is clicked,
final View feedbackButton = mInfo.findViewById(R.id.notification_guts_bundle_feedback);
feedbackButton.performClick();
@@ -253,7 +254,8 @@ public class BundleNotificationInfoTest extends SysuiTestCase {
false,
true,
mAssistantFeedbackController,
- mMetricsLogger);
+ mMetricsLogger,
+ null);
final View feedbackButton = mInfo.findViewById(R.id.notification_guts_bundle_feedback);
feedbackButton.performClick();
@@ -294,7 +296,8 @@ public class BundleNotificationInfoTest extends SysuiTestCase {
false,
true,
mAssistantFeedbackController,
- mMetricsLogger);
+ mMetricsLogger,
+ null);
final View feedbackButton = mInfo.findViewById(R.id.notification_guts_bundle_feedback);
feedbackButton.performClick();
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.kt
index 6a0a5bb3b191..39c42f183481 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.kt
@@ -544,6 +544,7 @@ class NotificationGutsManagerTest(flags: FlagsParameterization) : SysuiTestCase(
/* wasShownHighPriority = */ eq(true),
eq(assistantFeedbackController),
eq(metricsLogger),
+ any<View.OnClickListener>(),
)
}
@@ -580,6 +581,7 @@ class NotificationGutsManagerTest(flags: FlagsParameterization) : SysuiTestCase(
/* wasShownHighPriority = */ eq(false),
eq(assistantFeedbackController),
eq(metricsLogger),
+ any<View.OnClickListener>(),
)
}
@@ -614,6 +616,7 @@ class NotificationGutsManagerTest(flags: FlagsParameterization) : SysuiTestCase(
/* wasShownHighPriority = */ eq(false),
eq(assistantFeedbackController),
eq(metricsLogger),
+ any<View.OnClickListener>(),
)
}
@@ -651,6 +654,7 @@ class NotificationGutsManagerTest(flags: FlagsParameterization) : SysuiTestCase(
/* wasShownHighPriority = */ eq(false),
eq(assistantFeedbackController),
eq(metricsLogger),
+ any<View.OnClickListener>(),
)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java
index 245a6a0b130c..fdba7ba34855 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java
@@ -187,7 +187,7 @@ public class NotificationInfoTest extends SysuiTestCase {
false,
true,
mAssistantFeedbackController,
- mMetricsLogger);
+ mMetricsLogger, null);
final TextView textView = mNotificationInfo.findViewById(R.id.pkg_name);
assertTrue(textView.getText().toString().contains("App Name"));
assertEquals(VISIBLE, mNotificationInfo.findViewById(R.id.header).getVisibility());
@@ -213,7 +213,7 @@ public class NotificationInfoTest extends SysuiTestCase {
false,
true,
mAssistantFeedbackController,
- mMetricsLogger);
+ mMetricsLogger, null);
final ImageView iconView = mNotificationInfo.findViewById(R.id.pkg_icon);
assertEquals(iconDrawable, iconView.getDrawable());
}
@@ -235,7 +235,7 @@ public class NotificationInfoTest extends SysuiTestCase {
false,
true,
mAssistantFeedbackController,
- mMetricsLogger);
+ mMetricsLogger, null);
final TextView nameView = mNotificationInfo.findViewById(R.id.delegate_name);
assertEquals(GONE, nameView.getVisibility());
}
@@ -266,7 +266,7 @@ public class NotificationInfoTest extends SysuiTestCase {
false,
true,
mAssistantFeedbackController,
- mMetricsLogger);
+ mMetricsLogger, null);
final TextView nameView = mNotificationInfo.findViewById(R.id.delegate_name);
assertEquals(VISIBLE, nameView.getVisibility());
assertTrue(nameView.getText().toString().contains("Proxied"));
@@ -289,7 +289,7 @@ public class NotificationInfoTest extends SysuiTestCase {
false,
true,
mAssistantFeedbackController,
- mMetricsLogger);
+ mMetricsLogger, null);
final TextView groupNameView = mNotificationInfo.findViewById(R.id.group_name);
assertEquals(GONE, groupNameView.getVisibility());
}
@@ -317,7 +317,7 @@ public class NotificationInfoTest extends SysuiTestCase {
false,
true,
mAssistantFeedbackController,
- mMetricsLogger);
+ mMetricsLogger, null);
final TextView groupNameView = mNotificationInfo.findViewById(R.id.group_name);
assertEquals(View.VISIBLE, groupNameView.getVisibility());
assertEquals("Test Group Name", groupNameView.getText());
@@ -340,7 +340,7 @@ public class NotificationInfoTest extends SysuiTestCase {
false,
true,
mAssistantFeedbackController,
- mMetricsLogger);
+ mMetricsLogger, null);
final TextView textView = mNotificationInfo.findViewById(R.id.channel_name);
assertEquals(TEST_CHANNEL_NAME, textView.getText());
}
@@ -362,7 +362,7 @@ public class NotificationInfoTest extends SysuiTestCase {
false,
true,
mAssistantFeedbackController,
- mMetricsLogger);
+ mMetricsLogger, null);
final TextView textView = mNotificationInfo.findViewById(R.id.channel_name);
assertEquals(GONE, textView.getVisibility());
}
@@ -388,7 +388,8 @@ public class NotificationInfoTest extends SysuiTestCase {
false,
true,
mAssistantFeedbackController,
- mMetricsLogger);
+ mMetricsLogger,
+ null);
final TextView textView = mNotificationInfo.findViewById(R.id.channel_name);
assertEquals(VISIBLE, textView.getVisibility());
}
@@ -410,7 +411,8 @@ public class NotificationInfoTest extends SysuiTestCase {
true,
true,
mAssistantFeedbackController,
- mMetricsLogger);
+ mMetricsLogger,
+ null);
final TextView textView = mNotificationInfo.findViewById(R.id.channel_name);
assertEquals(VISIBLE, textView.getVisibility());
}
@@ -436,7 +438,8 @@ public class NotificationInfoTest extends SysuiTestCase {
false,
true,
mAssistantFeedbackController,
- mMetricsLogger);
+ mMetricsLogger,
+ null);
final View settingsButton = mNotificationInfo.findViewById(R.id.info);
settingsButton.performClick();
@@ -461,7 +464,8 @@ public class NotificationInfoTest extends SysuiTestCase {
false,
true,
mAssistantFeedbackController,
- mMetricsLogger);
+ mMetricsLogger,
+ null);
final View settingsButton = mNotificationInfo.findViewById(R.id.info);
assertTrue(settingsButton.getVisibility() != View.VISIBLE);
}
@@ -486,7 +490,8 @@ public class NotificationInfoTest extends SysuiTestCase {
false,
true,
mAssistantFeedbackController,
- mMetricsLogger);
+ mMetricsLogger,
+ null);
final View settingsButton = mNotificationInfo.findViewById(R.id.info);
assertTrue(settingsButton.getVisibility() != View.VISIBLE);
}
@@ -508,7 +513,8 @@ public class NotificationInfoTest extends SysuiTestCase {
false,
true,
mAssistantFeedbackController,
- mMetricsLogger);
+ mMetricsLogger,
+ null);
mNotificationInfo.bindNotification(
mMockPackageManager,
mMockINotificationManager,
@@ -524,7 +530,8 @@ public class NotificationInfoTest extends SysuiTestCase {
false,
true,
mAssistantFeedbackController,
- mMetricsLogger);
+ mMetricsLogger,
+ null);
final View settingsButton = mNotificationInfo.findViewById(R.id.info);
assertEquals(View.VISIBLE, settingsButton.getVisibility());
}
@@ -546,7 +553,8 @@ public class NotificationInfoTest extends SysuiTestCase {
true,
true,
mAssistantFeedbackController,
- mMetricsLogger);
+ mMetricsLogger,
+ null);
final TextView view = mNotificationInfo.findViewById(R.id.non_configurable_text);
assertEquals(View.VISIBLE, view.getVisibility());
assertEquals(mContext.getString(R.string.notification_unblockable_desc),
@@ -589,7 +597,8 @@ public class NotificationInfoTest extends SysuiTestCase {
false,
true,
mAssistantFeedbackController,
- mMetricsLogger);
+ mMetricsLogger,
+ null);
final TextView view = mNotificationInfo.findViewById(R.id.non_configurable_call_text);
assertEquals(View.VISIBLE, view.getVisibility());
assertEquals(mContext.getString(R.string.notification_unblockable_call_desc),
@@ -632,7 +641,8 @@ public class NotificationInfoTest extends SysuiTestCase {
false,
true,
mAssistantFeedbackController,
- mMetricsLogger);
+ mMetricsLogger,
+ null);
assertEquals(GONE,
mNotificationInfo.findViewById(R.id.non_configurable_call_text).getVisibility());
assertEquals(VISIBLE,
@@ -659,7 +669,8 @@ public class NotificationInfoTest extends SysuiTestCase {
false,
true,
mAssistantFeedbackController,
- mMetricsLogger);
+ mMetricsLogger,
+ null);
assertEquals(VISIBLE, mNotificationInfo.findViewById(R.id.automatic).getVisibility());
assertEquals(VISIBLE, mNotificationInfo.findViewById(R.id.automatic_summary).getVisibility());
}
@@ -681,7 +692,8 @@ public class NotificationInfoTest extends SysuiTestCase {
false,
true,
mAssistantFeedbackController,
- mMetricsLogger);
+ mMetricsLogger,
+ null);
assertEquals(GONE, mNotificationInfo.findViewById(R.id.automatic).getVisibility());
assertEquals(GONE, mNotificationInfo.findViewById(R.id.automatic_summary).getVisibility());
}
@@ -705,7 +717,8 @@ public class NotificationInfoTest extends SysuiTestCase {
false,
true,
mAssistantFeedbackController,
- mMetricsLogger);
+ mMetricsLogger,
+ null);
assertTrue(mNotificationInfo.findViewById(R.id.automatic).isSelected());
}
@@ -726,7 +739,8 @@ public class NotificationInfoTest extends SysuiTestCase {
false,
true,
mAssistantFeedbackController,
- mMetricsLogger);
+ mMetricsLogger,
+ null);
assertTrue(mNotificationInfo.findViewById(R.id.alert).isSelected());
}
@@ -747,7 +761,8 @@ public class NotificationInfoTest extends SysuiTestCase {
false,
false,
mAssistantFeedbackController,
- mMetricsLogger);
+ mMetricsLogger,
+ null);
assertTrue(mNotificationInfo.findViewById(R.id.silence).isSelected());
}
@@ -768,7 +783,8 @@ public class NotificationInfoTest extends SysuiTestCase {
false,
true,
mAssistantFeedbackController,
- mMetricsLogger);
+ mMetricsLogger,
+ null);
mTestableLooper.processAllMessages();
verify(mMockINotificationManager, never()).updateNotificationChannelForPackage(
anyString(), eq(TEST_UID), any());
@@ -791,7 +807,8 @@ public class NotificationInfoTest extends SysuiTestCase {
false,
true,
mAssistantFeedbackController,
- mMetricsLogger);
+ mMetricsLogger,
+ null);
assertEquals(1, mUiEventLogger.numLogs());
assertEquals(NotificationControlsEvent.NOTIFICATION_CONTROLS_OPEN.getId(),
mUiEventLogger.eventId(0));
@@ -815,7 +832,8 @@ public class NotificationInfoTest extends SysuiTestCase {
false,
false,
mAssistantFeedbackController,
- mMetricsLogger);
+ mMetricsLogger,
+ null);
mNotificationInfo.findViewById(R.id.alert).performClick();
mTestableLooper.processAllMessages();
@@ -842,7 +860,8 @@ public class NotificationInfoTest extends SysuiTestCase {
false,
true,
mAssistantFeedbackController,
- mMetricsLogger);
+ mMetricsLogger,
+ null);
mNotificationInfo.findViewById(R.id.silence).performClick();
mTestableLooper.processAllMessages();
@@ -869,7 +888,8 @@ public class NotificationInfoTest extends SysuiTestCase {
false,
true,
mAssistantFeedbackController,
- mMetricsLogger);
+ mMetricsLogger,
+ null);
mNotificationInfo.findViewById(R.id.automatic).performClick();
mTestableLooper.processAllMessages();
@@ -897,7 +917,8 @@ public class NotificationInfoTest extends SysuiTestCase {
false,
true,
mAssistantFeedbackController,
- mMetricsLogger);
+ mMetricsLogger,
+ null);
mNotificationInfo.handleCloseControls(true, false);
mTestableLooper.processAllMessages();
@@ -924,7 +945,8 @@ public class NotificationInfoTest extends SysuiTestCase {
false,
true,
mAssistantFeedbackController,
- mMetricsLogger);
+ mMetricsLogger,
+ null);
mNotificationInfo.handleCloseControls(true, false);
mTestableLooper.processAllMessages();
@@ -959,7 +981,8 @@ public class NotificationInfoTest extends SysuiTestCase {
false,
true,
mAssistantFeedbackController,
- mMetricsLogger);
+ mMetricsLogger,
+ null);
mNotificationInfo.handleCloseControls(true, false);
@@ -987,7 +1010,8 @@ public class NotificationInfoTest extends SysuiTestCase {
false,
true,
mAssistantFeedbackController,
- mMetricsLogger);
+ mMetricsLogger,
+ null);
mNotificationInfo.findViewById(R.id.silence).performClick();
mNotificationInfo.findViewById(R.id.done).performClick();
@@ -1028,7 +1052,8 @@ public class NotificationInfoTest extends SysuiTestCase {
false,
false,
mAssistantFeedbackController,
- mMetricsLogger);
+ mMetricsLogger,
+ null);
mNotificationInfo.findViewById(R.id.alert).performClick();
mNotificationInfo.findViewById(R.id.done).performClick();
@@ -1065,7 +1090,8 @@ public class NotificationInfoTest extends SysuiTestCase {
false,
true,
mAssistantFeedbackController,
- mMetricsLogger);
+ mMetricsLogger,
+ null);
mNotificationInfo.findViewById(R.id.automatic).performClick();
mNotificationInfo.findViewById(R.id.done).performClick();
@@ -1097,7 +1123,8 @@ public class NotificationInfoTest extends SysuiTestCase {
false,
true,
mAssistantFeedbackController,
- mMetricsLogger);
+ mMetricsLogger,
+ null);
mNotificationInfo.findViewById(R.id.silence).performClick();
mNotificationInfo.findViewById(R.id.done).performClick();
@@ -1133,7 +1160,8 @@ public class NotificationInfoTest extends SysuiTestCase {
false,
false,
mAssistantFeedbackController,
- mMetricsLogger);
+ mMetricsLogger,
+ null);
assertEquals(mContext.getString(R.string.inline_done_button),
((TextView) mNotificationInfo.findViewById(R.id.done)).getText());
@@ -1171,7 +1199,8 @@ public class NotificationInfoTest extends SysuiTestCase {
false,
false,
mAssistantFeedbackController,
- mMetricsLogger);
+ mMetricsLogger,
+ null);
mNotificationInfo.findViewById(R.id.silence).performClick();
mNotificationInfo.handleCloseControls(false, false);
@@ -1202,7 +1231,8 @@ public class NotificationInfoTest extends SysuiTestCase {
false,
false,
mAssistantFeedbackController,
- mMetricsLogger);
+ mMetricsLogger,
+ null);
assertEquals(mContext.getString(R.string.inline_done_button),
((TextView) mNotificationInfo.findViewById(R.id.done)).getText());
@@ -1240,7 +1270,8 @@ public class NotificationInfoTest extends SysuiTestCase {
false,
true,
mAssistantFeedbackController,
- mMetricsLogger);
+ mMetricsLogger,
+ null);
mNotificationInfo.findViewById(R.id.silence).performClick();
mNotificationInfo.findViewById(R.id.done).performClick();
@@ -1269,7 +1300,8 @@ public class NotificationInfoTest extends SysuiTestCase {
false,
false,
mAssistantFeedbackController,
- mMetricsLogger);
+ mMetricsLogger,
+ null);
assertEquals(mContext.getString(R.string.inline_done_button),
((TextView) mNotificationInfo.findViewById(R.id.done)).getText());
@@ -1300,7 +1332,8 @@ public class NotificationInfoTest extends SysuiTestCase {
false,
false,
mAssistantFeedbackController,
- mMetricsLogger);
+ mMetricsLogger,
+ null);
mNotificationInfo.findViewById(R.id.alert).performClick();
mNotificationInfo.findViewById(R.id.done).performClick();
@@ -1335,7 +1368,8 @@ public class NotificationInfoTest extends SysuiTestCase {
false,
false,
mAssistantFeedbackController,
- mMetricsLogger);
+ mMetricsLogger,
+ null);
mNotificationInfo.findViewById(R.id.alert).performClick();
mNotificationInfo.findViewById(R.id.done).performClick();
@@ -1368,7 +1402,8 @@ public class NotificationInfoTest extends SysuiTestCase {
false,
false,
mAssistantFeedbackController,
- mMetricsLogger);
+ mMetricsLogger,
+ null);
mNotificationInfo.findViewById(R.id.alert).performClick();
mNotificationInfo.findViewById(R.id.done).performClick();
@@ -1401,7 +1436,8 @@ public class NotificationInfoTest extends SysuiTestCase {
false,
false,
mAssistantFeedbackController,
- mMetricsLogger);
+ mMetricsLogger,
+ null);
mNotificationInfo.findViewById(R.id.alert).performClick();
@@ -1427,7 +1463,8 @@ public class NotificationInfoTest extends SysuiTestCase {
false,
false,
mAssistantFeedbackController,
- mMetricsLogger);
+ mMetricsLogger,
+ null);
assertFalse(mNotificationInfo.willBeRemoved());
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/PromotedNotificationInfoTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/PromotedNotificationInfoTest.java
new file mode 100644
index 000000000000..b33f93d5b523
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/PromotedNotificationInfoTest.java
@@ -0,0 +1,156 @@
+/*
+ * 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;
+
+import static android.app.Notification.EXTRA_BUILDER_APPLICATION_INFO;
+import static android.app.NotificationManager.IMPORTANCE_LOW;
+
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.INotificationManager;
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.os.UserHandle;
+import android.platform.test.annotations.EnableFlags;
+import android.service.notification.StatusBarNotification;
+import android.telecom.TelecomManager;
+import android.testing.TestableLooper;
+import android.view.LayoutInflater;
+import android.view.View;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.testing.UiEventLoggerFake;
+import com.android.systemui.Dependency;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.res.R;
+import com.android.systemui.statusbar.notification.AssistantFeedbackController;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
+
+import 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;
+
+import java.util.concurrent.CountDownLatch;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+@TestableLooper.RunWithLooper
+public class PromotedNotificationInfoTest extends SysuiTestCase {
+ private static final String TEST_PACKAGE_NAME = "test_package";
+ private static final int TEST_UID = 1;
+ private static final String TEST_CHANNEL = "test_channel";
+ private static final String TEST_CHANNEL_NAME = "TEST CHANNEL NAME";
+
+ private TestableLooper mTestableLooper;
+ private PromotedNotificationInfo mInfo;
+ private NotificationChannel mNotificationChannel;
+ private StatusBarNotification mSbn;
+ private NotificationEntry mEntry;
+ private UiEventLoggerFake mUiEventLogger = new UiEventLoggerFake();
+
+ @Rule
+ public MockitoRule mockito = MockitoJUnit.rule();
+ @Mock
+ private MetricsLogger mMetricsLogger;
+ @Mock
+ private INotificationManager mMockINotificationManager;
+ @Mock
+ private PackageManager mMockPackageManager;
+ @Mock
+ private OnUserInteractionCallback mOnUserInteractionCallback;
+ @Mock
+ private ChannelEditorDialogController mChannelEditorDialogController;
+ @Mock
+ private AssistantFeedbackController mAssistantFeedbackController;
+ @Mock
+ private TelecomManager mTelecomManager;
+
+ @Before
+ public void setUp() throws Exception {
+ final ApplicationInfo applicationInfo = new ApplicationInfo();
+ applicationInfo.uid = TEST_UID; // non-zero
+
+ mNotificationChannel = new NotificationChannel(
+ TEST_CHANNEL, TEST_CHANNEL_NAME, IMPORTANCE_LOW);
+ Notification notification = new Notification();
+ notification.extras.putParcelable(EXTRA_BUILDER_APPLICATION_INFO, applicationInfo);
+ mSbn = new StatusBarNotification(TEST_PACKAGE_NAME, TEST_PACKAGE_NAME, 0, null, TEST_UID, 0,
+ notification, UserHandle.getUserHandleForUid(TEST_UID), null, 0);
+ mEntry = new NotificationEntryBuilder().setSbn(mSbn).build();
+ when(mAssistantFeedbackController.isFeedbackEnabled()).thenReturn(false);
+
+ mTestableLooper = TestableLooper.get(this);
+
+ mContext.addMockSystemService(TelecomManager.class, mTelecomManager);
+
+ mDependency.injectTestDependency(Dependency.BG_LOOPER, mTestableLooper.getLooper());
+ // Inflate the layout
+ final LayoutInflater layoutInflater = LayoutInflater.from(mContext);
+ mInfo = (PromotedNotificationInfo) layoutInflater.inflate(
+ R.layout.promoted_notification_info, null);
+ mInfo.setGutsParent(mock(NotificationGuts.class));
+ // Our view is never attached to a window so the View#post methods in
+ // BundleNotificationInfo never get called. Setting this will skip the post and do the
+ // action immediately.
+ mInfo.mSkipPost = true;
+ }
+
+ @Test
+ @EnableFlags(android.app.Flags.FLAG_NOTIFICATION_CLASSIFICATION_UI)
+ public void testBindNotification_setsOnClickListenerForFeedback() throws Exception {
+
+ // Bind the notification to the Info object
+ final CountDownLatch latch = new CountDownLatch(1);
+ mInfo.bindNotification(
+ mMockPackageManager,
+ mMockINotificationManager,
+ mOnUserInteractionCallback,
+ mChannelEditorDialogController,
+ TEST_PACKAGE_NAME,
+ mNotificationChannel,
+ mEntry,
+ null,
+ null,
+ mUiEventLogger,
+ true,
+ false,
+ true,
+ mAssistantFeedbackController,
+ mMetricsLogger,
+ null);
+ // Click demote button
+ final View demoteButton = mInfo.findViewById(R.id.promoted_demote);
+ demoteButton.performClick();
+ // verify that notiManager tried to demote
+ verify(mMockINotificationManager, atLeastOnce()).setCanBePromoted(TEST_PACKAGE_NAME,
+ mSbn.getUid(), false, true);
+
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculatorTest.kt
index 50db9f7268e4..4b8a0c21f03d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculatorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculatorTest.kt
@@ -17,6 +17,7 @@
package com.android.systemui.statusbar.notification.stack
import android.annotation.DimenRes
+import android.platform.test.annotations.EnableFlags
import android.service.notification.StatusBarNotification
import android.view.View.VISIBLE
import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -30,6 +31,7 @@ import com.android.systemui.statusbar.StatusBarState
import com.android.systemui.statusbar.SysuiStatusBarStateController
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.domain.interactor.SeenNotificationsInteractor
+import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUiForceExpanded
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
import com.android.systemui.statusbar.notification.row.ExpandableView
import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController
@@ -152,6 +154,29 @@ class NotificationStackSizeCalculatorTest : SysuiTestCase() {
}
@Test
+ @EnableFlags(PromotedNotificationUiForceExpanded.FLAG_NAME)
+ fun maxKeyguardNotificationsForPromotedOngoing_onLockscreenSpaceForMinHeightButNotIntrinsicHeight_returnsOne() {
+ setGapHeight(0f)
+ // No divider height since we're testing one element where index = 0
+
+ whenever(sysuiStatusBarStateController.state).thenReturn(StatusBarState.KEYGUARD)
+ whenever(lockscreenShadeTransitionController.fractionToShade).thenReturn(0f)
+
+ val row = createMockRow(10f, isPromotedOngoing = true)
+ whenever(row.getMinHeight(any())).thenReturn(5)
+
+ val maxNotifications =
+ computeMaxKeyguardNotifications(
+ listOf(row),
+ /* spaceForNotifications= */ 5f,
+ /* spaceForShelf= */ 0f,
+ /* shelfHeight= */ 0f,
+ )
+
+ assertThat(maxNotifications).isEqualTo(1)
+ }
+
+ @Test
fun computeMaxKeyguardNotifications_spaceForTwo_returnsTwo() {
setGapHeight(gapHeight)
val shelfHeight = shelfHeight + dividerHeight
@@ -257,6 +282,26 @@ class NotificationStackSizeCalculatorTest : SysuiTestCase() {
}
@Test
+ @EnableFlags(PromotedNotificationUiForceExpanded.FLAG_NAME)
+ fun getSpaceNeeded_onLockscreenEnoughSpacePromotedOngoing_intrinsicHeight() {
+ setGapHeight(0f)
+ // No divider height since we're testing one element where index = 0
+
+ val row = createMockRow(10f, isPromotedOngoing = true)
+ whenever(row.getMinHeight(any())).thenReturn(5)
+
+ val space =
+ sizeCalculator.getSpaceNeeded(
+ row,
+ visibleIndex = 0,
+ previousView = null,
+ stack = stackLayout,
+ onLockscreen = true,
+ )
+ assertThat(space.whenEnoughSpace).isEqualTo(10f)
+ }
+
+ @Test
fun getSpaceNeeded_onLockscreenEnoughSpaceNotStickyHun_minHeight() {
setGapHeight(0f)
// No divider height since we're testing one element where index = 0
@@ -296,6 +341,26 @@ class NotificationStackSizeCalculatorTest : SysuiTestCase() {
}
@Test
+ @EnableFlags(PromotedNotificationUiForceExpanded.FLAG_NAME)
+ fun getSpaceNeeded_onLockscreenSavingSpacePromotedOngoing_minHeight() {
+ setGapHeight(0f)
+ // No divider height since we're testing one element where index = 0
+
+ val expandableView = createMockRow(10f, isPromotedOngoing = true)
+ whenever(expandableView.getMinHeight(any())).thenReturn(5)
+
+ val space =
+ sizeCalculator.getSpaceNeeded(
+ expandableView,
+ visibleIndex = 0,
+ previousView = null,
+ stack = stackLayout,
+ onLockscreen = true,
+ )
+ assertThat(space.whenSavingSpace).isEqualTo(5)
+ }
+
+ @Test
fun getSpaceNeeded_onLockscreenSavingSpaceNotStickyHun_minHeight() {
setGapHeight(0f)
// No divider height since we're testing one element where index = 0
@@ -366,6 +431,7 @@ class NotificationStackSizeCalculatorTest : SysuiTestCase() {
isSticky: Boolean = false,
isRemoved: Boolean = false,
visibility: Int = VISIBLE,
+ isPromotedOngoing: Boolean = false,
): ExpandableNotificationRow {
val row = mock(ExpandableNotificationRow::class.java)
val entry = mock(NotificationEntry::class.java)
@@ -378,6 +444,7 @@ class NotificationStackSizeCalculatorTest : SysuiTestCase() {
whenever(row.getMinHeight(any())).thenReturn(height.toInt())
whenever(row.intrinsicHeight).thenReturn(height.toInt())
whenever(row.heightWithoutLockscreenConstraints).thenReturn(height.toInt())
+ whenever(row.isPromotedOngoing).thenReturn(isPromotedOngoing)
return row
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.kt
index 43ad042ecf78..57b7df7a8d31 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.kt
@@ -162,7 +162,7 @@ class KeyguardStatusBarViewControllerTest : SysuiTestCase() {
Mockito.`when`(iconManagerFactory.create(ArgumentMatchers.any(), ArgumentMatchers.any()))
.thenReturn(iconManager)
- Mockito.`when`(statusBarContentInsetsProviderStore.defaultDisplay)
+ Mockito.`when`(statusBarContentInsetsProviderStore.forDisplay(context.displayId))
.thenReturn(kosmos.mockStatusBarContentInsetsProvider)
allowTestableLooperAsMainThread()
looper.runWithLooper {
@@ -180,6 +180,7 @@ class KeyguardStatusBarViewControllerTest : SysuiTestCase() {
private fun createController(): KeyguardStatusBarViewController {
return KeyguardStatusBarViewController(
kosmos.testDispatcher,
+ context,
keyguardStatusBarView,
carrierTextController,
configurationController,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogCallbacksInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogCallbacksInteractorTest.kt
index fec186e862be..b837253f44a4 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogCallbacksInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogCallbacksInteractorTest.kt
@@ -16,12 +16,16 @@
package com.android.systemui.volume.dialog.domain.interactor
+import android.media.AudioManager.RINGER_MODE_NORMAL
+import android.media.AudioManager.RINGER_MODE_SILENT
+import android.media.AudioManager.RINGER_MODE_VIBRATE
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.kosmos.collectLastValue
import com.android.systemui.kosmos.runTest
import com.android.systemui.kosmos.useUnconfinedTestDispatcher
+import com.android.systemui.plugins.fakeVolumeDialogController
import com.android.systemui.testKosmos
import com.android.systemui.volume.dialog.domain.model.VolumeDialogEventModel
import com.google.common.truth.Truth.assertThat
@@ -44,4 +48,30 @@ class VolumeDialogCallbacksInteractorTest : SysuiTestCase() {
val event by collectLastValue(underTest.event)
assertThat(event).isInstanceOf(VolumeDialogEventModel.SubscribedToEvents::class.java)
}
+
+ @Test
+ fun showSilentHint_setsRingerModeToNormal() =
+ kosmos.runTest {
+ fakeVolumeDialogController.setRingerMode(RINGER_MODE_VIBRATE, false)
+
+ underTest // It should eagerly collect the values and update the controller
+ fakeVolumeDialogController.onShowSilentHint()
+ fakeVolumeDialogController.getState()
+
+ assertThat(fakeVolumeDialogController.state.ringerModeInternal)
+ .isEqualTo(RINGER_MODE_NORMAL)
+ }
+
+ @Test
+ fun showVibrateHint_setsRingerModeToSilent() =
+ kosmos.runTest {
+ fakeVolumeDialogController.setRingerMode(RINGER_MODE_VIBRATE, false)
+
+ underTest // It should eagerly collect the values and update the controller
+ fakeVolumeDialogController.onShowVibrateHint()
+ fakeVolumeDialogController.getState()
+
+ assertThat(fakeVolumeDialogController.state.ringerModeInternal)
+ .isEqualTo(RINGER_MODE_SILENT)
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/dialog/ringer/domain/VolumeDialogRingerInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/dialog/ringer/domain/VolumeDialogRingerInteractorTest.kt
index 7d5559933cd8..12885a83a70b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/dialog/ringer/domain/VolumeDialogRingerInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/dialog/ringer/domain/VolumeDialogRingerInteractorTest.kt
@@ -25,14 +25,13 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.settingslib.volume.shared.model.RingerMode
import com.android.systemui.SysuiTestCase
-import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.kosmos.testScope
+import com.android.systemui.kosmos.collectLastValue
+import com.android.systemui.kosmos.runTest
+import com.android.systemui.kosmos.useUnconfinedTestDispatcher
import com.android.systemui.plugins.fakeVolumeDialogController
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.runCurrent
-import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -43,8 +42,7 @@ import org.junit.runner.RunWith
@TestableLooper.RunWithLooper
class VolumeDialogRingerInteractorTest : SysuiTestCase() {
- private val kosmos = testKosmos()
- private val testScope = kosmos.testScope
+ private val kosmos = testKosmos().useUnconfinedTestDispatcher()
private val controller = kosmos.fakeVolumeDialogController
private lateinit var underTest: VolumeDialogRingerInteractor
@@ -57,13 +55,11 @@ class VolumeDialogRingerInteractorTest : SysuiTestCase() {
@Test
fun setRingerMode_normal() =
- testScope.runTest {
- runCurrent()
+ kosmos.runTest {
val ringerModel by collectLastValue(underTest.ringerModel)
underTest.setRingerMode(RingerMode(RINGER_MODE_NORMAL))
controller.getState()
- runCurrent()
assertThat(ringerModel).isNotNull()
assertThat(ringerModel?.currentRingerMode).isEqualTo(RingerMode(RINGER_MODE_NORMAL))
@@ -71,13 +67,11 @@ class VolumeDialogRingerInteractorTest : SysuiTestCase() {
@Test
fun setRingerMode_silent() =
- testScope.runTest {
- runCurrent()
+ kosmos.runTest {
val ringerModel by collectLastValue(underTest.ringerModel)
underTest.setRingerMode(RingerMode(RINGER_MODE_SILENT))
controller.getState()
- runCurrent()
assertThat(ringerModel).isNotNull()
assertThat(ringerModel?.currentRingerMode).isEqualTo(RingerMode(RINGER_MODE_SILENT))
@@ -85,13 +79,11 @@ class VolumeDialogRingerInteractorTest : SysuiTestCase() {
@Test
fun setRingerMode_vibrate() =
- testScope.runTest {
- runCurrent()
+ kosmos.runTest {
val ringerModel by collectLastValue(underTest.ringerModel)
underTest.setRingerMode(RingerMode(RINGER_MODE_VIBRATE))
controller.getState()
- runCurrent()
assertThat(ringerModel).isNotNull()
assertThat(ringerModel?.currentRingerMode).isEqualTo(RingerMode(RINGER_MODE_VIBRATE))
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/dialog/sliders/domain/interactor/VolumeDialogSliderInputEventsInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/dialog/sliders/domain/interactor/VolumeDialogSliderInputEventsInteractorTest.kt
index 799ca4a49038..0a50722d8fed 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/dialog/sliders/domain/interactor/VolumeDialogSliderInputEventsInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/dialog/sliders/domain/interactor/VolumeDialogSliderInputEventsInteractorTest.kt
@@ -18,7 +18,6 @@ package com.android.systemui.volume.dialog.sliders.domain.interactor
import android.app.ActivityManager
import android.testing.TestableLooper
-import android.view.MotionEvent
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
@@ -29,6 +28,7 @@ import com.android.systemui.testKosmos
import com.android.systemui.volume.Events
import com.android.systemui.volume.dialog.domain.interactor.volumeDialogVisibilityInteractor
import com.android.systemui.volume.dialog.shared.model.VolumeDialogVisibilityModel
+import com.android.systemui.volume.dialog.sliders.shared.model.SliderInputEvent
import com.google.common.truth.Truth.assertThat
import kotlin.test.Test
import kotlin.time.Duration.Companion.seconds
@@ -73,16 +73,7 @@ class VolumeDialogSliderInputEventsInteractorTest : SysuiTestCase() {
assertThat(dialogVisibility)
.isInstanceOf(VolumeDialogVisibilityModel.Visible::class.java)
- underTest.onTouchEvent(
- MotionEvent.obtain(
- /* downTime = */ 0,
- /* eventTime = */ 0,
- /* action = */ 0,
- /* x = */ 0f,
- /* y = */ 0f,
- /* metaState = */ 0,
- )
- )
+ underTest.onTouchEvent(SliderInputEvent.Touch.Start(0f, 0f))
advanceTimeBy(volumeDialogTimeout / 2)
assertThat(dialogVisibility)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/wallpapers/GradientColorWallpaperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/wallpapers/GradientColorWallpaperTest.kt
index ba6ea9f5e8bb..89410593fe62 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/wallpapers/GradientColorWallpaperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/wallpapers/GradientColorWallpaperTest.kt
@@ -16,11 +16,14 @@
package com.android.systemui.wallpapers
+import android.app.Flags
import android.content.Context
import android.graphics.Canvas
import android.graphics.Paint
import android.graphics.Rect
import android.graphics.RectF
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
import android.service.wallpaper.WallpaperService.Engine
import android.testing.TestableLooper.RunWithLooper
import android.view.Surface
@@ -37,6 +40,7 @@ import org.mockito.Mockito.spy
import org.mockito.MockitoAnnotations
import org.mockito.kotlin.any
import org.mockito.kotlin.verify
+import org.mockito.kotlin.verifyZeroInteractions
import org.mockito.kotlin.whenever
@SmallTest
@@ -70,6 +74,18 @@ class GradientColorWallpaperTest : SysuiTestCase() {
}
@Test
+ @DisableFlags(Flags.FLAG_ENABLE_CONNECTED_DISPLAYS_WALLPAPER)
+ fun onSurfaceRedrawNeeded_flagDisabled_shouldNotDrawInCanvas() {
+ val engine = createGradientColorWallpaperEngine()
+ engine.onCreate(surfaceHolder)
+
+ engine.onSurfaceRedrawNeeded(surfaceHolder)
+
+ verifyZeroInteractions(canvas)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_CONNECTED_DISPLAYS_WALLPAPER)
fun onSurfaceRedrawNeeded_shouldDrawInCanvas() {
val engine = createGradientColorWallpaperEngine()
engine.onCreate(surfaceHolder)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/wallpapers/data/repository/FakeWallpaperRepository.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/wallpapers/data/repository/FakeWallpaperRepository.kt
index 2985053f56d5..d6343c840d9b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/wallpapers/data/repository/FakeWallpaperRepository.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/wallpapers/data/repository/FakeWallpaperRepository.kt
@@ -26,4 +26,5 @@ class FakeWallpaperRepository : WallpaperRepository {
override val wallpaperInfo = MutableStateFlow<WallpaperInfo?>(null)
override val wallpaperSupportsAmbientMode = flowOf(false)
override var rootView: View? = null
+ override val shouldSendFocalArea = MutableStateFlow(false)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/wallpapers/data/repository/WallpaperRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/wallpapers/data/repository/WallpaperRepositoryImplTest.kt
index 03753d9aa884..115edd0d3bb0 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/wallpapers/data/repository/WallpaperRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/wallpapers/data/repository/WallpaperRepositoryImplTest.kt
@@ -67,7 +67,6 @@ class WallpaperRepositoryImplTest : SysuiTestCase() {
fakeBroadcastDispatcher,
userRepository,
keyguardRepository,
- keyguardClockRepository,
wallpaperManager,
context,
)
@@ -252,7 +251,7 @@ class WallpaperRepositoryImplTest : SysuiTestCase() {
@EnableFlags(Flags.FLAG_MAGIC_PORTRAIT_WALLPAPERS)
fun shouldSendNotificationLayout_setMagicPortraitWallpaper_launchSendLayoutJob() =
testScope.runTest {
- val latest by collectLastValue(underTest.shouldSendNotificationLayout)
+ val latest by collectLastValue(underTest.shouldSendFocalArea)
val magicPortraitWallpaper =
mock<WallpaperInfo>().apply {
whenever(this.component)
@@ -273,7 +272,7 @@ class WallpaperRepositoryImplTest : SysuiTestCase() {
@EnableFlags(Flags.FLAG_MAGIC_PORTRAIT_WALLPAPERS)
fun shouldSendNotificationLayout_setNotMagicPortraitWallpaper_cancelSendLayoutJob() =
testScope.runTest {
- val latest by collectLastValue(underTest.shouldSendNotificationLayout)
+ val latest by collectLastValue(underTest.shouldSendFocalArea)
val magicPortraitWallpaper = MAGIC_PORTRAIT_WP
whenever(wallpaperManager.getWallpaperInfoForUser(any()))
.thenReturn(magicPortraitWallpaper)
diff --git a/packages/SystemUI/res-keyguard/layout/bouncer_message_view.xml b/packages/SystemUI/res-keyguard/layout/bouncer_message_view.xml
index f7dac13888c0..ea494b4642d5 100644
--- a/packages/SystemUI/res-keyguard/layout/bouncer_message_view.xml
+++ b/packages/SystemUI/res-keyguard/layout/bouncer_message_view.xml
@@ -21,7 +21,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/keyguard_lock_padding"
- android:focusable="true"
+ android:focusable="false"
/>
<com.android.keyguard.BouncerKeyguardMessageArea
@@ -30,6 +30,6 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/secondary_message_padding"
- android:focusable="true" />
+ android:focusable="false" />
</merge>
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_password_motion_layout.xml b/packages/SystemUI/res-keyguard/layout/keyguard_password_motion_layout.xml
index 2a8f1b596711..f231df2f1a10 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_password_motion_layout.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_password_motion_layout.xml
@@ -66,7 +66,7 @@
<com.android.systemui.bouncer.ui.BouncerMessageView
android:id="@+id/bouncer_message_view"
- android:importantForAccessibility="noHideDescendants"
+ android:screenReaderFocusable="true"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" />
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_password_view.xml b/packages/SystemUI/res-keyguard/layout/keyguard_password_view.xml
index 76f6f599c54c..04457229d573 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_password_view.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_password_view.xml
@@ -31,7 +31,7 @@
<com.android.systemui.bouncer.ui.BouncerMessageView
android:id="@+id/bouncer_message_view"
- android:importantForAccessibility="noHideDescendants"
+ android:screenReaderFocusable="true"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_pattern_motion_layout.xml b/packages/SystemUI/res-keyguard/layout/keyguard_pattern_motion_layout.xml
index 5879c110d8a1..b184344f2f24 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_pattern_motion_layout.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_pattern_motion_layout.xml
@@ -67,7 +67,7 @@
<com.android.systemui.bouncer.ui.BouncerMessageView
android:id="@+id/bouncer_message_view"
- android:importantForAccessibility="noHideDescendants"
+ android:screenReaderFocusable="true"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" />
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_pattern_view.xml b/packages/SystemUI/res-keyguard/layout/keyguard_pattern_view.xml
index 3f7b02835357..0e15ff66f3ee 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_pattern_view.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_pattern_view.xml
@@ -35,7 +35,7 @@
<com.android.systemui.bouncer.ui.BouncerMessageView
android:id="@+id/bouncer_message_view"
- android:importantForAccessibility="noHideDescendants"
+ android:screenReaderFocusable="true"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" />
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_pin_motion_layout.xml b/packages/SystemUI/res-keyguard/layout/keyguard_pin_motion_layout.xml
index b464fb3bafed..f6ac02aee657 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_pin_motion_layout.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_pin_motion_layout.xml
@@ -74,7 +74,7 @@
<com.android.systemui.bouncer.ui.BouncerMessageView
android:id="@+id/bouncer_message_view"
- android:importantForAccessibility="noHideDescendants"
+ android:screenReaderFocusable="true"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" />
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_pin_view.xml b/packages/SystemUI/res-keyguard/layout/keyguard_pin_view.xml
index 21580731aed2..ba4da794d777 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_pin_view.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_pin_view.xml
@@ -32,7 +32,7 @@
<com.android.systemui.bouncer.ui.BouncerMessageView
android:id="@+id/bouncer_message_view"
- android:importantForAccessibility="noHideDescendants"
+ android:screenReaderFocusable="true"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" />
diff --git a/packages/SystemUI/res-keyguard/layout/shade_carrier_new.xml b/packages/SystemUI/res-keyguard/layout/shade_carrier_new.xml
index cc99f5e125f3..dd5f7e4e2ed4 100644
--- a/packages/SystemUI/res-keyguard/layout/shade_carrier_new.xml
+++ b/packages/SystemUI/res-keyguard/layout/shade_carrier_new.xml
@@ -30,7 +30,7 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
- android:textAppearance="@style/TextAppearance.QS.Status.Carriers"
+ android:textAppearance="@style/TextAppearance.QS.Status"
android:layout_marginEnd="@dimen/qs_carrier_margin_width"
android:visibility="gone"
android:textDirection="locale"
diff --git a/packages/SystemUI/res-keyguard/values/styles.xml b/packages/SystemUI/res-keyguard/values/styles.xml
index 433c0a71008d..e7d6b2fe08f4 100644
--- a/packages/SystemUI/res-keyguard/values/styles.xml
+++ b/packages/SystemUI/res-keyguard/values/styles.xml
@@ -159,6 +159,17 @@
<item name="android:shadowRadius">?attr/shadowRadius</item>
</style>
+ <style name="TextAppearance.Keyguard.BottomArea.DoubleShadow">
+ <item name="keyShadowBlur">0.5dp</item>
+ <item name="keyShadowOffsetX">0.5dp</item>
+ <item name="keyShadowOffsetY">0.5dp</item>
+ <item name="keyShadowAlpha">0.8</item>
+ <item name="ambientShadowBlur">0.5dp</item>
+ <item name="ambientShadowOffsetX">0.5dp</item>
+ <item name="ambientShadowOffsetY">0.5dp</item>
+ <item name="ambientShadowAlpha">0.6</item>
+ </style>
+
<style name="TextAppearance.Keyguard.BottomArea.Button">
<item name="android:shadowRadius">0</item>
</style>
diff --git a/packages/SystemUI/res/drawable/notif_footer_btn_background.xml b/packages/SystemUI/res/drawable/notif_footer_btn_background.xml
index 1d5e09d9b260..e1e60920ab01 100644
--- a/packages/SystemUI/res/drawable/notif_footer_btn_background.xml
+++ b/packages/SystemUI/res/drawable/notif_footer_btn_background.xml
@@ -26,6 +26,9 @@
<padding
android:left="20dp"
android:right="20dp" />
+ <!-- TODO(b/294830092): Update to the blur surface effect color token when
+ the resource workaround is resolved and
+ notification_shade_blur is enabled. -->
<solid android:color="@androidprv:color/materialColorSurfaceContainerHigh" />
</shape>
</inset>
diff --git a/packages/SystemUI/res/layout/low_light_clock_dream.xml b/packages/SystemUI/res/layout/low_light_clock_dream.xml
new file mode 100644
index 000000000000..3d74a9fd8ae3
--- /dev/null
+++ b/packages/SystemUI/res/layout/low_light_clock_dream.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<FrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/low_light_clock_dream"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="@color/low_light_clock_background_color">
+
+ <TextClock
+ android:id="@+id/low_light_text_clock"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/low_light_clock_text_size"
+ android:layout_gravity="center"
+ android:fontFamily="google-sans-clock"
+ android:gravity="center_horizontal"
+ android:textColor="@color/low_light_clock_text_color"
+ android:autoSizeTextType="uniform"
+ android:autoSizeMaxTextSize="@dimen/low_light_clock_text_size"
+ android:format12Hour="h:mm"
+ android:format24Hour="H:mm"/>
+
+ <TextView
+ android:id="@+id/charging_status_text_view"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="@dimen/keyguard_indication_margin_bottom"
+ android:gravity="center"
+ android:minHeight="@dimen/low_light_clock_charging_text_min_height"
+ android:layout_gravity="center_horizontal|bottom"
+ android:paddingStart="@dimen/keyguard_indication_text_padding"
+ android:paddingEnd="@dimen/keyguard_indication_text_padding"
+ android:textAppearance="@style/TextAppearance.Keyguard.BottomArea"
+ android:textSize="@dimen/low_light_clock_charging_text_size"
+ android:textFontWeight="@integer/low_light_clock_charging_text_font_weight"
+ android:maxLines="2"
+ android:ellipsize="end"
+ android:accessibilityLiveRegion="polite" />
+ </FrameLayout>
+
diff --git a/packages/SystemUI/res/layout/promoted_notification_info.xml b/packages/SystemUI/res/layout/promoted_notification_info.xml
new file mode 100644
index 000000000000..5d170a98a806
--- /dev/null
+++ b/packages/SystemUI/res/layout/promoted_notification_info.xml
@@ -0,0 +1,387 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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.
+-->
+
+<com.android.systemui.statusbar.notification.row.PromotedNotificationInfo
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ android:id="@+id/notification_guts"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:focusable="true"
+ android:clipChildren="false"
+ android:clipToPadding="true"
+ android:orientation="vertical"
+ android:paddingStart="@dimen/notification_shade_content_margin_horizontal">
+
+ <!-- Package Info -->
+ <LinearLayout
+ android:id="@+id/header"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="center_vertical"
+ android:clipChildren="false"
+ android:paddingTop="@dimen/notification_guts_header_top_padding"
+ android:clipToPadding="true">
+ <ImageView
+ android:id="@+id/pkg_icon"
+ android:layout_width="@dimen/notification_guts_conversation_icon_size"
+ android:layout_height="@dimen/notification_guts_conversation_icon_size"
+ android:layout_centerVertical="true"
+ android:layout_alignParentStart="true"
+ android:layout_marginEnd="15dp" />
+ <LinearLayout
+ android:id="@+id/names"
+ android:layout_weight="1"
+ android:layout_width="0dp"
+ android:orientation="vertical"
+ android:layout_height="wrap_content"
+ android:minHeight="@dimen/notification_guts_conversation_icon_size"
+ android:layout_centerVertical="true"
+ android:gravity="center_vertical"
+ android:layout_alignEnd="@id/pkg_icon"
+ android:layout_toEndOf="@id/pkg_icon">
+ <TextView
+ android:id="@+id/channel_name"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:textDirection="locale"
+ style="@style/TextAppearance.NotificationImportanceChannel"/>
+ <TextView
+ android:id="@+id/group_name"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:textDirection="locale"
+ android:ellipsize="end"
+ style="@style/TextAppearance.NotificationImportanceChannelGroup"/>
+ <TextView
+ android:id="@+id/pkg_name"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ style="@style/TextAppearance.NotificationImportanceApp"
+ android:ellipsize="end"
+ android:textDirection="locale"
+ android:maxLines="1"/>
+ <TextView
+ android:id="@+id/delegate_name"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_centerVertical="true"
+ style="@style/TextAppearance.NotificationImportanceHeader"
+ android:layout_marginStart="2dp"
+ android:layout_marginEnd="2dp"
+ android:ellipsize="end"
+ android:textDirection="locale"
+ android:text="@string/notification_delegate_header"
+ android:maxLines="1" />
+
+ </LinearLayout>
+
+ <!-- end aligned fields -->
+ <!-- Optional link to app. Only appears if the channel is not disabled and the app
+asked for it -->
+ <ImageButton
+ android:id="@+id/app_settings"
+ android:layout_width="@dimen/notification_importance_toggle_size"
+ android:layout_height="@dimen/notification_importance_toggle_size"
+ android:layout_centerVertical="true"
+ android:visibility="gone"
+ android:background="@drawable/ripple_drawable"
+ android:contentDescription="@string/notification_app_settings"
+ android:src="@drawable/ic_info"
+ android:layout_toStartOf="@id/info"
+ android:tint="@androidprv:color/materialColorPrimary"/>
+ <ImageButton
+ android:id="@+id/info"
+ android:layout_width="@dimen/notification_importance_toggle_size"
+ android:layout_height="@dimen/notification_importance_toggle_size"
+ android:layout_centerVertical="true"
+ android:contentDescription="@string/notification_more_settings"
+ android:background="@drawable/ripple_drawable_20dp"
+ android:src="@drawable/ic_settings"
+ android:tint="@androidprv:color/materialColorPrimary"
+ android:layout_alignParentEnd="true" />
+
+ </LinearLayout>
+
+ <LinearLayout
+ android:id="@+id/inline_controls"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingEnd="@dimen/notification_shade_content_margin_horizontal"
+ android:layout_marginTop="@dimen/notification_guts_option_vertical_padding"
+ android:clipChildren="false"
+ android:clipToPadding="false"
+ android:orientation="vertical">
+
+ <!-- Non configurable app/channel text. appears instead of @+id/interruptiveness_settings-->
+ <TextView
+ android:id="@+id/non_configurable_text"
+ android:text="@string/notification_unblockable_desc"
+ android:visibility="gone"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ style="@*android:style/TextAppearance.DeviceDefault.Notification" />
+
+ <!-- Non configurable app/channel text. appears instead of @+id/interruptiveness_settings-->
+ <TextView
+ android:id="@+id/non_configurable_call_text"
+ android:text="@string/notification_unblockable_call_desc"
+ android:visibility="gone"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ style="@*android:style/TextAppearance.DeviceDefault.Notification" />
+
+ <!-- Non configurable multichannel text. appears instead of @+id/interruptiveness_settings-->
+ <TextView
+ android:id="@+id/non_configurable_multichannel_text"
+ android:text="@string/notification_multichannel_desc"
+ android:visibility="gone"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ style="@*android:style/TextAppearance.DeviceDefault.Notification" />
+
+ <LinearLayout
+ android:id="@+id/interruptiveness_settings"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="center"
+ android:orientation="vertical">
+ <com.android.systemui.statusbar.notification.row.ButtonLinearLayout
+ android:id="@+id/automatic"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="@dimen/notification_importance_button_separation"
+ android:padding="@dimen/notification_importance_button_padding"
+ android:clickable="true"
+ android:focusable="true"
+ android:background="@drawable/notification_guts_priority_button_bg"
+ android:orientation="vertical"
+ android:visibility="gone">
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:gravity="center"
+ >
+ <ImageView
+ android:id="@+id/automatic_icon"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:src="@drawable/ic_notifications_automatic"
+ android:background="@android:color/transparent"
+ android:tint="@color/notification_guts_priority_contents"
+ android:clickable="false"
+ android:focusable="false"/>
+ <TextView
+ android:id="@+id/automatic_label"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="@dimen/notification_importance_drawable_padding"
+ android:layout_weight="1"
+ android:ellipsize="end"
+ android:maxLines="1"
+ android:clickable="false"
+ android:focusable="false"
+ android:textAppearance="@style/TextAppearance.NotificationImportanceButton"
+ android:text="@string/notification_automatic_title"/>
+ </LinearLayout>
+ <TextView
+ android:id="@+id/automatic_summary"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="@dimen/notification_importance_button_description_top_margin"
+ android:visibility="gone"
+ android:text="@string/notification_channel_summary_automatic"
+ android:clickable="false"
+ android:focusable="false"
+ android:ellipsize="end"
+ android:maxLines="2"
+ android:textAppearance="@style/TextAppearance.NotificationImportanceDetail"/>
+ </com.android.systemui.statusbar.notification.row.ButtonLinearLayout>
+
+ <com.android.systemui.statusbar.notification.row.ButtonLinearLayout
+ android:id="@+id/alert"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:padding="@dimen/notification_importance_button_padding"
+ android:clickable="true"
+ android:focusable="true"
+ android:background="@drawable/notification_guts_priority_button_bg"
+ android:orientation="vertical">
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:gravity="center"
+ >
+ <ImageView
+ android:id="@+id/alert_icon"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:src="@drawable/ic_notifications_alert"
+ android:background="@android:color/transparent"
+ android:tint="@color/notification_guts_priority_contents"
+ android:clickable="false"
+ android:focusable="false"/>
+ <TextView
+ android:id="@+id/alert_label"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="@dimen/notification_importance_drawable_padding"
+ android:layout_weight="1"
+ android:ellipsize="end"
+ android:maxLines="1"
+ android:clickable="false"
+ android:focusable="false"
+ android:textAppearance="@style/TextAppearance.NotificationImportanceButton"
+ android:text="@string/notification_alert_title"/>
+ </LinearLayout>
+ <TextView
+ android:id="@+id/alert_summary"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="@dimen/notification_importance_button_description_top_margin"
+ android:visibility="gone"
+ android:text="@string/notification_channel_summary_default"
+ android:clickable="false"
+ android:focusable="false"
+ android:ellipsize="end"
+ android:maxLines="2"
+ android:textAppearance="@style/TextAppearance.NotificationImportanceDetail"/>
+ </com.android.systemui.statusbar.notification.row.ButtonLinearLayout>
+
+ <com.android.systemui.statusbar.notification.row.ButtonLinearLayout
+ android:id="@+id/silence"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="@dimen/notification_importance_button_separation"
+ android:padding="@dimen/notification_importance_button_padding"
+ android:clickable="true"
+ android:focusable="true"
+ android:background="@drawable/notification_guts_priority_button_bg"
+ android:orientation="vertical">
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:gravity="center"
+ >
+ <ImageView
+ android:id="@+id/silence_icon"
+ android:src="@drawable/ic_notifications_silence"
+ android:background="@android:color/transparent"
+ android:tint="@color/notification_guts_priority_contents"
+ android:layout_gravity="center"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:clickable="false"
+ android:focusable="false"/>
+ <TextView
+ android:id="@+id/silence_label"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:ellipsize="end"
+ android:maxLines="1"
+ android:clickable="false"
+ android:focusable="false"
+ android:layout_toEndOf="@id/silence_icon"
+ android:layout_marginStart="@dimen/notification_importance_drawable_padding"
+ android:textAppearance="@style/TextAppearance.NotificationImportanceButton"
+ android:text="@string/notification_silence_title"/>
+ </LinearLayout>
+ <TextView
+ android:id="@+id/silence_summary"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="@dimen/notification_importance_button_description_top_margin"
+ android:visibility="gone"
+ android:text="@string/notification_channel_summary_low"
+ android:clickable="false"
+ android:focusable="false"
+ android:ellipsize="end"
+ android:maxLines="2"
+ android:textAppearance="@style/TextAppearance.NotificationImportanceDetail"/>
+ </com.android.systemui.statusbar.notification.row.ButtonLinearLayout>
+
+ </LinearLayout>
+
+ <RelativeLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:minHeight="60dp"
+ android:gravity="center_vertical"
+ android:paddingStart="4dp"
+ android:paddingEnd="4dp"
+ >
+ <TextView
+ android:id="@+id/promoted_demote"
+ android:text="@string/notification_inline_disable_promotion"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentStart="true"
+ android:gravity="start|center_vertical"
+ android:minWidth="@dimen/notification_importance_toggle_size"
+ android:minHeight="@dimen/notification_importance_toggle_size"
+ android:maxWidth="200dp"
+ style="@style/TextAppearance.NotificationInfo.Button"/>
+ <TextView
+ android:id="@+id/promoted_dismiss"
+ android:text="@string/notification_inline_dismiss"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentEnd="true"
+ android:gravity="end|center_vertical"
+ android:minWidth="@dimen/notification_importance_toggle_size"
+ android:minHeight="@dimen/notification_importance_toggle_size"
+ android:maxWidth="125dp"
+ style="@style/TextAppearance.NotificationInfo.Button"/>
+ </RelativeLayout>
+
+ <RelativeLayout
+ android:id="@+id/bottom_buttons"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:minHeight="60dp"
+ android:gravity="center_vertical"
+ android:paddingStart="4dp"
+ android:paddingEnd="4dp"
+ >
+ <TextView
+ android:id="@+id/turn_off_notifications"
+ android:text="@string/inline_turn_off_notifications"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentStart="true"
+ android:gravity="start|center_vertical"
+ android:minWidth="@dimen/notification_importance_toggle_size"
+ android:minHeight="@dimen/notification_importance_toggle_size"
+ android:maxWidth="200dp"
+ style="@style/TextAppearance.NotificationInfo.Button"/>
+ <TextView
+ android:id="@+id/done"
+ android:text="@string/inline_ok_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentEnd="true"
+ android:gravity="end|center_vertical"
+ android:minWidth="@dimen/notification_importance_toggle_size"
+ android:minHeight="@dimen/notification_importance_toggle_size"
+ android:maxWidth="125dp"
+ style="@style/TextAppearance.NotificationInfo.Button"/>
+ </RelativeLayout>
+ </LinearLayout>
+</com.android.systemui.statusbar.notification.row.PromotedNotificationInfo>
diff --git a/packages/SystemUI/res/layout/shade_carrier.xml b/packages/SystemUI/res/layout/shade_carrier.xml
index 0fed393a7ed3..6a5df9c3ed10 100644
--- a/packages/SystemUI/res/layout/shade_carrier.xml
+++ b/packages/SystemUI/res/layout/shade_carrier.xml
@@ -33,7 +33,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
- android:textAppearance="@style/TextAppearance.QS.Status.Carriers"
+ android:textAppearance="@style/TextAppearance.QS.Status"
android:textDirection="locale"
android:marqueeRepeatLimit="marquee_forever"
android:singleLine="true"
diff --git a/packages/SystemUI/res/layout/shade_carrier_group.xml b/packages/SystemUI/res/layout/shade_carrier_group.xml
index 2e8f98cbd190..6551f3b8160d 100644
--- a/packages/SystemUI/res/layout/shade_carrier_group.xml
+++ b/packages/SystemUI/res/layout/shade_carrier_group.xml
@@ -32,7 +32,7 @@
android:minWidth="48dp"
android:minHeight="48dp"
android:gravity="center_vertical"
- android:textAppearance="@style/TextAppearance.QS.Status.Carriers.NoCarrierText"
+ android:textAppearance="@style/TextAppearance.QS.Status"
android:textDirection="locale"
android:marqueeRepeatLimit="marquee_forever"
android:singleLine="true"
diff --git a/packages/SystemUI/res/layout/volume_dialog_slider.xml b/packages/SystemUI/res/layout/volume_dialog_slider.xml
index c5f468e731f5..2628f4991b49 100644
--- a/packages/SystemUI/res/layout/volume_dialog_slider.xml
+++ b/packages/SystemUI/res/layout/volume_dialog_slider.xml
@@ -18,14 +18,10 @@
android:layout_height="match_parent"
android:maxHeight="@dimen/volume_dialog_slider_height">
- <com.google.android.material.slider.Slider
+ <androidx.compose.ui.platform.ComposeView
android:id="@+id/volume_dialog_slider"
- style="@style/SystemUI.Material3.Slider.Volume"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
- android:layout_marginTop="-20dp"
- android:layout_marginBottom="-20dp"
- android:orientation="vertical"
- android:theme="@style/Theme.Material3.DayNight" />
+ android:orientation="vertical" />
</FrameLayout> \ No newline at end of file
diff --git a/packages/SystemUI/res/values-xlarge-land/config.xml b/packages/SystemUI/res/values-xlarge-land/config.xml
index 5e4304e1c13a..6d8b64ade259 100644
--- a/packages/SystemUI/res/values-xlarge-land/config.xml
+++ b/packages/SystemUI/res/values-xlarge-land/config.xml
@@ -16,4 +16,5 @@
<resources>
<item name="shortcut_helper_screen_width_fraction" format="float" type="dimen">0.8</item>
+ <bool name="center_align_magic_portrait_shape">true</bool>
</resources>
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index 36ede64f91d9..015e0e83e57d 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -24,6 +24,7 @@
<color name="qs_tile_divider">#29ffffff</color><!-- 16% white -->
<color name="qs_detail_button_white">#B3FFFFFF</color><!-- 70% white -->
<color name="status_bar_clock_color">#FFFFFFFF</color>
+ <color name="shade_header_text_color">#FFFFFFFF</color>
<color name="qs_tile_disabled_color">#9E9E9E</color> <!-- 38% black -->
<color name="status_bar_icons_hover_color_light">#38FFFFFF</color> <!-- 22% white -->
<color name="status_bar_icons_hover_color_dark">#38000000</color> <!-- 22% black -->
@@ -260,4 +261,8 @@
<!-- Rear Display Education -->
<color name="rear_display_overlay_animation_background_color">#1E1B17</color>
<color name="rear_display_overlay_dialog_background_color">#1E1B17</color>
+
+ <!-- Low light Dream -->
+ <color name="low_light_clock_background_color">#000000</color>
+ <color name="low_light_clock_text_color">#CCCCCC</color>
</resources>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 940e87d3d163..68e33f27aefa 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -1104,7 +1104,10 @@
-->
<bool name="config_userSwitchingMustGoThroughLoginScreen">false</bool>
-
<!-- The dream component used when the device is low light environment. -->
<string translatable="false" name="config_lowLightDreamComponent"/>
+
+ <!--Whether we should position magic portrait shape effects in the center of lockscreen
+ it's false by default, and only be true in tablet landscape -->
+ <bool name="center_align_magic_portrait_shape">false</bool>
</resources>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 8a0ffb90bf09..c7f037f3d619 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -266,6 +266,9 @@
<!-- Height of a large notification in the status bar -->
<dimen name="notification_max_height">358dp</dimen>
+ <!-- Height of a large promoted ongoing notification in the status bar -->
+ <dimen name="notification_max_height_for_promoted_ongoing">272dp</dimen>
+
<!-- Height of a heads up notification in the status bar for legacy custom views -->
<dimen name="notification_max_heads_up_height_legacy">128dp</dimen>
@@ -1611,6 +1614,18 @@
<!-- GLANCEABLE_HUB -> DREAMING transition: Amount to shift dream overlay on entering -->
<dimen name="hub_to_dreaming_transition_dream_overlay_translation_x">824dp</dimen>
+ <!-- Low light clock -->
+ <!-- The text size of the low light clock is intentionally defined in dp to avoid scaling -->
+ <dimen name="low_light_clock_text_size">260dp</dimen>
+ <dimen name="low_light_clock_charging_text_size">14sp</dimen>
+ <dimen name="low_light_clock_charging_text_min_height">48dp</dimen>
+ <integer name="low_light_clock_charging_text_font_weight">500</integer>
+
+ <dimen name="low_light_clock_translate_animation_offset">40dp</dimen>
+ <integer name="low_light_clock_translate_animation_duration_ms">1167</integer>
+ <integer name="low_light_clock_alpha_animation_in_start_delay_ms">233</integer>
+ <integer name="low_light_clock_alpha_animation_duration_ms">250</integer>
+
<!-- Distance that the full shade transition takes in order for media to fully transition to
the shade -->
<dimen name="lockscreen_shade_media_transition_distance">120dp</dimen>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 1e217de60bad..3b89e9c42c93 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -2083,6 +2083,12 @@
<!-- [CHAR LIMIT=80] Text shown in feedback button in notification guts for a bundled notification -->
<string name="notification_guts_bundle_feedback" translatable="false">Provide Bundle Feedback</string>
+ <!-- [CHAR LIMIT=30] Text shown in button used to dismiss this single notification. -->
+ <string name="notification_inline_dismiss">Dismiss</string>
+
+ <!-- [CHAR LIMIT=30] Text shown in button used to prevent app from showing Live Updates, for this notification and all future ones -->
+ <string name="notification_inline_disable_promotion">Don\'t show again</string>
+
<!-- Notification: Control panel: Label that displays when the app's notifications cannot be blocked. -->
<string name="notification_unblockable_desc">These notifications can\'t be modified.</string>
@@ -2310,9 +2316,6 @@
<string name="group_system_lock_screen">Lock screen</string>
<!-- User visible title for the keyboard shortcut that pulls up Notes app for quick memo. [CHAR LIMIT=70] -->
<string name="group_system_quick_memo">Take a note</string>
- <!-- TODO(b/383734125): make it translatable once string is finalized by UXW.-->
- <!-- User visible title for the keyboard shortcut that toggles Voice Access. [CHAR LIMIT=70] -->
- <string name="group_system_toggle_voice_access" translatable="false">Toggle Voice Access</string>
<!-- User visible title for the multitasking keyboard shortcuts list. [CHAR LIMIT=70] -->
<string name="keyboard_shortcut_group_system_multitasking">Multitasking</string>
@@ -2371,6 +2374,23 @@
<!-- User visible title for the keyboard shortcut that takes the user to the maps app. [CHAR LIMIT=70] -->
<string name="keyboard_shortcut_group_applications_maps">Maps</string>
+ <!-- User visible title for the keyboard shortcut that toggles bounce keys. [CHAR LIMIT=70]-->
+ <string name="group_accessibility_toggle_bounce_keys">Toggle bounce keys</string>
+ <!-- User visible title for the keyboard shortcut that toggles mouse keys. [CHAR LIMIT=70]-->
+ <string name="group_accessibility_toggle_mouse_keys">Toggle mouse keys</string>
+ <!-- User visible title for the keyboard shortcut that toggles sticky keys. [CHAR LIMIT=70]-->
+ <string name="group_accessibility_toggle_sticky_keys">Toggle sticky keys</string>
+ <!-- User visible title for the keyboard shortcut that toggles slow keys. [CHAR LIMIT=70]-->
+ <string name="group_accessibility_toggle_slow_keys">Toggle slow keys</string>
+ <!-- User visible title for the keyboard shortcut that toggles Voice Access. [CHAR LIMIT=70] -->
+ <string name="group_accessibility_toggle_voice_access">Toggle Voice Access</string>
+ <!-- User visible title for the keyboard shortcut that toggles Talkback. [CHAR LIMIT=70] -->
+ <string name="group_accessibility_toggle_talkback">Toggle Talkback</string>
+ <!-- User visible title for the keyboard shortcut that toggles Magnification. [CHAR LIMIT=70] -->
+ <string name="group_accessibility_toggle_magnification">Toggle Magnification</string>
+ <!-- User visible title for the keyboard shortcut that activates Select to Speak service. [CHAR LIMIT=70] -->
+ <string name="group_accessibility_activate_select_to_speak">Activate Select to Speak</string>
+
<!-- SysUI Tuner: Label for screen about do not disturb settings [CHAR LIMIT=60] -->
<string name="volume_and_do_not_disturb">Do Not Disturb</string>
@@ -4005,13 +4025,13 @@
<!-- Touchpad switch apps gesture action name in tutorial [CHAR LIMIT=NONE] -->
<string name="touchpad_switch_apps_gesture_action_title">Switch apps</string>
<!-- Touchpad switch apps gesture guidance in gestures tutorial [CHAR LIMIT=NONE] -->
- <string name="touchpad_switch_apps_gesture_guidance">Swipe left using four fingers on your touchpad</string>
+ <string name="touchpad_switch_apps_gesture_guidance">Swipe right using four fingers on your touchpad</string>
<!-- Screen title after switch apps gesture was done successfully [CHAR LIMIT=NONE] -->
<string name="touchpad_switch_apps_gesture_success_title">Great job!</string>
<!-- Text shown to the user after they complete switch apps gesture tutorial [CHAR LIMIT=NONE] -->
<string name="touchpad_switch_apps_gesture_success_body">You completed the switch apps gesture.</string>
<!-- Text shown to the user after switch gesture was not done correctly [CHAR LIMIT=NONE] -->
- <string name="touchpad_switch_gesture_error_body">Swipe left using four fingers on your touchpad to switch apps</string>
+ <string name="touchpad_switch_gesture_error_body">Swipe right using four fingers on your touchpad to switch apps</string>
<!-- KEYBOARD TUTORIAL-->
<!-- Action key tutorial title [CHAR LIMIT=NONE] -->
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index b0d9bed05e27..fa6a41a74ca9 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -176,17 +176,11 @@
<style name="TextAppearance.QS.Status">
<item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
- <item name="android:textColor">?attr/onSurface</item>
+ <item name="android:textColor">@color/shade_header_text_color</item>
<item name="android:textSize">14sp</item>
<item name="android:letterSpacing">0.01</item>
</style>
- <style name="TextAppearance.QS.Status.Carriers" />
-
- <style name="TextAppearance.QS.Status.Carriers.NoCarrierText">
- <item name="android:textColor">?attr/onSurfaceVariant</item>
- </style>
-
<style name="TextAppearance.QS.Status.Build">
<item name="android:textColor">?attr/onSurfaceVariant</item>
</style>
@@ -565,16 +559,6 @@
<item name="android:windowNoTitle">true</item>
</style>
- <style name="SystemUI.Material3.Slider.Volume">
- <item name="trackHeight">40dp</item>
- <item name="thumbHeight">52dp</item>
- <item name="trackCornerSize">12dp</item>
- <item name="trackInsideCornerSize">2dp</item>
- <item name="trackStopIndicatorSize">6dp</item>
- <item name="trackIconSize">20dp</item>
- <item name="labelBehavior">gone</item>
- </style>
-
<style name="SystemUI.Material3.Slider" parent="@style/Widget.Material3.Slider">
<item name="labelStyle">@style/Widget.Material3.Slider.Label</item>
<item name="thumbColor">@color/thumb_color</item>
diff --git a/packages/SystemUI/res/xml/gradient_color_wallpaper.xml b/packages/SystemUI/res/xml/gradient_color_wallpaper.xml
new file mode 100644
index 000000000000..f453a4450750
--- /dev/null
+++ b/packages/SystemUI/res/xml/gradient_color_wallpaper.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2025 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+<wallpaper
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:showMetadataInPreview="false"
+ android:supportsMultipleDisplays="true" /> \ No newline at end of file
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ILauncherProxy.aidl
index d363e524a9f2..b43ffc530289 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ILauncherProxy.aidl
@@ -23,8 +23,8 @@ import android.os.IRemoteCallback;
import android.view.MotionEvent;
import com.android.systemui.shared.recents.ISystemUiProxy;
-// Next ID: 38
-oneway interface IOverviewProxy {
+// Next ID: 39
+oneway interface ILauncherProxy {
void onActiveNavBarRegionChanges(in Region activeRegion) = 11;
@@ -140,7 +140,7 @@ oneway interface IOverviewProxy {
void appTransitionPending(boolean pending) = 34;
/**
- * Sent right after OverviewProxy calls unbindService() on the TouchInteractionService.
+ * Sent right after LauncherProxyService calls unbindService() on the TouchInteractionService.
* TouchInteractionService is expected to send the reply once it has finished cleaning up.
*/
void onUnbind(IRemoteCallback reply) = 35;
@@ -154,4 +154,9 @@ oneway interface IOverviewProxy {
* Sent when {@link TaskbarDelegate#onDisplayRemoved} is called.
*/
void onDisplayRemoved(int displayId) = 37;
+
+ /**
+ * Sent when {@link TaskbarDelegate#onDisplayRemoveSystemDecorations} is called.
+ */
+ void onDisplayRemoveSystemDecorations(int displayId) = 38;
}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
index e332280bc31a..1f6bea18d53a 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
@@ -69,10 +69,10 @@ interface ISystemUiProxy {
/**
* Indicates that the given Assist invocation types should be handled by Launcher via
- * OverviewProxy#onAssistantOverrideInvoked and should not be invoked by SystemUI.
+ * LauncherProxy#onAssistantOverrideInvoked and should not be invoked by SystemUI.
*
* @param invocationTypes The invocation types that will henceforth be handled via
- * OverviewProxy (Launcher); other invocation types should be handled by SysUI.
+ * LauncherProxy (Launcher); other invocation types should be handled by SysUI.
*/
oneway void setAssistantOverridesRequested(in int[] invocationTypes) = 53;
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java
index 8576a6ebac42..a518c57bdd16 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java
@@ -72,6 +72,25 @@ public class Task {
@ViewDebug.ExportedProperty(category = "recents")
public final int displayId;
+ /**
+ * The component of the first activity in the task, can be considered the "application" of
+ * this task.
+ */
+ @Nullable
+ public ComponentName baseActivity;
+ /**
+ * The number of activities in this task (including running).
+ */
+ public int numActivities;
+ /**
+ * Whether the top activity is to be displayed. See {@link android.R.attr#windowNoDisplay}.
+ */
+ public boolean isTopActivityNoDisplay;
+ /**
+ * Whether fillsParent() is false for every activity in the tasks stack.
+ */
+ public boolean isActivityStackTransparent;
+
// The source component name which started this task
public final ComponentName sourceComponent;
@@ -90,6 +109,10 @@ public class Task {
this.userId = t.userId;
this.lastActiveTime = t.lastActiveTime;
this.displayId = t.displayId;
+ this.baseActivity = t.baseActivity;
+ this.numActivities = t.numActivities;
+ this.isTopActivityNoDisplay = t.isTopActivityNoDisplay;
+ this.isActivityStackTransparent = t.isActivityStackTransparent;
updateHashCode();
}
@@ -106,7 +129,9 @@ public class Task {
}
public TaskKey(int id, int windowingMode, @NonNull Intent intent,
- ComponentName sourceComponent, int userId, long lastActiveTime, int displayId) {
+ ComponentName sourceComponent, int userId, long lastActiveTime, int displayId,
+ @Nullable ComponentName baseActivity, int numActivities,
+ boolean isTopActivityNoDisplay, boolean isActivityStackTransparent) {
this.id = id;
this.windowingMode = windowingMode;
this.baseIntent = intent;
@@ -114,6 +139,10 @@ public class Task {
this.userId = userId;
this.lastActiveTime = lastActiveTime;
this.displayId = displayId;
+ this.baseActivity = baseActivity;
+ this.numActivities = numActivities;
+ this.isTopActivityNoDisplay = isTopActivityNoDisplay;
+ this.isActivityStackTransparent = isActivityStackTransparent;
updateHashCode();
}
@@ -185,6 +214,10 @@ public class Task {
parcel.writeLong(lastActiveTime);
parcel.writeInt(displayId);
parcel.writeTypedObject(sourceComponent, flags);
+ parcel.writeTypedObject(baseActivity, flags);
+ parcel.writeInt(numActivities);
+ parcel.writeBoolean(isTopActivityNoDisplay);
+ parcel.writeBoolean(isActivityStackTransparent);
}
private static TaskKey readFromParcel(Parcel parcel) {
@@ -195,9 +228,14 @@ public class Task {
long lastActiveTime = parcel.readLong();
int displayId = parcel.readInt();
ComponentName sourceComponent = parcel.readTypedObject(ComponentName.CREATOR);
+ ComponentName baseActivity = parcel.readTypedObject(ComponentName.CREATOR);
+ int numActivities = parcel.readInt();
+ boolean isTopActivityNoDisplay = parcel.readBoolean();
+ boolean isActivityStackTransparent = parcel.readBoolean();
return new TaskKey(id, windowingMode, baseIntent, sourceComponent, userId,
- lastActiveTime, displayId);
+ lastActiveTime, displayId, baseActivity, numActivities, isTopActivityNoDisplay,
+ isActivityStackTransparent);
}
@Override
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/Utilities.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/Utilities.java
index 41ad4373455e..9ebb15f43307 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/Utilities.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/Utilities.java
@@ -16,11 +16,12 @@
package com.android.systemui.shared.recents.utilities;
-import static android.app.StatusBarManager.NAVIGATION_HINT_BACK_ALT;
-import static android.app.StatusBarManager.NAVIGATION_HINT_IME_SHOWN;
-import static android.app.StatusBarManager.NAVIGATION_HINT_IME_SWITCHER_SHOWN;
+import static android.app.StatusBarManager.NAVBAR_BACK_DISMISS_IME;
+import static android.app.StatusBarManager.NAVBAR_IME_SWITCHER_BUTTON_VISIBLE;
+import static android.app.StatusBarManager.NAVBAR_IME_VISIBLE;
import android.annotation.TargetApi;
+import android.app.StatusBarManager.NavbarFlags;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Color;
@@ -103,38 +104,46 @@ public class Utilities {
}
/**
- * @return updated set of flags from InputMethodService based off {@param oldHints}
- * Leaves original hints unmodified
+ * Updates the navigation bar state flags with the given IME state.
+ *
+ * @param oldFlags current navigation bar state flags.
+ * @param backDisposition the IME back disposition mode. Only takes effect if
+ * {@code isImeVisible} is {@code true}.
+ * @param isImeVisible whether the IME is currently visible.
+ * @param showImeSwitcher whether the IME Switcher button should be shown. Only takes effect if
+ * {@code isImeVisible} is {@code true}.
*/
- public static int calculateBackDispositionHints(int oldHints,
- @BackDispositionMode int backDisposition, boolean imeShown, boolean showImeSwitcher) {
- int hints = oldHints;
+ @NavbarFlags
+ public static int updateNavbarFlagsFromIme(@NavbarFlags int oldFlags,
+ @BackDispositionMode int backDisposition, boolean isImeVisible,
+ boolean showImeSwitcher) {
+ int flags = oldFlags;
switch (backDisposition) {
case InputMethodService.BACK_DISPOSITION_DEFAULT:
case InputMethodService.BACK_DISPOSITION_WILL_NOT_DISMISS:
case InputMethodService.BACK_DISPOSITION_WILL_DISMISS:
- if (imeShown) {
- hints |= NAVIGATION_HINT_BACK_ALT;
+ if (isImeVisible) {
+ flags |= NAVBAR_BACK_DISMISS_IME;
} else {
- hints &= ~NAVIGATION_HINT_BACK_ALT;
+ flags &= ~NAVBAR_BACK_DISMISS_IME;
}
break;
case InputMethodService.BACK_DISPOSITION_ADJUST_NOTHING:
- hints &= ~NAVIGATION_HINT_BACK_ALT;
+ flags &= ~NAVBAR_BACK_DISMISS_IME;
break;
}
- if (imeShown) {
- hints |= NAVIGATION_HINT_IME_SHOWN;
+ if (isImeVisible) {
+ flags |= NAVBAR_IME_VISIBLE;
} else {
- hints &= ~NAVIGATION_HINT_IME_SHOWN;
+ flags &= ~NAVBAR_IME_VISIBLE;
}
- if (showImeSwitcher) {
- hints |= NAVIGATION_HINT_IME_SWITCHER_SHOWN;
+ if (showImeSwitcher && isImeVisible) {
+ flags |= NAVBAR_IME_SWITCHER_BUTTON_VISIBLE;
} else {
- hints &= ~NAVIGATION_HINT_IME_SWITCHER_SHOWN;
+ flags &= ~NAVBAR_IME_SWITCHER_BUTTON_VISIBLE;
}
- return hints;
+ return flags;
}
/** @return whether or not {@param context} represents that of a large screen device or not */
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/shadow/DoubleShadowTextView.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/shadow/DoubleShadowTextView.kt
index bd20777c7102..e928525afc14 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/shadow/DoubleShadowTextView.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/shadow/DoubleShadowTextView.kt
@@ -16,6 +16,7 @@
package com.android.systemui.shared.shadow
import android.content.Context
+import android.content.res.TypedArray
import android.graphics.Canvas
import android.graphics.drawable.Drawable
import android.util.AttributeSet
@@ -31,19 +32,23 @@ constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0,
- defStyleRes: Int = 0
+ defStyleRes: Int = 0,
) : TextView(context, attrs, defStyleAttr, defStyleRes) {
- private val mKeyShadowInfo: ShadowInfo
- private val mAmbientShadowInfo: ShadowInfo
+ private lateinit var mKeyShadowInfo: ShadowInfo
+ private lateinit var mAmbientShadowInfo: ShadowInfo
init {
- val attributes =
+ updateShadowDrawables(
context.obtainStyledAttributes(
attrs,
R.styleable.DoubleShadowTextView,
defStyleAttr,
- defStyleRes
+ defStyleRes,
)
+ )
+ }
+
+ private fun updateShadowDrawables(attributes: TypedArray) {
val drawableSize: Int
val drawableInsetSize: Int
try {
@@ -70,17 +75,17 @@ constructor(
ambientShadowBlur,
ambientShadowOffsetX,
ambientShadowOffsetY,
- ambientShadowAlpha
+ ambientShadowAlpha,
)
drawableSize =
attributes.getDimensionPixelSize(
R.styleable.DoubleShadowTextView_drawableIconSize,
- 0
+ 0,
)
drawableInsetSize =
attributes.getDimensionPixelSize(
R.styleable.DoubleShadowTextView_drawableIconInsetSize,
- 0
+ 0,
)
} finally {
attributes.recycle()
@@ -95,12 +100,19 @@ constructor(
mAmbientShadowInfo,
drawable,
drawableSize,
- drawableInsetSize
+ drawableInsetSize,
)
}
setCompoundDrawablesRelative(drawables[0], drawables[1], drawables[2], drawables[3])
}
+ override fun setTextAppearance(resId: Int) {
+ super.setTextAppearance(resId)
+ updateShadowDrawables(
+ context.obtainStyledAttributes(resId, R.styleable.DoubleShadowTextView)
+ )
+ }
+
public override fun onDraw(canvas: Canvas) {
applyShadows(mKeyShadowInfo, mAmbientShadowInfo, this, canvas) { super.onDraw(canvas) }
}
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 6f13d637d5c5..82ac78c6db15 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
@@ -98,12 +98,12 @@ public class QuickStepContract {
public static final long SYSUI_STATE_ONE_HANDED_ACTIVE = 1L << 16;
// Allow system gesture no matter the system bar(s) is visible or not
public static final long SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY = 1L << 17;
- // The IME is showing
- public static final long SYSUI_STATE_IME_SHOWING = 1L << 18;
+ // The IME is visible.
+ public static final long SYSUI_STATE_IME_VISIBLE = 1L << 18;
// The window magnification is overlapped with system gesture insets at the bottom.
public static final long SYSUI_STATE_MAGNIFICATION_OVERLAP = 1L << 19;
- // ImeSwitcher is showing
- public static final long SYSUI_STATE_IME_SWITCHER_SHOWING = 1L << 20;
+ // The IME Switcher button is visible.
+ public static final long SYSUI_STATE_IME_SWITCHER_BUTTON_VISIBLE = 1L << 20;
// Device dozing/AOD state
public static final long SYSUI_STATE_DEVICE_DOZING = 1L << 21;
// The home feature is disabled (either by SUW/SysUI/device policy)
@@ -134,6 +134,10 @@ public class QuickStepContract {
public static final long SYSUI_STATE_DISABLE_GESTURE_PIP_ANIMATING = 1L << 34;
// Communal hub is showing
public static final long SYSUI_STATE_COMMUNAL_HUB_SHOWING = 1L << 35;
+ // The back button is visually adjusted to indicate that it will dismiss the IME when pressed.
+ // This only takes effect while the IME is visible. By default, it is set while the IME is
+ // visible, but may be overridden by the backDispositionMode set by the IME.
+ public static final long SYSUI_STATE_BACK_DISMISS_IME = 1L << 36;
// Mask for SystemUiStateFlags to isolate SYSUI_STATE_AWAKE and
// SYSUI_STATE_WAKEFULNESS_TRANSITION, to match WAKEFULNESS_* constants
@@ -168,9 +172,9 @@ public class QuickStepContract {
SYSUI_STATE_DIALOG_SHOWING,
SYSUI_STATE_ONE_HANDED_ACTIVE,
SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY,
- SYSUI_STATE_IME_SHOWING,
+ SYSUI_STATE_IME_VISIBLE,
SYSUI_STATE_MAGNIFICATION_OVERLAP,
- SYSUI_STATE_IME_SWITCHER_SHOWING,
+ SYSUI_STATE_IME_SWITCHER_BUTTON_VISIBLE,
SYSUI_STATE_DEVICE_DOZING,
SYSUI_STATE_BACK_DISABLED,
SYSUI_STATE_BUBBLES_MANAGE_MENU_EXPANDED,
@@ -185,6 +189,7 @@ public class QuickStepContract {
SYSUI_STATE_TOUCHPAD_GESTURES_DISABLED,
SYSUI_STATE_DISABLE_GESTURE_PIP_ANIMATING,
SYSUI_STATE_COMMUNAL_HUB_SHOWING,
+ SYSUI_STATE_BACK_DISMISS_IME,
})
public @interface SystemUiStateFlags {}
@@ -244,14 +249,14 @@ public class QuickStepContract {
if ((flags & SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY) != 0) {
str.add("allow_gesture");
}
- if ((flags & SYSUI_STATE_IME_SHOWING) != 0) {
+ if ((flags & SYSUI_STATE_IME_VISIBLE) != 0) {
str.add("ime_visible");
}
if ((flags & SYSUI_STATE_MAGNIFICATION_OVERLAP) != 0) {
str.add("magnification_overlap");
}
- if ((flags & SYSUI_STATE_IME_SWITCHER_SHOWING) != 0) {
- str.add("ime_switcher_showing");
+ if ((flags & SYSUI_STATE_IME_SWITCHER_BUTTON_VISIBLE) != 0) {
+ str.add("ime_switcher_button_visible");
}
if ((flags & SYSUI_STATE_DEVICE_DOZING) != 0) {
str.add("device_dozing");
@@ -295,6 +300,9 @@ public class QuickStepContract {
if ((flags & SYSUI_STATE_COMMUNAL_HUB_SHOWING) != 0) {
str.add("communal_hub_showing");
}
+ if ((flags & SYSUI_STATE_BACK_DISMISS_IME) != 0) {
+ str.add("back_dismiss_ime");
+ }
return str.toString();
}
diff --git a/packages/SystemUI/src/com/android/keyguard/CarrierText.java b/packages/SystemUI/src/com/android/keyguard/CarrierText.java
index ae282c7e14bd..e2ac6a494020 100644
--- a/packages/SystemUI/src/com/android/keyguard/CarrierText.java
+++ b/packages/SystemUI/src/com/android/keyguard/CarrierText.java
@@ -22,6 +22,7 @@ import android.text.TextUtils;
import android.text.method.SingleLineTransformationMethod;
import android.util.AttributeSet;
import android.view.View;
+import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.TextView;
import com.android.systemui.res.R;
@@ -65,6 +66,14 @@ public class CarrierText extends TextView {
}
}
+ @Override
+ public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfo(info);
+ // Clear selected state set by CarrierTextController so "selected" not announced by
+ // accessibility but we can still marquee.
+ info.setSelected(false);
+ }
+
public boolean getShowAirplaneMode() {
return mShowAirplaneMode;
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardFingerprintListenModel.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardFingerprintListenModel.kt
index 953cf88feccb..943cbe87c8c2 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardFingerprintListenModel.kt
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardFingerprintListenModel.kt
@@ -49,6 +49,7 @@ data class KeyguardFingerprintListenModel(
var systemUser: Boolean = false,
var udfps: Boolean = false,
var userDoesNotHaveTrust: Boolean = false,
+ var communalShowing: Boolean = false,
) : KeyguardListenModel() {
/** List of [String] to be used as a [Row] with [DumpsysTableLogger]. */
@@ -81,6 +82,7 @@ data class KeyguardFingerprintListenModel(
systemUser.toString(),
udfps.toString(),
userDoesNotHaveTrust.toString(),
+ communalShowing.toString(),
)
}
@@ -122,6 +124,7 @@ data class KeyguardFingerprintListenModel(
systemUser = model.systemUser
udfps = model.udfps
userDoesNotHaveTrust = model.userDoesNotHaveTrust
+ communalShowing = model.communalShowing
}
}
@@ -170,6 +173,7 @@ data class KeyguardFingerprintListenModel(
"systemUser",
"underDisplayFingerprint",
"userDoesNotHaveTrust",
+ "communalShowing",
)
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java
index 7f176de547bc..0e9d8fec9717 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java
@@ -16,6 +16,8 @@
package com.android.keyguard;
+import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_FOCUSED;
+
import static com.android.systemui.Flags.pinInputFieldStyledFocusState;
import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow;
@@ -164,6 +166,8 @@ public abstract class KeyguardPinBasedInputViewController<T extends KeyguardPinB
layoutParams.height = (int) getResources().getDimension(
R.dimen.keyguard_pin_field_height);
}
+
+ mPasswordEntry.sendAccessibilityEvent(TYPE_VIEW_FOCUSED);
}
private void setKeyboardBasedFocusOutline(boolean isAnyKeyboardConnected) {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 101fcdfce7e5..c266a5b47cff 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -43,6 +43,7 @@ import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STR
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN;
+import static com.android.systemui.Flags.glanceableHubV2;
import static com.android.systemui.Flags.simPinBouncerReset;
import static com.android.systemui.Flags.simPinUseSlotId;
import static com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_OPENED;
@@ -128,6 +129,7 @@ import com.android.systemui.biometrics.AuthController;
import com.android.systemui.biometrics.FingerprintInteractiveToAuthProvider;
import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor;
import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
@@ -294,6 +296,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
private final Provider<JavaAdapter> mJavaAdapter;
private final Provider<SceneInteractor> mSceneInteractor;
private final Provider<AlternateBouncerInteractor> mAlternateBouncerInteractor;
+ private final CommunalSceneInteractor mCommunalSceneInteractor;
private final AuthController mAuthController;
private final UiEventLogger mUiEventLogger;
private final Set<String> mAllowFingerprintOnOccludingActivitiesFromPackage;
@@ -404,6 +407,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
protected int mFingerprintRunningState = BIOMETRIC_STATE_STOPPED;
private boolean mFingerprintDetectRunning;
private boolean mIsDreaming;
+ private boolean mCommunalShowing;
private int mActiveMobileDataSubscription = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
private final FingerprintInteractiveToAuthProvider mFingerprintInteractiveToAuthProvider;
@@ -2205,7 +2209,8 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
IActivityTaskManager activityTaskManagerService,
Provider<AlternateBouncerInteractor> alternateBouncerInteractor,
Provider<JavaAdapter> javaAdapter,
- Provider<SceneInteractor> sceneInteractor) {
+ Provider<SceneInteractor> sceneInteractor,
+ CommunalSceneInteractor communalSceneInteractor) {
mContext = context;
mSubscriptionManager = subscriptionManager;
mUserTracker = userTracker;
@@ -2254,6 +2259,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
mAlternateBouncerInteractor = alternateBouncerInteractor;
mJavaAdapter = javaAdapter;
mSceneInteractor = sceneInteractor;
+ mCommunalSceneInteractor = communalSceneInteractor;
mHandler = new Handler(mainLooper) {
@Override
@@ -2535,6 +2541,13 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
);
}
+ if (glanceableHubV2()) {
+ mJavaAdapter.get().alwaysCollectFlow(
+ mCommunalSceneInteractor.isCommunalVisible(),
+ this::onCommunalShowingChanged
+ );
+ }
+
// start() can be invoked in the middle of user switching, so check for this state and issue
// the call manually as that important event was missed.
if (mUserTracker.isUserSwitching()) {
@@ -2837,6 +2850,15 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
}
/**
+ * Sets whether the communal hub is showing.
+ */
+ @VisibleForTesting
+ void onCommunalShowingChanged(boolean showing) {
+ mCommunalShowing = showing;
+ updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE);
+ }
+
+ /**
* Whether the alternate bouncer is showing.
*/
public void setAlternateBouncerShowing(boolean showing) {
@@ -2998,11 +3020,14 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
final boolean strongerAuthRequired = !isUnlockingWithFingerprintAllowed();
final boolean shouldListenBouncerState =
!strongerAuthRequired || !isPrimaryBouncerShowingOrWillBeShowing();
+ final boolean isUdfpsAuthRequiredOnCommunal =
+ !mCommunalShowing || isAlternateBouncerShowing();
final boolean shouldListenUdfpsState = !isUdfps
|| (!userCanSkipBouncer
&& !strongerAuthRequired
- && userDoesNotHaveTrust);
+ && userDoesNotHaveTrust
+ && (!glanceableHubV2() || isUdfpsAuthRequiredOnCommunal));
boolean shouldListen = shouldListenKeyguardState && shouldListenUserState
@@ -3033,7 +3058,8 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
mSwitchingUser,
mIsSystemUser,
isUdfps,
- userDoesNotHaveTrust));
+ userDoesNotHaveTrust,
+ mCommunalShowing));
return shouldListen;
}
diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java
index 40c1f0f9895d..4a8e4ed3f6f1 100644
--- a/packages/SystemUI/src/com/android/systemui/Dependency.java
+++ b/packages/SystemUI/src/com/android/systemui/Dependency.java
@@ -39,7 +39,7 @@ import com.android.systemui.plugins.DarkIconDispatcher;
import com.android.systemui.plugins.PluginManager;
import com.android.systemui.plugins.VolumeDialogController;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.recents.OverviewProxyService;
+import com.android.systemui.recents.LauncherProxyService;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.NotificationMediaManager;
@@ -129,7 +129,7 @@ public class Dependency {
@Inject Lazy<MetricsLogger> mMetricsLogger;
@Inject Lazy<UiOffloadThread> mUiOffloadThread;
@Inject Lazy<LightBarController> mLightBarController;
- @Inject Lazy<OverviewProxyService> mOverviewProxyService;
+ @Inject Lazy<LauncherProxyService> mLauncherProxyService;
@Inject Lazy<NavigationModeController> mNavBarModeController;
@Inject Lazy<NavigationBarController> mNavigationBarController;
@Inject Lazy<StatusBarStateController> mStatusBarStateController;
@@ -175,7 +175,7 @@ public class Dependency {
mProviders.put(MetricsLogger.class, mMetricsLogger::get);
mProviders.put(UiOffloadThread.class, mUiOffloadThread::get);
mProviders.put(LightBarController.class, mLightBarController::get);
- mProviders.put(OverviewProxyService.class, mOverviewProxyService::get);
+ mProviders.put(LauncherProxyService.class, mLauncherProxyService::get);
mProviders.put(NavigationModeController.class, mNavBarModeController::get);
mProviders.put(NavigationBarController.class, mNavigationBarController::get);
mProviders.put(StatusBarStateController.class, mStatusBarStateController::get);
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationImpl.java b/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationImpl.java
index 5cba464fc24c..5482c3d3ea18 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationImpl.java
@@ -50,7 +50,7 @@ import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.model.SysUiState;
-import com.android.systemui.recents.OverviewProxyService;
+import com.android.systemui.recents.LauncherProxyService;
import com.android.systemui.settings.DisplayTracker;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.util.settings.SecureSettings;
@@ -79,7 +79,7 @@ public class MagnificationImpl implements Magnification, CommandQueue.Callbacks
private final Executor mExecutor;
private final AccessibilityManager mAccessibilityManager;
private final CommandQueue mCommandQueue;
- private final OverviewProxyService mOverviewProxyService;
+ private final LauncherProxyService mLauncherProxyService;
private final DisplayTracker mDisplayTracker;
private final AccessibilityLogger mA11yLogger;
@@ -225,13 +225,13 @@ public class MagnificationImpl implements Magnification, CommandQueue.Callbacks
public MagnificationImpl(Context context,
@Main Handler mainHandler, @Main Executor executor,
CommandQueue commandQueue, ModeSwitchesController modeSwitchesController,
- SysUiState sysUiState, OverviewProxyService overviewProxyService,
+ SysUiState sysUiState, LauncherProxyService launcherProxyService,
SecureSettings secureSettings, DisplayTracker displayTracker,
DisplayManager displayManager, AccessibilityLogger a11yLogger,
IWindowManager iWindowManager, AccessibilityManager accessibilityManager,
ViewCaptureAwareWindowManager viewCaptureAwareWindowManager) {
this(context, mainHandler.getLooper(), executor, commandQueue,
- modeSwitchesController, sysUiState, overviewProxyService, secureSettings,
+ modeSwitchesController, sysUiState, launcherProxyService, secureSettings,
displayTracker, displayManager, a11yLogger, iWindowManager, accessibilityManager,
viewCaptureAwareWindowManager);
}
@@ -239,7 +239,7 @@ public class MagnificationImpl implements Magnification, CommandQueue.Callbacks
@VisibleForTesting
public MagnificationImpl(Context context, Looper looper, @Main Executor executor,
CommandQueue commandQueue, ModeSwitchesController modeSwitchesController,
- SysUiState sysUiState, OverviewProxyService overviewProxyService,
+ SysUiState sysUiState, LauncherProxyService launcherProxyService,
SecureSettings secureSettings, DisplayTracker displayTracker,
DisplayManager displayManager, AccessibilityLogger a11yLogger,
IWindowManager iWindowManager,
@@ -258,7 +258,7 @@ public class MagnificationImpl implements Magnification, CommandQueue.Callbacks
mCommandQueue = commandQueue;
mModeSwitchesController = modeSwitchesController;
mSysUiState = sysUiState;
- mOverviewProxyService = overviewProxyService;
+ mLauncherProxyService = launcherProxyService;
mDisplayTracker = displayTracker;
mA11yLogger = a11yLogger;
mWindowMagnificationControllerSupplier = new WindowMagnificationControllerSupplier(context,
@@ -279,7 +279,7 @@ public class MagnificationImpl implements Magnification, CommandQueue.Callbacks
@Override
public void start() {
mCommandQueue.addCallback(this);
- mOverviewProxyService.addCallback(new OverviewProxyService.OverviewProxyListener() {
+ mLauncherProxyService.addCallback(new LauncherProxyService.LauncherProxyListener() {
@Override
public void onConnectionChanged(boolean isConnected) {
if (isConnected) {
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerController.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerController.java
index e7470a34a065..102efcf7badd 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerController.java
@@ -17,6 +17,7 @@
package com.android.systemui.accessibility.floatingmenu;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_EXCLUDE_FROM_SCREEN_MAGNIFICATION;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_UNRESTRICTED_GESTURE_EXCLUSION;
import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
import android.content.Context;
@@ -90,9 +91,11 @@ class MenuViewLayerController implements IAccessibilityFloatingMenu {
WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
PixelFormat.TRANSLUCENT);
+ params.setTitle("FloatingMenu");
params.receiveInsetsIgnoringZOrder = true;
params.privateFlags |=
- PRIVATE_FLAG_EXCLUDE_FROM_SCREEN_MAGNIFICATION | SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
+ PRIVATE_FLAG_EXCLUDE_FROM_SCREEN_MAGNIFICATION | SYSTEM_FLAG_SHOW_FOR_ALL_USERS
+ | PRIVATE_FLAG_UNRESTRICTED_GESTURE_EXCLUSION;
params.windowAnimations = android.R.style.Animation_Translucent;
// Insets are configured to allow the menu to display over navigation and system bars.
params.setFitInsetsTypes(0);
diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java b/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java
index 939d96e67f8f..da1c1bc49d23 100644
--- a/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java
+++ b/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java
@@ -37,7 +37,7 @@ import com.android.systemui.assist.ui.DefaultUiController;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.model.SysUiState;
-import com.android.systemui.recents.OverviewProxyService;
+import com.android.systemui.recents.LauncherProxyService;
import com.android.systemui.res.R;
import com.android.systemui.settings.DisplayTracker;
import com.android.systemui.settings.UserTracker;
@@ -142,7 +142,7 @@ public class AssistManager {
protected final Context mContext;
private final AssistDisclosure mAssistDisclosure;
private final PhoneStateMonitor mPhoneStateMonitor;
- private final OverviewProxyService mOverviewProxyService;
+ private final LauncherProxyService mLauncherProxyService;
private final UiController mUiController;
protected final Lazy<SysUiState> mSysUiState;
protected final AssistLogger mAssistLogger;
@@ -176,7 +176,7 @@ public class AssistManager {
private final CommandQueue mCommandQueue;
protected final AssistUtils mAssistUtils;
- // Invocation types that should be sent over OverviewProxy instead of handled here.
+ // Invocation types that should be sent over LauncherProxy instead of handled here.
private int[] mAssistOverrideInvocationTypes;
@Inject
@@ -186,7 +186,7 @@ public class AssistManager {
AssistUtils assistUtils,
CommandQueue commandQueue,
PhoneStateMonitor phoneStateMonitor,
- OverviewProxyService overviewProxyService,
+ LauncherProxyService launcherProxyService,
Lazy<SysUiState> sysUiState,
DefaultUiController defaultUiController,
AssistLogger assistLogger,
@@ -203,7 +203,7 @@ public class AssistManager {
mCommandQueue = commandQueue;
mAssistUtils = assistUtils;
mAssistDisclosure = new AssistDisclosure(context, uiHandler, viewCaptureAwareWindowManager);
- mOverviewProxyService = overviewProxyService;
+ mLauncherProxyService = launcherProxyService;
mPhoneStateMonitor = phoneStateMonitor;
mAssistLogger = assistLogger;
mUserTracker = userTracker;
@@ -220,7 +220,7 @@ public class AssistManager {
mSysUiState = sysUiState;
- mOverviewProxyService.addCallback(new OverviewProxyService.OverviewProxyListener() {
+ mLauncherProxyService.addCallback(new LauncherProxyService.LauncherProxyListener() {
@Override
public void onAssistantProgress(float progress) {
// Progress goes from 0 to 1 to indicate how close the assist gesture is to
@@ -288,14 +288,14 @@ public class AssistManager {
}
if (shouldOverrideAssist(args)) {
try {
- if (mOverviewProxyService.getProxy() == null) {
- Log.w(TAG, "No OverviewProxyService to invoke assistant override");
+ if (mLauncherProxyService.getProxy() == null) {
+ Log.w(TAG, "No LauncherProxyService to invoke assistant override");
return;
}
- mOverviewProxyService.getProxy().onAssistantOverrideInvoked(
+ mLauncherProxyService.getProxy().onAssistantOverrideInvoked(
args.getInt(INVOCATION_TYPE_KEY));
} catch (RemoteException e) {
- Log.w(TAG, "Unable to invoke assistant via OverviewProxyService override", e);
+ Log.w(TAG, "Unable to invoke assistant via LauncherProxyService override", e);
}
return;
}
@@ -333,7 +333,7 @@ public class AssistManager {
return shouldOverrideAssist(invocationType);
}
- /** @return true if the invocation type should be handled by OverviewProxy instead of SysUI. */
+ /** @return true if the invocation type should be handled by LauncherProxy instead of SysUI. */
public boolean shouldOverrideAssist(int invocationType) {
return mAssistOverrideInvocationTypes != null
&& Arrays.stream(mAssistOverrideInvocationTypes).anyMatch(
@@ -342,7 +342,7 @@ public class AssistManager {
/**
* @param invocationTypes The invocation types that will henceforth be handled via
- * OverviewProxy (Launcher); other invocation types should be handled by
+ * LauncherProxy (Launcher); other invocation types should be handled by
* this class.
*/
public void setAssistantOverridesRequested(int[] invocationTypes) {
diff --git a/packages/SystemUI/src/com/android/systemui/communal/DeviceInactiveCondition.java b/packages/SystemUI/src/com/android/systemui/communal/DeviceInactiveCondition.java
new file mode 100644
index 000000000000..2e1b5ad177b5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/DeviceInactiveCondition.java
@@ -0,0 +1,101 @@
+/*
+ * 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;
+
+import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_ASLEEP;
+import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_GOING_TO_SLEEP;
+
+import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.keyguard.KeyguardUpdateMonitorCallback;
+import com.android.systemui.dagger.qualifiers.Application;
+import com.android.systemui.keyguard.WakefulnessLifecycle;
+import com.android.systemui.shared.condition.Condition;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
+
+import kotlinx.coroutines.CoroutineScope;
+
+import javax.inject.Inject;
+
+/**
+ * Condition which estimates device inactivity in order to avoid launching a full-screen activity
+ * while the user is actively using the device.
+ */
+public class DeviceInactiveCondition extends Condition {
+ private final KeyguardStateController mKeyguardStateController;
+ private final WakefulnessLifecycle mWakefulnessLifecycle;
+ private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+ private final KeyguardStateController.Callback mKeyguardStateCallback =
+ new KeyguardStateController.Callback() {
+ @Override
+ public void onKeyguardShowingChanged() {
+ updateState();
+ }
+ };
+ private final WakefulnessLifecycle.Observer mWakefulnessObserver =
+ new WakefulnessLifecycle.Observer() {
+ @Override
+ public void onStartedGoingToSleep() {
+ updateState();
+ }
+ };
+ private final KeyguardUpdateMonitorCallback mKeyguardUpdateCallback =
+ new KeyguardUpdateMonitorCallback() {
+ @Override
+ public void onDreamingStateChanged(boolean dreaming) {
+ updateState();
+ }
+ };
+
+ @Inject
+ public DeviceInactiveCondition(@Application CoroutineScope scope,
+ KeyguardStateController keyguardStateController,
+ WakefulnessLifecycle wakefulnessLifecycle,
+ KeyguardUpdateMonitor keyguardUpdateMonitor) {
+ super(scope);
+ mKeyguardStateController = keyguardStateController;
+ mWakefulnessLifecycle = wakefulnessLifecycle;
+ mKeyguardUpdateMonitor = keyguardUpdateMonitor;
+ }
+
+ @Override
+ protected void start() {
+ updateState();
+ mKeyguardStateController.addCallback(mKeyguardStateCallback);
+ mKeyguardUpdateMonitor.registerCallback(mKeyguardUpdateCallback);
+ mWakefulnessLifecycle.addObserver(mWakefulnessObserver);
+ }
+
+ @Override
+ protected void stop() {
+ mKeyguardStateController.removeCallback(mKeyguardStateCallback);
+ mKeyguardUpdateMonitor.removeCallback(mKeyguardUpdateCallback);
+ mWakefulnessLifecycle.removeObserver(mWakefulnessObserver);
+ }
+
+ @Override
+ protected int getStartStrategy() {
+ return START_EAGERLY;
+ }
+
+ private void updateState() {
+ final boolean asleep =
+ mWakefulnessLifecycle.getWakefulness() == WAKEFULNESS_ASLEEP
+ || mWakefulnessLifecycle.getWakefulness() == WAKEFULNESS_GOING_TO_SLEEP;
+ updateCondition(asleep || mKeyguardStateController.isShowing()
+ || mKeyguardUpdateMonitor.isDreaming());
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index c02784dfab1b..fe5a82cb5b8c 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -81,6 +81,7 @@ import com.android.systemui.keyguard.ui.composable.LockscreenContent;
import com.android.systemui.log.dagger.LogModule;
import com.android.systemui.log.dagger.MonitorLog;
import com.android.systemui.log.table.TableLogBuffer;
+import com.android.systemui.lowlightclock.dagger.LowLightModule;
import com.android.systemui.mediaprojection.MediaProjectionModule;
import com.android.systemui.mediaprojection.appselector.MediaProjectionActivitiesModule;
import com.android.systemui.mediaprojection.taskswitcher.MediaProjectionTaskSwitcherModule;
@@ -285,7 +286,8 @@ import javax.inject.Named;
UserModule.class,
UtilModule.class,
NoteTaskModule.class,
- WalletModule.class
+ WalletModule.class,
+ LowLightModule.class
},
subcomponents = {
ComplicationComponent.class,
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java
index faab31eff4f7..15f73ee0eda6 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java
@@ -48,6 +48,8 @@ import com.android.systemui.qs.tiles.viewmodel.QSTileUIConfig;
import com.android.systemui.res.R;
import com.android.systemui.touch.TouchInsetManager;
+import com.google.android.systemui.lowlightclock.LowLightClockDreamService;
+
import dagger.Binds;
import dagger.BindsOptionalOf;
import dagger.Module;
@@ -238,15 +240,24 @@ public interface DreamModule {
ComponentName bindsLowLightClockDream();
/**
+ * Provides low light clock dream service component.
+ */
+ @Provides
+ @Named(LOW_LIGHT_CLOCK_DREAM)
+ static ComponentName providesLowLightClockDream(Context context) {
+ return new ComponentName(context, LowLightClockDreamService.class);
+ }
+
+ /**
* Provides the component name of the low light dream, or null if not configured.
*/
@Provides
@Nullable
@Named(LOW_LIGHT_DREAM_SERVICE)
static ComponentName providesLowLightDreamService(Context context,
- @Named(LOW_LIGHT_CLOCK_DREAM) Optional<ComponentName> clockDream) {
- if (Flags.lowLightClockDream() && clockDream.isPresent()) {
- return clockDream.get();
+ @Named(LOW_LIGHT_CLOCK_DREAM) ComponentName clockDream) {
+ if (Flags.lowLightClockDream()) {
+ return clockDream;
}
String lowLightDreamComponent = context.getResources().getString(
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 d7a4dba3188a..9bdf812713d7 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
@@ -37,8 +37,8 @@ import com.android.systemui.inputdevice.tutorial.data.repository.DeviceType
import com.android.systemui.inputdevice.tutorial.data.repository.DeviceType.KEYBOARD
import com.android.systemui.inputdevice.tutorial.data.repository.DeviceType.TOUCHPAD
import com.android.systemui.inputdevice.tutorial.data.repository.TutorialSchedulerRepository
-import com.android.systemui.recents.OverviewProxyService
-import com.android.systemui.recents.OverviewProxyService.OverviewProxyListener
+import com.android.systemui.recents.LauncherProxyService
+import com.android.systemui.recents.LauncherProxyService.LauncherProxyListener
import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
import java.time.Clock
import java.time.Instant
@@ -67,7 +67,7 @@ constructor(
private val contextualEducationInteractor: ContextualEducationInteractor,
private val userInputDeviceRepository: UserInputDeviceRepository,
private val tutorialRepository: TutorialSchedulerRepository,
- private val overviewProxyService: OverviewProxyService,
+ private val launcherProxyService: LauncherProxyService,
private val metricsLogger: ContextualEducationMetricsLogger,
@EduClock private val clock: Clock,
) : CoreStartable {
@@ -100,8 +100,8 @@ constructor(
val educationTriggered = _educationTriggered.asStateFlow()
private val statsUpdateRequests: Flow<StatsUpdateRequest> = conflatedCallbackFlow {
- val listener: OverviewProxyListener =
- object : OverviewProxyListener {
+ val listener: LauncherProxyListener =
+ object : LauncherProxyListener {
override fun updateContextualEduStats(
isTrackpadGesture: Boolean,
gestureType: GestureType,
@@ -113,8 +113,8 @@ constructor(
}
}
- overviewProxyService.addCallback(listener)
- awaitClose { overviewProxyService.removeCallback(listener) }
+ launcherProxyService.addCallback(listener)
+ awaitClose { launcherProxyService.removeCallback(listener) }
}
private val gestureModelMap: Flow<Map<GestureType, GestureEduModel>> =
diff --git a/packages/SystemUI/src/com/android/systemui/grid/ui/compose/SpannedGrids.kt b/packages/SystemUI/src/com/android/systemui/grid/ui/compose/SpannedGrids.kt
index 62ab18bbb738..96ef03c996a9 100644
--- a/packages/SystemUI/src/com/android/systemui/grid/ui/compose/SpannedGrids.kt
+++ b/packages/SystemUI/src/com/android/systemui/grid/ui/compose/SpannedGrids.kt
@@ -20,6 +20,7 @@ import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxScope
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.key
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.Layout
@@ -63,6 +64,7 @@ fun HorizontalSpannedGrid(
rowSpacing: Dp,
spans: List<Int>,
modifier: Modifier = Modifier,
+ keys: (spanIndex: Int) -> Any = { it },
composables: @Composable BoxScope.(spanIndex: Int) -> Unit,
) {
SpannedGrid(
@@ -72,6 +74,7 @@ fun HorizontalSpannedGrid(
spans = spans,
isVertical = false,
modifier = modifier,
+ keys = keys,
composables = composables,
)
}
@@ -103,6 +106,7 @@ fun VerticalSpannedGrid(
rowSpacing: Dp,
spans: List<Int>,
modifier: Modifier = Modifier,
+ keys: (spanIndex: Int) -> Any = { it },
composables: @Composable BoxScope.(spanIndex: Int) -> Unit,
) {
SpannedGrid(
@@ -112,6 +116,7 @@ fun VerticalSpannedGrid(
spans = spans,
isVertical = true,
modifier = modifier,
+ keys = keys,
composables = composables,
)
}
@@ -124,6 +129,7 @@ private fun SpannedGrid(
spans: List<Int>,
isVertical: Boolean,
modifier: Modifier = Modifier,
+ keys: (spanIndex: Int) -> Any = { it },
composables: @Composable BoxScope.(spanIndex: Int) -> Unit,
) {
val crossAxisArrangement = Arrangement.spacedBy(crossAxisSpacing)
@@ -167,17 +173,19 @@ private fun SpannedGrid(
Layout(
{
(0 until spans.size).map { spanIndex ->
- Box(
- Modifier.semantics {
- collectionItemInfo =
- if (isVertical) {
- CollectionItemInfo(spanIndex, 1, 0, 1)
- } else {
- CollectionItemInfo(0, 1, spanIndex, 1)
- }
+ key(keys(spanIndex)) {
+ Box(
+ Modifier.semantics {
+ collectionItemInfo =
+ if (isVertical) {
+ CollectionItemInfo(spanIndex, 1, 0, 1)
+ } else {
+ CollectionItemInfo(0, 1, spanIndex, 1)
+ }
+ }
+ ) {
+ composables(spanIndex)
}
- ) {
- composables(spanIndex)
}
}
},
diff --git a/packages/SystemUI/src/com/android/systemui/haptics/msdl/qs/TileHapticsViewModel.kt b/packages/SystemUI/src/com/android/systemui/haptics/msdl/qs/TileHapticsViewModel.kt
index 84c4bdf1621a..49625f8f9360 100644
--- a/packages/SystemUI/src/com/android/systemui/haptics/msdl/qs/TileHapticsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/haptics/msdl/qs/TileHapticsViewModel.kt
@@ -17,6 +17,7 @@
package com.android.systemui.haptics.msdl.qs
import android.service.quicksettings.Tile
+import androidx.compose.runtime.Stable
import com.android.systemui.Flags
import com.android.systemui.animation.Expandable
import com.android.systemui.dagger.SysUISingleton
@@ -175,6 +176,7 @@ constructor(
}
@SysUISingleton
+@Stable
class TileHapticsViewModelFactoryProvider
@Inject
constructor(private val tileHapticsViewModelFactory: TileHapticsViewModel.Factory) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/CustomInputGesturesRepository.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/CustomInputGesturesRepository.kt
index e5c638cbdfba..d355f761e5ae 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/CustomInputGesturesRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/CustomInputGesturesRepository.kt
@@ -32,18 +32,19 @@ import com.android.systemui.keyboard.shared.model.ShortcutCustomizationRequestRe
import com.android.systemui.keyboard.shared.model.ShortcutCustomizationRequestResult.ERROR_RESERVED_COMBINATION
import com.android.systemui.keyboard.shared.model.ShortcutCustomizationRequestResult.SUCCESS
import com.android.systemui.settings.UserTracker
+import javax.inject.Inject
+import kotlin.coroutines.CoroutineContext
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.withContext
-import javax.inject.Inject
-import kotlin.coroutines.CoroutineContext
@SysUISingleton
class CustomInputGesturesRepository
@Inject
-constructor(private val userTracker: UserTracker,
- @Background private val bgCoroutineContext: CoroutineContext)
-{
+constructor(
+ private val userTracker: UserTracker,
+ @Background private val bgCoroutineContext: CoroutineContext,
+) {
private val userContext: Context
get() = userTracker.createCurrentUserContext(userTracker.userContext)
@@ -55,8 +56,7 @@ constructor(private val userTracker: UserTracker,
private val _customInputGesture = MutableStateFlow<List<InputGestureData>>(emptyList())
- val customInputGestures =
- _customInputGesture.onStart { refreshCustomInputGestures() }
+ val customInputGestures = _customInputGesture.onStart { refreshCustomInputGestures() }
fun refreshCustomInputGestures() {
setCustomInputGestures(inputGestures = retrieveCustomInputGestures())
@@ -72,24 +72,24 @@ constructor(private val userTracker: UserTracker,
} else emptyList()
}
- suspend fun addCustomInputGesture(inputGesture: InputGestureData): ShortcutCustomizationRequestResult {
+ suspend fun addCustomInputGesture(
+ inputGesture: InputGestureData
+ ): ShortcutCustomizationRequestResult {
return withContext(bgCoroutineContext) {
when (val result = inputManager.addCustomInputGesture(inputGesture)) {
CUSTOM_INPUT_GESTURE_RESULT_SUCCESS -> {
refreshCustomInputGestures()
SUCCESS
}
- CUSTOM_INPUT_GESTURE_RESULT_ERROR_ALREADY_EXISTS ->
- ERROR_RESERVED_COMBINATION
+ CUSTOM_INPUT_GESTURE_RESULT_ERROR_ALREADY_EXISTS -> ERROR_RESERVED_COMBINATION
- CUSTOM_INPUT_GESTURE_RESULT_ERROR_RESERVED_GESTURE ->
- ERROR_RESERVED_COMBINATION
+ CUSTOM_INPUT_GESTURE_RESULT_ERROR_RESERVED_GESTURE -> ERROR_RESERVED_COMBINATION
else -> {
Log.w(
TAG,
"Attempted to add inputGesture: $inputGesture " +
- "but ran into an error with code: $result",
+ "but ran into an error with code: $result",
)
ERROR_OTHER
}
@@ -97,11 +97,11 @@ constructor(private val userTracker: UserTracker,
}
}
- suspend fun deleteCustomInputGesture(inputGesture: InputGestureData): ShortcutCustomizationRequestResult {
- return withContext(bgCoroutineContext){
- when (
- val result = inputManager.removeCustomInputGesture(inputGesture)
- ) {
+ suspend fun deleteCustomInputGesture(
+ inputGesture: InputGestureData
+ ): ShortcutCustomizationRequestResult {
+ return withContext(bgCoroutineContext) {
+ when (val result = inputManager.removeCustomInputGesture(inputGesture)) {
CUSTOM_INPUT_GESTURE_RESULT_SUCCESS -> {
refreshCustomInputGestures()
SUCCESS
@@ -110,7 +110,7 @@ constructor(private val userTracker: UserTracker,
Log.w(
TAG,
"Attempted to delete inputGesture: $inputGesture " +
- "but ran into an error with code: $result",
+ "but ran into an error with code: $result",
)
ERROR_OTHER
}
@@ -134,7 +134,10 @@ constructor(private val userTracker: UserTracker,
}
}
+ suspend fun getInputGestureByTrigger(trigger: InputGestureData.Trigger): InputGestureData? =
+ withContext(bgCoroutineContext) { inputManager.getInputGesture(trigger) }
+
private companion object {
private const val TAG = "CustomInputGesturesRepository"
}
-} \ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/CustomShortcutCategoriesRepository.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/CustomShortcutCategoriesRepository.kt
index 18ca877775df..6ae948d2da2e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/CustomShortcutCategoriesRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/CustomShortcutCategoriesRepository.kt
@@ -18,6 +18,7 @@ package com.android.systemui.keyboard.shortcut.data.repository
import android.hardware.input.InputGestureData
import android.hardware.input.InputGestureData.Builder
+import android.hardware.input.InputGestureData.Trigger
import android.hardware.input.InputGestureData.createKeyTrigger
import android.hardware.input.InputManager
import android.hardware.input.KeyGestureEvent.KeyGestureType
@@ -175,6 +176,11 @@ constructor(
return customInputGesturesRepository.resetAllCustomInputGestures()
}
+ suspend fun isSelectedKeyCombinationAvailable(): Boolean {
+ val trigger = buildTriggerFromSelectedKeyCombination() ?: return false
+ return customInputGesturesRepository.getInputGestureByTrigger(trigger) == null
+ }
+
private fun Builder.addKeyGestureTypeForShortcutBeingCustomized(): Builder {
val keyGestureType = getKeyGestureTypeForShortcutBeingCustomized()
@@ -222,7 +228,10 @@ constructor(
)
}
- private fun Builder.addTriggerFromSelectedKeyCombination(): Builder {
+ private fun Builder.addTriggerFromSelectedKeyCombination(): Builder =
+ setTrigger(buildTriggerFromSelectedKeyCombination())
+
+ private fun buildTriggerFromSelectedKeyCombination(): Trigger? {
val selectedKeyCombination = _selectedKeyCombination.value
if (selectedKeyCombination?.keyCode == null) {
Log.w(
@@ -230,16 +239,14 @@ constructor(
"User requested to set shortcut but selected key combination is " +
"$selectedKeyCombination",
)
- return this
+ return null
}
- return setTrigger(
- createKeyTrigger(
- /* keycode = */ selectedKeyCombination.keyCode,
- /* modifierState = */ shortcutCategoriesUtils.removeUnsupportedModifiers(
- selectedKeyCombination.modifiers
- ),
- )
+ return createKeyTrigger(
+ /* keycode= */ selectedKeyCombination.keyCode,
+ /* modifierState= */ shortcutCategoriesUtils.removeUnsupportedModifiers(
+ selectedKeyCombination.modifiers
+ ),
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/InputGestureMaps.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/InputGestureMaps.kt
index 6a42cdc876ca..0908e3b5bf85 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/InputGestureMaps.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/InputGestureMaps.kt
@@ -17,6 +17,7 @@
package com.android.systemui.keyboard.shortcut.data.repository
import android.content.Context
+import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_ACTIVATE_SELECT_TO_SPEAK
import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_ALL_APPS
import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_BACK
import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_LEFT
@@ -39,10 +40,16 @@ import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_SNAP_RIGHT_FREEFO
import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_LEFT
import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_RIGHT
import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_TAKE_SCREENSHOT
+import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_BOUNCE_KEYS
+import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_MAGNIFICATION
import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_MAXIMIZE_FREEFORM_WINDOW
+import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_MOUSE_KEYS
import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL
+import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_SLOW_KEYS
+import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_STICKY_KEYS
+import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_TALKBACK
import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_VOICE_ACCESS
-import com.android.hardware.input.Flags.enableVoiceAccessKeyGestures
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.Accessibility
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.AppCategories
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.MultiTasking
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.System
@@ -65,7 +72,6 @@ class InputGestureMaps @Inject constructor(private val context: Context) {
KEY_GESTURE_TYPE_LAUNCH_ASSISTANT to System,
KEY_GESTURE_TYPE_LAUNCH_VOICE_ASSISTANT to System,
KEY_GESTURE_TYPE_ALL_APPS to System,
- KEY_GESTURE_TYPE_TOGGLE_VOICE_ACCESS to System,
// Multitasking Category
KEY_GESTURE_TYPE_RECENT_APPS_SWITCHER to MultiTasking,
@@ -82,6 +88,16 @@ class InputGestureMaps @Inject constructor(private val context: Context) {
// App Category
KEY_GESTURE_TYPE_LAUNCH_APPLICATION to AppCategories,
+
+ // Accessibility Category
+ KEY_GESTURE_TYPE_TOGGLE_BOUNCE_KEYS to Accessibility,
+ KEY_GESTURE_TYPE_TOGGLE_MOUSE_KEYS to Accessibility,
+ KEY_GESTURE_TYPE_TOGGLE_STICKY_KEYS to Accessibility,
+ KEY_GESTURE_TYPE_TOGGLE_SLOW_KEYS to Accessibility,
+ KEY_GESTURE_TYPE_TOGGLE_VOICE_ACCESS to Accessibility,
+ KEY_GESTURE_TYPE_TOGGLE_TALKBACK to Accessibility,
+ KEY_GESTURE_TYPE_TOGGLE_MAGNIFICATION to Accessibility,
+ KEY_GESTURE_TYPE_ACTIVATE_SELECT_TO_SPEAK to Accessibility,
)
val gestureToInternalKeyboardShortcutGroupLabelResIdMap =
@@ -103,7 +119,6 @@ class InputGestureMaps @Inject constructor(private val context: Context) {
KEY_GESTURE_TYPE_LAUNCH_ASSISTANT to R.string.shortcut_helper_category_system_apps,
KEY_GESTURE_TYPE_LAUNCH_VOICE_ASSISTANT to
R.string.shortcut_helper_category_system_apps,
- KEY_GESTURE_TYPE_TOGGLE_VOICE_ACCESS to R.string.shortcut_helper_category_system_apps,
// Multitasking Category
KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_LEFT to
@@ -128,6 +143,17 @@ class InputGestureMaps @Inject constructor(private val context: Context) {
// App Category
KEY_GESTURE_TYPE_LAUNCH_APPLICATION to R.string.keyboard_shortcut_group_applications,
+
+ // Accessibility Category
+ KEY_GESTURE_TYPE_TOGGLE_BOUNCE_KEYS to R.string.shortcutHelper_category_accessibility,
+ KEY_GESTURE_TYPE_TOGGLE_MOUSE_KEYS to R.string.shortcutHelper_category_accessibility,
+ KEY_GESTURE_TYPE_TOGGLE_STICKY_KEYS to R.string.shortcutHelper_category_accessibility,
+ KEY_GESTURE_TYPE_TOGGLE_SLOW_KEYS to R.string.shortcutHelper_category_accessibility,
+ KEY_GESTURE_TYPE_TOGGLE_VOICE_ACCESS to R.string.shortcutHelper_category_accessibility,
+ KEY_GESTURE_TYPE_TOGGLE_TALKBACK to R.string.shortcutHelper_category_accessibility,
+ KEY_GESTURE_TYPE_TOGGLE_MAGNIFICATION to R.string.shortcutHelper_category_accessibility,
+ KEY_GESTURE_TYPE_ACTIVATE_SELECT_TO_SPEAK to
+ R.string.shortcutHelper_category_accessibility,
)
/**
@@ -152,7 +178,6 @@ class InputGestureMaps @Inject constructor(private val context: Context) {
KEY_GESTURE_TYPE_LAUNCH_ASSISTANT to R.string.group_system_access_google_assistant,
KEY_GESTURE_TYPE_LAUNCH_VOICE_ASSISTANT to
R.string.group_system_access_google_assistant,
- KEY_GESTURE_TYPE_TOGGLE_VOICE_ACCESS to R.string.group_system_toggle_voice_access,
// Multitasking Category
KEY_GESTURE_TYPE_RECENT_APPS_SWITCHER to R.string.group_system_cycle_forward,
@@ -169,6 +194,19 @@ class InputGestureMaps @Inject constructor(private val context: Context) {
R.string.system_desktop_mode_toggle_maximize_window,
KEY_GESTURE_TYPE_MOVE_TO_NEXT_DISPLAY to
R.string.system_multitasking_move_to_next_display,
+
+ // Accessibility Category
+ KEY_GESTURE_TYPE_TOGGLE_BOUNCE_KEYS to R.string.group_accessibility_toggle_bounce_keys,
+ KEY_GESTURE_TYPE_TOGGLE_MOUSE_KEYS to R.string.group_accessibility_toggle_mouse_keys,
+ KEY_GESTURE_TYPE_TOGGLE_STICKY_KEYS to R.string.group_accessibility_toggle_sticky_keys,
+ KEY_GESTURE_TYPE_TOGGLE_SLOW_KEYS to R.string.group_accessibility_toggle_slow_keys,
+ KEY_GESTURE_TYPE_TOGGLE_VOICE_ACCESS to
+ R.string.group_accessibility_toggle_voice_access,
+ KEY_GESTURE_TYPE_TOGGLE_TALKBACK to R.string.group_accessibility_toggle_talkback,
+ KEY_GESTURE_TYPE_TOGGLE_MAGNIFICATION to
+ R.string.group_accessibility_toggle_magnification,
+ KEY_GESTURE_TYPE_ACTIVATE_SELECT_TO_SPEAK to
+ R.string.group_accessibility_activate_select_to_speak,
)
val shortcutLabelToKeyGestureTypeMap: Map<String, Int>
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/source/AccessibilityShortcutsSource.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/source/AccessibilityShortcutsSource.kt
index d92c45591522..fdb80b2e0f87 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/source/AccessibilityShortcutsSource.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/source/AccessibilityShortcutsSource.kt
@@ -17,9 +17,24 @@
package com.android.systemui.keyboard.shortcut.data.source
import android.content.res.Resources
+import android.hardware.input.InputSettings
+import android.view.KeyEvent.KEYCODE_3
+import android.view.KeyEvent.KEYCODE_4
+import android.view.KeyEvent.KEYCODE_5
+import android.view.KeyEvent.KEYCODE_6
+import android.view.KeyEvent.KEYCODE_M
+import android.view.KeyEvent.KEYCODE_S
+import android.view.KeyEvent.KEYCODE_T
+import android.view.KeyEvent.KEYCODE_V
+import android.view.KeyEvent.META_ALT_ON
+import android.view.KeyEvent.META_META_ON
import android.view.KeyboardShortcutGroup
import android.view.KeyboardShortcutInfo
+import com.android.hardware.input.Flags.enableTalkbackAndMagnifierKeyGestures
+import com.android.hardware.input.Flags.enableVoiceAccessKeyGestures
+import com.android.hardware.input.Flags.keyboardA11yShortcutControl
import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.keyboard.shortcut.data.model.shortcutInfo
import com.android.systemui.res.R
import javax.inject.Inject
@@ -33,5 +48,96 @@ class AccessibilityShortcutsSource @Inject constructor(@Main private val resourc
)
)
- private fun accessibilityShortcuts() = listOf<KeyboardShortcutInfo>()
+ private fun accessibilityShortcuts(): List<KeyboardShortcutInfo> {
+ val shortcuts = mutableListOf<KeyboardShortcutInfo>()
+
+ if (keyboardA11yShortcutControl()) {
+ if (InputSettings.isAccessibilityBounceKeysFeatureEnabled()) {
+ shortcuts.add(
+ // Toggle bounce keys:
+ // - Meta + Alt + 3
+ shortcutInfo(
+ resources.getString(R.string.group_accessibility_toggle_bounce_keys)
+ ) {
+ command(META_META_ON or META_ALT_ON, KEYCODE_3)
+ }
+ )
+ }
+ if (InputSettings.isAccessibilityMouseKeysFeatureFlagEnabled()) {
+ shortcuts.add(
+ // Toggle mouse keys:
+ // - Meta + Alt + 4
+ shortcutInfo(
+ resources.getString(R.string.group_accessibility_toggle_mouse_keys)
+ ) {
+ command(META_META_ON or META_ALT_ON, KEYCODE_4)
+ }
+ )
+ }
+ if (InputSettings.isAccessibilityStickyKeysFeatureEnabled()) {
+ shortcuts.add(
+ // Toggle sticky keys:
+ // - Meta + Alt + 5
+ shortcutInfo(
+ resources.getString(R.string.group_accessibility_toggle_sticky_keys)
+ ) {
+ command(META_META_ON or META_ALT_ON, KEYCODE_5)
+ }
+ )
+ }
+ if (InputSettings.isAccessibilitySlowKeysFeatureFlagEnabled()) {
+ shortcuts.add(
+ // Toggle slow keys:
+ // - Meta + Alt + 6
+ shortcutInfo(
+ resources.getString(R.string.group_accessibility_toggle_slow_keys)
+ ) {
+ command(META_META_ON or META_ALT_ON, KEYCODE_6)
+ }
+ )
+ }
+ }
+
+ if (enableVoiceAccessKeyGestures()) {
+ shortcuts.add(
+ // Toggle voice access:
+ // - Meta + Alt + V
+ shortcutInfo(
+ resources.getString(R.string.group_accessibility_toggle_voice_access)
+ ) {
+ command(META_META_ON or META_ALT_ON, KEYCODE_V)
+ }
+ )
+ }
+
+ if (enableTalkbackAndMagnifierKeyGestures()) {
+ shortcuts.add(
+ // Toggle talkback:
+ // - Meta + Alt + T
+ shortcutInfo(resources.getString(R.string.group_accessibility_toggle_talkback)) {
+ command(META_META_ON or META_ALT_ON, KEYCODE_T)
+ }
+ )
+ shortcuts.add(
+ // Toggle magnification:
+ // - Meta + Alt + M
+ shortcutInfo(
+ resources.getString(R.string.group_accessibility_toggle_magnification)
+ ) {
+ command(META_META_ON or META_ALT_ON, KEYCODE_M)
+ }
+ )
+ shortcuts.add(
+ // Activate Select to Speak:
+ // - Meta + Alt + S
+ shortcutInfo(
+ resources.getString(R.string.group_accessibility_activate_select_to_speak)
+ ) {
+ command(META_META_ON or META_ALT_ON, KEYCODE_S)
+ }
+ )
+ }
+
+ return shortcuts
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/source/MultitaskingShortcutsSource.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/source/MultitaskingShortcutsSource.kt
index d785b5b5a7e7..464201f6ec12 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/source/MultitaskingShortcutsSource.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/source/MultitaskingShortcutsSource.kt
@@ -29,12 +29,12 @@ import android.view.KeyEvent.KEYCODE_RIGHT_BRACKET
import android.view.KeyEvent.META_CTRL_ON
import android.view.KeyEvent.META_META_ON
import android.view.KeyboardShortcutGroup
+import android.window.DesktopModeFlags
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.keyboard.shortcut.data.model.shortcutInfo
import com.android.systemui.res.R
import com.android.window.flags.Flags.enableMoveToNextDisplayShortcut
-import com.android.window.flags.Flags.enableTaskResizingKeyboardShortcuts
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
import javax.inject.Inject
@@ -85,7 +85,8 @@ constructor(@Main private val resources: Resources, @Application private val con
)
}
if (
- DesktopModeStatus.canEnterDesktopMode(context) && enableTaskResizingKeyboardShortcuts()
+ DesktopModeStatus.canEnterDesktopMode(context) &&
+ DesktopModeFlags.ENABLE_TASK_RESIZING_KEYBOARD_SHORTCUTS.isTrue
) {
// Snap a freeform window to the left
// - Meta + Left bracket
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/source/SystemShortcutsSource.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/source/SystemShortcutsSource.kt
index c3c9df97a682..5060abdda247 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/source/SystemShortcutsSource.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/source/SystemShortcutsSource.kt
@@ -32,14 +32,12 @@ import android.view.KeyEvent.KEYCODE_RECENT_APPS
import android.view.KeyEvent.KEYCODE_S
import android.view.KeyEvent.KEYCODE_SLASH
import android.view.KeyEvent.KEYCODE_TAB
-import android.view.KeyEvent.KEYCODE_V
import android.view.KeyEvent.META_ALT_ON
import android.view.KeyEvent.META_CTRL_ON
import android.view.KeyEvent.META_META_ON
import android.view.KeyEvent.META_SHIFT_ON
import android.view.KeyboardShortcutGroup
import android.view.KeyboardShortcutInfo
-import com.android.hardware.input.Flags.enableVoiceAccessKeyGestures
import com.android.systemui.Flags.shortcutHelperKeyGlyph
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.keyboard.shortcut.data.model.shortcutInfo
@@ -120,8 +118,8 @@ constructor(@Main private val resources: Resources, private val inputManager: In
return shortcuts
}
- private fun systemControlsShortcuts(): List<KeyboardShortcutInfo> {
- val shortcuts = mutableListOf(
+ private fun systemControlsShortcuts() =
+ listOf(
// Access list of all apps and search (i.e. Search/Launcher):
// - Meta
shortcutInfo(resources.getString(R.string.group_system_access_all_apps_search)) {
@@ -178,19 +176,6 @@ constructor(@Main private val resources: Resources, private val inputManager: In
},
)
- if (enableVoiceAccessKeyGestures()) {
- shortcuts.add(
- // Toggle voice access:
- // - Meta + Alt + V
- shortcutInfo(resources.getString(R.string.group_system_toggle_voice_access)) {
- command(META_META_ON or META_ALT_ON, KEYCODE_V)
- }
- )
- }
-
- return shortcuts
- }
-
private fun systemAppsShortcuts() =
listOf(
// Pull up Notes app for quick memo:
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutCustomizationInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutCustomizationInteractor.kt
index ef242678a8ac..1a62517ad01d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutCustomizationInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutCustomizationInteractor.kt
@@ -53,4 +53,7 @@ constructor(private val customShortcutRepository: CustomShortcutCategoriesReposi
suspend fun resetAllCustomShortcuts(): ShortcutCustomizationRequestResult {
return customShortcutRepository.resetAllCustomShortcuts()
}
+
+ suspend fun isSelectedKeyCombinationAvailable(): Boolean =
+ customShortcutRepository.isSelectedKeyCombinationAvailable()
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/ShortcutCustomizationDialogStarter.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/ShortcutCustomizationDialogStarter.kt
index f1945e657d52..54e27a61ac78 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/ShortcutCustomizationDialogStarter.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/ShortcutCustomizationDialogStarter.kt
@@ -43,6 +43,7 @@ import com.android.systemui.statusbar.phone.create
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import kotlinx.coroutines.awaitCancellation
+import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.launch
class ShortcutCustomizationDialogStarter
@@ -57,20 +58,25 @@ constructor(
private val viewModel = viewModelFactory.create()
override suspend fun onActivated(): Nothing {
- viewModel.shortcutCustomizationUiState.collect { uiState ->
- when (uiState) {
- is AddShortcutDialog,
- is DeleteShortcutDialog,
- is ResetShortcutDialog -> {
- if (dialog == null) {
- dialog = createDialog().also { it.show() }
+ coroutineScope {
+ launch {
+ viewModel.shortcutCustomizationUiState.collect { uiState ->
+ when (uiState) {
+ is AddShortcutDialog,
+ is DeleteShortcutDialog,
+ is ResetShortcutDialog -> {
+ if (dialog == null) {
+ dialog = createDialog().also { it.show() }
+ }
+ }
+ is ShortcutCustomizationUiState.Inactive -> {
+ dialog?.dismiss()
+ dialog = null
+ }
}
}
- is ShortcutCustomizationUiState.Inactive -> {
- dialog?.dismiss()
- dialog = null
- }
}
+ launch { viewModel.activate() }
}
awaitCancellation()
}
@@ -101,6 +107,7 @@ constructor(
onConfirmResetShortcut = {
coroutineScope.launch { viewModel.resetAllCustomShortcuts() }
},
+ onClearSelectedKeyCombination = { viewModel.clearSelectedKeyCombination() },
)
setDialogProperties(dialog, uiState)
}
@@ -108,22 +115,33 @@ constructor(
private fun setDialogProperties(dialog: SystemUIDialog, uiState: ShortcutCustomizationUiState) {
dialog.setOnDismissListener { viewModel.onDialogDismissed() }
- dialog.setTitle(
- resources.getString(
- when (uiState) {
- is AddShortcutDialog ->
- R.string.shortcut_customize_mode_add_shortcut_description
- is DeleteShortcutDialog ->
- R.string.shortcut_customize_mode_remove_shortcut_description
- else -> R.string.shortcut_customize_mode_reset_shortcut_description
- }
- )
- )
+ dialog.setTitle("${getDialogTitle(uiState)}. ${getDialogDescription(uiState)}")
// By default, apps cannot intercept action key. The system always handles it. This
// flag is needed to enable customisation dialog window to intercept action key
dialog.window?.addPrivateFlags(PRIVATE_FLAG_ALLOW_ACTION_KEY_EVENTS)
}
+ private fun getDialogTitle(uiState: ShortcutCustomizationUiState): String {
+ return when (uiState) {
+ is AddShortcutDialog -> uiState.shortcutLabel
+ is DeleteShortcutDialog ->
+ resources.getString(R.string.shortcut_customize_mode_remove_shortcut_dialog_title)
+ else ->
+ resources.getString(R.string.shortcut_customize_mode_reset_shortcut_dialog_title)
+ }
+ }
+
+ private fun getDialogDescription(uiState: ShortcutCustomizationUiState): String {
+ return resources.getString(
+ when (uiState) {
+ is AddShortcutDialog -> R.string.shortcut_customize_mode_add_shortcut_description
+ is DeleteShortcutDialog ->
+ R.string.shortcut_customize_mode_remove_shortcut_description
+ else -> R.string.shortcut_customize_mode_reset_shortcut_description
+ }
+ )
+ }
+
@AssistedFactory
interface Factory {
fun create(): ShortcutCustomizationDialogStarter
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/ShortcutHelperDialogStarter.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/ShortcutHelperDialogStarter.kt
index fa03883e2a35..ea36a10fb01a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/ShortcutHelperDialogStarter.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/ShortcutHelperDialogStarter.kt
@@ -24,7 +24,6 @@ import android.os.UserHandle
import android.provider.Settings
import androidx.annotation.VisibleForTesting
import androidx.compose.foundation.layout.width
-import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
@@ -36,6 +35,7 @@ import com.android.systemui.keyboard.shortcut.ui.composable.ShortcutHelper
import com.android.systemui.keyboard.shortcut.ui.composable.ShortcutHelperBottomSheet
import com.android.systemui.keyboard.shortcut.ui.composable.getWidth
import com.android.systemui.keyboard.shortcut.ui.viewmodel.ShortcutHelperViewModel
+import com.android.systemui.lifecycle.rememberActivated
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.res.R
import com.android.systemui.statusbar.phone.SystemUIDialogFactory
@@ -51,14 +51,13 @@ class ShortcutHelperDialogStarter
constructor(
@Application private val applicationScope: CoroutineScope,
private val shortcutHelperViewModel: ShortcutHelperViewModel,
- shortcutCustomizationDialogStarterFactory: ShortcutCustomizationDialogStarter.Factory,
+ private val shortcutCustomizationDialogStarterFactory:
+ ShortcutCustomizationDialogStarter.Factory,
private val dialogFactory: SystemUIDialogFactory,
private val activityStarter: ActivityStarter,
) : CoreStartable {
@VisibleForTesting var dialog: Dialog? = null
- private val shortcutCustomizationDialogStarter =
- shortcutCustomizationDialogStarterFactory.create()
override fun start() {
shortcutHelperViewModel.shouldShow
@@ -77,7 +76,10 @@ constructor(
content = { dialog ->
val shortcutsUiState by
shortcutHelperViewModel.shortcutsUiState.collectAsStateWithLifecycle()
- LaunchedEffect(Unit) { shortcutCustomizationDialogStarter.activate() }
+ val shortcutCustomizationDialogStarter =
+ rememberActivated(traceName = "shortcutCustomizationDialogStarter") {
+ shortcutCustomizationDialogStarterFactory.create()
+ }
ShortcutHelper(
modifier = Modifier.width(getWidth()),
shortcutsUiState = shortcutsUiState,
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutCustomizer.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutCustomizer.kt
index 550438aa220f..66e45056989d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutCustomizer.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutCustomizer.kt
@@ -18,14 +18,10 @@ package com.android.systemui.keyboard.shortcut.ui.composable
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.background
-import androidx.compose.foundation.border
-import androidx.compose.foundation.interaction.MutableInteractionSource
-import androidx.compose.foundation.interaction.collectIsFocusedAsState
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
-import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.heightIn
@@ -40,13 +36,15 @@ import androidx.compose.material.icons.filled.Add
import androidx.compose.material.icons.filled.ErrorOutline
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.OutlinedTextField
+import androidx.compose.material3.OutlinedTextFieldDefaults
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
-import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.focus.FocusDirection
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusProperties
import androidx.compose.ui.focus.focusRequester
@@ -57,9 +55,15 @@ import androidx.compose.ui.input.key.KeyEventType
import androidx.compose.ui.input.key.key
import androidx.compose.ui.input.key.onPreviewKeyEvent
import androidx.compose.ui.input.key.type
+import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.semantics.LiveRegionMode
+import androidx.compose.ui.semantics.contentDescription
+import androidx.compose.ui.semantics.liveRegion
+import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.android.compose.ui.graphics.painter.rememberDrawablePainter
@@ -76,6 +80,7 @@ fun ShortcutCustomizationDialog(
onConfirmSetShortcut: () -> Unit,
onConfirmDeleteShortcut: () -> Unit,
onConfirmResetShortcut: () -> Unit,
+ onClearSelectedKeyCombination: () -> Unit,
) {
when (uiState) {
is ShortcutCustomizationUiState.AddShortcutDialog -> {
@@ -85,6 +90,7 @@ fun ShortcutCustomizationDialog(
onShortcutKeyCombinationSelected,
onCancel,
onConfirmSetShortcut,
+ onClearSelectedKeyCombination,
)
}
is ShortcutCustomizationUiState.DeleteShortcutDialog -> {
@@ -106,6 +112,7 @@ private fun AddShortcutDialog(
onShortcutKeyCombinationSelected: (KeyEvent) -> Boolean,
onCancel: () -> Unit,
onConfirmSetShortcut: () -> Unit,
+ onClearSelectedKeyCombination: () -> Unit,
) {
Column(modifier = modifier, horizontalAlignment = Alignment.CenterHorizontally) {
Title(uiState.shortcutLabel)
@@ -121,6 +128,7 @@ private fun AddShortcutDialog(
onShortcutKeyCombinationSelected = onShortcutKeyCombinationSelected,
pressedKeys = uiState.pressedKeys,
onConfirmSetShortcut = onConfirmSetShortcut,
+ onClearSelectedKeyCombination = onClearSelectedKeyCombination,
)
ErrorMessageContainer(uiState.errorMessage)
DialogButtons(
@@ -244,7 +252,11 @@ private fun ErrorMessageContainer(errorMessage: String) {
lineHeight = 20.sp,
fontWeight = FontWeight.W500,
color = MaterialTheme.colorScheme.error,
- modifier = Modifier.padding(start = 24.dp).width(252.dp),
+ modifier =
+ Modifier.padding(start = 24.dp).width(252.dp).semantics {
+ contentDescription = errorMessage
+ liveRegion = LiveRegionMode.Polite
+ },
)
}
}
@@ -256,72 +268,82 @@ private fun SelectedKeyCombinationContainer(
onShortcutKeyCombinationSelected: (KeyEvent) -> Boolean,
pressedKeys: List<ShortcutKey>,
onConfirmSetShortcut: () -> Unit,
+ onClearSelectedKeyCombination: () -> Unit,
) {
- val interactionSource = remember { MutableInteractionSource() }
- val isFocused by interactionSource.collectIsFocusedAsState()
- val outlineColor =
- if (!isFocused) MaterialTheme.colorScheme.outline
- else if (shouldShowError) MaterialTheme.colorScheme.error
- else MaterialTheme.colorScheme.primary
val focusRequester = remember { FocusRequester() }
-
+ val focusManager = LocalFocusManager.current
LaunchedEffect(Unit) { focusRequester.requestFocus() }
- ClickableShortcutSurface(
- onClick = {},
- color = Color.Transparent,
- shape = RoundedCornerShape(50.dp),
+ OutlinedInputField(
modifier =
Modifier.padding(all = 16.dp)
.sizeIn(minWidth = 332.dp, minHeight = 56.dp)
- .border(width = 2.dp, color = outlineColor, shape = RoundedCornerShape(50.dp))
+ .focusRequester(focusRequester)
+ .focusProperties { canFocus = true }
.onPreviewKeyEvent { keyEvent ->
val keyEventProcessed = onShortcutKeyCombinationSelected(keyEvent)
- if (
- !keyEventProcessed &&
- keyEvent.key == Key.Enter &&
- keyEvent.type == KeyEventType.KeyUp
- ) {
- onConfirmSetShortcut()
+ if (keyEventProcessed) {
true
- } else keyEventProcessed
- }
- .focusProperties { canFocus = true } // enables keyboard focus when in touch mode
- .focusRequester(focusRequester),
- interactionSource = interactionSource,
- ) {
- Row(
- modifier = Modifier.padding(start = 24.dp, top = 16.dp, end = 16.dp, bottom = 16.dp),
- verticalAlignment = Alignment.CenterVertically,
- ) {
- if (pressedKeys.isEmpty()) {
- PressKeyPrompt()
+ } else {
+ if (keyEvent.type == KeyEventType.KeyUp) {
+ when (keyEvent.key) {
+ Key.Enter -> {
+ onConfirmSetShortcut()
+ return@onPreviewKeyEvent true
+ }
+ Key.Backspace -> {
+ onClearSelectedKeyCombination()
+ return@onPreviewKeyEvent true
+ }
+ Key.DirectionDown -> {
+ focusManager.moveFocus(FocusDirection.Down)
+ return@onPreviewKeyEvent true
+ }
+ else -> return@onPreviewKeyEvent false
+ }
+ } else false
+ }
+ },
+ trailingIcon = { ErrorIcon(shouldShowError) },
+ isError = shouldShowError,
+ placeholder = { PressKeyPrompt() },
+ content =
+ if (pressedKeys.isNotEmpty()) {
+ { PressedKeysTextContainer(pressedKeys) }
} else {
- PressedKeysTextContainer(pressedKeys)
- }
- Spacer(modifier = Modifier.weight(1f))
- if (shouldShowError) {
- Icon(
- imageVector = Icons.Default.ErrorOutline,
- contentDescription = null,
- modifier = Modifier.size(20.dp),
- tint = MaterialTheme.colorScheme.error,
- )
- }
- }
+ null
+ },
+ )
+}
+
+@Composable
+private fun ErrorIcon(shouldShowError: Boolean) {
+ if (shouldShowError) {
+ Icon(
+ imageVector = Icons.Default.ErrorOutline,
+ contentDescription = null,
+ modifier = Modifier.size(20.dp),
+ tint = MaterialTheme.colorScheme.error,
+ )
}
}
@Composable
-private fun RowScope.PressedKeysTextContainer(pressedKeys: List<ShortcutKey>) {
- pressedKeys.forEachIndexed { keyIndex, key ->
- if (keyIndex > 0) {
- ShortcutKeySeparator()
- }
- if (key is ShortcutKey.Text) {
- ShortcutTextKey(key)
- } else if (key is ShortcutKey.Icon) {
- ShortcutIconKey(key)
+private fun PressedKeysTextContainer(pressedKeys: List<ShortcutKey>) {
+ Row(
+ modifier =
+ Modifier.semantics(mergeDescendants = true) { liveRegion = LiveRegionMode.Polite },
+ verticalAlignment = Alignment.CenterVertically,
+ ) {
+ pressedKeys.forEachIndexed { keyIndex, key ->
+ if (keyIndex > 0) {
+ ShortcutKeySeparator()
+ }
+ if (key is ShortcutKey.Text) {
+ ShortcutTextKey(key)
+ } else if (key is ShortcutKey.Icon) {
+ ShortcutIconKey(key)
+ }
}
}
}
@@ -338,7 +360,7 @@ private fun ShortcutKeySeparator() {
}
@Composable
-private fun RowScope.ShortcutIconKey(key: ShortcutKey.Icon) {
+private fun ShortcutIconKey(key: ShortcutKey.Icon) {
Icon(
painter =
when (key) {
@@ -346,7 +368,7 @@ private fun RowScope.ShortcutIconKey(key: ShortcutKey.Icon) {
is ShortcutKey.Icon.DrawableIcon -> rememberDrawablePainter(drawable = key.drawable)
},
contentDescription = null,
- modifier = Modifier.align(Alignment.CenterVertically).height(24.dp),
+ modifier = Modifier.height(24.dp),
tint = MaterialTheme.colorScheme.onSurfaceVariant,
)
}
@@ -397,6 +419,7 @@ private fun Description(text: String) {
.width(316.dp)
.wrapContentSize(Alignment.Center),
color = MaterialTheme.colorScheme.onSurfaceVariant,
+ textAlign = TextAlign.Center,
)
}
@@ -464,3 +487,31 @@ private fun PlusIconContainer() {
modifier = Modifier.padding(vertical = 12.dp).size(24.dp).wrapContentSize(Alignment.Center),
)
}
+
+@Composable
+private fun OutlinedInputField(
+ content: @Composable (() -> Unit)?,
+ placeholder: @Composable () -> Unit,
+ trailingIcon: @Composable () -> Unit,
+ isError: Boolean,
+ modifier: Modifier = Modifier,
+) {
+ OutlinedTextField(
+ value = "",
+ onValueChange = {},
+ placeholder = if (content == null) placeholder else null,
+ prefix = content,
+ singleLine = true,
+ modifier = modifier,
+ trailingIcon = trailingIcon,
+ colors =
+ OutlinedTextFieldDefaults.colors()
+ .copy(
+ focusedIndicatorColor = MaterialTheme.colorScheme.primary,
+ unfocusedIndicatorColor = MaterialTheme.colorScheme.outline,
+ errorIndicatorColor = MaterialTheme.colorScheme.error,
+ ),
+ shape = RoundedCornerShape(50.dp),
+ isError = isError,
+ )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutCustomizationViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutCustomizationViewModel.kt
index 915a66c43a12..f4ba99c6a394 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutCustomizationViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutCustomizationViewModel.kt
@@ -28,16 +28,17 @@ import com.android.systemui.keyboard.shared.model.ShortcutCustomizationRequestRe
import com.android.systemui.keyboard.shortcut.domain.interactor.ShortcutCustomizationInteractor
import com.android.systemui.keyboard.shortcut.shared.model.KeyCombination
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCustomizationRequestInfo
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutKey
import com.android.systemui.keyboard.shortcut.ui.model.ShortcutCustomizationUiState
import com.android.systemui.keyboard.shortcut.ui.model.ShortcutCustomizationUiState.AddShortcutDialog
import com.android.systemui.keyboard.shortcut.ui.model.ShortcutCustomizationUiState.DeleteShortcutDialog
import com.android.systemui.keyboard.shortcut.ui.model.ShortcutCustomizationUiState.ResetShortcutDialog
+import com.android.systemui.lifecycle.ExclusiveActivatable
import com.android.systemui.res.R
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
class ShortcutCustomizationViewModel
@@ -45,26 +46,12 @@ class ShortcutCustomizationViewModel
constructor(
private val context: Context,
private val shortcutCustomizationInteractor: ShortcutCustomizationInteractor,
-) {
+) : ExclusiveActivatable() {
private var keyDownEventCache: KeyEvent? = null
private val _shortcutCustomizationUiState =
MutableStateFlow<ShortcutCustomizationUiState>(ShortcutCustomizationUiState.Inactive)
- val shortcutCustomizationUiState =
- shortcutCustomizationInteractor.pressedKeys
- .map { keys ->
- // Note that Action Key is excluded as it's already displayed on the UI
- keys.filter {
- it != shortcutCustomizationInteractor.getDefaultCustomShortcutModifierKey()
- }
- }
- .combine(_shortcutCustomizationUiState) { keys, uiState ->
- if (uiState is AddShortcutDialog) {
- uiState.copy(pressedKeys = keys)
- } else {
- uiState
- }
- }
+ val shortcutCustomizationUiState = _shortcutCustomizationUiState.asStateFlow()
fun onShortcutCustomizationRequested(requestInfo: ShortcutCustomizationRequestInfo) {
shortcutCustomizationInteractor.onCustomizationRequested(requestInfo)
@@ -92,7 +79,7 @@ constructor(
fun onDialogDismissed() {
_shortcutCustomizationUiState.value = ShortcutCustomizationUiState.Inactive
shortcutCustomizationInteractor.onCustomizationRequested(null)
- shortcutCustomizationInteractor.updateUserSelectedKeyCombination(null)
+ clearSelectedKeyCombination()
}
fun onShortcutKeyCombinationSelected(keyEvent: KeyEvent): Boolean {
@@ -112,7 +99,6 @@ constructor(
suspend fun onSetShortcut() {
val result = shortcutCustomizationInteractor.confirmAndSetShortcutCurrentlyBeingCustomized()
-
_shortcutCustomizationUiState.update { uiState ->
when (result) {
ShortcutCustomizationRequestResult.SUCCESS -> ShortcutCustomizationUiState.Inactive
@@ -158,6 +144,10 @@ constructor(
}
}
+ fun clearSelectedKeyCombination() {
+ shortcutCustomizationInteractor.updateUserSelectedKeyCombination(null)
+ }
+
private fun getUiStateWithErrorMessage(
uiState: ShortcutCustomizationUiState,
errorMessage: String,
@@ -180,11 +170,41 @@ constructor(
keyDownEventCache = null
}
+ private suspend fun isSelectedKeyCombinationAvailable() =
+ shortcutCustomizationInteractor.isSelectedKeyCombinationAvailable()
+
@AssistedFactory
interface Factory {
fun create(): ShortcutCustomizationViewModel
}
+ override suspend fun onActivated(): Nothing {
+ shortcutCustomizationInteractor.pressedKeys.collect {
+ val keys = filterDefaultCustomShortcutModifierKey(it)
+ val errorMessage = getErrorMessageForPressedKeys(keys)
+
+ _shortcutCustomizationUiState.update { uiState ->
+ if (uiState is AddShortcutDialog) {
+ uiState.copy(pressedKeys = keys, errorMessage = errorMessage)
+ } else {
+ uiState
+ }
+ }
+ }
+ }
+
+ private suspend fun getErrorMessageForPressedKeys(keys: List<ShortcutKey>): String {
+ return if (keys.isEmpty() or isSelectedKeyCombinationAvailable()) {
+ ""
+ }
+ else {
+ context.getString(R.string.shortcut_customizer_key_combination_in_use_error_message)
+ }
+ }
+
+ private fun filterDefaultCustomShortcutModifierKey(keys: List<ShortcutKey>) =
+ keys.filter { it != shortcutCustomizationInteractor.getDefaultCustomShortcutModifierKey() }
+
companion object {
private val SUPPORTED_MODIFIERS =
listOf(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
index 5e0768a2fd24..f37e7685f21c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
@@ -27,6 +27,7 @@ import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryHapticsInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
+import com.android.systemui.keyguard.domain.interactor.WallpaperFocalAreaInteractor
import com.android.systemui.keyguard.ui.binder.KeyguardBlueprintViewBinder
import com.android.systemui.keyguard.ui.binder.KeyguardRootViewBinder
import com.android.systemui.keyguard.ui.binder.LightRevealScrimViewBinder
@@ -79,6 +80,7 @@ constructor(
private val keyguardClockViewModel: KeyguardClockViewModel,
private val smartspaceViewModel: KeyguardSmartspaceViewModel,
private val clockInteractor: KeyguardClockInteractor,
+ private val wallpaperFocalAreaInteractor: WallpaperFocalAreaInteractor,
private val keyguardViewMediator: KeyguardViewMediator,
private val deviceEntryUnlockTrackerViewBinder: Optional<DeviceEntryUnlockTrackerViewBinder>,
private val statusBarKeyguardViewManager: StatusBarKeyguardViewManager,
@@ -139,6 +141,7 @@ constructor(
screenOffAnimationController,
shadeInteractor,
clockInteractor,
+ wallpaperFocalAreaInteractor,
keyguardClockViewModel,
interactionJankMonitor,
deviceEntryHapticsInteractor,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
index a39982dd31e7..11477fb6cad1 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
@@ -17,6 +17,7 @@
package com.android.systemui.keyguard.data.repository
import android.graphics.Point
+import android.graphics.RectF
import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.internal.widget.LockPatternUtils
import com.android.keyguard.KeyguardUpdateMonitor
@@ -258,6 +259,8 @@ interface KeyguardRepository {
val notificationStackAbsoluteBottom: StateFlow<Float>
+ val wallpaperFocalAreaBounds: StateFlow<RectF>
+
/**
* Returns `true` if the keyguard is showing; `false` otherwise.
*
@@ -329,6 +332,8 @@ interface KeyguardRepository {
* this value
*/
fun setNotificationStackAbsoluteBottom(bottom: Float)
+
+ fun setWallpaperFocalAreaBounds(bounds: RectF)
}
/** Encapsulates application state for the keyguard. */
@@ -380,7 +385,6 @@ constructor(
override val onCameraLaunchDetected = MutableStateFlow(CameraLaunchSourceModel())
override val panelAlpha: MutableStateFlow<Float> = MutableStateFlow(1f)
-
override val topClippingBounds = MutableStateFlow<Int?>(null)
override val isKeyguardShowing: MutableStateFlow<Boolean> =
@@ -622,6 +626,10 @@ constructor(
private val _notificationStackAbsoluteBottom = MutableStateFlow(0F)
override val notificationStackAbsoluteBottom = _notificationStackAbsoluteBottom.asStateFlow()
+ private val _wallpaperFocalAreaBounds = MutableStateFlow(RectF(0F, 0F, 0F, 0F))
+ override val wallpaperFocalAreaBounds: StateFlow<RectF> =
+ _wallpaperFocalAreaBounds.asStateFlow()
+
init {
val callback =
object : KeyguardStateController.Callback {
@@ -700,6 +708,10 @@ constructor(
_notificationStackAbsoluteBottom.value = bottom
}
+ override fun setWallpaperFocalAreaBounds(bounds: RectF) {
+ _wallpaperFocalAreaBounds.value = bounds
+ }
+
private fun dozeMachineStateToModel(state: DozeMachine.State): DozeStateModel {
return when (state) {
DozeMachine.State.UNINITIALIZED -> DozeStateModel.UNINITIALIZED
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt
index 3565b612a3c9..c5d40a0dcf30 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt
@@ -48,7 +48,6 @@ import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.debounce
-import com.android.app.tracing.coroutines.launchTraced as launch
import kotlinx.coroutines.withContext
@OptIn(FlowPreview::class)
@@ -92,6 +91,7 @@ constructor(
listenForHubToAlternateBouncer()
listenForHubToOccluded()
listenForHubToGone()
+ listenForHubToDreaming()
}
override fun getDefaultAnimatorForTransitionsToState(toState: KeyguardState): ValueAnimator {
@@ -177,6 +177,24 @@ constructor(
}
}
+ private fun listenForHubToDreaming() {
+ if (!communalSettingsInteractor.isV2FlagEnabled()) {
+ return
+ }
+
+ scope.launch {
+ keyguardInteractor.isAbleToDream
+ .filterRelevantKeyguardStateAnd { isAbleToDream -> isAbleToDream }
+ .collect {
+ communalSceneInteractor.changeScene(
+ newScene = CommunalScenes.Blank,
+ loggingReason = "hub to dreaming",
+ keyguardState = KeyguardState.DREAMING,
+ )
+ }
+ }
+ }
+
private fun listenForHubToOccluded() {
if (KeyguardWmStateRefactor.isEnabled) {
scope.launch {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WallpaperFocalAreaInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WallpaperFocalAreaInteractor.kt
new file mode 100644
index 000000000000..934afe248a36
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WallpaperFocalAreaInteractor.kt
@@ -0,0 +1,176 @@
+/*
+ * 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.keyguard.domain.interactor
+
+import android.content.Context
+import android.content.res.Resources
+import android.graphics.RectF
+import android.util.TypedValue
+import com.android.app.animation.MathUtils.max
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.keyguard.data.repository.KeyguardClockRepository
+import com.android.systemui.keyguard.data.repository.KeyguardRepository
+import com.android.systemui.res.R
+import com.android.systemui.shade.data.repository.ShadeRepository
+import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor
+import com.android.systemui.wallpapers.data.repository.WallpaperRepository
+import javax.inject.Inject
+import kotlin.math.min
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.stateIn
+
+@SysUISingleton
+class WallpaperFocalAreaInteractor
+@Inject
+constructor(
+ @Application private val applicationScope: CoroutineScope,
+ context: Context,
+ private val keyguardRepository: KeyguardRepository,
+ shadeRepository: ShadeRepository,
+ activeNotificationsInteractor: ActiveNotificationsInteractor,
+ keyguardClockRepository: KeyguardClockRepository,
+ wallpaperRepository: WallpaperRepository,
+) {
+ // When there's notifications in splitshade, magic portrait shape effects should be left
+ // aligned in foldable
+ private val notificationInShadeWideLayout: Flow<Boolean> =
+ combine(
+ shadeRepository.isShadeLayoutWide,
+ activeNotificationsInteractor.areAnyNotificationsPresent,
+ ) { isShadeLayoutWide, areAnyNotificationsPresent: Boolean ->
+ when {
+ !isShadeLayoutWide -> false
+ !areAnyNotificationsPresent -> false
+ else -> true
+ }
+ }
+
+ val shouldSendFocalArea = wallpaperRepository.shouldSendFocalArea
+ val wallpaperFocalAreaBounds: StateFlow<RectF?> =
+ combine(
+ shadeRepository.isShadeLayoutWide,
+ notificationInShadeWideLayout,
+ keyguardRepository.notificationStackAbsoluteBottom,
+ keyguardRepository.shortcutAbsoluteTop,
+ keyguardClockRepository.notificationDefaultTop,
+ ) {
+ isShadeLayoutWide,
+ notificationInShadeWideLayout,
+ notificationStackAbsoluteBottom,
+ shortcutAbsoluteTop,
+ notificationDefaultTop ->
+ // Wallpaper will be zoomed in with config_wallpaperMaxScale in lockscreen
+ // so we need to give a bounds taking this scale in consideration
+ val wallpaperZoomedInScale = getSystemWallpaperMaximumScale(context)
+ val screenBounds =
+ RectF(
+ 0F,
+ 0F,
+ context.resources.displayMetrics.widthPixels.toFloat(),
+ context.resources.displayMetrics.heightPixels.toFloat(),
+ )
+ val scaledBounds =
+ RectF(
+ screenBounds.centerX() - screenBounds.width() / 2F / wallpaperZoomedInScale,
+ screenBounds.centerY() -
+ screenBounds.height() / 2F / wallpaperZoomedInScale,
+ screenBounds.centerX() + screenBounds.width() / 2F / wallpaperZoomedInScale,
+ screenBounds.centerY() + screenBounds.height() / 2F / wallpaperZoomedInScale,
+ )
+ val maxFocalAreaWidth =
+ TypedValue.applyDimension(
+ TypedValue.COMPLEX_UNIT_DIP,
+ FOCAL_AREA_MAX_WIDTH_DP.toFloat(),
+ context.resources.displayMetrics,
+ )
+ val (left, right) =
+ // tablet landscape
+ if (context.resources.getBoolean(R.bool.center_align_magic_portrait_shape)) {
+ Pair(
+ scaledBounds.centerX() - maxFocalAreaWidth / 2F,
+ scaledBounds.centerX() + maxFocalAreaWidth / 2F,
+ )
+ // unfold foldable landscape
+ } else if (isShadeLayoutWide) {
+ if (notificationInShadeWideLayout) {
+ Pair(scaledBounds.left, scaledBounds.centerX())
+ } else {
+ Pair(scaledBounds.centerX(), scaledBounds.right)
+ }
+ // handheld / portrait
+ } else {
+ val focalAreaWidth = min(scaledBounds.width(), maxFocalAreaWidth)
+ Pair(
+ scaledBounds.centerX() - focalAreaWidth / 2F,
+ scaledBounds.centerX() + focalAreaWidth / 2F,
+ )
+ }
+ val scaledBottomMargin =
+ (context.resources.displayMetrics.heightPixels - shortcutAbsoluteTop) /
+ wallpaperZoomedInScale
+ val top =
+ // tablet landscape
+ if (context.resources.getBoolean(R.bool.center_align_magic_portrait_shape)) {
+ // no strict constraints for top, use bottom margin to make it symmetric
+ // vertically
+ scaledBounds.top + scaledBottomMargin
+ }
+ // unfold foldable landscape
+ else if (isShadeLayoutWide) {
+ // For all landscape, we should use bottom of smartspace to constrain
+ scaledBounds.top + notificationDefaultTop / wallpaperZoomedInScale
+ // handheld / portrait
+ } else {
+ scaledBounds.top +
+ max(notificationDefaultTop, notificationStackAbsoluteBottom) /
+ wallpaperZoomedInScale
+ }
+ val bottom = scaledBounds.bottom - scaledBottomMargin
+ RectF(left, top, right, bottom)
+ }
+ .stateIn(
+ applicationScope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue = null,
+ )
+
+ fun setWallpaperFocalAreaBounds(bounds: RectF) {
+ keyguardRepository.setWallpaperFocalAreaBounds(bounds)
+ }
+
+ companion object {
+ fun getSystemWallpaperMaximumScale(context: Context): Float {
+ return context.resources.getFloat(
+ Resources.getSystem()
+ .getIdentifier(
+ /* name= */ "config_wallpaperMaxScale",
+ /* defType= */ "dimen",
+ /* defPackage= */ "android",
+ )
+ )
+ }
+
+ // A max width for magic portrait shape effects bounds, to avoid it going too large
+ // in large screen portrait mode
+ const val FOCAL_AREA_MAX_WIDTH_DP = 500
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt
index 184f30237e8d..6a354821f628 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt
@@ -24,7 +24,6 @@ import com.android.systemui.Flags.transitionRaceCondition
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
-import com.android.systemui.keyguard.shared.model.BiometricUnlockMode
import com.android.systemui.keyguard.shared.model.Edge
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.KeyguardState.Companion.deviceIsAsleepInState
@@ -331,17 +330,9 @@ constructor(
* clock/smartspace/notif icons are visible.
*/
val aodVisibility: Flow<Boolean> =
- combine(
- keyguardInteractor.isDozing,
- keyguardInteractor.isAodAvailable,
- keyguardInteractor.biometricUnlockState,
- ) { isDozing, isAodAvailable, biometricUnlockState ->
- // AOD is visible if we're dozing, unless we are wake and unlocking (where we go
- // directly from AOD to unlocked while dozing).
- isDozing &&
- isAodAvailable &&
- !BiometricUnlockMode.isWakeAndUnlock(biometricUnlockState.mode)
- }
+ transitionInteractor
+ .transitionValue(KeyguardState.AOD)
+ .map { it == 1f }
.distinctUntilChanged()
companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardIndicationAreaBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardIndicationAreaBinder.kt
index 92b49ed6156c..21c9b0b82b2d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardIndicationAreaBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardIndicationAreaBinder.kt
@@ -23,6 +23,7 @@ import android.widget.TextView
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
import com.android.app.tracing.coroutines.launchTraced as launch
+import com.android.systemui.Flags
import com.android.systemui.keyguard.ui.viewmodel.KeyguardIndicationAreaViewModel
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.res.R
@@ -73,7 +74,6 @@ object KeyguardIndicationAreaBinder {
disposables +=
view.repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.STARTED) {
-
launch("$TAG#viewModel.indicationAreaTranslationX") {
viewModel.indicationAreaTranslationX.collect { translationX ->
view.translationX = translationX
@@ -119,6 +119,9 @@ object KeyguardIndicationAreaBinder {
launch("$TAG#viewModel.configurationChange") {
viewModel.configurationChange.collect {
configurationBasedDimensions.value = loadFromResources(view)
+ if (Flags.indicationTextA11yFix()) {
+ indicationController.onConfigurationChanged()
+ }
}
}
@@ -140,7 +143,7 @@ object KeyguardIndicationAreaBinder {
view.resources.getDimensionPixelOffset(R.dimen.keyguard_indication_area_padding),
indicationTextSizePx =
view.resources.getDimensionPixelSize(
- com.android.internal.R.dimen.text_size_small_material,
+ com.android.internal.R.dimen.text_size_small_material
),
)
}
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 6d270b219c81..d8bd4452f2a6 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
@@ -54,6 +54,7 @@ import com.android.systemui.customization.R as customR
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryHapticsInteractor
import com.android.systemui.keyguard.KeyguardViewMediator
import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
+import com.android.systemui.keyguard.domain.interactor.WallpaperFocalAreaInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.ui.viewmodel.BurnInParameters
@@ -87,6 +88,7 @@ import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.DisposableHandle
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.update
@@ -105,6 +107,7 @@ object KeyguardRootViewBinder {
screenOffAnimationController: ScreenOffAnimationController,
shadeInteractor: ShadeInteractor,
clockInteractor: KeyguardClockInteractor,
+ wallpaperFocalAreaInteractor: WallpaperFocalAreaInteractor,
clockViewModel: KeyguardClockViewModel,
interactionJankMonitor: InteractionJankMonitor?,
deviceEntryHapticsInteractor: DeviceEntryHapticsInteractor?,
@@ -309,12 +312,15 @@ object KeyguardRootViewBinder {
.setTag(clockId)
jankMonitor.begin(builder)
}
+
TransitionState.CANCELED ->
jankMonitor.cancel(CUJ_SCREEN_OFF_SHOW_AOD)
+
TransitionState.FINISHED -> {
keyguardViewMediator?.maybeHandlePendingLock()
jankMonitor.end(CUJ_SCREEN_OFF_SHOW_AOD)
}
+
TransitionState.RUNNING -> Unit
}
}
@@ -378,6 +384,21 @@ object KeyguardRootViewBinder {
}
disposables +=
+ view.repeatWhenAttached {
+ repeatOnLifecycle(Lifecycle.State.STARTED) {
+ if (viewModel.shouldSendFocalArea.value) {
+ launch {
+ wallpaperFocalAreaInteractor.wallpaperFocalAreaBounds
+ .filterNotNull()
+ .collect {
+ wallpaperFocalAreaInteractor.setWallpaperFocalAreaBounds(it)
+ }
+ }
+ }
+ }
+ }
+
+ disposables +=
view.onLayoutChanged(
OnLayoutChange(
viewModel,
@@ -523,6 +544,7 @@ object KeyguardRootViewBinder {
View.INVISIBLE
}
}
+
else -> {
if (isVisible.value) {
CrossFadeHelper.fadeIn(this, animatorListener)
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 a2107871a585..7605bdd3d7b5 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
@@ -55,6 +55,7 @@ import com.android.systemui.customization.R as customR
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.keyguard.domain.interactor.WallpaperFocalAreaInteractor
import com.android.systemui.keyguard.shared.model.ClockSizeSetting
import com.android.systemui.keyguard.ui.binder.KeyguardPreviewClockViewBinder
import com.android.systemui.keyguard.ui.binder.KeyguardPreviewSmartspaceViewBinder
@@ -117,6 +118,7 @@ constructor(
private val secureSettings: SecureSettings,
private val defaultShortcutsSection: DefaultShortcutsSection,
private val keyguardQuickAffordanceViewBinder: KeyguardQuickAffordanceViewBinder,
+ private val wallpaperFocalAreaInteractor: WallpaperFocalAreaInteractor,
) {
val hostToken: IBinder? = bundle.getBinder(KEY_HOST_TOKEN)
private val width: Int = bundle.getInt(KEY_VIEW_WIDTH)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/transitions/BlurConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/transitions/BlurConfig.kt
index 3eb8522e0338..542fb9b46bef 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/transitions/BlurConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/transitions/BlurConfig.kt
@@ -23,10 +23,4 @@ data class BlurConfig(val minBlurRadiusPx: Float, val maxBlurRadiusPx: Float) {
// No-op config that will be used by dagger of other SysUI variants which don't blur the
// background surface.
@Inject constructor() : this(0.0f, 0.0f)
-
- companion object {
- // Blur the shade much lesser than the background surface so that the surface is
- // distinguishable from the background.
- @JvmStatic fun Float.maxBlurRadiusToNotificationPanelBlurRadius(): Float = this / 3.0f
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/InWindowLauncherUnlockAnimationManager.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/InWindowLauncherUnlockAnimationManager.kt
index eb005f226cf1..454ba9af5745 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/InWindowLauncherUnlockAnimationManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/InWindowLauncherUnlockAnimationManager.kt
@@ -76,9 +76,8 @@ constructor(
private var manualUnlockAmount: Float? = null
/**
- * Called from [OverviewProxyService] to provide us with the launcher unlock animation
- * controller, which can be used to start and update the unlock animation in the launcher
- * process.
+ * Called from Launcher to provide us with the launcher unlock animation controller, which can
+ * be used to start and update the unlock animation in the launcher process.
*/
override fun setLauncherUnlockController(
activityClass: String,
@@ -117,7 +116,7 @@ constructor(
launcher.prepareForUnlock(
false,
Rect(),
- 0
+ 0,
) // TODO(b/293894758): Add smartspace animation support.
}
}
@@ -134,14 +133,14 @@ constructor(
Log.e(
TAG,
"Called prepareForUnlock(), but not playUnlockAnimation(). " +
- "Failing-safe by calling setUnlockAmount(1f)"
+ "Failing-safe by calling setUnlockAmount(1f)",
)
setUnlockAmount(1f, forceIfAnimating = true)
} else if (manualUnlockSetButNotFullyVisible) {
Log.e(
TAG,
"Unlock has ended, but manual unlock amount != 1f. " +
- "Failing-safe by calling setUnlockAmount(1f)"
+ "Failing-safe by calling setUnlockAmount(1f)",
)
setUnlockAmount(1f, forceIfAnimating = true)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToPrimaryBouncerTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToPrimaryBouncerTransitionViewModel.kt
index 733d7d71061e..a6d15b96547d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToPrimaryBouncerTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToPrimaryBouncerTransitionViewModel.kt
@@ -24,7 +24,6 @@ import com.android.systemui.keyguard.shared.model.KeyguardState.ALTERNATE_BOUNCE
import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER
import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
import com.android.systemui.keyguard.ui.transitions.BlurConfig
-import com.android.systemui.keyguard.ui.transitions.BlurConfig.Companion.maxBlurRadiusToNotificationPanelBlurRadius
import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
import com.android.systemui.keyguard.ui.transitions.PrimaryBouncerTransition
import com.android.systemui.scene.shared.flag.SceneContainerFlag
@@ -89,9 +88,7 @@ constructor(
shadeDependentFlows.transitionFlow(
flowWhenShadeIsNotExpanded = emptyFlow(),
flowWhenShadeIsExpanded =
- transitionAnimation.immediatelyTransitionTo(
- blurConfig.maxBlurRadiusPx.maxBlurRadiusToNotificationPanelBlurRadius()
- ),
+ transitionAnimation.immediatelyTransitionTo(blurConfig.maxBlurRadiusPx),
)
} else {
emptyFlow<Float>()
@@ -103,7 +100,11 @@ constructor(
override val windowBlurRadius: Flow<Float> =
shadeDependentFlows.transitionFlow(
flowWhenShadeIsExpanded =
- transitionAnimation.immediatelyTransitionTo(blurConfig.maxBlurRadiusPx),
+ if (Flags.notificationShadeBlur()) {
+ transitionAnimation.immediatelyTransitionTo(blurConfig.maxBlurRadiusPx)
+ } else {
+ emptyFlow()
+ },
flowWhenShadeIsNotExpanded =
transitionAnimation.sharedFlow(
duration = FromAlternateBouncerTransitionInteractor.TO_PRIMARY_BOUNCER_DURATION,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardMediaViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardMediaViewModel.kt
index e68e465ed55a..ba03c48c65e9 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardMediaViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardMediaViewModel.kt
@@ -16,10 +16,50 @@
package com.android.systemui.keyguard.ui.viewmodel
+import androidx.compose.runtime.getValue
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.lifecycle.ExclusiveActivatable
+import com.android.systemui.lifecycle.Hydrator
import com.android.systemui.media.controls.domain.pipeline.interactor.MediaCarouselInteractor
-import javax.inject.Inject
-import kotlinx.coroutines.flow.StateFlow
+import com.android.systemui.utils.coroutines.flow.flatMapLatestConflated
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import kotlinx.coroutines.flow.flowOf
-class KeyguardMediaViewModel @Inject constructor(mediaCarouselInteractor: MediaCarouselInteractor) {
- val isMediaVisible: StateFlow<Boolean> = mediaCarouselInteractor.hasActiveMediaOrRecommendation
+class KeyguardMediaViewModel
+@AssistedInject
+constructor(
+ mediaCarouselInteractor: MediaCarouselInteractor,
+ keyguardInteractor: KeyguardInteractor,
+) : ExclusiveActivatable() {
+
+ private val hydrator = Hydrator("KeyguardMediaViewModel.hydrator")
+ /**
+ * Whether media carousel is visible on lockscreen. Media may be presented on lockscreen but
+ * still hidden on certain surfaces like AOD
+ */
+ val isMediaVisible: Boolean by
+ hydrator.hydratedStateOf(
+ traceName = "isMediaVisible",
+ source =
+ keyguardInteractor.isDozing.flatMapLatestConflated { isDozing ->
+ if (isDozing) {
+ flowOf(false)
+ } else {
+ mediaCarouselInteractor.hasActiveMediaOrRecommendation
+ }
+ },
+ initialValue =
+ !keyguardInteractor.isDozing.value &&
+ mediaCarouselInteractor.hasActiveMediaOrRecommendation.value,
+ )
+
+ override suspend fun onActivated(): Nothing {
+ hydrator.activate()
+ }
+
+ @AssistedFactory
+ interface Factory {
+ fun create(): KeyguardMediaViewModel
+ }
}
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 e51e05b8ab61..aa4293a201ac 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
@@ -28,6 +28,7 @@ import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.domain.interactor.PulseExpansionInteractor
+import com.android.systemui.keyguard.domain.interactor.WallpaperFocalAreaInteractor
import com.android.systemui.keyguard.shared.model.Edge
import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING
@@ -133,6 +134,7 @@ constructor(
private val screenOffAnimationController: ScreenOffAnimationController,
private val aodBurnInViewModel: AodBurnInViewModel,
private val shadeInteractor: ShadeInteractor,
+ wallpaperFocalAreaInteractor: WallpaperFocalAreaInteractor,
) {
val burnInLayerVisibility: Flow<Int> =
keyguardTransitionInteractor.startedKeyguardTransitionStep
@@ -362,6 +364,8 @@ constructor(
initialValue = AnimatedValue.NotAnimating(false),
)
+ val shouldSendFocalArea = wallpaperFocalAreaInteractor.shouldSendFocalArea
+
fun onNotificationContainerBoundsChanged(top: Float, bottom: Float, animate: Boolean = false) {
keyguardInteractor.setNotificationContainerBounds(
NotificationContainerBounds(top = top, bottom = bottom, isAnimated = animate)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModel.kt
index 44c4c8723dcb..e25e4e5af425 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModel.kt
@@ -24,7 +24,6 @@ import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER
import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
import com.android.systemui.keyguard.ui.transitions.BlurConfig
-import com.android.systemui.keyguard.ui.transitions.BlurConfig.Companion.maxBlurRadiusToNotificationPanelBlurRadius
import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
import com.android.systemui.keyguard.ui.transitions.PrimaryBouncerTransition
import com.android.systemui.scene.shared.flag.SceneContainerFlag
@@ -88,9 +87,7 @@ constructor(
shadeDependentFlows.transitionFlow(
flowWhenShadeIsNotExpanded = emptyFlow(),
flowWhenShadeIsExpanded =
- transitionAnimation.immediatelyTransitionTo(
- blurConfig.maxBlurRadiusPx.maxBlurRadiusToNotificationPanelBlurRadius()
- ),
+ transitionAnimation.immediatelyTransitionTo(blurConfig.maxBlurRadiusPx),
)
} else {
emptyFlow()
@@ -110,7 +107,11 @@ constructor(
override val windowBlurRadius: Flow<Float> =
shadeDependentFlows.transitionFlow(
flowWhenShadeIsExpanded =
- transitionAnimation.immediatelyTransitionTo(blurConfig.maxBlurRadiusPx),
+ if (Flags.notificationShadeBlur()) {
+ transitionAnimation.immediatelyTransitionTo(blurConfig.maxBlurRadiusPx)
+ } else {
+ emptyFlow()
+ },
flowWhenShadeIsNotExpanded =
transitionAnimation.sharedFlow(
duration = FromLockscreenTransitionInteractor.TO_PRIMARY_BOUNCER_DURATION,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToPrimaryBouncerTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToPrimaryBouncerTransitionViewModel.kt
index 4d3e27265cea..3c126aa23fef 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToPrimaryBouncerTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToPrimaryBouncerTransitionViewModel.kt
@@ -16,6 +16,7 @@
package com.android.systemui.keyguard.ui.viewmodel
+import com.android.systemui.Flags
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.domain.interactor.FromOccludedTransitionInteractor
import com.android.systemui.keyguard.shared.model.Edge
@@ -26,12 +27,16 @@ import com.android.systemui.keyguard.ui.transitions.BlurConfig
import com.android.systemui.keyguard.ui.transitions.PrimaryBouncerTransition
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.emptyFlow
@SysUISingleton
class OccludedToPrimaryBouncerTransitionViewModel
@Inject
-constructor(blurConfig: BlurConfig, animationFlow: KeyguardTransitionAnimationFlow) :
- PrimaryBouncerTransition {
+constructor(
+ shadeDependentFlows: ShadeDependentFlows,
+ blurConfig: BlurConfig,
+ animationFlow: KeyguardTransitionAnimationFlow,
+) : PrimaryBouncerTransition {
private val transitionAnimation =
animationFlow
.setup(
@@ -41,8 +46,21 @@ constructor(blurConfig: BlurConfig, animationFlow: KeyguardTransitionAnimationFl
.setupWithoutSceneContainer(edge = Edge.create(OCCLUDED, PRIMARY_BOUNCER))
override val windowBlurRadius: Flow<Float> =
- transitionAnimation.immediatelyTransitionTo(blurConfig.maxBlurRadiusPx)
+ shadeDependentFlows.transitionFlow(
+ flowWhenShadeIsExpanded =
+ if (Flags.notificationShadeBlur()) {
+ transitionAnimation.immediatelyTransitionTo(blurConfig.maxBlurRadiusPx)
+ } else {
+ emptyFlow()
+ },
+ flowWhenShadeIsNotExpanded =
+ transitionAnimation.immediatelyTransitionTo(blurConfig.maxBlurRadiusPx),
+ )
override val notificationBlurRadius: Flow<Float> =
- transitionAnimation.immediatelyTransitionTo(0.0f)
+ shadeDependentFlows.transitionFlow(
+ flowWhenShadeIsExpanded =
+ transitionAnimation.immediatelyTransitionTo(blurConfig.maxBlurRadiusPx),
+ flowWhenShadeIsNotExpanded = emptyFlow(),
+ )
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModel.kt
index c53a408a88e1..9b01803f1fd5 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModel.kt
@@ -18,6 +18,7 @@ package com.android.systemui.keyguard.ui.viewmodel
import android.util.MathUtils
import com.android.app.animation.Interpolators.EMPHASIZED_ACCELERATE
+import com.android.systemui.Flags
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.domain.interactor.FromPrimaryBouncerTransitionInteractor
import com.android.systemui.keyguard.shared.model.Edge
@@ -32,6 +33,7 @@ import javax.inject.Inject
import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.emptyFlow
/**
* Breaks down PRIMARY BOUNCER->LOCKSCREEN transition into discrete steps for corresponding views to
@@ -78,7 +80,11 @@ constructor(
override val windowBlurRadius: Flow<Float> =
shadeDependentFlows.transitionFlow(
flowWhenShadeIsExpanded =
- transitionAnimation.immediatelyTransitionTo(blurConfig.maxBlurRadiusPx),
+ if (Flags.notificationShadeBlur()) {
+ transitionAnimation.immediatelyTransitionTo(blurConfig.maxBlurRadiusPx)
+ } else {
+ emptyFlow()
+ },
flowWhenShadeIsNotExpanded =
transitionAnimation.sharedFlow(
duration = FromPrimaryBouncerTransitionInteractor.TO_LOCKSCREEN_DURATION,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToOccludedTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToOccludedTransitionViewModel.kt
index fe1708efea2f..0f0e7b6faa66 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToOccludedTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToOccludedTransitionViewModel.kt
@@ -16,6 +16,7 @@
package com.android.systemui.keyguard.ui.viewmodel
+import com.android.systemui.Flags
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.domain.interactor.FromPrimaryBouncerTransitionInteractor
import com.android.systemui.keyguard.shared.model.Edge
@@ -26,12 +27,16 @@ import com.android.systemui.keyguard.ui.transitions.BlurConfig
import com.android.systemui.keyguard.ui.transitions.PrimaryBouncerTransition
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.emptyFlow
@SysUISingleton
class PrimaryBouncerToOccludedTransitionViewModel
@Inject
-constructor(private val blurConfig: BlurConfig, animationFlow: KeyguardTransitionAnimationFlow) :
- PrimaryBouncerTransition {
+constructor(
+ shadeDependentFlows: ShadeDependentFlows,
+ blurConfig: BlurConfig,
+ animationFlow: KeyguardTransitionAnimationFlow,
+) : PrimaryBouncerTransition {
private val transitionAnimation =
animationFlow
.setup(
@@ -41,7 +46,16 @@ constructor(private val blurConfig: BlurConfig, animationFlow: KeyguardTransitio
.setupWithoutSceneContainer(edge = Edge.create(PRIMARY_BOUNCER, OCCLUDED))
override val windowBlurRadius: Flow<Float> =
- transitionAnimation.immediatelyTransitionTo(blurConfig.minBlurRadiusPx)
+ shadeDependentFlows.transitionFlow(
+ flowWhenShadeIsExpanded =
+ if (Flags.notificationShadeBlur()) {
+ transitionAnimation.immediatelyTransitionTo(blurConfig.maxBlurRadiusPx)
+ } else {
+ emptyFlow()
+ },
+ flowWhenShadeIsNotExpanded =
+ transitionAnimation.immediatelyTransitionTo(blurConfig.minBlurRadiusPx),
+ )
override val notificationBlurRadius: Flow<Float> =
transitionAnimation.immediatelyTransitionTo(0.0f)
diff --git a/packages/SystemUI/src/com/android/systemui/log/table/TableLogBufferFactory.kt b/packages/SystemUI/src/com/android/systemui/log/table/TableLogBufferFactory.kt
index 425e674ec804..fb69b793d975 100644
--- a/packages/SystemUI/src/com/android/systemui/log/table/TableLogBufferFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/table/TableLogBufferFactory.kt
@@ -21,6 +21,7 @@ import com.android.systemui.dump.DumpManager
import com.android.systemui.log.LogBufferHelper.Companion.adjustMaxSize
import com.android.systemui.log.LogcatEchoTracker
import com.android.systemui.util.time.SystemClock
+import java.util.concurrent.ConcurrentHashMap
import javax.inject.Inject
@SysUISingleton
@@ -31,7 +32,7 @@ constructor(
private val systemClock: SystemClock,
private val logcatEchoTracker: LogcatEchoTracker,
) {
- private val existingBuffers = mutableMapOf<String, TableLogBuffer>()
+ private val existingBuffers = ConcurrentHashMap<String, TableLogBuffer>()
/**
* Creates a new [TableLogBuffer]. This method should only be called from static contexts, where
@@ -42,17 +43,9 @@ constructor(
* @param maxSize the buffer max size. See [adjustMaxSize]
* @return a new [TableLogBuffer] registered with [DumpManager]
*/
- fun create(
- name: String,
- maxSize: Int,
- ): TableLogBuffer {
+ fun create(name: String, maxSize: Int): TableLogBuffer {
val tableBuffer =
- TableLogBuffer(
- adjustMaxSize(maxSize),
- name,
- systemClock,
- logcatEchoTracker,
- )
+ TableLogBuffer(adjustMaxSize(maxSize), name, systemClock, logcatEchoTracker)
dumpManager.registerTableLogBuffer(name, tableBuffer)
return tableBuffer
}
@@ -66,13 +59,12 @@ constructor(
*
* @return a [TableLogBuffer] suitable for reuse
*/
- fun getOrCreate(
- name: String,
- maxSize: Int,
- ): TableLogBuffer =
- existingBuffers.getOrElse(name) {
- val buffer = create(name, maxSize)
- existingBuffers[name] = buffer
- buffer
+ fun getOrCreate(name: String, maxSize: Int): TableLogBuffer =
+ synchronized(existingBuffers) {
+ existingBuffers.getOrElse(name) {
+ val buffer = create(name, maxSize)
+ existingBuffers[name] = buffer
+ buffer
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/lowlightclock/AmbientLightModeMonitor.kt b/packages/SystemUI/src/com/android/systemui/lowlightclock/AmbientLightModeMonitor.kt
new file mode 100644
index 000000000000..ece97bd27df7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/lowlightclock/AmbientLightModeMonitor.kt
@@ -0,0 +1,137 @@
+/*
+ * 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.lowlightclock
+
+import android.annotation.IntDef
+import android.hardware.Sensor
+import android.hardware.SensorEvent
+import android.hardware.SensorEventListener
+import android.hardware.SensorManager
+import android.util.Log
+import com.android.systemui.Dumpable
+import com.android.systemui.lowlightclock.dagger.LowLightModule.LIGHT_SENSOR
+import com.android.systemui.util.sensors.AsyncSensorManager
+import java.io.PrintWriter
+import java.util.Optional
+import javax.inject.Inject
+import javax.inject.Named
+
+/**
+ * Monitors ambient light signals, applies a debouncing algorithm, and produces the current ambient
+ * light mode.
+ *
+ * @property algorithm the debounce algorithm which transforms light sensor events into an ambient
+ * light mode.
+ * @property sensorManager the sensor manager used to register sensor event updates.
+ */
+class AmbientLightModeMonitor
+@Inject
+constructor(
+ private val algorithm: Optional<DebounceAlgorithm>,
+ private val sensorManager: AsyncSensorManager,
+ @Named(LIGHT_SENSOR) private val lightSensor: Optional<Sensor>,
+) : Dumpable {
+ companion object {
+ private const val TAG = "AmbientLightModeMonitor"
+ private val DEBUG = Log.isLoggable(TAG, Log.DEBUG)
+
+ const val AMBIENT_LIGHT_MODE_LIGHT = 0
+ const val AMBIENT_LIGHT_MODE_DARK = 1
+ const val AMBIENT_LIGHT_MODE_UNDECIDED = 2
+ }
+
+ // Represents all ambient light modes.
+ @Retention(AnnotationRetention.SOURCE)
+ @IntDef(AMBIENT_LIGHT_MODE_LIGHT, AMBIENT_LIGHT_MODE_DARK, AMBIENT_LIGHT_MODE_UNDECIDED)
+ annotation class AmbientLightMode
+
+ /**
+ * Start monitoring the current ambient light mode.
+ *
+ * @param callback callback that gets triggered when the ambient light mode changes.
+ */
+ fun start(callback: Callback) {
+ if (DEBUG) Log.d(TAG, "start monitoring ambient light mode")
+
+ if (lightSensor.isEmpty) {
+ if (DEBUG) Log.w(TAG, "light sensor not available")
+ return
+ }
+
+ if (algorithm.isEmpty) {
+ if (DEBUG) Log.w(TAG, "debounce algorithm not available")
+ return
+ }
+
+ algorithm.get().start(callback)
+ sensorManager.registerListener(
+ mSensorEventListener,
+ lightSensor.get(),
+ SensorManager.SENSOR_DELAY_NORMAL,
+ )
+ }
+
+ /** Stop monitoring the current ambient light mode. */
+ fun stop() {
+ if (DEBUG) Log.d(TAG, "stop monitoring ambient light mode")
+
+ if (algorithm.isPresent) {
+ algorithm.get().stop()
+ }
+ sensorManager.unregisterListener(mSensorEventListener)
+ }
+
+ override fun dump(pw: PrintWriter, args: Array<out String>) {
+ pw.println()
+ pw.println("Ambient light mode monitor:")
+ pw.println(" lightSensor=$lightSensor")
+ pw.println()
+ }
+
+ private val mSensorEventListener: SensorEventListener =
+ object : SensorEventListener {
+ override fun onSensorChanged(event: SensorEvent) {
+ if (event.values.isEmpty()) {
+ if (DEBUG) Log.w(TAG, "SensorEvent doesn't have any value")
+ return
+ }
+
+ if (algorithm.isPresent) {
+ algorithm.get().onUpdateLightSensorEvent(event.values[0])
+ }
+ }
+
+ override fun onAccuracyChanged(sensor: Sensor, accuracy: Int) {
+ // Do nothing.
+ }
+ }
+
+ /** Interface of the ambient light mode callback, which gets triggered when the mode changes. */
+ interface Callback {
+ fun onChange(@AmbientLightMode mode: Int)
+ }
+
+ /** Interface of the algorithm that transforms light sensor events to an ambient light mode. */
+ interface DebounceAlgorithm {
+ // Setting Callback to nullable so mockito can verify without throwing NullPointerException.
+ fun start(callback: Callback?)
+
+ fun stop()
+
+ fun onUpdateLightSensorEvent(value: Float)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/lowlightclock/ChargingStatusProvider.java b/packages/SystemUI/src/com/android/systemui/lowlightclock/ChargingStatusProvider.java
new file mode 100644
index 000000000000..8cc399b0a22b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/lowlightclock/ChargingStatusProvider.java
@@ -0,0 +1,258 @@
+/*
+ * 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.lowlightclock;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.os.BatteryManager;
+import android.os.RemoteException;
+import android.text.format.Formatter;
+import android.util.Log;
+
+import com.android.internal.app.IBatteryStats;
+import com.android.internal.util.Preconditions;
+import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.keyguard.KeyguardUpdateMonitorCallback;
+import com.android.settingslib.fuelgauge.BatteryStatus;
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.res.R;
+
+import java.text.NumberFormat;
+
+import javax.inject.Inject;
+
+/**
+ * Provides charging status as a string to a registered callback such that it can be displayed to
+ * the user (e.g. on the low-light clock).
+ * TODO(b/223681352): Make this code shareable with {@link KeyguardIndicationController}.
+ */
+public class ChargingStatusProvider {
+ private static final String TAG = "ChargingStatusProvider";
+
+ private final Resources mResources;
+ private final Context mContext;
+ private final IBatteryStats mBatteryInfo;
+ private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+ private final BatteryState mBatteryState = new BatteryState();
+ // This callback is registered with KeyguardUpdateMonitor, which only keeps weak references to
+ // its callbacks. Therefore, an explicit reference needs to be kept here to avoid the
+ // callback being GC'd.
+ private ChargingStatusCallback mChargingStatusCallback;
+
+ private Callback mCallback;
+
+ @Inject
+ public ChargingStatusProvider(
+ Context context,
+ @Main Resources resources,
+ IBatteryStats iBatteryStats,
+ KeyguardUpdateMonitor keyguardUpdateMonitor) {
+ mContext = context;
+ mResources = resources;
+ mBatteryInfo = iBatteryStats;
+ mKeyguardUpdateMonitor = keyguardUpdateMonitor;
+ }
+
+ /**
+ * Start using the {@link ChargingStatusProvider}.
+ * @param callback A callback to be called when the charging status changes.
+ */
+ public void startUsing(Callback callback) {
+ Preconditions.checkState(
+ mCallback == null, "ChargingStatusProvider already started!");
+ mCallback = callback;
+ mChargingStatusCallback = new ChargingStatusCallback();
+ mKeyguardUpdateMonitor.registerCallback(mChargingStatusCallback);
+ reportStatusToCallback();
+ }
+
+ /**
+ * Stop using the {@link ChargingStatusProvider}.
+ */
+ public void stopUsing() {
+ mCallback = null;
+
+ if (mChargingStatusCallback != null) {
+ mKeyguardUpdateMonitor.removeCallback(mChargingStatusCallback);
+ mChargingStatusCallback = null;
+ }
+ }
+
+ private String computeChargingString() {
+ if (!mBatteryState.isValid()) {
+ return null;
+ }
+
+ int chargingId;
+
+ if (mBatteryState.isBatteryDefender()) {
+ return mResources.getString(
+ R.string.keyguard_plugged_in_charging_limited,
+ mBatteryState.getBatteryLevelAsPercentage());
+ } else if (mBatteryState.isPowerCharged()) {
+ return mResources.getString(R.string.keyguard_charged);
+ }
+
+ final long chargingTimeRemaining = mBatteryState.getChargingTimeRemaining(mBatteryInfo);
+ final boolean hasChargingTime = chargingTimeRemaining > 0;
+ if (mBatteryState.isPowerPluggedInWired()) {
+ switch (mBatteryState.getChargingSpeed(mContext)) {
+ case BatteryStatus.CHARGING_FAST:
+ chargingId = hasChargingTime
+ ? R.string.keyguard_indication_charging_time_fast
+ : R.string.keyguard_plugged_in_charging_fast;
+ break;
+ case BatteryStatus.CHARGING_SLOWLY:
+ chargingId = hasChargingTime
+ ? R.string.keyguard_indication_charging_time_slowly
+ : R.string.keyguard_plugged_in_charging_slowly;
+ break;
+ default:
+ chargingId = hasChargingTime
+ ? R.string.keyguard_indication_charging_time
+ : R.string.keyguard_plugged_in;
+ break;
+ }
+ } else if (mBatteryState.isPowerPluggedInWireless()) {
+ chargingId = hasChargingTime
+ ? R.string.keyguard_indication_charging_time_wireless
+ : R.string.keyguard_plugged_in_wireless;
+ } else if (mBatteryState.isPowerPluggedInDocked()) {
+ chargingId = hasChargingTime
+ ? R.string.keyguard_indication_charging_time_dock
+ : R.string.keyguard_plugged_in_dock;
+ } else {
+ chargingId = hasChargingTime
+ ? R.string.keyguard_indication_charging_time
+ : R.string.keyguard_plugged_in;
+ }
+
+ final String percentage = mBatteryState.getBatteryLevelAsPercentage();
+ if (hasChargingTime) {
+ final String chargingTimeFormatted =
+ Formatter.formatShortElapsedTimeRoundingUpToMinutes(
+ mContext, chargingTimeRemaining);
+ return mResources.getString(chargingId, chargingTimeFormatted,
+ percentage);
+ } else {
+ return mResources.getString(chargingId, percentage);
+ }
+ }
+
+ private void reportStatusToCallback() {
+ if (mCallback != null) {
+ final boolean shouldShowStatus =
+ mBatteryState.isPowerPluggedIn() || mBatteryState.isBatteryDefenderEnabled();
+ mCallback.onChargingStatusChanged(shouldShowStatus, computeChargingString());
+ }
+ }
+
+ private class ChargingStatusCallback extends KeyguardUpdateMonitorCallback {
+ @Override
+ public void onRefreshBatteryInfo(BatteryStatus status) {
+ mBatteryState.setBatteryStatus(status);
+ reportStatusToCallback();
+ }
+ }
+
+ /***
+ * A callback to be called when the charging status changes.
+ */
+ public interface Callback {
+ /***
+ * Called when the charging status changes.
+ * @param shouldShowStatus Whether or not to show a charging status message.
+ * @param statusMessage A charging status message.
+ */
+ void onChargingStatusChanged(boolean shouldShowStatus, String statusMessage);
+ }
+
+ /***
+ * A wrapper around {@link BatteryStatus} for fetching various properties of the current
+ * battery and charging state.
+ */
+ private static class BatteryState {
+ private BatteryStatus mBatteryStatus;
+
+ public void setBatteryStatus(BatteryStatus batteryStatus) {
+ mBatteryStatus = batteryStatus;
+ }
+
+ public boolean isValid() {
+ return mBatteryStatus != null;
+ }
+
+ public long getChargingTimeRemaining(IBatteryStats batteryInfo) {
+ try {
+ return isPowerPluggedIn() ? batteryInfo.computeChargeTimeRemaining() : -1;
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error calling IBatteryStats: ", e);
+ return -1;
+ }
+ }
+
+ public boolean isBatteryDefenderEnabled() {
+ return isValid() && mBatteryStatus.isPluggedIn() && isBatteryDefender();
+ }
+
+ public boolean isBatteryDefender() {
+ return isValid() && mBatteryStatus.isBatteryDefender();
+ }
+
+ public int getBatteryLevel() {
+ return isValid() ? mBatteryStatus.level : 0;
+ }
+
+ public int getChargingSpeed(Context context) {
+ return isValid() ? mBatteryStatus.getChargingSpeed(context) : 0;
+ }
+
+ public boolean isPowerCharged() {
+ return isValid() && mBatteryStatus.isCharged();
+ }
+
+ public boolean isPowerPluggedIn() {
+ return isValid() && mBatteryStatus.isPluggedIn() && isChargingOrFull();
+ }
+
+ public boolean isPowerPluggedInWired() {
+ return isValid()
+ && mBatteryStatus.isPluggedInWired()
+ && isChargingOrFull();
+ }
+
+ public boolean isPowerPluggedInWireless() {
+ return isValid()
+ && mBatteryStatus.isPluggedInWireless()
+ && isChargingOrFull();
+ }
+
+ public boolean isPowerPluggedInDocked() {
+ return isValid() && mBatteryStatus.isPluggedInDock() && isChargingOrFull();
+ }
+
+ private boolean isChargingOrFull() {
+ return isValid()
+ && (mBatteryStatus.status == BatteryManager.BATTERY_STATUS_CHARGING
+ || mBatteryStatus.isCharged());
+ }
+
+ private String getBatteryLevelAsPercentage() {
+ return NumberFormat.getPercentInstance().format(getBatteryLevel() / 100f);
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/lowlightclock/DirectBootCondition.kt b/packages/SystemUI/src/com/android/systemui/lowlightclock/DirectBootCondition.kt
new file mode 100644
index 000000000000..4c1da0198498
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/lowlightclock/DirectBootCondition.kt
@@ -0,0 +1,60 @@
+/*
+ * 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.lowlightclock
+
+import android.content.Intent
+import android.content.IntentFilter
+import android.os.UserManager
+import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.shared.condition.Condition
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.flow.cancellable
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.launch
+
+class DirectBootCondition
+@Inject
+constructor(
+ broadcastDispatcher: BroadcastDispatcher,
+ private val userManager: UserManager,
+ @Application private val coroutineScope: CoroutineScope,
+) : Condition(coroutineScope) {
+ private var job: Job? = null
+ private val directBootFlow =
+ broadcastDispatcher
+ .broadcastFlow(IntentFilter(Intent.ACTION_USER_UNLOCKED))
+ .map { !userManager.isUserUnlocked }
+ .cancellable()
+ .distinctUntilChanged()
+
+ override fun start() {
+ job = coroutineScope.launch { directBootFlow.collect { updateCondition(it) } }
+ updateCondition(!userManager.isUserUnlocked)
+ }
+
+ override fun stop() {
+ job?.cancel()
+ }
+
+ override fun getStartStrategy(): Int {
+ return START_EAGERLY
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/lowlightclock/ForceLowLightCondition.java b/packages/SystemUI/src/com/android/systemui/lowlightclock/ForceLowLightCondition.java
new file mode 100644
index 000000000000..7f21d0707f63
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/lowlightclock/ForceLowLightCondition.java
@@ -0,0 +1,137 @@
+/*
+ * 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.lowlightclock;
+
+import android.text.TextUtils;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+
+import com.android.systemui.dagger.qualifiers.Application;
+import com.android.systemui.shared.condition.Condition;
+import com.android.systemui.statusbar.commandline.Command;
+import com.android.systemui.statusbar.commandline.CommandRegistry;
+
+import kotlinx.coroutines.CoroutineScope;
+
+import java.io.PrintWriter;
+import java.util.List;
+
+import javax.inject.Inject;
+
+/**
+ * This condition registers for and fulfills cmd shell commands to force a device into or out of
+ * low-light conditions.
+ */
+public class ForceLowLightCondition extends Condition {
+ /**
+ * Command root
+ */
+ public static final String COMMAND_ROOT = "low-light";
+ /**
+ * Command for forcing device into low light.
+ */
+ public static final String COMMAND_ENABLE_LOW_LIGHT = "enable";
+
+ /**
+ * Command for preventing a device from entering low light.
+ */
+ public static final String COMMAND_DISABLE_LOW_LIGHT = "disable";
+
+ /**
+ * Command for clearing previously forced low-light conditions.
+ */
+ public static final String COMMAND_CLEAR_LOW_LIGHT = "clear";
+
+ private static final String TAG = "ForceLowLightCondition";
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+ /**
+ * Default Constructor.
+ *
+ * @param commandRegistry command registry to register commands with.
+ */
+ @Inject
+ public ForceLowLightCondition(
+ @Application CoroutineScope scope,
+ CommandRegistry commandRegistry
+ ) {
+ super(scope, null, true);
+
+ if (DEBUG) {
+ Log.d(TAG, "registering commands");
+ }
+ commandRegistry.registerCommand(COMMAND_ROOT, () -> new Command() {
+ @Override
+ public void execute(@NonNull PrintWriter pw, @NonNull List<String> args) {
+ if (args.size() != 1) {
+ pw.println("no command specified");
+ help(pw);
+ return;
+ }
+
+ final String cmd = args.get(0);
+
+ if (TextUtils.equals(cmd, COMMAND_ENABLE_LOW_LIGHT)) {
+ logAndPrint(pw, "forcing low light");
+ updateCondition(true);
+ } else if (TextUtils.equals(cmd, COMMAND_DISABLE_LOW_LIGHT)) {
+ logAndPrint(pw, "forcing to not enter low light");
+ updateCondition(false);
+ } else if (TextUtils.equals(cmd, COMMAND_CLEAR_LOW_LIGHT)) {
+ logAndPrint(pw, "clearing any forced low light");
+ clearCondition();
+ } else {
+ pw.println("invalid command");
+ help(pw);
+ }
+ }
+
+ @Override
+ public void help(@NonNull PrintWriter pw) {
+ pw.println("Usage: adb shell cmd statusbar low-light <cmd>");
+ pw.println("Supported commands:");
+ pw.println(" - enable");
+ pw.println(" forces device into low-light");
+ pw.println(" - disable");
+ pw.println(" forces device to not enter low-light");
+ pw.println(" - clear");
+ pw.println(" clears any previously forced state");
+ }
+
+ private void logAndPrint(PrintWriter pw, String message) {
+ pw.println(message);
+ if (DEBUG) {
+ Log.d(TAG, message);
+ }
+ }
+ });
+ }
+
+ @Override
+ protected void start() {
+ }
+
+ @Override
+ protected void stop() {
+ }
+
+ @Override
+ protected int getStartStrategy() {
+ return START_EAGERLY;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/lowlightclock/LowLightClockAnimationProvider.java b/packages/SystemUI/src/com/android/systemui/lowlightclock/LowLightClockAnimationProvider.java
new file mode 100644
index 000000000000..6de599803a57
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/lowlightclock/LowLightClockAnimationProvider.java
@@ -0,0 +1,127 @@
+/*
+ * 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.lowlightclock;
+
+import android.animation.Animator;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.view.View;
+import android.view.animation.Interpolator;
+
+import com.android.app.animation.Interpolators;
+import com.android.dream.lowlight.util.TruncatedInterpolator;
+import com.android.systemui.lowlightclock.dagger.LowLightModule;
+import com.android.systemui.statusbar.CrossFadeHelper;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+
+/***
+ * A class that provides the animations used by the low-light clock.
+ *
+ * The entry and exit animations are opposites, with the only difference being a delay before the
+ * text fades in on entry.
+ */
+public class LowLightClockAnimationProvider {
+ private final int mYTranslationAnimationInStartOffset;
+ private final long mYTranslationAnimationInDurationMillis;
+ private final long mAlphaAnimationInStartDelayMillis;
+ private final long mAlphaAnimationDurationMillis;
+
+ /**
+ * Custom interpolator used for the translate out animation, which uses an emphasized easing
+ * like the translate in animation, but is scaled to match the length of the alpha animation.
+ */
+ private final Interpolator mTranslationOutInterpolator;
+
+ @Inject
+ public LowLightClockAnimationProvider(
+ @Named(LowLightModule.Y_TRANSLATION_ANIMATION_OFFSET)
+ int yTranslationAnimationInStartOffset,
+ @Named(LowLightModule.Y_TRANSLATION_ANIMATION_DURATION_MILLIS)
+ long yTranslationAnimationInDurationMillis,
+ @Named(LowLightModule.ALPHA_ANIMATION_IN_START_DELAY_MILLIS)
+ long alphaAnimationInStartDelayMillis,
+ @Named(LowLightModule.ALPHA_ANIMATION_DURATION_MILLIS)
+ long alphaAnimationDurationMillis) {
+ mYTranslationAnimationInStartOffset = yTranslationAnimationInStartOffset;
+ mYTranslationAnimationInDurationMillis = yTranslationAnimationInDurationMillis;
+ mAlphaAnimationInStartDelayMillis = alphaAnimationInStartDelayMillis;
+ mAlphaAnimationDurationMillis = alphaAnimationDurationMillis;
+
+ mTranslationOutInterpolator = new TruncatedInterpolator(Interpolators.EMPHASIZED,
+ /*originalDuration=*/ mYTranslationAnimationInDurationMillis,
+ /*newDuration=*/ mAlphaAnimationDurationMillis);
+ }
+
+ /***
+ * Provides an animation for when the given views become visible.
+ * @param views Any number of views to animate in together.
+ */
+ public Animator provideAnimationIn(View... views) {
+ final AnimatorSet animatorSet = new AnimatorSet();
+
+ for (View view : views) {
+ if (view == null) continue;
+ // Set the alpha to 0 to start because the alpha animation has a start delay.
+ CrossFadeHelper.fadeOut(view, 0f, false);
+
+ final Animator alphaAnimator =
+ ObjectAnimator.ofFloat(view, View.ALPHA, 1f);
+ alphaAnimator.setStartDelay(mAlphaAnimationInStartDelayMillis);
+ alphaAnimator.setDuration(mAlphaAnimationDurationMillis);
+ alphaAnimator.setInterpolator(Interpolators.LINEAR);
+
+ final Animator positionAnimator = ObjectAnimator
+ .ofFloat(view, View.TRANSLATION_Y, mYTranslationAnimationInStartOffset, 0f);
+ positionAnimator.setDuration(mYTranslationAnimationInDurationMillis);
+ positionAnimator.setInterpolator(Interpolators.EMPHASIZED);
+
+ // The position animator must be started first since the alpha animator has a start
+ // delay.
+ animatorSet.playTogether(positionAnimator, alphaAnimator);
+ }
+
+ return animatorSet;
+ }
+
+ /***
+ * Provides an animation for when the given views are going out of view.
+ * @param views Any number of views to animate out.
+ */
+ public Animator provideAnimationOut(View... views) {
+ final AnimatorSet animatorSet = new AnimatorSet();
+
+ for (View view : views) {
+ if (view == null) continue;
+ final Animator alphaAnimator =
+ ObjectAnimator.ofFloat(view, View.ALPHA, 0f);
+ alphaAnimator.setDuration(mAlphaAnimationDurationMillis);
+ alphaAnimator.setInterpolator(Interpolators.LINEAR);
+
+ final Animator positionAnimator = ObjectAnimator
+ .ofFloat(view, View.TRANSLATION_Y, mYTranslationAnimationInStartOffset);
+ // Use the same duration as the alpha animation plus our custom interpolator.
+ positionAnimator.setDuration(mAlphaAnimationDurationMillis);
+ positionAnimator.setInterpolator(mTranslationOutInterpolator);
+ animatorSet.playTogether(alphaAnimator, positionAnimator);
+ }
+
+ return animatorSet;
+ }
+
+}
diff --git a/packages/SystemUI/src/com/android/systemui/lowlightclock/LowLightCondition.java b/packages/SystemUI/src/com/android/systemui/lowlightclock/LowLightCondition.java
new file mode 100644
index 000000000000..e91be5028777
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/lowlightclock/LowLightCondition.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 com.android.systemui.lowlightclock;
+
+import com.android.internal.logging.UiEventLogger;
+import com.android.systemui.dagger.qualifiers.Application;
+import com.android.systemui.shared.condition.Condition;
+
+import kotlinx.coroutines.CoroutineScope;
+
+import javax.inject.Inject;
+
+/**
+ * Condition for monitoring when the device enters and exits lowlight mode.
+ */
+public class LowLightCondition extends Condition {
+ private final AmbientLightModeMonitor mAmbientLightModeMonitor;
+ private final UiEventLogger mUiEventLogger;
+
+ @Inject
+ public LowLightCondition(@Application CoroutineScope scope,
+ AmbientLightModeMonitor ambientLightModeMonitor,
+ UiEventLogger uiEventLogger) {
+ super(scope);
+ mAmbientLightModeMonitor = ambientLightModeMonitor;
+ mUiEventLogger = uiEventLogger;
+ }
+
+ @Override
+ protected void start() {
+ mAmbientLightModeMonitor.start(this::onLowLightChanged);
+ }
+
+ @Override
+ protected void stop() {
+ mAmbientLightModeMonitor.stop();
+
+ // Reset condition met to false.
+ updateCondition(false);
+ }
+
+ @Override
+ protected int getStartStrategy() {
+ // As this condition keeps the lowlight sensor active, it should only run when needed.
+ return START_WHEN_NEEDED;
+ }
+
+ private void onLowLightChanged(int lowLightMode) {
+ if (lowLightMode == AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_UNDECIDED) {
+ // Ignore undecided mode changes.
+ return;
+ }
+
+ final boolean isLowLight = lowLightMode == AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_DARK;
+ if (isLowLight == isConditionMet()) {
+ // No change in condition, don't do anything.
+ return;
+ }
+ mUiEventLogger.log(isLowLight ? LowLightDockEvent.AMBIENT_LIGHT_TO_DARK
+ : LowLightDockEvent.AMBIENT_LIGHT_TO_LIGHT);
+ updateCondition(isLowLight);
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/NotificationsShadeUserActionsViewModelKosmos.kt b/packages/SystemUI/src/com/android/systemui/lowlightclock/LowLightDisplayController.kt
index 6345c4076412..9a9d813b18c5 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/NotificationsShadeUserActionsViewModelKosmos.kt
+++ b/packages/SystemUI/src/com/android/systemui/lowlightclock/LowLightDisplayController.kt
@@ -14,11 +14,10 @@
* limitations under the License.
*/
-package com.android.systemui.shade.ui.viewmodel
+package com.android.systemui.lowlightclock
-import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.kosmos.Kosmos.Fixture
-import com.android.systemui.notifications.ui.viewmodel.NotificationsShadeUserActionsViewModel
+interface LowLightDisplayController {
+ fun isDisplayBrightnessModeSupported(): Boolean
-val Kosmos.notificationsShadeUserActionsViewModel:
- NotificationsShadeUserActionsViewModel by Fixture { NotificationsShadeUserActionsViewModel() }
+ fun setDisplayBrightnessModeEnabled(enabled: Boolean)
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderHapticsViewBinderKosmos.kt b/packages/SystemUI/src/com/android/systemui/lowlightclock/LowLightDockEvent.kt
index d6845b1ff7e3..b99aeb6eeacc 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderHapticsViewBinderKosmos.kt
+++ b/packages/SystemUI/src/com/android/systemui/lowlightclock/LowLightDockEvent.kt
@@ -14,20 +14,18 @@
* limitations under the License.
*/
-package com.android.systemui.volume.dialog.sliders.ui
+package com.android.systemui.lowlightclock
-import com.android.systemui.haptics.msdl.msdlPlayer
-import com.android.systemui.haptics.vibratorHelper
-import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.util.time.systemClock
-import com.android.systemui.volume.dialog.sliders.ui.viewmodel.volumeDialogSliderInputEventsViewModel
+import com.android.internal.logging.UiEvent
+import com.android.internal.logging.UiEventLogger
-val Kosmos.volumeDialogSliderHapticsViewBinder by
- Kosmos.Fixture {
- VolumeDialogSliderHapticsViewBinder(
- volumeDialogSliderInputEventsViewModel,
- vibratorHelper,
- msdlPlayer,
- systemClock,
- )
+enum class LowLightDockEvent(private val id: Int) : UiEventLogger.UiEventEnum {
+ @UiEvent(doc = "Ambient light changed from light to dark") AMBIENT_LIGHT_TO_DARK(999),
+ @UiEvent(doc = "The low light mode has started") LOW_LIGHT_STARTED(1000),
+ @UiEvent(doc = "Ambient light changed from dark to light") AMBIENT_LIGHT_TO_LIGHT(1001),
+ @UiEvent(doc = "The low light mode has stopped") LOW_LIGHT_STOPPED(1002);
+
+ override fun getId(): Int {
+ return id
}
+}
diff --git a/packages/SystemUI/src/com/android/systemui/lowlightclock/LowLightLogger.kt b/packages/SystemUI/src/com/android/systemui/lowlightclock/LowLightLogger.kt
new file mode 100644
index 000000000000..11d75215edf5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/lowlightclock/LowLightLogger.kt
@@ -0,0 +1,28 @@
+/*
+ * 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.lowlightclock
+
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.LogLevel
+import com.android.systemui.lowlightclock.dagger.LowLightLog
+import javax.inject.Inject
+
+/** Logs to a {@link LogBuffer} anything related to low-light features. */
+class LowLightLogger @Inject constructor(@LowLightLog private val buffer: LogBuffer) {
+ /** Logs a debug message to the buffer. */
+ fun d(tag: String, message: String) = buffer.log(tag, LogLevel.DEBUG, message)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/lowlightclock/LowLightMonitor.java b/packages/SystemUI/src/com/android/systemui/lowlightclock/LowLightMonitor.java
new file mode 100644
index 000000000000..912ace7675d5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/lowlightclock/LowLightMonitor.java
@@ -0,0 +1,133 @@
+/*
+ * 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.lowlightclock;
+
+import static com.android.dream.lowlight.LowLightDreamManager.AMBIENT_LIGHT_MODE_LOW_LIGHT;
+import static com.android.dream.lowlight.LowLightDreamManager.AMBIENT_LIGHT_MODE_REGULAR;
+import static com.android.systemui.dreams.dagger.DreamModule.LOW_LIGHT_DREAM_SERVICE;
+import static com.android.systemui.keyguard.ScreenLifecycle.SCREEN_ON;
+import static com.android.systemui.lowlightclock.dagger.LowLightModule.LOW_LIGHT_PRECONDITIONS;
+
+import android.content.ComponentName;
+import android.content.pm.PackageManager;
+
+import androidx.annotation.Nullable;
+
+import com.android.dream.lowlight.LowLightDreamManager;
+import com.android.systemui.dagger.qualifiers.SystemUser;
+import com.android.systemui.keyguard.ScreenLifecycle;
+import com.android.systemui.shared.condition.Condition;
+import com.android.systemui.shared.condition.Monitor;
+import com.android.systemui.util.condition.ConditionalCoreStartable;
+
+import dagger.Lazy;
+
+import java.util.Set;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+
+/**
+ * Tracks environment (low-light or not) in order to correctly show or hide a low-light clock while
+ * dreaming.
+ */
+public class LowLightMonitor extends ConditionalCoreStartable implements Monitor.Callback,
+ ScreenLifecycle.Observer {
+ private static final String TAG = "LowLightMonitor";
+
+ private final Lazy<LowLightDreamManager> mLowLightDreamManager;
+ private final Monitor mConditionsMonitor;
+ private final Lazy<Set<Condition>> mLowLightConditions;
+ private Monitor.Subscription.Token mSubscriptionToken;
+ private ScreenLifecycle mScreenLifecycle;
+ private final LowLightLogger mLogger;
+
+ private final ComponentName mLowLightDreamService;
+
+ private final PackageManager mPackageManager;
+
+ @Inject
+ public LowLightMonitor(Lazy<LowLightDreamManager> lowLightDreamManager,
+ @SystemUser Monitor conditionsMonitor,
+ @Named(LOW_LIGHT_PRECONDITIONS) Lazy<Set<Condition>> lowLightConditions,
+ ScreenLifecycle screenLifecycle,
+ LowLightLogger lowLightLogger,
+ @Nullable @Named(LOW_LIGHT_DREAM_SERVICE) ComponentName lowLightDreamService,
+ PackageManager packageManager) {
+ super(conditionsMonitor);
+ mLowLightDreamManager = lowLightDreamManager;
+ mConditionsMonitor = conditionsMonitor;
+ mLowLightConditions = lowLightConditions;
+ mScreenLifecycle = screenLifecycle;
+ mLogger = lowLightLogger;
+ mLowLightDreamService = lowLightDreamService;
+ mPackageManager = packageManager;
+ }
+
+ @Override
+ public void onConditionsChanged(boolean allConditionsMet) {
+ mLogger.d(TAG, "Low light enabled: " + allConditionsMet);
+
+ mLowLightDreamManager.get().setAmbientLightMode(allConditionsMet
+ ? AMBIENT_LIGHT_MODE_LOW_LIGHT : AMBIENT_LIGHT_MODE_REGULAR);
+ }
+
+ @Override
+ public void onScreenTurnedOn() {
+ if (mSubscriptionToken == null) {
+ mLogger.d(TAG, "Screen turned on. Subscribing to low light conditions.");
+
+ mSubscriptionToken = mConditionsMonitor.addSubscription(
+ new Monitor.Subscription.Builder(this)
+ .addConditions(mLowLightConditions.get())
+ .build());
+ }
+ }
+
+
+ @Override
+ public void onScreenTurnedOff() {
+ if (mSubscriptionToken != null) {
+ mLogger.d(TAG, "Screen turned off. Removing subscription to low light conditions.");
+
+ mConditionsMonitor.removeSubscription(mSubscriptionToken);
+ mSubscriptionToken = null;
+ }
+ }
+
+ @Override
+ protected void onStart() {
+ if (mLowLightDreamService != null) {
+ // Note that the dream service is disabled by default. This prevents the dream from
+ // appearing in settings on devices that don't have it explicitly excluded (done in
+ // the settings overlay). Therefore, the component is enabled if it is to be used
+ // here.
+ mPackageManager.setComponentEnabledSetting(
+ mLowLightDreamService,
+ PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
+ PackageManager.DONT_KILL_APP
+ );
+ } else {
+ // If there is no low light dream service, do not observe conditions.
+ return;
+ }
+
+ mScreenLifecycle.addObserver(this);
+ if (mScreenLifecycle.getScreenState() == SCREEN_ON) {
+ onScreenTurnedOn();
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/lowlightclock/ScreenSaverEnabledCondition.java b/packages/SystemUI/src/com/android/systemui/lowlightclock/ScreenSaverEnabledCondition.java
new file mode 100644
index 000000000000..fd6ce1762a28
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/lowlightclock/ScreenSaverEnabledCondition.java
@@ -0,0 +1,87 @@
+/*
+ * 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.lowlightclock;
+
+import android.content.res.Resources;
+import android.database.ContentObserver;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.util.Log;
+
+import com.android.systemui.dagger.qualifiers.Application;
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.shared.condition.Condition;
+import com.android.systemui.util.settings.SecureSettings;
+
+import kotlinx.coroutines.CoroutineScope;
+
+import javax.inject.Inject;
+
+/**
+ * Condition for monitoring if the screensaver setting is enabled.
+ */
+public class ScreenSaverEnabledCondition extends Condition {
+ private static final String TAG = ScreenSaverEnabledCondition.class.getSimpleName();
+
+ private final boolean mScreenSaverEnabledByDefaultConfig;
+ private final SecureSettings mSecureSettings;
+
+ private final ContentObserver mScreenSaverSettingObserver = new ContentObserver(null) {
+ @Override
+ public void onChange(boolean selfChange) {
+ updateScreenSaverEnabledSetting();
+ }
+ };
+
+ @Inject
+ public ScreenSaverEnabledCondition(@Application CoroutineScope scope, @Main Resources resources,
+ SecureSettings secureSettings) {
+ super(scope);
+ mScreenSaverEnabledByDefaultConfig = resources.getBoolean(
+ com.android.internal.R.bool.config_dreamsEnabledByDefault);
+ mSecureSettings = secureSettings;
+ }
+
+ @Override
+ protected void start() {
+ mSecureSettings.registerContentObserverForUserSync(
+ Settings.Secure.SCREENSAVER_ENABLED,
+ mScreenSaverSettingObserver, UserHandle.USER_CURRENT);
+ updateScreenSaverEnabledSetting();
+ }
+
+ @Override
+ protected void stop() {
+ mSecureSettings.unregisterContentObserverSync(mScreenSaverSettingObserver);
+ }
+
+ @Override
+ protected int getStartStrategy() {
+ return START_EAGERLY;
+ }
+
+ private void updateScreenSaverEnabledSetting() {
+ final boolean enabled = mSecureSettings.getIntForUser(
+ Settings.Secure.SCREENSAVER_ENABLED,
+ mScreenSaverEnabledByDefaultConfig ? 1 : 0,
+ UserHandle.USER_CURRENT) != 0;
+ if (!enabled) {
+ Log.i(TAG, "Disabling low-light clock because screen saver has been disabled");
+ }
+ updateCondition(enabled);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/lowlightclock/dagger/LowLightLog.kt b/packages/SystemUI/src/com/android/systemui/lowlightclock/dagger/LowLightLog.kt
new file mode 100644
index 000000000000..0819664c921c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/lowlightclock/dagger/LowLightLog.kt
@@ -0,0 +1,22 @@
+/*
+ * 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.lowlightclock.dagger
+
+import javax.inject.Qualifier
+
+/** A [com.android.systemui.log.LogBuffer] for logging related to low light features. */
+@Qualifier @MustBeDocumented @Retention(AnnotationRetention.RUNTIME) annotation class LowLightLog
diff --git a/packages/SystemUI/src/com/android/systemui/lowlightclock/dagger/LowLightModule.java b/packages/SystemUI/src/com/android/systemui/lowlightclock/dagger/LowLightModule.java
new file mode 100644
index 000000000000..c08be51c0699
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/lowlightclock/dagger/LowLightModule.java
@@ -0,0 +1,150 @@
+/*
+ * 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.lowlightclock.dagger;
+
+import android.content.res.Resources;
+import android.hardware.Sensor;
+
+import com.android.dream.lowlight.dagger.LowLightDreamModule;
+import com.android.systemui.CoreStartable;
+import com.android.systemui.communal.DeviceInactiveCondition;
+import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.log.LogBuffer;
+import com.android.systemui.log.LogBufferFactory;
+import com.android.systemui.lowlightclock.AmbientLightModeMonitor;
+import com.android.systemui.lowlightclock.DirectBootCondition;
+import com.android.systemui.lowlightclock.ForceLowLightCondition;
+import com.android.systemui.lowlightclock.LowLightCondition;
+import com.android.systemui.lowlightclock.LowLightDisplayController;
+import com.android.systemui.lowlightclock.LowLightMonitor;
+import com.android.systemui.lowlightclock.ScreenSaverEnabledCondition;
+import com.android.systemui.res.R;
+import com.android.systemui.shared.condition.Condition;
+
+import dagger.Binds;
+import dagger.BindsOptionalOf;
+import dagger.Module;
+import dagger.Provides;
+import dagger.multibindings.ClassKey;
+import dagger.multibindings.IntoMap;
+import dagger.multibindings.IntoSet;
+
+import javax.inject.Named;
+
+@Module(includes = LowLightDreamModule.class)
+public abstract class LowLightModule {
+ public static final String Y_TRANSLATION_ANIMATION_OFFSET =
+ "y_translation_animation_offset";
+ public static final String Y_TRANSLATION_ANIMATION_DURATION_MILLIS =
+ "y_translation_animation_duration_millis";
+ public static final String ALPHA_ANIMATION_IN_START_DELAY_MILLIS =
+ "alpha_animation_in_start_delay_millis";
+ public static final String ALPHA_ANIMATION_DURATION_MILLIS =
+ "alpha_animation_duration_millis";
+ public static final String LOW_LIGHT_PRECONDITIONS = "low_light_preconditions";
+ public static final String LIGHT_SENSOR = "low_light_monitor_light_sensor";
+
+
+ /**
+ * Provides a {@link LogBuffer} for logs related to low-light features.
+ */
+ @Provides
+ @SysUISingleton
+ @LowLightLog
+ public static LogBuffer provideLowLightLogBuffer(LogBufferFactory factory) {
+ return factory.create("LowLightLog", 250);
+ }
+
+ @Binds
+ @IntoSet
+ @Named(LOW_LIGHT_PRECONDITIONS)
+ abstract Condition bindScreenSaverEnabledCondition(ScreenSaverEnabledCondition condition);
+
+ @Provides
+ @IntoSet
+ @Named(com.android.systemui.lowlightclock.dagger.LowLightModule.LOW_LIGHT_PRECONDITIONS)
+ static Condition provideLowLightCondition(LowLightCondition lowLightCondition,
+ DirectBootCondition directBootCondition) {
+ // Start lowlight if we are either in lowlight or in direct boot. The ordering of the
+ // conditions matters here since we don't want to start the lowlight condition if
+ // we are in direct boot mode.
+ return directBootCondition.or(lowLightCondition);
+ }
+
+ @Binds
+ @IntoSet
+ @Named(LOW_LIGHT_PRECONDITIONS)
+ abstract Condition bindForceLowLightCondition(ForceLowLightCondition condition);
+
+ @Binds
+ @IntoSet
+ @Named(LOW_LIGHT_PRECONDITIONS)
+ abstract Condition bindDeviceInactiveCondition(DeviceInactiveCondition condition);
+
+ @BindsOptionalOf
+ abstract LowLightDisplayController bindsLowLightDisplayController();
+
+ @BindsOptionalOf
+ @Named(LIGHT_SENSOR)
+ abstract Sensor bindsLightSensor();
+
+ @BindsOptionalOf
+ abstract AmbientLightModeMonitor.DebounceAlgorithm bindsDebounceAlgorithm();
+
+ /**
+ *
+ */
+ @Provides
+ @Named(Y_TRANSLATION_ANIMATION_OFFSET)
+ static int providesAnimationInOffset(@Main Resources resources) {
+ return resources.getDimensionPixelOffset(
+ R.dimen.low_light_clock_translate_animation_offset);
+ }
+
+ /**
+ *
+ */
+ @Provides
+ @Named(Y_TRANSLATION_ANIMATION_DURATION_MILLIS)
+ static long providesAnimationDurationMillis(@Main Resources resources) {
+ return resources.getInteger(R.integer.low_light_clock_translate_animation_duration_ms);
+ }
+
+ /**
+ *
+ */
+ @Provides
+ @Named(ALPHA_ANIMATION_IN_START_DELAY_MILLIS)
+ static long providesAlphaAnimationInStartDelayMillis(@Main Resources resources) {
+ return resources.getInteger(R.integer.low_light_clock_alpha_animation_in_start_delay_ms);
+ }
+
+ /**
+ *
+ */
+ @Provides
+ @Named(ALPHA_ANIMATION_DURATION_MILLIS)
+ static long providesAlphaAnimationDurationMillis(@Main Resources resources) {
+ return resources.getInteger(R.integer.low_light_clock_alpha_animation_duration_ms);
+ }
+ /** Inject into LowLightMonitor. */
+ @Binds
+ @IntoMap
+ @ClassKey(LowLightMonitor.class)
+ abstract CoreStartable bindLowLightMonitor(LowLightMonitor lowLightMonitor);
+}
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 f1f299aac2b4..52749c54b9ba 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
@@ -1268,13 +1268,21 @@ class LegacyMediaDataManagerImpl(
}
private fun getResumeMediaAction(action: Runnable): MediaAction {
+ val iconId =
+ if (Flags.mediaControlsUiUpdate()) {
+ R.drawable.ic_media_play_button
+ } else {
+ R.drawable.ic_media_play
+ }
return MediaAction(
- Icon.createWithResource(context, R.drawable.ic_media_play)
- .setTint(themeText)
- .loadDrawable(context),
+ Icon.createWithResource(context, iconId).setTint(themeText).loadDrawable(context),
action,
context.getString(R.string.controls_media_resume),
- context.getDrawable(R.drawable.ic_media_play_container),
+ if (Flags.mediaControlsUiUpdate()) {
+ context.getDrawable(R.drawable.ic_media_play_button_container)
+ } else {
+ context.getDrawable(R.drawable.ic_media_play_container)
+ },
)
}
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
index a176e0c1c2a6..8bb7303a8386 100644
--- 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
@@ -43,7 +43,9 @@ import android.support.v4.media.MediaMetadataCompat
import android.text.TextUtils
import android.util.Log
import androidx.media.utils.MediaConstants
+import com.android.app.tracing.coroutines.asyncTraced as async
import com.android.app.tracing.coroutines.traceCoroutine
+import com.android.systemui.Flags
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
@@ -67,7 +69,6 @@ import kotlin.coroutines.coroutineContext
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
-import com.android.app.tracing.coroutines.asyncTraced as async
import kotlinx.coroutines.cancel
import kotlinx.coroutines.delay
import kotlinx.coroutines.ensureActive
@@ -511,13 +512,21 @@ constructor(
sbn.notification.extras.containsKey(Notification.EXTRA_MEDIA_REMOTE_DEVICE)
private fun getResumeMediaAction(action: Runnable): MediaAction {
+ val iconId =
+ if (Flags.mediaControlsUiUpdate()) {
+ R.drawable.ic_media_play_button
+ } else {
+ R.drawable.ic_media_play
+ }
return MediaAction(
- Icon.createWithResource(context, R.drawable.ic_media_play)
- .setTint(themeText)
- .loadDrawable(context),
+ Icon.createWithResource(context, iconId).setTint(themeText).loadDrawable(context),
action,
context.getString(R.string.controls_media_resume),
- context.getDrawable(R.drawable.ic_media_play_container),
+ if (Flags.mediaControlsUiUpdate()) {
+ context.getDrawable(R.drawable.ic_media_play_button_container)
+ } else {
+ context.getDrawable(R.drawable.ic_media_play_container)
+ },
)
}
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 a524db4437a5..587a678c6ac0 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
@@ -1197,13 +1197,21 @@ class MediaDataProcessor(
}
private fun getResumeMediaAction(action: Runnable): MediaAction {
+ val iconId =
+ if (Flags.mediaControlsUiUpdate()) {
+ R.drawable.ic_media_play_button
+ } else {
+ R.drawable.ic_media_play
+ }
return MediaAction(
- Icon.createWithResource(context, R.drawable.ic_media_play)
- .setTint(themeText)
- .loadDrawable(context),
+ Icon.createWithResource(context, iconId).setTint(themeText).loadDrawable(context),
action,
context.getString(R.string.controls_media_resume),
- context.getDrawable(R.drawable.ic_media_play_container),
+ if (Flags.mediaControlsUiUpdate()) {
+ context.getDrawable(R.drawable.ic_media_play_button_container)
+ } else {
+ context.getDrawable(R.drawable.ic_media_play_container)
+ },
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
index 1b0aeb47e36a..53f3b3a7a59d 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
@@ -28,7 +28,6 @@ import android.graphics.drawable.Drawable;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
-import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.CheckBox;
import android.widget.TextView;
@@ -485,13 +484,6 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter {
== MediaDevice.MediaDeviceType.TYPE_BLUETOOTH_DEVICE
? R.string.accessibility_bluetooth_name
: R.string.accessibility_cast_name, device.getName()));
- view.setAccessibilityDelegate(new View.AccessibilityDelegate() {
- public void onInitializeAccessibilityNodeInfo(View host,
- AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(host, info);
- host.setOnClickListener(null);
- }
- });
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/model/SysUiState.java b/packages/SystemUI/src/com/android/systemui/model/SysUiState.java
index 67fe0e981b09..1a5e605c96f8 100644
--- a/packages/SystemUI/src/com/android/systemui/model/SysUiState.java
+++ b/packages/SystemUI/src/com/android/systemui/model/SysUiState.java
@@ -79,7 +79,8 @@ public class SysUiState implements Dumpable {
/** Methods to this call can be chained together before calling {@link #commitUpdate(int)}. */
public SysUiState setFlag(@SystemUiStateFlags long flag, boolean enabled) {
- final Boolean overrideOrNull = mSceneContainerPlugin.flagValueOverride(flag);
+ final Boolean overrideOrNull = mSceneContainerPlugin != null
+ ? mSceneContainerPlugin.flagValueOverride(flag) : null;
if (overrideOrNull != null && enabled != overrideOrNull) {
if (DEBUG) {
Log.d(TAG, "setFlag for flag " + flag + " and value " + enabled + " overridden to "
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java
index 173a964cc5d3..1807847e3f3c 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java
@@ -77,7 +77,7 @@ import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.navigationbar.gestural.EdgeBackGestureHandler;
-import com.android.systemui.recents.OverviewProxyService;
+import com.android.systemui.recents.LauncherProxyService;
import com.android.systemui.settings.DisplayTracker;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.shared.Flags;
@@ -115,7 +115,7 @@ public final class NavBarHelper implements
AccessibilityButtonModeObserver.ModeChangedListener,
AccessibilityButtonTargetsObserver.TargetsChangedListener,
AccessibilityGestureTargetsObserver.TargetsChangedListener,
- OverviewProxyService.OverviewProxyListener, NavigationModeController.ModeChangedListener,
+ LauncherProxyService.LauncherProxyListener, NavigationModeController.ModeChangedListener,
Dumpable, CommandQueue.Callbacks, ConfigurationController.ConfigurationListener {
private static final String TAG = NavBarHelper.class.getSimpleName();
@@ -199,7 +199,7 @@ public final class NavBarHelper implements
AccessibilityButtonTargetsObserver accessibilityButtonTargetsObserver,
AccessibilityGestureTargetsObserver accessibilityGestureTargetsObserver,
SystemActions systemActions,
- OverviewProxyService overviewProxyService,
+ LauncherProxyService launcherProxyService,
Lazy<AssistManager> assistManagerLazy,
Lazy<Optional<CentralSurfaces>> centralSurfacesOptionalLazy,
KeyguardStateController keyguardStateController,
@@ -240,7 +240,7 @@ public final class NavBarHelper implements
mNavBarMode = navigationModeController.addListener(this);
mCommandQueue.addCallback(this);
configurationController.addCallback(this);
- overviewProxyService.addCallback(this);
+ launcherProxyService.addCallback(this);
dumpManager.registerDumpable(this);
}
@@ -536,10 +536,12 @@ public final class NavBarHelper implements
}
/**
- * @return Whether the IME is shown on top of the screen given the {@code vis} flag of
- * {@link InputMethodService} and the keyguard states.
+ * Checks whether the IME is visible on top of the screen, based on the given IME window
+ * visibility flags, and the current keyguard state.
+ *
+ * @param vis the IME window visibility.
*/
- public boolean isImeShown(@ImeWindowVisibility int vis) {
+ public boolean isImeVisible(@ImeWindowVisibility int vis) {
View shadeWindowView = mNotificationShadeWindowController.getWindowRootView();
boolean isKeyguardShowing = mKeyguardStateController.isShowing();
boolean imeVisibleOnShade = shadeWindowView != null && shadeWindowView.isAttachedToWindow()
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java
index 645bd0b4b441..ebda3765cf90 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java
@@ -52,7 +52,7 @@ import com.android.systemui.dump.DumpManager;
import com.android.systemui.model.SysUiState;
import com.android.systemui.navigationbar.views.NavigationBar;
import com.android.systemui.navigationbar.views.NavigationBarView;
-import com.android.systemui.recents.OverviewProxyService;
+import com.android.systemui.recents.LauncherProxyService;
import com.android.systemui.settings.DisplayTracker;
import com.android.systemui.shared.statusbar.phone.BarTransitions.TransitionMode;
import com.android.systemui.shared.system.TaskStackChangeListeners;
@@ -115,7 +115,7 @@ public class NavigationBarControllerImpl implements
@Inject
public NavigationBarControllerImpl(Context context,
- OverviewProxyService overviewProxyService,
+ LauncherProxyService launcherProxyService,
NavigationModeController navigationModeController,
SysUiState sysUiFlagsContainer,
CommandQueue commandQueue,
@@ -145,7 +145,7 @@ public class NavigationBarControllerImpl implements
mNavMode = navigationModeController.addListener(this);
mNavBarHelper = navBarHelper;
mTaskbarDelegate = taskbarDelegate;
- mTaskbarDelegate.setDependencies(commandQueue, overviewProxyService,
+ mTaskbarDelegate.setDependencies(commandQueue, launcherProxyService,
navBarHelper, navigationModeController, sysUiFlagsContainer,
dumpManager, autoHideControllerStore.forDisplay(mContext.getDisplayId()),
lightBarController, pipOptional, backAnimation.orElse(null),
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
index 05d8bff2ceb6..9d8943052b38 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
@@ -17,8 +17,9 @@
package com.android.systemui.navigationbar;
import static android.app.ActivityManager.LOCK_TASK_MODE_PINNED;
-import static android.app.StatusBarManager.NAVIGATION_HINT_IME_SHOWN;
-import static android.app.StatusBarManager.NAVIGATION_HINT_IME_SWITCHER_SHOWN;
+import static android.app.StatusBarManager.NAVBAR_BACK_DISMISS_IME;
+import static android.app.StatusBarManager.NAVBAR_IME_SWITCHER_BUTTON_VISIBLE;
+import static android.app.StatusBarManager.NAVBAR_IME_VISIBLE;
import static android.app.StatusBarManager.WINDOW_STATE_SHOWING;
import static android.view.WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
@@ -29,14 +30,16 @@ import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BACK_DISABLED;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BACK_DISMISS_IME;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_HOME_DISABLED;
-import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SHOWING;
-import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SWITCHER_SHOWING;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SWITCHER_BUTTON_VISIBLE;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_VISIBLE;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NAV_BAR_HIDDEN;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SCREEN_PINNING;
import android.app.StatusBarManager;
+import android.app.StatusBarManager.NavbarFlags;
import android.app.StatusBarManager.WindowVisibleState;
import android.content.Context;
import android.graphics.Rect;
@@ -67,7 +70,7 @@ import com.android.systemui.dump.DumpManager;
import com.android.systemui.model.SysUiState;
import com.android.systemui.navigationbar.gestural.EdgeBackGestureHandler;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.recents.OverviewProxyService;
+import com.android.systemui.recents.LauncherProxyService;
import com.android.systemui.settings.DisplayTracker;
import com.android.systemui.shared.recents.utilities.Utilities;
import com.android.systemui.shared.statusbar.phone.BarTransitions;
@@ -93,7 +96,7 @@ import javax.inject.Inject;
/** */
@SysUISingleton
public class TaskbarDelegate implements CommandQueue.Callbacks,
- OverviewProxyService.OverviewProxyListener, NavigationModeController.ModeChangedListener,
+ LauncherProxyService.LauncherProxyListener, NavigationModeController.ModeChangedListener,
Dumpable {
private static final String TAG = TaskbarDelegate.class.getSimpleName();
@@ -101,7 +104,7 @@ public class TaskbarDelegate implements CommandQueue.Callbacks,
private final LightBarTransitionsController.Factory mLightBarTransitionsControllerFactory;
private boolean mInitialized;
private CommandQueue mCommandQueue;
- private OverviewProxyService mOverviewProxyService;
+ private LauncherProxyService mLauncherProxyService;
private NavBarHelper mNavBarHelper;
private NavigationModeController mNavigationModeController;
private SysUiState mSysUiState;
@@ -111,7 +114,8 @@ public class TaskbarDelegate implements CommandQueue.Callbacks,
private TaskStackChangeListeners mTaskStackChangeListeners;
private Optional<Pip> mPipOptional;
private int mDefaultDisplayId;
- private int mNavigationIconHints;
+ @NavbarFlags
+ private int mNavbarFlags;
private final NavBarHelper.NavbarTaskbarStateUpdater mNavbarTaskbarStateUpdater =
new NavBarHelper.NavbarTaskbarStateUpdater() {
@Override
@@ -206,7 +210,7 @@ public class TaskbarDelegate implements CommandQueue.Callbacks,
}
public void setDependencies(CommandQueue commandQueue,
- OverviewProxyService overviewProxyService,
+ LauncherProxyService launcherProxyService,
NavBarHelper navBarHelper,
NavigationModeController navigationModeController,
SysUiState sysUiState, DumpManager dumpManager,
@@ -218,7 +222,7 @@ public class TaskbarDelegate implements CommandQueue.Callbacks,
DisplayTracker displayTracker) {
// TODO: adding this in the ctor results in a dagger dependency cycle :(
mCommandQueue = commandQueue;
- mOverviewProxyService = overviewProxyService;
+ mLauncherProxyService = launcherProxyService;
mNavBarHelper = navBarHelper;
mNavigationModeController = navigationModeController;
mSysUiState = sysUiState;
@@ -236,12 +240,12 @@ public class TaskbarDelegate implements CommandQueue.Callbacks,
@Override
public void onDisplayReady(int displayId) {
CommandQueue.Callbacks.super.onDisplayReady(displayId);
- if (mOverviewProxyService.getProxy() == null) {
+ if (mLauncherProxyService.getProxy() == null) {
return;
}
try {
- mOverviewProxyService.getProxy().onDisplayReady(displayId);
+ mLauncherProxyService.getProxy().onDisplayReady(displayId);
} catch (RemoteException e) {
Log.e(TAG, "onDisplayReady() failed", e);
}
@@ -250,17 +254,31 @@ public class TaskbarDelegate implements CommandQueue.Callbacks,
@Override
public void onDisplayRemoved(int displayId) {
CommandQueue.Callbacks.super.onDisplayRemoved(displayId);
- if (mOverviewProxyService.getProxy() == null) {
+ if (mLauncherProxyService.getProxy() == null) {
return;
}
try {
- mOverviewProxyService.getProxy().onDisplayRemoved(displayId);
+ mLauncherProxyService.getProxy().onDisplayRemoved(displayId);
} catch (RemoteException e) {
Log.e(TAG, "onDisplayRemoved() failed", e);
}
}
+ @Override
+ public void onDisplayRemoveSystemDecorations(int displayId) {
+ CommandQueue.Callbacks.super.onDisplayRemoveSystemDecorations(displayId);
+ if (mLauncherProxyService.getProxy() == null) {
+ return;
+ }
+
+ try {
+ mLauncherProxyService.getProxy().onDisplayRemoveSystemDecorations(displayId);
+ } catch (RemoteException e) {
+ Log.e(TAG, "onDisplaySystemDecorationsRemoved() failed", e);
+ }
+ }
+
// Separated into a method to keep setDependencies() clean/readable.
private LightBarTransitionsController createLightBarTransitionsController() {
@@ -269,7 +287,7 @@ public class TaskbarDelegate implements CommandQueue.Callbacks,
@Override
public void applyDarkIntensity(float darkIntensity) {
mBgHandler.post(() -> {
- mOverviewProxyService.onNavButtonsDarkIntensityChanged(darkIntensity);
+ mLauncherProxyService.onNavButtonsDarkIntensityChanged(darkIntensity);
});
}
@@ -291,7 +309,7 @@ public class TaskbarDelegate implements CommandQueue.Callbacks,
mDefaultDisplayId = displayId;
parseCurrentSysuiState();
mCommandQueue.addCallback(this);
- mOverviewProxyService.addCallback(this);
+ mLauncherProxyService.addCallback(this);
onNavigationModeChanged(mNavigationModeController.addListener(this));
mNavBarHelper.registerNavTaskStateUpdater(mNavbarTaskbarStateUpdater);
// Initialize component callback
@@ -316,7 +334,7 @@ public class TaskbarDelegate implements CommandQueue.Callbacks,
return;
}
mCommandQueue.removeCallback(this);
- mOverviewProxyService.removeCallback(this);
+ mLauncherProxyService.removeCallback(this);
mNavigationModeController.removeListener(this);
mNavBarHelper.removeNavTaskStateUpdater(mNavbarTaskbarStateUpdater);
mScreenPinningNotify = null;
@@ -360,10 +378,12 @@ public class TaskbarDelegate implements CommandQueue.Callbacks,
mSysUiState.setFlag(SYSUI_STATE_A11Y_BUTTON_CLICKABLE, clickable)
.setFlag(SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE, longClickable)
- .setFlag(SYSUI_STATE_IME_SHOWING,
- (mNavigationIconHints & NAVIGATION_HINT_IME_SHOWN) != 0)
- .setFlag(SYSUI_STATE_IME_SWITCHER_SHOWING,
- (mNavigationIconHints & NAVIGATION_HINT_IME_SWITCHER_SHOWN) != 0)
+ .setFlag(SYSUI_STATE_IME_VISIBLE,
+ (mNavbarFlags & NAVBAR_IME_VISIBLE) != 0)
+ .setFlag(SYSUI_STATE_IME_SWITCHER_BUTTON_VISIBLE,
+ (mNavbarFlags & NAVBAR_IME_SWITCHER_BUTTON_VISIBLE) != 0)
+ .setFlag(SYSUI_STATE_BACK_DISMISS_IME,
+ (mNavbarFlags & NAVBAR_BACK_DISMISS_IME) != 0)
.setFlag(SYSUI_STATE_OVERVIEW_DISABLED,
(mDisabledFlags & View.STATUS_BAR_DISABLE_RECENT) != 0)
.setFlag(SYSUI_STATE_HOME_DISABLED,
@@ -381,43 +401,43 @@ public class TaskbarDelegate implements CommandQueue.Callbacks,
}
void onTransitionModeUpdated(int barMode, boolean checkBarModes) {
- if (mOverviewProxyService.getProxy() == null) {
+ if (mLauncherProxyService.getProxy() == null) {
return;
}
try {
- mOverviewProxyService.getProxy().onTransitionModeUpdated(barMode, checkBarModes);
+ mLauncherProxyService.getProxy().onTransitionModeUpdated(barMode, checkBarModes);
} catch (RemoteException e) {
Log.e(TAG, "onTransitionModeUpdated() failed, barMode: " + barMode, e);
}
}
void checkNavBarModes(int displayId) {
- if (mOverviewProxyService.getProxy() == null) {
+ if (mLauncherProxyService.getProxy() == null) {
return;
}
try {
- mOverviewProxyService.getProxy().checkNavBarModes(displayId);
+ mLauncherProxyService.getProxy().checkNavBarModes(displayId);
} catch (RemoteException e) {
Log.e(TAG, "checkNavBarModes() failed", e);
}
}
void finishBarAnimations(int displayId) {
- if (mOverviewProxyService.getProxy() == null) {
+ if (mLauncherProxyService.getProxy() == null) {
return;
}
try {
- mOverviewProxyService.getProxy().finishBarAnimations(displayId);
+ mLauncherProxyService.getProxy().finishBarAnimations(displayId);
} catch (RemoteException e) {
Log.e(TAG, "finishBarAnimations() failed", e);
}
}
void touchAutoDim(int displayId) {
- if (mOverviewProxyService.getProxy() == null) {
+ if (mLauncherProxyService.getProxy() == null) {
return;
}
@@ -425,31 +445,31 @@ public class TaskbarDelegate implements CommandQueue.Callbacks,
int state = mStatusBarStateController.getState();
boolean shouldReset =
state != StatusBarState.KEYGUARD && state != StatusBarState.SHADE_LOCKED;
- mOverviewProxyService.getProxy().touchAutoDim(displayId, shouldReset);
+ mLauncherProxyService.getProxy().touchAutoDim(displayId, shouldReset);
} catch (RemoteException e) {
Log.e(TAG, "touchAutoDim() failed", e);
}
}
void transitionTo(int displayId, @BarTransitions.TransitionMode int barMode, boolean animate) {
- if (mOverviewProxyService.getProxy() == null) {
+ if (mLauncherProxyService.getProxy() == null) {
return;
}
try {
- mOverviewProxyService.getProxy().transitionTo(displayId, barMode, animate);
+ mLauncherProxyService.getProxy().transitionTo(displayId, barMode, animate);
} catch (RemoteException e) {
Log.e(TAG, "transitionTo() failed, barMode: " + barMode, e);
}
}
private void updateAssistantAvailability(boolean assistantAvailable,
boolean longPressHomeEnabled) {
- if (mOverviewProxyService.getProxy() == null) {
+ if (mLauncherProxyService.getProxy() == null) {
return;
}
try {
- mOverviewProxyService.getProxy().onAssistantAvailable(assistantAvailable,
+ mLauncherProxyService.getProxy().onAssistantAvailable(assistantAvailable,
longPressHomeEnabled);
} catch (RemoteException e) {
Log.e(TAG, "onAssistantAvailable() failed, available: " + assistantAvailable, e);
@@ -457,24 +477,24 @@ public class TaskbarDelegate implements CommandQueue.Callbacks,
}
private void updateWallpaperVisible(int displayId, boolean visible) {
- if (mOverviewProxyService.getProxy() == null) {
+ if (mLauncherProxyService.getProxy() == null) {
return;
}
try {
- mOverviewProxyService.getProxy().updateWallpaperVisibility(displayId, visible);
+ mLauncherProxyService.getProxy().updateWallpaperVisibility(displayId, visible);
} catch (RemoteException e) {
Log.e(TAG, "updateWallpaperVisibility() failed, visible: " + visible, e);
}
}
private void appTransitionPending(boolean pending) {
- if (mOverviewProxyService.getProxy() == null) {
+ if (mLauncherProxyService.getProxy() == null) {
return;
}
try {
- mOverviewProxyService.getProxy().appTransitionPending(pending);
+ mLauncherProxyService.getProxy().appTransitionPending(pending);
} catch (RemoteException e) {
Log.e(TAG, "appTransitionPending() failed, pending: " + pending, e);
}
@@ -483,18 +503,17 @@ public class TaskbarDelegate implements CommandQueue.Callbacks,
@Override
public void setImeWindowStatus(int displayId, @ImeWindowVisibility int vis,
@BackDispositionMode int backDisposition, boolean showImeSwitcher) {
- boolean imeShown = mNavBarHelper.isImeShown(vis);
- if (!imeShown) {
- // Count imperceptible changes as visible so we transition taskbar out quickly.
- imeShown = (vis & InputMethodService.IME_VISIBLE_IMPERCEPTIBLE) != 0;
- }
- showImeSwitcher = imeShown && showImeSwitcher;
- int hints = Utilities.calculateBackDispositionHints(mNavigationIconHints, backDisposition,
- imeShown, showImeSwitcher);
- if (hints != mNavigationIconHints) {
- mNavigationIconHints = hints;
- updateSysuiFlags();
+ // Count imperceptible changes as visible so we transition taskbar out quickly.
+ final boolean isImeVisible = mNavBarHelper.isImeVisible(vis)
+ || (vis & InputMethodService.IME_VISIBLE_IMPERCEPTIBLE) != 0;
+ final int flags = Utilities.updateNavbarFlagsFromIme(mNavbarFlags, backDisposition,
+ isImeVisible, showImeSwitcher);
+ if (flags == mNavbarFlags) {
+ return;
}
+
+ mNavbarFlags = flags;
+ updateSysuiFlags();
}
@Override
@@ -509,14 +528,14 @@ public class TaskbarDelegate implements CommandQueue.Callbacks,
@Override
public void onRotationProposal(int rotation, boolean isValid) {
- mOverviewProxyService.onRotationProposal(rotation, isValid);
+ mLauncherProxyService.onRotationProposal(rotation, isValid);
}
@Override
public void disable(int displayId, int state1, int state2, boolean animate) {
mDisabledFlags = state1;
updateSysuiFlags();
- mOverviewProxyService.disable(displayId, state1, state2, animate);
+ mLauncherProxyService.disable(displayId, state1, state2, animate);
}
@Override
@@ -524,7 +543,7 @@ public class TaskbarDelegate implements CommandQueue.Callbacks,
AppearanceRegion[] appearanceRegions, boolean navbarColorManagedByIme, int behavior,
@InsetsType int requestedVisibleTypes, String packageName,
LetterboxDetails[] letterboxDetails) {
- mOverviewProxyService.onSystemBarAttributesChanged(displayId, behavior);
+ mLauncherProxyService.onSystemBarAttributesChanged(displayId, behavior);
boolean nbModeChanged = false;
if (mAppearance != appearance) {
mAppearance = appearance;
@@ -577,12 +596,12 @@ public class TaskbarDelegate implements CommandQueue.Callbacks,
@Override
public void toggleTaskbar() {
- if (mOverviewProxyService.getProxy() == null) {
+ if (mLauncherProxyService.getProxy() == null) {
return;
}
try {
- mOverviewProxyService.getProxy().onTaskbarToggled();
+ mLauncherProxyService.getProxy().onTaskbarToggled();
} catch (RemoteException e) {
Log.e(TAG, "onTaskbarToggled() failed", e);
}
@@ -654,7 +673,7 @@ public class TaskbarDelegate implements CommandQueue.Callbacks,
@Override
public void setNavigationBarLumaSamplingEnabled(int displayId, boolean enable) {
- mOverviewProxyService.onNavigationBarLumaSamplingEnabled(displayId, enable);
+ mLauncherProxyService.onNavigationBarLumaSamplingEnabled(displayId, enable);
}
@Override
@@ -688,7 +707,7 @@ public class TaskbarDelegate implements CommandQueue.Callbacks,
@Override
public void dump(@NonNull PrintWriter pw, @NonNull String[] args) {
pw.println("TaskbarDelegate (mDefaultDisplayId=" + mDefaultDisplayId + "):");
- pw.println(" mNavigationIconHints=" + mNavigationIconHints);
+ pw.println(" mNavbarFlags=" + mNavbarFlags);
pw.println(" mNavigationMode=" + mNavigationMode);
pw.println(" mDisabledFlags=" + mDisabledFlags);
pw.println(" mTaskBarWindowState=" + mTaskBarWindowState);
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
index 1c94f56f0942..f44c2c01951c 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
@@ -85,7 +85,7 @@ import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.NavigationEdgeBackPlugin;
import com.android.systemui.plugins.PluginListener;
import com.android.systemui.plugins.PluginManager;
-import com.android.systemui.recents.OverviewProxyService;
+import com.android.systemui.recents.LauncherProxyService;
import com.android.systemui.res.R;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.shared.system.ActivityManagerWrapper;
@@ -155,8 +155,8 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack
}
};
- private OverviewProxyService.OverviewProxyListener mQuickSwitchListener =
- new OverviewProxyService.OverviewProxyListener() {
+ private LauncherProxyService.LauncherProxyListener mQuickSwitchListener =
+ new LauncherProxyService.LauncherProxyListener() {
@Override
public void onPrioritizedRotation(@Surface.Rotation int rotation) {
mStartingQuickstepRotation = rotation;
@@ -197,7 +197,7 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack
private final Context mContext;
private final UserTracker mUserTracker;
- private final OverviewProxyService mOverviewProxyService;
+ private final LauncherProxyService mLauncherProxyService;
private final SysUiState mSysUiState;
private Runnable mStateChangeCallback;
private Consumer<Boolean> mButtonForcedVisibleCallback;
@@ -332,7 +332,7 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack
: SysUiStatsLog.BACK_GESTURE__TYPE__COMPLETED);
if (!mInRejectedExclusion) {
// Log successful back gesture to contextual edu stats
- mOverviewProxyService.updateContextualEduStats(mIsTrackpadThreeFingerSwipe,
+ mLauncherProxyService.updateContextualEduStats(mIsTrackpadThreeFingerSwipe,
GestureType.BACK);
}
}
@@ -441,7 +441,7 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack
@AssistedInject
EdgeBackGestureHandler(
@Assisted Context context,
- OverviewProxyService overviewProxyService,
+ LauncherProxyService launcherProxyService,
SysUiState sysUiState,
PluginManager pluginManager,
@BackPanelUiThread UiThreadContext uiThreadContext,
@@ -468,7 +468,7 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack
mBackgroundExecutor = backgroundExecutor;
mBgHandler = bgHandler;
mUserTracker = userTracker;
- mOverviewProxyService = overviewProxyService;
+ mLauncherProxyService = launcherProxyService;
mSysUiState = sysUiState;
mPluginManager = pluginManager;
mNavigationModeController = navigationModeController;
@@ -620,7 +620,7 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack
*/
public void onNavBarAttached() {
mIsAttached = true;
- mOverviewProxyService.addCallback(mQuickSwitchListener);
+ mLauncherProxyService.addCallback(mQuickSwitchListener);
mSysUiState.addCallback(mSysUiStateCallback);
mInputManager.registerInputDeviceListener(mInputDeviceListener, mBgHandler);
int[] inputDevices = mInputManager.getInputDeviceIds();
@@ -636,7 +636,7 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack
*/
public void onNavBarDetached() {
mIsAttached = false;
- mOverviewProxyService.removeCallback(mQuickSwitchListener);
+ mLauncherProxyService.removeCallback(mQuickSwitchListener);
mSysUiState.removeCallback(mSysUiStateCallback);
mInputManager.unregisterInputDeviceListener(mInputDeviceListener);
mTrackpadsConnected.clear();
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBar.java
index c78750718cf0..f95f45906b23 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBar.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBar.java
@@ -17,12 +17,14 @@
package com.android.systemui.navigationbar.views;
import static android.app.ActivityManager.LOCK_TASK_MODE_PINNED;
-import static android.app.StatusBarManager.NAVIGATION_HINT_IME_SHOWN;
-import static android.app.StatusBarManager.NAVIGATION_HINT_IME_SWITCHER_SHOWN;
+import static android.app.StatusBarManager.NAVBAR_BACK_DISMISS_IME;
+import static android.app.StatusBarManager.NAVBAR_IME_SWITCHER_BUTTON_VISIBLE;
+import static android.app.StatusBarManager.NAVBAR_IME_VISIBLE;
import static android.app.StatusBarManager.WINDOW_STATE_HIDDEN;
import static android.app.StatusBarManager.WINDOW_STATE_SHOWING;
import static android.app.StatusBarManager.WindowType;
import static android.app.StatusBarManager.WindowVisibleState;
+import static android.app.StatusBarManager.navbarFlagsToString;
import static android.app.StatusBarManager.windowStateToString;
import static android.app.WindowConfiguration.ROTATION_UNDEFINED;
import static android.view.InsetsSource.FLAG_SUPPRESS_SCRIM;
@@ -34,7 +36,7 @@ import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL;
import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.HOME_BUTTON_LONG_PRESS_DURATION_MS;
import static com.android.systemui.navigationbar.NavBarHelper.transitionMode;
-import static com.android.systemui.recents.OverviewProxyService.OverviewProxyListener;
+import static com.android.systemui.recents.LauncherProxyService.LauncherProxyListener;
import static com.android.systemui.shared.recents.utilities.Utilities.isLargeScreen;
import static com.android.systemui.shared.rotation.RotationButtonController.DEBUG_ROTATION;
import static com.android.systemui.shared.statusbar.phone.BarTransitions.MODE_OPAQUE;
@@ -42,8 +44,9 @@ import static com.android.systemui.shared.statusbar.phone.BarTransitions.Transit
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.system.QuickStepContract.SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY;
-import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SHOWING;
-import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SWITCHER_SHOWING;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BACK_DISMISS_IME;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SWITCHER_BUTTON_VISIBLE;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_VISIBLE;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NAV_BAR_HIDDEN;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SCREEN_PINNING;
import static com.android.systemui.shared.system.QuickStepContract.isGesturalMode;
@@ -56,6 +59,7 @@ import android.annotation.NonNull;
import android.app.ActivityTaskManager;
import android.app.IActivityTaskManager;
import android.app.StatusBarManager;
+import android.app.StatusBarManager.NavbarFlags;
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.Insets;
@@ -127,7 +131,7 @@ import com.android.systemui.navigationbar.views.buttons.KeyButtonView;
import com.android.systemui.navigationbar.views.buttons.NavBarButtonClickLogger;
import com.android.systemui.navigationbar.views.buttons.NavbarOrientationTrackingLogger;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.recents.OverviewProxyService;
+import com.android.systemui.recents.LauncherProxyService;
import com.android.systemui.recents.Recents;
import com.android.systemui.res.R;
import com.android.systemui.settings.DisplayTracker;
@@ -208,7 +212,7 @@ public class NavigationBar extends ViewController<NavigationBarView> implements
private final ShadeViewController mShadeViewController;
private final PanelExpansionInteractor mPanelExpansionInteractor;
private final NotificationRemoteInputManager mNotificationRemoteInputManager;
- private final OverviewProxyService mOverviewProxyService;
+ private final LauncherProxyService mLauncherProxyService;
private final NavigationModeController mNavigationModeController;
private final UserTracker mUserTracker;
private final CommandQueue mCommandQueue;
@@ -233,7 +237,8 @@ public class NavigationBar extends ViewController<NavigationBarView> implements
private @WindowVisibleState int mNavigationBarWindowState = WINDOW_STATE_SHOWING;
- private int mNavigationIconHints = 0;
+ @NavbarFlags
+ private int mNavbarFlags;
private @TransitionMode int mTransitionMode;
private boolean mLongPressHomeEnabled;
@@ -278,7 +283,7 @@ public class NavigationBar extends ViewController<NavigationBarView> implements
* gesture to indicate to them that they can continue in that orientation without having to
* rotate the phone
* The secondary handle will show when we get
- * {@link OverviewProxyListener#notifyPrioritizedRotation(int)} callback with the
+ * {@link LauncherProxyListener#notifyPrioritizedRotation(int)} callback with the
* original handle hidden and we'll flip the visibilities once the
* {@link #mTasksFrozenListener} fires
*/
@@ -382,12 +387,12 @@ public class NavigationBar extends ViewController<NavigationBarView> implements
}
};
- private final OverviewProxyListener mOverviewProxyListener = new OverviewProxyListener() {
+ private final LauncherProxyListener mLauncherProxyListener = new LauncherProxyListener() {
@Override
public void onConnectionChanged(boolean isConnected) {
- mView.onOverviewProxyConnectionChange(
- mOverviewProxyService.isEnabled());
- mView.setShouldShowSwipeUpUi(mOverviewProxyService.shouldShowSwipeUpUI());
+ mView.onLauncherProxyConnectionChange(
+ mLauncherProxyService.isEnabled());
+ mView.setShouldShowSwipeUpUi(mLauncherProxyService.shouldShowSwipeUpUI());
updateScreenPinningGestures();
}
@@ -560,7 +565,7 @@ public class NavigationBar extends ViewController<NavigationBarView> implements
AccessibilityManager accessibilityManager,
DeviceProvisionedController deviceProvisionedController,
MetricsLogger metricsLogger,
- OverviewProxyService overviewProxyService,
+ LauncherProxyService launcherProxyService,
NavigationModeController navigationModeController,
StatusBarStateController statusBarStateController,
StatusBarKeyguardViewManager statusBarKeyguardViewManager,
@@ -613,7 +618,7 @@ public class NavigationBar extends ViewController<NavigationBarView> implements
mShadeViewController = shadeViewController;
mPanelExpansionInteractor = panelExpansionInteractor;
mNotificationRemoteInputManager = notificationRemoteInputManager;
- mOverviewProxyService = overviewProxyService;
+ mLauncherProxyService = launcherProxyService;
mNavigationModeController = navigationModeController;
mUserTracker = userTracker;
mCommandQueue = commandQueue;
@@ -649,18 +654,18 @@ public class NavigationBar extends ViewController<NavigationBarView> implements
if (!mEdgeBackGestureHandler.isHandlingGestures()) {
// We're in 2/3 button mode OR back button force-shown in SUW
if (!mImeVisible) {
- // IME not showing, take all touches
+ // IME is not visible, take all touches
info.setTouchableInsets(InternalInsetsInfo.TOUCHABLE_INSETS_FRAME);
return;
}
if (!mView.isImeRenderingNavButtons()) {
- // IME showing but not drawing any buttons, take all touches
+ // IME is visible but not drawing any buttons, take all touches
info.setTouchableInsets(InternalInsetsInfo.TOUCHABLE_INSETS_FRAME);
return;
}
}
- // When in gestural and the IME is showing, don't use the nearest region since it will
+ // When in gestural and the IME is visible, don't use the nearest region since it will
// take gesture space away from the IME
info.setTouchableInsets(InternalInsetsInfo.TOUCHABLE_INSETS_REGION);
info.touchableRegion.set(
@@ -817,13 +822,12 @@ public class NavigationBar extends ViewController<NavigationBarView> implements
if (mSavedState != null) {
getBarTransitions().getLightTransitionsController().restoreState(mSavedState);
}
- setNavigationIconHints(mNavigationIconHints);
setWindowVisible(isNavBarWindowVisible());
mView.setBehavior(mBehavior);
setNavBarMode(mNavBarMode);
repositionNavigationBar(mCurrentRotation);
mView.setUpdateActiveTouchRegionsCallback(
- () -> mOverviewProxyService.onActiveNavBarRegionChanges(
+ () -> mLauncherProxyService.onActiveNavBarRegionChanges(
getButtonLocations(true /* inScreen */, true /* useNearestRegion */)));
mView.getViewTreeObserver().addOnComputeInternalInsetsListener(
@@ -839,7 +843,7 @@ public class NavigationBar extends ViewController<NavigationBarView> implements
mWakefulnessLifecycle.addObserver(mWakefulnessObserver);
notifyNavigationBarScreenOn();
- mOverviewProxyService.addCallback(mOverviewProxyListener);
+ mLauncherProxyService.addCallback(mLauncherProxyListener);
updateSystemUiStateFlags();
// Currently there is no accelerometer sensor on non-default display.
@@ -877,7 +881,7 @@ public class NavigationBar extends ViewController<NavigationBarView> implements
public void onViewDetached() {
mView.setUpdateActiveTouchRegionsCallback(null);
getBarTransitions().destroy();
- mOverviewProxyService.removeCallback(mOverviewProxyListener);
+ mLauncherProxyService.removeCallback(mLauncherProxyListener);
mUserTracker.removeCallback(mUserChangedCallback);
mWakefulnessLifecycle.removeObserver(mWakefulnessObserver);
if (mOrientationHandle != null) {
@@ -1111,6 +1115,7 @@ public class NavigationBar extends ViewController<NavigationBarView> implements
pw.println(" mLongPressHomeEnabled=" + mLongPressHomeEnabled);
pw.println(" mNavigationBarWindowState="
+ windowStateToString(mNavigationBarWindowState));
+ pw.println(" mNavbarFlags=" + navbarFlagsToString(mNavbarFlags));
pw.println(" mTransitionMode="
+ BarTransitions.modeToString(mTransitionMode));
pw.println(" mTransientShown=" + mTransientShown);
@@ -1135,13 +1140,14 @@ public class NavigationBar extends ViewController<NavigationBarView> implements
if (displayId != mDisplayId) {
return;
}
- boolean imeShown = mNavBarHelper.isImeShown(vis);
- showImeSwitcher = imeShown && showImeSwitcher;
- int hints = Utilities.calculateBackDispositionHints(mNavigationIconHints, backDisposition,
- imeShown, showImeSwitcher);
- if (hints == mNavigationIconHints) return;
+ final boolean isImeVisible = mNavBarHelper.isImeVisible(vis);
+ final int flags = Utilities.updateNavbarFlagsFromIme(mNavbarFlags, backDisposition,
+ isImeVisible, showImeSwitcher);
+ if (flags == mNavbarFlags) {
+ return;
+ }
- setNavigationIconHints(hints);
+ setNavbarFlags(flags);
checkBarModes();
updateSystemUiStateFlags();
}
@@ -1680,10 +1686,12 @@ public class NavigationBar extends ViewController<NavigationBarView> implements
mSysUiFlagsContainer.setFlag(SYSUI_STATE_A11Y_BUTTON_CLICKABLE, clickable)
.setFlag(SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE, longClickable)
.setFlag(SYSUI_STATE_NAV_BAR_HIDDEN, !isNavBarWindowVisible())
- .setFlag(SYSUI_STATE_IME_SHOWING,
- (mNavigationIconHints & NAVIGATION_HINT_IME_SHOWN) != 0)
- .setFlag(SYSUI_STATE_IME_SWITCHER_SHOWING,
- (mNavigationIconHints & NAVIGATION_HINT_IME_SWITCHER_SHOWN) != 0)
+ .setFlag(SYSUI_STATE_IME_VISIBLE,
+ (mNavbarFlags & NAVBAR_IME_VISIBLE) != 0)
+ .setFlag(SYSUI_STATE_IME_SWITCHER_BUTTON_VISIBLE,
+ (mNavbarFlags & NAVBAR_IME_SWITCHER_BUTTON_VISIBLE) != 0)
+ .setFlag(SYSUI_STATE_BACK_DISMISS_IME,
+ (mNavbarFlags & NAVBAR_BACK_DISMISS_IME) != 0)
.setFlag(SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY,
allowSystemGestureIgnoringBarVisibility())
.commitUpdate(mDisplayId);
@@ -1691,9 +1699,9 @@ public class NavigationBar extends ViewController<NavigationBarView> implements
private void updateAssistantEntrypoints(boolean assistantAvailable,
boolean longPressHomeEnabled) {
- if (mOverviewProxyService.getProxy() != null) {
+ if (mLauncherProxyService.getProxy() != null) {
try {
- mOverviewProxyService.getProxy().onAssistantAvailable(assistantAvailable,
+ mLauncherProxyService.getProxy().onAssistantAvailable(assistantAvailable,
longPressHomeEnabled);
} catch (RemoteException e) {
Log.w(TAG, "Unable to send assistant availability data to launcher");
@@ -1926,30 +1934,37 @@ public class NavigationBar extends ViewController<NavigationBarView> implements
};
@VisibleForTesting
- int getNavigationIconHints() {
- return mNavigationIconHints;
+ @NavbarFlags
+ int getNavbarFlags() {
+ return mNavbarFlags;
}
- private void setNavigationIconHints(int hints) {
- if (hints == mNavigationIconHints) return;
+ /**
+ * Sets the navigation bar state flags.
+ *
+ * @param flags the navigation bar state flags.
+ */
+ private void setNavbarFlags(@NavbarFlags int flags) {
+ if (flags == mNavbarFlags) {
+ return;
+ }
if (!isLargeScreen(mContext)) {
// All IME functions handled by launcher via Sysui flags for large screen
- final boolean newBackAlt = (hints & StatusBarManager.NAVIGATION_HINT_BACK_ALT) != 0;
- final boolean oldBackAlt =
- (mNavigationIconHints & StatusBarManager.NAVIGATION_HINT_BACK_ALT) != 0;
- if (newBackAlt != oldBackAlt) {
- mView.onBackAltChanged(newBackAlt);
+ final boolean backDismissIme = (flags & StatusBarManager.NAVBAR_BACK_DISMISS_IME) != 0;
+ final boolean oldBackDismissIme =
+ (mNavbarFlags & StatusBarManager.NAVBAR_BACK_DISMISS_IME) != 0;
+ if (backDismissIme != oldBackDismissIme) {
+ mView.onBackDismissImeChanged(backDismissIme);
}
- mImeVisible = (hints & NAVIGATION_HINT_IME_SHOWN) != 0;
+ mImeVisible = (flags & NAVBAR_IME_VISIBLE) != 0;
- mView.setNavigationIconHints(hints);
+ mView.setNavbarFlags(flags);
}
if (DEBUG) {
- android.widget.Toast.makeText(mContext,
- "Navigation icon hints = " + hints,
- 500).show();
+ android.widget.Toast.makeText(mContext, "Navbar flags = " + flags, 500)
+ .show();
}
- mNavigationIconHints = hints;
+ mNavbarFlags = flags;
}
/**
@@ -2093,7 +2108,7 @@ public class NavigationBar extends ViewController<NavigationBarView> implements
if (!canShowSecondaryHandle()) {
resetSecondaryHandle();
}
- mView.setShouldShowSwipeUpUi(mOverviewProxyService.shouldShowSwipeUpUI());
+ mView.setShouldShowSwipeUpUi(mLauncherProxyService.shouldShowSwipeUpUI());
}
};
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBarInflaterView.java b/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBarInflaterView.java
index 96b730c08397..2c5a9c84645b 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBarInflaterView.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBarInflaterView.java
@@ -41,7 +41,7 @@ import com.android.systemui.navigationbar.views.buttons.ButtonDispatcher;
import com.android.systemui.navigationbar.views.buttons.KeyButtonView;
import com.android.systemui.navigationbar.views.buttons.ReverseLinearLayout;
import com.android.systemui.navigationbar.views.buttons.ReverseLinearLayout.ReverseRelativeLayout;
-import com.android.systemui.recents.OverviewProxyService;
+import com.android.systemui.recents.LauncherProxyService;
import com.android.systemui.res.R;
import com.android.systemui.shared.system.QuickStepContract;
@@ -117,13 +117,13 @@ public class NavigationBarInflaterView extends FrameLayout {
private boolean mIsVertical;
private boolean mAlternativeOrder;
- private OverviewProxyService mOverviewProxyService;
+ private LauncherProxyService mLauncherProxyService;
private int mNavBarMode = NAV_BAR_MODE_3BUTTON;
public NavigationBarInflaterView(Context context, AttributeSet attrs) {
super(context, attrs);
createInflaters();
- mOverviewProxyService = Dependency.get(OverviewProxyService.class);
+ mLauncherProxyService = Dependency.get(LauncherProxyService.class);
mListener = new Listener(this);
mNavBarMode = Dependency.get(NavigationModeController.class).addListener(mListener);
}
@@ -159,7 +159,7 @@ public class NavigationBarInflaterView extends FrameLayout {
protected String getDefaultLayout() {
final int defaultResource = QuickStepContract.isGesturalMode(mNavBarMode)
? R.string.config_navBarLayoutHandle
- : mOverviewProxyService.shouldShowSwipeUpUI()
+ : mLauncherProxyService.shouldShowSwipeUpUI()
? R.string.config_navBarLayoutQuickstep
: R.string.config_navBarLayout;
return getContext().getString(defaultResource);
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBarView.java
index d5ae72165c4a..36cb8fa374b0 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBarView.java
@@ -16,6 +16,9 @@
package com.android.systemui.navigationbar.views;
+import static android.app.StatusBarManager.NAVBAR_BACK_DISMISS_IME;
+import static android.app.StatusBarManager.NAVBAR_IME_SWITCHER_BUTTON_VISIBLE;
+import static android.app.StatusBarManager.NAVBAR_IME_VISIBLE;
import static android.inputmethodservice.InputMethodService.canImeRenderGesturalNavButtons;
import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL;
@@ -31,7 +34,7 @@ import android.animation.PropertyValuesHolder;
import android.animation.TimeInterpolator;
import android.animation.ValueAnimator;
import android.annotation.DrawableRes;
-import android.app.StatusBarManager;
+import android.app.StatusBarManager.NavbarFlags;
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.Canvas;
@@ -113,7 +116,8 @@ public class NavigationBarView extends FrameLayout {
boolean mLongClickableAccessibilityButton;
int mDisabledFlags = 0;
- int mNavigationIconHints = 0;
+ @NavbarFlags
+ private int mNavbarFlags;
private int mNavBarMode;
private boolean mImeDrawsImeNavBar;
@@ -176,7 +180,7 @@ public class NavigationBarView extends FrameLayout {
*/
private final boolean mImeCanRenderGesturalNavButtons = canImeRenderGesturalNavButtons();
private Gefingerpoken mTouchHandler;
- private boolean mOverviewProxyEnabled;
+ private boolean mLauncherProxyEnabled;
private boolean mShowSwipeUpUi;
private UpdateActiveTouchRegionsCallback mUpdateActiveTouchRegionsCallback;
@@ -210,7 +214,11 @@ public class NavigationBarView extends FrameLayout {
}
}
- public void onBackAltCleared() {
+ /**
+ * Called when the back button is no longer visually adjusted to indicate that it will
+ * dismiss the IME when pressed.
+ */
+ public void onBackDismissImeCleared() {
ButtonDispatcher backButton = getBackButton();
// When dismissing ime during unlock, force the back button to run the same appearance
@@ -499,10 +507,9 @@ public class NavigationBarView extends FrameLayout {
}
private void orientBackButton(KeyButtonDrawable drawable) {
- final boolean useAltBack =
- (mNavigationIconHints & StatusBarManager.NAVIGATION_HINT_BACK_ALT) != 0;
+ final boolean isBackDismissIme = (mNavbarFlags & NAVBAR_BACK_DISMISS_IME) != 0;
final boolean isRtl = mConfiguration.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
- float degrees = useAltBack ? (isRtl ? 90 : -90) : 0;
+ float degrees = isBackDismissIme ? (isRtl ? 90 : -90) : 0;
if (drawable.getRotation() == degrees) {
return;
}
@@ -514,7 +521,7 @@ public class NavigationBarView extends FrameLayout {
// Animate the back button's rotation to the new degrees and only in portrait move up the
// back button to line up with the other buttons
- float targetY = !mShowSwipeUpUi && !mIsVertical && useAltBack
+ float targetY = !mShowSwipeUpUi && !mIsVertical && isBackDismissIme
? - getResources().getDimension(R.dimen.navbar_back_button_ime_offset)
: 0;
ObjectAnimator navBarAnimator = ObjectAnimator.ofPropertyValuesHolder(drawable,
@@ -555,22 +562,25 @@ public class NavigationBarView extends FrameLayout {
super.setLayoutDirection(layoutDirection);
}
- void setNavigationIconHints(int hints) {
- if (hints == mNavigationIconHints) return;
- mNavigationIconHints = hints;
+ void setNavbarFlags(@NavbarFlags int flags) {
+ if (flags == mNavbarFlags) {
+ return;
+ }
+ mNavbarFlags = flags;
updateNavButtonIcons();
}
/**
- * Called when the boolean value of whether to adjust the back button for the IME changed.
+ * Called when the state of the back button being visually adjusted to indicate that it will
+ * dismiss the IME when pressed has changed.
*
- * @param useBackAlt whether to adjust the back button for the IME.
+ * @param isBackDismissIme whether the back button is adjusted for IME dismissal.
*
* @see android.inputmethodservice.InputMethodService.BackDispositionMode
*/
- void onBackAltChanged(boolean useBackAlt) {
- if (!useBackAlt) {
- mTransitionListener.onBackAltCleared();
+ void onBackDismissImeChanged(boolean isBackDismissIme) {
+ if (!isBackDismissIme) {
+ mTransitionListener.onBackDismissImeCleared();
}
}
@@ -594,8 +604,7 @@ public class NavigationBarView extends FrameLayout {
// We have to replace or restore the back and home button icons when exiting or entering
// carmode, respectively. Recents are not available in CarMode in nav bar so change
// to recent icon is not required.
- final boolean useAltBack =
- (mNavigationIconHints & StatusBarManager.NAVIGATION_HINT_BACK_ALT) != 0;
+ final boolean isBackDismissIme = (mNavbarFlags & NAVBAR_BACK_DISMISS_IME) != 0;
KeyButtonDrawable backIcon = mBackIcon;
orientBackButton(backIcon);
KeyButtonDrawable homeIcon = mHomeDefaultIcon;
@@ -607,11 +616,12 @@ public class NavigationBarView extends FrameLayout {
updateRecentsIcon();
- // Update IME button visibility, a11y and rotate button always overrides the appearance
- boolean disableImeSwitcher =
- (mNavigationIconHints & StatusBarManager.NAVIGATION_HINT_IME_SWITCHER_SHOWN) == 0
- || isImeRenderingNavButtons();
- mContextualButtonGroup.setButtonVisibility(R.id.ime_switcher, !disableImeSwitcher);
+ // Update IME switcher button visibility, a11y and rotate button always overrides
+ // the appearance.
+ final boolean isImeSwitcherButtonVisible =
+ (mNavbarFlags & NAVBAR_IME_SWITCHER_BUTTON_VISIBLE) != 0
+ && !isImeRenderingNavButtons();
+ mContextualButtonGroup.setButtonVisibility(R.id.ime_switcher, isImeSwitcherButtonVisible);
mBarTransitions.reapplyDarkIntensity();
@@ -625,14 +635,14 @@ public class NavigationBarView extends FrameLayout {
boolean disableHomeHandle = disableRecent
&& ((mDisabledFlags & View.STATUS_BAR_DISABLE_HOME) != 0);
- boolean disableBack = !useAltBack && (mEdgeBackGestureHandler.isHandlingGestures()
+ boolean disableBack = !isBackDismissIme && (mEdgeBackGestureHandler.isHandlingGestures()
|| ((mDisabledFlags & View.STATUS_BAR_DISABLE_BACK) != 0))
|| isImeRenderingNavButtons();
// When screen pinning, don't hide back and home when connected service or back and
// recents buttons when disconnected from launcher service in screen pinning mode,
// as they are used for exiting.
- if (mOverviewProxyEnabled) {
+ if (mLauncherProxyEnabled) {
// Force disable recents when not in legacy mode
disableRecent |= !QuickStepContract.isLegacyMode(mNavBarMode);
if (mScreenPinningActive && !QuickStepContract.isGesturalMode(mNavBarMode)) {
@@ -663,9 +673,8 @@ public class NavigationBarView extends FrameLayout {
* Returns whether the IME is currently visible and drawing the nav buttons.
*/
boolean isImeRenderingNavButtons() {
- return mImeDrawsImeNavBar
- && mImeCanRenderGesturalNavButtons
- && (mNavigationIconHints & StatusBarManager.NAVIGATION_HINT_IME_SHOWN) != 0;
+ return mImeDrawsImeNavBar && mImeCanRenderGesturalNavButtons
+ && (mNavbarFlags & NAVBAR_IME_VISIBLE) != 0;
}
@VisibleForTesting
@@ -755,8 +764,8 @@ public class NavigationBarView extends FrameLayout {
}
}
- void onOverviewProxyConnectionChange(boolean enabled) {
- mOverviewProxyEnabled = enabled;
+ void onLauncherProxyConnectionChange(boolean enabled) {
+ mLauncherProxyEnabled = enabled;
}
void setShouldShowSwipeUpUi(boolean showSwipeUpUi) {
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/views/buttons/KeyButtonView.java b/packages/SystemUI/src/com/android/systemui/navigationbar/views/buttons/KeyButtonView.java
index 111a2d43f881..32a03e5b10e9 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/views/buttons/KeyButtonView.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/views/buttons/KeyButtonView.java
@@ -61,7 +61,7 @@ import com.android.internal.logging.UiEventLoggerImpl;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.systemui.Dependency;
import com.android.systemui.assist.AssistManager;
-import com.android.systemui.recents.OverviewProxyService;
+import com.android.systemui.recents.LauncherProxyService;
import com.android.systemui.res.R;
import com.android.systemui.shared.navigationbar.KeyButtonRipple;
import com.android.systemui.shared.system.QuickStepContract;
@@ -82,7 +82,7 @@ public class KeyButtonView extends ImageView implements ButtonInterface {
@VisibleForTesting boolean mLongClicked;
private OnClickListener mOnClickListener;
private final KeyButtonRipple mRipple;
- private final OverviewProxyService mOverviewProxyService;
+ private final LauncherProxyService mLauncherProxyService;
private final MetricsLogger mMetricsLogger = Dependency.get(MetricsLogger.class);
private final InputManagerGlobal mInputManagerGlobal;
private final Paint mOvalBgPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
@@ -181,7 +181,7 @@ public class KeyButtonView extends ImageView implements ButtonInterface {
mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
mRipple = new KeyButtonRipple(context, this, R.dimen.key_button_ripple_max_width);
- mOverviewProxyService = Dependency.get(OverviewProxyService.class);
+ mLauncherProxyService = Dependency.get(LauncherProxyService.class);
mInputManagerGlobal = manager;
setBackground(mRipple);
setWillNotDraw(false);
@@ -282,7 +282,7 @@ public class KeyButtonView extends ImageView implements ButtonInterface {
@Override
public boolean onTouchEvent(MotionEvent ev) {
- final boolean showSwipeUI = mOverviewProxyService.shouldShowSwipeUpUI();
+ final boolean showSwipeUI = mLauncherProxyService.shouldShowSwipeUpUI();
final int action = ev.getAction();
int x, y;
if (action == MotionEvent.ACTION_DOWN) {
diff --git a/packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayActionsViewModel.kt b/packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayActionsViewModel.kt
index 195b0cebe2eb..1b9251061f3d 100644
--- a/packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayActionsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayActionsViewModel.kt
@@ -21,7 +21,8 @@ import com.android.compose.animation.scene.Swipe
import com.android.compose.animation.scene.UserAction
import com.android.compose.animation.scene.UserActionResult
import com.android.compose.animation.scene.UserActionResult.HideOverlay
-import com.android.compose.animation.scene.UserActionResult.ReplaceByOverlay
+import com.android.compose.animation.scene.UserActionResult.ShowOverlay
+import com.android.compose.animation.scene.UserActionResult.ShowOverlay.HideCurrentOverlays
import com.android.systemui.scene.shared.model.Overlays
import com.android.systemui.scene.ui.viewmodel.SceneContainerEdge
import com.android.systemui.scene.ui.viewmodel.UserActionsViewModel
@@ -38,7 +39,10 @@ class NotificationsShadeOverlayActionsViewModel @AssistedInject constructor() :
Swipe.Up to HideOverlay(Overlays.NotificationsShade),
Back to HideOverlay(Overlays.NotificationsShade),
Swipe.Down(fromSource = SceneContainerEdge.TopRight) to
- ReplaceByOverlay(Overlays.QuickSettingsShade),
+ ShowOverlay(
+ Overlays.QuickSettingsShade,
+ hideCurrentOverlays = HideCurrentOverlays.Some(Overlays.NotificationsShade),
+ ),
)
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeUserActionsViewModel.kt b/packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeUserActionsViewModel.kt
deleted file mode 100644
index 398ace4b67f4..000000000000
--- a/packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeUserActionsViewModel.kt
+++ /dev/null
@@ -1,52 +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.notifications.ui.viewmodel
-
-import com.android.compose.animation.scene.Back
-import com.android.compose.animation.scene.Swipe
-import com.android.compose.animation.scene.UserAction
-import com.android.compose.animation.scene.UserActionResult
-import com.android.compose.animation.scene.UserActionResult.ReplaceByOverlay
-import com.android.systemui.scene.shared.model.Overlays
-import com.android.systemui.scene.shared.model.SceneFamilies
-import com.android.systemui.scene.ui.viewmodel.SceneContainerEdge
-import com.android.systemui.scene.ui.viewmodel.UserActionsViewModel
-import dagger.assisted.AssistedFactory
-import dagger.assisted.AssistedInject
-
-/**
- * Models the UI state for the user actions that the user can perform to navigate to other scenes.
- */
-class NotificationsShadeUserActionsViewModel @AssistedInject constructor() :
- UserActionsViewModel() {
-
- override suspend fun hydrateActions(setActions: (Map<UserAction, UserActionResult>) -> Unit) {
- setActions(
- mapOf(
- Back to SceneFamilies.Home,
- Swipe.Up to SceneFamilies.Home,
- Swipe.Down(fromSource = SceneContainerEdge.TopRight) to
- ReplaceByOverlay(Overlays.QuickSettingsShade),
- )
- )
- }
-
- @AssistedFactory
- interface Factory {
- fun create(): NotificationsShadeUserActionsViewModel
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
index afb852ae824c..c8f7be6d80b2 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
@@ -17,7 +17,6 @@
package com.android.systemui.qs;
import static com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import static com.android.systemui.Flags.quickSettingsVisualHapticsLongpress;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -364,12 +363,7 @@ public abstract class QSPanelControllerBase<T extends QSPanel> extends ViewContr
}
private void addTile(final QSTile tile, boolean collapsedView) {
- QSLongPressEffect longPressEffect;
- if (quickSettingsVisualHapticsLongpress()) {
- longPressEffect = mLongPressEffectProvider.get();
- } else {
- longPressEffect = null;
- }
+ QSLongPressEffect longPressEffect = mLongPressEffectProvider.get();
final QSTileViewImpl tileView = new QSTileViewImpl(
getContext(), collapsedView, longPressEffect);
final TileRecord r = new TileRecord(tile, tileView);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/BounceableInfo.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/BounceableInfo.kt
index c9d767e6d152..302242ca11dd 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/BounceableInfo.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/BounceableInfo.kt
@@ -16,7 +16,7 @@
package com.android.systemui.qs.panels.ui.compose
-import android.processor.immutability.Immutable
+import androidx.compose.runtime.Stable
import com.android.compose.animation.Bounceable
import com.android.systemui.qs.panels.shared.model.SizedTile
import com.android.systemui.qs.panels.ui.model.GridCell
@@ -24,7 +24,7 @@ import com.android.systemui.qs.panels.ui.model.TileGridCell
import com.android.systemui.qs.panels.ui.viewmodel.BounceableTileViewModel
import com.android.systemui.qs.panels.ui.viewmodel.TileViewModel
-@Immutable
+@Stable
data class BounceableInfo(
val bounceable: BounceableTileViewModel,
val previousTile: Bounceable?,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt
index 5cb30b999e13..b084f79a5bba 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt
@@ -19,8 +19,8 @@ package com.android.systemui.qs.panels.ui.compose
import androidx.compose.foundation.layout.Box
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
+import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
-import androidx.compose.runtime.key
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
@@ -49,6 +49,8 @@ fun ContentScope.QuickQuickSettings(
val squishiness by viewModel.squishinessViewModel.squishiness.collectAsStateWithLifecycle()
val scope = rememberCoroutineScope()
+ val spans by remember(sizedTiles) { derivedStateOf { sizedTiles.fastMap { it.width } } }
+
DisposableEffect(tiles) {
val token = Any()
tiles.forEach { it.startListening(token) }
@@ -62,26 +64,24 @@ fun ContentScope.QuickQuickSettings(
columns = columns,
columnSpacing = dimensionResource(R.dimen.qs_tile_margin_horizontal),
rowSpacing = dimensionResource(R.dimen.qs_tile_margin_vertical),
- spans = sizedTiles.fastMap { it.width },
+ spans = spans,
modifier = Modifier.sysuiResTag("qqs_tile_layout"),
+ keys = { sizedTiles[it].tile.spec },
) { spanIndex ->
val it = sizedTiles[spanIndex]
val column = cellIndex % columns
cellIndex += it.width
- key(it.tile.spec) {
- Tile(
- tile = it.tile,
- iconOnly = it.isIcon,
- modifier = Modifier.element(it.tile.spec.toElementKey(spanIndex)),
- squishiness = { squishiness },
- coroutineScope = scope,
- bounceableInfo = bounceables.bounceableInfo(it, spanIndex, column, columns),
- tileHapticsViewModelFactoryProvider =
- viewModel.tileHapticsViewModelFactoryProvider,
- // There should be no QuickQuickSettings when the details view is enabled.
- detailsViewModel = null,
- )
- }
+ Tile(
+ tile = it.tile,
+ iconOnly = it.isIcon,
+ modifier = Modifier.element(it.tile.spec.toElementKey(spanIndex)),
+ squishiness = { squishiness },
+ coroutineScope = scope,
+ bounceableInfo = bounceables.bounceableInfo(it, spanIndex, column, columns),
+ tileHapticsViewModelFactoryProvider = viewModel.tileHapticsViewModelFactoryProvider,
+ // There should be no QuickQuickSettings when the details view is enabled.
+ detailsViewModel = null,
+ )
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/EditTile.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/EditTile.kt
index d2ee126ace91..2cccaddc02a8 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/EditTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/EditTile.kt
@@ -498,11 +498,9 @@ fun gridHeight(rows: Int, tileHeight: Dp, tilePadding: Dp, gridPadding: Dp): Dp
return ((tileHeight + tilePadding) * rows) + gridPadding * 2
}
-private fun GridCell.key(index: Int, dragAndDropState: DragAndDropState): Any {
+private fun GridCell.key(index: Int): Any {
return when (this) {
- is TileGridCell -> {
- if (dragAndDropState.isMoving(tile.tileSpec)) index else key
- }
+ is TileGridCell -> key
is SpacerGridCell -> index
}
}
@@ -510,10 +508,13 @@ private fun GridCell.key(index: Int, dragAndDropState: DragAndDropState): Any {
/**
* Adds a list of [GridCell] to the lazy grid
*
- * @param cells the pairs of [GridCell] to [AnimatableTileViewModel]
+ * @param cells the pairs of [GridCell] to [BounceableTileViewModel]
+ * @param columns the number of columns of this tile grid
* @param dragAndDropState the [DragAndDropState] for this grid
* @param selectionState the [MutableSelectionState] for this grid
- * @param onToggleSize the callback when a tile's size is toggled
+ * @param coroutineScope the [CoroutineScope] to be used for the tiles
+ * @param largeTilesSpan the width used for large tiles
+ * @param onResize the callback when a tile has a new [ResizeOperation]
*/
fun LazyGridScope.EditTiles(
cells: List<Pair<GridCell, BounceableTileViewModel>>,
@@ -526,7 +527,7 @@ fun LazyGridScope.EditTiles(
) {
items(
count = cells.size,
- key = { cells[it].first.key(it, dragAndDropState) },
+ key = { cells[it].first.key(it) },
span = { cells[it].first.span },
contentType = { TileType },
) { index ->
@@ -536,13 +537,12 @@ fun LazyGridScope.EditTiles(
// If the tile is being moved, replace it with a visible spacer
SpacerGridCell(
Modifier.background(
- color =
- MaterialTheme.colorScheme.secondary.copy(
- alpha = EditModeTileDefaults.PLACEHOLDER_ALPHA
- ),
- shape = RoundedCornerShape(InactiveCornerRadius),
- )
- .animateItem()
+ color =
+ MaterialTheme.colorScheme.secondary.copy(
+ alpha = EditModeTileDefaults.PLACEHOLDER_ALPHA
+ ),
+ shape = RoundedCornerShape(InactiveCornerRadius),
+ )
)
} else {
TileGridCell(
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/InfiniteGridLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/InfiniteGridLayout.kt
index 4432d336237f..cc4c3af1dc63 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/InfiniteGridLayout.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/InfiniteGridLayout.kt
@@ -18,8 +18,8 @@ package com.android.systemui.qs.panels.ui.compose.infinitegrid
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
+import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
-import androidx.compose.runtime.key
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
@@ -86,27 +86,28 @@ constructor(
val scope = rememberCoroutineScope()
var cellIndex = 0
+ val spans by remember(sizedTiles) { derivedStateOf { sizedTiles.fastMap { it.width } } }
+
VerticalSpannedGrid(
columns = columns,
columnSpacing = dimensionResource(R.dimen.qs_tile_margin_horizontal),
rowSpacing = dimensionResource(R.dimen.qs_tile_margin_vertical),
- spans = sizedTiles.fastMap { it.width },
+ spans = spans,
+ keys = { sizedTiles[it].tile.spec },
) { spanIndex ->
val it = sizedTiles[spanIndex]
val column = cellIndex % columns
cellIndex += it.width
- key(it.tile.spec) {
- Tile(
- tile = it.tile,
- iconOnly = iconTilesViewModel.isIconTile(it.tile.spec),
- modifier = Modifier.element(it.tile.spec.toElementKey(spanIndex)),
- squishiness = { squishiness },
- tileHapticsViewModelFactoryProvider = tileHapticsViewModelFactoryProvider,
- coroutineScope = scope,
- bounceableInfo = bounceables.bounceableInfo(it, spanIndex, column, columns),
- detailsViewModel = detailsViewModel,
- )
- }
+ Tile(
+ tile = it.tile,
+ iconOnly = iconTilesViewModel.isIconTile(it.tile.spec),
+ modifier = Modifier.element(it.tile.spec.toElementKey(spanIndex)),
+ squishiness = { squishiness },
+ tileHapticsViewModelFactoryProvider = tileHapticsViewModelFactoryProvider,
+ coroutineScope = scope,
+ bounceableInfo = bounceables.bounceableInfo(it, spanIndex, column, columns),
+ detailsViewModel = detailsViewModel,
+ )
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/TileSpec.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/TileSpec.kt
index 16c27223a471..8a627c452081 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/TileSpec.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/TileSpec.kt
@@ -18,6 +18,7 @@ package com.android.systemui.qs.pipeline.shared
import android.content.ComponentName
import android.text.TextUtils
+import androidx.compose.runtime.Stable
import com.android.systemui.qs.external.CustomTile
/**
@@ -34,6 +35,7 @@ sealed class TileSpec private constructor(open val spec: String) {
data object Invalid : TileSpec("")
/** Container for the spec of a tile provided by SystemUI. */
+ @Stable
data class PlatformTileSpec internal constructor(override val spec: String) : TileSpec(spec) {
override fun toString(): String {
return "P($spec)"
@@ -45,6 +47,7 @@ sealed class TileSpec private constructor(open val spec: String) {
*
* [componentName] indicates the associated `TileService`.
*/
+ @Stable
data class CustomTileSpec
internal constructor(override val spec: String, val componentName: ComponentName) :
TileSpec(spec) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTileNewImpl.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTileNewImpl.kt
index 6d3e5d07c251..b1f99cccff70 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTileNewImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTileNewImpl.kt
@@ -61,6 +61,7 @@ constructor(
private val internetDialogManager: InternetDialogManager,
private val wifiStateWorker: WifiStateWorker,
private val accessPointController: AccessPointController,
+ private val internetDetailsViewModelFactory: InternetDetailsViewModel.Factory,
) :
QSTileImpl<QSTile.BooleanState>(
host,
@@ -107,7 +108,7 @@ constructor(
}
override fun getDetailsViewModel(): TileDetailsViewModel {
- return InternetDetailsViewModel { longClick(null) }
+ return internetDetailsViewModelFactory.create { longClick(null) }
}
override fun handleSecondaryClick(expandable: Expandable?) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ModesTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/ModesTile.kt
index 0051bf5de7f2..ad5dd27f07c2 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ModesTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ModesTile.kt
@@ -35,12 +35,14 @@ import com.android.systemui.modes.shared.ModesUiIcons
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.plugins.qs.QSTile
+import com.android.systemui.plugins.qs.TileDetailsViewModel
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.qs.QSHost
import com.android.systemui.qs.QsEventLogger
import com.android.systemui.qs.asQSTileIcon
import com.android.systemui.qs.logging.QSLogger
import com.android.systemui.qs.tileimpl.QSTileImpl
+import com.android.systemui.qs.tiles.dialog.ModesDetailsViewModel
import com.android.systemui.qs.tiles.impl.modes.domain.interactor.ModesTileDataInteractor
import com.android.systemui.qs.tiles.impl.modes.domain.interactor.ModesTileUserActionInteractor
import com.android.systemui.qs.tiles.impl.modes.domain.model.ModesTileModel
@@ -48,6 +50,7 @@ import com.android.systemui.qs.tiles.impl.modes.ui.ModesTileMapper
import com.android.systemui.qs.tiles.viewmodel.QSTileConfigProvider
import com.android.systemui.qs.tiles.viewmodel.QSTileState
import com.android.systemui.res.R
+import com.android.systemui.statusbar.policy.ui.dialog.viewmodel.ModesDialogViewModel
import javax.inject.Inject
import kotlinx.coroutines.runBlocking
@@ -67,6 +70,7 @@ constructor(
private val dataInteractor: ModesTileDataInteractor,
private val tileMapper: ModesTileMapper,
private val userActionInteractor: ModesTileUserActionInteractor,
+ private val modesDialogViewModel: ModesDialogViewModel,
) :
QSTileImpl<QSTile.State>(
host,
@@ -114,6 +118,13 @@ constructor(
userActionInteractor.handleToggleClick(model)
}
+ override fun getDetailsViewModel(): TileDetailsViewModel {
+ return ModesDetailsViewModel(
+ onSettingsClick = { userActionInteractor.handleLongClick(null) },
+ viewModel = modesDialogViewModel,
+ )
+ }
+
override fun getLongClickIntent(): Intent = userActionInteractor.longClickIntent
@VisibleForTesting
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDetailsContentManager.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDetailsContentManager.kt
index c64532a2c4ba..733159e285e8 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDetailsContentManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDetailsContentManager.kt
@@ -57,10 +57,8 @@ import com.android.settingslib.satellite.SatelliteDialogUtils.mayStartSatelliteW
import com.android.settingslib.wifi.WifiEnterpriseRestrictionUtils
import com.android.systemui.Prefs
import com.android.systemui.accessibility.floatingmenu.AnnotationLinkSpan
-import com.android.systemui.animation.DialogTransitionAnimator
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.qs.flags.QsDetailedView
import com.android.systemui.res.R
import com.android.systemui.statusbar.phone.SystemUIDialog
import com.android.systemui.statusbar.policy.KeyguardStateController
@@ -75,11 +73,7 @@ import kotlinx.coroutines.Job
/**
* View content for the Internet tile details that handles all UI interactions and state management.
- *
- * @param internetDialog non-null if the details should be shown as part of a dialog and null
- * otherwise.
*/
-// TODO: b/377388104 Make this content for details view only.
class InternetDetailsContentManager
@AssistedInject
constructor(
@@ -88,9 +82,7 @@ constructor(
@Assisted(CAN_CONFIG_WIFI) private val canConfigWifi: Boolean,
@Assisted private val coroutineScope: CoroutineScope,
@Assisted private var context: Context,
- @Assisted private var internetDialog: SystemUIDialog?,
private val uiEventLogger: UiEventLogger,
- private val dialogTransitionAnimator: DialogTransitionAnimator,
@Main private val handler: Handler,
@Background private val backgroundExecutor: Executor,
private val keyguard: KeyguardStateController,
@@ -104,8 +96,6 @@ constructor(
// UI Components
private lateinit var contentView: View
- private lateinit var internetDialogTitleView: TextView
- private lateinit var internetDialogSubTitleView: TextView
private lateinit var divider: View
private lateinit var progressBar: ProgressBar
private lateinit var ethernetLayout: LinearLayout
@@ -132,7 +122,6 @@ constructor(
private lateinit var shareWifiButton: Button
private lateinit var airplaneModeButton: Button
private var alertDialog: AlertDialog? = null
- private lateinit var doneButton: Button
private val canChangeWifiState =
WifiEnterpriseRestrictionUtils.isChangeWifiStateAllowed(context)
@@ -153,7 +142,6 @@ constructor(
@Assisted(CAN_CONFIG_WIFI) canConfigWifi: Boolean,
coroutineScope: CoroutineScope,
context: Context,
- internetDialog: SystemUIDialog?,
): InternetDetailsContentManager
}
@@ -209,8 +197,6 @@ constructor(
}
// Network layouts
- internetDialogTitleView = contentView.requireViewById(R.id.internet_dialog_title)
- internetDialogSubTitleView = contentView.requireViewById(R.id.internet_dialog_subtitle)
divider = contentView.requireViewById(R.id.divider)
progressBar = contentView.requireViewById(R.id.wifi_searching_progress)
@@ -219,15 +205,6 @@ constructor(
setMobileLayout()
ethernetLayout = contentView.requireViewById(R.id.ethernet_layout)
- // Done button is only visible for the dialog view
- doneButton = contentView.requireViewById(R.id.done_button)
- if (internetDialog == null) {
- doneButton.visibility = View.GONE
- } else {
- // Set done button if qs details view is not enabled.
- doneButton.setOnClickListener { internetDialog!!.dismiss() }
- }
-
// Share WiFi
shareWifiButton = contentView.requireViewById(R.id.share_wifi_button)
shareWifiButton.setOnClickListener { view ->
@@ -251,6 +228,17 @@ constructor(
// Background drawables
backgroundOn = context.getDrawable(R.drawable.settingslib_switch_bar_bg_on)
backgroundOff = context.getDrawable(R.drawable.internet_dialog_selected_effect)
+
+ // Done button is only visible for the dialog view
+ contentView.findViewById<Button>(R.id.done_button).apply { visibility = View.GONE }
+
+ // Title and subtitle will be added in the `TileDetails`
+ contentView.findViewById<TextView>(R.id.internet_dialog_title).apply {
+ visibility = View.GONE
+ }
+ contentView.findViewById<TextView>(R.id.internet_dialog_subtitle).apply {
+ visibility = View.GONE
+ }
}
private fun setWifiLayout() {
@@ -336,21 +324,19 @@ constructor(
}
}
- private fun getDialogTitleText(): CharSequence {
- return internetDetailsContentController.getDialogTitleText()
+ fun getTitleText(): String {
+ return internetDetailsContentController.getDialogTitleText().toString()
+ }
+
+ fun getSubtitleText(): String {
+ return internetDetailsContentController.getSubtitleText(isProgressBarVisible).toString()
}
private fun updateDetailsUI(internetContent: InternetContent) {
if (DEBUG) {
Log.d(TAG, "updateDetailsUI ")
}
- if (QsDetailedView.isEnabled) {
- internetDialogTitleView.visibility = View.GONE
- internetDialogSubTitleView.visibility = View.GONE
- } else {
- internetDialogTitleView.text = internetContent.internetDialogTitleString
- internetDialogSubTitleView.text = internetContent.internetDialogSubTitle
- }
+
airplaneModeButton.visibility =
if (internetContent.isAirplaneModeEnabled) View.VISIBLE else View.GONE
@@ -361,17 +347,11 @@ constructor(
private fun getStartingInternetContent(): InternetContent {
return InternetContent(
- internetDialogTitleString = getDialogTitleText(),
- internetDialogSubTitle = getSubtitleText(),
isWifiEnabled = internetDetailsContentController.isWifiEnabled,
isDeviceLocked = internetDetailsContentController.isDeviceLocked,
)
}
- private fun getSubtitleText(): String {
- return internetDetailsContentController.getSubtitleText(isProgressBarVisible).toString()
- }
-
@VisibleForTesting
internal fun hideWifiViews() {
setProgressBarVisible(false)
@@ -393,7 +373,6 @@ constructor(
progressBar.visibility = if (visible) View.VISIBLE else View.GONE
progressBar.isIndeterminate = visible
divider.visibility = if (visible) View.GONE else View.VISIBLE
- internetDialogSubTitleView.text = getSubtitleText()
}
private fun showTurnOffAutoDataSwitchDialog(subId: Int) {
@@ -418,12 +397,7 @@ constructor(
SystemUIDialog.setShowForAllUsers(alertDialog, true)
SystemUIDialog.registerDismissListener(alertDialog)
SystemUIDialog.setWindowOnTop(alertDialog, keyguard.isShowing())
- if (QsDetailedView.isEnabled) {
- alertDialog!!.show()
- } else {
- dialogTransitionAnimator.showFromDialog(alertDialog!!, internetDialog!!, null, false)
- Log.e(TAG, "Internet dialog is shown with the refactor code")
- }
+ alertDialog!!.show()
}
private fun shouldShowMobileDialog(): Boolean {
@@ -466,11 +440,8 @@ constructor(
SystemUIDialog.setShowForAllUsers(alertDialog, true)
SystemUIDialog.registerDismissListener(alertDialog)
SystemUIDialog.setWindowOnTop(alertDialog, keyguard.isShowing())
- if (QsDetailedView.isEnabled) {
- alertDialog!!.show()
- } else {
- dialogTransitionAnimator.showFromDialog(alertDialog!!, internetDialog!!, null, false)
- }
+
+ alertDialog!!.show()
}
private fun onClickConnectedWifi(view: View?) {
@@ -803,7 +774,6 @@ constructor(
secondaryMobileNetworkLayout?.setOnClickListener(null)
seeAllLayout.setOnClickListener(null)
wifiToggle.setOnCheckedChangeListener(null)
- doneButton.setOnClickListener(null)
shareWifiButton.setOnClickListener(null)
airplaneModeButton.setOnClickListener(null)
internetDetailsContentController.onStop()
@@ -825,8 +795,6 @@ constructor(
private fun getInternetContent(shouldUpdateMobileNetwork: Boolean): InternetContent {
return InternetContent(
shouldUpdateMobileNetwork = shouldUpdateMobileNetwork,
- internetDialogTitleString = getDialogTitleText(),
- internetDialogSubTitle = getSubtitleText(),
activeNetworkIsCellular =
if (shouldUpdateMobileNetwork)
internetDetailsContentController.activeNetworkIsCellular()
@@ -924,10 +892,7 @@ constructor(
if (DEBUG) {
Log.d(TAG, "dismissDialog")
}
- if (internetDialog != null) {
- internetDialog!!.dismiss()
- internetDialog = null
- }
+ // TODO: b/377388104 Close details view
}
override fun onAccessPointsChanged(
@@ -967,8 +932,6 @@ constructor(
@VisibleForTesting
data class InternetContent(
- val internetDialogTitleString: CharSequence,
- val internetDialogSubTitle: CharSequence,
val isAirplaneModeEnabled: Boolean = false,
val hasEthernet: Boolean = false,
val shouldUpdateMobileNetwork: Boolean = false,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDetailsViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDetailsViewModel.kt
index f239a179d79a..df4dddbca9e6 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDetailsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDetailsViewModel.kt
@@ -16,44 +16,93 @@
package com.android.systemui.qs.tiles.dialog
+import android.util.Log
import android.view.LayoutInflater
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.viewinterop.AndroidView
import com.android.systemui.plugins.qs.TileDetailsViewModel
import com.android.systemui.res.R
+import com.android.systemui.statusbar.connectivity.AccessPointController
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
-class InternetDetailsViewModel(
- onLongClick: () -> Unit,
+class InternetDetailsViewModel
+@AssistedInject
+constructor(
+ private val accessPointController: AccessPointController,
+ private val contentManagerFactory: InternetDetailsContentManager.Factory,
+ @Assisted private val onLongClick: () -> Unit,
) : TileDetailsViewModel() {
- private val _onLongClick = onLongClick
+ private lateinit var internetDetailsContentManager: InternetDetailsContentManager
@Composable
override fun GetContentView() {
+ val coroutineScope = rememberCoroutineScope()
+ val context = LocalContext.current
+
+ internetDetailsContentManager = remember {
+ contentManagerFactory.create(
+ canConfigMobileData = accessPointController.canConfigMobileData(),
+ canConfigWifi = accessPointController.canConfigWifi(),
+ coroutineScope = coroutineScope,
+ context = context,
+ )
+ }
AndroidView(
modifier = Modifier.fillMaxWidth().fillMaxHeight(),
factory = { context ->
- // Inflate with the existing dialog xml layout
- LayoutInflater.from(context)
- .inflate(R.layout.internet_connectivity_dialog, null)
- // TODO: b/377388104 - Implement the internet details view
+ // Inflate with the existing dialog xml layout and bind it with the manager
+ val view =
+ LayoutInflater.from(context)
+ .inflate(R.layout.internet_connectivity_dialog, null)
+ internetDetailsContentManager.bind(view)
+
+ view
+ // TODO: b/377388104 - Polish the internet details view UI
+ },
+ onRelease = {
+ internetDetailsContentManager.unBind()
+ if (DEBUG) {
+ Log.d(TAG, "onRelease")
+ }
},
)
}
override fun clickOnSettingsButton() {
- _onLongClick()
+ onLongClick()
}
override fun getTitle(): String {
+ // TODO: b/377388104 make title and sub title mutable states of string
+ // by internetDetailsContentManager.getTitleText()
+ // TODO: test title change between airplane mode and not airplane mode
// TODO: b/377388104 Update the placeholder text
return "Internet"
}
override fun getSubTitle(): String {
+ // TODO: b/377388104 make title and sub title mutable states of string
+ // by internetDetailsContentManager.getSubtitleText()
+ // TODO: test subtitle change between airplane mode and not airplane mode
// TODO: b/377388104 Update the placeholder text
return "Tab a network to connect"
}
+
+ @AssistedFactory
+ interface Factory {
+ fun create(onLongClick: () -> Unit): InternetDetailsViewModel
+ }
+
+ companion object {
+ private const val TAG = "InternetDetailsVModel"
+ private val DEBUG: Boolean = Log.isLoggable(TAG, Log.DEBUG)
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/ModesDetailsViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/ModesDetailsViewModel.kt
new file mode 100644
index 000000000000..511597d05d37
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/ModesDetailsViewModel.kt
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.dialog
+
+import androidx.compose.runtime.Composable
+import com.android.systemui.plugins.qs.TileDetailsViewModel
+import com.android.systemui.statusbar.policy.ui.dialog.composable.ModeTileGrid
+import com.android.systemui.statusbar.policy.ui.dialog.viewmodel.ModesDialogViewModel
+
+/** The view model used for the modes details view in the Quick Settings */
+class ModesDetailsViewModel(
+ private val onSettingsClick: () -> Unit,
+ private val viewModel: ModesDialogViewModel,
+) : TileDetailsViewModel() {
+ @Composable
+ override fun GetContentView() {
+ // TODO(b/378513940): Finish implementing this function.
+ ModeTileGrid(viewModel = viewModel)
+ }
+
+ override fun clickOnSettingsButton() {
+ onSettingsClick()
+ }
+
+ override fun getTitle(): String {
+ // TODO(b/388321032): Replace this string with a string in a translatable xml file.
+ return "Modes"
+ }
+
+ override fun getSubTitle(): String {
+ // TODO(b/388321032): Replace this string with a string in a translatable xml file.
+ return "Silences interruptions from people and apps in different circumstances"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileUserActionInteractor.kt
index c4f9515b819f..6e2c437b9c16 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileUserActionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileUserActionInteractor.kt
@@ -43,6 +43,7 @@ constructor(
private val wifiStateWorker: WifiStateWorker,
private val accessPointController: AccessPointController,
private val qsTileIntentUserActionHandler: QSTileIntentUserInputHandler,
+ private val internetDetailsViewModelFactory: InternetDetailsViewModel.Factory,
) : QSTileUserActionInteractor<InternetTileModel> {
override suspend fun handleInput(input: QSTileInput<InternetTileModel>): Unit =
@@ -70,7 +71,7 @@ constructor(
}
override val detailsViewModel: TileDetailsViewModel =
- InternetDetailsViewModel { handleLongClick(null) }
+ internetDetailsViewModelFactory.create { handleLongClick(null) }
private fun handleLongClick(expandable:Expandable?){
qsTileIntentUserActionHandler.handle(
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractor.kt
index 5ce7f0d039c8..b5da044b886a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractor.kt
@@ -54,7 +54,7 @@ constructor(
handleToggleClick(input.data)
}
is QSTileUserAction.LongClick -> {
- qsTileIntentUserInputHandler.handle(action.expandable, longClickIntent)
+ handleLongClick(action.expandable)
}
}
}
@@ -95,6 +95,10 @@ constructor(
}
}
+ fun handleLongClick(expandable: Expandable?) {
+ qsTileIntentUserInputHandler.handle(expandable, longClickIntent)
+ }
+
companion object {
const val TAG = "ModesTileUserActionInteractor"
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayActionsViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayActionsViewModel.kt
index 000f7f8a7d31..5bc26f50f70f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayActionsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayActionsViewModel.kt
@@ -21,7 +21,8 @@ import com.android.compose.animation.scene.Swipe
import com.android.compose.animation.scene.UserAction
import com.android.compose.animation.scene.UserActionResult
import com.android.compose.animation.scene.UserActionResult.HideOverlay
-import com.android.compose.animation.scene.UserActionResult.ReplaceByOverlay
+import com.android.compose.animation.scene.UserActionResult.ShowOverlay
+import com.android.compose.animation.scene.UserActionResult.ShowOverlay.HideCurrentOverlays
import com.android.systemui.qs.panels.ui.viewmodel.EditModeViewModel
import com.android.systemui.scene.shared.model.Overlays
import com.android.systemui.scene.ui.viewmodel.SceneContainerEdge
@@ -47,7 +48,11 @@ constructor(private val editModeViewModel: EditModeViewModel) : UserActionsViewM
}
put(
Swipe.Down(fromSource = SceneContainerEdge.TopLeft),
- ReplaceByOverlay(Overlays.NotificationsShade),
+ ShowOverlay(
+ Overlays.NotificationsShade,
+ hideCurrentOverlays =
+ HideCurrentOverlays.Some(Overlays.QuickSettingsShade),
+ ),
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/LauncherProxyService.java
index 60c2cca1ae8b..9af4630bf492 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/LauncherProxyService.java
@@ -100,14 +100,14 @@ import com.android.systemui.navigationbar.views.NavigationBar;
import com.android.systemui.navigationbar.views.NavigationBarView;
import com.android.systemui.navigationbar.views.buttons.KeyButtonView;
import com.android.systemui.process.ProcessWrapper;
-import com.android.systemui.recents.OverviewProxyService.OverviewProxyListener;
+import com.android.systemui.recents.LauncherProxyService.LauncherProxyListener;
import com.android.systemui.scene.domain.interactor.SceneInteractor;
import com.android.systemui.scene.shared.flag.SceneContainerFlag;
import com.android.systemui.settings.DisplayTracker;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.shade.ShadeViewController;
import com.android.systemui.shade.domain.interactor.ShadeInteractor;
-import com.android.systemui.shared.recents.IOverviewProxy;
+import com.android.systemui.shared.recents.ILauncherProxy;
import com.android.systemui.shared.recents.ISystemUiProxy;
import com.android.systemui.shared.system.QuickStepContract;
import com.android.systemui.shared.system.QuickStepContract.SystemUiStateFlags;
@@ -135,16 +135,16 @@ import javax.inject.Inject;
import javax.inject.Provider;
/**
- * Class to send information from overview to launcher with a binder.
+ * Class to send information from SysUI to Launcher with a binder.
*/
@SysUISingleton
-public class OverviewProxyService implements CallbackController<OverviewProxyListener>,
+public class LauncherProxyService implements CallbackController<LauncherProxyListener>,
NavigationModeController.ModeChangedListener, Dumpable {
@VisibleForTesting
static final String ACTION_QUICKSTEP = "android.intent.action.QUICKSTEP_SERVICE";
- public static final String TAG_OPS = "OverviewProxyService";
+ public static final String TAG_OPS = "LauncherProxyService";
private static final long BACKOFF_MILLIS = 1000;
private static final long DEFERRED_CALLBACK_MILLIS = 5000;
// Max backoff caps at 5 mins
@@ -165,7 +165,7 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis
private final Runnable mConnectionRunnable = () ->
internalConnectToCurrentUser("runnable: startConnectionToCurrentUser");
private final ComponentName mRecentsComponentName;
- private final List<OverviewProxyListener> mConnectionCallbacks = new ArrayList<>();
+ private final List<LauncherProxyListener> mConnectionCallbacks = new ArrayList<>();
private final Intent mQuickStepIntent;
private final ScreenshotHelper mScreenshotHelper;
private final CommandQueue mCommandQueue;
@@ -179,12 +179,12 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis
private final BroadcastDispatcher mBroadcastDispatcher;
private final BackAnimation mBackAnimation;
- private IOverviewProxy mOverviewProxy;
+ private ILauncherProxy mLauncherProxy;
private int mConnectionBackoffAttempts;
private boolean mBound;
private boolean mIsEnabled;
- // This is set to false when the overview service is requested to be bound until it is notified
- // that the previous service has been cleaned up in IOverviewProxy#onUnbind(). It is also set to
+ // This is set to false when the launcher service is requested to be bound until it is notified
+ // that the previous service has been cleaned up in ILauncherProxy#onUnbind(). It is also set to
// true after a 1000ms timeout by mDeferredBindAfterTimedOutCleanup.
private boolean mIsPrevServiceCleanedUp = true;
@@ -341,7 +341,7 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis
@Override
public void updateContextualEduStats(boolean isTrackpadGesture, String gestureType) {
verifyCallerAndClearCallingIdentityPostMain("updateContextualEduStats",
- () -> mHandler.post(() -> OverviewProxyService.this.updateContextualEduStats(
+ () -> mHandler.post(() -> LauncherProxyService.this.updateContextualEduStats(
isTrackpadGesture, GestureType.valueOf(gestureType))));
}
@@ -504,7 +504,7 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis
public void onReceive(Context context, Intent intent) {
if (Objects.equals(intent.getAction(), Intent.ACTION_USER_UNLOCKED)) {
if (keyguardPrivateNotifications()) {
- // Start the overview connection to the launcher service
+ // Start the launcher connection to the launcher service
// Connect if user hasn't connected yet
if (getProxy() == null) {
startConnectionToCurrentUser();
@@ -546,14 +546,14 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis
}
};
- private final ServiceConnection mOverviewServiceConnection = new ServiceConnection() {
+ private final ServiceConnection mLauncherServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
- Log.d(TAG_OPS, "Overview proxy service connected");
+ Log.d(TAG_OPS, "Launcher proxy service connected");
mConnectionBackoffAttempts = 0;
mHandler.removeCallbacks(mDeferredConnectionCallback);
try {
- service.linkToDeath(mOverviewServiceDeathRcpt, 0);
+ service.linkToDeath(mLauncherServiceDeathRcpt, 0);
} catch (RemoteException e) {
// Failed to link to death (process may have died between binding and connecting),
// just unbind the service for now and retry again
@@ -564,7 +564,7 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis
}
mCurrentBoundedUserId = mUserTracker.getUserId();
- mOverviewProxy = IOverviewProxy.Stub.asInterface(service);
+ mLauncherProxy = ILauncherProxy.Stub.asInterface(service);
Bundle params = new Bundle();
addInterface(mSysUiProxy, params);
@@ -574,8 +574,8 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis
mShellInterface.createExternalInterfaces(params);
try {
- Log.d(TAG_OPS, "OverviewProxyService connected, initializing overview proxy");
- mOverviewProxy.onInitialize(params);
+ Log.d(TAG_OPS, "LauncherProxyService connected, initializing launcher proxy");
+ mLauncherProxy.onInitialize(params);
} catch (RemoteException e) {
mCurrentBoundedUserId = -1;
Log.e(TAG_OPS, "Failed to call onInitialize()", e);
@@ -614,7 +614,7 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis
private final StatusBarWindowCallback mStatusBarWindowCallback = this::onStatusBarStateChanged;
// This is the death handler for the binder from the launcher service
- private final IBinder.DeathRecipient mOverviewServiceDeathRcpt
+ private final IBinder.DeathRecipient mLauncherServiceDeathRcpt
= this::cleanupAfterDeath;
private final IVoiceInteractionSessionListener mVoiceInteractionSessionListener =
@@ -632,7 +632,7 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis
@Override
public void onVoiceSessionWindowVisibilityChanged(boolean visible) {
mContext.getMainExecutor().execute(() ->
- OverviewProxyService.this.onVoiceSessionWindowVisibilityChanged(visible));
+ LauncherProxyService.this.onVoiceSessionWindowVisibilityChanged(visible));
}
@Override
@@ -652,7 +652,7 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
@Inject
- public OverviewProxyService(Context context,
+ public LauncherProxyService(Context context,
@Main Executor mainExecutor,
CommandQueue commandQueue,
ShellInterface shellInterface,
@@ -755,14 +755,14 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis
@Override
public void moveFocusedTaskToStageSplit(int displayId, boolean leftOrTop) {
- if (mOverviewProxy != null) {
+ if (mLauncherProxy != null) {
try {
if (DesktopModeStatus.canEnterDesktopMode(mContext)
&& (sysUiState.getFlags()
& SYSUI_STATE_FREEFORM_ACTIVE_IN_DESKTOP_MODE) != 0) {
return;
}
- mOverviewProxy.enterStageSplitFromRunningApp(leftOrTop);
+ mLauncherProxy.enterStageSplitFromRunningApp(leftOrTop);
} catch (RemoteException e) {
Log.w(TAG_OPS, "Unable to enter stage split from the current running app");
}
@@ -817,12 +817,12 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis
private void notifySystemUiStateFlags(@SystemUiStateFlags long flags) {
if (SysUiState.DEBUG) {
- Log.d(TAG_OPS, "Notifying sysui state change to overview service: proxy="
- + mOverviewProxy + " flags=" + flags);
+ Log.d(TAG_OPS, "Notifying sysui state change to launcher service: proxy="
+ + mLauncherProxy + " flags=" + flags);
}
try {
- if (mOverviewProxy != null) {
- mOverviewProxy.onSystemUiStateChanged(flags);
+ if (mLauncherProxy != null) {
+ mLauncherProxy.onSystemUiStateChanged(flags);
}
} catch (RemoteException e) {
Log.e(TAG_OPS, "Failed to notify sysui state change", e);
@@ -854,9 +854,9 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis
}
private void dispatchNavButtonBounds() {
- if (mOverviewProxy != null && mActiveNavBarRegion != null) {
+ if (mLauncherProxy != null && mActiveNavBarRegion != null) {
try {
- mOverviewProxy.onActiveNavBarRegionChanges(mActiveNavBarRegion);
+ mLauncherProxy.onActiveNavBarRegionChanges(mActiveNavBarRegion);
} catch (RemoteException e) {
Log.e(TAG_OPS, "Failed to call onActiveNavBarRegionChanges()", e);
}
@@ -888,7 +888,7 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis
// This should not happen, but if any per-user SysUI component has a dependency on OPS,
// then this could get triggered
Log.w(TAG_OPS,
- "Skipping connection to overview service due to non-system foreground user "
+ "Skipping connection to launcher service due to non-system foreground user "
+ "caller");
return;
}
@@ -925,7 +925,7 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis
}
try {
mBound = mContext.bindServiceAsUser(mQuickStepIntent,
- mOverviewServiceConnection,
+ mLauncherServiceConnection,
Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE,
currentUser);
} catch (SecurityException e) {
@@ -954,15 +954,15 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis
}
@Override
- public void addCallback(@NonNull OverviewProxyListener listener) {
+ public void addCallback(@NonNull LauncherProxyListener listener) {
if (!mConnectionCallbacks.contains(listener)) {
mConnectionCallbacks.add(listener);
}
- listener.onConnectionChanged(mOverviewProxy != null);
+ listener.onConnectionChanged(mLauncherProxy != null);
}
@Override
- public void removeCallback(@NonNull OverviewProxyListener listener) {
+ public void removeCallback(@NonNull LauncherProxyListener listener) {
mConnectionCallbacks.remove(listener);
}
@@ -974,21 +974,21 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis
return mIsEnabled;
}
- public IOverviewProxy getProxy() {
- return mOverviewProxy;
+ public ILauncherProxy getProxy() {
+ return mLauncherProxy;
}
private void disconnectFromLauncherService(String disconnectReason) {
Log.d(TAG_OPS, "disconnectFromLauncherService bound?: " + mBound +
- " currentProxy: " + mOverviewProxy + " disconnectReason: " + disconnectReason,
+ " currentProxy: " + mLauncherProxy + " disconnectReason: " + disconnectReason,
new Throwable());
if (mBound) {
// Always unbind the service (ie. if called through onNullBinding or onBindingDied)
- mContext.unbindService(mOverviewServiceConnection);
+ mContext.unbindService(mLauncherServiceConnection);
mBound = false;
- if (mOverviewProxy != null) {
+ if (mLauncherProxy != null) {
try {
- mOverviewProxy.onUnbind(new IRemoteCallback.Stub() {
+ mLauncherProxy.onUnbind(new IRemoteCallback.Stub() {
@Override
public void sendResult(Bundle data) throws RemoteException {
// Received Launcher reply, try to bind anew.
@@ -1006,9 +1006,9 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis
}
}
- if (mOverviewProxy != null) {
- mOverviewProxy.asBinder().unlinkToDeath(mOverviewServiceDeathRcpt, 0);
- mOverviewProxy = null;
+ if (mLauncherProxy != null) {
+ mLauncherProxy.asBinder().unlinkToDeath(mLauncherServiceDeathRcpt, 0);
+ mLauncherProxy = null;
notifyConnectionChanged();
}
}
@@ -1044,7 +1044,7 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis
private void notifyConnectionChanged() {
for (int i = mConnectionCallbacks.size() - 1; i >= 0; --i) {
- mConnectionCallbacks.get(i).onConnectionChanged(mOverviewProxy != null);
+ mConnectionCallbacks.get(i).onConnectionChanged(mLauncherProxy != null);
}
}
@@ -1095,10 +1095,10 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis
public void notifyAssistantVisibilityChanged(float visibility) {
try {
- if (mOverviewProxy != null) {
- mOverviewProxy.onAssistantVisibilityChanged(visibility);
+ if (mLauncherProxy != null) {
+ mLauncherProxy.onAssistantVisibilityChanged(visibility);
} else {
- Log.e(TAG_OPS, "Failed to get overview proxy for assistant visibility.");
+ Log.e(TAG_OPS, "Failed to get launcher proxy for assistant visibility.");
}
} catch (RemoteException e) {
Log.e(TAG_OPS, "Failed to call notifyAssistantVisibilityChanged()", e);
@@ -1148,10 +1148,10 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis
public void disable(int displayId, int state1, int state2, boolean animate) {
try {
- if (mOverviewProxy != null) {
- mOverviewProxy.disable(displayId, state1, state2, animate);
+ if (mLauncherProxy != null) {
+ mLauncherProxy.disable(displayId, state1, state2, animate);
} else {
- Log.e(TAG_OPS, "Failed to get overview proxy for disable flags.");
+ Log.e(TAG_OPS, "Failed to get launcher proxy for disable flags.");
}
} catch (RemoteException e) {
Log.e(TAG_OPS, "Failed to call disable()", e);
@@ -1160,10 +1160,10 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis
public void onRotationProposal(int rotation, boolean isValid) {
try {
- if (mOverviewProxy != null) {
- mOverviewProxy.onRotationProposal(rotation, isValid);
+ if (mLauncherProxy != null) {
+ mLauncherProxy.onRotationProposal(rotation, isValid);
} else {
- Log.e(TAG_OPS, "Failed to get overview proxy for proposing rotation.");
+ Log.e(TAG_OPS, "Failed to get launcher proxy for proposing rotation.");
}
} catch (RemoteException e) {
Log.e(TAG_OPS, "Failed to call onRotationProposal()", e);
@@ -1172,10 +1172,10 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis
public void onSystemBarAttributesChanged(int displayId, int behavior) {
try {
- if (mOverviewProxy != null) {
- mOverviewProxy.onSystemBarAttributesChanged(displayId, behavior);
+ if (mLauncherProxy != null) {
+ mLauncherProxy.onSystemBarAttributesChanged(displayId, behavior);
} else {
- Log.e(TAG_OPS, "Failed to get overview proxy for system bar attr change.");
+ Log.e(TAG_OPS, "Failed to get launcher proxy for system bar attr change.");
}
} catch (RemoteException e) {
Log.e(TAG_OPS, "Failed to call onSystemBarAttributesChanged()", e);
@@ -1184,10 +1184,10 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis
public void onNavButtonsDarkIntensityChanged(float darkIntensity) {
try {
- if (mOverviewProxy != null) {
- mOverviewProxy.onNavButtonsDarkIntensityChanged(darkIntensity);
+ if (mLauncherProxy != null) {
+ mLauncherProxy.onNavButtonsDarkIntensityChanged(darkIntensity);
} else {
- Log.e(TAG_OPS, "Failed to get overview proxy to update nav buttons dark intensity");
+ Log.e(TAG_OPS, "Failed to get launcher proxy to update nav buttons dark intensity");
}
} catch (RemoteException e) {
Log.e(TAG_OPS, "Failed to call onNavButtonsDarkIntensityChanged()", e);
@@ -1196,10 +1196,10 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis
public void onNavigationBarLumaSamplingEnabled(int displayId, boolean enable) {
try {
- if (mOverviewProxy != null) {
- mOverviewProxy.onNavigationBarLumaSamplingEnabled(displayId, enable);
+ if (mLauncherProxy != null) {
+ mLauncherProxy.onNavigationBarLumaSamplingEnabled(displayId, enable);
} else {
- Log.e(TAG_OPS, "Failed to get overview proxy to enable/disable nav bar luma"
+ Log.e(TAG_OPS, "Failed to get launcher proxy to enable/disable nav bar luma"
+ "sampling");
}
} catch (RemoteException e) {
@@ -1221,7 +1221,7 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis
@Override
public void dump(PrintWriter pw, String[] args) {
pw.println(TAG_OPS + " state:");
- pw.print(" isConnected="); pw.println(mOverviewProxy != null);
+ pw.print(" isConnected="); pw.println(mLauncherProxy != null);
pw.print(" mIsEnabled="); pw.println(isEnabled());
pw.print(" mRecentsComponentName="); pw.println(mRecentsComponentName);
pw.print(" mQuickStepIntent="); pw.println(mQuickStepIntent);
@@ -1237,7 +1237,7 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis
mSysUiState.dump(pw, args);
}
- public interface OverviewProxyListener {
+ public interface LauncherProxyListener {
default void onConnectionChanged(boolean isConnected) {}
default void onPrioritizedRotation(@Surface.Rotation int rotation) {}
default void onOverviewShown(boolean fromHome) {}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyRecentsImpl.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyRecentsImpl.java
index 21c5ae8aec40..e51b73dd96c6 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyRecentsImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyRecentsImpl.java
@@ -23,30 +23,30 @@ import android.util.Log;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.plugins.ActivityStarter;
-import com.android.systemui.shared.recents.IOverviewProxy;
+import com.android.systemui.shared.recents.ILauncherProxy;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import javax.inject.Inject;
/**
- * An implementation of the Recents interface which proxies to the OverviewProxyService.
+ * An implementation of the Recents interface which proxies to the LauncherProxyService.
*/
@SysUISingleton
public class OverviewProxyRecentsImpl implements RecentsImplementation {
private final static String TAG = "OverviewProxyRecentsImpl";
private Handler mHandler;
- private final OverviewProxyService mOverviewProxyService;
+ private final LauncherProxyService mLauncherProxyService;
private final ActivityStarter mActivityStarter;
private final KeyguardStateController mKeyguardStateController;
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
@Inject
public OverviewProxyRecentsImpl(
- OverviewProxyService overviewProxyService,
+ LauncherProxyService launcherProxyService,
ActivityStarter activityStarter,
KeyguardStateController keyguardStateController) {
- mOverviewProxyService = overviewProxyService;
+ mLauncherProxyService = launcherProxyService;
mActivityStarter = activityStarter;
mKeyguardStateController = keyguardStateController;
}
@@ -58,10 +58,10 @@ public class OverviewProxyRecentsImpl implements RecentsImplementation {
@Override
public void showRecentApps(boolean triggeredFromAltTab) {
- IOverviewProxy overviewProxy = mOverviewProxyService.getProxy();
- if (overviewProxy != null) {
+ ILauncherProxy launcherProxy = mLauncherProxyService.getProxy();
+ if (launcherProxy != null) {
try {
- overviewProxy.onOverviewShown(triggeredFromAltTab);
+ launcherProxy.onOverviewShown(triggeredFromAltTab);
} catch (RemoteException e) {
Log.e(TAG, "Failed to send overview show event to launcher.", e);
}
@@ -70,10 +70,10 @@ public class OverviewProxyRecentsImpl implements RecentsImplementation {
@Override
public void hideRecentApps(boolean triggeredFromAltTab, boolean triggeredFromHomeKey) {
- IOverviewProxy overviewProxy = mOverviewProxyService.getProxy();
- if (overviewProxy != null) {
+ ILauncherProxy launcherProxy = mLauncherProxyService.getProxy();
+ if (launcherProxy != null) {
try {
- overviewProxy.onOverviewHidden(triggeredFromAltTab, triggeredFromHomeKey);
+ launcherProxy.onOverviewHidden(triggeredFromAltTab, triggeredFromHomeKey);
} catch (RemoteException e) {
Log.e(TAG, "Failed to send overview hide event to launcher.", e);
}
@@ -83,13 +83,13 @@ public class OverviewProxyRecentsImpl implements RecentsImplementation {
@Override
public void toggleRecentApps() {
// If connected to launcher service, let it handle the toggle logic
- IOverviewProxy overviewProxy = mOverviewProxyService.getProxy();
- if (overviewProxy != null) {
+ ILauncherProxy launcherProxy = mLauncherProxyService.getProxy();
+ if (launcherProxy != null) {
final Runnable toggleRecents = () -> {
try {
- if (mOverviewProxyService.getProxy() != null) {
- mOverviewProxyService.getProxy().onOverviewToggle();
- mOverviewProxyService.notifyToggleRecentApps();
+ if (mLauncherProxyService.getProxy() != null) {
+ mLauncherProxyService.getProxy().onOverviewToggle();
+ mLauncherProxyService.notifyToggleRecentApps();
}
} catch (RemoteException e) {
Log.e(TAG, "Cannot send toggle recents through proxy service.", e);
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
index ecd002705c60..63c10c9b971a 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
@@ -945,10 +945,6 @@ constructor(
override fun onTransitionAnimationEnd() {
sceneInteractor.onTransitionAnimationEnd()
}
-
- override fun onTransitionAnimationCancelled() {
- sceneInteractor.onTransitionAnimationCancelled()
- }
}
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.kt
index 08214c456897..f5c605211520 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.kt
@@ -35,7 +35,6 @@ import android.util.Log
import android.view.Display
import android.view.ScrollCaptureResponse
import android.view.ViewRootImpl.ActivityConfigCallback
-import android.view.WindowManager
import android.view.WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE
import android.widget.Toast
import android.window.WindowContext
@@ -218,9 +217,7 @@ internal constructor(
window.setFocusable(true)
viewProxy.requestFocus()
- if (screenshot.type != WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE) {
- enqueueScrollCaptureRequest(requestId, screenshot.userHandle)
- }
+ enqueueScrollCaptureRequest(requestId, screenshot.userHandle)
window.attachWindow()
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotCrossProfileService.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotCrossProfileService.kt
index 2e6c7567259f..d82a8bd0ddcd 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotCrossProfileService.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotCrossProfileService.kt
@@ -31,7 +31,7 @@ class ScreenshotCrossProfileService : Service() {
private val mBinder: IBinder =
object : ICrossProfileService.Stub() {
- override fun launchIntent(intent: Intent, bundle: Bundle) {
+ override fun launchIntent(intent: Intent, bundle: Bundle?) {
startActivity(intent, bundle)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/scrim/ScrimView.java b/packages/SystemUI/src/com/android/systemui/scrim/ScrimView.java
index 49f3cfc4ceaf..917869a66ca4 100644
--- a/packages/SystemUI/src/com/android/systemui/scrim/ScrimView.java
+++ b/packages/SystemUI/src/com/android/systemui/scrim/ScrimView.java
@@ -27,6 +27,8 @@ import android.graphics.PorterDuff;
import android.graphics.PorterDuff.Mode;
import android.graphics.PorterDuffColorFilter;
import android.graphics.Rect;
+import android.graphics.RenderEffect;
+import android.graphics.Shader;
import android.graphics.drawable.Drawable;
import android.os.Looper;
import android.util.AttributeSet;
@@ -373,4 +375,18 @@ public class ScrimView extends View {
((ScrimDrawable) mDrawable).setRoundedCorners(radius);
}
}
+
+ /**
+ * Blur the view with the specific blur radius or clear any blurs if the radius is 0
+ */
+ public void setBlurRadius(float blurRadius) {
+ if (blurRadius > 0) {
+ setRenderEffect(RenderEffect.createBlurEffect(
+ blurRadius,
+ blurRadius,
+ Shader.TileMode.CLAMP));
+ } else {
+ setRenderEffect(null);
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 19bf4c0bab81..28f5694c3332 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -109,7 +109,6 @@ import com.android.systemui.keyguard.shared.model.Edge;
import com.android.systemui.keyguard.shared.model.TransitionState;
import com.android.systemui.keyguard.shared.model.TransitionStep;
import com.android.systemui.keyguard.ui.binder.KeyguardLongPressViewBinder;
-import com.android.systemui.keyguard.ui.transitions.BlurConfig;
import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel;
import com.android.systemui.keyguard.ui.viewmodel.KeyguardTouchHandlingViewModel;
import com.android.systemui.media.controls.domain.pipeline.MediaDataManager;
@@ -915,8 +914,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
if (!com.android.systemui.Flags.bouncerUiRevamp()) return;
if (isBouncerShowing && isExpanded()) {
- float shadeBlurEffect = BlurConfig.maxBlurRadiusToNotificationPanelBlurRadius(
- mDepthController.getMaxBlurRadiusPx());
+ float shadeBlurEffect = mDepthController.getMaxBlurRadiusPx();
mView.setRenderEffect(RenderEffect.createBlurEffect(
shadeBlurEffect,
shadeBlurEffect,
@@ -2253,7 +2251,8 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
}
private boolean isPanelVisibleBecauseOfHeadsUp() {
- boolean headsUpVisible = mHeadsUpManager.hasPinnedHeadsUp() || mHeadsUpAnimatingAway;
+ boolean headsUpVisible = (mHeadsUpManager != null && mHeadsUpManager.hasPinnedHeadsUp())
+ || mHeadsUpAnimatingAway;
return headsUpVisible && mBarState == StatusBarState.SHADE;
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java
index 48bbb0407ee3..48e374746bf5 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java
@@ -17,6 +17,7 @@
package com.android.systemui.shade;
import static android.os.Trace.TRACE_TAG_APP;
+import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED;
import static com.android.systemui.Flags.enableViewCaptureTracing;
import static com.android.systemui.statusbar.phone.CentralSurfaces.DEBUG;
@@ -49,10 +50,12 @@ import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.view.Window;
import android.view.WindowInsetsController;
+import android.view.accessibility.AccessibilityEvent;
import com.android.app.viewcapture.ViewCaptureFactory;
import com.android.internal.view.FloatingActionMode;
import com.android.internal.widget.floatingtoolbar.FloatingToolbar;
+import com.android.systemui.Flags;
import com.android.systemui.scene.ui.view.WindowRootView;
import com.android.systemui.shade.shared.flag.ShadeWindowGoesAround;
import com.android.systemui.statusbar.phone.ConfigurationForwarder;
@@ -77,6 +80,8 @@ public class NotificationShadeWindowView extends WindowRootView {
private SafeCloseable mViewCaptureCloseable;
+ private boolean mAnimatingContentLaunch = false;
+
public NotificationShadeWindowView(Context context, AttributeSet attrs) {
super(context, attrs);
setMotionEventSplittingEnabled(false);
@@ -188,6 +193,22 @@ public class NotificationShadeWindowView extends WindowRootView {
}
}
+ @Override
+ public boolean requestSendAccessibilityEvent(View child, AccessibilityEvent event) {
+ if (Flags.shadeLaunchAccessibility() && mAnimatingContentLaunch
+ && event.getEventType() == TYPE_VIEW_ACCESSIBILITY_FOCUSED) {
+ // Block accessibility focus events during launch animations to avoid stray TalkBack
+ // announcements.
+ return false;
+ }
+
+ return super.requestSendAccessibilityEvent(child, event);
+ }
+
+ public void setAnimatingContentLaunch(boolean animating) {
+ mAnimatingContentLaunch = animating;
+ }
+
public void setConfigurationForwarder(ConfigurationForwarder configurationForwarder) {
ShadeWindowGoesAround.isUnexpectedlyInLegacyMode();
mConfigurationForwarder = configurationForwarder;
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
index e5dcd2338b9d..ffec8f284c48 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
@@ -16,10 +16,12 @@
package com.android.systemui.shade;
+import static com.android.systemui.Flags.shadeLaunchAccessibility;
import static com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING;
import static com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN;
import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow;
+import static com.android.systemui.util.kotlin.JavaAdapterKt.combineFlows;
import android.app.StatusBarManager;
import android.util.Log;
@@ -59,6 +61,7 @@ import com.android.systemui.res.R;
import com.android.systemui.scene.shared.flag.SceneContainerFlag;
import com.android.systemui.settings.brightness.domain.interactor.BrightnessMirrorShowingInteractor;
import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor;
+import com.android.systemui.shade.domain.interactor.ShadeAnimationInteractor;
import com.android.systemui.shade.shared.flag.ShadeWindowGoesAround;
import com.android.systemui.shared.animation.DisableSubpixelTextTransitionListener;
import com.android.systemui.statusbar.BlurUtils;
@@ -85,6 +88,7 @@ import com.android.systemui.window.ui.WindowRootViewBinder;
import com.android.systemui.window.ui.viewmodel.WindowRootViewModel;
import kotlinx.coroutines.ExperimentalCoroutinesApi;
+import kotlinx.coroutines.flow.Flow;
import java.io.PrintWriter;
import java.util.Optional;
@@ -174,6 +178,7 @@ public class NotificationShadeWindowViewController implements Dumpable {
NotificationShadeDepthController depthController,
NotificationShadeWindowView notificationShadeWindowView,
ShadeViewController shadeViewController,
+ ShadeAnimationInteractor shadeAnimationInteractor,
PanelExpansionInteractor panelExpansionInteractor,
ShadeExpansionStateManager shadeExpansionStateManager,
NotificationStackScrollLayoutController notificationStackScrollLayoutController,
@@ -238,9 +243,17 @@ public class NotificationShadeWindowViewController implements Dumpable {
collectFlow(mView, keyguardTransitionInteractor.transition(
Edge.create(LOCKSCREEN, DREAMING)),
mLockscreenToDreamingTransition);
+ Flow<Boolean> isLaunchAnimationRunning =
+ shadeLaunchAccessibility()
+ ? combineFlows(
+ notificationLaunchAnimationInteractor.isLaunchAnimationRunning(),
+ shadeAnimationInteractor.isLaunchingActivity(),
+ (notificationLaunching, shadeLaunching) ->
+ notificationLaunching || shadeLaunching)
+ : notificationLaunchAnimationInteractor.isLaunchAnimationRunning();
collectFlow(
mView,
- notificationLaunchAnimationInteractor.isLaunchAnimationRunning(),
+ isLaunchAnimationRunning,
this::setExpandAnimationRunning);
if (QSComposeFragment.isEnabled()) {
collectFlow(mView,
@@ -726,9 +739,17 @@ public class NotificationShadeWindowViewController implements Dumpable {
if (ActivityTransitionAnimator.DEBUG_TRANSITION_ANIMATION) {
Log.d(TAG, "Setting mExpandAnimationRunning=" + running);
}
+
if (running) {
mLaunchAnimationTimeout = mClock.uptimeMillis() + 5000;
}
+
+ if (shadeLaunchAccessibility()) {
+ // The view needs to know when an animation is ongoing so it can intercept
+ // unnecessary accessibility events.
+ mView.setAnimatingContentLaunch(running);
+ }
+
mExpandAnimationRunning = running;
mNotificationShadeWindowController.setLaunchingActivity(mExpandAnimationRunning);
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt
index 7299f092640f..cf310dd32613 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt
@@ -34,8 +34,8 @@ import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.navigationbar.NavigationModeController
import com.android.systemui.plugins.qs.QS
import com.android.systemui.plugins.qs.QSContainerController
-import com.android.systemui.recents.OverviewProxyService
-import com.android.systemui.recents.OverviewProxyService.OverviewProxyListener
+import com.android.systemui.recents.LauncherProxyService
+import com.android.systemui.recents.LauncherProxyService.LauncherProxyListener
import com.android.systemui.res.R
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.shared.system.QuickStepContract
@@ -57,7 +57,7 @@ class NotificationsQSContainerController
constructor(
view: NotificationsQuickSettingsContainer,
private val navigationModeController: NavigationModeController,
- private val overviewProxyService: OverviewProxyService,
+ private val launcherProxyService: LauncherProxyService,
private val shadeHeaderController: ShadeHeaderController,
private val shadeInteractor: ShadeInteractor,
private val fragmentService: FragmentService,
@@ -85,8 +85,8 @@ constructor(
private var isGestureNavigation = true
private var taskbarVisible = false
- private val taskbarVisibilityListener: OverviewProxyListener =
- object : OverviewProxyListener {
+ private val taskbarVisibilityListener: LauncherProxyListener =
+ object : LauncherProxyListener {
override fun onTaskbarStatusUpdated(visible: Boolean, stashed: Boolean) {
taskbarVisible = visible
}
@@ -134,7 +134,7 @@ constructor(
public override fun onViewAttached() {
updateResources()
- overviewProxyService.addCallback(taskbarVisibilityListener)
+ launcherProxyService.addCallback(taskbarVisibilityListener)
mView.setInsetsChangedListener(delayedInsetSetter)
mView.setQSFragmentAttachedListener { qs: QS -> qs.setContainerController(this) }
mView.setConfigurationChangedListener { updateResources() }
@@ -142,7 +142,7 @@ constructor(
}
override fun onViewDetached() {
- overviewProxyService.removeCallback(taskbarVisibilityListener)
+ launcherProxyService.removeCallback(taskbarVisibilityListener)
mView.removeOnInsetsChangedListener()
mView.removeQSFragmentAttachedListener()
mView.setConfigurationChangedListener(null)
diff --git a/packages/SystemUI/src/com/android/systemui/shade/QsBatteryModeController.kt b/packages/SystemUI/src/com/android/systemui/shade/QsBatteryModeController.kt
index 7a70966c2b12..b15615a83698 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/QsBatteryModeController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/QsBatteryModeController.kt
@@ -5,6 +5,7 @@ import android.view.DisplayCutout
import com.android.systemui.battery.BatteryMeterView
import com.android.systemui.res.R
import com.android.systemui.statusbar.data.repository.StatusBarContentInsetsProviderStore
+import com.android.systemui.statusbar.layout.StatusBarContentInsetsProvider
import javax.inject.Inject
/**
@@ -15,11 +16,9 @@ class QsBatteryModeController
@Inject
constructor(
@ShadeDisplayAware private val context: Context,
- insetsProviderStore: StatusBarContentInsetsProviderStore,
+ private val insetsProviderStore: StatusBarContentInsetsProviderStore,
) {
- private val insetsProvider = insetsProviderStore.defaultDisplay
-
private companion object {
// MotionLayout frames are in [0, 100]. Where 0 and 100 are reserved for start and end
// frames.
@@ -43,17 +42,19 @@ constructor(
* animation.
*/
@BatteryMeterView.BatteryPercentMode
- fun getBatteryMode(cutout: DisplayCutout?, qsExpandedFraction: Float): Int? =
- when {
+ fun getBatteryMode(cutout: DisplayCutout?, qsExpandedFraction: Float): Int? {
+ val insetsProvider = insetsProviderStore.forDisplay(context.displayId)
+ return when {
qsExpandedFraction > fadeInStartFraction -> BatteryMeterView.MODE_ESTIMATE
- qsExpandedFraction < fadeOutCompleteFraction ->
- if (hasCenterCutout(cutout)) {
+ insetsProvider != null && qsExpandedFraction < fadeOutCompleteFraction ->
+ if (hasCenterCutout(cutout, insetsProvider)) {
BatteryMeterView.MODE_ON
} else {
BatteryMeterView.MODE_ESTIMATE
}
else -> null
}
+ }
fun updateResources() {
fadeInStartFraction =
@@ -64,7 +65,10 @@ constructor(
MOTION_LAYOUT_MAX_FRAME.toFloat()
}
- private fun hasCenterCutout(cutout: DisplayCutout?): Boolean =
+ private fun hasCenterCutout(
+ cutout: DisplayCutout?,
+ insetsProvider: StatusBarContentInsetsProvider,
+ ): Boolean =
cutout?.let {
!insetsProvider.currentRotationHasCornerCutout() && !it.boundingRectTop.isEmpty
} ?: false
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt
index 747642097327..f926d39760fe 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt
@@ -36,7 +36,6 @@ import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.res.R
import com.android.systemui.scene.ui.view.WindowRootView
-import com.android.systemui.shade.data.repository.MutableShadeDisplaysRepository
import com.android.systemui.shade.data.repository.ShadeDisplaysRepository
import com.android.systemui.shade.data.repository.ShadeDisplaysRepositoryImpl
import com.android.systemui.shade.display.ShadeDisplayPolicyModule
@@ -211,15 +210,6 @@ object ShadeDisplayAwareModule {
return impl
}
- @SysUISingleton
- @Provides
- fun provideMutableShadePositionRepository(
- impl: ShadeDisplaysRepositoryImpl
- ): MutableShadeDisplaysRepository {
- ShadeWindowGoesAround.isUnexpectedlyInLegacyMode()
- return impl
- }
-
@Provides
@SysUISingleton
fun provideShadeDialogContextInteractor(
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt
index e8a792c30aa2..fa40aa2bad24 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt
@@ -57,6 +57,7 @@ import com.android.systemui.shade.ShadeHeaderController.Companion.QS_HEADER_CONS
import com.android.systemui.shade.ShadeViewProviderModule.Companion.SHADE_HEADER
import com.android.systemui.shade.carrier.ShadeCarrierGroup
import com.android.systemui.shade.carrier.ShadeCarrierGroupController
+import com.android.systemui.shade.data.repository.ShadeDisplaysRepository
import com.android.systemui.statusbar.data.repository.StatusBarContentInsetsProviderStore
import com.android.systemui.statusbar.phone.StatusBarLocation
import com.android.systemui.statusbar.phone.StatusIconContainer
@@ -90,8 +91,9 @@ constructor(
private val statusBarIconController: StatusBarIconController,
private val tintedIconManagerFactory: TintedIconManager.Factory,
private val privacyIconsController: HeaderPrivacyIconsController,
- private val insetsProviderStore: StatusBarContentInsetsProviderStore,
+ private val statusBarContentInsetsProviderStore: StatusBarContentInsetsProviderStore,
@ShadeDisplayAware private val configurationController: ConfigurationController,
+ private val shadeDisplaysRepository: ShadeDisplaysRepository,
private val variableDateViewControllerFactory: VariableDateViewController.Factory,
@Named(SHADE_HEADER) private val batteryMeterViewController: BatteryMeterViewController,
private val dumpManager: DumpManager,
@@ -104,7 +106,9 @@ constructor(
private val statusOverlayHoverListenerFactory: StatusOverlayHoverListenerFactory,
) : ViewController<View>(header), Dumpable {
- private val insetsProvider = insetsProviderStore.defaultDisplay
+ private val statusBarContentInsetsProvider
+ get() =
+ statusBarContentInsetsProviderStore.forDisplay(shadeDisplaysRepository.displayId.value)
companion object {
/** IDs for transitions and constraints for the [MotionLayout]. */
@@ -222,10 +226,14 @@ constructor(
private val insetListener =
View.OnApplyWindowInsetsListener { view, insets ->
- updateConstraintsForInsets(view as MotionLayout, insets)
- lastInsets = WindowInsets(insets)
-
- view.onApplyWindowInsets(insets)
+ val windowInsets = WindowInsets(insets)
+ if (windowInsets != lastInsets) {
+ updateConstraintsForInsets(view as MotionLayout, insets)
+ lastInsets = windowInsets
+ view.onApplyWindowInsets(insets)
+ } else {
+ insets
+ }
}
private var singleCarrier = false
@@ -285,7 +293,7 @@ constructor(
override fun onDensityOrFontScaleChanged() {
clock.setTextAppearance(R.style.TextAppearance_QS_Status)
date.setTextAppearance(R.style.TextAppearance_QS_Status)
- mShadeCarrierGroup.updateTextAppearance(R.style.TextAppearance_QS_Status_Carriers)
+ mShadeCarrierGroup.updateTextAppearance(R.style.TextAppearance_QS_Status)
loadConstraints()
header.minHeight =
resources.getDimensionPixelSize(R.dimen.large_screen_shade_header_min_height)
@@ -414,6 +422,7 @@ constructor(
}
private fun updateConstraintsForInsets(view: MotionLayout, insets: WindowInsets) {
+ val insetsProvider = statusBarContentInsetsProvider ?: return
val cutout = insets.displayCutout.also { this.cutout = it }
val sbInsets: Insets = insetsProvider.getStatusBarContentInsetsForCurrentRotation()
@@ -508,6 +517,9 @@ constructor(
systemIconsHoverContainer.setOnClickListener(null)
systemIconsHoverContainer.isClickable = false
}
+
+ lastInsets?.let { updateConstraintsForInsets(header, it) }
+
header.jumpToState(header.startState)
updatePosition()
updateScrollY()
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadePrimaryDisplayCommand.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadePrimaryDisplayCommand.kt
index 7bfe40c3d811..173da336c62f 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadePrimaryDisplayCommand.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadePrimaryDisplayCommand.kt
@@ -20,7 +20,7 @@ import android.provider.Settings.Global.DEVELOPMENT_SHADE_DISPLAY_AWARENESS
import com.android.systemui.CoreStartable
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.display.data.repository.DisplayRepository
-import com.android.systemui.shade.data.repository.MutableShadeDisplaysRepository
+import com.android.systemui.shade.data.repository.ShadeDisplaysRepository
import com.android.systemui.shade.display.ShadeDisplayPolicy
import com.android.systemui.statusbar.commandline.Command
import com.android.systemui.statusbar.commandline.CommandRegistry
@@ -35,7 +35,7 @@ constructor(
private val globalSettings: GlobalSettings,
private val commandRegistry: CommandRegistry,
private val displaysRepository: DisplayRepository,
- private val positionRepository: MutableShadeDisplaysRepository,
+ private val positionRepository: ShadeDisplaysRepository,
private val policies: Set<@JvmSuppressWildcards ShadeDisplayPolicy>,
private val defaultPolicy: ShadeDisplayPolicy,
) : Command, CoreStartable {
@@ -103,7 +103,7 @@ constructor(
}
private fun printPolicies() {
- val currentPolicyName = positionRepository.policy.value.name
+ val currentPolicyName = positionRepository.currentPolicy.name
pw.println("Available policies: ")
policies.forEach {
pw.print(" - ${it.name}")
diff --git a/packages/SystemUI/src/com/android/systemui/shade/data/repository/FakeShadeDisplayRepository.kt b/packages/SystemUI/src/com/android/systemui/shade/data/repository/FakeShadeDisplayRepository.kt
index 732d4d1500e7..3513334f2a5c 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/data/repository/FakeShadeDisplayRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/data/repository/FakeShadeDisplayRepository.kt
@@ -17,6 +17,8 @@
package com.android.systemui.shade.data.repository
import android.view.Display
+import com.android.systemui.shade.display.FakeShadeDisplayPolicy
+import com.android.systemui.shade.display.ShadeDisplayPolicy
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
@@ -30,7 +32,6 @@ class FakeShadeDisplayRepository : ShadeDisplaysRepository {
override val displayId: StateFlow<Int>
get() = _displayId
- fun resetDisplayId() {
- _displayId.value = Display.DEFAULT_DISPLAY
- }
+ override val currentPolicy: ShadeDisplayPolicy
+ get() = FakeShadeDisplayPolicy
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeDisplaysRepository.kt b/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeDisplaysRepository.kt
index af48231e0a99..f959f7fe0c31 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeDisplaysRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeDisplaysRepository.kt
@@ -20,14 +20,18 @@ import android.provider.Settings.Global.DEVELOPMENT_SHADE_DISPLAY_AWARENESS
import android.view.Display
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.keyguard.data.repository.KeyguardRepository
+import com.android.systemui.shade.ShadeOnDefaultDisplayWhenLocked
import com.android.systemui.shade.display.ShadeDisplayPolicy
import com.android.systemui.util.settings.GlobalSettings
import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
+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.map
@@ -38,12 +42,8 @@ import kotlinx.coroutines.flow.stateIn
interface ShadeDisplaysRepository {
/** ID of the display which currently hosts the shade */
val displayId: StateFlow<Int>
-}
-
-/** Allows to change the policy that determines in which display the Shade window is visible. */
-interface MutableShadeDisplaysRepository : ShadeDisplaysRepository {
- /** Updates the policy to select where the shade is visible. */
- val policy: StateFlow<ShadeDisplayPolicy>
+ /** The current policy set. */
+ val currentPolicy: ShadeDisplayPolicy
}
/** Keeps the policy and propagates the display id for the shade from it. */
@@ -56,9 +56,11 @@ constructor(
defaultPolicy: ShadeDisplayPolicy,
@Background bgScope: CoroutineScope,
policies: Set<@JvmSuppressWildcards ShadeDisplayPolicy>,
-) : MutableShadeDisplaysRepository {
+ @ShadeOnDefaultDisplayWhenLocked private val shadeOnDefaultDisplayWhenLocked: Boolean,
+ keyguardRepository: KeyguardRepository,
+) : ShadeDisplaysRepository {
- override val policy: StateFlow<ShadeDisplayPolicy> =
+ private val policy: StateFlow<ShadeDisplayPolicy> =
globalSettings
.observerFlow(DEVELOPMENT_SHADE_DISPLAY_AWARENESS)
.onStart { emit(Unit) }
@@ -71,10 +73,32 @@ constructor(
return@map defaultPolicy
}
.distinctUntilChanged()
- .stateIn(bgScope, SharingStarted.WhileSubscribed(), defaultPolicy)
+ .stateIn(bgScope, SharingStarted.Eagerly, defaultPolicy)
+
+ private val displayIdFromPolicy: Flow<Int> = policy.flatMapLatest { it.displayId }
+
+ private val keyguardAwareDisplayPolicy: Flow<Int> =
+ if (!shadeOnDefaultDisplayWhenLocked) {
+ displayIdFromPolicy
+ } else {
+ keyguardRepository.isKeyguardShowing.combine(displayIdFromPolicy) {
+ isKeyguardShowing,
+ currentDisplayId ->
+ if (isKeyguardShowing) {
+ Display.DEFAULT_DISPLAY
+ } else {
+ currentDisplayId
+ }
+ }
+ }
+
+ override val currentPolicy: ShadeDisplayPolicy
+ get() = policy.value
override val displayId: StateFlow<Int> =
- policy
- .flatMapLatest { it.displayId }
- .stateIn(bgScope, SharingStarted.WhileSubscribed(), Display.DEFAULT_DISPLAY)
+ keyguardAwareDisplayPolicy.stateIn(
+ bgScope,
+ SharingStarted.WhileSubscribed(),
+ Display.DEFAULT_DISPLAY,
+ )
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/display/FakeShadeDisplayPolicy.kt b/packages/SystemUI/src/com/android/systemui/shade/display/FakeShadeDisplayPolicy.kt
new file mode 100644
index 000000000000..e010bd6f9880
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/display/FakeShadeDisplayPolicy.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shade.display
+
+import android.view.Display
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+
+/** Used only for testing. */
+object FakeShadeDisplayPolicy : ShadeDisplayPolicy {
+ override val name: String
+ get() = "fake_shade_policy"
+
+ override val displayId: StateFlow<Int>
+ get() = _displayId
+
+ private val _displayId = MutableStateFlow(Display.DEFAULT_DISPLAY)
+
+ fun setDisplayId(displayId: Int) {
+ _displayId.value = displayId
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/display/FocusShadeDisplayPolicy.kt b/packages/SystemUI/src/com/android/systemui/shade/display/FocusShadeDisplayPolicy.kt
new file mode 100644
index 000000000000..7d8f7c59ad66
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/display/FocusShadeDisplayPolicy.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shade.display
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.display.data.repository.FocusedDisplayRepository
+import javax.inject.Inject
+import kotlinx.coroutines.flow.StateFlow
+
+/** Policy that just emits the [FocusedDisplayRepository] display id. */
+@SysUISingleton
+class FocusShadeDisplayPolicy
+@Inject
+constructor(private val focusedDisplayRepository: FocusedDisplayRepository) : ShadeDisplayPolicy {
+ override val name: String
+ get() = "focused_display"
+
+ override val displayId: StateFlow<Int>
+ get() = focusedDisplayRepository.focusedDisplayId
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/display/ShadeDisplayPolicy.kt b/packages/SystemUI/src/com/android/systemui/shade/display/ShadeDisplayPolicy.kt
index bf5deff5cff5..677e41a47afe 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/display/ShadeDisplayPolicy.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/display/ShadeDisplayPolicy.kt
@@ -19,7 +19,8 @@ package com.android.systemui.shade.display
import com.android.systemui.shade.domain.interactor.ShadeExpandedStateInteractor.ShadeElement
import dagger.Binds
import dagger.Module
-import dagger.multibindings.IntoSet
+import dagger.Provides
+import dagger.multibindings.ElementsIntoSet
import kotlinx.coroutines.flow.StateFlow
/** Describes the display the shade should be shown in. */
@@ -53,27 +54,25 @@ interface ShadeExpansionIntent {
fun consumeExpansionIntent(): ShadeElement?
}
-@Module
+@Module(includes = [AllShadeDisplayPoliciesModule::class])
interface ShadeDisplayPolicyModule {
@Binds fun provideDefaultPolicy(impl: DefaultDisplayShadePolicy): ShadeDisplayPolicy
@Binds
fun provideShadeExpansionIntent(impl: StatusBarTouchShadeDisplayPolicy): ShadeExpansionIntent
+}
- @IntoSet
- @Binds
- fun provideDefaultDisplayPolicyToSet(impl: DefaultDisplayShadePolicy): ShadeDisplayPolicy
-
- @IntoSet
- @Binds
- fun provideAnyExternalShadeDisplayPolicyToSet(
- impl: AnyExternalShadeDisplayPolicy
- ): ShadeDisplayPolicy
-
- @Binds
- @IntoSet
- fun provideStatusBarTouchShadeDisplayPolicy(
- impl: StatusBarTouchShadeDisplayPolicy
- ): ShadeDisplayPolicy
+@Module
+internal object AllShadeDisplayPoliciesModule {
+ @Provides
+ @ElementsIntoSet
+ fun provideShadeDisplayPolicies(
+ defaultPolicy: DefaultDisplayShadePolicy,
+ externalPolicy: AnyExternalShadeDisplayPolicy,
+ statusBarPolicy: StatusBarTouchShadeDisplayPolicy,
+ focusPolicy: FocusShadeDisplayPolicy,
+ ): Set<ShadeDisplayPolicy> {
+ return setOf(defaultPolicy, externalPolicy, statusBarPolicy, focusPolicy)
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/display/StatusBarTouchShadeDisplayPolicy.kt b/packages/SystemUI/src/com/android/systemui/shade/display/StatusBarTouchShadeDisplayPolicy.kt
index 91020aa7bdb0..b155ada87efd 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/display/StatusBarTouchShadeDisplayPolicy.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/display/StatusBarTouchShadeDisplayPolicy.kt
@@ -23,8 +23,6 @@ import com.android.app.tracing.coroutines.launchTraced
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.display.data.repository.DisplayRepository
-import com.android.systemui.keyguard.data.repository.KeyguardRepository
-import com.android.systemui.shade.ShadeOnDefaultDisplayWhenLocked
import com.android.systemui.shade.domain.interactor.NotificationShadeElement
import com.android.systemui.shade.domain.interactor.QSShadeElement
import com.android.systemui.shade.domain.interactor.ShadeExpandedStateInteractor.ShadeElement
@@ -38,13 +36,10 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.collectLatest
-import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.stateIn
/**
* Moves the shade on the last display that received a status bar touch.
@@ -57,9 +52,7 @@ class StatusBarTouchShadeDisplayPolicy
@Inject
constructor(
displayRepository: DisplayRepository,
- keyguardRepository: KeyguardRepository,
@Background private val backgroundScope: CoroutineScope,
- @ShadeOnDefaultDisplayWhenLocked private val shadeOnDefaultDisplayWhenLocked: Boolean,
private val shadeInteractor: Lazy<ShadeInteractor>,
private val qsShadeElement: Lazy<QSShadeElement>,
private val notificationElement: Lazy<NotificationShadeElement>,
@@ -72,20 +65,7 @@ constructor(
private var latestIntent = AtomicReference<ShadeElement?>()
private var timeoutJob: Job? = null
- override val displayId: StateFlow<Int> =
- if (shadeOnDefaultDisplayWhenLocked) {
- keyguardRepository.isKeyguardShowing
- .combine(currentDisplayId) { isKeyguardShowing, currentDisplayId ->
- if (isKeyguardShowing) {
- Display.DEFAULT_DISPLAY
- } else {
- currentDisplayId
- }
- }
- .stateIn(backgroundScope, SharingStarted.WhileSubscribed(), currentDisplayId.value)
- } else {
- currentDisplayId
- }
+ override val displayId: StateFlow<Int> = currentDisplayId
private var removalListener: Job? = null
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeModeInteractor.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeModeInteractor.kt
index edf503d03f3e..59d812403777 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeModeInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeModeInteractor.kt
@@ -16,17 +16,20 @@
package com.android.systemui.shade.domain.interactor
+import android.provider.Settings
import androidx.annotation.FloatRange
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.shade.data.repository.ShadeRepository
import com.android.systemui.shade.shared.flag.DualShade
import com.android.systemui.shade.shared.model.ShadeMode
+import com.android.systemui.shared.settings.data.repository.SecureSettingsRepository
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.stateIn
/**
@@ -76,29 +79,53 @@ interface ShadeModeInteractor {
class ShadeModeInteractorImpl
@Inject
-constructor(@Application applicationScope: CoroutineScope, repository: ShadeRepository) :
- ShadeModeInteractor {
+constructor(
+ @Application applicationScope: CoroutineScope,
+ repository: ShadeRepository,
+ secureSettingsRepository: SecureSettingsRepository,
+) : ShadeModeInteractor {
+
+ private val isDualShadeEnabled: Flow<Boolean> =
+ secureSettingsRepository.boolSetting(
+ Settings.Secure.DUAL_SHADE,
+ defaultValue = DUAL_SHADE_ENABLED_DEFAULT,
+ )
override val isShadeLayoutWide: StateFlow<Boolean> = repository.isShadeLayoutWide
override val shadeMode: StateFlow<ShadeMode> =
- isShadeLayoutWide
- .map(this::determineShadeMode)
+ combine(isDualShadeEnabled, repository.isShadeLayoutWide, ::determineShadeMode)
.stateIn(
applicationScope,
SharingStarted.Eagerly,
- initialValue = determineShadeMode(isShadeLayoutWide.value),
+ initialValue =
+ determineShadeMode(
+ isDualShadeEnabled = DUAL_SHADE_ENABLED_DEFAULT,
+ isShadeLayoutWide = repository.isShadeLayoutWide.value,
+ ),
)
@FloatRange(from = 0.0, to = 1.0) override fun getTopEdgeSplitFraction(): Float = 0.5f
- private fun determineShadeMode(isShadeLayoutWide: Boolean): ShadeMode {
+ private fun determineShadeMode(
+ isDualShadeEnabled: Boolean,
+ isShadeLayoutWide: Boolean,
+ ): ShadeMode {
return when {
- DualShade.isEnabled -> ShadeMode.Dual
+ isDualShadeEnabled ||
+ // TODO(b/388793191): This ensures that the dual_shade aconfig flag can also enable
+ // Dual Shade, to avoid breaking unit tests. Remove this once all references to the
+ // flag are removed.
+ DualShade.isEnabled -> ShadeMode.Dual
isShadeLayoutWide -> ShadeMode.Split
else -> ShadeMode.Single
}
}
+
+ companion object {
+ /* Whether the Dual Shade setting is enabled by default. */
+ private const val DUAL_SHADE_ENABLED_DEFAULT = false
+ }
}
class ShadeModeInteractorEmptyImpl @Inject constructor() : ShadeModeInteractor {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index 7fc1510f1136..dcea8d85e10d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -273,12 +273,12 @@ public class CommandQueue extends IStatusBar.Stub implements
default void toggleQuickSettingsPanel() { }
/**
- * Called to notify IME window status changes.
+ * Sets the new IME window status.
*
- * @param displayId The id of the display to notify.
- * @param vis IME visibility.
- * @param backDisposition Disposition mode of back button.
- * @param showImeSwitcher {@code true} to show IME switch button.
+ * @param displayId The id of the display to which the IME is bound.
+ * @param vis The IME window visibility.
+ * @param backDisposition The IME back disposition mode.
+ * @param showImeSwitcher Whether the IME Switcher button should be shown.
*/
default void setImeWindowStatus(int displayId, @ImeWindowVisibility int vis,
@BackDispositionMode int backDisposition, boolean showImeSwitcher) { }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index a5595edcbb95..4269f60e1c2a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
@@ -88,6 +88,7 @@ import com.android.keyguard.TrustGrantFlags;
import com.android.keyguard.logging.KeyguardLogger;
import com.android.settingslib.Utils;
import com.android.settingslib.fuelgauge.BatteryStatus;
+import com.android.systemui.Flags;
import com.android.systemui.biometrics.AuthController;
import com.android.systemui.biometrics.FaceHelpMessageDeferral;
import com.android.systemui.biometrics.FaceHelpMessageDeferralFactory;
@@ -199,7 +200,7 @@ public class KeyguardIndicationController {
private CharSequence mBiometricMessage;
private CharSequence mBiometricMessageFollowUp;
private BiometricSourceType mBiometricMessageSource;
- protected ColorStateList mInitialTextColorState;
+ private ColorStateList mInitialTextColorState;
private boolean mVisible;
private boolean mOrganizationOwnedDevice;
@@ -393,13 +394,27 @@ public class KeyguardIndicationController {
return mIndicationArea;
}
+ /**
+ * Notify controller about configuration changes.
+ */
+ public void onConfigurationChanged() {
+ // Get new text color in case theme has changed
+ if (Flags.indicationTextA11yFix()) {
+ setIndicationColorToThemeColor();
+ }
+ }
+
public void setIndicationArea(ViewGroup indicationArea) {
mIndicationArea = indicationArea;
mTopIndicationView = indicationArea.findViewById(R.id.keyguard_indication_text);
mLockScreenIndicationView = indicationArea.findViewById(
R.id.keyguard_indication_text_bottom);
- mInitialTextColorState = mTopIndicationView != null
- ? mTopIndicationView.getTextColors() : ColorStateList.valueOf(Color.WHITE);
+ if (Flags.indicationTextA11yFix()) {
+ setIndicationColorToThemeColor();
+ } else {
+ setIndicationTextColor(mTopIndicationView != null
+ ? mTopIndicationView.getTextColors() : ColorStateList.valueOf(Color.WHITE));
+ }
if (mRotateTextViewController != null) {
mRotateTextViewController.destroy();
}
@@ -436,6 +451,12 @@ public class KeyguardIndicationController {
mIsLogoutEnabledCallback);
}
+ @NonNull
+ private ColorStateList wallpaperTextColor() {
+ return ColorStateList.valueOf(
+ Utils.getColorAttrDefaultColor(mContext, R.attr.wallpaperTextColor));
+ }
+
/**
* Cleanup
*/
@@ -513,7 +534,7 @@ public class KeyguardIndicationController {
.setMessage(mContext.getResources().getString(
com.android.systemui.res.R.string.dismissible_keyguard_swipe)
)
- .setTextColor(mInitialTextColorState)
+ .setTextColor(getInitialTextColorState())
.build(),
/* updateImmediately */ true);
} else {
@@ -533,7 +554,7 @@ public class KeyguardIndicationController {
INDICATION_TYPE_DISCLOSURE,
new KeyguardIndication.Builder()
.setMessage(disclosure)
- .setTextColor(mInitialTextColorState)
+ .setTextColor(getInitialTextColorState())
.build(),
/* updateImmediately */ false);
}
@@ -602,7 +623,7 @@ public class KeyguardIndicationController {
INDICATION_TYPE_OWNER_INFO,
new KeyguardIndication.Builder()
.setMessage(finalInfo)
- .setTextColor(mInitialTextColorState)
+ .setTextColor(getInitialTextColorState())
.build(),
false);
} else {
@@ -624,7 +645,7 @@ public class KeyguardIndicationController {
INDICATION_TYPE_BATTERY,
new KeyguardIndication.Builder()
.setMessage(powerIndication)
- .setTextColor(mInitialTextColorState)
+ .setTextColor(getInitialTextColorState())
.build(),
animate);
} else {
@@ -645,7 +666,7 @@ public class KeyguardIndicationController {
new KeyguardIndication.Builder()
.setMessage(mContext.getResources().getText(
com.android.internal.R.string.lockscreen_storage_locked))
- .setTextColor(mInitialTextColorState)
+ .setTextColor(getInitialTextColorState())
.build(),
false);
} else {
@@ -666,7 +687,7 @@ public class KeyguardIndicationController {
.setMessage(mBiometricMessage)
.setForceAccessibilityLiveRegionAssertive()
.setMinVisibilityMillis(IMPORTANT_MSG_MIN_DURATION)
- .setTextColor(mInitialTextColorState)
+ .setTextColor(getInitialTextColorState())
.build(),
true
);
@@ -680,7 +701,7 @@ public class KeyguardIndicationController {
new KeyguardIndication.Builder()
.setMessage(mBiometricMessageFollowUp)
.setMinVisibilityMillis(IMPORTANT_MSG_MIN_DURATION)
- .setTextColor(mInitialTextColorState)
+ .setTextColor(getInitialTextColorState())
.build(),
true
);
@@ -711,7 +732,7 @@ public class KeyguardIndicationController {
INDICATION_TYPE_TRUST,
new KeyguardIndication.Builder()
.setMessage(trustGrantedIndication)
- .setTextColor(mInitialTextColorState)
+ .setTextColor(getInitialTextColorState())
.build(),
true);
hideBiometricMessage();
@@ -722,7 +743,7 @@ public class KeyguardIndicationController {
INDICATION_TYPE_TRUST,
new KeyguardIndication.Builder()
.setMessage(trustManagedIndication)
- .setTextColor(mInitialTextColorState)
+ .setTextColor(getInitialTextColorState())
.build(),
false);
} else {
@@ -751,7 +772,7 @@ public class KeyguardIndicationController {
INDICATION_TYPE_PERSISTENT_UNLOCK_MESSAGE,
new KeyguardIndication.Builder()
.setMessage(mPersistentUnlockMessage)
- .setTextColor(mInitialTextColorState)
+ .setTextColor(getInitialTextColorState())
.build(),
true);
} else {
@@ -792,7 +813,7 @@ public class KeyguardIndicationController {
new KeyguardIndication.Builder()
.setMessage(mContext.getString(
R.string.keyguard_indication_after_adaptive_auth_lock))
- .setTextColor(mInitialTextColorState)
+ .setTextColor(getInitialTextColorState())
.build(),
true);
} else {
@@ -1179,7 +1200,8 @@ public class KeyguardIndicationController {
} else {
message = mContext.getString(R.string.keyguard_retry);
}
- mStatusBarKeyguardViewManager.setKeyguardMessage(message, mInitialTextColorState,
+ mStatusBarKeyguardViewManager.setKeyguardMessage(message,
+ getInitialTextColorState(),
null);
}
} else {
@@ -1232,7 +1254,7 @@ public class KeyguardIndicationController {
public void dump(PrintWriter pw, String[] args) {
pw.println("KeyguardIndicationController:");
- pw.println(" mInitialTextColorState: " + mInitialTextColorState);
+ pw.println(" mInitialTextColorState: " + getInitialTextColorState());
pw.println(" mPowerPluggedInWired: " + mPowerPluggedInWired);
pw.println(" mPowerPluggedIn: " + mPowerPluggedIn);
pw.println(" mPowerCharged: " + mPowerCharged);
@@ -1253,6 +1275,22 @@ public class KeyguardIndicationController {
mRotateTextViewController.dump(pw, args);
}
+ protected ColorStateList getInitialTextColorState() {
+ return mInitialTextColorState;
+ }
+
+ private void setIndicationColorToThemeColor() {
+ mInitialTextColorState = wallpaperTextColor();
+ }
+
+ /**
+ * @deprecated Use {@link #setIndicationColorToThemeColor}
+ */
+ @Deprecated
+ private void setIndicationTextColor(ColorStateList color) {
+ mInitialTextColorState = color;
+ }
+
protected class BaseKeyguardCallback extends KeyguardUpdateMonitorCallback {
@Override
public void onTimeChanged() {
@@ -1358,7 +1396,7 @@ public class KeyguardIndicationController {
mBouncerMessageInteractor.setFaceAcquisitionMessage(helpString);
}
mStatusBarKeyguardViewManager.setKeyguardMessage(helpString,
- mInitialTextColorState, biometricSourceType);
+ getInitialTextColorState(), biometricSourceType);
} else if (mScreenLifecycle.getScreenState() == SCREEN_ON) {
if (isCoExFaceAcquisitionMessage && msgId == FACE_ACQUIRED_TOO_DARK) {
showBiometricMessage(
@@ -1655,7 +1693,7 @@ public class KeyguardIndicationController {
private void showErrorMessageNowOrLater(String errString, @Nullable String followUpMsg,
BiometricSourceType biometricSourceType) {
if (mStatusBarKeyguardViewManager.isBouncerShowing()) {
- mStatusBarKeyguardViewManager.setKeyguardMessage(errString, mInitialTextColorState,
+ mStatusBarKeyguardViewManager.setKeyguardMessage(errString, getInitialTextColorState(),
biometricSourceType);
} else if (mScreenLifecycle.getScreenState() == SCREEN_ON) {
showBiometricMessage(errString, followUpMsg, biometricSourceType);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java
index ccea254defaa..4c6fa4839e5c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java
@@ -284,7 +284,8 @@ public class NotificationListener extends NotificationListenerWithPlugins implem
/* rankingAdjustment= */ 0,
/* isBubble= */ false,
/* proposedImportance= */ 0,
- /* sensitiveContent= */ false
+ /* sensitiveContent= */ false,
+ /* summarization = */ null
);
}
return ranking;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
index c8f972774ab0..382fc7058bf0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
@@ -70,7 +70,7 @@ import com.android.systemui.flags.FeatureFlagsClassic;
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
-import com.android.systemui.recents.OverviewProxyService;
+import com.android.systemui.recents.LauncherProxyService;
import com.android.systemui.scene.shared.flag.SceneContainerFlag;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
@@ -152,7 +152,7 @@ public class NotificationLockscreenUserManagerImpl implements
private final List<UserChangedListener> mListeners = new ArrayList<>();
private final BroadcastDispatcher mBroadcastDispatcher;
private final NotificationClickNotifier mClickNotifier;
- private final Lazy<OverviewProxyService> mOverviewProxyServiceLazy;
+ private final Lazy<LauncherProxyService> mLauncherProxyServiceLazy;
private final FeatureFlagsClassic mFeatureFlags;
private boolean mShowLockscreenNotifications;
private LockPatternUtils mLockPatternUtils;
@@ -235,8 +235,8 @@ public class NotificationLockscreenUserManagerImpl implements
if (!keyguardPrivateNotifications()) {
// Start the overview connection to the launcher service
// Connect if user hasn't connected yet
- if (mOverviewProxyServiceLazy.get().getProxy() == null) {
- mOverviewProxyServiceLazy.get().startConnectionToCurrentUser();
+ if (mLauncherProxyServiceLazy.get().getProxy() == null) {
+ mLauncherProxyServiceLazy.get().startConnectionToCurrentUser();
}
}
} else if (Objects.equals(action, NOTIFICATION_UNLOCKED_BY_WORK_CHALLENGE_ACTION)) {
@@ -318,7 +318,7 @@ public class NotificationLockscreenUserManagerImpl implements
Lazy<NotificationVisibilityProvider> visibilityProviderLazy,
Lazy<CommonNotifCollection> commonNotifCollectionLazy,
NotificationClickNotifier clickNotifier,
- Lazy<OverviewProxyService> overviewProxyServiceLazy,
+ Lazy<LauncherProxyService> launcherProxyServiceLazy,
KeyguardManager keyguardManager,
StatusBarStateController statusBarStateController,
@Main Executor mainExecutor,
@@ -343,7 +343,7 @@ public class NotificationLockscreenUserManagerImpl implements
mVisibilityProviderLazy = visibilityProviderLazy;
mCommonNotifCollectionLazy = commonNotifCollectionLazy;
mClickNotifier = clickNotifier;
- mOverviewProxyServiceLazy = overviewProxyServiceLazy;
+ mLauncherProxyServiceLazy = launcherProxyServiceLazy;
statusBarStateController.addCallback(this);
mLockPatternUtils = lockPatternUtils;
mKeyguardManager = keyguardManager;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModel.kt
index b7cad625b7b8..46456b841e3f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModel.kt
@@ -30,6 +30,7 @@ import com.android.systemui.statusbar.notification.domain.interactor.HeadsUpNoti
import com.android.systemui.statusbar.notification.domain.model.TopPinnedState
import com.android.systemui.statusbar.notification.headsup.PinnedStatus
import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel
+import com.android.systemui.statusbar.phone.ongoingcall.StatusBarChipsModernization
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
@@ -73,17 +74,24 @@ constructor(
OngoingActivityChipModel.ChipIcon.StatusBarNotificationIcon(this.key)
}
val colors = this.promotedContent.toCustomColorsModel()
- val onClickListener =
+
+ val clickListener: () -> Unit = {
+ // The notification pipeline needs everything to run on the main thread, so keep
+ // this event on the main thread.
+ applicationScope.launch {
+ notifChipsInteractor.onPromotedNotificationChipTapped(this@toActivityChipModel.key)
+ }
+ }
+ val onClickListenerLegacy =
View.OnClickListener {
- // The notification pipeline needs everything to run on the main thread, so keep
- // this event on the main thread.
- applicationScope.launch {
- notifChipsInteractor.onPromotedNotificationChipTapped(
- this@toActivityChipModel.key
- )
- }
+ StatusBarChipsModernization.assertInLegacyMode()
+ clickListener.invoke()
}
- val clickBehavior = OngoingActivityChipModel.ClickBehavior.None
+ val clickBehavior =
+ OngoingActivityChipModel.ClickBehavior.ShowHeadsUpNotification({
+ StatusBarChipsModernization.assertInNewMode()
+ clickListener.invoke()
+ })
val isShowingHeadsUpFromChipTap =
headsUpState is TopPinnedState.Pinned &&
@@ -95,7 +103,7 @@ constructor(
return OngoingActivityChipModel.Shown.IconOnly(
icon,
colors,
- onClickListener,
+ onClickListenerLegacy,
clickBehavior,
)
}
@@ -105,7 +113,7 @@ constructor(
icon,
colors,
this.promotedContent.shortCriticalText,
- onClickListener,
+ onClickListenerLegacy,
clickBehavior,
)
}
@@ -121,7 +129,7 @@ constructor(
return OngoingActivityChipModel.Shown.IconOnly(
icon,
colors,
- onClickListener,
+ onClickListenerLegacy,
clickBehavior,
)
}
@@ -130,7 +138,7 @@ constructor(
return OngoingActivityChipModel.Shown.IconOnly(
icon,
colors,
- onClickListener,
+ onClickListenerLegacy,
clickBehavior,
)
}
@@ -140,7 +148,7 @@ constructor(
icon,
colors,
time = this.promotedContent.time.time,
- onClickListener,
+ onClickListenerLegacy,
clickBehavior,
)
}
@@ -149,7 +157,7 @@ constructor(
icon,
colors,
startTimeMs = this.promotedContent.time.time,
- onClickListener,
+ onClickListenerLegacy,
clickBehavior,
)
}
@@ -159,7 +167,7 @@ constructor(
icon,
colors,
startTimeMs = this.promotedContent.time.time,
- onClickListener,
+ onClickListenerLegacy,
clickBehavior,
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/OngoingActivityChip.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/OngoingActivityChip.kt
index a682f9674e2e..279792ef7536 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/OngoingActivityChip.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/OngoingActivityChip.kt
@@ -66,6 +66,9 @@ fun OngoingActivityChip(model: OngoingActivityChipModel.Shown, modifier: Modifie
ChipBody(model, onClick = { clickBehavior.onClick(expandable) })
}
}
+ is OngoingActivityChipModel.ClickBehavior.ShowHeadsUpNotification -> {
+ ChipBody(model, onClick = { clickBehavior.onClick() })
+ }
is OngoingActivityChipModel.ClickBehavior.None -> {
ChipBody(model, modifier = modifier)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/OngoingActivityChipModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/OngoingActivityChipModel.kt
index 68c8f8cb4254..c6d6da2ad9aa 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/OngoingActivityChipModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/OngoingActivityChipModel.kt
@@ -170,5 +170,8 @@ sealed class OngoingActivityChipModel {
/** The chip expands into a dialog or activity on click. */
data class ExpandAction(val onClick: (Expandable) -> Unit) : ClickBehavior
+
+ /** Clicking the chip will show the heads up notification associated with the chip. */
+ data class ShowHeadsUpNotification(val onClick: () -> Unit) : ClickBehavior
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/headsup/shared/StatusBarNoHunBehavior.kt b/packages/SystemUI/src/com/android/systemui/statusbar/headsup/shared/StatusBarNoHunBehavior.kt
index 2ae54d7c6c83..c9024d934068 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/headsup/shared/StatusBarNoHunBehavior.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/headsup/shared/StatusBarNoHunBehavior.kt
@@ -33,7 +33,7 @@ object StatusBarNoHunBehavior {
/** Is the refactor enabled */
@JvmStatic
inline val isEnabled
- get() = Flags.statusBarNoHunBehavior() && android.app.Flags.notificationsRedesignAppIcons()
+ get() = Flags.statusBarNoHunBehavior()
/**
* Called to ensure code is only run when the flag is enabled. This protects users from the
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt
index 3825c098ca5d..b6ef95893036 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt
@@ -17,6 +17,7 @@
package com.android.systemui.statusbar.notification
import android.app.Notification
+import android.app.Notification.EXTRA_SUMMARIZED_CONTENT
import android.content.Context
import android.content.pm.LauncherApps
import android.graphics.drawable.AnimatedImageDrawable
@@ -66,6 +67,12 @@ constructor(
messagingStyle.shortcutIcon = launcherApps.getShortcutIcon(shortcutInfo)
shortcutInfo.label?.let { label -> messagingStyle.conversationTitle = label }
}
+ if (NmSummarizationUiFlag.isEnabled) {
+ entry.sbn.notification.extras.putCharSequence(
+ EXTRA_SUMMARIZED_CONTENT, entry.ranking.summarization
+ )
+ }
+
messagingStyle.unreadMessageCount =
conversationNotificationManager.getUnreadCount(entry, recoveredBuilder)
return messagingStyle
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NmSummarizationUiFlag.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NmSummarizationUiFlag.kt
new file mode 100644
index 000000000000..feac0a514828
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NmSummarizationUiFlag.kt
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.statusbar.notification
+
+import android.app.Flags;
+import com.android.systemui.flags.FlagToken
+import com.android.systemui.flags.RefactorFlagUtils
+
+/**
+ * Helper for android.app.nm_summarization and android.nm_summarization_ui. The new functionality
+ * should be enabled if either flag is enabled.
+ */
+@Suppress("NOTHING_TO_INLINE")
+object NmSummarizationUiFlag {
+ const val FLAG_DESC = "android.app.nm_summarization(_ui)"
+
+ @JvmStatic
+ inline val isEnabled
+ get() = Flags.nmSummarizationUi() || Flags.nmSummarization()
+
+ /**
+ * Called to ensure code is only run when the flag is enabled. This protects users from the
+ * unintended behaviors caused by accidentally running new logic, while also crashing on an eng
+ * build to ensure that the refactor author catches issues in testing.
+ */
+ @JvmStatic
+ inline fun isUnexpectedlyInLegacyMode() =
+ RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, FLAG_DESC)
+
+ /**
+ * Called to ensure code is only run when the flag is disabled. This will throw an exception if
+ * the flag is enabled to ensure that the refactor author catches issues in testing.
+ */
+ @JvmStatic
+ inline fun assertInLegacyMode() =
+ RefactorFlagUtils.assertInLegacyMode(isEnabled, FLAG_DESC)
+} \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
index 417e57d2205f..5cc79df9130a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
@@ -836,6 +836,14 @@ public final class NotificationEntry extends ListEntry {
}
/**
+ * Returns whether the NotificationEntry is promoted ongoing.
+ */
+ @FlaggedApi(Flags.FLAG_API_RICH_ONGOING)
+ public boolean isOngoingPromoted() {
+ return mSbn.getNotification().isPromotedOngoing();
+ }
+
+ /**
* Returns whether this row is considered blockable (i.e. it's not a system notif
* or is not in an allowList).
*/
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustment.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustment.kt
index 331ef1c01596..aa5008b8416e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustment.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustment.kt
@@ -40,6 +40,7 @@ internal constructor(
@RedactionType val redactionType: Int,
val isChildInGroup: Boolean,
val isGroupSummary: Boolean,
+ val summarization: String?,
) {
companion object {
@JvmStatic
@@ -61,6 +62,7 @@ internal constructor(
AsyncGroupHeaderViewInflation.isEnabled &&
!oldAdjustment.isGroupSummary &&
newAdjustment.isGroupSummary -> true
+ oldAdjustment.summarization != newAdjustment.summarization -> true
else -> false
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProvider.kt
index 97e55c19d2f4..465bc288cbc1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProvider.kt
@@ -152,5 +152,6 @@ constructor(
},
isChildInGroup = entry.hasEverBeenGroupChild(),
isGroupSummary = entry.hasEverBeenGroupSummary(),
+ summarization = entry.ranking.summarization
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java
index d401283aa84e..96192b1ea315 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java
@@ -19,6 +19,7 @@ package com.android.systemui.statusbar.notification.footer.ui.view;
import static android.graphics.PorterDuff.Mode.SRC_ATOP;
import static com.android.systemui.Flags.notificationFooterBackgroundTintOptimization;
+import static com.android.systemui.Flags.notificationShadeBlur;
import static com.android.systemui.util.ColorUtilKt.hexColorString;
import android.annotation.ColorInt;
@@ -29,6 +30,7 @@ import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.Configuration;
import android.content.res.Resources;
+import android.graphics.Color;
import android.graphics.ColorFilter;
import android.graphics.PorterDuffColorFilter;
import android.graphics.drawable.Drawable;
@@ -39,6 +41,8 @@ import android.widget.TextView;
import androidx.annotation.NonNull;
+import com.android.internal.graphics.ColorUtils;
+import com.android.systemui.common.shared.colors.SurfaceEffectColors;
import com.android.systemui.res.R;
import com.android.systemui.scene.shared.flag.SceneContainerFlag;
import com.android.systemui.statusbar.notification.ColorUpdateLogger;
@@ -383,9 +387,23 @@ public class FooterView extends StackScrollerDecorView {
final Drawable historyBg = NotifRedesignFooter.isEnabled()
? theme.getDrawable(R.drawable.notif_footer_btn_background) : null;
final @ColorInt int scHigh;
+
if (!notificationFooterBackgroundTintOptimization()) {
- scHigh = mContext.getColor(
- com.android.internal.R.color.materialColorSurfaceContainerHigh);
+ if (notificationShadeBlur()) {
+ Color backgroundColor = Color.valueOf(
+ SurfaceEffectColors.surfaceEffect0(getResources()));
+ scHigh = ColorUtils.setAlphaComponent(backgroundColor.toArgb(), 0xFF);
+ // Apply alpha on background drawables.
+ int backgroundAlpha = (int) (backgroundColor.alpha() * 0xFF);
+ clearAllBg.setAlpha(backgroundAlpha);
+ settingsBg.setAlpha(backgroundAlpha);
+ if (historyBg != null) {
+ historyBg.setAlpha(backgroundAlpha);
+ }
+ } else {
+ scHigh = mContext.getColor(
+ com.android.internal.R.color.materialColorSurfaceContainerHigh);
+ }
if (scHigh != 0) {
final ColorFilter bgColorFilter = new PorterDuffColorFilter(scHigh, SRC_ATOP);
clearAllBg.setColorFilter(bgColorFilter);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerImpl.java
index 0171fb72e158..be61dc95fe20 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerImpl.java
@@ -706,7 +706,7 @@ public class HeadsUpManagerImpl
}
private void updateHeadsUpFlow() {
- mHeadsUpNotificationRows.setValue(new HashSet<>(getHeadsUpEntryPhoneMap().values()));
+ mHeadsUpNotificationRows.setValue(new HashSet<>(mHeadsUpEntryMap.values()));
}
@Override
@@ -732,11 +732,6 @@ public class HeadsUpManagerImpl
return mHeadsUpAnimatingAway.getValue();
}
- @NonNull
- private ArrayMap<String, HeadsUpEntry> getHeadsUpEntryPhoneMap() {
- return mHeadsUpEntryMap;
- }
-
/**
* Called to notify the listeners that the HUN animating away animation has ended.
*/
@@ -1014,7 +1009,7 @@ public class HeadsUpManagerImpl
@Override
public void setRemoteInputActive(
@NonNull NotificationEntry entry, boolean remoteInputActive) {
- HeadsUpEntry headsUpEntry = getHeadsUpEntryPhone(entry.getKey());
+ HeadsUpEntry headsUpEntry = mHeadsUpEntryMap.get(entry.getKey());
if (headsUpEntry != null && headsUpEntry.mRemoteInputActive != remoteInputActive) {
headsUpEntry.mRemoteInputActive = remoteInputActive;
if (ExpandHeadsUpOnInlineReply.isEnabled() && remoteInputActive) {
@@ -1029,11 +1024,6 @@ public class HeadsUpManagerImpl
}
}
- @Nullable
- private HeadsUpEntry getHeadsUpEntryPhone(@NonNull String key) {
- return mHeadsUpEntryMap.get(key);
- }
-
@Override
public void setGutsShown(@NonNull NotificationEntry entry, boolean gutsShown) {
HeadsUpEntry headsUpEntry = getHeadsUpEntry(entry.getKey());
@@ -1125,7 +1115,7 @@ public class HeadsUpManagerImpl
return true;
}
- HeadsUpEntry headsUpEntry = getHeadsUpEntryPhone(key);
+ HeadsUpEntry headsUpEntry = mHeadsUpEntryMap.get(key);
HeadsUpEntry topEntry = getTopHeadsUpEntryPhone();
if (headsUpEntry == null || headsUpEntry != topEntry) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/BundleNotificationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/BundleNotificationInfo.java
index aad618d50067..9c6e41c482b6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/BundleNotificationInfo.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/BundleNotificationInfo.java
@@ -64,11 +64,11 @@ public class BundleNotificationInfo extends NotificationInfo {
boolean isNonblockable,
boolean wasShownHighPriority,
AssistantFeedbackController assistantFeedbackController,
- MetricsLogger metricsLogger) throws RemoteException {
+ MetricsLogger metricsLogger, OnClickListener onCloseClick) throws RemoteException {
super.bindNotification(pm, iNotificationManager, onUserInteractionCallback,
channelEditorDialogController, pkg, notificationChannel, entry, onSettingsClick,
onAppSettingsClick, uiEventLogger, isDeviceProvisioned, isNonblockable,
- wasShownHighPriority, assistantFeedbackController, metricsLogger);
+ wasShownHighPriority, assistantFeedbackController, metricsLogger, onCloseClick);
// Additionally, bind the feedback button.
ComponentName assistant = iNotificationManager.getAllowedNotificationAssistant();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index 5d7b8e6e8a84..d986aaebc0f8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -21,6 +21,7 @@ import static android.app.Notification.Action.SEMANTIC_ACTION_MARK_CONVERSATION_
import static android.service.notification.NotificationListenerService.REASON_CANCEL;
import static com.android.systemui.flags.Flags.ENABLE_NOTIFICATIONS_SIMULATE_SLOW_MEASURE;
+import static com.android.systemui.Flags.notificationsPinnedHunInShade;
import static com.android.systemui.statusbar.notification.collection.NotificationEntry.DismissState.PARENT_DISMISSED;
import static com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_HEADSUP;
import static com.android.systemui.statusbar.policy.RemoteInputView.FOCUS_ANIMATION_MIN_SCALE;
@@ -106,6 +107,7 @@ import com.android.systemui.statusbar.notification.headsup.HeadsUpManager;
import com.android.systemui.statusbar.notification.headsup.PinnedStatus;
import com.android.systemui.statusbar.notification.logging.NotificationCounters;
import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
+import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUiForceExpanded;
import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag;
import com.android.systemui.statusbar.notification.row.shared.AsyncGroupHeaderViewInflation;
import com.android.systemui.statusbar.notification.row.shared.LockscreenOtpRedaction;
@@ -163,6 +165,8 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
private boolean mShowSnooze = false;
private boolean mIsFaded;
+ private boolean mIsPromotedOngoing = false;
+
/**
* Listener for when {@link ExpandableNotificationRow} is laid out.
*/
@@ -196,6 +200,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
private int mMaxSmallHeightBeforeS;
private int mMaxSmallHeight;
private int mMaxExpandedHeight;
+ private int mMaxExpandedHeightForPromotedOngoing;
private int mNotificationLaunchHeight;
private boolean mMustStayOnScreen;
@@ -331,6 +336,15 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
private boolean mSaveSpaceOnLockscreen;
/**
+ * It is added for unit testing purpose.
+ * Please do not use it for other purposes.
+ */
+ @VisibleForTesting
+ public void setIgnoreLockscreenConstraints(boolean ignoreLockscreenConstraints) {
+ mIgnoreLockscreenConstraints = ignoreLockscreenConstraints;
+ }
+
+ /**
* True if we use intrinsic height regardless of vertical space available on lockscreen.
*/
private boolean mIgnoreLockscreenConstraints;
@@ -804,6 +818,13 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
}
private void updateLimitsForView(NotificationContentView layout) {
+ final int maxExpandedHeight;
+ if (isPromotedOngoing()) {
+ maxExpandedHeight = mMaxExpandedHeightForPromotedOngoing;
+ } else {
+ maxExpandedHeight = mMaxExpandedHeight;
+ }
+
View contractedView = layout.getContractedChild();
boolean customView = contractedView != null
&& contractedView.getId()
@@ -824,7 +845,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
smallHeight = mMaxSmallHeightBeforeS;
}
} else if (isCallLayout) {
- smallHeight = mMaxExpandedHeight;
+ smallHeight = maxExpandedHeight;
} else {
smallHeight = mMaxSmallHeight;
}
@@ -848,7 +869,8 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
if (headsUpWrapper != null) {
headsUpHeight = Math.max(headsUpHeight, headsUpWrapper.getMinLayoutHeight());
}
- layout.setHeights(smallHeight, headsUpHeight, mMaxExpandedHeight);
+
+ layout.setHeights(smallHeight, headsUpHeight, maxExpandedHeight);
}
@NonNull
@@ -1258,6 +1280,9 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
if (mIsSummaryWithChildren) {
return mChildrenContainer.getIntrinsicHeight();
}
+ if (isPromotedOngoing()) {
+ return getMaxExpandHeight();
+ }
if (mExpandedWhenPinned) {
return Math.max(getMaxExpandHeight(), getHeadsUpHeight());
} else if (android.app.Flags.compactHeadsUpNotification()
@@ -2077,6 +2102,8 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
}
mMaxExpandedHeight = NotificationUtils.getFontScaledHeight(mContext,
R.dimen.notification_max_height);
+ mMaxExpandedHeightForPromotedOngoing = NotificationUtils.getFontScaledHeight(mContext,
+ R.dimen.notification_max_height_for_promoted_ongoing);
mMaxHeadsUpHeightBeforeN = NotificationUtils.getFontScaledHeight(mContext,
R.dimen.notification_max_heads_up_height_legacy);
mMaxHeadsUpHeightBeforeP = NotificationUtils.getFontScaledHeight(mContext,
@@ -2762,6 +2789,9 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
if (mIsSummaryWithChildren && !shouldShowPublic()) {
return !mChildrenExpanded;
}
+ if (isPromotedOngoing()) {
+ return false;
+ }
return mEnableNonGroupedNotificationExpand && mExpandable;
}
@@ -2770,6 +2800,18 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
mPrivateLayout.updateExpandButtons(isExpandable());
}
+ /**
+ * Set this notification to be promoted ongoing
+ */
+ public void setPromotedOngoing(boolean promotedOngoing) {
+ if (PromotedNotificationUiForceExpanded.isUnexpectedlyInLegacyMode()) {
+ return;
+ }
+
+ mIsPromotedOngoing = promotedOngoing;
+ setExpandable(!mIsPromotedOngoing);
+ }
+
@Override
public void setClipToActualHeight(boolean clipToActualHeight) {
super.setClipToActualHeight(clipToActualHeight || isUserLocked());
@@ -2839,6 +2881,8 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
}
public void setUserLocked(boolean userLocked) {
+ if (isPromotedOngoing()) return;
+
mUserLocked = userLocked;
mPrivateLayout.setUserExpanding(userLocked);
// This is intentionally not guarded with mIsSummaryWithChildren since we might have had
@@ -3000,6 +3044,35 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
}
}
+ public boolean isPromotedOngoing() {
+ return PromotedNotificationUiForceExpanded.isEnabled() && mIsPromotedOngoing;
+ }
+
+ private boolean isPromotedNotificationExpanded(boolean allowOnKeyguard) {
+ // public view in non group notifications is always collapsed.
+ if (shouldShowPublic()) {
+ return false;
+ }
+ // RON will always be expanded when it is not on keyguard.
+ if (!mOnKeyguard) {
+ return true;
+ }
+ // RON will always be expanded when it is allowed on keyguard.
+ // allowOnKeyguard is used for getting the maximum height by NotificationContentView and
+ // NotificationChildrenContainer.
+ if (allowOnKeyguard) {
+ return true;
+ }
+
+ // RON will be expanded when it needs to ignore lockscreen constraints.
+ if (mIgnoreLockscreenConstraints) {
+ return true;
+ }
+
+ // RON will need be collapsed when it needs to save space on the lock screen.
+ return !mSaveSpaceOnLockscreen;
+ }
+
/**
* Check whether the view state is currently expanded. This is given by the system in {@link
* #setSystemExpanded(boolean)} and can be overridden by user expansion or
@@ -3013,6 +3086,10 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
}
public boolean isExpanded(boolean allowOnKeyguard) {
+ if (isPromotedOngoing()) {
+ return isPromotedNotificationExpanded(allowOnKeyguard);
+ }
+
return (!shouldShowPublic()) && (!mOnKeyguard || allowOnKeyguard)
&& (!hasUserChangedExpansion()
&& (isSystemExpanded() || isSystemChildExpanded())
@@ -3184,7 +3261,10 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
@Override
public boolean mustStayOnScreen() {
- return mIsHeadsUp && mMustStayOnScreen;
+ // Must stay on screen in the open shade regardless how much the stack is scrolled if:
+ // 1. Is HUN and not marked as seen yet (isHeadsUp && mustStayOnScreen)
+ // 2. Is an FSI HUN (isPinned)
+ return mIsHeadsUp && mMustStayOnScreen || notificationsPinnedHunInShade() && isPinned();
}
/**
@@ -4011,6 +4091,11 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
+ (!shouldShowPublic() && mIsSummaryWithChildren));
pw.print(", mShowNoBackground: " + mShowNoBackground);
pw.print(", clipBounds: " + getClipBounds());
+ if (PromotedNotificationUiForceExpanded.isEnabled()) {
+ pw.print(", isPromotedOngoing: " + isPromotedOngoing());
+ pw.print(", isExpandable: " + isExpandable());
+ pw.print(", mExpandable: " + mExpandable);
+ }
pw.println();
if (NotificationContentView.INCLUDE_HEIGHTS_TO_DUMP) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridConversationNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridConversationNotificationView.java
index 1ff0d9262476..92c10abff735 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridConversationNotificationView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridConversationNotificationView.java
@@ -288,14 +288,21 @@ public class HybridConversationNotificationView extends HybridNotificationView {
public void setText(
CharSequence titleText,
CharSequence contentText,
- CharSequence conversationSenderName
+ CharSequence conversationSenderName,
+ @Nullable String summarization
) {
if (AsyncHybridViewInflation.isUnexpectedlyInLegacyMode()) return;
- if (conversationSenderName == null) {
+ if (summarization != null) {
mConversationSenderName.setVisibility(GONE);
+ titleText = null;
+ contentText = summarization;
} else {
- mConversationSenderName.setVisibility(VISIBLE);
- mConversationSenderName.setText(conversationSenderName);
+ if (conversationSenderName == null) {
+ mConversationSenderName.setVisibility(GONE);
+ } else {
+ mConversationSenderName.setVisibility(VISIBLE);
+ mConversationSenderName.setText(conversationSenderName);
+ }
}
// TODO (b/217799515): super.bind() doesn't use contentView, remove the contentView
// argument when the flag is removed
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/MagicActionBackgroundDrawable.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/MagicActionBackgroundDrawable.kt
index 793b3b8b1e42..6aa5e405f29c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/MagicActionBackgroundDrawable.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/MagicActionBackgroundDrawable.kt
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.notification.row
+import android.animation.ValueAnimator
import android.content.Context
import android.graphics.BlendMode
import android.graphics.Canvas
@@ -38,8 +39,8 @@ import androidx.compose.ui.viewinterop.AndroidView
import com.android.internal.graphics.ColorUtils
import com.android.systemui.res.R
import com.android.systemui.surfaceeffects.shaderutil.ShaderUtilLibrary
-import kotlin.math.max
-import kotlin.math.roundToInt
+import com.android.wm.shell.shared.animation.Interpolators
+import kotlin.math.min
/**
* A background style for smarter-smart-actions. The style is composed by a simplex3d noise,
@@ -48,7 +49,7 @@ import kotlin.math.roundToInt
class MagicActionBackgroundDrawable(
context: Context,
primaryContainer: Int? = null,
- private val seed: Float = 0f,
+ seed: Float = 0f,
) : Drawable() {
private val pixelDensity = context.resources.displayMetrics.density
@@ -60,17 +61,15 @@ class MagicActionBackgroundDrawable(
.toFloat()
private val buttonShape = Path()
private val paddingVertical =
- context.resources
- .getDimensionPixelSize(R.dimen.smart_reply_button_padding_vertical)
- .toFloat()
+ context.resources.getDimensionPixelSize(R.dimen.smart_action_button_icon_padding).toFloat()
/** The color of the button background. */
private val mainColor =
primaryContainer
?: context.getColor(com.android.internal.R.color.materialColorPrimaryContainer)
- /** Slightly dimmed down version of [mainColor] used on the simplex noise. */
- private val dimColor: Int
+ /** Slightly brighter version of [mainColor] used on the simplex noise. */
+ private val effectColor: Int
get() {
val labColor = arrayOf(0.0, 0.0, 0.0).toDoubleArray()
ColorUtils.colorToLAB(mainColor, labColor)
@@ -78,56 +77,97 @@ class MagicActionBackgroundDrawable(
return ColorUtils.CAMToColor(
camColor.hue,
camColor.chroma,
- max(0f, (labColor[0] - 20).toFloat()),
+ min(100f, (labColor[0] + 10).toFloat()),
)
}
private val bgShader = MagicActionBackgroundShader()
private val bgPaint = Paint()
private val outlinePaint = Paint()
+ private val gradientAnimator =
+ ValueAnimator.ofFloat(0f, 1f).apply {
+ duration = 2500
+ interpolator = Interpolators.LINEAR
+ addUpdateListener { invalidateSelf() }
+ }
+ private val turbulenceAnimator =
+ ValueAnimator.ofFloat(seed, seed + TURBULENCE_MOVEMENT).apply {
+ duration = ANIMATION_DURATION
+ interpolator = Interpolators.LINEAR
+ addUpdateListener { invalidateSelf() }
+ start()
+ }
+ private val effectFadeAnimation =
+ ValueAnimator.ofFloat(0f, 1f).apply {
+ duration = 1000
+ startDelay = ANIMATION_DURATION - 1000L
+ interpolator = Interpolators.STANDARD_DECELERATE
+ addUpdateListener { invalidateSelf() }
+ }
init {
bgShader.setColorUniform("in_color", mainColor)
- bgShader.setColorUniform("in_dimColor", dimColor)
+ bgShader.setColorUniform("in_effectColor", effectColor)
bgPaint.shader = bgShader
outlinePaint.style = Paint.Style.STROKE
// Stroke is doubled in width and then clipped, to avoid anti-aliasing artifacts at the edge
// of the rectangle.
outlinePaint.strokeWidth = outlineStrokeWidth * 2
outlinePaint.blendMode = BlendMode.SCREEN
- outlinePaint.alpha = (255 * 0.32f).roundToInt()
+ outlinePaint.alpha = OUTLINE_ALPHA
+
+ animate()
+ }
+
+ private fun animate() {
+ turbulenceAnimator.start()
+ gradientAnimator.start()
+ effectFadeAnimation.start()
}
override fun draw(canvas: Canvas) {
+ updateShaders()
+
// We clip instead of drawing 2 rounded rects, otherwise there will be artifacts where
// around the button background and the outline.
+ canvas.save()
canvas.clipPath(buttonShape)
+ canvas.drawPath(buttonShape, bgPaint)
+ canvas.drawPath(buttonShape, outlinePaint)
+ canvas.restore()
+ }
- canvas.drawRect(bounds, bgPaint)
- canvas.drawRoundRect(
- bounds.left.toFloat(),
- bounds.top + paddingVertical,
- bounds.right.toFloat(),
- bounds.bottom - paddingVertical,
- cornerRadius,
- cornerRadius,
- outlinePaint,
- )
+ private fun updateShaders() {
+ val effectAlpha = 1f - effectFadeAnimation.animatedValue as Float
+ val turbulenceZ = turbulenceAnimator.animatedValue as Float
+ bgShader.setFloatUniform("in_sparkleMove", turbulenceZ * 1000)
+ bgShader.setFloatUniform("in_noiseMove", 0f, 0f, turbulenceZ)
+ bgShader.setFloatUniform("in_turbulenceAlpha", effectAlpha)
+ bgShader.setFloatUniform("in_spkarkleAlpha", SPARKLE_ALPHA * effectAlpha)
+ val gradientOffset = gradientAnimator.animatedValue as Float * bounds.width()
+ val outlineGradient =
+ LinearGradient(
+ gradientOffset + bounds.left.toFloat(),
+ 0f,
+ gradientOffset + bounds.right.toFloat(),
+ 0f,
+ mainColor,
+ ColorUtils.setAlphaComponent(mainColor, 0),
+ Shader.TileMode.MIRROR,
+ )
+ outlinePaint.shader = outlineGradient
}
override fun onBoundsChange(bounds: Rect) {
super.onBoundsChange(bounds)
val width = bounds.width().toFloat()
- val height = bounds.height() - paddingVertical * 2
+ val height = bounds.height().toFloat()
if (width == 0f || height == 0f) return
bgShader.setFloatUniform("in_gridNum", NOISE_SIZE)
- bgShader.setFloatUniform("in_spkarkleAlpha", SPARKLE_ALPHA)
- bgShader.setFloatUniform("in_noiseMove", 0f, 0f, 0f)
bgShader.setFloatUniform("in_size", width, height)
bgShader.setFloatUniform("in_aspectRatio", width / height)
- bgShader.setFloatUniform("in_time", seed)
bgShader.setFloatUniform("in_pixelDensity", pixelDensity)
buttonShape.reset()
@@ -140,18 +180,6 @@ class MagicActionBackgroundDrawable(
cornerRadius,
Path.Direction.CW,
)
-
- val outlineGradient =
- LinearGradient(
- bounds.left.toFloat(),
- 0f,
- bounds.right.toFloat(),
- 0f,
- mainColor,
- ColorUtils.setAlphaComponent(mainColor, 0),
- Shader.TileMode.CLAMP,
- )
- outlinePaint.shader = outlineGradient
}
override fun setAlpha(alpha: Int) {
@@ -168,10 +196,15 @@ class MagicActionBackgroundDrawable(
companion object {
/** Smoothness of the turbulence. Larger numbers yield more detail. */
- private const val NOISE_SIZE = 0.7f
-
+ private const val NOISE_SIZE = 0.57f
/** Strength of the sparkles overlaid on the turbulence. */
private const val SPARKLE_ALPHA = 0.15f
+ /** Alpha (0..255) of the button outline */
+ private const val OUTLINE_ALPHA = 82
+ /** Turbulence grid size */
+ private const val TURBULENCE_MOVEMENT = 4.3f
+ /** Total animation duration in millis */
+ private const val ANIMATION_DURATION = 5000L
}
}
@@ -183,24 +216,25 @@ private class MagicActionBackgroundShader : RuntimeShader(SHADER) {
"""
uniform float in_gridNum;
uniform vec3 in_noiseMove;
+ uniform half in_sparkleMove;
uniform vec2 in_size;
uniform float in_aspectRatio;
- uniform half in_time;
uniform half in_pixelDensity;
+ uniform float in_turbulenceAlpha;
uniform float in_spkarkleAlpha;
layout(color) uniform vec4 in_color;
- layout(color) uniform vec4 in_dimColor;
+ layout(color) uniform vec4 in_effectColor;
"""
private const val MAIN_SHADER =
"""vec4 main(vec2 p) {
vec2 uv = p / in_size.xy;
uv.x *= in_aspectRatio;
vec3 noiseP = vec3(uv + in_noiseMove.xy, in_noiseMove.z) * in_gridNum;
- half luma = 1.0 - getLuminosity(half3(simplex3d(noiseP)));
- half4 turbulenceColor = mix(in_color, in_dimColor, luma);
- float sparkle = sparkles(p - mod(p, in_pixelDensity * 0.8), in_time);
+ half luma = getLuminosity(half3(simplex3d(noiseP)));
+ half4 turbulenceColor = mix(in_color, in_effectColor, luma * in_turbulenceAlpha);
+ float sparkle = sparkles(p - mod(p, in_pixelDensity * 0.8), in_sparkleMove);
sparkle = min(sparkle * in_spkarkleAlpha, in_spkarkleAlpha);
- return turbulenceColor + half4(half3(sparkle), 1.0);
+ return saturate(turbulenceColor + half4(sparkle));
}
"""
private const val SHADER = UNIFORMS + ShaderUtilLibrary.SHADER_LIB + MAIN_SHADER
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
index 57fe24f40acb..13ed6c449797 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
@@ -57,6 +57,7 @@ import com.android.systemui.statusbar.notification.ConversationNotificationProce
import com.android.systemui.statusbar.notification.InflationException;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.promoted.PromotedNotificationContentExtractor;
+import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUiForceExpanded;
import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel;
import com.android.systemui.statusbar.notification.row.shared.AsyncGroupHeaderViewInflation;
import com.android.systemui.statusbar.notification.row.shared.AsyncHybridViewInflation;
@@ -216,7 +217,8 @@ public class NotificationContentInflater implements NotificationRowContentBinder
messagingStyle,
builder,
row.getContext(),
- false
+ false,
+ entry.getRanking().getSummarization()
);
// If the messagingStyle is null, we want to inflate the normal view
isConversation = viewModel.isConversation();
@@ -238,7 +240,8 @@ public class NotificationContentInflater implements NotificationRowContentBinder
messagingStyle,
builder,
row.getContext(),
- true);
+ true,
+ entry.getRanking().getSummarization());
} else {
result.mPublicInflatedSingleLineViewModel =
SingleLineViewInflater.inflateRedactedSingleLineViewModel(
@@ -1111,6 +1114,10 @@ public class NotificationContentInflater implements NotificationRowContentBinder
entry.setHeadsUpStatusBarText(result.headsUpStatusBarText);
entry.setHeadsUpStatusBarTextPublic(result.headsUpStatusBarTextPublic);
+ if (PromotedNotificationUiForceExpanded.isEnabled()) {
+ row.setPromotedOngoing(entry.isOngoingPromoted());
+ }
+
Trace.endAsyncSection(APPLY_TRACE_METHOD, System.identityHashCode(row));
if (endListener != null) {
endListener.onAsyncInflationFinished(entry);
@@ -1313,7 +1320,8 @@ public class NotificationContentInflater implements NotificationRowContentBinder
messagingStyle,
recoveredBuilder,
mContext,
- false
+ false,
+ mEntry.getRanking().getSummarization()
);
result.mInflatedSingleLineView =
SingleLineViewInflater.inflatePrivateSingleLineView(
@@ -1333,7 +1341,8 @@ public class NotificationContentInflater implements NotificationRowContentBinder
messagingStyle,
recoveredBuilder,
mContext,
- true
+ true,
+ null
);
} else {
result.mPublicInflatedSingleLineViewModel =
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
index 9712db8a1812..b1e5b22f9b1a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
@@ -70,10 +70,10 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider;
import com.android.systemui.statusbar.notification.collection.render.NotifGutsViewListener;
import com.android.systemui.statusbar.notification.collection.render.NotifGutsViewManager;
+import com.android.systemui.statusbar.notification.headsup.HeadsUpManager;
import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
import com.android.systemui.statusbar.phone.CentralSurfaces;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
-import com.android.systemui.statusbar.notification.headsup.HeadsUpManager;
import com.android.systemui.util.kotlin.JavaAdapter;
import com.android.systemui.wmshell.BubblesManager;
@@ -422,7 +422,8 @@ public class NotificationGutsManager implements NotifGutsViewManager, CoreStarta
row.getIsNonblockable(),
mHighPriorityProvider.isHighPriority(row.getEntry()),
mAssistantFeedbackController,
- mMetricsLogger);
+ mMetricsLogger,
+ row.getCloseButtonOnClickListener(row));
}
/**
@@ -476,7 +477,8 @@ public class NotificationGutsManager implements NotifGutsViewManager, CoreStarta
row.getIsNonblockable(),
mHighPriorityProvider.isHighPriority(row.getEntry()),
mAssistantFeedbackController,
- mMetricsLogger);
+ mMetricsLogger,
+ row.getCloseButtonOnClickListener(row));
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java
index 20120991b5ac..8d26f94ced21 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java
@@ -202,7 +202,7 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G
boolean isNonblockable,
boolean wasShownHighPriority,
AssistantFeedbackController assistantFeedbackController,
- MetricsLogger metricsLogger)
+ MetricsLogger metricsLogger, OnClickListener onCloseClick)
throws RemoteException {
mINotificationManager = iNotificationManager;
mMetricsLogger = metricsLogger;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java
index 6e8ec9576f80..bf738aa1128f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java
@@ -42,6 +42,7 @@ import android.widget.FrameLayout.LayoutParams;
import com.android.app.animation.Interpolators;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.Flags;
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
import com.android.systemui.res.R;
import com.android.systemui.statusbar.AlphaOptimizedImageView;
@@ -270,6 +271,10 @@ public class NotificationMenuRow implements NotificationMenuRowPlugin, View.OnCl
mInfoItem = createPartialConversationItem(mContext);
} else if (personNotifType >= PeopleNotificationIdentifier.TYPE_FULL_PERSON) {
mInfoItem = createConversationItem(mContext);
+ } else if (android.app.Flags.uiRichOngoing()
+ && Flags.permissionHelperUiRichOngoing()
+ && entry.getSbn().getNotification().isPromotedOngoing()) {
+ mInfoItem = createPromotedItem(mContext);
} else {
mInfoItem = createInfoItem(mContext);
}
@@ -682,6 +687,16 @@ public class NotificationMenuRow implements NotificationMenuRowPlugin, View.OnCl
R.drawable.ic_settings);
}
+ static NotificationMenuItem createPromotedItem(Context context) {
+ Resources res = context.getResources();
+ String infoDescription = res.getString(R.string.notification_menu_gear_description);
+ PromotedNotificationInfo infoContent =
+ (PromotedNotificationInfo) LayoutInflater.from(context).inflate(
+ R.layout.promoted_notification_info, null, false);
+ return new NotificationMenuItem(context, infoDescription, infoContent,
+ R.drawable.ic_settings);
+ }
+
static NotificationMenuItem createBundleItem(Context context) {
Resources res = context.getResources();
String infoDescription = res.getString(R.string.notification_menu_gear_description);
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 0b299d965b09..f4aae6e288a7 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
@@ -719,6 +719,7 @@ constructor(
builder = builder,
systemUiContext = systemUiContext,
redactText = false,
+ summarization = entry.ranking.summarization
)
} else null
@@ -735,6 +736,7 @@ constructor(
builder = builder,
systemUiContext = systemUiContext,
redactText = true,
+ summarization = null
)
} else {
SingleLineViewInflater.inflateRedactedSingleLineViewModel(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PromotedNotificationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PromotedNotificationInfo.java
new file mode 100644
index 000000000000..e4a0fa5b534e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PromotedNotificationInfo.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.statusbar.notification.row;
+
+import android.app.INotificationManager;
+import android.app.NotificationChannel;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.os.RemoteException;
+import android.service.notification.StatusBarNotification;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.View;
+
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.UiEventLogger;
+import com.android.systemui.res.R;
+import com.android.systemui.statusbar.notification.AssistantFeedbackController;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+
+/**
+ * The guts of a notification revealed when performing a long press, specifically
+ * for notifications that are shown as promoted. Contains extra controls to allow user to revoke
+ * app permissions for sending promoted notifications.
+ */
+public class PromotedNotificationInfo extends NotificationInfo {
+ private static final String TAG = "PromotedNotifInfoGuts";
+ private INotificationManager mNotificationManager;
+
+ public PromotedNotificationInfo(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ @Override
+ public void bindNotification(
+ PackageManager pm,
+ INotificationManager iNotificationManager,
+ OnUserInteractionCallback onUserInteractionCallback,
+ ChannelEditorDialogController channelEditorDialogController,
+ String pkg,
+ NotificationChannel notificationChannel,
+ NotificationEntry entry,
+ OnSettingsClickListener onSettingsClick,
+ OnAppSettingsClickListener onAppSettingsClick,
+ UiEventLogger uiEventLogger,
+ boolean isDeviceProvisioned,
+ boolean isNonblockable,
+ boolean wasShownHighPriority,
+ AssistantFeedbackController assistantFeedbackController,
+ MetricsLogger metricsLogger, OnClickListener onCloseClick) throws RemoteException {
+ super.bindNotification(pm, iNotificationManager, onUserInteractionCallback,
+ channelEditorDialogController, pkg, notificationChannel, entry, onSettingsClick,
+ onAppSettingsClick, uiEventLogger, isDeviceProvisioned, isNonblockable,
+ wasShownHighPriority, assistantFeedbackController, metricsLogger, onCloseClick);
+
+ mNotificationManager = iNotificationManager;
+
+ bindDismiss(entry.getSbn(), onCloseClick);
+ bindDemote(entry.getSbn(), pkg);
+ }
+
+
+ protected void bindDismiss(StatusBarNotification sbn,
+ View.OnClickListener onCloseClick) {
+ View dismissButton = findViewById(R.id.promoted_dismiss);
+
+ dismissButton.setOnClickListener(onCloseClick);
+ dismissButton.setVisibility(!sbn.isNonDismissable()
+ && dismissButton.hasOnClickListeners() ? VISIBLE : GONE);
+
+ }
+
+ protected void bindDemote(StatusBarNotification sbn, String packageName) {
+ View demoteButton = findViewById(R.id.promoted_demote);
+ demoteButton.setOnClickListener(getDemoteClickListener(sbn, packageName));
+ demoteButton.setVisibility(demoteButton.hasOnClickListeners() ? VISIBLE : GONE);
+ }
+
+ private OnClickListener getDemoteClickListener(StatusBarNotification sbn, String packageName) {
+ return ((View unusedView) -> {
+ try {
+ // TODO(b/391661009): Signal AutomaticPromotionCoordinator here
+ mNotificationManager.setCanBePromoted(packageName, sbn.getUid(), false, true);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Couldn't revoke live update permission", e);
+ }
+ });
+ }
+}
+
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/SingleLineViewInflater.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/SingleLineViewInflater.kt
index fe2803bfc5d6..c051513ef3b4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/SingleLineViewInflater.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/SingleLineViewInflater.kt
@@ -61,6 +61,7 @@ internal object SingleLineViewInflater {
builder: Notification.Builder,
systemUiContext: Context,
redactText: Boolean,
+ summarization: String?
): SingleLineViewModel {
if (AsyncHybridViewInflation.isUnexpectedlyInLegacyMode()) {
return SingleLineViewModel(null, null, null)
@@ -108,6 +109,7 @@ internal object SingleLineViewInflater {
conversationSenderName =
if (isGroupConversation) conversationTextData?.senderName else null,
avatar = conversationAvatar,
+ summarization = summarization
)
return SingleLineViewModel(
@@ -132,6 +134,7 @@ internal object SingleLineViewInflater {
.ic_redacted_notification_single_line_icon
)
),
+ null
)
} else {
null
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewbinder/SingleLineViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewbinder/SingleLineViewBinder.kt
index a17197c1f8ea..a50fc4c7986a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewbinder/SingleLineViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewbinder/SingleLineViewBinder.kt
@@ -32,6 +32,7 @@ object SingleLineViewBinder {
viewModel?.titleText,
viewModel?.contentText,
viewModel?.conversationData?.conversationSenderName,
+ viewModel?.conversationData?.summarization
)
} else {
// bind the title and content text views
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/SingleLineViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/SingleLineViewModel.kt
index d583fa5d97ed..32ded25f18a1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/SingleLineViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/SingleLineViewModel.kt
@@ -46,6 +46,7 @@ data class SingleLineViewModel(
data class ConversationData(
val conversationSenderName: CharSequence?,
val avatar: ConversationAvatar,
+ val summarization: String?
)
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt
index a96d972af2c4..08bc8f5d5bb9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt
@@ -29,6 +29,7 @@ import com.android.systemui.statusbar.LockscreenShadeTransitionController
import com.android.systemui.statusbar.StatusBarState.KEYGUARD
import com.android.systemui.statusbar.SysuiStatusBarStateController
import com.android.systemui.statusbar.notification.domain.interactor.SeenNotificationsInteractor
+import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUiForceExpanded
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
import com.android.systemui.statusbar.notification.row.ExpandableView
import com.android.systemui.statusbar.notification.shared.NotificationMinimalism
@@ -463,7 +464,12 @@ constructor(
var size =
if (onLockscreen) {
- if (view is ExpandableNotificationRow && view.entry.isStickyAndNotDemoted) {
+ if (
+ view is ExpandableNotificationRow &&
+ (view.entry.isStickyAndNotDemoted ||
+ (PromotedNotificationUiForceExpanded.isEnabled &&
+ view.isPromotedOngoing))
+ ) {
height
} else {
view.getMinHeight(/* ignoreTemporaryStates= */ true).toFloat()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt
index 1965b9538df0..f7401440cfcb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt
@@ -36,6 +36,8 @@ import com.android.systemui.util.kotlin.DisposableHandles
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.DisposableHandle
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.map
/** Binds the shared notification container to its view-model. */
@SysUISingleton
@@ -143,21 +145,25 @@ constructor(
if (!SceneContainerFlag.isEnabled) {
if (Flags.magicPortraitWallpapers()) {
launch {
- viewModel
- .getNotificationStackAbsoluteBottom(
- calculateMaxNotifications = calculateMaxNotifications,
- calculateHeight = { maxNotifications ->
- notificationStackSizeCalculator.computeHeight(
- maxNotifs = maxNotifications,
- shelfHeight = controller.getShelfHeight().toFloat(),
- stack = controller.view,
- )
- },
- controller.getShelfHeight().toFloat(),
+ combine(
+ viewModel.getNotificationStackAbsoluteBottom(
+ calculateMaxNotifications = calculateMaxNotifications,
+ calculateHeight = { maxNotifications ->
+ notificationStackSizeCalculator.computeHeight(
+ maxNotifs = maxNotifications,
+ shelfHeight =
+ controller.getShelfHeight().toFloat(),
+ stack = controller.view,
+ )
+ },
+ controller.getShelfHeight().toFloat(),
+ ),
+ viewModel.configurationBasedDimensions.map { it.marginTop },
+ ::Pair,
)
- .collect { bottom ->
+ .collect { (bottom: Float, marginTop: Int) ->
keyguardInteractor.setNotificationStackAbsoluteBottom(
- bottom
+ marginTop + bottom
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardIndicationTextView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardIndicationTextView.java
index 16e9c717935c..a2f1ded042f4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardIndicationTextView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardIndicationTextView.java
@@ -26,24 +26,31 @@ import android.graphics.drawable.Drawable;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.View;
-import android.widget.TextView;
import androidx.annotation.StyleRes;
+import androidx.core.graphics.ColorUtils;
import com.android.app.animation.Interpolators;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.Flags;
import com.android.systemui.keyguard.KeyguardIndication;
import com.android.systemui.res.R;
+import com.android.systemui.shared.shadow.DoubleShadowTextView;
/**
* A view to show hints on Keyguard ("Swipe up to unlock", "Tap again to open").
*/
-public class KeyguardIndicationTextView extends TextView {
+public class KeyguardIndicationTextView extends DoubleShadowTextView {
+ // Minimum luminance for texts to receive shadows.
+ private static final float MIN_TEXT_SHADOW_LUMINANCE = 0.5f;
public static final long Y_IN_DURATION = 600L;
@StyleRes
private static int sStyleId = R.style.TextAppearance_Keyguard_BottomArea;
@StyleRes
+ private static int sStyleWithDoubleShadowTextId =
+ R.style.TextAppearance_Keyguard_BottomArea_DoubleShadow;
+ @StyleRes
private static int sButtonStyleId = R.style.TextAppearance_Keyguard_BottomArea_Button;
private boolean mAnimationsEnabled = true;
@@ -226,7 +233,14 @@ public class KeyguardIndicationTextView extends TextView {
if (mKeyguardIndicationInfo.getBackground() != null) {
setTextAppearance(sButtonStyleId);
} else {
- setTextAppearance(sStyleId);
+ // If text is transparent or dark color, don't draw any shadow
+ if (Flags.indicationTextA11yFix() && ColorUtils.calculateLuminance(
+ mKeyguardIndicationInfo.getTextColor().getDefaultColor())
+ > MIN_TEXT_SHADOW_LUMINANCE) {
+ setTextAppearance(sStyleWithDoubleShadowTextId);
+ } else {
+ setTextAppearance(sStyleId);
+ }
}
setBackground(mKeyguardIndicationInfo.getBackground());
setTextColor(mKeyguardIndicationInfo.getTextColor());
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
index 4c2bfe5ca257..40245aef4f67 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
@@ -23,6 +23,7 @@ import static com.android.systemui.Flags.updateUserSwitcherBackground;
import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow;
+import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.database.ContentObserver;
@@ -56,6 +57,7 @@ import com.android.systemui.log.core.LogLevel;
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.ShadeDisplayAware;
import com.android.systemui.shade.ShadeViewStateProvider;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.StatusBarState;
@@ -114,6 +116,7 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat
R.id.keyguard_hun_animator_start_tag);
private final CoroutineDispatcher mCoroutineDispatcher;
+ private final Context mContext;
private final CarrierTextController mCarrierTextController;
private final ConfigurationController mConfigurationController;
private final SystemStatusAnimationScheduler mAnimationScheduler;
@@ -129,7 +132,7 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat
private final KeyguardStatusBarViewModel mKeyguardStatusBarViewModel;
private final BiometricUnlockController mBiometricUnlockController;
private final SysuiStatusBarStateController mStatusBarStateController;
- private final StatusBarContentInsetsProvider mInsetsProvider;
+ private final StatusBarContentInsetsProviderStore mInsetsProviderStore;
private final UserManager mUserManager;
private final StatusBarUserChipViewModel mStatusBarUserChipViewModel;
private final SecureSettings mSecureSettings;
@@ -314,6 +317,7 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat
@Inject
public KeyguardStatusBarViewController(
@Main CoroutineDispatcher dispatcher,
+ @ShadeDisplayAware Context context,
KeyguardStatusBarView view,
CarrierTextController carrierTextController,
ConfigurationController configurationController,
@@ -347,6 +351,7 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat
) {
super(view);
mCoroutineDispatcher = dispatcher;
+ mContext = context;
mCarrierTextController = carrierTextController;
mConfigurationController = configurationController;
mAnimationScheduler = animationScheduler;
@@ -362,7 +367,7 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat
mKeyguardStatusBarViewModel = keyguardStatusBarViewModel;
mBiometricUnlockController = biometricUnlockController;
mStatusBarStateController = statusBarStateController;
- mInsetsProvider = statusBarContentInsetsProviderStore.getDefaultDisplay();
+ mInsetsProviderStore = statusBarContentInsetsProviderStore;
mUserManager = userManager;
mStatusBarUserChipViewModel = userChipViewModel;
mSecureSettings = secureSettings;
@@ -404,6 +409,10 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat
mStatusOverlayHoverListenerFactory = statusOverlayHoverListenerFactory;
}
+ private StatusBarContentInsetsProvider insetsProvider() {
+ return mInsetsProviderStore.forDisplay(mContext.getDisplayId());
+ }
+
@Override
protected void onInit() {
super.onInit();
@@ -446,7 +455,7 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat
.createDarkAwareListener(mSystemIconsContainer, mView.darkChangeFlow());
mSystemIconsContainer.setOnHoverListener(hoverListener);
mView.setOnApplyWindowInsetsListener(
- (view, windowInsets) -> mView.updateWindowInsets(windowInsets, mInsetsProvider));
+ (view, windowInsets) -> mView.updateWindowInsets(windowInsets, insetsProvider()));
mSecureSettings.registerContentObserverForUserSync(
Settings.Secure.STATUS_BAR_SHOW_VIBRATE_ICON,
false,
@@ -645,7 +654,7 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat
* {@code OnApplyWindowInsetsListener}s.
*/
public void setDisplayCutout(@Nullable DisplayCutout displayCutout) {
- mView.setDisplayCutout(displayCutout, mInsetsProvider);
+ mView.setDisplayCutout(displayCutout, insetsProvider());
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
index 3f44f7bdef90..caf8a43b2aaf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
@@ -46,7 +46,6 @@ import com.android.systemui.shade.shared.flag.ShadeWindowGoesAround
import com.android.systemui.shared.animation.UnfoldMoveFromCenterAnimator
import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
import com.android.systemui.statusbar.data.repository.StatusBarContentInsetsProviderStore
-import com.android.systemui.statusbar.layout.StatusBarContentInsetsProvider
import com.android.systemui.statusbar.policy.Clock
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.statusbar.window.StatusBarWindowStateController
@@ -84,7 +83,7 @@ private constructor(
private val configurationController: ConfigurationController,
private val statusOverlayHoverListenerFactory: StatusOverlayHoverListenerFactory,
private val darkIconDispatcher: DarkIconDispatcher,
- private val statusBarContentInsetsProvider: StatusBarContentInsetsProvider,
+ private val statusBarContentInsetsProviderStore: StatusBarContentInsetsProviderStore,
private val lazyStatusBarShadeDisplayPolicy: Lazy<StatusBarTouchShadeDisplayPolicy>,
) : ViewController<PhoneStatusBarView>(view) {
@@ -92,6 +91,8 @@ private constructor(
private lateinit var clock: Clock
private lateinit var startSideContainer: View
private lateinit var endSideContainer: View
+ private val statusBarContentInsetsProvider
+ get() = statusBarContentInsetsProviderStore.forDisplay(context.displayId)
private val iconsOnTouchListener =
object : View.OnTouchListener {
@@ -189,11 +190,9 @@ private constructor(
init {
// These should likely be done in `onInit`, not `init`.
mView.setTouchEventHandler(PhoneStatusBarViewTouchHandler())
- mView.setHasCornerCutoutFetcher {
- statusBarContentInsetsProvider.currentRotationHasCornerCutout()
- }
- mView.setInsetsFetcher {
- statusBarContentInsetsProvider.getStatusBarContentInsetsForCurrentRotation()
+ statusBarContentInsetsProvider?.let {
+ mView.setHasCornerCutoutFetcher { it.currentRotationHasCornerCutout() }
+ mView.setInsetsFetcher { it.getStatusBarContentInsetsForCurrentRotation() }
}
mView.init(userChipViewModel)
}
@@ -393,7 +392,7 @@ private constructor(
configurationController,
statusOverlayHoverListenerFactory,
darkIconDispatcher,
- statusBarContentInsetsProviderStore.defaultDisplay,
+ statusBarContentInsetsProviderStore,
lazyStatusBarShadeDisplayPolicy,
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
index 0f6c3069609e..be48c3d928f4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -66,6 +66,7 @@ import com.android.systemui.keyguard.shared.model.KeyguardState;
import com.android.systemui.keyguard.shared.model.ScrimAlpha;
import com.android.systemui.keyguard.shared.model.TransitionState;
import com.android.systemui.keyguard.shared.model.TransitionStep;
+import com.android.systemui.keyguard.ui.transitions.BlurConfig;
import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerToGoneTransitionViewModel;
import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel;
import com.android.systemui.res.R;
@@ -258,6 +259,7 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump
private int mScrimsVisibility;
private final TriConsumer<ScrimState, Float, GradientColors> mScrimStateListener;
private final LargeScreenShadeInterpolator mLargeScreenShadeInterpolator;
+ private final BlurConfig mBlurConfig;
private Consumer<Integer> mScrimVisibleListener;
private boolean mBlankScreen;
private boolean mScreenBlankingCallbackCalled;
@@ -339,9 +341,11 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump
KeyguardTransitionInteractor keyguardTransitionInteractor,
KeyguardInteractor keyguardInteractor,
@Main CoroutineDispatcher mainDispatcher,
- LargeScreenShadeInterpolator largeScreenShadeInterpolator) {
+ LargeScreenShadeInterpolator largeScreenShadeInterpolator,
+ BlurConfig blurConfig) {
mScrimStateListener = lightBarController::setScrimState;
mLargeScreenShadeInterpolator = largeScreenShadeInterpolator;
+ mBlurConfig = blurConfig;
// All scrims default alpha need to match bouncer background alpha to make sure the
// transitions involving the bouncer are smooth and don't overshoot the bouncer alpha.
mDefaultScrimAlpha =
@@ -406,7 +410,7 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump
final ScrimState[] states = ScrimState.values();
for (int i = 0; i < states.length; i++) {
- states[i].init(mScrimInFront, mScrimBehind, mDozeParameters, mDockManager);
+ states[i].init(mScrimInFront, mScrimBehind, mDozeParameters, mDockManager, mBlurConfig);
states[i].setScrimBehindAlphaKeyguard(mScrimBehindAlphaKeyguard);
states[i].setDefaultScrimAlpha(mDefaultScrimAlpha);
}
@@ -868,7 +872,7 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump
* bounds instead.
*/
public void setClipsQsScrim(boolean clipScrim) {
- if (Flags.notificationShadeBlur()) {
+ if (Flags.notificationShadeBlur() || Flags.bouncerUiRevamp()) {
// Never clip scrims when blur is enabled, colors of UI elements are supposed to "add"
// up across the scrims.
mClipsQsScrim = false;
@@ -1210,6 +1214,12 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump
dispatchBackScrimState(mScrimBehind.getViewAlpha());
}
+ if (Flags.bouncerUiRevamp()) {
+ // Blur the notification scrim as needed. The blur is needed only when we show the
+ // expanded shade behind the bouncer. Without it, the notification scrim outline is
+ // visible behind the bouncer.
+ mNotificationsScrim.setBlurRadius(mState.getNotifBlurRadius());
+ }
// We also want to hide FLAG_SHOW_WHEN_LOCKED activities under the scrim.
boolean hideFlagShowWhenLockedActivities =
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
index 8170e6d91a0f..5f423cf35edd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
@@ -24,6 +24,7 @@ import android.graphics.Color;
import com.android.app.tracing.coroutines.TrackTracer;
import com.android.systemui.Flags;
import com.android.systemui.dock.DockManager;
+import com.android.systemui.keyguard.ui.transitions.BlurConfig;
import com.android.systemui.res.R;
import com.android.systemui.scrim.ScrimView;
import com.android.systemui.shade.ui.ShadeColors;
@@ -149,7 +150,14 @@ public enum ScrimState {
@Override
public void prepare(ScrimState previousState) {
if (Flags.bouncerUiRevamp()) {
- mBehindAlpha = 0f;
+ if (previousState == SHADE_LOCKED) {
+ mBehindAlpha = previousState.getBehindAlpha();
+ mNotifAlpha = previousState.getNotifAlpha();
+ mNotifBlurRadius = mBlurConfig.getMaxBlurRadiusPx();
+ } else {
+ mNotifAlpha = 0f;
+ mBehindAlpha = 0f;
+ }
mFrontAlpha = TRANSPARENT_BOUNCER_SCRIM_ALPHA;
mFrontTint = mSurfaceColor;
return;
@@ -395,6 +403,7 @@ public enum ScrimState {
DozeParameters mDozeParameters;
DockManager mDockManager;
boolean mDisplayRequiresBlanking;
+ protected BlurConfig mBlurConfig;
boolean mLaunchingAffordanceWithPreview;
boolean mOccludeAnimationPlaying;
boolean mWakeLockScreenSensorActive;
@@ -403,8 +412,12 @@ public enum ScrimState {
boolean mClipQsScrim;
int mBackgroundColor;
+ // This is needed to blur the scrim behind the scrimmed bouncer to avoid showing
+ // the notification section border
+ protected float mNotifBlurRadius = 0.0f;
+
public void init(ScrimView scrimInFront, ScrimView scrimBehind, DozeParameters dozeParameters,
- DockManager dockManager) {
+ DockManager dockManager, BlurConfig blurConfig) {
mBackgroundColor = scrimBehind.getContext().getColor(R.color.shade_scrim_background_dark);
mScrimInFront = scrimInFront;
mScrimBehind = scrimBehind;
@@ -412,6 +425,7 @@ public enum ScrimState {
mDozeParameters = dozeParameters;
mDockManager = dockManager;
mDisplayRequiresBlanking = dozeParameters.getDisplayNeedsBlanking();
+ mBlurConfig = blurConfig;
}
/** Prepare state for transition. */
@@ -518,4 +532,8 @@ public enum ScrimState {
public void setClipQsScrim(boolean clipsQsScrim) {
mClipQsScrim = clipsQsScrim;
}
+
+ public float getNotifBlurRadius() {
+ return mNotifBlurRadius;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTransitionAnimatorController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTransitionAnimatorController.kt
index 705a11df83fc..e12b21eb2c4a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTransitionAnimatorController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTransitionAnimatorController.kt
@@ -1,6 +1,7 @@
package com.android.systemui.statusbar.phone
import android.view.View
+import com.android.systemui.Flags
import com.android.systemui.animation.ActivityTransitionAnimator
import com.android.systemui.animation.TransitionAnimator
import com.android.systemui.animation.TransitionAnimator.Companion.getProgress
@@ -22,7 +23,7 @@ class StatusBarTransitionAnimatorController(
private val notificationShadeWindowController: NotificationShadeWindowController,
private val commandQueue: CommandQueue,
@DisplayId private val displayId: Int,
- private val isLaunchForActivity: Boolean = true
+ private val isLaunchForActivity: Boolean = true,
) : ActivityTransitionAnimator.Controller by delegate {
private var hideIconsDuringLaunchAnimation: Boolean = true
@@ -41,8 +42,16 @@ class StatusBarTransitionAnimatorController(
}
override fun onTransitionAnimationStart(isExpandingFullyAbove: Boolean) {
- delegate.onTransitionAnimationStart(isExpandingFullyAbove)
- shadeAnimationInteractor.setIsLaunchingActivity(true)
+ if (Flags.shadeLaunchAccessibility()) {
+ // We set this before calling the delegate to make sure that accessibility is disabled
+ // for the whole duration of the transition, so that we don't have stray TalkBack events
+ // once the animating view becomes invisible.
+ shadeAnimationInteractor.setIsLaunchingActivity(true)
+ delegate.onTransitionAnimationStart(isExpandingFullyAbove)
+ } else {
+ delegate.onTransitionAnimationStart(isExpandingFullyAbove)
+ shadeAnimationInteractor.setIsLaunchingActivity(true)
+ }
if (!isExpandingFullyAbove) {
shadeController.collapseWithDuration(
ActivityTransitionAnimator.TIMINGS.totalDuration.toInt()
@@ -59,7 +68,7 @@ class StatusBarTransitionAnimatorController(
override fun onTransitionAnimationProgress(
state: TransitionAnimator.State,
progress: Float,
- linearProgress: Float
+ linearProgress: Float,
) {
delegate.onTransitionAnimationProgress(state, progress, linearProgress)
val hideIcons =
@@ -67,7 +76,7 @@ class StatusBarTransitionAnimatorController(
ActivityTransitionAnimator.TIMINGS,
linearProgress,
ANIMATION_DELAY_ICON_FADE_IN,
- 100
+ 100,
) == 0.0f
if (hideIcons != hideIconsDuringLaunchAnimation) {
hideIconsDuringLaunchAnimation = hideIcons
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/TutorialSelectionScreen.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/TutorialSelectionScreen.kt
index 69b7e892a380..9795cda97f37 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/TutorialSelectionScreen.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/TutorialSelectionScreen.kt
@@ -49,8 +49,10 @@ import androidx.compose.ui.input.pointer.pointerInteropFilter
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.res.vectorResource
+import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import com.android.systemui.inputdevice.tutorial.ui.composable.DoneButton
+import com.android.systemui.keyboard.shortcut.ui.composable.hasCompactWindowSize
import com.android.systemui.res.R
import com.android.systemui.touchpad.tutorial.ui.gesture.isFourFingerTouchpadSwipe
import com.android.systemui.touchpad.tutorial.ui.gesture.isThreeFingerTouchpadSwipe
@@ -80,6 +82,7 @@ fun TutorialSelectionScreen(
}
),
) {
+ val padding = if (hasCompactWindowSize()) 24.dp else 60.dp
val configuration = LocalConfiguration.current
when (configuration.orientation) {
Configuration.ORIENTATION_LANDSCAPE -> {
@@ -88,7 +91,7 @@ fun TutorialSelectionScreen(
onHomeTutorialClicked = onHomeTutorialClicked,
onRecentAppsTutorialClicked = onRecentAppsTutorialClicked,
onSwitchAppsTutorialClicked = onSwitchAppsTutorialClicked,
- modifier = Modifier.weight(1f).padding(60.dp),
+ modifier = Modifier.weight(1f).padding(padding),
lastSelectedScreen,
)
}
@@ -98,7 +101,7 @@ fun TutorialSelectionScreen(
onHomeTutorialClicked = onHomeTutorialClicked,
onRecentAppsTutorialClicked = onRecentAppsTutorialClicked,
onSwitchAppsTutorialClicked = onSwitchAppsTutorialClicked,
- modifier = Modifier.weight(1f).padding(60.dp),
+ modifier = Modifier.weight(1f).padding(padding),
lastSelectedScreen,
)
}
@@ -106,7 +109,7 @@ fun TutorialSelectionScreen(
// because other composables have weight 1, Done button will be positioned first
DoneButton(
onDoneButtonClicked = onDoneButtonClicked,
- modifier = Modifier.padding(horizontal = 60.dp),
+ modifier = Modifier.padding(horizontal = padding),
)
}
}
@@ -146,7 +149,7 @@ private fun VerticalSelectionButtons(
lastSelectedScreen: Screen,
) {
Column(
- verticalArrangement = Arrangement.spacedBy(20.dp),
+ verticalArrangement = Arrangement.spacedBy(16.dp),
horizontalAlignment = Alignment.CenterHorizontally,
modifier = modifier,
) {
@@ -244,8 +247,13 @@ private fun TutorialButton(
modifier = Modifier.width(30.dp).height(30.dp),
tint = iconColor,
)
- Spacer(modifier = Modifier.height(16.dp))
- Text(text = text, style = MaterialTheme.typography.headlineLarge, color = iconColor)
+ if (!hasCompactWindowSize()) Spacer(modifier = Modifier.height(16.dp))
+ Text(
+ text = text,
+ textAlign = TextAlign.Center,
+ style = MaterialTheme.typography.headlineLarge,
+ color = iconColor,
+ )
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/VolumeDialog.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/VolumeDialog.kt
index 39b434ad65f1..83b7c1818341 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/VolumeDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/VolumeDialog.kt
@@ -16,7 +16,6 @@
package com.android.systemui.volume.dialog
-import android.app.Dialog
import android.content.Context
import android.graphics.PixelFormat
import android.os.Bundle
@@ -24,6 +23,7 @@ import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
import android.view.WindowManager
+import androidx.activity.ComponentDialog
import com.android.app.tracing.coroutines.coroutineScopeTraced
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.lifecycle.repeatWhenAttached
@@ -40,7 +40,7 @@ constructor(
@Application context: Context,
private val componentFactory: VolumeDialogComponent.Factory,
private val visibilityInteractor: VolumeDialogVisibilityInteractor,
-) : Dialog(context, R.style.Theme_SystemUI_Dialog_Volume) {
+) : ComponentDialog(context, R.style.Theme_SystemUI_Dialog_Volume) {
init {
with(window!!) {
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogCallbacksInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogCallbacksInteractor.kt
index 8c018606ebda..e261ceebf33e 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogCallbacksInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogCallbacksInteractor.kt
@@ -17,6 +17,8 @@
package com.android.systemui.volume.dialog.domain.interactor
import android.annotation.SuppressLint
+import android.media.AudioManager.RINGER_MODE_NORMAL
+import android.media.AudioManager.RINGER_MODE_SILENT
import android.os.Handler
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.plugins.VolumeDialogController
@@ -60,10 +62,10 @@ constructor(
awaitClose { volumeDialogController.removeCallback(producer) }
}
.buffer(capacity = BUFFER_CAPACITY, onBufferOverflow = BufferOverflow.DROP_OLDEST)
- .shareIn(replay = 0, scope = coroutineScope, started = SharingStarted.WhileSubscribed())
+ .shareIn(replay = 0, scope = coroutineScope, started = SharingStarted.Eagerly)
.onStart { emit(VolumeDialogEventModel.SubscribedToEvents) }
- private class VolumeDialogEventModelProducer(
+ private inner class VolumeDialogEventModelProducer(
private val scope: ProducerScope<VolumeDialogEventModel>
) : VolumeDialogController.Callbacks {
override fun onShowRequested(reason: Int, keyguardLocked: Boolean, lockTaskModeState: Int) {
@@ -93,14 +95,6 @@ constructor(
// Configuration change is never emitted by the VolumeDialogControllerImpl now.
override fun onConfigurationChanged() = Unit
- override fun onShowVibrateHint() {
- scope.trySend(VolumeDialogEventModel.ShowVibrateHint)
- }
-
- override fun onShowSilentHint() {
- scope.trySend(VolumeDialogEventModel.ShowSilentHint)
- }
-
override fun onScreenOff() {
scope.trySend(VolumeDialogEventModel.ScreenOff)
}
@@ -113,16 +107,6 @@ constructor(
scope.trySend(VolumeDialogEventModel.AccessibilityModeChanged(showA11yStream == true))
}
- // Captions button is remove from the Volume Dialog
- override fun onCaptionComponentStateChanged(
- isComponentEnabled: Boolean,
- fromTooltip: Boolean,
- ) = Unit
-
- // Captions button is remove from the Volume Dialog
- override fun onCaptionEnabledStateChanged(isEnabled: Boolean, checkBeforeSwitch: Boolean) =
- Unit
-
override fun onShowCsdWarning(csdWarning: Int, durationMs: Int) {
scope.trySend(
VolumeDialogEventModel.ShowCsdWarning(
@@ -135,5 +119,25 @@ constructor(
override fun onVolumeChangedFromKey() {
scope.trySend(VolumeDialogEventModel.VolumeChangedFromKey)
}
+
+ // This should've been handled in side the controller itself.
+ override fun onShowVibrateHint() {
+ volumeDialogController.setRingerMode(RINGER_MODE_SILENT, false)
+ }
+
+ // This should've been handled in side the controller itself.
+ override fun onShowSilentHint() {
+ volumeDialogController.setRingerMode(RINGER_MODE_NORMAL, false)
+ }
+
+ // Captions button is remove from the Volume Dialog
+ override fun onCaptionComponentStateChanged(
+ isComponentEnabled: Boolean,
+ fromTooltip: Boolean,
+ ) = Unit
+
+ // Captions button is remove from the Volume Dialog
+ override fun onCaptionEnabledStateChanged(isEnabled: Boolean, checkBeforeSwitch: Boolean) =
+ Unit
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/model/VolumeDialogEventModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/model/VolumeDialogEventModel.kt
index 9793d2be6b98..a0214dc957a4 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/model/VolumeDialogEventModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/model/VolumeDialogEventModel.kt
@@ -38,10 +38,6 @@ sealed interface VolumeDialogEventModel {
data class LayoutDirectionChanged(val layoutDirection: Int) : VolumeDialogEventModel
- data object ShowVibrateHint : VolumeDialogEventModel
-
- data object ShowSilentHint : VolumeDialogEventModel
-
data object ScreenOff : VolumeDialogEventModel
data class ShowSafetyWarning(val flags: Int) : VolumeDialogEventModel
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/domain/VolumeDialogRingerInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/domain/VolumeDialogRingerInteractor.kt
index 40719185e290..73f6236393b2 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/domain/VolumeDialogRingerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/domain/VolumeDialogRingerInteractor.kt
@@ -47,7 +47,6 @@ constructor(
private val audioSystemRepository: AudioSystemRepository,
private val ringerFeedbackRepository: VolumeDialogRingerFeedbackRepository,
) {
-
val ringerModel: Flow<VolumeDialogRingerModel> =
volumeDialogStateInteractor.volumeDialogState
.mapNotNull { toRingerModel(it) }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/dagger/VolumeDialogSliderComponent.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/dagger/VolumeDialogSliderComponent.kt
index 940c79c78d76..577e47bb3b83 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/dagger/VolumeDialogSliderComponent.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/dagger/VolumeDialogSliderComponent.kt
@@ -18,7 +18,6 @@ package com.android.systemui.volume.dialog.sliders.dagger
import com.android.systemui.volume.dialog.sliders.domain.model.VolumeDialogSliderType
import com.android.systemui.volume.dialog.sliders.ui.VolumeDialogOverscrollViewBinder
-import com.android.systemui.volume.dialog.sliders.ui.VolumeDialogSliderHapticsViewBinder
import com.android.systemui.volume.dialog.sliders.ui.VolumeDialogSliderViewBinder
import dagger.BindsInstance
import dagger.Subcomponent
@@ -33,8 +32,6 @@ interface VolumeDialogSliderComponent {
fun sliderViewBinder(): VolumeDialogSliderViewBinder
- fun sliderHapticsViewBinder(): VolumeDialogSliderHapticsViewBinder
-
fun overscrollViewBinder(): VolumeDialogOverscrollViewBinder
@Subcomponent.Factory
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/data/repository/VolumeDialogSliderTouchEventsRepository.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/data/repository/VolumeDialogSliderTouchEventsRepository.kt
index 82885d65c513..07954f850286 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/data/repository/VolumeDialogSliderTouchEventsRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/data/repository/VolumeDialogSliderTouchEventsRepository.kt
@@ -16,8 +16,8 @@
package com.android.systemui.volume.dialog.sliders.data.repository
-import android.view.MotionEvent
import com.android.systemui.volume.dialog.sliders.dagger.VolumeDialogSliderScope
+import com.android.systemui.volume.dialog.sliders.shared.model.SliderInputEvent
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
@@ -26,10 +26,11 @@ import kotlinx.coroutines.flow.filterNotNull
@VolumeDialogSliderScope
class VolumeDialogSliderTouchEventsRepository @Inject constructor() {
- private val mutableSliderTouchEvents: MutableStateFlow<MotionEvent?> = MutableStateFlow(null)
- val sliderTouchEvent: Flow<MotionEvent> = mutableSliderTouchEvents.filterNotNull()
+ private val mutableSliderTouchEvents: MutableStateFlow<SliderInputEvent.Touch?> =
+ MutableStateFlow(null)
+ val sliderTouchEvent: Flow<SliderInputEvent.Touch> = mutableSliderTouchEvents.filterNotNull()
- fun update(event: MotionEvent) {
- mutableSliderTouchEvents.tryEmit(MotionEvent.obtain(event))
+ fun update(touch: SliderInputEvent.Touch) {
+ mutableSliderTouchEvents.value = touch
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/domain/interactor/VolumeDialogSliderInputEventsInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/domain/interactor/VolumeDialogSliderInputEventsInteractor.kt
index c7b4184a9f2f..351832bd275a 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/domain/interactor/VolumeDialogSliderInputEventsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/domain/interactor/VolumeDialogSliderInputEventsInteractor.kt
@@ -16,7 +16,6 @@
package com.android.systemui.volume.dialog.sliders.domain.interactor
-import android.view.MotionEvent
import com.android.systemui.volume.dialog.dagger.scope.VolumeDialog
import com.android.systemui.volume.dialog.domain.interactor.VolumeDialogCallbacksInteractor
import com.android.systemui.volume.dialog.domain.interactor.VolumeDialogVisibilityInteractor
@@ -45,7 +44,7 @@ constructor(
val event: Flow<SliderInputEvent> =
merge(
- repository.sliderTouchEvent.map { SliderInputEvent.Touch(it) },
+ repository.sliderTouchEvent,
volumeDialogCallbacksInteractor.event
.filterIsInstance(VolumeDialogEventModel.VolumeChangedFromKey::class)
.map { SliderInputEvent.Button },
@@ -55,7 +54,7 @@ constructor(
event.onEach { visibilityInteractor.resetDismissTimeout() }.launchIn(coroutineScope)
}
- fun onTouchEvent(newEvent: MotionEvent) {
- repository.update(newEvent)
+ fun onTouchEvent(pointerEvent: SliderInputEvent.Touch) {
+ repository.update(pointerEvent)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/shared/model/SliderInputEvent.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/shared/model/SliderInputEvent.kt
index 37dbb4b3a81d..841730857d71 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/shared/model/SliderInputEvent.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/shared/model/SliderInputEvent.kt
@@ -16,12 +16,20 @@
package com.android.systemui.volume.dialog.sliders.shared.model
-import android.view.MotionEvent
-
/** Models input event happened on the Volume Slider */
sealed interface SliderInputEvent {
- data class Touch(val event: MotionEvent) : SliderInputEvent
+ interface Touch : SliderInputEvent {
+
+ val x: Float
+ val y: Float
+
+ data class Start(override val x: Float, override val y: Float) : Touch
+
+ data class Move(override val x: Float, override val y: Float) : Touch
+
+ data class End(override val x: Float, override val y: Float) : Touch
+ }
data object Button : SliderInputEvent
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogOverscrollViewBinder.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogOverscrollViewBinder.kt
index 8109b50aa34a..38feb69aad7b 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogOverscrollViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogOverscrollViewBinder.kt
@@ -20,11 +20,9 @@ import android.view.View
import androidx.dynamicanimation.animation.FloatValueHolder
import androidx.dynamicanimation.animation.SpringAnimation
import androidx.dynamicanimation.animation.SpringForce
-import com.android.systemui.res.R
import com.android.systemui.volume.dialog.sliders.dagger.VolumeDialogSliderScope
import com.android.systemui.volume.dialog.sliders.ui.viewmodel.VolumeDialogOverscrollViewModel
import com.android.systemui.volume.dialog.sliders.ui.viewmodel.VolumeDialogOverscrollViewModel.OverscrollEventModel
-import com.google.android.material.slider.Slider
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.launchIn
@@ -51,10 +49,6 @@ constructor(private val viewModel: VolumeDialogOverscrollViewModel) {
)
.addUpdateListener { _, value, _ -> viewsToAnimate.setTranslationY(value) }
- view.requireViewById<Slider>(R.id.volume_dialog_slider).addOnChangeListener { s, value, _ ->
- viewModel.setSlider(value = value, min = s.valueFrom, max = s.valueTo)
- }
-
viewModel.overscrollEvent
.onEach { event ->
when (event) {
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderHapticsViewBinder.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderHapticsViewBinder.kt
deleted file mode 100644
index 5a7fbc6341f2..000000000000
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderHapticsViewBinder.kt
+++ /dev/null
@@ -1,82 +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.volume.dialog.sliders.ui
-
-import android.view.View
-import com.android.systemui.haptics.slider.HapticSlider
-import com.android.systemui.haptics.slider.HapticSliderPlugin
-import com.android.systemui.res.R
-import com.android.systemui.statusbar.VibratorHelper
-import com.android.systemui.util.time.SystemClock
-import com.android.systemui.volume.dialog.sliders.dagger.VolumeDialogSliderScope
-import com.android.systemui.volume.dialog.sliders.shared.model.SliderInputEvent
-import com.android.systemui.volume.dialog.sliders.ui.viewmodel.VolumeDialogSliderInputEventsViewModel
-import com.google.android.material.slider.Slider
-import com.google.android.msdl.domain.MSDLPlayer
-import javax.inject.Inject
-import kotlin.math.roundToInt
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.launchIn
-import kotlinx.coroutines.flow.onEach
-
-@VolumeDialogSliderScope
-class VolumeDialogSliderHapticsViewBinder
-@Inject
-constructor(
- private val inputEventsViewModel: VolumeDialogSliderInputEventsViewModel,
- private val vibratorHelper: VibratorHelper,
- private val msdlPlayer: MSDLPlayer,
- private val systemClock: SystemClock,
-) {
-
- fun CoroutineScope.bind(view: View) {
- val sliderView = view.requireViewById<Slider>(R.id.volume_dialog_slider)
- val hapticSliderPlugin =
- HapticSliderPlugin(
- slider = HapticSlider.Slider(sliderView),
- vibratorHelper = vibratorHelper,
- msdlPlayer = msdlPlayer,
- systemClock = systemClock,
- )
- hapticSliderPlugin.startInScope(this)
-
- sliderView.addOnChangeListener { _, value, fromUser ->
- hapticSliderPlugin.onProgressChanged(value.roundToInt(), fromUser)
- }
- sliderView.addOnSliderTouchListener(
- object : Slider.OnSliderTouchListener {
-
- override fun onStartTrackingTouch(slider: Slider) {
- hapticSliderPlugin.onStartTrackingTouch()
- }
-
- override fun onStopTrackingTouch(slider: Slider) {
- hapticSliderPlugin.onStopTrackingTouch()
- }
- }
- )
-
- inputEventsViewModel.event
- .onEach {
- when (it) {
- is SliderInputEvent.Button -> hapticSliderPlugin.onKeyDown()
- is SliderInputEvent.Touch -> hapticSliderPlugin.onTouchEvent(it.event)
- }
- }
- .launchIn(this)
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinder.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinder.kt
index d40302408dd6..21a392776235 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinder.kt
@@ -16,96 +16,211 @@
package com.android.systemui.volume.dialog.sliders.ui
-import android.annotation.SuppressLint
+import android.graphics.drawable.Drawable
import android.view.View
-import androidx.dynamicanimation.animation.FloatPropertyCompat
-import androidx.dynamicanimation.animation.SpringAnimation
-import androidx.dynamicanimation.animation.SpringForce
+import androidx.compose.animation.AnimatedVisibility
+import androidx.compose.animation.core.tween
+import androidx.compose.animation.fadeIn
+import androidx.compose.animation.fadeOut
+import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.foundation.layout.BoxScope
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
+import androidx.compose.material3.Icon
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.SliderDefaults
+import androidx.compose.material3.SliderState
+import androidx.compose.material3.VerticalSlider
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableFloatStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.runtime.snapshotFlow
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.input.pointer.pointerInput
+import androidx.compose.ui.platform.ComposeView
+import androidx.compose.ui.unit.dp
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
+import com.android.compose.ui.graphics.painter.DrawablePainter
+import com.android.systemui.haptics.slider.compose.ui.SliderHapticsViewModel
+import com.android.systemui.lifecycle.rememberViewModel
import com.android.systemui.res.R
import com.android.systemui.volume.dialog.sliders.dagger.VolumeDialogSliderScope
-import com.android.systemui.volume.dialog.sliders.ui.viewmodel.VolumeDialogSliderInputEventsViewModel
-import com.android.systemui.volume.dialog.sliders.ui.viewmodel.VolumeDialogSliderStateModel
+import com.android.systemui.volume.dialog.sliders.ui.compose.VolumeDialogSliderTrack
+import com.android.systemui.volume.dialog.sliders.ui.viewmodel.VolumeDialogOverscrollViewModel
import com.android.systemui.volume.dialog.sliders.ui.viewmodel.VolumeDialogSliderViewModel
-import com.google.android.material.slider.Slider
-import com.google.android.material.slider.Slider.OnSliderTouchListener
+import com.android.systemui.volume.haptics.ui.VolumeHapticsConfigsProvider
import javax.inject.Inject
+import kotlin.math.round
import kotlin.math.roundToInt
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.launchIn
-import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.currentCoroutineContext
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.isActive
@VolumeDialogSliderScope
class VolumeDialogSliderViewBinder
@Inject
constructor(
private val viewModel: VolumeDialogSliderViewModel,
- private val inputViewModel: VolumeDialogSliderInputEventsViewModel,
+ private val overscrollViewModel: VolumeDialogOverscrollViewModel,
+ private val hapticsViewModelFactory: SliderHapticsViewModel.Factory,
) {
+ fun bind(view: View) {
+ val sliderComposeView: ComposeView = view.requireViewById(R.id.volume_dialog_slider)
+ sliderComposeView.setContent {
+ VolumeDialogSlider(
+ viewModel = viewModel,
+ overscrollViewModel = overscrollViewModel,
+ hapticsViewModelFactory =
+ if (com.android.systemui.Flags.hapticsForComposeSliders()) {
+ hapticsViewModelFactory
+ } else {
+ null
+ },
+ )
+ }
+ }
+}
- private val sliderValueProperty =
- object : FloatPropertyCompat<Slider>("value") {
- override fun getValue(slider: Slider): Float = slider.value
+@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class)
+@Composable
+private fun VolumeDialogSlider(
+ viewModel: VolumeDialogSliderViewModel,
+ overscrollViewModel: VolumeDialogOverscrollViewModel,
+ hapticsViewModelFactory: SliderHapticsViewModel.Factory?,
+ modifier: Modifier = Modifier,
+) {
+
+ val colors =
+ SliderDefaults.colors(
+ thumbColor = MaterialTheme.colorScheme.primary,
+ activeTickColor = MaterialTheme.colorScheme.surfaceContainerHighest,
+ inactiveTickColor = MaterialTheme.colorScheme.primary,
+ activeTrackColor = MaterialTheme.colorScheme.primary,
+ inactiveTrackColor = MaterialTheme.colorScheme.surfaceContainerHighest,
+ )
+ val collectedSliderState by viewModel.state.collectAsStateWithLifecycle(null)
+ val sliderState = collectedSliderState ?: return
- override fun setValue(slider: Slider, value: Float) {
- slider.value = value
+ val interactionSource = remember { MutableInteractionSource() }
+ val hapticsViewModel: SliderHapticsViewModel? =
+ hapticsViewModelFactory?.let {
+ rememberViewModel(traceName = "SliderHapticsViewModel") {
+ it.create(
+ interactionSource,
+ sliderState.valueRange,
+ Orientation.Vertical,
+ VolumeHapticsConfigsProvider.sliderHapticFeedbackConfig(sliderState.valueRange),
+ VolumeHapticsConfigsProvider.seekableSliderTrackerConfig,
+ )
}
}
- private val springForce =
- SpringForce().apply {
- stiffness = SpringForce.STIFFNESS_MEDIUM
- dampingRatio = SpringForce.DAMPING_RATIO_NO_BOUNCY
- }
- @SuppressLint("ClickableViewAccessibility")
- fun CoroutineScope.bind(view: View) {
- var isInitialUpdate = true
- val sliderView: Slider = view.requireViewById(R.id.volume_dialog_slider)
- val animation = SpringAnimation(sliderView, sliderValueProperty)
- animation.spring = springForce
- sliderView.setOnTouchListener { _, event ->
- inputViewModel.onTouchEvent(event)
- false
- }
- sliderView.addOnChangeListener { _, value, fromUser ->
- viewModel.setStreamVolume(value.roundToInt(), fromUser)
+ val state =
+ remember(sliderState.valueRange) {
+ SliderState(
+ value = sliderState.value,
+ valueRange = sliderState.valueRange,
+ steps =
+ (sliderState.valueRange.endInclusive - sliderState.valueRange.start - 1)
+ .toInt(),
+ )
+ .apply {
+ onValueChangeFinished = {
+ viewModel.onStreamChangeFinished(value.roundToInt())
+ hapticsViewModel?.onValueChangeEnded()
+ }
+ setOnValueChangeListener {
+ value = it
+ hapticsViewModel?.addVelocityDataPoint(it)
+ overscrollViewModel.setSlider(
+ value = value,
+ min = valueRange.start,
+ max = valueRange.endInclusive,
+ )
+ viewModel.setStreamVolume(it, true)
+ }
+ }
}
- sliderView.addOnSliderTouchListener(
- object : OnSliderTouchListener {
- override fun onStartTrackingTouch(slider: Slider) {}
+ var lastDiscreteStep by remember { mutableFloatStateOf(round(sliderState.value)) }
+ LaunchedEffect(sliderState.value) {
+ state.value = sliderState.value
+ snapshotFlow { sliderState.value }
+ .map { round(it) }
+ .filter { it != lastDiscreteStep }
+ .distinctUntilChanged()
+ .collect { discreteStep ->
+ lastDiscreteStep = discreteStep
+ hapticsViewModel?.onValueChange(discreteStep)
+ }
+ }
- override fun onStopTrackingTouch(slider: Slider) {
- viewModel.onStreamChangeFinished(slider.value.roundToInt())
+ VerticalSlider(
+ state = state,
+ enabled = !sliderState.isDisabled,
+ reverseDirection = true,
+ colors = colors,
+ interactionSource = interactionSource,
+ modifier =
+ modifier.pointerInput(Unit) {
+ coroutineScope {
+ val currentContext = currentCoroutineContext()
+ awaitPointerEventScope {
+ while (currentContext.isActive) {
+ viewModel.onTouchEvent(awaitPointerEvent())
+ }
+ }
}
- }
- )
+ },
+ track = {
+ VolumeDialogSliderTrack(
+ state,
+ colors = colors,
+ isEnabled = !sliderState.isDisabled,
+ activeTrackEndIcon = { iconsState ->
+ VolumeIcon(sliderState.icon, iconsState.isActiveTrackEndIconVisible)
+ },
+ inactiveTrackEndIcon = { iconsState ->
+ VolumeIcon(sliderState.icon, !iconsState.isActiveTrackEndIconVisible)
+ },
+ )
+ },
+ )
+}
- viewModel.isDisabledByZenMode.onEach { sliderView.isEnabled = !it }.launchIn(this)
- viewModel.state
- .onEach {
- sliderView.setModel(it, animation, isInitialUpdate)
- isInitialUpdate = false
- }
- .launchIn(this)
+@Composable
+private fun BoxScope.VolumeIcon(
+ drawable: Drawable,
+ isVisible: Boolean,
+ modifier: Modifier = Modifier,
+) {
+ AnimatedVisibility(
+ visible = isVisible,
+ enter = fadeIn(animationSpec = tween(durationMillis = 50)),
+ exit = fadeOut(animationSpec = tween(durationMillis = 50)),
+ modifier = modifier.align(Alignment.Center).size(40.dp).padding(10.dp),
+ ) {
+ Icon(painter = DrawablePainter(drawable), contentDescription = null)
}
+}
- @SuppressLint("UseCompatLoadingForDrawables")
- private fun Slider.setModel(
- model: VolumeDialogSliderStateModel,
- animation: SpringAnimation,
- isInitialUpdate: Boolean,
- ) {
- valueFrom = model.minValue
- animation.setMinValue(model.minValue)
- valueTo = model.maxValue
- animation.setMaxValue(model.maxValue)
- // coerce the current value to the new value range before animating it. This prevents
- // animating from the value that is outside of current [valueFrom, valueTo].
- value = value.coerceIn(valueFrom, valueTo)
- trackIconActiveStart = model.icon
- if (isInitialUpdate) {
- value = model.value
- } else {
- animation.animateToFinalPosition(model.value)
- }
+@OptIn(ExperimentalMaterial3Api::class)
+fun SliderState.setOnValueChangeListener(onValueChange: ((Float) -> Unit)?) {
+ with(javaClass.getDeclaredField("onValueChange")) {
+ val oldIsAccessible = isAccessible
+ AutoCloseable { isAccessible = oldIsAccessible }
+ .use {
+ isAccessible = true
+ set(this@setOnValueChangeListener, onValueChange)
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSlidersViewBinder.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSlidersViewBinder.kt
index 75d427acc05b..c66955a0c187 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSlidersViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSlidersViewBinder.kt
@@ -71,7 +71,6 @@ constructor(private val viewModel: VolumeDialogSlidersViewModel) {
viewsToAnimate: Array<View>,
) {
with(component.sliderViewBinder()) { bind(sliderContainer) }
- with(component.sliderHapticsViewBinder()) { bind(sliderContainer) }
with(component.overscrollViewBinder()) { bind(sliderContainer, viewsToAnimate) }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/compose/VolumeDialogSliderTrack.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/compose/VolumeDialogSliderTrack.kt
new file mode 100644
index 000000000000..1dd9ddac79be
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/compose/VolumeDialogSliderTrack.kt
@@ -0,0 +1,347 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.dialog.sliders.ui.compose
+
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.BoxScope
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.width
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
+import androidx.compose.material3.LocalContentColor
+import androidx.compose.material3.SliderColors
+import androidx.compose.material3.SliderDefaults
+import androidx.compose.material3.SliderState
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.runtime.MutableState
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.layout.Layout
+import androidx.compose.ui.layout.Measurable
+import androidx.compose.ui.layout.MeasurePolicy
+import androidx.compose.ui.layout.MeasureResult
+import androidx.compose.ui.layout.MeasureScope
+import androidx.compose.ui.layout.Placeable
+import androidx.compose.ui.layout.layoutId
+import androidx.compose.ui.unit.Constraints
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.util.fastFilter
+import androidx.compose.ui.util.fastFirst
+import kotlin.math.min
+
+@Composable
+@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class)
+fun VolumeDialogSliderTrack(
+ sliderState: SliderState,
+ colors: SliderColors,
+ isEnabled: Boolean,
+ modifier: Modifier = Modifier,
+ thumbTrackGapSize: Dp = 6.dp,
+ trackCornerSize: Dp = 12.dp,
+ trackInsideCornerSize: Dp = 2.dp,
+ trackSize: Dp = 40.dp,
+ activeTrackStartIcon: (@Composable BoxScope.(iconsState: SliderIconsState) -> Unit)? = null,
+ activeTrackEndIcon: (@Composable BoxScope.(iconsState: SliderIconsState) -> Unit)? = null,
+ inactiveTrackStartIcon: (@Composable BoxScope.(iconsState: SliderIconsState) -> Unit)? = null,
+ inactiveTrackEndIcon: (@Composable BoxScope.(iconsState: SliderIconsState) -> Unit)? = null,
+) {
+ val measurePolicy = remember(sliderState) { TrackMeasurePolicy(sliderState) }
+ Layout(
+ measurePolicy = measurePolicy,
+ content = {
+ SliderDefaults.Track(
+ sliderState = sliderState,
+ colors = colors,
+ enabled = isEnabled,
+ trackCornerSize = trackCornerSize,
+ trackInsideCornerSize = trackInsideCornerSize,
+ drawStopIndicator = null,
+ thumbTrackGapSize = thumbTrackGapSize,
+ drawTick = { _, _ -> },
+ modifier = Modifier.width(trackSize).layoutId(Contents.Track),
+ )
+
+ TrackIcon(
+ icon = activeTrackStartIcon,
+ contentsId = Contents.Active.TrackStartIcon,
+ isEnabled = isEnabled,
+ colors = colors,
+ state = measurePolicy,
+ )
+ TrackIcon(
+ icon = activeTrackEndIcon,
+ contentsId = Contents.Active.TrackEndIcon,
+ isEnabled = isEnabled,
+ colors = colors,
+ state = measurePolicy,
+ )
+ TrackIcon(
+ icon = inactiveTrackStartIcon,
+ contentsId = Contents.Inactive.TrackStartIcon,
+ isEnabled = isEnabled,
+ colors = colors,
+ state = measurePolicy,
+ )
+ TrackIcon(
+ icon = inactiveTrackEndIcon,
+ contentsId = Contents.Inactive.TrackEndIcon,
+ isEnabled = isEnabled,
+ colors = colors,
+ state = measurePolicy,
+ )
+ },
+ modifier = modifier,
+ )
+}
+
+@Composable
+private fun TrackIcon(
+ icon: (@Composable BoxScope.(sliderIconsState: SliderIconsState) -> Unit)?,
+ isEnabled: Boolean,
+ contentsId: Contents,
+ state: SliderIconsState,
+ colors: SliderColors,
+ modifier: Modifier = Modifier,
+) {
+ icon ?: return
+ Box(modifier = modifier.layoutId(contentsId).fillMaxSize()) {
+ CompositionLocalProvider(
+ LocalContentColor provides contentsId.getColor(colors, isEnabled)
+ ) {
+ icon(state)
+ }
+ }
+}
+
+@OptIn(ExperimentalMaterial3Api::class)
+private class TrackMeasurePolicy(private val sliderState: SliderState) :
+ MeasurePolicy, SliderIconsState {
+
+ private val isVisible: Map<Contents, MutableState<Boolean>> =
+ mutableMapOf(
+ Contents.Active.TrackStartIcon to mutableStateOf(false),
+ Contents.Active.TrackEndIcon to mutableStateOf(false),
+ Contents.Inactive.TrackStartIcon to mutableStateOf(false),
+ Contents.Inactive.TrackEndIcon to mutableStateOf(false),
+ )
+
+ override val isActiveTrackStartIconVisible: Boolean
+ get() = isVisible.getValue(Contents.Active.TrackStartIcon).value
+
+ override val isActiveTrackEndIconVisible: Boolean
+ get() = isVisible.getValue(Contents.Active.TrackEndIcon).value
+
+ override val isInactiveTrackStartIconVisible: Boolean
+ get() = isVisible.getValue(Contents.Inactive.TrackStartIcon).value
+
+ override val isInactiveTrackEndIconVisible: Boolean
+ get() = isVisible.getValue(Contents.Inactive.TrackEndIcon).value
+
+ override fun MeasureScope.measure(
+ measurables: List<Measurable>,
+ constraints: Constraints,
+ ): MeasureResult {
+ val track = measurables.fastFirst { it.layoutId == Contents.Track }.measure(constraints)
+
+ val iconSize = min(track.width, track.height)
+ val iconConstraints = constraints.copy(maxWidth = iconSize, maxHeight = iconSize)
+
+ val icons =
+ measurables
+ .fastFilter { it.layoutId != Contents.Track }
+ .associateBy(
+ keySelector = { it.layoutId as Contents },
+ valueTransform = { it.measure(iconConstraints) },
+ )
+
+ return layout(track.width, track.height) {
+ with(Contents.Track) {
+ performPlacing(
+ placeable = track,
+ width = track.width,
+ height = track.height,
+ sliderState = sliderState,
+ )
+ }
+
+ for (iconLayoutId in icons.keys) {
+ with(iconLayoutId) {
+ performPlacing(
+ placeable = icons.getValue(iconLayoutId),
+ width = track.width,
+ height = track.height,
+ sliderState = sliderState,
+ )
+
+ isVisible.getValue(iconLayoutId).value =
+ isVisible(
+ placeable = icons.getValue(iconLayoutId),
+ width = track.width,
+ height = track.height,
+ sliderState = sliderState,
+ )
+ }
+ }
+ }
+ }
+}
+
+@OptIn(ExperimentalMaterial3Api::class)
+private sealed interface Contents {
+
+ data object Track : Contents {
+ override fun Placeable.PlacementScope.performPlacing(
+ placeable: Placeable,
+ width: Int,
+ height: Int,
+ sliderState: SliderState,
+ ) = placeable.place(x = 0, y = 0)
+
+ override fun isVisible(
+ placeable: Placeable,
+ width: Int,
+ height: Int,
+ sliderState: SliderState,
+ ) = true
+
+ override fun getColor(sliderColors: SliderColors, isEnabled: Boolean): Color =
+ error("Unsupported")
+ }
+
+ interface Active : Contents {
+ override fun getColor(sliderColors: SliderColors, isEnabled: Boolean): Color {
+ return if (isEnabled) {
+ sliderColors.activeTickColor
+ } else {
+ sliderColors.disabledActiveTickColor
+ }
+ }
+
+ data object TrackStartIcon : Active {
+ override fun Placeable.PlacementScope.performPlacing(
+ placeable: Placeable,
+ width: Int,
+ height: Int,
+ sliderState: SliderState,
+ ) =
+ placeable.place(
+ x = 0,
+ y = (height * (1 - sliderState.coercedValueAsFraction)).toInt(),
+ )
+
+ override fun isVisible(
+ placeable: Placeable,
+ width: Int,
+ height: Int,
+ sliderState: SliderState,
+ ): Boolean = (height * (sliderState.coercedValueAsFraction)).toInt() > placeable.height
+ }
+
+ data object TrackEndIcon : Active {
+ override fun Placeable.PlacementScope.performPlacing(
+ placeable: Placeable,
+ width: Int,
+ height: Int,
+ sliderState: SliderState,
+ ) = placeable.place(x = 0, y = (height - placeable.height))
+
+ override fun isVisible(
+ placeable: Placeable,
+ width: Int,
+ height: Int,
+ sliderState: SliderState,
+ ): Boolean = (height * (sliderState.coercedValueAsFraction)).toInt() > placeable.height
+ }
+ }
+
+ interface Inactive : Contents {
+
+ override fun getColor(sliderColors: SliderColors, isEnabled: Boolean): Color {
+ return if (isEnabled) {
+ sliderColors.inactiveTickColor
+ } else {
+ sliderColors.disabledInactiveTickColor
+ }
+ }
+
+ data object TrackStartIcon : Inactive {
+ override fun Placeable.PlacementScope.performPlacing(
+ placeable: Placeable,
+ width: Int,
+ height: Int,
+ sliderState: SliderState,
+ ) {
+ placeable.place(x = 0, y = 0)
+ }
+
+ override fun isVisible(
+ placeable: Placeable,
+ width: Int,
+ height: Int,
+ sliderState: SliderState,
+ ): Boolean =
+ (height * (1 - sliderState.coercedValueAsFraction)).toInt() > placeable.height
+ }
+
+ data object TrackEndIcon : Inactive {
+ override fun Placeable.PlacementScope.performPlacing(
+ placeable: Placeable,
+ width: Int,
+ height: Int,
+ sliderState: SliderState,
+ ) {
+ placeable.place(
+ x = 0,
+ y =
+ (height * (1 - sliderState.coercedValueAsFraction)).toInt() -
+ placeable.height,
+ )
+ }
+
+ override fun isVisible(
+ placeable: Placeable,
+ width: Int,
+ height: Int,
+ sliderState: SliderState,
+ ): Boolean =
+ (height * (1 - sliderState.coercedValueAsFraction)).toInt() > placeable.height
+ }
+ }
+
+ fun Placeable.PlacementScope.performPlacing(
+ placeable: Placeable,
+ width: Int,
+ height: Int,
+ sliderState: SliderState,
+ )
+
+ fun isVisible(placeable: Placeable, width: Int, height: Int, sliderState: SliderState): Boolean
+
+ fun getColor(sliderColors: SliderColors, isEnabled: Boolean): Color
+}
+
+/** Provides visibility state for each of the Slider's icons. */
+interface SliderIconsState {
+ val isActiveTrackStartIconVisible: Boolean
+ val isActiveTrackEndIconVisible: Boolean
+ val isInactiveTrackStartIconVisible: Boolean
+ val isInactiveTrackEndIconVisible: Boolean
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogOverscrollViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogOverscrollViewModel.kt
index 0d41860d9f57..0fdf5d6266d0 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogOverscrollViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogOverscrollViewModel.kt
@@ -95,18 +95,17 @@ constructor(
private fun overscrollEvents(direction: Float): Flow<OverscrollEventModel> {
var startPosition: Float? = null
return inputEventsInteractor.event
- .mapNotNull { (it as? SliderInputEvent.Touch)?.event }
+ .mapNotNull { it as? SliderInputEvent.Touch }
.transform { touchEvent ->
// Skip events from inside the slider bounds for the case when the user adjusts
- // slider
- // towards max when the slider is already on max value.
- if (touchEvent.isFinalEvent()) {
+ // slider towards max when the slider is already on max value.
+ if (touchEvent is SliderInputEvent.Touch.End) {
startPosition = null
emit(OverscrollEventModel.Animate(0f))
return@transform
}
val currentStartPosition = startPosition
- val newPosition: Float = touchEvent.rawY
+ val newPosition: Float = touchEvent.y
if (currentStartPosition == null) {
startPosition = newPosition
} else {
@@ -126,11 +125,6 @@ constructor(
}
}
- /** @return true when the [MotionEvent] indicates the end of the gesture. */
- private fun MotionEvent.isFinalEvent(): Boolean {
- return actionMasked == MotionEvent.ACTION_UP || actionMasked == MotionEvent.ACTION_CANCEL
- }
-
/** Models overscroll event */
sealed interface OverscrollEventModel {
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderInputEventsViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderInputEventsViewModel.kt
deleted file mode 100644
index 755776ac9723..000000000000
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderInputEventsViewModel.kt
+++ /dev/null
@@ -1,43 +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.volume.dialog.sliders.ui.viewmodel
-
-import android.view.MotionEvent
-import com.android.systemui.volume.dialog.dagger.scope.VolumeDialog
-import com.android.systemui.volume.dialog.sliders.dagger.VolumeDialogSliderScope
-import com.android.systemui.volume.dialog.sliders.domain.interactor.VolumeDialogSliderInputEventsInteractor
-import javax.inject.Inject
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.SharingStarted
-import kotlinx.coroutines.flow.filterNotNull
-import kotlinx.coroutines.flow.stateIn
-
-@VolumeDialogSliderScope
-class VolumeDialogSliderInputEventsViewModel
-@Inject
-constructor(
- @VolumeDialog private val coroutineScope: CoroutineScope,
- private val interactor: VolumeDialogSliderInputEventsInteractor,
-) {
-
- val event =
- interactor.event.stateIn(coroutineScope, SharingStarted.Eagerly, null).filterNotNull()
-
- fun onTouchEvent(event: MotionEvent) {
- interactor.onTouchEvent(event)
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderStateModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderStateModel.kt
index 8df9e788905c..b01046b377b0 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderStateModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderStateModel.kt
@@ -20,17 +20,20 @@ import android.graphics.drawable.Drawable
import com.android.systemui.volume.dialog.shared.model.VolumeDialogStreamModel
data class VolumeDialogSliderStateModel(
- val minValue: Float,
- val maxValue: Float,
val value: Float,
+ val isDisabled: Boolean,
+ val valueRange: ClosedFloatingPointRange<Float>,
val icon: Drawable,
)
-fun VolumeDialogStreamModel.toStateModel(icon: Drawable): VolumeDialogSliderStateModel {
+fun VolumeDialogStreamModel.toStateModel(
+ isDisabled: Boolean,
+ icon: Drawable,
+): VolumeDialogSliderStateModel {
return VolumeDialogSliderStateModel(
- minValue = levelMin.toFloat(),
value = level.toFloat(),
- maxValue = levelMax.toFloat(),
+ isDisabled = isDisabled,
+ valueRange = levelMin.toFloat()..levelMax.toFloat(),
icon = icon,
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderViewModel.kt
index a752f1f78e74..e89d5ab53560 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderViewModel.kt
@@ -16,6 +16,9 @@
package com.android.systemui.volume.dialog.sliders.ui.viewmodel
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.input.pointer.PointerEvent
+import androidx.compose.ui.input.pointer.PointerEventType
import com.android.systemui.util.time.SystemClock
import com.android.systemui.volume.Events
import com.android.systemui.volume.dialog.dagger.scope.VolumeDialog
@@ -23,20 +26,23 @@ import com.android.systemui.volume.dialog.domain.interactor.VolumeDialogVisibili
import com.android.systemui.volume.dialog.shared.VolumeDialogLogger
import com.android.systemui.volume.dialog.shared.model.VolumeDialogStreamModel
import com.android.systemui.volume.dialog.sliders.dagger.VolumeDialogSliderScope
+import com.android.systemui.volume.dialog.sliders.domain.interactor.VolumeDialogSliderInputEventsInteractor
import com.android.systemui.volume.dialog.sliders.domain.interactor.VolumeDialogSliderInteractor
import com.android.systemui.volume.dialog.sliders.domain.model.VolumeDialogSliderType
+import com.android.systemui.volume.dialog.sliders.shared.model.SliderInputEvent
import javax.inject.Inject
+import kotlin.math.roundToInt
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.launchIn
-import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.flow.stateIn
@@ -62,6 +68,7 @@ constructor(
private val visibilityInteractor: VolumeDialogVisibilityInteractor,
@VolumeDialog private val coroutineScope: CoroutineScope,
private val volumeDialogSliderIconProvider: VolumeDialogSliderIconProvider,
+ private val inputEventsInteractor: VolumeDialogSliderInputEventsInteractor,
private val systemClock: SystemClock,
private val logger: VolumeDialogLogger,
) {
@@ -77,11 +84,12 @@ constructor(
.stateIn(coroutineScope, SharingStarted.Eagerly, null)
.filterNotNull()
- val isDisabledByZenMode: Flow<Boolean> = interactor.isDisabledByZenMode
val state: Flow<VolumeDialogSliderStateModel> =
- model
- .flatMapLatest { streamModel ->
- with(streamModel) {
+ combine(
+ interactor.isDisabledByZenMode,
+ model,
+ model.flatMapLatest { streamModel ->
+ with(streamModel) {
val isMuted = muteSupported && muted
when (sliderType) {
is VolumeDialogSliderType.Stream ->
@@ -101,7 +109,9 @@ constructor(
}
}
}
- .map { icon -> streamModel.toStateModel(icon) }
+ },
+ ) { isDisabledByZenMode, model, icon ->
+ model.toStateModel(icon = icon, isDisabled = isDisabledByZenMode)
}
.stateIn(coroutineScope, SharingStarted.Eagerly, null)
.filterNotNull()
@@ -116,11 +126,14 @@ constructor(
.launchIn(coroutineScope)
}
- fun setStreamVolume(volume: Int, fromUser: Boolean) {
+ fun setStreamVolume(volume: Float, fromUser: Boolean) {
if (fromUser) {
visibilityInteractor.resetDismissTimeout()
userVolumeUpdates.value =
- VolumeUpdate(newVolumeLevel = volume, timestampMillis = getTimestampMillis())
+ VolumeUpdate(
+ newVolumeLevel = volume.roundToInt(),
+ timestampMillis = getTimestampMillis(),
+ )
}
}
@@ -128,6 +141,28 @@ constructor(
logger.onVolumeSliderAdjustmentFinished(volume = volume, stream = sliderType.audioStream)
}
+ fun onTouchEvent(pointerEvent: PointerEvent) {
+ val position: Offset = pointerEvent.changes.first().position
+ when (pointerEvent.type) {
+ PointerEventType.Press ->
+ inputEventsInteractor.onTouchEvent(
+ SliderInputEvent.Touch.Start(position.x, position.y)
+ )
+ PointerEventType.Move ->
+ inputEventsInteractor.onTouchEvent(
+ SliderInputEvent.Touch.Move(position.x, position.y)
+ )
+ PointerEventType.Scroll ->
+ inputEventsInteractor.onTouchEvent(
+ SliderInputEvent.Touch.Move(position.x, position.y)
+ )
+ PointerEventType.Release ->
+ inputEventsInteractor.onTouchEvent(
+ SliderInputEvent.Touch.End(position.x, position.y)
+ )
+ }
+ }
+
private fun getTimestampMillis(): Long = systemClock.uptimeMillis()
private data class VolumeUpdate(val newVolumeLevel: Int, val timestampMillis: Long)
diff --git a/packages/SystemUI/src/com/android/systemui/volume/haptics/ui/VolumeHapticsConfigsProvider.kt b/packages/SystemUI/src/com/android/systemui/volume/haptics/ui/VolumeHapticsConfigsProvider.kt
new file mode 100644
index 000000000000..92e9bf2d1ffc
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/haptics/ui/VolumeHapticsConfigsProvider.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.haptics.ui
+
+import com.android.systemui.haptics.slider.SeekableSliderTrackerConfig
+import com.android.systemui.haptics.slider.SliderHapticFeedbackConfig
+
+object VolumeHapticsConfigsProvider {
+
+ fun sliderHapticFeedbackConfig(
+ valueRange: ClosedFloatingPointRange<Float>
+ ): SliderHapticFeedbackConfig {
+ val sliderStepSize = 1f / (valueRange.endInclusive - valueRange.start)
+ return SliderHapticFeedbackConfig(
+ lowerBookendScale = 0.2f,
+ progressBasedDragMinScale = 0.2f,
+ progressBasedDragMaxScale = 0.5f,
+ deltaProgressForDragThreshold = 0f,
+ additionalVelocityMaxBump = 0.2f,
+ maxVelocityToScale = 0.1f, /* slider progress(from 0 to 1) per sec */
+ sliderStepSize = sliderStepSize,
+ )
+ }
+
+ val seekableSliderTrackerConfig =
+ SeekableSliderTrackerConfig(lowerBookendThreshold = 0f, upperBookendThreshold = 1f)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/wallpapers/GradientColorWallpaper.kt b/packages/SystemUI/src/com/android/systemui/wallpapers/GradientColorWallpaper.kt
index c1fb0e80cafc..760e94c72f19 100644
--- a/packages/SystemUI/src/com/android/systemui/wallpapers/GradientColorWallpaper.kt
+++ b/packages/SystemUI/src/com/android/systemui/wallpapers/GradientColorWallpaper.kt
@@ -16,6 +16,7 @@
package com.android.systemui.wallpapers
+import android.app.Flags
import android.graphics.Canvas
import android.graphics.Paint
import android.service.wallpaper.WallpaperService
@@ -26,7 +27,15 @@ import androidx.core.graphics.toRectF
/** A wallpaper that shows a static gradient color image wallpaper. */
class GradientColorWallpaper : WallpaperService() {
- override fun onCreateEngine(): Engine = GradientColorWallpaperEngine()
+ override fun onCreateEngine(): Engine =
+ if (Flags.enableConnectedDisplaysWallpaper()) {
+ GradientColorWallpaperEngine()
+ } else {
+ EmptyWallpaperEngine()
+ }
+
+ /** Empty engine used when the feature flag is disabled. */
+ inner class EmptyWallpaperEngine : Engine()
inner class GradientColorWallpaperEngine : Engine() {
init {
diff --git a/packages/SystemUI/src/com/android/systemui/wallpapers/data/repository/NoopWallpaperRepository.kt b/packages/SystemUI/src/com/android/systemui/wallpapers/data/repository/NoopWallpaperRepository.kt
index 8487ee751948..ec74f4f47bc9 100644
--- a/packages/SystemUI/src/com/android/systemui/wallpapers/data/repository/NoopWallpaperRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/wallpapers/data/repository/NoopWallpaperRepository.kt
@@ -36,4 +36,5 @@ class NoopWallpaperRepository @Inject constructor() : WallpaperRepository {
override val wallpaperInfo: StateFlow<WallpaperInfo?> = MutableStateFlow(null).asStateFlow()
override val wallpaperSupportsAmbientMode = flowOf(false)
override var rootView: View? = null
+ override val shouldSendFocalArea: StateFlow<Boolean> = MutableStateFlow(false).asStateFlow()
}
diff --git a/packages/SystemUI/src/com/android/systemui/wallpapers/data/repository/WallpaperRepository.kt b/packages/SystemUI/src/com/android/systemui/wallpapers/data/repository/WallpaperRepository.kt
index ed43f8323c31..9794c619041e 100644
--- a/packages/SystemUI/src/com/android/systemui/wallpapers/data/repository/WallpaperRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/wallpapers/data/repository/WallpaperRepository.kt
@@ -31,7 +31,6 @@ import com.android.systemui.Flags
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
-import com.android.systemui.keyguard.data.repository.KeyguardClockRepository
import com.android.systemui.keyguard.data.repository.KeyguardRepository
import com.android.systemui.shared.Flags.ambientAod
import com.android.systemui.user.data.model.SelectedUserModel
@@ -45,6 +44,7 @@ import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.SharingStarted.Companion.WhileSubscribed
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.combine
@@ -65,6 +65,9 @@ interface WallpaperRepository {
/** Set rootView to get its windowToken afterwards */
var rootView: View?
+
+ /** when we use magic portrait wallpapers, we should always get its bounds from keyguard */
+ val shouldSendFocalArea: StateFlow<Boolean>
}
@SysUISingleton
@@ -76,7 +79,6 @@ constructor(
broadcastDispatcher: BroadcastDispatcher,
userRepository: UserRepository,
keyguardRepository: KeyguardRepository,
- keyguardClockRepository: KeyguardClockRepository,
private val wallpaperManager: WallpaperManager,
context: Context,
) : WallpaperRepository {
@@ -97,27 +99,7 @@ constructor(
// Only update the wallpaper status once the user selection has finished.
.filter { it.selectionStatus == SelectionStatus.SELECTION_COMPLETE }
- /** The bottom of notification stack respect to the top of screen. */
- private val notificationStackAbsoluteBottom: StateFlow<Float> =
- keyguardRepository.notificationStackAbsoluteBottom
-
- /** The top of shortcut respect to the top of screen. */
- private val shortcutAbsoluteTop: StateFlow<Float> = keyguardRepository.shortcutAbsoluteTop
-
- /**
- * The top of notification stack to give a default state of lockscreen remaining space for
- * states with notifications to compare with. It's the bottom of smartspace date and weather
- * smartspace in small clock state, plus proper bottom margin.
- */
- private val notificationStackDefaultTop = keyguardClockRepository.notificationDefaultTop
@VisibleForTesting var sendLockscreenLayoutJob: Job? = null
- private val lockscreenRemainingSpaceWithNotification: Flow<Triple<Float, Float, Float>> =
- combine(
- notificationStackAbsoluteBottom,
- notificationStackDefaultTop,
- shortcutAbsoluteTop,
- ::Triple,
- )
override val wallpaperInfo: StateFlow<WallpaperInfo?> =
if (!wallpaperManager.isWallpaperSupported) {
@@ -140,15 +122,16 @@ constructor(
override var rootView: View? = null
- val shouldSendNotificationLayout =
+ override val shouldSendFocalArea =
wallpaperInfo
.map {
- val shouldSendNotificationLayout = shouldSendNotificationLayout(it)
+ val shouldSendNotificationLayout =
+ it?.component?.className == MAGIC_PORTRAIT_CLASSNAME
if (shouldSendNotificationLayout) {
sendLockscreenLayoutJob =
scope.launch {
- lockscreenRemainingSpaceWithNotification.collect {
- (notificationBottom, notificationDefaultTop, shortcutTop) ->
+ keyguardRepository.wallpaperFocalAreaBounds.collect {
+ wallpaperFocalAreaBounds ->
wallpaperManager.sendWallpaperCommand(
/* windowToken = */ rootView?.windowToken,
/* action = */ WallpaperManager
@@ -157,14 +140,22 @@ constructor(
/* y = */ 0,
/* z = */ 0,
/* extras = */ Bundle().apply {
- putFloat("screenLeft", 0F)
- putFloat("smartspaceBottom", notificationDefaultTop)
- putFloat("notificationBottom", notificationBottom)
putFloat(
- "screenRight",
- context.resources.displayMetrics.widthPixels.toFloat(),
+ "wallpaperFocalAreaLeft",
+ wallpaperFocalAreaBounds.left,
+ )
+ putFloat(
+ "wallpaperFocalAreaRight",
+ wallpaperFocalAreaBounds.right,
+ )
+ putFloat(
+ "wallpaperFocalAreaTop",
+ wallpaperFocalAreaBounds.top,
+ )
+ putFloat(
+ "wallpaperFocalAreaBottom",
+ wallpaperFocalAreaBounds.bottom,
)
- putFloat("shortCutTop", shortcutTop)
},
)
}
@@ -176,10 +167,9 @@ constructor(
}
.stateIn(
scope,
- // Always be listening for wallpaper changes.
- if (Flags.magicPortraitWallpapers()) SharingStarted.Eagerly
- else SharingStarted.Lazily,
- initialValue = false,
+ // Always be listening for wallpaper changes when magic portrait flag is on
+ if (Flags.magicPortraitWallpapers()) SharingStarted.Eagerly else WhileSubscribed(),
+ initialValue = Flags.magicPortraitWallpapers(),
)
private suspend fun getWallpaper(selectedUser: SelectedUserModel): WallpaperInfo? {
@@ -188,14 +178,6 @@ constructor(
}
}
- private fun shouldSendNotificationLayout(wallpaperInfo: WallpaperInfo?): Boolean {
- return if (wallpaperInfo != null && wallpaperInfo.component != null) {
- wallpaperInfo.component!!.className == MAGIC_PORTRAIT_CLASSNAME
- } else {
- false
- }
- }
-
companion object {
const val MAGIC_PORTRAIT_CLASSNAME =
"com.google.android.apps.magicportrait.service.MagicPortraitWallpaperService"
diff --git a/packages/SystemUI/src/com/google/android/systemui/lowlightclock/LowLightClockDreamService.java b/packages/SystemUI/src/com/google/android/systemui/lowlightclock/LowLightClockDreamService.java
new file mode 100644
index 000000000000..8a5f7eaf8776
--- /dev/null
+++ b/packages/SystemUI/src/com/google/android/systemui/lowlightclock/LowLightClockDreamService.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.google.android.systemui.lowlightclock;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.annotation.Nullable;
+import android.service.dreams.DreamService;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.TextClock;
+import android.widget.TextView;
+
+import com.android.dream.lowlight.LowLightTransitionCoordinator;
+import com.android.systemui.lowlightclock.ChargingStatusProvider;
+import com.android.systemui.lowlightclock.LowLightClockAnimationProvider;
+import com.android.systemui.lowlightclock.LowLightDisplayController;
+import com.android.systemui.res.R;
+
+import java.util.Optional;
+
+import javax.inject.Inject;
+import javax.inject.Provider;
+
+/**
+ * A dark themed text clock dream to be shown when the device is in a low light environment.
+ */
+public class LowLightClockDreamService extends DreamService implements
+ LowLightTransitionCoordinator.LowLightExitListener {
+ private static final String TAG = "LowLightClockDreamService";
+
+ private final ChargingStatusProvider mChargingStatusProvider;
+ private final LowLightDisplayController mDisplayController;
+ private final LowLightClockAnimationProvider mAnimationProvider;
+ private final LowLightTransitionCoordinator mLowLightTransitionCoordinator;
+ private boolean mIsDimBrightnessSupported = false;
+
+ private TextView mChargingStatusTextView;
+ private TextClock mTextClock;
+ @Nullable
+ private Animator mAnimationIn;
+ @Nullable
+ private Animator mAnimationOut;
+
+ @Inject
+ public LowLightClockDreamService(
+ ChargingStatusProvider chargingStatusProvider,
+ LowLightClockAnimationProvider animationProvider,
+ LowLightTransitionCoordinator lowLightTransitionCoordinator,
+ Optional<Provider<LowLightDisplayController>> displayController) {
+ super();
+
+ mAnimationProvider = animationProvider;
+ mDisplayController = displayController.map(Provider::get).orElse(null);
+ mChargingStatusProvider = chargingStatusProvider;
+ mLowLightTransitionCoordinator = lowLightTransitionCoordinator;
+ }
+
+ @Override
+ public void onAttachedToWindow() {
+ super.onAttachedToWindow();
+
+ setInteractive(false);
+ setFullscreen(true);
+
+ setContentView(LayoutInflater.from(getApplicationContext()).inflate(
+ R.layout.low_light_clock_dream, null));
+
+ mTextClock = findViewById(R.id.low_light_text_clock);
+
+ mChargingStatusTextView = findViewById(R.id.charging_status_text_view);
+
+ mChargingStatusProvider.startUsing(this::updateChargingMessage);
+
+ mLowLightTransitionCoordinator.setLowLightExitListener(this);
+ }
+
+ @Override
+ public void onDreamingStarted() {
+ mAnimationIn = mAnimationProvider.provideAnimationIn(mTextClock, mChargingStatusTextView);
+ mAnimationIn.start();
+
+ if (mDisplayController != null) {
+ mIsDimBrightnessSupported = mDisplayController.isDisplayBrightnessModeSupported();
+
+ if (mIsDimBrightnessSupported) {
+ Log.v(TAG, "setting dim brightness state");
+ mDisplayController.setDisplayBrightnessModeEnabled(true);
+ } else {
+ Log.v(TAG, "dim brightness not supported");
+ }
+ }
+ }
+
+ @Override
+ public void onDreamingStopped() {
+ if (mIsDimBrightnessSupported) {
+ Log.v(TAG, "clearing dim brightness state");
+ mDisplayController.setDisplayBrightnessModeEnabled(false);
+ }
+ }
+
+ @Override
+ public void onWakeUp() {
+ if (mAnimationIn != null) {
+ mAnimationIn.cancel();
+ }
+ mAnimationOut = mAnimationProvider.provideAnimationOut(mTextClock, mChargingStatusTextView);
+ mAnimationOut.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animator) {
+ LowLightClockDreamService.super.onWakeUp();
+ }
+ });
+ mAnimationOut.start();
+ }
+
+ @Override
+ public void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+
+ if (mAnimationOut != null) {
+ mAnimationOut.cancel();
+ }
+
+ mChargingStatusProvider.stopUsing();
+
+ mLowLightTransitionCoordinator.setLowLightExitListener(null);
+ }
+
+ private void updateChargingMessage(boolean showChargingStatus, String chargingStatusMessage) {
+ mChargingStatusTextView.setText(chargingStatusMessage);
+ mChargingStatusTextView.setVisibility(showChargingStatus ? View.VISIBLE : View.INVISIBLE);
+ }
+
+ @Override
+ public Animator onBeforeExitLowLight() {
+ mAnimationOut = mAnimationProvider.provideAnimationOut(mTextClock, mChargingStatusTextView);
+ mAnimationOut.start();
+
+ // Return the animator so that the transition coordinator waits for the low light exit
+ // animations to finish before entering low light, as otherwise the default DreamActivity
+ // animation plays immediately and there's no time for this animation to play.
+ return mAnimationOut;
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index 2645811fa4ad..312d2ffd74e4 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -38,6 +38,7 @@ import static com.android.keyguard.KeyguardUpdateMonitor.BIOMETRIC_STATE_CANCELL
import static com.android.keyguard.KeyguardUpdateMonitor.BIOMETRIC_STATE_STOPPED;
import static com.android.keyguard.KeyguardUpdateMonitor.DEFAULT_CANCEL_SIGNAL_TIMEOUT;
import static com.android.keyguard.KeyguardUpdateMonitor.HAL_POWER_PRESS_TIMEOUT;
+import static com.android.systemui.Flags.FLAG_GLANCEABLE_HUB_V2;
import static com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_OPENED;
import static com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_UNKNOWN;
@@ -139,6 +140,7 @@ import com.android.systemui.biometrics.AuthController;
import com.android.systemui.biometrics.FingerprintInteractiveToAuthProvider;
import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor;
import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor;
import com.android.systemui.deviceentry.data.repository.FaceWakeUpTriggersConfig;
import com.android.systemui.deviceentry.data.repository.FaceWakeUpTriggersConfigImpl;
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor;
@@ -180,6 +182,9 @@ import org.mockito.MockitoAnnotations;
import org.mockito.MockitoSession;
import org.mockito.quality.Strictness;
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4;
+import platform.test.runner.parameterized.Parameters;
+
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -189,9 +194,6 @@ import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
-import platform.test.runner.parameterized.ParameterizedAndroidJunit4;
-import platform.test.runner.parameterized.Parameters;
-
@SmallTest
@RunWith(ParameterizedAndroidJunit4.class)
@TestableLooper.RunWithLooper
@@ -304,6 +306,8 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
private JavaAdapter mJavaAdapter;
@Mock
private SceneInteractor mSceneInteractor;
+ @Mock
+ private CommunalSceneInteractor mCommunalSceneInteractor;
@Captor
private ArgumentCaptor<FaceAuthenticationListener> mFaceAuthenticationListener;
@@ -1084,6 +1088,49 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
}
@Test
+ @EnableFlags(FLAG_GLANCEABLE_HUB_V2)
+ public void udfpsStopsListeningWhenCommunalShowing() {
+ // GIVEN keyguard showing
+ mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
+ mKeyguardUpdateMonitor.setKeyguardShowing(true, false);
+
+ assertThat(mKeyguardUpdateMonitor.shouldListenForFingerprint(true)).isTrue();
+
+ // WHEN communal is shown
+ mKeyguardUpdateMonitor.onCommunalShowingChanged(true);
+
+ // THEN shouldn't listen for fingerprint
+ assertThat(mKeyguardUpdateMonitor.shouldListenForFingerprint(true)).isFalse();
+
+ // WHEN alternate bouncer shows on top of communal, we should listen for fingerprint
+ mKeyguardUpdateMonitor.setAlternateBouncerVisibility(true);
+ assertThat(mKeyguardUpdateMonitor.shouldListenForFingerprint(true)).isTrue();
+
+ // WHEN communal is hidden
+ mKeyguardUpdateMonitor.onCommunalShowingChanged(false);
+ mKeyguardUpdateMonitor.setAlternateBouncerVisibility(false);
+
+ // THEN listen for fingerprint
+ assertThat(mKeyguardUpdateMonitor.shouldListenForFingerprint(true)).isTrue();
+ }
+
+ @Test
+ @EnableFlags(FLAG_GLANCEABLE_HUB_V2)
+ public void sfpsNotAffectedByCommunalShowing() {
+ // GIVEN keyguard showing
+ mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
+ mKeyguardUpdateMonitor.setKeyguardShowing(true, false);
+
+ assertThat(mKeyguardUpdateMonitor.shouldListenForFingerprint(false)).isTrue();
+
+ // WHEN communal is shown
+ mKeyguardUpdateMonitor.onCommunalShowingChanged(true);
+
+ // THEN we should still listen for fingerprint if not UDFPS
+ assertThat(mKeyguardUpdateMonitor.shouldListenForFingerprint(false)).isTrue();
+ }
+
+ @Test
public void testFingerprintPowerPressed_restartsFingerprintListeningStateWithDelay() {
mKeyguardUpdateMonitor.mFingerprintAuthenticationCallback
.onAuthenticationError(FingerprintManager.BIOMETRIC_ERROR_POWER_PRESSED, "");
@@ -2669,7 +2716,8 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
mTaskStackChangeListeners, mSelectedUserInteractor, mActivityTaskManager,
() -> mAlternateBouncerInteractor,
() -> mJavaAdapter,
- () -> mSceneInteractor);
+ () -> mSceneInteractor,
+ mCommunalSceneInteractor);
setAlternateBouncerVisibility(false);
setPrimaryBouncerVisibility(false);
setStrongAuthTracker(KeyguardUpdateMonitorTest.this.mStrongAuthTracker);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationTest.java
index 057ddcd54e68..8bfd2545ff2b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationTest.java
@@ -21,7 +21,7 @@ import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_
import static com.android.systemui.accessibility.AccessibilityLogger.MagnificationSettingsEvent;
import static com.android.systemui.accessibility.WindowMagnificationSettings.MagnificationSize;
-import static com.android.systemui.recents.OverviewProxyService.OverviewProxyListener;
+import static com.android.systemui.recents.LauncherProxyService.LauncherProxyListener;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_MAGNIFICATION_OVERLAP;
import static org.mockito.ArgumentMatchers.any;
@@ -53,7 +53,7 @@ import androidx.test.filters.SmallTest;
import com.android.app.viewcapture.ViewCaptureAwareWindowManager;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.model.SysUiState;
-import com.android.systemui.recents.OverviewProxyService;
+import com.android.systemui.recents.LauncherProxyService;
import com.android.systemui.settings.FakeDisplayTracker;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.util.settings.SecureSettings;
@@ -80,13 +80,13 @@ public class MagnificationTest extends SysuiTestCase {
@Mock
private IMagnificationConnectionCallback mConnectionCallback;
@Mock
- private OverviewProxyService mOverviewProxyService;
+ private LauncherProxyService mLauncherProxyService;
@Mock
private SecureSettings mSecureSettings;
private CommandQueue mCommandQueue;
private MagnificationImpl mMagnification;
- private OverviewProxyListener mOverviewProxyListener;
+ private LauncherProxyListener mLauncherProxyListener;
private FakeDisplayTracker mDisplayTracker = new FakeDisplayTracker(mContext);
@Mock
@@ -130,7 +130,7 @@ public class MagnificationTest extends SysuiTestCase {
mMagnification = new MagnificationImpl(getContext(),
getContext().getMainThreadHandler(), mContext.getMainExecutor(),
mCommandQueue, mModeSwitchesController,
- mSysUiState, mOverviewProxyService, mSecureSettings, mDisplayTracker,
+ mSysUiState, mLauncherProxyService, mSecureSettings, mDisplayTracker,
getContext().getSystemService(DisplayManager.class), mA11yLogger, mIWindowManager,
getContext().getSystemService(AccessibilityManager.class),
mViewCaptureAwareWindowManager);
@@ -140,10 +140,10 @@ public class MagnificationTest extends SysuiTestCase {
mContext.getSystemService(DisplayManager.class), mMagnificationSettingsController);
mMagnification.start();
- final ArgumentCaptor<OverviewProxyListener> listenerArgumentCaptor =
- ArgumentCaptor.forClass(OverviewProxyListener.class);
- verify(mOverviewProxyService).addCallback(listenerArgumentCaptor.capture());
- mOverviewProxyListener = listenerArgumentCaptor.getValue();
+ final ArgumentCaptor<LauncherProxyListener> listenerArgumentCaptor =
+ ArgumentCaptor.forClass(LauncherProxyListener.class);
+ verify(mLauncherProxyService).addCallback(listenerArgumentCaptor.capture());
+ mLauncherProxyListener = listenerArgumentCaptor.getValue();
}
@Test
@@ -336,7 +336,7 @@ public class MagnificationTest extends SysuiTestCase {
@Test
public void overviewProxyIsConnected_noController_resetFlag() {
- mOverviewProxyListener.onConnectionChanged(true);
+ mLauncherProxyListener.onConnectionChanged(true);
verify(mSysUiState).setFlag(SYSUI_STATE_MAGNIFICATION_OVERLAP, false);
verify(mSysUiState).commitUpdate(mContext.getDisplayId());
@@ -349,7 +349,7 @@ public class MagnificationTest extends SysuiTestCase {
mContext.getSystemService(DisplayManager.class), mController);
mMagnification.mWindowMagnificationControllerSupplier.get(TEST_DISPLAY);
- mOverviewProxyListener.onConnectionChanged(true);
+ mLauncherProxyListener.onConnectionChanged(true);
verify(mController).updateSysUIStateFlag();
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
index 21519b0cb38a..ab691c630f97 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
@@ -304,9 +304,7 @@ class KeyguardQuickAffordanceInteractorParameterizedTest : SysuiTestCase() {
testScope = TestScope(testDispatcher)
underTest =
KeyguardQuickAffordanceInteractor(
- keyguardInteractor =
- KeyguardInteractorFactory.create(featureFlags = featureFlags)
- .keyguardInteractor,
+ keyguardInteractor = kosmos.keyguardInteractor,
shadeInteractor = kosmos.shadeInteractor,
lockPatternUtils = lockPatternUtils,
keyguardStateController = keyguardStateController,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt
index b5a227104900..051aba3d593f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt
@@ -44,9 +44,10 @@ import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanc
import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.KeyguardQuickAffordanceRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
-import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory
import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceInteractor
+import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
@@ -191,9 +192,8 @@ class KeyguardQuickAffordancesCombinedViewModelTest : SysuiTestCase() {
dockManager = DockManagerFake()
biometricSettingsRepository = FakeBiometricSettingsRepository()
- val withDeps = KeyguardInteractorFactory.create()
- keyguardInteractor = withDeps.keyguardInteractor
- repository = withDeps.repository
+ keyguardInteractor = kosmos.keyguardInteractor
+ repository = kosmos.fakeKeyguardRepository
whenever(userTracker.userHandle).thenReturn(mock())
whenever(lockPatternUtils.getStrongAuthForUser(ArgumentMatchers.anyInt()))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/lowlightclock/AmbientLightModeMonitorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/lowlightclock/AmbientLightModeMonitorTest.kt
new file mode 100644
index 000000000000..43ee388e44a7
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/lowlightclock/AmbientLightModeMonitorTest.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.
+ */
+
+package com.android.systemui.lowlightclock
+
+import android.hardware.Sensor
+import android.hardware.SensorEventListener
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.sensors.AsyncSensorManager
+import java.util.Optional
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Mock
+import org.mockito.Mockito.any
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.eq
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class AmbientLightModeMonitorTest : SysuiTestCase() {
+ @Mock private lateinit var sensorManager: AsyncSensorManager
+ @Mock private lateinit var sensor: Sensor
+ @Mock private lateinit var algorithm: AmbientLightModeMonitor.DebounceAlgorithm
+
+ private lateinit var ambientLightModeMonitor: AmbientLightModeMonitor
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ ambientLightModeMonitor =
+ AmbientLightModeMonitor(Optional.of(algorithm), sensorManager, Optional.of(sensor))
+ }
+
+ @Test
+ fun shouldRegisterSensorEventListenerOnStart() {
+ val callback = mock(AmbientLightModeMonitor.Callback::class.java)
+ ambientLightModeMonitor.start(callback)
+
+ verify(sensorManager).registerListener(any(), eq(sensor), anyInt())
+ }
+
+ @Test
+ fun shouldUnregisterSensorEventListenerOnStop() {
+ val callback = mock(AmbientLightModeMonitor.Callback::class.java)
+ ambientLightModeMonitor.start(callback)
+
+ val sensorEventListener = captureSensorEventListener()
+
+ ambientLightModeMonitor.stop()
+
+ verify(sensorManager).unregisterListener(eq(sensorEventListener))
+ }
+
+ @Test
+ fun shouldStartDebounceAlgorithmOnStart() {
+ val callback = mock(AmbientLightModeMonitor.Callback::class.java)
+ ambientLightModeMonitor.start(callback)
+
+ verify(algorithm).start(eq(callback))
+ }
+
+ @Test
+ fun shouldStopDebounceAlgorithmOnStop() {
+ val callback = mock(AmbientLightModeMonitor.Callback::class.java)
+ ambientLightModeMonitor.start(callback)
+ ambientLightModeMonitor.stop()
+
+ verify(algorithm).stop()
+ }
+
+ @Test
+ fun shouldNotRegisterForSensorUpdatesIfSensorNotAvailable() {
+ val ambientLightModeMonitor =
+ AmbientLightModeMonitor(Optional.of(algorithm), sensorManager, Optional.empty())
+
+ val callback = mock(AmbientLightModeMonitor.Callback::class.java)
+ ambientLightModeMonitor.start(callback)
+
+ verify(sensorManager, never()).registerListener(any(), any(Sensor::class.java), anyInt())
+ }
+
+ // Captures [SensorEventListener], assuming it has been registered with [sensorManager].
+ private fun captureSensorEventListener(): SensorEventListener {
+ val captor = ArgumentCaptor.forClass(SensorEventListener::class.java)
+ verify(sensorManager).registerListener(captor.capture(), any(), anyInt())
+ return captor.value
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/lowlightclock/ChargingStatusProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/lowlightclock/ChargingStatusProviderTest.java
new file mode 100644
index 000000000000..2c8c1e1e70b1
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/lowlightclock/ChargingStatusProviderTest.java
@@ -0,0 +1,226 @@
+/*
+ * 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.lowlightclock;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.res.Resources;
+import android.os.BatteryManager;
+import android.os.RemoteException;
+import android.testing.AndroidTestingRunner;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.app.IBatteryStats;
+import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.keyguard.KeyguardUpdateMonitorCallback;
+import com.android.settingslib.fuelgauge.BatteryStatus;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.res.R;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class ChargingStatusProviderTest extends SysuiTestCase {
+ @Mock
+ private Resources mResources;
+ @Mock
+ private IBatteryStats mBatteryInfo;
+ @Mock
+ private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+ @Mock
+ private ChargingStatusProvider.Callback mCallback;
+
+ private ChargingStatusProvider mProvider;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+
+ mProvider = new ChargingStatusProvider(
+ mContext, mResources, mBatteryInfo, mKeyguardUpdateMonitor);
+ }
+
+ @Test
+ public void testStartUsingReportsStatusToCallback() {
+ mProvider.startUsing(mCallback);
+ verify(mCallback).onChargingStatusChanged(false, null);
+ }
+
+ @Test
+ public void testStartUsingRegistersCallbackWithKeyguardUpdateMonitor() {
+ mProvider.startUsing(mCallback);
+ verify(mKeyguardUpdateMonitor).registerCallback(any());
+ }
+
+ @Test
+ public void testCallbackNotCalledAfterStopUsing() {
+ mProvider.startUsing(mCallback);
+ ArgumentCaptor<KeyguardUpdateMonitorCallback> keyguardUpdateMonitorCallbackArgumentCaptor =
+ ArgumentCaptor.forClass(KeyguardUpdateMonitorCallback.class);
+ verify(mKeyguardUpdateMonitor)
+ .registerCallback(keyguardUpdateMonitorCallbackArgumentCaptor.capture());
+ mProvider.stopUsing();
+ keyguardUpdateMonitorCallbackArgumentCaptor.getValue()
+ .onRefreshBatteryInfo(getChargingBattery());
+ verify(mCallback, never()).onChargingStatusChanged(eq(true), any());
+ }
+
+ @Test
+ public void testKeyguardUpdateMonitorCallbackRemovedAfterStopUsing() {
+ mProvider.startUsing(mCallback);
+ ArgumentCaptor<KeyguardUpdateMonitorCallback> keyguardUpdateMonitorCallbackArgumentCaptor =
+ ArgumentCaptor.forClass(KeyguardUpdateMonitorCallback.class);
+ verify(mKeyguardUpdateMonitor)
+ .registerCallback(keyguardUpdateMonitorCallbackArgumentCaptor.capture());
+ mProvider.stopUsing();
+ verify(mKeyguardUpdateMonitor)
+ .removeCallback(keyguardUpdateMonitorCallbackArgumentCaptor.getValue());
+ }
+
+ @Test
+ public void testChargingStatusReportsHideWhenNotPluggedIn() {
+ ArgumentCaptor<KeyguardUpdateMonitorCallback> keyguardUpdateMonitorCallbackArgumentCaptor =
+ ArgumentCaptor.forClass(KeyguardUpdateMonitorCallback.class);
+ mProvider.startUsing(mCallback);
+ verify(mKeyguardUpdateMonitor)
+ .registerCallback(keyguardUpdateMonitorCallbackArgumentCaptor.capture());
+ keyguardUpdateMonitorCallbackArgumentCaptor.getValue()
+ .onRefreshBatteryInfo(getUnpluggedBattery());
+ // Once for init() and once for the status change.
+ verify(mCallback, times(2)).onChargingStatusChanged(false, null);
+ }
+
+ @Test
+ public void testChargingStatusReportsShowWhenBatteryOverheated() {
+ ArgumentCaptor<KeyguardUpdateMonitorCallback> keyguardUpdateMonitorCallbackArgumentCaptor =
+ ArgumentCaptor.forClass(KeyguardUpdateMonitorCallback.class);
+ mProvider.startUsing(mCallback);
+ verify(mCallback).onChargingStatusChanged(false, null);
+ verify(mKeyguardUpdateMonitor)
+ .registerCallback(keyguardUpdateMonitorCallbackArgumentCaptor.capture());
+ keyguardUpdateMonitorCallbackArgumentCaptor.getValue()
+ .onRefreshBatteryInfo(getBatteryDefender());
+ verify(mCallback).onChargingStatusChanged(eq(true), any());
+ }
+
+ @Test
+ public void testChargingStatusReportsShowWhenPluggedIn() {
+ ArgumentCaptor<KeyguardUpdateMonitorCallback> keyguardUpdateMonitorCallbackArgumentCaptor =
+ ArgumentCaptor.forClass(KeyguardUpdateMonitorCallback.class);
+ mProvider.startUsing(mCallback);
+ verify(mCallback).onChargingStatusChanged(false, null);
+ verify(mKeyguardUpdateMonitor)
+ .registerCallback(keyguardUpdateMonitorCallbackArgumentCaptor.capture());
+ keyguardUpdateMonitorCallbackArgumentCaptor.getValue()
+ .onRefreshBatteryInfo(getChargingBattery());
+ verify(mCallback).onChargingStatusChanged(eq(true), any());
+ }
+
+ @Test
+ public void testChargingStatusReportsChargingLimitedWhenOverheated() {
+ ArgumentCaptor<KeyguardUpdateMonitorCallback> keyguardUpdateMonitorCallbackArgumentCaptor =
+ ArgumentCaptor.forClass(KeyguardUpdateMonitorCallback.class);
+ mProvider.startUsing(mCallback);
+ verify(mCallback).onChargingStatusChanged(false, null);
+ verify(mKeyguardUpdateMonitor)
+ .registerCallback(keyguardUpdateMonitorCallbackArgumentCaptor.capture());
+ keyguardUpdateMonitorCallbackArgumentCaptor.getValue()
+ .onRefreshBatteryInfo(getBatteryDefender());
+ verify(mResources).getString(eq(R.string.keyguard_plugged_in_charging_limited), any());
+ }
+
+ @Test
+ public void testChargingStatusReportsChargedWhenCharged() {
+ ArgumentCaptor<KeyguardUpdateMonitorCallback> keyguardUpdateMonitorCallbackArgumentCaptor =
+ ArgumentCaptor.forClass(KeyguardUpdateMonitorCallback.class);
+ mProvider.startUsing(mCallback);
+ verify(mCallback).onChargingStatusChanged(false, null);
+ verify(mKeyguardUpdateMonitor)
+ .registerCallback(keyguardUpdateMonitorCallbackArgumentCaptor.capture());
+ keyguardUpdateMonitorCallbackArgumentCaptor.getValue()
+ .onRefreshBatteryInfo(getChargedBattery());
+ verify(mResources).getString(R.string.keyguard_charged);
+ }
+
+ @Test
+ public void testChargingStatusReportsPluggedInWhenDockedAndChargingTimeUnknown() throws
+ RemoteException {
+ ArgumentCaptor<KeyguardUpdateMonitorCallback> keyguardUpdateMonitorCallbackArgumentCaptor =
+ ArgumentCaptor.forClass(KeyguardUpdateMonitorCallback.class);
+ mProvider.startUsing(mCallback);
+ verify(mCallback).onChargingStatusChanged(false, null);
+ verify(mKeyguardUpdateMonitor)
+ .registerCallback(keyguardUpdateMonitorCallbackArgumentCaptor.capture());
+ when(mBatteryInfo.computeChargeTimeRemaining()).thenReturn(-1L);
+ keyguardUpdateMonitorCallbackArgumentCaptor.getValue()
+ .onRefreshBatteryInfo(getChargingBattery());
+ verify(mResources).getString(
+ eq(R.string.keyguard_plugged_in_dock), any());
+ }
+
+ @Test
+ public void testChargingStatusReportsTimeRemainingWhenDockedAndCharging() throws
+ RemoteException {
+ ArgumentCaptor<KeyguardUpdateMonitorCallback> keyguardUpdateMonitorCallbackArgumentCaptor =
+ ArgumentCaptor.forClass(KeyguardUpdateMonitorCallback.class);
+ mProvider.startUsing(mCallback);
+ verify(mCallback).onChargingStatusChanged(false, null);
+ verify(mKeyguardUpdateMonitor)
+ .registerCallback(keyguardUpdateMonitorCallbackArgumentCaptor.capture());
+ when(mBatteryInfo.computeChargeTimeRemaining()).thenReturn(1L);
+ keyguardUpdateMonitorCallbackArgumentCaptor.getValue()
+ .onRefreshBatteryInfo(getChargingBattery());
+ verify(mResources).getString(
+ eq(R.string.keyguard_indication_charging_time_dock), any(), any());
+ }
+
+ private BatteryStatus getUnpluggedBattery() {
+ return new BatteryStatus(BatteryManager.BATTERY_STATUS_NOT_CHARGING,
+ 80, BatteryManager.BATTERY_PLUGGED_ANY, BatteryManager.BATTERY_HEALTH_GOOD,
+ 0, true);
+ }
+
+ private BatteryStatus getChargingBattery() {
+ return new BatteryStatus(BatteryManager.BATTERY_STATUS_CHARGING,
+ 80, BatteryManager.BATTERY_PLUGGED_DOCK,
+ BatteryManager.BATTERY_HEALTH_GOOD, 0, true);
+ }
+
+ private BatteryStatus getChargedBattery() {
+ return new BatteryStatus(BatteryManager.BATTERY_STATUS_FULL,
+ 100, BatteryManager.BATTERY_PLUGGED_DOCK,
+ BatteryManager.BATTERY_HEALTH_GOOD, 0, true);
+ }
+
+ private BatteryStatus getBatteryDefender() {
+ return new BatteryStatus(BatteryManager.BATTERY_STATUS_CHARGING,
+ 80, BatteryManager.BATTERY_PLUGGED_DOCK,
+ BatteryManager.CHARGING_POLICY_ADAPTIVE_LONGLIFE, 0, true);
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/lowlightclock/DirectBootConditionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/lowlightclock/DirectBootConditionTest.kt
new file mode 100644
index 000000000000..173f243cb2b0
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/lowlightclock/DirectBootConditionTest.kt
@@ -0,0 +1,102 @@
+/*
+ * 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.lowlightclock
+
+import android.content.Intent
+import android.os.UserManager
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.shared.condition.Condition
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.advanceUntilIdle
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@OptIn(ExperimentalCoroutinesApi::class)
+class DirectBootConditionTest : SysuiTestCase() {
+ @Mock private lateinit var userManager: UserManager
+ @Mock private lateinit var callback: Condition.Callback
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ }
+
+ @Test
+ fun receiverRegisteredOnStart() = runTest {
+ val condition = buildCondition(this)
+ // No receivers are registered yet
+ assertThat(fakeBroadcastDispatcher.numReceiversRegistered).isEqualTo(0)
+ condition.addCallback(callback)
+ advanceUntilIdle()
+ // Receiver is registered after a callback is added
+ assertThat(fakeBroadcastDispatcher.numReceiversRegistered).isEqualTo(1)
+ condition.removeCallback(callback)
+ }
+
+ @Test
+ fun unregisterReceiverOnStop() = runTest {
+ val condition = buildCondition(this)
+
+ condition.addCallback(callback)
+ advanceUntilIdle()
+
+ assertThat(fakeBroadcastDispatcher.numReceiversRegistered).isEqualTo(1)
+
+ condition.removeCallback(callback)
+ advanceUntilIdle()
+
+ // Receiver is unregistered when nothing is listening to the condition
+ assertThat(fakeBroadcastDispatcher.numReceiversRegistered).isEqualTo(0)
+ }
+
+ @Test
+ fun callbackTriggeredWhenUserUnlocked() = runTest {
+ val condition = buildCondition(this)
+
+ setUserUnlocked(false)
+ condition.addCallback(callback)
+ advanceUntilIdle()
+
+ assertThat(condition.isConditionMet).isTrue()
+
+ setUserUnlocked(true)
+ advanceUntilIdle()
+
+ assertThat(condition.isConditionMet).isFalse()
+ condition.removeCallback(callback)
+ }
+
+ private fun buildCondition(scope: CoroutineScope): DirectBootCondition {
+ return DirectBootCondition(fakeBroadcastDispatcher, userManager, scope)
+ }
+
+ private fun setUserUnlocked(unlocked: Boolean) {
+ whenever(userManager.isUserUnlocked).thenReturn(unlocked)
+ fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
+ context,
+ Intent(Intent.ACTION_USER_UNLOCKED),
+ )
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/lowlightclock/ForceLowLightConditionTest.java b/packages/SystemUI/tests/src/com/android/systemui/lowlightclock/ForceLowLightConditionTest.java
new file mode 100644
index 000000000000..7297e0f3bff5
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/lowlightclock/ForceLowLightConditionTest.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.systemui.lowlightclock;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+
+import android.testing.AndroidTestingRunner;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.shared.condition.Condition;
+import com.android.systemui.statusbar.commandline.Command;
+import com.android.systemui.statusbar.commandline.CommandRegistry;
+
+import kotlin.jvm.functions.Function0;
+
+import kotlinx.coroutines.CoroutineScope;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+import java.io.PrintWriter;
+import java.util.Arrays;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class ForceLowLightConditionTest extends SysuiTestCase {
+ @Mock
+ private CommandRegistry mCommandRegistry;
+
+ @Mock
+ private Condition.Callback mCallback;
+
+ @Mock
+ private PrintWriter mPrintWriter;
+
+ @Mock
+ CoroutineScope mScope;
+
+ private ForceLowLightCondition mCondition;
+ private Command mCommand;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mCondition = new ForceLowLightCondition(mScope, mCommandRegistry);
+ mCondition.addCallback(mCallback);
+ ArgumentCaptor<Function0<Command>> commandCaptor =
+ ArgumentCaptor.forClass(Function0.class);
+ verify(mCommandRegistry).registerCommand(eq(ForceLowLightCondition.COMMAND_ROOT),
+ commandCaptor.capture());
+ mCommand = commandCaptor.getValue().invoke();
+ }
+
+ @Test
+ public void testEnableLowLight() {
+ mCommand.execute(mPrintWriter,
+ Arrays.asList(ForceLowLightCondition.COMMAND_ENABLE_LOW_LIGHT));
+ verify(mCallback).onConditionChanged(mCondition);
+ assertThat(mCondition.isConditionSet()).isTrue();
+ assertThat(mCondition.isConditionMet()).isTrue();
+ }
+
+ @Test
+ public void testDisableLowLight() {
+ mCommand.execute(mPrintWriter,
+ Arrays.asList(ForceLowLightCondition.COMMAND_DISABLE_LOW_LIGHT));
+ verify(mCallback).onConditionChanged(mCondition);
+ assertThat(mCondition.isConditionSet()).isTrue();
+ assertThat(mCondition.isConditionMet()).isFalse();
+ }
+
+ @Test
+ public void testClearEnableLowLight() {
+ mCommand.execute(mPrintWriter,
+ Arrays.asList(ForceLowLightCondition.COMMAND_ENABLE_LOW_LIGHT));
+ verify(mCallback).onConditionChanged(mCondition);
+ assertThat(mCondition.isConditionSet()).isTrue();
+ assertThat(mCondition.isConditionMet()).isTrue();
+ Mockito.clearInvocations(mCallback);
+ mCommand.execute(mPrintWriter,
+ Arrays.asList(ForceLowLightCondition.COMMAND_CLEAR_LOW_LIGHT));
+ verify(mCallback).onConditionChanged(mCondition);
+ assertThat(mCondition.isConditionSet()).isFalse();
+ assertThat(mCondition.isConditionMet()).isFalse();
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/lowlightclock/LowLightClockAnimationProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/lowlightclock/LowLightClockAnimationProviderTest.kt
new file mode 100644
index 000000000000..663880f098cd
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/lowlightclock/LowLightClockAnimationProviderTest.kt
@@ -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.systemui.lowlightclock
+
+import android.animation.Animator
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper.RunWithLooper
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.any
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.verify
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@RunWithLooper(setAsMainLooper = true)
+class LowLightClockAnimationProviderTest : SysuiTestCase() {
+
+ private val underTest by lazy {
+ LowLightClockAnimationProvider(
+ Y_TRANSLATION_ANIMATION_OFFSET,
+ Y_TRANSLATION_ANIMATION_DURATION_MILLIS,
+ ALPHA_ANIMATION_IN_START_DELAY_MILLIS,
+ ALPHA_ANIMATION_DURATION_MILLIS,
+ )
+ }
+
+ @Test
+ fun animationOutEndsImmediatelyIfViewIsNull() {
+ val animator = underTest.provideAnimationOut(null, null)
+
+ val listener = mock<Animator.AnimatorListener>()
+ animator.addListener(listener)
+
+ animator.start()
+ verify(listener).onAnimationStart(any(), eq(false))
+ verify(listener).onAnimationEnd(any(), eq(false))
+ }
+
+ @Test
+ fun animationInEndsImmediatelyIfViewIsNull() {
+ val animator = underTest.provideAnimationIn(null, null)
+
+ val listener = mock<Animator.AnimatorListener>()
+ animator.addListener(listener)
+
+ animator.start()
+ verify(listener).onAnimationStart(any(), eq(false))
+ verify(listener).onAnimationEnd(any(), eq(false))
+ }
+
+ private companion object {
+ const val Y_TRANSLATION_ANIMATION_OFFSET = 100
+ const val Y_TRANSLATION_ANIMATION_DURATION_MILLIS = 100L
+ const val ALPHA_ANIMATION_IN_START_DELAY_MILLIS = 200L
+ const val ALPHA_ANIMATION_DURATION_MILLIS = 300L
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/lowlightclock/LowLightClockDreamServiceTest.java b/packages/SystemUI/tests/src/com/android/systemui/lowlightclock/LowLightClockDreamServiceTest.java
new file mode 100644
index 000000000000..22a13cc41425
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/lowlightclock/LowLightClockDreamServiceTest.java
@@ -0,0 +1,160 @@
+/*
+ * 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.lowlightclock;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.animation.Animator;
+import android.os.RemoteException;
+import android.testing.AndroidTestingRunner;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.dream.lowlight.LowLightTransitionCoordinator;
+import com.android.systemui.SysuiTestCase;
+
+import com.google.android.systemui.lowlightclock.LowLightClockDreamService;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Optional;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class LowLightClockDreamServiceTest extends SysuiTestCase {
+ @Mock
+ private ChargingStatusProvider mChargingStatusProvider;
+ @Mock
+ private LowLightDisplayController mDisplayController;
+ @Mock
+ private LowLightClockAnimationProvider mAnimationProvider;
+ @Mock
+ private LowLightTransitionCoordinator mLowLightTransitionCoordinator;
+ @Mock
+ Animator mAnimationInAnimator;
+ @Mock
+ Animator mAnimationOutAnimator;
+
+ private LowLightClockDreamService mService;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ mService = new LowLightClockDreamService(
+ mChargingStatusProvider,
+ mAnimationProvider,
+ mLowLightTransitionCoordinator,
+ Optional.of(() -> mDisplayController));
+
+ when(mAnimationProvider.provideAnimationIn(any(), any())).thenReturn(mAnimationInAnimator);
+ when(mAnimationProvider.provideAnimationOut(any())).thenReturn(
+ mAnimationOutAnimator);
+ }
+
+ @Test
+ public void testSetDbmStateWhenSupported() throws RemoteException {
+ when(mDisplayController.isDisplayBrightnessModeSupported()).thenReturn(true);
+
+ mService.onDreamingStarted();
+
+ verify(mDisplayController).setDisplayBrightnessModeEnabled(true);
+ }
+
+ @Test
+ public void testNotSetDbmStateWhenNotSupported() throws RemoteException {
+ when(mDisplayController.isDisplayBrightnessModeSupported()).thenReturn(false);
+
+ mService.onDreamingStarted();
+
+ verify(mDisplayController, never()).setDisplayBrightnessModeEnabled(anyBoolean());
+ }
+
+ @Test
+ public void testClearDbmState() throws RemoteException {
+ when(mDisplayController.isDisplayBrightnessModeSupported()).thenReturn(true);
+
+ mService.onDreamingStarted();
+ clearInvocations(mDisplayController);
+
+ mService.onDreamingStopped();
+
+ verify(mDisplayController).setDisplayBrightnessModeEnabled(false);
+ }
+
+ @Test
+ public void testAnimationsStartedOnDreamingStarted() {
+ mService.onDreamingStarted();
+
+ // Entry animation started.
+ verify(mAnimationInAnimator).start();
+ }
+
+ @Test
+ public void testAnimationsStartedOnWakeUp() {
+ // Start dreaming then wake up.
+ mService.onDreamingStarted();
+ mService.onWakeUp();
+
+ // Entry animation started.
+ verify(mAnimationInAnimator).cancel();
+
+ // Exit animation started.
+ verify(mAnimationOutAnimator).start();
+ }
+
+ @Test
+ public void testAnimationsStartedBeforeExitingLowLight() {
+ mService.onBeforeExitLowLight();
+
+ // Exit animation started.
+ verify(mAnimationOutAnimator).start();
+ }
+
+ @Test
+ public void testWakeUpAnimationCancelledOnDetach() {
+ mService.onWakeUp();
+
+ // Exit animation started.
+ verify(mAnimationOutAnimator).start();
+
+ mService.onDetachedFromWindow();
+
+ verify(mAnimationOutAnimator).cancel();
+ }
+
+ @Test
+ public void testExitLowLightAnimationCancelledOnDetach() {
+ mService.onBeforeExitLowLight();
+
+ // Exit animation started.
+ verify(mAnimationOutAnimator).start();
+
+ mService.onDetachedFromWindow();
+
+ verify(mAnimationOutAnimator).cancel();
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/lowlightclock/LowLightConditionTest.java b/packages/SystemUI/tests/src/com/android/systemui/lowlightclock/LowLightConditionTest.java
new file mode 100644
index 000000000000..2c216244985e
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/lowlightclock/LowLightConditionTest.java
@@ -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.lowlightclock;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.testing.AndroidTestingRunner;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.logging.UiEventLogger;
+import com.android.systemui.SysuiTestCase;
+
+import kotlinx.coroutines.CoroutineScope;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class LowLightConditionTest extends SysuiTestCase {
+ @Mock
+ private AmbientLightModeMonitor mAmbientLightModeMonitor;
+ @Mock
+ private UiEventLogger mUiEventLogger;
+ @Mock
+ CoroutineScope mScope;
+ private LowLightCondition mCondition;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ mCondition = new LowLightCondition(mScope, mAmbientLightModeMonitor, mUiEventLogger);
+ mCondition.start();
+ }
+
+ @Test
+ public void testLowLightFalse() {
+ changeLowLightMode(AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_LIGHT);
+ assertThat(mCondition.isConditionMet()).isFalse();
+ }
+
+ @Test
+ public void testLowLightTrue() {
+ changeLowLightMode(AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_DARK);
+ assertThat(mCondition.isConditionMet()).isTrue();
+ }
+
+ @Test
+ public void testUndecidedLowLightStateIgnored() {
+ changeLowLightMode(AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_DARK);
+ assertThat(mCondition.isConditionMet()).isTrue();
+ changeLowLightMode(AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_UNDECIDED);
+ assertThat(mCondition.isConditionMet()).isTrue();
+ }
+
+ @Test
+ public void testLowLightChange() {
+ changeLowLightMode(AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_LIGHT);
+ assertThat(mCondition.isConditionMet()).isFalse();
+ changeLowLightMode(AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_DARK);
+ assertThat(mCondition.isConditionMet()).isTrue();
+ }
+
+ @Test
+ public void testResetIsConditionMetUponStop() {
+ changeLowLightMode(AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_DARK);
+ assertThat(mCondition.isConditionMet()).isTrue();
+
+ mCondition.stop();
+ assertThat(mCondition.isConditionMet()).isFalse();
+ }
+
+ @Test
+ public void testLoggingAmbientLightNotLowToLow() {
+ changeLowLightMode(AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_DARK);
+ // Only logged once.
+ verify(mUiEventLogger, times(1)).log(any());
+ // Logged with the correct state.
+ verify(mUiEventLogger).log(LowLightDockEvent.AMBIENT_LIGHT_TO_DARK);
+ }
+
+ @Test
+ public void testLoggingAmbientLightLowToLow() {
+ changeLowLightMode(AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_DARK);
+ reset(mUiEventLogger);
+
+ changeLowLightMode(AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_DARK);
+ // Doesn't log.
+ verify(mUiEventLogger, never()).log(any());
+ }
+
+ @Test
+ public void testLoggingAmbientLightNotLowToNotLow() {
+ changeLowLightMode(AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_LIGHT);
+ // Doesn't log.
+ verify(mUiEventLogger, never()).log(any());
+ }
+
+ @Test
+ public void testLoggingAmbientLightLowToNotLow() {
+ changeLowLightMode(AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_DARK);
+ reset(mUiEventLogger);
+
+ changeLowLightMode(AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_LIGHT);
+ // Only logged once.
+ verify(mUiEventLogger).log(any());
+ // Logged with the correct state.
+ verify(mUiEventLogger).log(LowLightDockEvent.AMBIENT_LIGHT_TO_LIGHT);
+ }
+
+ private void changeLowLightMode(int mode) {
+ ArgumentCaptor<AmbientLightModeMonitor.Callback> ambientLightCallbackCaptor =
+ ArgumentCaptor.forClass(AmbientLightModeMonitor.Callback.class);
+ verify(mAmbientLightModeMonitor).start(ambientLightCallbackCaptor.capture());
+ ambientLightCallbackCaptor.getValue().onChange(mode);
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/lowlightclock/LowLightMonitorTest.java b/packages/SystemUI/tests/src/com/android/systemui/lowlightclock/LowLightMonitorTest.java
new file mode 100644
index 000000000000..69485e848a6a
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/lowlightclock/LowLightMonitorTest.java
@@ -0,0 +1,183 @@
+/*
+ * 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.lowlightclock;
+
+import static com.android.dream.lowlight.LowLightDreamManager.AMBIENT_LIGHT_MODE_LOW_LIGHT;
+import static com.android.dream.lowlight.LowLightDreamManager.AMBIENT_LIGHT_MODE_REGULAR;
+import static com.android.systemui.keyguard.ScreenLifecycle.SCREEN_ON;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.ComponentName;
+import android.content.pm.PackageManager;
+import android.testing.AndroidTestingRunner;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.dream.lowlight.LowLightDreamManager;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.keyguard.ScreenLifecycle;
+import com.android.systemui.shared.condition.Condition;
+import com.android.systemui.shared.condition.Monitor;
+
+import dagger.Lazy;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Set;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class LowLightMonitorTest extends SysuiTestCase {
+
+ @Mock
+ private Lazy<LowLightDreamManager> mLowLightDreamManagerLazy;
+ @Mock
+ private LowLightDreamManager mLowLightDreamManager;
+ @Mock
+ private Monitor mMonitor;
+ @Mock
+ private ScreenLifecycle mScreenLifecycle;
+ @Mock
+ private LowLightLogger mLogger;
+
+ private LowLightMonitor mLowLightMonitor;
+
+ @Mock
+ Lazy<Set<Condition>> mLazyConditions;
+
+ @Mock
+ private PackageManager mPackageManager;
+
+ @Mock
+ private ComponentName mDreamComponent;
+
+ Condition mCondition = mock(Condition.class);
+ Set<Condition> mConditionSet = Set.of(mCondition);
+
+ @Captor
+ ArgumentCaptor<Monitor.Subscription> mPreconditionsSubscriptionCaptor;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ when(mLowLightDreamManagerLazy.get()).thenReturn(mLowLightDreamManager);
+ when(mLazyConditions.get()).thenReturn(mConditionSet);
+ mLowLightMonitor = new LowLightMonitor(mLowLightDreamManagerLazy,
+ mMonitor, mLazyConditions, mScreenLifecycle, mLogger, mDreamComponent,
+ mPackageManager);
+ }
+
+ @Test
+ public void testSetAmbientLowLightWhenInLowLight() {
+ mLowLightMonitor.onConditionsChanged(true);
+ // Verify setting low light when condition is true
+ verify(mLowLightDreamManager).setAmbientLightMode(AMBIENT_LIGHT_MODE_LOW_LIGHT);
+ }
+
+ @Test
+ public void testExitAmbientLowLightWhenNotInLowLight() {
+ mLowLightMonitor.onConditionsChanged(true);
+ mLowLightMonitor.onConditionsChanged(false);
+ // Verify ambient light toggles back to light mode regular
+ verify(mLowLightDreamManager).setAmbientLightMode(AMBIENT_LIGHT_MODE_REGULAR);
+ }
+
+ @Test
+ public void testStartMonitorLowLightConditionsWhenScreenTurnsOn() {
+ mLowLightMonitor.onScreenTurnedOn();
+
+ // Verify subscribing to low light conditions monitor when screen turns on.
+ verify(mMonitor).addSubscription(any());
+ }
+
+ @Test
+ public void testStopMonitorLowLightConditionsWhenScreenTurnsOff() {
+ final Monitor.Subscription.Token token = mock(Monitor.Subscription.Token.class);
+ when(mMonitor.addSubscription(any())).thenReturn(token);
+ mLowLightMonitor.onScreenTurnedOn();
+
+ // Verify removing subscription when screen turns off.
+ mLowLightMonitor.onScreenTurnedOff();
+ verify(mMonitor).removeSubscription(token);
+ }
+
+ @Test
+ public void testSubscribeToLowLightConditionsOnlyOnceWhenScreenTurnsOn() {
+ final Monitor.Subscription.Token token = mock(Monitor.Subscription.Token.class);
+ when(mMonitor.addSubscription(any())).thenReturn(token);
+
+ mLowLightMonitor.onScreenTurnedOn();
+ mLowLightMonitor.onScreenTurnedOn();
+ // Verify subscription is only added once.
+ verify(mMonitor, times(1)).addSubscription(any());
+ }
+
+ @Test
+ public void testSubscribedToExpectedConditions() {
+ final Monitor.Subscription.Token token = mock(Monitor.Subscription.Token.class);
+ when(mMonitor.addSubscription(any())).thenReturn(token);
+
+ mLowLightMonitor.onScreenTurnedOn();
+ mLowLightMonitor.onScreenTurnedOn();
+ Set<Condition> conditions = captureConditions();
+ // Verify Monitor is subscribed to the expected conditions
+ assertThat(conditions).isEqualTo(mConditionSet);
+ }
+
+ @Test
+ public void testNotUnsubscribeIfNotSubscribedWhenScreenTurnsOff() {
+ mLowLightMonitor.onScreenTurnedOff();
+
+ // Verify doesn't remove subscription since there is none.
+ verify(mMonitor, never()).removeSubscription(any());
+ }
+
+ @Test
+ public void testSubscribeIfScreenIsOnWhenStarting() {
+ when(mScreenLifecycle.getScreenState()).thenReturn(SCREEN_ON);
+ mLowLightMonitor.start();
+ // Verify to add subscription on start if the screen state is on
+ verify(mMonitor, times(1)).addSubscription(any());
+ }
+
+ @Test
+ public void testNoSubscribeIfDreamNotPresent() {
+ LowLightMonitor lowLightMonitor = new LowLightMonitor(mLowLightDreamManagerLazy,
+ mMonitor, mLazyConditions, mScreenLifecycle, mLogger, null, mPackageManager);
+ when(mScreenLifecycle.getScreenState()).thenReturn(SCREEN_ON);
+ lowLightMonitor.start();
+ verify(mScreenLifecycle, never()).addObserver(any());
+ }
+
+ private Set<Condition> captureConditions() {
+ verify(mMonitor).addSubscription(mPreconditionsSubscriptionCaptor.capture());
+ return mPreconditionsSubscriptionCaptor.getValue().getConditions();
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/lowlightclock/ScreenSaverEnabledConditionTest.java b/packages/SystemUI/tests/src/com/android/systemui/lowlightclock/ScreenSaverEnabledConditionTest.java
new file mode 100644
index 000000000000..366c071fb93f
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/lowlightclock/ScreenSaverEnabledConditionTest.java
@@ -0,0 +1,108 @@
+/*
+ * 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.lowlightclock;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.res.Resources;
+import android.database.ContentObserver;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.testing.AndroidTestingRunner;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.util.settings.SecureSettings;
+
+import kotlinx.coroutines.CoroutineScope;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class ScreenSaverEnabledConditionTest extends SysuiTestCase {
+ @Mock
+ private Resources mResources;
+ @Mock
+ private SecureSettings mSecureSettings;
+ @Mock
+ CoroutineScope mScope;
+ @Captor
+ private ArgumentCaptor<ContentObserver> mSettingsObserverCaptor;
+ private ScreenSaverEnabledCondition mCondition;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ // Default dreams to enabled by default
+ doReturn(true).when(mResources).getBoolean(
+ com.android.internal.R.bool.config_dreamsEnabledByDefault);
+
+ mCondition = new ScreenSaverEnabledCondition(mScope, mResources, mSecureSettings);
+ }
+
+ @Test
+ public void testScreenSaverInitiallyEnabled() {
+ setScreenSaverEnabled(true);
+ mCondition.start();
+ assertThat(mCondition.isConditionMet()).isTrue();
+ }
+
+ @Test
+ public void testScreenSaverInitiallyDisabled() {
+ setScreenSaverEnabled(false);
+ mCondition.start();
+ assertThat(mCondition.isConditionMet()).isFalse();
+ }
+
+ @Test
+ public void testScreenSaverStateChanges() {
+ setScreenSaverEnabled(false);
+ mCondition.start();
+ assertThat(mCondition.isConditionMet()).isFalse();
+
+ setScreenSaverEnabled(true);
+ final ContentObserver observer = captureSettingsObserver();
+ observer.onChange(/* selfChange= */ false);
+ assertThat(mCondition.isConditionMet()).isTrue();
+ }
+
+ private void setScreenSaverEnabled(boolean enabled) {
+ when(mSecureSettings.getIntForUser(eq(Settings.Secure.SCREENSAVER_ENABLED), anyInt(),
+ eq(UserHandle.USER_CURRENT))).thenReturn(enabled ? 1 : 0);
+ }
+
+ private ContentObserver captureSettingsObserver() {
+ verify(mSecureSettings).registerContentObserverForUserSync(
+ eq(Settings.Secure.SCREENSAVER_ENABLED),
+ mSettingsObserverCaptor.capture(), eq(UserHandle.USER_CURRENT));
+ return mSettingsObserverCaptor.getValue();
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerImplTest.java
index d59a404b15bb..0924df2538e1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerImplTest.java
@@ -52,7 +52,7 @@ import com.android.systemui.dump.DumpManager;
import com.android.systemui.kosmos.KosmosJavaAdapter;
import com.android.systemui.model.SysUiState;
import com.android.systemui.navigationbar.views.NavigationBar;
-import com.android.systemui.recents.OverviewProxyService;
+import com.android.systemui.recents.LauncherProxyService;
import com.android.systemui.settings.FakeDisplayTracker;
import com.android.systemui.shared.recents.utilities.Utilities;
import com.android.systemui.shared.system.TaskStackChangeListeners;
@@ -109,7 +109,7 @@ public class NavigationBarControllerImplTest extends SysuiTestCase {
MockitoAnnotations.initMocks(this);
mNavigationBarController = spy(
new NavigationBarControllerImpl(mContext,
- mock(OverviewProxyService.class),
+ mock(LauncherProxyService.class),
mock(NavigationModeController.class),
mock(SysUiState.class),
mCommandQueue,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDetailsContentManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDetailsContentManagerTest.kt
index a192446e535b..50b8f37f8d25 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDetailsContentManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDetailsContentManagerTest.kt
@@ -19,6 +19,7 @@ package com.android.systemui.qs.tiles.dialog
import android.content.Intent
import android.os.Handler
import android.os.fakeExecutorHandler
+import android.platform.test.annotations.EnableFlags
import android.telephony.SubscriptionManager
import android.telephony.TelephonyManager
import android.telephony.telephonyManager
@@ -38,8 +39,7 @@ import com.android.internal.logging.UiEventLogger
import com.android.settingslib.wifi.WifiEnterpriseRestrictionUtils
import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
-import com.android.systemui.animation.DialogTransitionAnimator
-import com.android.systemui.flags.setFlagValue
+import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.res.R
import com.android.systemui.statusbar.policy.KeyguardStateController
@@ -62,6 +62,8 @@ import org.mockito.kotlin.whenever
@SmallTest
@RunWith(AndroidJUnit4::class)
@RunWithLooper(setAsMainLooper = true)
+@EnableSceneContainer
+@EnableFlags(Flags.FLAG_QS_TILE_DETAILED_VIEW, Flags.FLAG_DUAL_SHADE)
@UiThreadTest
class InternetDetailsContentManagerTest : SysuiTestCase() {
private val kosmos = Kosmos()
@@ -74,11 +76,8 @@ class InternetDetailsContentManagerTest : SysuiTestCase() {
private val internetDetailsContentController: InternetDetailsContentController =
mock<InternetDetailsContentController>()
private val keyguard: KeyguardStateController = mock<KeyguardStateController>()
- private val dialogTransitionAnimator: DialogTransitionAnimator =
- mock<DialogTransitionAnimator>()
private val bgExecutor = FakeExecutor(FakeSystemClock())
private lateinit var internetDetailsContentManager: InternetDetailsContentManager
- private var subTitle: View? = null
private var ethernet: LinearLayout? = null
private var mobileDataLayout: LinearLayout? = null
private var mobileToggleSwitch: Switch? = null
@@ -96,8 +95,6 @@ class InternetDetailsContentManagerTest : SysuiTestCase() {
@Before
fun setUp() {
- // TODO: b/377388104 enable this flag after integrating with details view.
- mSetFlagsRule.setFlagValue(Flags.FLAG_QS_TILE_DETAILED_VIEW, false)
whenever(telephonyManager.createForSubscriptionId(ArgumentMatchers.anyInt()))
.thenReturn(telephonyManager)
whenever(internetWifiEntry.title).thenReturn(WIFI_TITLE)
@@ -133,9 +130,7 @@ class InternetDetailsContentManagerTest : SysuiTestCase() {
canConfigWifi = true,
coroutineScope = scope,
context = mContext,
- internetDialog = null,
uiEventLogger = mock<UiEventLogger>(),
- dialogTransitionAnimator = dialogTransitionAnimator,
handler = handler,
backgroundExecutor = bgExecutor,
keyguard = keyguard,
@@ -146,7 +141,6 @@ class InternetDetailsContentManagerTest : SysuiTestCase() {
internetDetailsContentManager.connectedWifiEntry = internetWifiEntry
internetDetailsContentManager.wifiEntriesCount = wifiEntries.size
- subTitle = contentView.requireViewById(R.id.internet_dialog_subtitle)
ethernet = contentView.requireViewById(R.id.ethernet_layout)
mobileDataLayout = contentView.requireViewById(R.id.mobile_network_layout)
mobileToggleSwitch = contentView.requireViewById(R.id.mobile_toggle)
@@ -185,32 +179,6 @@ class InternetDetailsContentManagerTest : SysuiTestCase() {
}
@Test
- fun updateContent_withApmOn_internetDialogSubTitleGone() {
- whenever(internetDetailsContentController.isAirplaneModeEnabled).thenReturn(true)
- internetDetailsContentManager.updateContent(true)
- bgExecutor.runAllReady()
-
- internetDetailsContentManager.internetContentData.observe(
- internetDetailsContentManager.lifecycleOwner!!
- ) {
- assertThat(subTitle!!.visibility).isEqualTo(View.VISIBLE)
- }
- }
-
- @Test
- fun updateContent_withApmOff_internetDialogSubTitleVisible() {
- whenever(internetDetailsContentController.isAirplaneModeEnabled).thenReturn(false)
- internetDetailsContentManager.updateContent(true)
- bgExecutor.runAllReady()
-
- internetDetailsContentManager.internetContentData.observe(
- internetDetailsContentManager.lifecycleOwner!!
- ) {
- assertThat(subTitle!!.visibility).isEqualTo(View.VISIBLE)
- }
- }
-
- @Test
fun updateContent_apmOffAndHasEthernet_showEthernet() {
whenever(internetDetailsContentController.isAirplaneModeEnabled).thenReturn(false)
whenever(internetDetailsContentController.hasEthernet()).thenReturn(true)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/recents/LauncherProxyServiceTest.kt
index 4e1ccfb07220..69b762b470b7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/recents/LauncherProxyServiceTest.kt
@@ -40,11 +40,11 @@ import com.android.systemui.model.sceneContainerPlugin
import com.android.systemui.navigationbar.NavigationBarController
import com.android.systemui.navigationbar.NavigationModeController
import com.android.systemui.process.ProcessWrapper
-import com.android.systemui.recents.OverviewProxyService.ACTION_QUICKSTEP
+import com.android.systemui.recents.LauncherProxyService.ACTION_QUICKSTEP
import com.android.systemui.settings.FakeDisplayTracker
import com.android.systemui.settings.UserTracker
import com.android.systemui.shade.ShadeViewController
-import com.android.systemui.shared.recents.IOverviewProxy
+import com.android.systemui.shared.recents.ILauncherProxy
import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_WAKEFULNESS_MASK
import com.android.systemui.shared.system.QuickStepContract.WAKEFULNESS_ASLEEP
import com.android.systemui.shared.system.QuickStepContract.WAKEFULNESS_AWAKE
@@ -83,12 +83,12 @@ import org.mockito.MockitoAnnotations
@SmallTest
@RunWith(AndroidJUnit4::class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
-class OverviewProxyServiceTest : SysuiTestCase() {
+class LauncherProxyServiceTest : SysuiTestCase() {
@Main private val executor: Executor = MoreExecutors.directExecutor()
private val kosmos = testKosmos()
- private lateinit var subject: OverviewProxyService
+ private lateinit var subject: LauncherProxyService
@Mock private val dumpManager = DumpManager()
@Mock private val processWrapper = ProcessWrapper()
private val displayTracker = FakeDisplayTracker(mContext)
@@ -97,10 +97,10 @@ class OverviewProxyServiceTest : SysuiTestCase() {
private val wakefulnessLifecycle =
WakefulnessLifecycle(mContext, null, fakeSystemClock, dumpManager)
- @Mock private lateinit var overviewProxy: IOverviewProxy.Stub
+ @Mock private lateinit var launcherProxy: ILauncherProxy.Stub
@Mock private lateinit var packageManager: PackageManager
- // The following mocks belong to not-yet-tested parts of OverviewProxyService.
+ // The following mocks belong to not-yet-tested parts of LauncherProxyService.
@Mock private lateinit var commandQueue: CommandQueue
@Mock private lateinit var shellInterface: ShellInterface
@Mock private lateinit var navBarController: NavigationBarController
@@ -127,18 +127,18 @@ class OverviewProxyServiceTest : SysuiTestCase() {
MockitoAnnotations.initMocks(this)
val serviceComponent = ComponentName("test_package", "service_provider")
- context.addMockService(serviceComponent, overviewProxy)
+ context.addMockService(serviceComponent, launcherProxy)
context.addMockServiceResolver(
TestableContext.MockServiceResolver {
if (it.action == ACTION_QUICKSTEP) serviceComponent else null
}
)
- whenever(overviewProxy.queryLocalInterface(ArgumentMatchers.anyString()))
- .thenReturn(overviewProxy)
- whenever(overviewProxy.asBinder()).thenReturn(overviewProxy)
+ whenever(launcherProxy.queryLocalInterface(ArgumentMatchers.anyString()))
+ .thenReturn(launcherProxy)
+ whenever(launcherProxy.asBinder()).thenReturn(launcherProxy)
// packageManager.resolveServiceAsUser has to return non-null for
- // OverviewProxyService#isEnabled to become true.
+ // LauncherProxyService#isEnabled to become true.
context.setMockPackageManager(packageManager)
whenever(packageManager.resolveServiceAsUser(any(), anyInt(), anyInt()))
.thenReturn(mock(ResolveInfo::class.java))
@@ -147,7 +147,7 @@ class OverviewProxyServiceTest : SysuiTestCase() {
// return isSystemUser as true by default.
`when`(processWrapper.isSystemUser).thenReturn(true)
- subject = createOverviewProxyService(context)
+ subject = createLauncherProxyService(context)
}
@After
@@ -159,11 +159,11 @@ class OverviewProxyServiceTest : SysuiTestCase() {
fun wakefulnessLifecycle_dispatchFinishedWakingUpSetsSysUIflagToAWAKE() {
// WakefulnessLifecycle is initialized to AWAKE initially, and won't emit a noop.
wakefulnessLifecycle.dispatchFinishedGoingToSleep()
- clearInvocations(overviewProxy)
+ clearInvocations(launcherProxy)
wakefulnessLifecycle.dispatchFinishedWakingUp()
- verify(overviewProxy)
+ verify(launcherProxy)
.onSystemUiStateChanged(
longThat { it and SYSUI_STATE_WAKEFULNESS_MASK == WAKEFULNESS_AWAKE }
)
@@ -173,7 +173,7 @@ class OverviewProxyServiceTest : SysuiTestCase() {
fun wakefulnessLifecycle_dispatchStartedWakingUpSetsSysUIflagToWAKING() {
wakefulnessLifecycle.dispatchStartedWakingUp(PowerManager.WAKE_REASON_UNKNOWN)
- verify(overviewProxy)
+ verify(launcherProxy)
.onSystemUiStateChanged(
longThat { it and SYSUI_STATE_WAKEFULNESS_MASK == WAKEFULNESS_WAKING }
)
@@ -183,7 +183,7 @@ class OverviewProxyServiceTest : SysuiTestCase() {
fun wakefulnessLifecycle_dispatchFinishedGoingToSleepSetsSysUIflagToASLEEP() {
wakefulnessLifecycle.dispatchFinishedGoingToSleep()
- verify(overviewProxy)
+ verify(launcherProxy)
.onSystemUiStateChanged(
longThat { it and SYSUI_STATE_WAKEFULNESS_MASK == WAKEFULNESS_ASLEEP }
)
@@ -195,56 +195,56 @@ class OverviewProxyServiceTest : SysuiTestCase() {
PowerManager.GO_TO_SLEEP_REASON_POWER_BUTTON
)
- verify(overviewProxy)
+ verify(launcherProxy)
.onSystemUiStateChanged(
longThat { it and SYSUI_STATE_WAKEFULNESS_MASK == WAKEFULNESS_GOING_TO_SLEEP }
)
}
@Test
- fun connectToOverviewService_primaryUserNoVisibleBgUsersSupported_expectBindService() {
+ fun connectToLauncherService_primaryUserNoVisibleBgUsersSupported_expectBindService() {
`when`(processWrapper.isSystemUser).thenReturn(true)
`when`(userManager.isVisibleBackgroundUsersSupported()).thenReturn(false)
val spyContext = spy(context)
- val ops = createOverviewProxyService(spyContext)
+ val ops = createLauncherProxyService(spyContext)
ops.startConnectionToCurrentUser()
verify(spyContext, atLeast(1)).bindServiceAsUser(any(), any(), anyInt(), any())
}
@Test
- fun connectToOverviewService_nonPrimaryUserNoVisibleBgUsersSupported_expectNoBindService() {
+ fun connectToLauncherService_nonPrimaryUserNoVisibleBgUsersSupported_expectNoBindService() {
`when`(processWrapper.isSystemUser).thenReturn(false)
`when`(userManager.isVisibleBackgroundUsersSupported()).thenReturn(false)
val spyContext = spy(context)
- val ops = createOverviewProxyService(spyContext)
+ val ops = createLauncherProxyService(spyContext)
ops.startConnectionToCurrentUser()
verify(spyContext, times(0)).bindServiceAsUser(any(), any(), anyInt(), any())
}
@Test
- fun connectToOverviewService_nonPrimaryBgUserVisibleBgUsersSupported_expectBindService() {
+ fun connectToLauncherService_nonPrimaryBgUserVisibleBgUsersSupported_expectBindService() {
`when`(processWrapper.isSystemUser).thenReturn(false)
`when`(userManager.isVisibleBackgroundUsersSupported()).thenReturn(true)
`when`(userManager.isUserForeground()).thenReturn(false)
val spyContext = spy(context)
- val ops = createOverviewProxyService(spyContext)
+ val ops = createLauncherProxyService(spyContext)
ops.startConnectionToCurrentUser()
verify(spyContext, atLeast(1)).bindServiceAsUser(any(), any(), anyInt(), any())
}
@Test
- fun connectToOverviewService_nonPrimaryFgUserVisibleBgUsersSupported_expectNoBindService() {
+ fun connectToLauncherService_nonPrimaryFgUserVisibleBgUsersSupported_expectNoBindService() {
`when`(processWrapper.isSystemUser).thenReturn(false)
`when`(userManager.isVisibleBackgroundUsersSupported()).thenReturn(true)
`when`(userManager.isUserForeground()).thenReturn(true)
val spyContext = spy(context)
- val ops = createOverviewProxyService(spyContext)
+ val ops = createLauncherProxyService(spyContext)
ops.startConnectionToCurrentUser()
verify(spyContext, times(0)).bindServiceAsUser(any(), any(), anyInt(), any())
}
- private fun createOverviewProxyService(ctx: Context): OverviewProxyService {
- return OverviewProxyService(
+ private fun createLauncherProxyService(ctx: Context): LauncherProxyService {
+ return LauncherProxyService(
ctx,
executor,
commandQueue,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
index e68153ad2606..70450d29c74e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
@@ -57,7 +57,10 @@ import com.android.systemui.res.R
import com.android.systemui.settings.brightness.data.repository.BrightnessMirrorShowingRepository
import com.android.systemui.settings.brightness.domain.interactor.BrightnessMirrorShowingInteractor
import com.android.systemui.shade.NotificationShadeWindowView.InteractionEventHandler
+import com.android.systemui.shade.data.repository.ShadeAnimationRepository
+import com.android.systemui.shade.data.repository.ShadeRepositoryImpl
import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor
+import com.android.systemui.shade.domain.interactor.ShadeAnimationInteractorLegacyImpl
import com.android.systemui.statusbar.BlurUtils
import com.android.systemui.statusbar.DragDownHelper
import com.android.systemui.statusbar.LockscreenShadeTransitionController
@@ -219,6 +222,10 @@ class NotificationShadeWindowViewControllerTest(flags: FlagsParameterization) :
notificationShadeDepthController,
view,
shadeViewController,
+ ShadeAnimationInteractorLegacyImpl(
+ ShadeAnimationRepository(),
+ ShadeRepositoryImpl(testScope),
+ ),
panelExpansionInteractor,
ShadeExpansionStateManager(),
stackScrollLayoutController,
@@ -521,6 +528,18 @@ class NotificationShadeWindowViewControllerTest(flags: FlagsParameterization) :
verify(view).findViewById<ViewGroup>(R.id.keyguard_message_area)
}
+ @EnableFlags(Flags.FLAG_SHADE_LAUNCH_ACCESSIBILITY)
+ @Test
+ fun notifiesTheViewWhenLaunchAnimationIsRunning() {
+ testScope.runTest {
+ underTest.setExpandAnimationRunning(true)
+ verify(view).setAnimatingContentLaunch(true)
+
+ underTest.setExpandAnimationRunning(false)
+ verify(view).setAnimatingContentLaunch(false)
+ }
+ }
+
@Test
@DisableSceneContainer
fun setsUpCommunalHubLayout_whenFlagEnabled() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt
index a04ca038021e..9abe9aa5e598 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt
@@ -32,8 +32,8 @@ import com.android.systemui.fragments.FragmentService
import com.android.systemui.navigationbar.NavigationModeController
import com.android.systemui.navigationbar.NavigationModeController.ModeChangedListener
import com.android.systemui.plugins.qs.QS
-import com.android.systemui.recents.OverviewProxyService
-import com.android.systemui.recents.OverviewProxyService.OverviewProxyListener
+import com.android.systemui.recents.LauncherProxyService
+import com.android.systemui.recents.LauncherProxyService.LauncherProxyListener
import com.android.systemui.res.R
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
@@ -71,7 +71,7 @@ class NotificationsQSContainerControllerLegacyTest : SysuiTestCase() {
private val view = mock<NotificationsQuickSettingsContainer>()
private val navigationModeController = mock<NavigationModeController>()
- private val overviewProxyService = mock<OverviewProxyService>()
+ private val mLauncherProxyService = mock<LauncherProxyService>()
private val shadeHeaderController = mock<ShadeHeaderController>()
private val shadeInteractor = mock<ShadeInteractor>()
private val fragmentService = mock<FragmentService>()
@@ -81,7 +81,7 @@ class NotificationsQSContainerControllerLegacyTest : SysuiTestCase() {
private val largeScreenHeaderHelper = mock<LargeScreenHeaderHelper>()
@Captor lateinit var navigationModeCaptor: ArgumentCaptor<ModeChangedListener>
- @Captor lateinit var taskbarVisibilityCaptor: ArgumentCaptor<OverviewProxyListener>
+ @Captor lateinit var taskbarVisibilityCaptor: ArgumentCaptor<LauncherProxyListener>
@Captor lateinit var windowInsetsCallbackCaptor: ArgumentCaptor<Consumer<WindowInsets>>
@Captor lateinit var constraintSetCaptor: ArgumentCaptor<ConstraintSet>
@Captor lateinit var attachStateListenerCaptor: ArgumentCaptor<View.OnAttachStateChangeListener>
@@ -89,7 +89,7 @@ class NotificationsQSContainerControllerLegacyTest : SysuiTestCase() {
lateinit var underTest: NotificationsQSContainerController
private lateinit var navigationModeCallback: ModeChangedListener
- private lateinit var taskbarVisibilityCallback: OverviewProxyListener
+ private lateinit var taskbarVisibilityCallback: LauncherProxyListener
private lateinit var windowInsetsCallback: Consumer<WindowInsets>
private lateinit var fakeSystemClock: FakeSystemClock
private lateinit var delayableExecutor: FakeExecutor
@@ -110,7 +110,7 @@ class NotificationsQSContainerControllerLegacyTest : SysuiTestCase() {
NotificationsQSContainerController(
view,
navigationModeController,
- overviewProxyService,
+ mLauncherProxyService,
shadeHeaderController,
shadeInteractor,
fragmentService,
@@ -127,7 +127,7 @@ class NotificationsQSContainerControllerLegacyTest : SysuiTestCase() {
overrideResource(R.dimen.qs_footer_action_inset, FOOTER_ACTIONS_INSET)
whenever(navigationModeController.addListener(navigationModeCaptor.capture()))
.thenReturn(GESTURES_NAVIGATION)
- doNothing().`when`(overviewProxyService).addCallback(taskbarVisibilityCaptor.capture())
+ doNothing().`when`(mLauncherProxyService).addCallback(taskbarVisibilityCaptor.capture())
doNothing().`when`(view).setInsetsChangedListener(windowInsetsCallbackCaptor.capture())
doNothing().`when`(view).applyConstraints(constraintSetCaptor.capture())
doNothing().`when`(view).addOnAttachStateChangeListener(attachStateListenerCaptor.capture())
@@ -402,7 +402,7 @@ class NotificationsQSContainerControllerLegacyTest : SysuiTestCase() {
NotificationsQSContainerController(
container,
navigationModeController,
- overviewProxyService,
+ mLauncherProxyService,
shadeHeaderController,
shadeInteractor,
fragmentService,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerTest.kt
index 24f8843e935d..4c12cc886e33 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerTest.kt
@@ -32,8 +32,8 @@ import com.android.systemui.fragments.FragmentService
import com.android.systemui.navigationbar.NavigationModeController
import com.android.systemui.navigationbar.NavigationModeController.ModeChangedListener
import com.android.systemui.plugins.qs.QS
-import com.android.systemui.recents.OverviewProxyService
-import com.android.systemui.recents.OverviewProxyService.OverviewProxyListener
+import com.android.systemui.recents.LauncherProxyService
+import com.android.systemui.recents.LauncherProxyService.LauncherProxyListener
import com.android.systemui.res.R
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
@@ -70,7 +70,7 @@ class NotificationsQSContainerControllerTest : SysuiTestCase() {
private val view = mock<NotificationsQuickSettingsContainer>()
private val navigationModeController = mock<NavigationModeController>()
- private val overviewProxyService = mock<OverviewProxyService>()
+ private val mLauncherProxyService = mock<LauncherProxyService>()
private val shadeHeaderController = mock<ShadeHeaderController>()
private val shadeInteractor = mock<ShadeInteractor>()
private val fragmentService = mock<FragmentService>()
@@ -80,7 +80,7 @@ class NotificationsQSContainerControllerTest : SysuiTestCase() {
private val largeScreenHeaderHelper = mock<LargeScreenHeaderHelper>()
@Captor lateinit var navigationModeCaptor: ArgumentCaptor<ModeChangedListener>
- @Captor lateinit var taskbarVisibilityCaptor: ArgumentCaptor<OverviewProxyListener>
+ @Captor lateinit var taskbarVisibilityCaptor: ArgumentCaptor<LauncherProxyListener>
@Captor lateinit var windowInsetsCallbackCaptor: ArgumentCaptor<Consumer<WindowInsets>>
@Captor lateinit var constraintSetCaptor: ArgumentCaptor<ConstraintSet>
@Captor lateinit var attachStateListenerCaptor: ArgumentCaptor<View.OnAttachStateChangeListener>
@@ -88,7 +88,7 @@ class NotificationsQSContainerControllerTest : SysuiTestCase() {
lateinit var underTest: NotificationsQSContainerController
private lateinit var navigationModeCallback: ModeChangedListener
- private lateinit var taskbarVisibilityCallback: OverviewProxyListener
+ private lateinit var taskbarVisibilityCallback: LauncherProxyListener
private lateinit var windowInsetsCallback: Consumer<WindowInsets>
private lateinit var fakeSystemClock: FakeSystemClock
private lateinit var delayableExecutor: FakeExecutor
@@ -110,7 +110,7 @@ class NotificationsQSContainerControllerTest : SysuiTestCase() {
NotificationsQSContainerController(
view,
navigationModeController,
- overviewProxyService,
+ mLauncherProxyService,
shadeHeaderController,
shadeInteractor,
fragmentService,
@@ -127,7 +127,7 @@ class NotificationsQSContainerControllerTest : SysuiTestCase() {
overrideResource(R.dimen.qs_footer_action_inset, FOOTER_ACTIONS_INSET)
whenever(navigationModeController.addListener(navigationModeCaptor.capture()))
.thenReturn(GESTURES_NAVIGATION)
- doNothing().`when`(overviewProxyService).addCallback(taskbarVisibilityCaptor.capture())
+ doNothing().`when`(mLauncherProxyService).addCallback(taskbarVisibilityCaptor.capture())
doNothing().`when`(view).setInsetsChangedListener(windowInsetsCallbackCaptor.capture())
doNothing().`when`(view).applyConstraints(constraintSetCaptor.capture())
doNothing().`when`(view).addOnAttachStateChangeListener(attachStateListenerCaptor.capture())
@@ -457,7 +457,7 @@ class NotificationsQSContainerControllerTest : SysuiTestCase() {
NotificationsQSContainerController(
container,
navigationModeController,
- overviewProxyService,
+ mLauncherProxyService,
shadeHeaderController,
shadeInteractor,
fragmentService,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeHeaderControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeHeaderControllerTest.kt
index eae828562223..e8ab76181af2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeHeaderControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeHeaderControllerTest.kt
@@ -53,6 +53,7 @@ import com.android.systemui.shade.ShadeHeaderController.Companion.QQS_HEADER_CON
import com.android.systemui.shade.ShadeHeaderController.Companion.QS_HEADER_CONSTRAINT
import com.android.systemui.shade.carrier.ShadeCarrierGroup
import com.android.systemui.shade.carrier.ShadeCarrierGroupController
+import com.android.systemui.shade.data.repository.shadeDisplaysRepository
import com.android.systemui.statusbar.data.repository.fakeStatusBarContentInsetsProviderStore
import com.android.systemui.statusbar.phone.StatusIconContainer
import com.android.systemui.statusbar.phone.StatusOverlayHoverListenerFactory
@@ -96,7 +97,7 @@ class ShadeHeaderControllerTest : SysuiTestCase() {
private val kosmos = testKosmos()
private val insetsProviderStore = kosmos.fakeStatusBarContentInsetsProviderStore
- private val insetsProvider = insetsProviderStore.defaultDisplay
+ private val insetsProvider = insetsProviderStore.forDisplay(context.displayId)
@Mock(answer = Answers.RETURNS_MOCKS) private lateinit var view: MotionLayout
@Mock private lateinit var statusIcons: StatusIconContainer
@@ -196,6 +197,7 @@ class ShadeHeaderControllerTest : SysuiTestCase() {
privacyIconsController,
insetsProviderStore,
configurationController,
+ kosmos.shadeDisplaysRepository,
variableDateViewControllerFactory,
batteryMeterViewController,
dumpManager,
@@ -294,7 +296,7 @@ class ShadeHeaderControllerTest : SysuiTestCase() {
verify(clock).setTextAppearance(R.style.TextAppearance_QS_Status)
verify(date).setTextAppearance(R.style.TextAppearance_QS_Status)
- verify(carrierGroup).updateTextAppearance(R.style.TextAppearance_QS_Status_Carriers)
+ verify(carrierGroup).updateTextAppearance(R.style.TextAppearance_QS_Status)
}
@Test
@@ -809,6 +811,43 @@ class ShadeHeaderControllerTest : SysuiTestCase() {
}
@Test
+ fun sameInsetsTwice_listenerCallsOnApplyWindowInsetsOnlyOnce() {
+ val windowInsets = createWindowInsets()
+
+ val captor = ArgumentCaptor.forClass(View.OnApplyWindowInsetsListener::class.java)
+ verify(view).setOnApplyWindowInsetsListener(capture(captor))
+
+ val listener = captor.value
+
+ listener.onApplyWindowInsets(view, windowInsets)
+
+ verify(view, times(1)).onApplyWindowInsets(any())
+
+ listener.onApplyWindowInsets(view, windowInsets)
+
+ verify(view, times(1)).onApplyWindowInsets(any())
+ }
+
+ @Test
+ fun twoDifferentInsets_listenerCallsOnApplyWindowInsetsTwice() {
+ val windowInsets1 = WindowInsets(Rect(1, 2, 3, 4))
+ val windowInsets2 = WindowInsets(Rect(5, 6, 7, 8))
+
+ val captor = ArgumentCaptor.forClass(View.OnApplyWindowInsetsListener::class.java)
+ verify(view).setOnApplyWindowInsetsListener(capture(captor))
+
+ val listener = captor.value
+
+ listener.onApplyWindowInsets(view, windowInsets1)
+
+ verify(view, times(1)).onApplyWindowInsets(any())
+
+ listener.onApplyWindowInsets(view, windowInsets2)
+
+ verify(view, times(2)).onApplyWindowInsets(any())
+ }
+
+ @Test
fun alarmIconNotIgnored() {
verify(statusIcons, Mockito.never())
.addIgnoredSlot(context.getString(com.android.internal.R.string.status_bar_alarm_clock))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
index 5aee92939ed5..493468e8f675 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
@@ -69,6 +69,7 @@ import com.android.systemui.statusbar.notification.FeedbackIcon;
import com.android.systemui.statusbar.notification.SourceType;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.headsup.PinnedStatus;
+import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUiForceExpanded;
import com.android.systemui.statusbar.notification.row.ExpandableView.OnHeightChangedListener;
import com.android.systemui.statusbar.notification.row.wrapper.NotificationViewWrapper;
import com.android.systemui.statusbar.notification.shared.NotificationContentAlphaOptimization;
@@ -878,6 +879,85 @@ public class ExpandableNotificationRowTest extends SysuiTestCase {
}
@Test
+ @EnableFlags(PromotedNotificationUiForceExpanded.FLAG_NAME)
+ public void isExpanded_sensitivePromotedNotification_notExpanded() throws Exception {
+ // GIVEN
+ final ExpandableNotificationRow row = mNotificationTestHelper.createRow();
+ row.setPromotedOngoing(true);
+ row.setSensitive(/* sensitive= */true, /* hideSensitive= */false);
+ row.setHideSensitiveForIntrinsicHeight(/* hideSensitive= */true);
+
+ // THEN
+ assertThat(row.isExpanded()).isFalse();
+ }
+
+ @Test
+ @EnableFlags(PromotedNotificationUiForceExpanded.FLAG_NAME)
+ public void isExpanded_promotedNotificationNotOnKeyguard_expanded() throws Exception {
+ // GIVEN
+ final ExpandableNotificationRow row = mNotificationTestHelper.createRow();
+ row.setPromotedOngoing(true);
+ row.setOnKeyguard(false);
+
+ // THEN
+ assertThat(row.isExpanded()).isTrue();
+ }
+
+ @Test
+ @EnableFlags(PromotedNotificationUiForceExpanded.FLAG_NAME)
+ public void isExpanded_promotedNotificationAllowOnKeyguard_expanded() throws Exception {
+ // GIVEN
+ final ExpandableNotificationRow row = mNotificationTestHelper.createRow();
+ row.setPromotedOngoing(true);
+ row.setOnKeyguard(true);
+
+ // THEN
+ assertThat(row.isExpanded(/* allowOnKeyguard = */ true)).isTrue();
+ }
+
+ @Test
+ @EnableFlags(PromotedNotificationUiForceExpanded.FLAG_NAME)
+ public void isExpanded_promotedNotificationIgnoreLockscreenConstraints_expanded()
+ throws Exception {
+ // GIVEN
+ final ExpandableNotificationRow row = mNotificationTestHelper.createRow();
+ row.setPromotedOngoing(true);
+ row.setOnKeyguard(true);
+ row.setIgnoreLockscreenConstraints(true);
+
+ // THEN
+ assertThat(row.isExpanded()).isTrue();
+ }
+
+ @Test
+ @EnableFlags(PromotedNotificationUiForceExpanded.FLAG_NAME)
+ public void isExpanded_promotedNotificationSaveSpaceOnLockScreen_notExpanded()
+ throws Exception {
+ // GIVEN
+ final ExpandableNotificationRow row = mNotificationTestHelper.createRow();
+ row.setPromotedOngoing(true);
+ row.setOnKeyguard(true);
+ row.setSaveSpaceOnLockscreen(true);
+
+ // THEN
+ assertThat(row.isExpanded()).isFalse();
+ }
+
+ @Test
+ @EnableFlags(PromotedNotificationUiForceExpanded.FLAG_NAME)
+ public void isExpanded_promotedNotificationNotSaveSpaceOnLockScreen_expanded()
+ throws Exception {
+ // GIVEN
+ final ExpandableNotificationRow row = mNotificationTestHelper.createRow();
+ row.setPromotedOngoing(true);
+ row.setOnKeyguard(true);
+ row.setSaveSpaceOnLockscreen(false);
+
+ // THEN
+ assertThat(row.isExpanded()).isTrue();
+ }
+
+ @Test
public void onDisappearAnimationFinished_shouldSetFalse_headsUpAnimatingAway()
throws Exception {
final ExpandableNotificationRow row = mNotificationTestHelper.createRow();
@@ -941,6 +1021,57 @@ public class ExpandableNotificationRowTest extends SysuiTestCase {
assertThat(row.getImageResolver().getContext()).isSameInstanceAs(userContext);
}
+ @Test
+ @EnableFlags(com.android.systemui.Flags.FLAG_NOTIFICATIONS_PINNED_HUN_IN_SHADE)
+ public void mustStayOnScreen_false() throws Exception {
+ final ExpandableNotificationRow row = mNotificationTestHelper.createRow();
+ assertThat(row.mustStayOnScreen()).isFalse();
+ }
+
+ @Test
+ @EnableFlags(com.android.systemui.Flags.FLAG_NOTIFICATIONS_PINNED_HUN_IN_SHADE)
+ public void mustStayOnScreen_isHeadsUp_markedAsSeen() throws Exception {
+ final ExpandableNotificationRow row = mNotificationTestHelper.createRow();
+ // When the row is a HUN
+ row.setHeadsUp(true);
+ //Then it must stay on screen
+ assertThat(row.mustStayOnScreen()).isTrue();
+ // And when the user has seen it
+ row.markHeadsUpSeen();
+ // Then it should NOT stay on screen anymore
+ assertThat(row.mustStayOnScreen()).isFalse();
+ }
+
+ @Test
+ @EnableFlags(com.android.systemui.Flags.FLAG_NOTIFICATIONS_PINNED_HUN_IN_SHADE)
+ public void mustStayOnScreen_isPinned_markedAsSeen() throws Exception {
+ final ExpandableNotificationRow row = mNotificationTestHelper.createRow();
+ // When a HUN is pinned
+ row.setHeadsUp(true);
+ row.setPinnedStatus(PinnedStatus.PinnedBySystem);
+ //Then it must stay on screen
+ assertThat(row.mustStayOnScreen()).isTrue();
+ // And when the user has seen it
+ row.markHeadsUpSeen();
+ // Then it should still stay on screen
+ assertThat(row.mustStayOnScreen()).isTrue();
+ }
+
+ @Test
+ @DisableFlags(com.android.systemui.Flags.FLAG_NOTIFICATIONS_PINNED_HUN_IN_SHADE)
+ public void mustStayOnScreen_isPinned_markedAsSeen_false() throws Exception {
+ final ExpandableNotificationRow row = mNotificationTestHelper.createRow();
+ // When a HUN is pinned
+ row.setHeadsUp(true);
+ row.setPinnedStatus(PinnedStatus.PinnedBySystem);
+ //Then it must stay on screen
+ assertThat(row.mustStayOnScreen()).isTrue();
+ // And when the user has seen it
+ row.markHeadsUpSeen();
+ // Then it should NOT stay on screen anymore
+ assertThat(row.mustStayOnScreen()).isFalse();
+ }
+
private void setDrawableIconsInImageView(CachingIconView icon, Drawable iconDrawable,
Drawable rightIconDrawable) {
ImageView iconView = mock(ImageView.class);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerWithScenesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerWithScenesTest.kt
index 61943f2283e0..8645a40319f4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerWithScenesTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerWithScenesTest.kt
@@ -444,6 +444,7 @@ class NotificationGutsManagerWithScenesTest : SysuiTestCase() {
eq(true), /* wasShownHighPriority */
eq(assistantFeedbackController),
any<MetricsLogger>(),
+ any<View.OnClickListener>(),
)
}
@@ -476,6 +477,7 @@ class NotificationGutsManagerWithScenesTest : SysuiTestCase() {
eq(false), /* wasShownHighPriority */
eq(assistantFeedbackController),
any<MetricsLogger>(),
+ any<View.OnClickListener>(),
)
}
@@ -508,6 +510,7 @@ class NotificationGutsManagerWithScenesTest : SysuiTestCase() {
eq(false), /* wasShownHighPriority */
eq(assistantFeedbackController),
any<MetricsLogger>(),
+ any<View.OnClickListener>(),
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/SingleLineViewBinderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/SingleLineViewBinderTest.kt
index 1eb88c5a5616..0457255fee4e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/SingleLineViewBinderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/SingleLineViewBinderTest.kt
@@ -19,10 +19,12 @@ import android.app.Notification
import android.app.Person
import android.platform.test.annotations.EnableFlags
import android.testing.TestableLooper
+import android.view.View.GONE
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.internal.R
import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.RankingBuilder
import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_PUBLIC_SINGLE_LINE
import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_SINGLE_LINE
import com.android.systemui.statusbar.notification.row.SingleLineViewInflater.inflatePrivateSingleLineView
@@ -90,6 +92,7 @@ class SingleLineViewBinderTest : SysuiTestCase() {
builder = notificationBuilder,
systemUiContext = context,
redactText = false,
+ summarization = null
)
// WHEN: binds the viewHolder
@@ -151,6 +154,7 @@ class SingleLineViewBinderTest : SysuiTestCase() {
builder = notificationBuilder,
systemUiContext = context,
redactText = false,
+ summarization = null
)
// WHEN: binds the view
SingleLineViewBinder.bind(viewModel, view)
@@ -200,6 +204,7 @@ class SingleLineViewBinderTest : SysuiTestCase() {
builder = notificationBuilder,
systemUiContext = context,
redactText = false,
+ summarization = null
)
// WHEN: binds the view with the view model
SingleLineViewBinder.bind(viewModel, view)
@@ -211,6 +216,70 @@ class SingleLineViewBinderTest : SysuiTestCase() {
assertNull(viewModel.conversationData)
}
+ @Test
+ @EnableFlags(AsyncHybridViewInflation.FLAG_NAME, android.app.Flags.FLAG_NM_SUMMARIZATION_UI,
+ android.app.Flags.FLAG_NM_SUMMARIZATION)
+ fun bindSummarizedGroupConversationSingleLineView() {
+ // GIVEN a row with a group conversation notification
+ val user =
+ Person.Builder()
+ .setName(USER_NAME)
+ .build()
+ val style =
+ Notification.MessagingStyle(user)
+ .addMessage(MESSAGE_TEXT, System.currentTimeMillis(), user)
+ .addMessage(
+ "How about lunch?",
+ System.currentTimeMillis(),
+ Person.Builder().setName("user2").build(),
+ )
+ .setGroupConversation(true)
+ notificationBuilder.setStyle(style).setShortcutId(SHORTCUT_ID)
+ val notification = notificationBuilder.build()
+ val row = helper.createRow(notification)
+ val rb = RankingBuilder(row.entry.ranking)
+ rb.setSummarization("summary!")
+ row.entry.ranking = rb.build()
+
+ val view =
+ inflatePrivateSingleLineView(
+ isConversation = true,
+ reinflateFlags = FLAG_CONTENT_VIEW_SINGLE_LINE,
+ entry = row.entry,
+ context = context,
+ logger = mock(),
+ )
+ as HybridConversationNotificationView
+
+ val publicView =
+ inflatePublicSingleLineView(
+ isConversation = true,
+ reinflateFlags = FLAG_CONTENT_VIEW_PUBLIC_SINGLE_LINE,
+ entry = row.entry,
+ context = context,
+ logger = mock(),
+ )
+ as HybridConversationNotificationView
+ assertNotNull(publicView)
+
+ val viewModel =
+ SingleLineViewInflater.inflateSingleLineViewModel(
+ notification = notification,
+ messagingStyle = style,
+ builder = notificationBuilder,
+ systemUiContext = context,
+ redactText = false,
+ summarization = "summary"
+ )
+ // WHEN: binds the view
+ SingleLineViewBinder.bind(viewModel, view)
+
+ // THEN: the single-line conversation view should only include summarization content
+ assertEquals(viewModel.conversationData?.summarization, view.textView.text)
+ assertEquals("", view.conversationSenderNameView.text)
+ assertEquals(GONE, view.conversationSenderNameView.visibility)
+ }
+
private companion object {
const val CHANNEL_ID = "CHANNEL_ID"
const val CONTENT_TITLE = "A Cool New Feature"
@@ -218,5 +287,6 @@ class SingleLineViewBinderTest : SysuiTestCase() {
const val USER_NAME = "USER_NAME"
const val MESSAGE_TEXT = "MESSAGE_TEXT"
const val SHORTCUT_ID = "Shortcut"
+ const val SUMMARIZATION = "summarization"
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/SingleLineViewInflaterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/SingleLineViewInflaterTest.kt
index ef70e277832e..13724a8b44da 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/SingleLineViewInflaterTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/SingleLineViewInflaterTest.kt
@@ -272,6 +272,35 @@ class SingleLineViewInflaterTest : SysuiTestCase() {
}
}
+ @Test
+ fun createViewModelForSummarizedConversationNotification() {
+ // Given: a non-group conversation notification
+ val notificationType = OneToOneConversation()
+ val notification = getNotification(notificationType)
+
+ // When: inflate the SingleLineViewModel
+ val singleLineViewModel = notification.makeSingleLineViewModel(notificationType)
+
+ // Then: the inflated SingleLineViewModel should be as expected
+ // titleText: Notification.ConversationTitle
+ // contentText: the last message text
+ // conversationSenderName: null, because it's not a group conversation
+ // conversationData.avatar: a single icon of the last sender
+ // summarizedText: the summary text from the ranking
+ assertEquals(CONVERSATION_TITLE, singleLineViewModel.titleText)
+ assertEquals(LAST_MESSAGE, singleLineViewModel.contentText)
+ assertNull(
+ singleLineViewModel.conversationData?.conversationSenderName,
+ "Sender name should be null for one-on-one conversation"
+ )
+ assertTrue {
+ singleLineViewModel.conversationData
+ ?.avatar
+ ?.equalsTo(SingleIcon(firstSenderIcon.loadDrawable(context))) == true
+ }
+ assertEquals("summary", singleLineViewModel.conversationData?.summarization)
+ }
+
sealed class NotificationType(val largeIcon: Icon? = null)
class NonMessaging(largeIcon: Icon? = null) : NotificationType(largeIcon)
@@ -380,7 +409,8 @@ class SingleLineViewInflaterTest : SysuiTestCase() {
if (isConversation) messagingStyle else null,
builder,
context,
- false
+ false,
+ "summary"
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
index 437ccb6a9821..68f66611c981 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
@@ -90,6 +90,8 @@ class PhoneStatusBarViewControllerTest : SysuiTestCase() {
private val kosmos = testKosmos()
private val statusBarContentInsetsProviderStore = kosmos.fakeStatusBarContentInsetsProviderStore
private val statusBarContentInsetsProvider = statusBarContentInsetsProviderStore.defaultDisplay
+ private val statusBarContentInsetsProviderForSecondaryDisplay =
+ statusBarContentInsetsProviderStore.forDisplay(SECONDARY_DISPLAY_ID)
private val fakeDarkIconDispatcher = kosmos.fakeDarkIconDispatcher
@Mock private lateinit var shadeViewController: ShadeViewController
@@ -144,6 +146,12 @@ class PhoneStatusBarViewControllerTest : SysuiTestCase() {
controller = createAndInitController(view)
}
+ `when`(
+ statusBarContentInsetsProviderForSecondaryDisplay
+ .getStatusBarContentInsetsForCurrentRotation()
+ )
+ .thenReturn(Insets.NONE)
+
val contextForSecondaryDisplay =
SysuiTestableContext(
mContext.createDisplayContext(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
index a02d333d1507..a7fe1ba76590 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
@@ -59,6 +59,7 @@ import com.android.internal.colorextraction.ColorExtractor.GradientColors;
import com.android.keyguard.BouncerPanelExpansionCalculator;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.DejankUtils;
+import com.android.systemui.Flags;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.animation.ShadeInterpolation;
import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants;
@@ -71,6 +72,7 @@ import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInterac
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.keyguard.ui.transitions.BlurConfig;
import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerToGoneTransitionViewModel;
import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel;
import com.android.systemui.kosmos.KosmosJavaAdapter;
@@ -110,6 +112,9 @@ import java.util.Map;
@RunWith(AndroidJUnit4.class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
@SmallTest
+// TODO(b/381263619) there are more changes and tweaks required to match the new bouncer/shade specs
+// Disabling for now but it will be fixed before the flag is fully ramped up.
+@DisableFlags(Flags.FLAG_BOUNCER_UI_REVAMP)
public class ScrimControllerTest extends SysuiTestCase {
@Rule public Expect mExpect = Expect.create();
@@ -286,7 +291,8 @@ public class ScrimControllerTest extends SysuiTestCase {
mKeyguardTransitionInteractor,
mKeyguardInteractor,
mKosmos.getTestDispatcher(),
- mLinearLargeScreenShadeInterpolator);
+ mLinearLargeScreenShadeInterpolator,
+ new BlurConfig(0.0f, 0.0f));
mScrimController.setScrimVisibleListener(visible -> mScrimVisibility = visible);
mScrimController.attachViews(mScrimBehind, mNotificationsScrim, mScrimInFront);
mScrimController.setAnimatorListener(mAnimatorListener);
@@ -1251,7 +1257,8 @@ public class ScrimControllerTest extends SysuiTestCase {
mKeyguardTransitionInteractor,
mKeyguardInteractor,
mKosmos.getTestDispatcher(),
- mLinearLargeScreenShadeInterpolator);
+ mLinearLargeScreenShadeInterpolator,
+ new BlurConfig(0.0f, 0.0f));
mScrimController.setScrimVisibleListener(visible -> mScrimVisibility = visible);
mScrimController.attachViews(mScrimBehind, mNotificationsScrim, mScrimInFront);
mScrimController.setAnimatorListener(mAnimatorListener);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
index fab7922f58e7..5d88f72b805b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -98,6 +98,7 @@ import android.view.View;
import android.view.ViewTreeObserver;
import android.view.WindowManager;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.test.filters.SmallTest;
@@ -2894,6 +2895,12 @@ public class BubblesTest extends SysuiTestCase {
@Override
public void animateBubbleBarLocation(BubbleBarLocation location) {
}
+
+ @Override
+ public void onDragItemOverBubbleBarDragZone(@NonNull BubbleBarLocation location) {}
+
+ @Override
+ public void onItemDraggedOutsideBubbleBarDropZone() {}
}
private static class FakeBubbleProperties implements BubbleProperties {
diff --git a/packages/SystemUI/tests/utils/src/android/hardware/input/FakeInputManager.kt b/packages/SystemUI/tests/utils/src/android/hardware/input/FakeInputManager.kt
index de4bbecaaf0e..42c509eeaa0b 100644
--- a/packages/SystemUI/tests/utils/src/android/hardware/input/FakeInputManager.kt
+++ b/packages/SystemUI/tests/utils/src/android/hardware/input/FakeInputManager.kt
@@ -16,16 +16,19 @@
package android.hardware.input
+import android.hardware.input.InputGestureData.Trigger
+import android.hardware.input.InputManager.CUSTOM_INPUT_GESTURE_RESULT_ERROR_ALREADY_EXISTS
+import android.hardware.input.InputManager.CUSTOM_INPUT_GESTURE_RESULT_ERROR_DOES_NOT_EXIST
+import android.hardware.input.InputManager.CUSTOM_INPUT_GESTURE_RESULT_SUCCESS
import android.hardware.input.InputManager.InputDeviceListener
import android.view.InputDevice
import android.view.KeyCharacterMap
import android.view.KeyCharacterMap.VIRTUAL_KEYBOARD
import android.view.KeyEvent
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.mock
-import com.android.systemui.util.mockito.whenever
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.invocation.InvocationOnMock
+import org.mockito.kotlin.any
+import org.mockito.kotlin.mock
class FakeInputManager {
@@ -49,36 +52,79 @@ class FakeInputManager {
)
private var inputDeviceListener: InputDeviceListener? = null
+ private val customInputGestures: MutableMap<Trigger, InputGestureData> = mutableMapOf()
+ var addCustomInputGestureErrorCode = CUSTOM_INPUT_GESTURE_RESULT_ERROR_ALREADY_EXISTS
+
+ val inputManager: InputManager = mock {
+ on { getCustomInputGestures(any()) }.then { customInputGestures.values.toList() }
+
+ on { addCustomInputGesture(any()) }
+ .then {
+ val inputGestureData = it.getArgument<InputGestureData>(0)
+ val trigger = inputGestureData.trigger
+
+ if (customInputGestures.containsKey(trigger)) {
+ addCustomInputGestureErrorCode
+ } else {
+ customInputGestures[trigger] = inputGestureData
+ CUSTOM_INPUT_GESTURE_RESULT_SUCCESS
+ }
+ }
+
+ on { removeCustomInputGesture(any()) }
+ .then {
+ val inputGestureData = it.getArgument<InputGestureData>(0)
+ val trigger = inputGestureData.trigger
+
+ if (customInputGestures.containsKey(trigger)) {
+ customInputGestures.remove(trigger)
+ CUSTOM_INPUT_GESTURE_RESULT_SUCCESS
+ } else {
+ CUSTOM_INPUT_GESTURE_RESULT_ERROR_DOES_NOT_EXIST
+ }
+ }
+
+ on { removeAllCustomInputGestures(any()) }.then { customInputGestures.clear() }
- val inputManager =
- mock<InputManager> {
- whenever(getInputDevice(anyInt())).thenAnswer { invocation ->
+ on { getInputGesture(any()) }
+ .then {
+ val trigger = it.getArgument<Trigger>(0)
+ customInputGestures[trigger]
+ }
+
+ on { getInputDevice(anyInt()) }
+ .thenAnswer { invocation ->
val deviceId = invocation.arguments[0] as Int
return@thenAnswer devices[deviceId]
}
- whenever(inputDeviceIds).thenAnswer {
+ on { inputDeviceIds }
+ .thenAnswer {
return@thenAnswer devices.keys.toIntArray()
}
- fun setDeviceEnabled(invocation: InvocationOnMock, enabled: Boolean) {
- val deviceId = invocation.arguments[0] as Int
- val device = devices[deviceId] ?: return
- devices[deviceId] = device.copy(enabled = enabled)
- }
+ fun setDeviceEnabled(invocation: InvocationOnMock, enabled: Boolean) {
+ val deviceId = invocation.arguments[0] as Int
+ val device = devices[deviceId] ?: return
+ devices[deviceId] = device.copy(enabled = enabled)
+ }
- whenever(disableInputDevice(anyInt())).thenAnswer { invocation ->
- setDeviceEnabled(invocation, enabled = false)
- }
- whenever(enableInputDevice(anyInt())).thenAnswer { invocation ->
- setDeviceEnabled(invocation, enabled = true)
- }
- whenever(deviceHasKeys(any(), any())).thenAnswer { invocation ->
+ on { disableInputDevice(anyInt()) }
+ .thenAnswer { invocation -> setDeviceEnabled(invocation, enabled = false) }
+ on { enableInputDevice(anyInt()) }
+ .thenAnswer { invocation -> setDeviceEnabled(invocation, enabled = true) }
+ on { deviceHasKeys(any(), any()) }
+ .thenAnswer { invocation ->
val deviceId = invocation.arguments[0] as Int
val keyCodes = invocation.arguments[1] as IntArray
val supportedKeyCodes = supportedKeyCodesByDeviceId[deviceId]!!
return@thenAnswer keyCodes.map { supportedKeyCodes.contains(it) }.toBooleanArray()
}
- }
+ }
+
+ fun resetCustomInputGestures() {
+ customInputGestures.clear()
+ addCustomInputGestureErrorCode = CUSTOM_INPUT_GESTURE_RESULT_ERROR_ALREADY_EXISTS
+ }
fun addPhysicalKeyboardIfNotPresent(deviceId: Int, enabled: Boolean = true) {
if (devices.containsKey(deviceId)) {
@@ -97,7 +143,7 @@ class FakeInputManager {
vendorId: Int = 0,
productId: Int = 0,
isFullKeyboard: Boolean = true,
- enabled: Boolean = true
+ enabled: Boolean = true,
) {
check(id > 0) { "Physical keyboard ids have to be > 0" }
addKeyboard(id, vendorId, productId, isFullKeyboard, enabled)
@@ -113,7 +159,7 @@ class FakeInputManager {
vendorId: Int = 0,
productId: Int = 0,
isFullKeyboard: Boolean = true,
- enabled: Boolean = true
+ enabled: Boolean = true,
) {
val keyboardType =
if (isFullKeyboard) InputDevice.KEYBOARD_TYPE_ALPHABETIC
@@ -152,7 +198,7 @@ class FakeInputManager {
id: Int = getId(),
type: Int = keyboardType,
sources: Int = getSources(),
- enabled: Boolean = isEnabled
+ enabled: Boolean = isEnabled,
) =
InputDevice.Builder()
.setId(id)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeFocusedDisplayRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeFocusedDisplayRepository.kt
index 83df5d874ad6..ad9370f7ac84 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeFocusedDisplayRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeFocusedDisplayRepository.kt
@@ -33,7 +33,7 @@ class FakeFocusedDisplayRepository @Inject constructor() : FocusedDisplayReposit
override val focusedDisplayId: StateFlow<Int>
get() = flow.asStateFlow()
- suspend fun emit(focusedDisplay: Int) = flow.emit(focusedDisplay)
+ suspend fun setDisplayId(focusedDisplay: Int) = flow.emit(focusedDisplay)
}
@Module
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 93e7f2e588b0..83f4e8f5aa49 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
@@ -25,7 +25,7 @@ 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.recents.OverviewProxyService
+import com.android.systemui.recents.LauncherProxyService
import com.android.systemui.touchpad.data.repository.touchpadRepository
import com.android.systemui.user.data.repository.userRepository
import org.mockito.kotlin.mock
@@ -43,12 +43,12 @@ var Kosmos.keyboardTouchpadEduInteractor by
userRepository,
),
tutorialRepository = tutorialSchedulerRepository,
- overviewProxyService = mockOverviewProxyService,
+ launcherProxyService = mockLauncherProxyService,
metricsLogger = mockEduMetricsLogger,
clock = fakeEduClock,
)
}
var Kosmos.mockEduMetricsLogger by Kosmos.Fixture { mock<ContextualEducationMetricsLogger>() }
-var Kosmos.mockOverviewProxyService by Kosmos.Fixture { mock<OverviewProxyService>() }
+var Kosmos.mockLauncherProxyService by Kosmos.Fixture { mock<LauncherProxyService>() }
var Kosmos.mockEduInputManager by Kosmos.Fixture { mock<InputManager>() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/KeyboardShortcutHelperKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/KeyboardShortcutHelperKosmos.kt
index 0192fa47b434..739f6c2af2b4 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/KeyboardShortcutHelperKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/KeyboardShortcutHelperKosmos.kt
@@ -79,7 +79,7 @@ var Kosmos.shortcutHelperInputShortcutsSource: KeyboardShortcutGroupsSource by
var Kosmos.shortcutHelperCurrentAppShortcutsSource: KeyboardShortcutGroupsSource by
Kosmos.Fixture { CurrentAppShortcutsSource(windowManager) }
-val Kosmos.shortcutHelperAccessibilityShortcutsSource: KeyboardShortcutGroupsSource by
+var Kosmos.shortcutHelperAccessibilityShortcutsSource: KeyboardShortcutGroupsSource by
Kosmos.Fixture { AccessibilityShortcutsSource(mainResources) }
val Kosmos.shortcutHelperExclusions by
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
index 8489d8380041..8ea80081a871 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
@@ -18,6 +18,7 @@
package com.android.systemui.keyguard.data.repository
import android.graphics.Point
+import android.graphics.RectF
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.shared.model.BiometricUnlockMode
import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
@@ -129,6 +130,10 @@ class FakeKeyguardRepository @Inject constructor() : KeyguardRepository {
override val notificationStackAbsoluteBottom: StateFlow<Float>
get() = _notificationStackAbsoluteBottom.asStateFlow()
+ private val _wallpaperFocalAreaBounds = MutableStateFlow(RectF(0f, 0f, 0f, 0f))
+ override val wallpaperFocalAreaBounds: StateFlow<RectF>
+ get() = _wallpaperFocalAreaBounds.asStateFlow()
+
private val _isKeyguardEnabled = MutableStateFlow(true)
override val isKeyguardEnabled: StateFlow<Boolean> = _isKeyguardEnabled.asStateFlow()
@@ -287,6 +292,10 @@ class FakeKeyguardRepository @Inject constructor() : KeyguardRepository {
_notificationStackAbsoluteBottom.value = bottom
}
+ override fun setWallpaperFocalAreaBounds(bounds: RectF) {
+ _wallpaperFocalAreaBounds.value = bounds
+ }
+
override fun setCanIgnoreAuthAndReturnToGone(canWake: Boolean) {
_canIgnoreAuthAndReturnToGone.value = canWake
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt
index f4791003c828..026f8f97d2ae 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt
@@ -124,8 +124,8 @@ class FakeKeyguardTransitionRepository(
/**
* Sends TransitionSteps between [from] and [to], calling [runCurrent] after each step.
*
- * By default, sends steps through FINISHED (STARTED, RUNNING, FINISHED) but can be halted part
- * way using [throughTransitionState].
+ * By default, sends steps through FINISHED (STARTED, RUNNING @0.5f, RUNNING @1f, FINISHED) but
+ * can be halted part way using [throughTransitionState].
*/
suspend fun sendTransitionSteps(
from: KeyguardState,
@@ -137,6 +137,25 @@ class FakeKeyguardTransitionRepository(
}
/**
+ * Sends a STARTED step between [from] and [to], followed by two RUNNING steps at value
+ * [throughValue] / 2 and [throughValue], calling [runCurrent] after each step.
+ */
+ suspend fun sendTransitionStepsThroughRunning(
+ from: KeyguardState,
+ to: KeyguardState,
+ testScope: TestScope,
+ throughValue: Float = 1f,
+ ) {
+ sendTransitionSteps(
+ from,
+ to,
+ testScope.testScheduler,
+ TransitionState.RUNNING,
+ throughValue,
+ )
+ }
+
+ /**
* Sends the provided [step] and makes sure that all previous [TransitionState]'s are sent when
* [fillInSteps] is true. e.g. when a step FINISHED is provided, a step with STARTED and RUNNING
* is also sent.
@@ -178,14 +197,15 @@ class FakeKeyguardTransitionRepository(
/**
* Sends TransitionSteps between [from] and [to], calling [runCurrent] after each step.
*
- * By default, sends steps through FINISHED (STARTED, RUNNING, FINISHED) but can be halted part
- * way using [throughTransitionState].
+ * By default, sends steps through FINISHED (STARTED, RUNNING @0.5f, RUNNING @1f, FINISHED) but
+ * can be halted part way using [throughTransitionState].
*/
suspend fun sendTransitionSteps(
from: KeyguardState,
to: KeyguardState,
testScheduler: TestCoroutineScheduler,
throughTransitionState: TransitionState = TransitionState.FINISHED,
+ throughTransitionValue: Float = 1f,
) {
val lastStep = _transitions.replayCache.lastOrNull()
if (lastStep != null && lastStep.transitionState != TransitionState.FINISHED) {
@@ -216,13 +236,14 @@ class FakeKeyguardTransitionRepository(
throughTransitionState == TransitionState.RUNNING ||
throughTransitionState == TransitionState.FINISHED
) {
+ // Send two steps to better simulate RUNNING transitions.
sendTransitionStep(
step =
TransitionStep(
transitionState = TransitionState.RUNNING,
from = from,
to = to,
- value = 0.5f,
+ value = throughTransitionValue / 2f,
)
)
testScheduler.runCurrent()
@@ -233,7 +254,7 @@ class FakeKeyguardTransitionRepository(
transitionState = TransitionState.RUNNING,
from = from,
to = to,
- value = 1f,
+ value = throughTransitionValue,
)
)
testScheduler.runCurrent()
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/WallpaperFocalAreaInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/WallpaperFocalAreaInteractorKosmos.kt
new file mode 100644
index 000000000000..8fd6f62b315f
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/WallpaperFocalAreaInteractorKosmos.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import android.content.applicationContext
+import com.android.systemui.keyguard.data.repository.keyguardClockRepository
+import com.android.systemui.keyguard.data.repository.keyguardRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.shade.data.repository.shadeRepository
+import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor
+import com.android.systemui.wallpapers.data.repository.wallpaperRepository
+
+val Kosmos.wallpaperFocalAreaInteractor by
+ Kosmos.Fixture {
+ WallpaperFocalAreaInteractor(
+ applicationScope = applicationCoroutineScope,
+ context = applicationContext,
+ keyguardRepository = keyguardRepository,
+ shadeRepository = shadeRepository,
+ activeNotificationsInteractor = activeNotificationsInteractor,
+ keyguardClockRepository = keyguardClockRepository,
+ wallpaperRepository = wallpaperRepository,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderInputEventsViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardMediaViewModelFactoryKosmos.kt
index 2de0e8f76a4b..16d3fdc26613 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderInputEventsViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardMediaViewModelFactoryKosmos.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2024 The Android Open Source Project
+ * Copyright (C) 2025 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,16 +14,17 @@
* limitations under the License.
*/
-package com.android.systemui.volume.dialog.sliders.ui.viewmodel
+package com.android.systemui.keyguard.ui.viewmodel
+import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.kosmos.applicationCoroutineScope
-import com.android.systemui.volume.dialog.sliders.domain.interactor.volumeDialogSliderInputEventsInteractor
+import com.android.systemui.media.controls.domain.pipeline.interactor.mediaCarouselInteractor
-val Kosmos.volumeDialogSliderInputEventsViewModel by
+val Kosmos.keyguardMediaViewModelFactory by
Kosmos.Fixture {
- VolumeDialogSliderInputEventsViewModel(
- applicationCoroutineScope,
- volumeDialogSliderInputEventsInteractor,
- )
+ object : KeyguardMediaViewModel.Factory {
+ override fun create(): KeyguardMediaViewModel {
+ return KeyguardMediaViewModel(mediaCarouselInteractor, keyguardInteractor)
+ }
+ }
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt
index 40b8e0e62b03..37df05b68f9e 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt
@@ -20,6 +20,7 @@ import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
import com.android.systemui.keyguard.domain.interactor.pulseExpansionInteractor
+import com.android.systemui.keyguard.domain.interactor.wallpaperFocalAreaInteractor
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
import com.android.systemui.kosmos.applicationCoroutineScope
@@ -87,5 +88,6 @@ val Kosmos.keyguardRootViewModel by Fixture {
screenOffAnimationController = screenOffAnimationController,
aodBurnInViewModel = aodBurnInViewModel,
shadeInteractor = shadeInteractor,
+ wallpaperFocalAreaInteractor = wallpaperFocalAreaInteractor,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToPrimaryBouncerTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToPrimaryBouncerTransitionViewModelKosmos.kt
index 004f97d95673..c97c4e3ba302 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToPrimaryBouncerTransitionViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToPrimaryBouncerTransitionViewModelKosmos.kt
@@ -25,5 +25,6 @@ val Kosmos.occludedToPrimaryBouncerTransitionViewModel by Fixture {
OccludedToPrimaryBouncerTransitionViewModel(
animationFlow = keyguardTransitionAnimationFlow,
blurConfig = blurConfig,
+ shadeDependentFlows = shadeDependentFlows,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToOccludedTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToOccludedTransitionViewModelKosmos.kt
index 2256c10eebc9..ed5dd454a087 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToOccludedTransitionViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToOccludedTransitionViewModelKosmos.kt
@@ -25,5 +25,6 @@ val Kosmos.primaryBouncerToOccludedTransitionViewModel by Fixture {
PrimaryBouncerToOccludedTransitionViewModel(
animationFlow = keyguardTransitionAnimationFlow,
blurConfig = blurConfig,
+ shadeDependentFlows = shadeDependentFlows,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/FakeVolumeDialogController.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/FakeVolumeDialogController.kt
index 9d73ae3f176f..a1e7d5cede13 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/FakeVolumeDialogController.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/FakeVolumeDialogController.kt
@@ -40,7 +40,8 @@ class FakeVolumeDialogController(private val audioManager: AudioManager) : Volum
private val callbacks = CopyOnWriteArraySet<VolumeDialogController.Callbacks>()
private var hasVibrator: Boolean = true
- private var state = VolumeDialogController.State()
+ var state = VolumeDialogController.State()
+ private set
override fun setActiveStream(stream: Int) {
updateState {
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/ShadeDisplaysRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/ShadeDisplaysRepositoryKosmos.kt
index aaef27d257c5..d9a348d93533 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/ShadeDisplaysRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/ShadeDisplaysRepositoryKosmos.kt
@@ -16,12 +16,15 @@
package com.android.systemui.shade.data.repository
+import com.android.systemui.display.data.repository.FakeFocusedDisplayRepository
import com.android.systemui.display.data.repository.displayRepository
import com.android.systemui.keyguard.data.repository.keyguardRepository
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.testScope
import com.android.systemui.shade.display.AnyExternalShadeDisplayPolicy
import com.android.systemui.shade.display.DefaultDisplayShadePolicy
+import com.android.systemui.shade.display.FakeShadeDisplayPolicy
+import com.android.systemui.shade.display.FocusShadeDisplayPolicy
import com.android.systemui.shade.display.ShadeDisplayPolicy
import com.android.systemui.shade.display.ShadeExpansionIntent
import com.android.systemui.shade.display.StatusBarTouchShadeDisplayPolicy
@@ -46,8 +49,6 @@ val Kosmos.statusBarTouchShadeDisplayPolicy: StatusBarTouchShadeDisplayPolicy by
StatusBarTouchShadeDisplayPolicy(
displayRepository = displayRepository,
backgroundScope = testScope.backgroundScope,
- keyguardRepository = keyguardRepository,
- shadeOnDefaultDisplayWhenLocked = false,
shadeInteractor = { shadeInteractor },
notificationElement = { notificationElement },
qsShadeElement = { qsElement },
@@ -55,13 +56,15 @@ val Kosmos.statusBarTouchShadeDisplayPolicy: StatusBarTouchShadeDisplayPolicy by
}
val Kosmos.shadeExpansionIntent: ShadeExpansionIntent by
Kosmos.Fixture { statusBarTouchShadeDisplayPolicy }
-val Kosmos.shadeDisplaysRepository: MutableShadeDisplaysRepository by
+val Kosmos.shadeDisplaysRepository: ShadeDisplaysRepository by
Kosmos.Fixture {
ShadeDisplaysRepositoryImpl(
bgScope = testScope.backgroundScope,
globalSettings = fakeGlobalSettings,
policies = shadeDisplayPolicies,
defaultPolicy = defaultShadeDisplayPolicy,
+ shadeOnDefaultDisplayWhenLocked = true,
+ keyguardRepository = keyguardRepository,
)
}
@@ -71,8 +74,16 @@ val Kosmos.shadeDisplayPolicies: Set<ShadeDisplayPolicy> by
defaultShadeDisplayPolicy,
anyExternalShadeDisplayPolicy,
statusBarTouchShadeDisplayPolicy,
+ FakeShadeDisplayPolicy,
)
}
val Kosmos.fakeShadeDisplaysRepository: FakeShadeDisplayRepository by
Kosmos.Fixture { FakeShadeDisplayRepository() }
+val Kosmos.fakeFocusedDisplayRepository: FakeFocusedDisplayRepository by
+ Kosmos.Fixture { FakeFocusedDisplayRepository() }
+
+val Kosmos.focusShadeDisplayPolicy: FocusShadeDisplayPolicy by
+ Kosmos.Fixture {
+ FocusShadeDisplayPolicy(focusedDisplayRepository = fakeFocusedDisplayRepository)
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeModeInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeModeInteractorKosmos.kt
index 7892e962d63d..1b50094ec0b7 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeModeInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeModeInteractorKosmos.kt
@@ -16,14 +16,57 @@
package com.android.systemui.shade.domain.interactor
+import android.provider.Settings
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.shade.data.repository.fakeShadeRepository
import com.android.systemui.shade.data.repository.shadeRepository
+import com.android.systemui.shared.settings.data.repository.secureSettingsRepository
+import kotlinx.coroutines.launch
val Kosmos.shadeModeInteractor by Fixture {
ShadeModeInteractorImpl(
applicationScope = applicationCoroutineScope,
repository = shadeRepository,
+ secureSettingsRepository = secureSettingsRepository,
)
}
+
+// TODO(b/391578667): Make this user-aware once supported by FakeSecureSettingsRepository.
+/**
+ * Enables the Dual Shade setting, and (optionally) sets the shade layout to be wide (`true`)
+ * or narrow (`false`).
+ *
+ * In a wide layout, notifications and quick settings shades each take up only half the screen
+ * width. In a narrow layout, they each take up the entire screen width.
+ */
+fun Kosmos.enableDualShade(wideLayout: Boolean? = null) {
+ testScope.launch {
+ secureSettingsRepository.setInt(Settings.Secure.DUAL_SHADE, 1)
+
+ if (wideLayout != null) {
+ fakeShadeRepository.setShadeLayoutWide(wideLayout)
+ }
+ }
+}
+
+// TODO(b/391578667): Make this user-aware once supported by FakeSecureSettingsRepository.
+fun Kosmos.disableDualShade() {
+ testScope.launch { secureSettingsRepository.setInt(Settings.Secure.DUAL_SHADE, 0) }
+}
+
+fun Kosmos.enableSingleShade() {
+ testScope.launch {
+ disableDualShade()
+ fakeShadeRepository.setShadeLayoutWide(false)
+ }
+}
+
+fun Kosmos.enableSplitShade() {
+ testScope.launch {
+ disableDualShade()
+ fakeShadeRepository.setShadeLayoutWide(true)
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/RankingBuilder.java b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/RankingBuilder.java
index 6cd6594c3404..c6daed1aa58f 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/RankingBuilder.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/RankingBuilder.java
@@ -61,6 +61,7 @@ public class RankingBuilder {
private boolean mIsBubble = false;
private int mProposedImportance = IMPORTANCE_UNSPECIFIED;
private boolean mSensitiveContent = false;
+ private String mSummarization = null;
public RankingBuilder() {
}
@@ -92,6 +93,7 @@ public class RankingBuilder {
mIsBubble = ranking.isBubble();
mProposedImportance = ranking.getProposedImportance();
mSensitiveContent = ranking.hasSensitiveContent();
+ mSummarization = ranking.getSummarization();
}
public Ranking build() {
@@ -122,7 +124,8 @@ public class RankingBuilder {
mRankingAdjustment,
mIsBubble,
mProposedImportance,
- mSensitiveContent);
+ mSensitiveContent,
+ mSummarization);
return ranking;
}
@@ -262,6 +265,11 @@ public class RankingBuilder {
return this;
}
+ public RankingBuilder setSummarization(String summary) {
+ mSummarization = summary;
+ return this;
+ }
+
private static <E> ArrayList<E> copyList(List<E> list) {
if (list == null) {
return null;
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/dagger/VolumeDialogSliderComponentKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/dagger/VolumeDialogSliderComponentKosmos.kt
index 36fa82f82f0d..4ca044d60f3f 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/dagger/VolumeDialogSliderComponentKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/dagger/VolumeDialogSliderComponentKosmos.kt
@@ -32,10 +32,8 @@ import com.android.systemui.volume.dialog.data.repository.volumeDialogVisibility
import com.android.systemui.volume.dialog.sliders.domain.model.VolumeDialogSliderType
import com.android.systemui.volume.dialog.sliders.domain.model.volumeDialogSliderType
import com.android.systemui.volume.dialog.sliders.ui.VolumeDialogOverscrollViewBinder
-import com.android.systemui.volume.dialog.sliders.ui.VolumeDialogSliderHapticsViewBinder
import com.android.systemui.volume.dialog.sliders.ui.VolumeDialogSliderViewBinder
import com.android.systemui.volume.dialog.sliders.ui.volumeDialogOverscrollViewBinder
-import com.android.systemui.volume.dialog.sliders.ui.volumeDialogSliderHapticsViewBinder
import com.android.systemui.volume.dialog.sliders.ui.volumeDialogSliderViewBinder
import com.android.systemui.volume.mediaControllerRepository
import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.mediaControllerInteractor
@@ -65,9 +63,6 @@ fun Kosmos.volumeDialogSliderComponent(type: VolumeDialogSliderType): VolumeDial
override fun sliderViewBinder(): VolumeDialogSliderViewBinder =
localKosmos.volumeDialogSliderViewBinder
- override fun sliderHapticsViewBinder(): VolumeDialogSliderHapticsViewBinder =
- localKosmos.volumeDialogSliderHapticsViewBinder
-
override fun overscrollViewBinder(): VolumeDialogOverscrollViewBinder =
localKosmos.volumeDialogOverscrollViewBinder
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinderKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinderKosmos.kt
index c6db717e004f..484a7cc30152 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinderKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinderKosmos.kt
@@ -16,14 +16,16 @@
package com.android.systemui.volume.dialog.sliders.ui
+import com.android.systemui.haptics.slider.sliderHapticsViewModelFactory
import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.volume.dialog.sliders.ui.viewmodel.volumeDialogSliderInputEventsViewModel
+import com.android.systemui.volume.dialog.sliders.ui.viewmodel.volumeDialogOverscrollViewModel
import com.android.systemui.volume.dialog.sliders.ui.viewmodel.volumeDialogSliderViewModel
val Kosmos.volumeDialogSliderViewBinder by
Kosmos.Fixture {
VolumeDialogSliderViewBinder(
volumeDialogSliderViewModel,
- volumeDialogSliderInputEventsViewModel,
+ volumeDialogOverscrollViewModel,
+ sliderHapticsViewModelFactory,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderViewModelKosmos.kt
index b26081c40c38..90bbb28ff519 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderViewModelKosmos.kt
@@ -21,6 +21,7 @@ import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.util.time.systemClock
import com.android.systemui.volume.dialog.domain.interactor.volumeDialogVisibilityInteractor
import com.android.systemui.volume.dialog.shared.volumeDialogLogger
+import com.android.systemui.volume.dialog.sliders.domain.interactor.volumeDialogSliderInputEventsInteractor
import com.android.systemui.volume.dialog.sliders.domain.interactor.volumeDialogSliderInteractor
import com.android.systemui.volume.dialog.sliders.domain.model.volumeDialogSliderType
@@ -29,6 +30,7 @@ val Kosmos.volumeDialogSliderViewModel by
VolumeDialogSliderViewModel(
sliderType = volumeDialogSliderType,
interactor = volumeDialogSliderInteractor,
+ inputEventsInteractor = volumeDialogSliderInputEventsInteractor,
visibilityInteractor = volumeDialogVisibilityInteractor,
coroutineScope = applicationCoroutineScope,
volumeDialogSliderIconProvider = volumeDialogSliderIconProvider,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/wallpapers/data/repository/WallpaperRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/wallpapers/data/repository/WallpaperRepositoryKosmos.kt
index ddb9a3ffee6d..f0c0d30e6db4 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/wallpapers/data/repository/WallpaperRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/wallpapers/data/repository/WallpaperRepositoryKosmos.kt
@@ -19,7 +19,6 @@ package com.android.systemui.wallpapers.data.repository
import android.content.applicationContext
import com.android.app.wallpaperManager
import com.android.systemui.broadcast.broadcastDispatcher
-import com.android.systemui.keyguard.data.repository.keyguardClockRepository
import com.android.systemui.keyguard.data.repository.keyguardRepository
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
@@ -34,8 +33,7 @@ val Kosmos.wallpaperRepository by Fixture {
bgDispatcher = testDispatcher,
broadcastDispatcher = broadcastDispatcher,
userRepository = userRepository,
- wallpaperManager = wallpaperManager,
- keyguardClockRepository = keyguardClockRepository,
keyguardRepository = keyguardRepository,
+ wallpaperManager = wallpaperManager,
)
}
diff --git a/packages/Vcn/framework-b/framework-vcn-jarjar-rules.txt b/packages/Vcn/framework-b/framework-vcn-jarjar-rules.txt
index 7e27b24f749c..33287b7f3449 100644
--- a/packages/Vcn/framework-b/framework-vcn-jarjar-rules.txt
+++ b/packages/Vcn/framework-b/framework-vcn-jarjar-rules.txt
@@ -1,2 +1,3 @@
rule android.net.vcn.persistablebundleutils.** android.net.connectivity.android.net.vcn.persistablebundleutils.@1
-rule android.net.vcn.util.** android.net.connectivity.android.net.vcn.util.@1 \ No newline at end of file
+rule android.net.vcn.util.** android.net.connectivity.android.net.vcn.util.@1
+rule android.util.IndentingPrintWriter android.net.connectivity.android.util.IndentingPrintWriter \ No newline at end of file
diff --git a/packages/Vcn/service-b/src/com/android/server/ConnectivityServiceInitializerB.java b/packages/Vcn/service-b/src/com/android/server/ConnectivityServiceInitializerB.java
index 81c7edf4adf1..b9dcc6160d68 100644
--- a/packages/Vcn/service-b/src/com/android/server/ConnectivityServiceInitializerB.java
+++ b/packages/Vcn/service-b/src/com/android/server/ConnectivityServiceInitializerB.java
@@ -32,8 +32,7 @@ import com.android.tools.r8.keepanno.annotations.UsedByReflection;
// Without this annotation, this class will be treated as unused class and be removed during build
// time.
@UsedByReflection(kind = KeepItemKind.CLASS_AND_METHODS)
-// TODO(b/374174952): Replace VANILLA_ICE_CREAM with BAKLAVA after Android B finalization
-@TargetApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
+@TargetApi(Build.VERSION_CODES.BAKLAVA)
public final class ConnectivityServiceInitializerB extends SystemService {
private static final String TAG = ConnectivityServiceInitializerB.class.getSimpleName();
private final VcnManagementService mVcnManagementService;
diff --git a/packages/Vcn/service-b/src/com/android/server/VcnManagementService.java b/packages/Vcn/service-b/src/com/android/server/VcnManagementService.java
index c9a99d729e91..8edd63dc341f 100644
--- a/packages/Vcn/service-b/src/com/android/server/VcnManagementService.java
+++ b/packages/Vcn/service-b/src/com/android/server/VcnManagementService.java
@@ -165,8 +165,7 @@ import java.util.concurrent.TimeUnit;
* @hide
*/
// TODO(b/180451994): ensure all incoming + outgoing calls have a cleared calling identity
-// TODO(b/374174952): Replace VANILLA_ICE_CREAM with BAKLAVA after Android B finalization
-@TargetApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
+@TargetApi(Build.VERSION_CODES.BAKLAVA)
public class VcnManagementService extends IVcnManagementService.Stub {
@NonNull private static final String TAG = VcnManagementService.class.getSimpleName();
@NonNull private static final String CONTEXT_ATTRIBUTION_TAG = "VCN";
diff --git a/packages/Vcn/service-b/src/com/android/server/vcn/TelephonySubscriptionTracker.java b/packages/Vcn/service-b/src/com/android/server/vcn/TelephonySubscriptionTracker.java
index b04e25dff276..cedb2d16808f 100644
--- a/packages/Vcn/service-b/src/com/android/server/vcn/TelephonySubscriptionTracker.java
+++ b/packages/Vcn/service-b/src/com/android/server/vcn/TelephonySubscriptionTracker.java
@@ -79,8 +79,7 @@ import java.util.Set;
*
* @hide
*/
-// TODO(b/374174952): Replace VANILLA_ICE_CREAM with BAKLAVA after Android B finalization
-@TargetApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
+@TargetApi(Build.VERSION_CODES.BAKLAVA)
public class TelephonySubscriptionTracker extends BroadcastReceiver {
@NonNull private static final String TAG = TelephonySubscriptionTracker.class.getSimpleName();
private static final boolean LOG_DBG = false; // STOPSHIP if true
diff --git a/packages/Vcn/service-b/src/com/android/server/vcn/Vcn.java b/packages/Vcn/service-b/src/com/android/server/vcn/Vcn.java
index 97f86b1bff5b..0f8b2885cf4d 100644
--- a/packages/Vcn/service-b/src/com/android/server/vcn/Vcn.java
+++ b/packages/Vcn/service-b/src/com/android/server/vcn/Vcn.java
@@ -77,8 +77,7 @@ import java.util.Set;
*
* @hide
*/
-// TODO(b/374174952): Replace VANILLA_ICE_CREAM with BAKLAVA after Android B finalization
-@TargetApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
+@TargetApi(Build.VERSION_CODES.BAKLAVA)
public class Vcn extends Handler {
private static final String TAG = Vcn.class.getSimpleName();
diff --git a/packages/Vcn/service-b/src/com/android/server/vcn/VcnGatewayConnection.java b/packages/Vcn/service-b/src/com/android/server/vcn/VcnGatewayConnection.java
index 300b80f942ef..da411174f95c 100644
--- a/packages/Vcn/service-b/src/com/android/server/vcn/VcnGatewayConnection.java
+++ b/packages/Vcn/service-b/src/com/android/server/vcn/VcnGatewayConnection.java
@@ -174,8 +174,7 @@ import java.util.function.Consumer;
*
* @hide
*/
-// TODO(b/374174952): Replace VANILLA_ICE_CREAM with BAKLAVA after Android B finalization
-@TargetApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
+@TargetApi(Build.VERSION_CODES.BAKLAVA)
public class VcnGatewayConnection extends StateMachine {
private static final String TAG = VcnGatewayConnection.class.getSimpleName();
diff --git a/packages/Vcn/service-b/src/com/android/server/vcn/VcnNetworkProvider.java b/packages/Vcn/service-b/src/com/android/server/vcn/VcnNetworkProvider.java
index 38fcf09145d9..bc815eb27454 100644
--- a/packages/Vcn/service-b/src/com/android/server/vcn/VcnNetworkProvider.java
+++ b/packages/Vcn/service-b/src/com/android/server/vcn/VcnNetworkProvider.java
@@ -58,8 +58,7 @@ import java.util.concurrent.Executor;
*/
// TODO(b/388919146): Implement a more generic solution to prevent concurrent modifications on
// mListeners and mRequests
-// TODO(b/374174952): Replace VANILLA_ICE_CREAM with BAKLAVA after Android B finalization
-@TargetApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
+@TargetApi(Build.VERSION_CODES.BAKLAVA)
public class VcnNetworkProvider extends NetworkProvider {
private static final String TAG = VcnNetworkProvider.class.getSimpleName();
diff --git a/packages/Vcn/service-b/src/com/android/server/vcn/routeselection/IpSecPacketLossDetector.java b/packages/Vcn/service-b/src/com/android/server/vcn/routeselection/IpSecPacketLossDetector.java
index c8c645f1276d..aff7068b6c4f 100644
--- a/packages/Vcn/service-b/src/com/android/server/vcn/routeselection/IpSecPacketLossDetector.java
+++ b/packages/Vcn/service-b/src/com/android/server/vcn/routeselection/IpSecPacketLossDetector.java
@@ -61,8 +61,7 @@ import java.util.concurrent.TimeUnit;
*
* <p>This class is flag gated by "network_metric_monitor" and "ipsec_tramsform_state"
*/
-// TODO(b/374174952) Replace VANILLA_ICE_CREAM with BAKLAVA after Android B finalization
-@TargetApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
+@TargetApi(Build.VERSION_CODES.BAKLAVA)
public class IpSecPacketLossDetector extends NetworkMetricMonitor {
private static final String TAG = IpSecPacketLossDetector.class.getSimpleName();
diff --git a/packages/Vcn/service-b/src/com/android/server/vcn/routeselection/NetworkMetricMonitor.java b/packages/Vcn/service-b/src/com/android/server/vcn/routeselection/NetworkMetricMonitor.java
index 55829a5fe978..fc9c7ac8a335 100644
--- a/packages/Vcn/service-b/src/com/android/server/vcn/routeselection/NetworkMetricMonitor.java
+++ b/packages/Vcn/service-b/src/com/android/server/vcn/routeselection/NetworkMetricMonitor.java
@@ -44,8 +44,7 @@ import java.util.concurrent.Executor;
*
* <p>This class is flag gated by "network_metric_monitor"
*/
-// TODO(b/374174952): Replace VANILLA_ICE_CREAM with BAKLAVA after Android B finalization
-@TargetApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
+@TargetApi(Build.VERSION_CODES.BAKLAVA)
public abstract class NetworkMetricMonitor implements AutoCloseable {
private static final String TAG = NetworkMetricMonitor.class.getSimpleName();
diff --git a/packages/Vcn/service-b/src/com/android/server/vcn/routeselection/NetworkPriorityClassifier.java b/packages/Vcn/service-b/src/com/android/server/vcn/routeselection/NetworkPriorityClassifier.java
index 705141f3f1b4..7cb3257193a5 100644
--- a/packages/Vcn/service-b/src/com/android/server/vcn/routeselection/NetworkPriorityClassifier.java
+++ b/packages/Vcn/service-b/src/com/android/server/vcn/routeselection/NetworkPriorityClassifier.java
@@ -52,8 +52,7 @@ import java.util.Map;
import java.util.Set;
/** @hide */
-// TODO(b/374174952): Replace VANILLA_ICE_CREAM with BAKLAVA after Android B finalization
-@TargetApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
+@TargetApi(Build.VERSION_CODES.BAKLAVA)
class NetworkPriorityClassifier {
@NonNull private static final String TAG = NetworkPriorityClassifier.class.getSimpleName();
/**
diff --git a/packages/Vcn/service-b/src/com/android/server/vcn/routeselection/UnderlyingNetworkController.java b/packages/Vcn/service-b/src/com/android/server/vcn/routeselection/UnderlyingNetworkController.java
index bc552e7e6afd..37ec0e8f40dc 100644
--- a/packages/Vcn/service-b/src/com/android/server/vcn/routeselection/UnderlyingNetworkController.java
+++ b/packages/Vcn/service-b/src/com/android/server/vcn/routeselection/UnderlyingNetworkController.java
@@ -75,8 +75,7 @@ import java.util.TreeSet;
*
* @hide
*/
-// TODO(b/374174952): Replace VANILLA_ICE_CREAM with BAKLAVA after Android B finalization
-@TargetApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
+@TargetApi(Build.VERSION_CODES.BAKLAVA)
public class UnderlyingNetworkController {
@NonNull private static final String TAG = UnderlyingNetworkController.class.getSimpleName();
diff --git a/packages/Vcn/service-b/src/com/android/server/vcn/routeselection/UnderlyingNetworkEvaluator.java b/packages/Vcn/service-b/src/com/android/server/vcn/routeselection/UnderlyingNetworkEvaluator.java
index 776931bad73b..164b59f6f0cd 100644
--- a/packages/Vcn/service-b/src/com/android/server/vcn/routeselection/UnderlyingNetworkEvaluator.java
+++ b/packages/Vcn/service-b/src/com/android/server/vcn/routeselection/UnderlyingNetworkEvaluator.java
@@ -52,8 +52,7 @@ import java.util.concurrent.TimeUnit;
*
* @hide
*/
-// TODO(b/374174952): Replace VANILLA_ICE_CREAM with BAKLAVA after Android B finalization
-@TargetApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
+@TargetApi(Build.VERSION_CODES.BAKLAVA)
public class UnderlyingNetworkEvaluator {
private static final String TAG = UnderlyingNetworkEvaluator.class.getSimpleName();
diff --git a/ravenwood/Android.bp b/ravenwood/Android.bp
index 8e998426685b..65550f2b4273 100644
--- a/ravenwood/Android.bp
+++ b/ravenwood/Android.bp
@@ -175,9 +175,9 @@ java_library {
}
java_device_for_host {
- name: "ravenwood-junit-impl-for-ravenizer",
+ name: "ravenwood-junit-for-ravenizer",
libs: [
- "ravenwood-junit-impl",
+ "ravenwood-junit",
],
visibility: [":__subpackages__"],
}
@@ -661,6 +661,9 @@ android_ravenwood_libgroup {
// StatsD
"framework-statsd.ravenwood",
+ // Graphics
+ "framework-graphics.ravenwood",
+
// Provide runtime versions of utils linked in below
"junit",
"truth",
diff --git a/ravenwood/Framework.bp b/ravenwood/Framework.bp
index 99fc31b258e9..71496b0d5766 100644
--- a/ravenwood/Framework.bp
+++ b/ravenwood/Framework.bp
@@ -399,3 +399,55 @@ java_genrule {
"framework-statsd.ravenwood.jar",
],
}
+
+//////////////////////
+// framework-graphics
+//////////////////////
+
+java_genrule {
+ name: "framework-graphics.ravenwood-base",
+ tools: ["hoststubgen"],
+ cmd: "$(location hoststubgen) " +
+ "@$(location :ravenwood-standard-options) " +
+
+ "--debug-log $(location framework-graphics.log) " +
+ "--stats-file $(location framework-graphics_stats.csv) " +
+ "--supported-api-list-file $(location framework-graphics_apis.csv) " +
+ "--gen-keep-all-file $(location framework-graphics_keep_all.txt) " +
+ "--gen-input-dump-file $(location framework-graphics_dump.txt) " +
+
+ "--out-impl-jar $(location ravenwood.jar) " +
+ "--in-jar $(location :framework-graphics.impl{.jar}) " +
+
+ "--policy-override-file $(location :ravenwood-common-policies) ",
+ srcs: [
+ ":framework-graphics.impl{.jar}",
+
+ ":ravenwood-common-policies",
+ ":ravenwood-standard-options",
+ ],
+ out: [
+ "ravenwood.jar",
+
+ // Following files are created just as FYI.
+ "framework-graphics_keep_all.txt",
+ "framework-graphics_dump.txt",
+
+ "framework-graphics.log",
+ "framework-graphics_stats.csv",
+ "framework-graphics_apis.csv",
+ ],
+ visibility: ["//visibility:private"],
+}
+
+java_genrule {
+ name: "framework-graphics.ravenwood",
+ defaults: ["ravenwood-internal-only-visibility-genrule"],
+ cmd: "cp $(in) $(out)",
+ srcs: [
+ ":framework-graphics.ravenwood-base{ravenwood.jar}",
+ ],
+ out: [
+ "framework-graphics.ravenwood.jar",
+ ],
+}
diff --git a/ravenwood/scripts/pta-framework.sh b/ravenwood/scripts/pta-framework.sh
index 224ab59e2e09..46c2c01c8ee8 100755
--- a/ravenwood/scripts/pta-framework.sh
+++ b/ravenwood/scripts/pta-framework.sh
@@ -79,6 +79,7 @@ run_pta() {
$extra_args
if ! [[ -f $OUT_SCRIPT ]] ; then
+ echo "No files need updating."
# no operations generated.
exit 0
fi
@@ -88,4 +89,4 @@ run_pta() {
return 0
}
-run_pta "$extra_args" \ No newline at end of file
+run_pta "$extra_args"
diff --git a/ravenwood/texts/ravenwood-framework-policies.txt b/ravenwood/texts/ravenwood-framework-policies.txt
index 4033782c607e..fff9e6ad41d5 100644
--- a/ravenwood/texts/ravenwood-framework-policies.txt
+++ b/ravenwood/texts/ravenwood-framework-policies.txt
@@ -62,4 +62,4 @@ class android.text.ClipboardManager keep # no-pta
# Just enough to allow ResourcesManager to run
class android.hardware.display.DisplayManagerGlobal keep # no-pta
- method getInstance ()Landroid/hardware/display/DisplayManagerGlobal; ignore
+ method getInstance ()Landroid/hardware/display/DisplayManagerGlobal; ignore # no-pta
diff --git a/ravenwood/texts/ravenwood-standard-options.txt b/ravenwood/texts/ravenwood-standard-options.txt
index 27223d8b72ff..91fd9283aff2 100644
--- a/ravenwood/texts/ravenwood-standard-options.txt
+++ b/ravenwood/texts/ravenwood-standard-options.txt
@@ -31,6 +31,9 @@
--remove-annotation
android.ravenwood.annotation.RavenwoodRemove
+--ignore-annotation
+ android.ravenwood.annotation.RavenwoodIgnore
+
--substitute-annotation
android.ravenwood.annotation.RavenwoodReplace
diff --git a/ravenwood/tools/ravenhelper/src/com/android/platform/test/ravenwood/ravenhelper/policytoannot/Annotations.kt b/ravenwood/tools/ravenhelper/src/com/android/platform/test/ravenwood/ravenhelper/policytoannot/Annotations.kt
index 4a11259a8ef7..ef1cb5dfca89 100644
--- a/ravenwood/tools/ravenhelper/src/com/android/platform/test/ravenwood/ravenhelper/policytoannot/Annotations.kt
+++ b/ravenwood/tools/ravenhelper/src/com/android/platform/test/ravenwood/ravenhelper/policytoannot/Annotations.kt
@@ -45,7 +45,8 @@ class Annotations {
"@android.ravenwood.annotation.RavenwoodRedirect"
FilterPolicy.Throw ->
"@android.ravenwood.annotation.RavenwoodThrow"
- FilterPolicy.Ignore -> null // Ignore has no annotation. (because it's not very safe.)
+ FilterPolicy.Ignore ->
+ "@android.ravenwood.annotation.RavenwoodIgnore"
FilterPolicy.Remove ->
"@android.ravenwood.annotation.RavenwoodRemove"
}
diff --git a/ravenwood/tools/ravenizer/Android.bp b/ravenwood/tools/ravenizer/Android.bp
index 2892d0778ec6..a52a04b44f2d 100644
--- a/ravenwood/tools/ravenizer/Android.bp
+++ b/ravenwood/tools/ravenizer/Android.bp
@@ -19,7 +19,7 @@ java_binary_host {
"ow2-asm-tree",
"ow2-asm-util",
"junit",
- "ravenwood-junit-impl-for-ravenizer",
+ "ravenwood-junit-for-ravenizer",
],
visibility: ["//visibility:public"],
}
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
index 75c629b77700..fda57d6bb986 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
@@ -45,6 +45,7 @@ import android.view.MotionEvent.PointerProperties;
import android.view.accessibility.AccessibilityEvent;
import com.android.server.LocalServices;
+import com.android.server.accessibility.autoclick.AutoclickController;
import com.android.server.accessibility.gestures.TouchExplorer;
import com.android.server.accessibility.magnification.FullScreenMagnificationController;
import com.android.server.accessibility.magnification.FullScreenMagnificationGestureHandler;
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 875b655fe3d2..8e0a7785c597 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -607,7 +607,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
mLock,
mContext,
new MagnificationScaleProvider(mContext),
- Executors.newSingleThreadExecutor()
+ Executors.newSingleThreadExecutor(),
+ mContext.getMainLooper()
);
mMagnificationProcessor = new MagnificationProcessor(mMagnificationController);
mCaptioningManagerImpl = new CaptioningManagerImpl(mContext);
@@ -5084,39 +5085,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
final List<String> permittedServices = dpm.getPermittedAccessibilityServices(userId);
// permittedServices null means all accessibility services are allowed.
- boolean allowed = permittedServices == null || permittedServices.contains(packageName);
- if (allowed) {
- if (android.permission.flags.Flags.enhancedConfirmationModeApisEnabled()
- && android.security.Flags.extendEcmToAllSettings()) {
- try {
- final EnhancedConfirmationManager userContextEcm =
- mContext.createContextAsUser(UserHandle.of(userId), /* flags = */ 0)
- .getSystemService(EnhancedConfirmationManager.class);
- if (userContextEcm != null) {
- return !userContextEcm.isRestricted(packageName,
- AppOpsManager.OPSTR_BIND_ACCESSIBILITY_SERVICE);
- }
- return false;
- } catch (PackageManager.NameNotFoundException e) {
- Log.e(LOG_TAG, "Exception when retrieving package:" + packageName, e);
- return false;
- }
- } else {
- try {
- final int mode = mContext.getSystemService(AppOpsManager.class)
- .noteOpNoThrow(AppOpsManager.OP_ACCESS_RESTRICTED_SETTINGS,
- uid, packageName);
- final boolean ecmEnabled = mContext.getResources().getBoolean(
- com.android.internal.R.bool.config_enhancedConfirmationModeEnabled);
- return !ecmEnabled || mode == AppOpsManager.MODE_ALLOWED
- || mode == AppOpsManager.MODE_DEFAULT;
- } catch (Exception e) {
- // Fallback in case if app ops is not available in testing.
- return false;
- }
- }
- }
- return false;
+ return permittedServices == null || permittedServices.contains(packageName);
} finally {
Binder.restoreCallingIdentity(identity);
}
diff --git a/services/accessibility/java/com/android/server/accessibility/AutoclickController.java b/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickController.java
index 8b758d29a2ac..1bc9c783df76 100644
--- a/services/accessibility/java/com/android/server/accessibility/AutoclickController.java
+++ b/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickController.java
@@ -14,11 +14,14 @@
* limitations under the License.
*/
-package com.android.server.accessibility;
+package com.android.server.accessibility.autoclick;
-import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+import static android.view.MotionEvent.BUTTON_PRIMARY;
+import static android.view.accessibility.AccessibilityManager.AUTOCLICK_CURSOR_AREA_SIZE_DEFAULT;
+import static android.view.accessibility.AccessibilityManager.AUTOCLICK_DELAY_DEFAULT;
+import static android.view.accessibility.AccessibilityManager.AUTOCLICK_IGNORE_MINOR_CURSOR_MOVEMENT_DEFAULT;
-import static com.android.server.accessibility.AutoclickIndicatorView.SHOW_INDICATOR_DELAY_TIME;
+import static com.android.server.accessibility.autoclick.AutoclickIndicatorView.SHOW_INDICATOR_DELAY_TIME;
import android.accessibilityservice.AccessibilityTrace;
import android.annotation.NonNull;
@@ -26,7 +29,6 @@ import android.annotation.Nullable;
import android.content.ContentResolver;
import android.content.Context;
import android.database.ContentObserver;
-import android.graphics.PixelFormat;
import android.net.Uri;
import android.os.Handler;
import android.os.SystemClock;
@@ -37,10 +39,14 @@ import android.view.MotionEvent;
import android.view.MotionEvent.PointerCoords;
import android.view.MotionEvent.PointerProperties;
import android.view.WindowManager;
-import android.view.accessibility.AccessibilityManager;
import androidx.annotation.VisibleForTesting;
+import com.android.internal.accessibility.util.AccessibilityUtils;
+import com.android.server.accessibility.AccessibilityTraceManager;
+import com.android.server.accessibility.BaseEventStreamTransformation;
+import com.android.server.accessibility.Flags;
+
/**
* Implements "Automatically click on mouse stop" feature.
*
@@ -96,8 +102,7 @@ public class AutoclickController extends BaseEventStreamTransformation {
initiateAutoclickIndicator(handler);
}
- mClickScheduler =
- new ClickScheduler(handler, AccessibilityManager.AUTOCLICK_DELAY_DEFAULT);
+ mClickScheduler = new ClickScheduler(handler, AUTOCLICK_DELAY_DEFAULT);
mAutoclickSettingsObserver = new AutoclickSettingsObserver(mUserId, handler);
mAutoclickSettingsObserver.start(
mContext.getContentResolver(),
@@ -117,21 +122,8 @@ public class AutoclickController extends BaseEventStreamTransformation {
mAutoclickIndicatorScheduler = new AutoclickIndicatorScheduler(handler);
mAutoclickIndicatorView = new AutoclickIndicatorView(mContext);
- final WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams();
- layoutParams.type = WindowManager.LayoutParams.TYPE_SECURE_SYSTEM_OVERLAY;
- layoutParams.flags =
- WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
- | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
- | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
- layoutParams.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
- layoutParams.setFitInsetsTypes(0);
- layoutParams.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
- layoutParams.format = PixelFormat.TRANSLUCENT;
- layoutParams.setTitle(AutoclickIndicatorView.class.getSimpleName());
- layoutParams.inputFeatures |= WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL;
-
mWindowManager = mContext.getSystemService(WindowManager.class);
- mWindowManager.addView(mAutoclickIndicatorView, layoutParams);
+ mWindowManager.addView(mAutoclickIndicatorView, mAutoclickIndicatorView.getLayoutParams());
}
@Override
@@ -209,6 +201,11 @@ public class AutoclickController extends BaseEventStreamTransformation {
private final Uri mAutoclickCursorAreaSizeSettingUri =
Settings.Secure.getUriFor(Settings.Secure.ACCESSIBILITY_AUTOCLICK_CURSOR_AREA_SIZE);
+ /** URI used to identify ignore minor cursor movement setting with content resolver. */
+ private final Uri mAutoclickIgnoreMinorCursorMovementSettingUri =
+ Settings.Secure.getUriFor(
+ Settings.Secure.ACCESSIBILITY_AUTOCLICK_IGNORE_MINOR_CURSOR_MOVEMENT);
+
private ContentResolver mContentResolver;
private ClickScheduler mClickScheduler;
private AutoclickIndicatorScheduler mAutoclickIndicatorScheduler;
@@ -247,18 +244,32 @@ public class AutoclickController extends BaseEventStreamTransformation {
mContentResolver = contentResolver;
mClickScheduler = clickScheduler;
mAutoclickIndicatorScheduler = autoclickIndicatorScheduler;
- mContentResolver.registerContentObserver(mAutoclickDelaySettingUri, false, this,
+ mContentResolver.registerContentObserver(
+ mAutoclickDelaySettingUri,
+ /* notifyForDescendants= */ false,
+ /* observer= */ this,
mUserId);
// Initialize mClickScheduler's initial delay value.
- onChange(true, mAutoclickDelaySettingUri);
+ onChange(/* selfChange= */ true, mAutoclickDelaySettingUri);
if (Flags.enableAutoclickIndicator()) {
// Register observer to listen to cursor area size setting change.
mContentResolver.registerContentObserver(
- mAutoclickCursorAreaSizeSettingUri, false, this, mUserId);
+ mAutoclickCursorAreaSizeSettingUri,
+ /* notifyForDescendants= */ false,
+ /* observer= */ this,
+ mUserId);
// Initialize mAutoclickIndicatorView's initial size.
- onChange(true, mAutoclickCursorAreaSizeSettingUri);
+ onChange(/* selfChange= */ true, mAutoclickCursorAreaSizeSettingUri);
+
+ // Register observer to listen to ignore minor cursor movement setting change.
+ mContentResolver.registerContentObserver(
+ mAutoclickIgnoreMinorCursorMovementSettingUri,
+ /* notifyForDescendants= */ false,
+ /* observer= */ this,
+ mUserId);
+ onChange(/* selfChange= */ true, mAutoclickIgnoreMinorCursorMovementSettingUri);
}
}
@@ -279,21 +290,41 @@ public class AutoclickController extends BaseEventStreamTransformation {
@Override
public void onChange(boolean selfChange, Uri uri) {
if (mAutoclickDelaySettingUri.equals(uri)) {
- int delay = Settings.Secure.getIntForUser(
- mContentResolver, Settings.Secure.ACCESSIBILITY_AUTOCLICK_DELAY,
- AccessibilityManager.AUTOCLICK_DELAY_DEFAULT, mUserId);
- mClickScheduler.updateDelay(delay);
- }
- if (Flags.enableAutoclickIndicator()
- && mAutoclickCursorAreaSizeSettingUri.equals(uri)) {
- int size =
+ int delay =
Settings.Secure.getIntForUser(
mContentResolver,
- Settings.Secure.ACCESSIBILITY_AUTOCLICK_CURSOR_AREA_SIZE,
- AccessibilityManager.AUTOCLICK_CURSOR_AREA_SIZE_DEFAULT,
+ Settings.Secure.ACCESSIBILITY_AUTOCLICK_DELAY,
+ AUTOCLICK_DELAY_DEFAULT,
mUserId);
- if (mAutoclickIndicatorScheduler != null) {
- mAutoclickIndicatorScheduler.updateCursorAreaSize(size);
+ mClickScheduler.updateDelay(delay);
+ }
+
+ if (Flags.enableAutoclickIndicator()) {
+ if (mAutoclickCursorAreaSizeSettingUri.equals(uri)) {
+ int size =
+ Settings.Secure.getIntForUser(
+ mContentResolver,
+ Settings.Secure.ACCESSIBILITY_AUTOCLICK_CURSOR_AREA_SIZE,
+ AUTOCLICK_CURSOR_AREA_SIZE_DEFAULT,
+ mUserId);
+ if (mAutoclickIndicatorScheduler != null) {
+ mAutoclickIndicatorScheduler.updateCursorAreaSize(size);
+ }
+ mClickScheduler.updateMovementSlope(size);
+ }
+
+ if (mAutoclickIgnoreMinorCursorMovementSettingUri.equals(uri)) {
+ boolean ignoreMinorCursorMovement =
+ Settings.Secure.getIntForUser(
+ mContentResolver,
+ Settings.Secure
+ .ACCESSIBILITY_AUTOCLICK_IGNORE_MINOR_CURSOR_MOVEMENT,
+ AUTOCLICK_IGNORE_MINOR_CURSOR_MOVEMENT_DEFAULT
+ ? AccessibilityUtils.State.ON
+ : AccessibilityUtils.State.OFF,
+ mUserId)
+ == AccessibilityUtils.State.ON;
+ mClickScheduler.setIgnoreMinorCursorMovement(ignoreMinorCursorMovement);
}
}
}
@@ -365,11 +396,16 @@ public class AutoclickController extends BaseEventStreamTransformation {
@VisibleForTesting
final class ClickScheduler implements Runnable {
/**
- * Minimal distance pointer has to move relative to anchor in order for movement not to be
- * discarded as noise. Anchor is the position of the last MOVE event that was not considered
- * noise.
+ * Default minimal distance pointer has to move relative to anchor in order for movement not
+ * to be discarded as noise. Anchor is the position of the last MOVE event that was not
+ * considered noise.
*/
- private static final double MOVEMENT_SLOPE = 20f;
+ private static final double DEFAULT_MOVEMENT_SLOPE = 20f;
+
+ private double mMovementSlope = DEFAULT_MOVEMENT_SLOPE;
+
+ /** Whether the minor cursor movement should be ignored. */
+ private boolean mIgnoreMinorCursorMovement = AUTOCLICK_IGNORE_MINOR_CURSOR_MOVEMENT_DEFAULT;
/** Whether there is pending click. */
private boolean mActive;
@@ -553,7 +589,19 @@ public class AutoclickController extends BaseEventStreamTransformation {
float deltaX = mAnchorCoords.x - event.getX(pointerIndex);
float deltaY = mAnchorCoords.y - event.getY(pointerIndex);
double delta = Math.hypot(deltaX, deltaY);
- return delta > MOVEMENT_SLOPE;
+ double slope =
+ ((Flags.enableAutoclickIndicator() && mIgnoreMinorCursorMovement)
+ ? mMovementSlope
+ : DEFAULT_MOVEMENT_SLOPE);
+ return delta > slope;
+ }
+
+ public void setIgnoreMinorCursorMovement(boolean ignoreMinorCursorMovement) {
+ mIgnoreMinorCursorMovement = ignoreMinorCursorMovement;
+ }
+
+ private void updateMovementSlope(double slope) {
+ mMovementSlope = slope;
}
/**
@@ -581,18 +629,30 @@ public class AutoclickController extends BaseEventStreamTransformation {
final long now = SystemClock.uptimeMillis();
- MotionEvent downEvent = MotionEvent.obtain(now, now, MotionEvent.ACTION_DOWN, 1,
- mTempPointerProperties, mTempPointerCoords, mMetaState,
- MotionEvent.BUTTON_PRIMARY, 1.0f, 1.0f, mLastMotionEvent.getDeviceId(), 0,
- mLastMotionEvent.getSource(), mLastMotionEvent.getFlags());
+ MotionEvent downEvent =
+ MotionEvent.obtain(
+ /* downTime= */ now,
+ /* eventTime= */ now,
+ MotionEvent.ACTION_DOWN,
+ /* pointerCount= */ 1,
+ mTempPointerProperties,
+ mTempPointerCoords,
+ mMetaState,
+ BUTTON_PRIMARY,
+ /* xPrecision= */ 1.0f,
+ /* yPrecision= */ 1.0f,
+ mLastMotionEvent.getDeviceId(),
+ /* edgeFlags= */ 0,
+ mLastMotionEvent.getSource(),
+ mLastMotionEvent.getFlags());
MotionEvent pressEvent = MotionEvent.obtain(downEvent);
pressEvent.setAction(MotionEvent.ACTION_BUTTON_PRESS);
- pressEvent.setActionButton(MotionEvent.BUTTON_PRIMARY);
+ pressEvent.setActionButton(BUTTON_PRIMARY);
MotionEvent releaseEvent = MotionEvent.obtain(downEvent);
releaseEvent.setAction(MotionEvent.ACTION_BUTTON_RELEASE);
- releaseEvent.setActionButton(MotionEvent.BUTTON_PRIMARY);
+ releaseEvent.setActionButton(BUTTON_PRIMARY);
releaseEvent.setButtonState(0);
MotionEvent upEvent = MotionEvent.obtain(downEvent);
diff --git a/services/accessibility/java/com/android/server/accessibility/AutoclickIndicatorView.java b/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickIndicatorView.java
index f87dcdb200bb..557e1b2afcd5 100644
--- a/services/accessibility/java/com/android/server/accessibility/AutoclickIndicatorView.java
+++ b/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickIndicatorView.java
@@ -14,17 +14,21 @@
* limitations under the License.
*/
-package com.android.server.accessibility;
+package com.android.server.accessibility.autoclick;
+import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
import static android.view.accessibility.AccessibilityManager.AUTOCLICK_CURSOR_AREA_SIZE_DEFAULT;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
+import android.graphics.PixelFormat;
import android.graphics.RectF;
import android.util.DisplayMetrics;
import android.view.View;
+import android.view.WindowInsets;
+import android.view.WindowManager;
import android.view.accessibility.AccessibilityManager;
import android.view.animation.LinearInterpolator;
@@ -81,6 +85,26 @@ public class AutoclickIndicatorView extends View {
mRingRect = new RectF();
}
+ /**
+ * Retrieves the layout params for AutoclickIndicatorView, used when it's added to the Window
+ * Manager.
+ */
+ public final WindowManager.LayoutParams getLayoutParams() {
+ final WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams();
+ layoutParams.type = WindowManager.LayoutParams.TYPE_SECURE_SYSTEM_OVERLAY;
+ layoutParams.flags =
+ WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
+ | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+ | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
+ layoutParams.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
+ layoutParams.setFitInsetsTypes(WindowInsets.Type.statusBars());
+ layoutParams.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+ layoutParams.format = PixelFormat.TRANSLUCENT;
+ layoutParams.setTitle(AutoclickIndicatorView.class.getSimpleName());
+ layoutParams.inputFeatures |= WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL;
+ return layoutParams;
+ }
+
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java
index 75ec8ea88ace..486f1f449691 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java
@@ -36,6 +36,8 @@ import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.Region;
import android.hardware.display.DisplayManager;
+import android.os.Handler;
+import android.os.Looper;
import android.os.SystemClock;
import android.os.UserHandle;
import android.provider.Settings;
@@ -53,6 +55,7 @@ import android.view.accessibility.MagnificationAnimationCallback;
import com.android.internal.accessibility.util.AccessibilityStatsLogUtils;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.function.pooled.PooledLambda;
import com.android.server.LocalServices;
import com.android.server.accessibility.AccessibilityManagerService;
import com.android.server.wm.WindowManagerInternal;
@@ -111,6 +114,20 @@ public class MagnificationController implements MagnificationConnectionManager.C
private final Executor mBackgroundExecutor;
+ private final Handler mHandler;
+ private @PanDirection int mActivePanDirection = PAN_DIRECTION_DOWN;
+ private int mActivePanDisplay = Display.INVALID_DISPLAY;
+ private boolean mRepeatKeysEnabled = true;
+
+ private @ZoomDirection int mActiveZoomDirection = ZOOM_DIRECTION_IN;
+ private int mActiveZoomDisplay = Display.INVALID_DISPLAY;
+
+ // TODO(b/355499907): Get initial repeat interval from repeat keys settings.
+ @VisibleForTesting
+ public static final int INITIAL_KEYBOARD_REPEAT_INTERVAL_MS = 500;
+ @VisibleForTesting
+ public static final int KEYBOARD_REPEAT_INTERVAL_MS = 60;
+
@GuardedBy("mLock")
private final SparseIntArray mCurrentMagnificationModeArray = new SparseIntArray();
@GuardedBy("mLock")
@@ -287,12 +304,13 @@ public class MagnificationController implements MagnificationConnectionManager.C
public MagnificationController(AccessibilityManagerService ams, Object lock,
Context context, MagnificationScaleProvider scaleProvider,
- Executor backgroundExecutor) {
+ Executor backgroundExecutor, Looper looper) {
mAms = ams;
mLock = lock;
mContext = context;
mScaleProvider = scaleProvider;
mBackgroundExecutor = backgroundExecutor;
+ mHandler = new Handler(looper);
LocalServices.getService(WindowManagerInternal.class)
.getAccessibilityController().setUiChangesForAccessibilityCallbacks(this);
mSupportWindowMagnification = context.getPackageManager().hasSystemFeature(
@@ -303,14 +321,20 @@ public class MagnificationController implements MagnificationConnectionManager.C
mAlwaysOnMagnificationFeatureFlag = new AlwaysOnMagnificationFeatureFlag(context);
mAlwaysOnMagnificationFeatureFlag.addOnChangedListener(
mBackgroundExecutor, mAms::updateAlwaysOnMagnification);
+
+ // TODO(b/355499907): Add an observer for repeat keys enabled changes,
+ // rather than initializing once at startup.
+ mRepeatKeysEnabled = Settings.Secure.getIntForUser(
+ mContext.getContentResolver(), Settings.Secure.KEY_REPEAT_ENABLED, 1,
+ UserHandle.USER_CURRENT) != 0;
}
@VisibleForTesting
public MagnificationController(AccessibilityManagerService ams, Object lock,
Context context, FullScreenMagnificationController fullScreenMagnificationController,
MagnificationConnectionManager magnificationConnectionManager,
- MagnificationScaleProvider scaleProvider, Executor backgroundExecutor) {
- this(ams, lock, context, scaleProvider, backgroundExecutor);
+ MagnificationScaleProvider scaleProvider, Executor backgroundExecutor, Looper looper) {
+ this(ams, lock, context, scaleProvider, backgroundExecutor, looper);
mFullScreenMagnificationController = fullScreenMagnificationController;
mMagnificationConnectionManager = magnificationConnectionManager;
}
@@ -354,27 +378,60 @@ public class MagnificationController implements MagnificationConnectionManager.C
// pan diagonally) by decreasing diagonal movement by sqrt(2) to make it appear the same
// speed as non-diagonal movement.
panMagnificationByStep(displayId, direction);
+ mActivePanDirection = direction;
+ mActivePanDisplay = displayId;
+ if (mRepeatKeysEnabled) {
+ mHandler.sendMessageDelayed(
+ PooledLambda.obtainMessage(MagnificationController::maybeContinuePan, this),
+ INITIAL_KEYBOARD_REPEAT_INTERVAL_MS);
+ }
}
@Override
public void onPanMagnificationStop(int displayId,
@MagnificationController.PanDirection int direction) {
- // TODO(b/388847283): Handle held key gestures, which can be used
- // for continuous scaling and panning, until they are released.
-
+ if (direction == mActivePanDirection) {
+ mActivePanDisplay = Display.INVALID_DISPLAY;
+ }
}
@Override
public void onScaleMagnificationStart(int displayId,
@MagnificationController.ZoomDirection int direction) {
scaleMagnificationByStep(displayId, direction);
+ mActiveZoomDirection = direction;
+ mActiveZoomDisplay = displayId;
+ if (mRepeatKeysEnabled) {
+ mHandler.sendMessageDelayed(
+ PooledLambda.obtainMessage(MagnificationController::maybeContinueZoom, this),
+ INITIAL_KEYBOARD_REPEAT_INTERVAL_MS);
+ }
}
@Override
public void onScaleMagnificationStop(int displayId,
@MagnificationController.ZoomDirection int direction) {
- // TODO(b/388847283): Handle held key gestures, which can be used
- // for continuous scaling and panning, until they are released.
+ if (direction == mActiveZoomDirection) {
+ mActiveZoomDisplay = Display.INVALID_DISPLAY;
+ }
+ }
+
+ private void maybeContinuePan() {
+ if (mActivePanDisplay != Display.INVALID_DISPLAY) {
+ panMagnificationByStep(mActivePanDisplay, mActivePanDirection);
+ mHandler.sendMessageDelayed(
+ PooledLambda.obtainMessage(MagnificationController::maybeContinuePan, this),
+ KEYBOARD_REPEAT_INTERVAL_MS);
+ }
+ }
+
+ private void maybeContinueZoom() {
+ if (mActiveZoomDisplay != Display.INVALID_DISPLAY) {
+ scaleMagnificationByStep(mActiveZoomDisplay, mActiveZoomDirection);
+ mHandler.sendMessageDelayed(
+ PooledLambda.obtainMessage(MagnificationController::maybeContinueZoom, this),
+ KEYBOARD_REPEAT_INTERVAL_MS);
+ }
}
private void handleUserInteractionChanged(int displayId, int mode) {
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index a37b2b926c9c..02a8f6218468 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -612,7 +612,7 @@ public class CompanionDeviceManagerService extends SystemService {
@Override
public void enablePermissionsSync(int associationId) {
- if (UserHandle.getAppId(Binder.getCallingUid()) == SYSTEM_UID) {
+ if (UserHandle.getAppId(Binder.getCallingUid()) != SYSTEM_UID) {
throw new SecurityException("Caller must be system UID");
}
mSystemDataTransferProcessor.enablePermissionsSync(associationId);
@@ -620,7 +620,7 @@ public class CompanionDeviceManagerService extends SystemService {
@Override
public void disablePermissionsSync(int associationId) {
- if (UserHandle.getAppId(Binder.getCallingUid()) == SYSTEM_UID) {
+ if (UserHandle.getAppId(Binder.getCallingUid()) != SYSTEM_UID) {
throw new SecurityException("Caller must be system UID");
}
mSystemDataTransferProcessor.disablePermissionsSync(associationId);
@@ -628,7 +628,7 @@ public class CompanionDeviceManagerService extends SystemService {
@Override
public PermissionSyncRequest getPermissionSyncRequest(int associationId) {
- if (UserHandle.getAppId(Binder.getCallingUid()) == SYSTEM_UID) {
+ if (UserHandle.getAppId(Binder.getCallingUid()) != SYSTEM_UID) {
throw new SecurityException("Caller must be system UID");
}
return mSystemDataTransferProcessor.getPermissionSyncRequest(associationId);
@@ -704,7 +704,7 @@ public class CompanionDeviceManagerService extends SystemService {
@Override
public byte[] getBackupPayload(int userId) {
- if (UserHandle.getAppId(Binder.getCallingUid()) == SYSTEM_UID) {
+ if (UserHandle.getAppId(Binder.getCallingUid()) != SYSTEM_UID) {
throw new SecurityException("Caller must be system");
}
return mBackupRestoreProcessor.getBackupPayload(userId);
@@ -712,7 +712,7 @@ public class CompanionDeviceManagerService extends SystemService {
@Override
public void applyRestoredPayload(byte[] payload, int userId) {
- if (UserHandle.getAppId(Binder.getCallingUid()) == SYSTEM_UID) {
+ if (UserHandle.getAppId(Binder.getCallingUid()) != SYSTEM_UID) {
throw new SecurityException("Caller must be system");
}
mBackupRestoreProcessor.applyRestoredPayload(payload, userId);
diff --git a/services/core/java/com/android/server/MasterClearReceiver.java b/services/core/java/com/android/server/MasterClearReceiver.java
index 2b30c013235c..6f2ecd82a79c 100644
--- a/services/core/java/com/android/server/MasterClearReceiver.java
+++ b/services/core/java/com/android/server/MasterClearReceiver.java
@@ -130,7 +130,7 @@ public class MasterClearReceiver extends BroadcastReceiver {
if (mWipeExternalStorage) {
// thr will be started at the end of this task.
Slog.i(TAG, "Wiping external storage on async task");
- new WipeDataTask(context, thr).execute();
+ new WipeDataTask(context, thr).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
} else {
Slog.i(TAG, "NOT wiping external storage; starting thread " + thr.getName());
thr.start();
diff --git a/services/core/java/com/android/server/am/BroadcastController.java b/services/core/java/com/android/server/am/BroadcastController.java
index bfacfbba4e22..bec5db79ff9d 100644
--- a/services/core/java/com/android/server/am/BroadcastController.java
+++ b/services/core/java/com/android/server/am/BroadcastController.java
@@ -317,7 +317,7 @@ class BroadcastController {
Slog.w(TAG, "registerReceiverWithFeature: no app for " + caller);
return null;
}
- if (callerApp.info.uid != SYSTEM_UID
+ if (!UserHandle.isCore(callerApp.info.uid)
&& !callerApp.getPkgList().containsKey(callerPackage)) {
throw new SecurityException("Given caller package " + callerPackage
+ " is not running in process " + callerApp);
diff --git a/services/core/java/com/android/server/am/PendingIntentRecord.java b/services/core/java/com/android/server/am/PendingIntentRecord.java
index 0b7890167c08..2c0366e6a6db 100644
--- a/services/core/java/com/android/server/am/PendingIntentRecord.java
+++ b/services/core/java/com/android/server/am/PendingIntentRecord.java
@@ -24,11 +24,14 @@ import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_COMPAT;
import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED;
import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED;
import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOW_IF_VISIBLE;
+import static android.os.PowerWhitelistManager.TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED;
+import static android.os.PowerWhitelistManager.TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_NOT_ALLOWED;
import static android.os.Process.ROOT_UID;
import static android.os.Process.SYSTEM_UID;
import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.window.flags.Flags.balClearAllowlistDuration;
import android.annotation.IntDef;
import android.annotation.Nullable;
@@ -327,10 +330,11 @@ public final class PendingIntentRecord extends IIntentSender.Stub {
mAllowBgActivityStartsForActivitySender.remove(token);
mAllowBgActivityStartsForBroadcastSender.remove(token);
mAllowBgActivityStartsForServiceSender.remove(token);
- if (mAllowlistDuration != null) {
- mAllowlistDuration.remove(token);
- if (mAllowlistDuration.isEmpty()) {
- mAllowlistDuration = null;
+ if (mAllowlistDuration != null && balClearAllowlistDuration()) {
+ TempAllowListDuration duration = mAllowlistDuration.get(token);
+ if (duration != null
+ && duration.type == TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED) {
+ duration.type = TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_NOT_ALLOWED;
}
}
}
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index 8e09e3b8e112..833599810210 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -604,7 +604,7 @@ public class AppOpsService extends IAppOpsService.Stub {
}
}
- /** Returned from {@link #verifyAndGetBypass(int, String, String, String, boolean)}. */
+ /** Returned from {@link #verifyAndGetBypass(int, String, String, int, String, boolean)}. */
private static final class PackageVerificationResult {
final RestrictionBypass bypass;
@@ -3087,10 +3087,10 @@ public class AppOpsService extends IAppOpsService.Stub {
public int checkPackage(int uid, String packageName) {
Objects.requireNonNull(packageName);
try {
- verifyAndGetBypass(uid, packageName, null, null, true);
+ verifyAndGetBypass(uid, packageName, null, Process.INVALID_UID, null, true);
// When the caller is the system, it's possible that the packageName is the special
// one (e.g., "root") which isn't actually existed.
- if (resolveUid(packageName) == uid
+ if (resolveNonAppUid(packageName) == uid
|| (isPackageExisted(packageName)
&& !filterAppAccessUnlocked(packageName, UserHandle.getUserId(uid)))) {
return AppOpsManager.MODE_ALLOWED;
@@ -3306,7 +3306,7 @@ public class AppOpsService extends IAppOpsService.Stub {
boolean shouldCollectMessage, int notedCount) {
PackageVerificationResult pvr;
try {
- pvr = verifyAndGetBypass(uid, packageName, attributionTag, proxyPackageName);
+ pvr = verifyAndGetBypass(uid, packageName, attributionTag, proxyUid, proxyPackageName);
if (!pvr.isAttributionTagValid) {
attributionTag = null;
}
@@ -3930,7 +3930,7 @@ public class AppOpsService extends IAppOpsService.Stub {
// Test if the proxied operation will succeed before starting the proxy operation
final SyncNotedAppOp testProxiedOp = startOperationDryRun(code,
proxiedUid, resolvedProxiedPackageName, proxiedAttributionTag,
- proxiedVirtualDeviceId, resolvedProxyPackageName, proxiedFlags,
+ proxiedVirtualDeviceId, proxyUid, resolvedProxyPackageName, proxiedFlags,
startIfModeDefault);
if (!shouldStartForMode(testProxiedOp.getOpMode(), startIfModeDefault)) {
@@ -3970,7 +3970,7 @@ public class AppOpsService extends IAppOpsService.Stub {
int attributionChainId) {
PackageVerificationResult pvr;
try {
- pvr = verifyAndGetBypass(uid, packageName, attributionTag, proxyPackageName);
+ pvr = verifyAndGetBypass(uid, packageName, attributionTag, proxyUid, proxyPackageName);
if (!pvr.isAttributionTagValid) {
attributionTag = null;
}
@@ -4097,11 +4097,11 @@ public class AppOpsService extends IAppOpsService.Stub {
*/
private SyncNotedAppOp startOperationDryRun(int code, int uid,
@NonNull String packageName, @Nullable String attributionTag, int virtualDeviceId,
- String proxyPackageName, @OpFlags int flags,
+ int proxyUid, String proxyPackageName, @OpFlags int flags,
boolean startIfModeDefault) {
PackageVerificationResult pvr;
try {
- pvr = verifyAndGetBypass(uid, packageName, attributionTag, proxyPackageName);
+ pvr = verifyAndGetBypass(uid, packageName, attributionTag, proxyUid, proxyPackageName);
if (!pvr.isAttributionTagValid) {
attributionTag = null;
}
@@ -4656,13 +4656,17 @@ public class AppOpsService extends IAppOpsService.Stub {
private boolean isSpecialPackage(int callingUid, @Nullable String packageName) {
final String resolvedPackage = AppOpsManager.resolvePackageName(callingUid, packageName);
return callingUid == Process.SYSTEM_UID
- || resolveUid(resolvedPackage) != Process.INVALID_UID;
+ || resolveNonAppUid(resolvedPackage) != Process.INVALID_UID;
}
private boolean isCallerAndAttributionTrusted(@NonNull AttributionSource attributionSource) {
if (attributionSource.getUid() != Binder.getCallingUid()
&& attributionSource.isTrusted(mContext)) {
- return true;
+ // if there is a next attribution source, it must be trusted, as well.
+ if (attributionSource.getNext() == null
+ || attributionSource.getNext().isTrusted(mContext)) {
+ return true;
+ }
}
return mContext.checkPermission(android.Manifest.permission.UPDATE_APP_OPS_STATS,
Binder.getCallingPid(), Binder.getCallingUid(), null)
@@ -4757,19 +4761,20 @@ public class AppOpsService extends IAppOpsService.Stub {
}
/**
- * @see #verifyAndGetBypass(int, String, String, String, boolean)
+ * @see #verifyAndGetBypass(int, String, String, int, String, boolean)
*/
private @NonNull PackageVerificationResult verifyAndGetBypass(int uid, String packageName,
@Nullable String attributionTag) {
- return verifyAndGetBypass(uid, packageName, attributionTag, null);
+ return verifyAndGetBypass(uid, packageName, attributionTag, Process.INVALID_UID, null);
}
/**
- * @see #verifyAndGetBypass(int, String, String, String, boolean)
+ * @see #verifyAndGetBypass(int, String, String, int, String, boolean)
*/
private @NonNull PackageVerificationResult verifyAndGetBypass(int uid, String packageName,
- @Nullable String attributionTag, @Nullable String proxyPackageName) {
- return verifyAndGetBypass(uid, packageName, attributionTag, proxyPackageName, false);
+ @Nullable String attributionTag, int proxyUid, @Nullable String proxyPackageName) {
+ return verifyAndGetBypass(uid, packageName, attributionTag, proxyUid, proxyPackageName,
+ false);
}
/**
@@ -4780,14 +4785,15 @@ public class AppOpsService extends IAppOpsService.Stub {
* @param uid The uid the package belongs to
* @param packageName The package the might belong to the uid
* @param attributionTag attribution tag or {@code null} if no need to verify
- * @param proxyPackageName The proxy package, from which the attribution tag is to be pulled
+ * @param proxyUid The proxy uid, from which the attribution tag is to be pulled
+ * @param proxyPackageName The proxy package, from which the attribution tag may be pulled
* @param suppressErrorLogs Whether to print to logcat about nonmatching parameters
*
* @return PackageVerificationResult containing {@link RestrictionBypass} and whether the
* attribution tag is valid
*/
private @NonNull PackageVerificationResult verifyAndGetBypass(int uid, String packageName,
- @Nullable String attributionTag, @Nullable String proxyPackageName,
+ @Nullable String attributionTag, int proxyUid, @Nullable String proxyPackageName,
boolean suppressErrorLogs) {
if (uid == Process.ROOT_UID) {
// For backwards compatibility, don't check package name for root UID.
@@ -4831,34 +4837,47 @@ public class AppOpsService extends IAppOpsService.Stub {
int callingUid = Binder.getCallingUid();
- // Allow any attribution tag for resolvable uids
- int pkgUid;
+ // Allow any attribution tag for resolvable, non-app uids
+ int nonAppUid;
if (Objects.equals(packageName, "com.android.shell")) {
// Special case for the shell which is a package but should be able
// to bypass app attribution tag restrictions.
- pkgUid = Process.SHELL_UID;
+ nonAppUid = Process.SHELL_UID;
} else {
- pkgUid = resolveUid(packageName);
+ nonAppUid = resolveNonAppUid(packageName);
}
- if (pkgUid != Process.INVALID_UID) {
- if (pkgUid != UserHandle.getAppId(uid)) {
+ if (nonAppUid != Process.INVALID_UID) {
+ if (nonAppUid != UserHandle.getAppId(uid)) {
if (!suppressErrorLogs) {
Slog.e(TAG, "Bad call made by uid " + callingUid + ". "
- + "Package \"" + packageName + "\" does not belong to uid " + uid
- + ".");
+ + "Package \"" + packageName + "\" does not belong to uid " + uid
+ + ".");
+ }
+ String otherUidMessage =
+ DEBUG ? " but it is really " + nonAppUid : " but it is not";
+ throw new SecurityException("Specified package \"" + packageName
+ + "\" under uid " + UserHandle.getAppId(uid) + otherUidMessage);
+ }
+ // We only allow bypassing the attribution tag verification if the proxy is a
+ // system app (or is null), in order to prevent abusive apps clogging the appops
+ // system with unlimited attribution tags via proxy calls.
+ boolean proxyIsSystemAppOrNull = true;
+ if (proxyPackageName != null) {
+ int proxyAppId = UserHandle.getAppId(proxyUid);
+ if (proxyAppId >= Process.FIRST_APPLICATION_UID) {
+ proxyIsSystemAppOrNull =
+ mPackageManagerInternal.isSystemPackage(proxyPackageName);
}
- String otherUidMessage = DEBUG ? " but it is really " + pkgUid : " but it is not";
- throw new SecurityException("Specified package \"" + packageName + "\" under uid "
- + UserHandle.getAppId(uid) + otherUidMessage);
}
return new PackageVerificationResult(RestrictionBypass.UNRESTRICTED,
- /* isAttributionTagValid */ true);
+ /* isAttributionTagValid */ proxyIsSystemAppOrNull);
}
int userId = UserHandle.getUserId(uid);
RestrictionBypass bypass = null;
boolean isAttributionTagValid = false;
+ int pkgUid = nonAppUid;
final long ident = Binder.clearCallingIdentity();
try {
PackageManagerInternal pmInt = LocalServices.getService(PackageManagerInternal.class);
@@ -5649,7 +5668,7 @@ public class AppOpsService extends IAppOpsService.Stub {
if (nonpackageUid != -1) {
packageName = null;
} else {
- packageUid = resolveUid(packageName);
+ packageUid = resolveNonAppUid(packageName);
if (packageUid < 0) {
packageUid = AppGlobals.getPackageManager().getPackageUid(packageName,
PackageManager.MATCH_UNINSTALLED_PACKAGES, userId);
@@ -6749,7 +6768,13 @@ public class AppOpsService extends IAppOpsService.Stub {
if (restricted && attrOp.isRunning()) {
attrOp.pause();
} else if (attrOp.isPaused()) {
- attrOp.resume();
+ RestrictionBypass bypass = verifyAndGetBypass(uid, ops.packageName, attrOp.tag)
+ .bypass;
+ if (!isOpRestrictedLocked(uid, code, ops.packageName, attrOp.tag,
+ Context.DEVICE_ID_DEFAULT, bypass, false)) {
+ // Only resume if there are no other restrictions remaining on this op
+ attrOp.resume();
+ }
}
}
}
@@ -7198,7 +7223,7 @@ public class AppOpsService extends IAppOpsService.Stub {
}
}
- private static int resolveUid(String packageName) {
+ private static int resolveNonAppUid(String packageName) {
if (packageName == null) {
return Process.INVALID_UID;
}
diff --git a/services/core/java/com/android/server/audio/AudioManagerShellCommand.java b/services/core/java/com/android/server/audio/AudioManagerShellCommand.java
index 030ce12f5063..fece7a899f0a 100644
--- a/services/core/java/com/android/server/audio/AudioManagerShellCommand.java
+++ b/services/core/java/com/android/server/audio/AudioManagerShellCommand.java
@@ -65,6 +65,12 @@ class AudioManagerShellCommand extends ShellCommand {
return setRingerMode();
case "set-volume":
return setVolume();
+ case "get-min-volume":
+ return getMinVolume();
+ case "get-max-volume":
+ return getMaxVolume();
+ case "get-stream-volume":
+ return getStreamVolume();
case "set-device-volume":
return setDeviceVolume();
case "adj-mute":
@@ -106,6 +112,12 @@ class AudioManagerShellCommand extends ShellCommand {
pw.println(" Sets the Ringer mode to one of NORMAL|SILENT|VIBRATE");
pw.println(" set-volume STREAM_TYPE VOLUME_INDEX");
pw.println(" Sets the volume for STREAM_TYPE to VOLUME_INDEX");
+ pw.println(" get-min-volume STREAM_TYPE");
+ pw.println(" Gets the min volume for STREAM_TYPE");
+ pw.println(" get-max-volume STREAM_TYPE");
+ pw.println(" Gets the max volume for STREAM_TYPE");
+ pw.println(" get-stream-volume STREAM_TYPE");
+ pw.println(" Gets the volume for STREAM_TYPE");
pw.println(" set-device-volume STREAM_TYPE VOLUME_INDEX NATIVE_DEVICE_TYPE");
pw.println(" Sets for NATIVE_DEVICE_TYPE the STREAM_TYPE volume to VOLUME_INDEX");
pw.println(" adj-mute STREAM_TYPE");
@@ -296,6 +308,33 @@ class AudioManagerShellCommand extends ShellCommand {
return 0;
}
+ private int getMinVolume() {
+ final Context context = mService.mContext;
+ final AudioManager am = context.getSystemService(AudioManager.class);
+ final int stream = readIntArg();
+ final int result = am.getStreamMinVolume(stream);
+ getOutPrintWriter().println("AudioManager.getStreamMinVolume(" + stream + ") -> " + result);
+ return 0;
+ }
+
+ private int getMaxVolume() {
+ final Context context = mService.mContext;
+ final AudioManager am = context.getSystemService(AudioManager.class);
+ final int stream = readIntArg();
+ final int result = am.getStreamMaxVolume(stream);
+ getOutPrintWriter().println("AudioManager.getStreamMaxVolume(" + stream + ") -> " + result);
+ return 0;
+ }
+
+ private int getStreamVolume() {
+ final Context context = mService.mContext;
+ final AudioManager am = context.getSystemService(AudioManager.class);
+ final int stream = readIntArg();
+ final int result = am.getStreamVolume(stream);
+ getOutPrintWriter().println("AudioManager.getStreamVolume(" + stream + ") -> " + result);
+ return 0;
+ }
+
private int setDeviceVolume() {
final Context context = mService.mContext;
final AudioDeviceVolumeManager advm = (AudioDeviceVolumeManager) context.getSystemService(
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 709c13bc9704..320dd8f188c0 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -47,9 +47,10 @@ import static android.media.AudioManager.RINGER_MODE_NORMAL;
import static android.media.AudioManager.RINGER_MODE_SILENT;
import static android.media.AudioManager.RINGER_MODE_VIBRATE;
import static android.media.AudioManager.STREAM_SYSTEM;
-import static android.media.IAudioManagerNative.HardeningType;
import static android.media.audio.Flags.autoPublicVolumeApiHardening;
import static android.media.audio.Flags.automaticBtDeviceType;
+import static android.media.audio.Flags.cacheGetStreamMinMaxVolume;
+import static android.media.audio.Flags.cacheGetStreamVolume;
import static android.media.audio.Flags.concurrentAudioRecordBypassPermission;
import static android.media.audio.Flags.featureSpatialAudioHeadtrackingLowLatency;
import static android.media.audio.Flags.focusFreezeTestApi;
@@ -898,6 +899,16 @@ public class AudioService extends IAudioService.Stub
public void permissionUpdateBarrier() {
AudioService.this.permissionUpdateBarrier();
}
+
+ /**
+ * Update mute state event for port
+ * @param portId Port id to update
+ * @param event the mute event containing info about the mute
+ */
+ @Override
+ public void portMuteEvent(int portId, int event) {
+ mPlaybackMonitor.portMuteEvent(portId, event, Binder.getCallingUid());
+ }
};
// List of binder death handlers for setMode() client processes.
@@ -1900,6 +1911,12 @@ public class AudioService extends IAudioService.Stub
mSpatializerHelper.onRoutingUpdated();
}
checkMuteAwaitConnection();
+ if (cacheGetStreamVolume()) {
+ if (DEBUG_VOL) {
+ Log.d(TAG, "Clear volume cache after routing update");
+ }
+ AudioManager.clearVolumeCache(AudioManager.VOLUME_CACHING_API);
+ }
}
//-----------------------------------------------------------------
@@ -4976,6 +4993,10 @@ public class AudioService extends IAudioService.Stub
+ ringMyCar());
pw.println("\tandroid.media.audio.Flags.concurrentAudioRecordBypassPermission:"
+ concurrentAudioRecordBypassPermission());
+ pw.println("\tandroid.media.audio.Flags.cacheGetStreamMinMaxVolume:"
+ + cacheGetStreamMinMaxVolume());
+ pw.println("\tandroid.media.audio.Flags.cacheGetStreamVolume:"
+ + cacheGetStreamVolume());
}
private void dumpAudioMode(PrintWriter pw) {
@@ -7042,6 +7063,13 @@ public class AudioService extends IAudioService.Stub
streamState.mIsMuted = false;
}
}
+ if (cacheGetStreamVolume()) {
+ if (DEBUG_VOL) {
+ Log.d(TAG,
+ "Clear volume cache after possibly changing mute in readAudioSettings");
+ }
+ AudioManager.clearVolumeCache(AudioManager.VOLUME_CACHING_API);
+ }
}
readVolumeGroupsSettings(userSwitch);
@@ -9262,11 +9290,23 @@ public class AudioService extends IAudioService.Stub
public void put(int key, int value) {
super.put(key, value);
record("put", key, value);
+ if (cacheGetStreamVolume()) {
+ if (DEBUG_VOL) {
+ Log.d(TAG, "Clear volume cache after update index map");
+ }
+ AudioManager.clearVolumeCache(AudioManager.VOLUME_CACHING_API);
+ }
}
@Override
public void setValueAt(int index, int value) {
super.setValueAt(index, value);
record("setValueAt", keyAt(index), value);
+ if (cacheGetStreamVolume()) {
+ if (DEBUG_VOL) {
+ Log.d(TAG, "Clear volume cache after update index map");
+ }
+ AudioManager.clearVolumeCache(AudioManager.VOLUME_CACHING_API);
+ }
}
// Record all changes in the VolumeStreamState
@@ -9366,6 +9406,12 @@ public class AudioService extends IAudioService.Stub
mIndexMinNoPerm = mIndexMin;
}
}
+ if (cacheGetStreamMinMaxVolume() && mStreamType == AudioSystem.STREAM_VOICE_CALL) {
+ if (DEBUG_VOL) {
+ Log.d(TAG, "Clear min volume cache from updateIndexFactors");
+ }
+ AudioManager.clearVolumeCache(AudioManager.VOLUME_MIN_CACHING_API);
+ }
final int status = AudioSystem.initStreamVolume(
mStreamType, indexMinVolCurve, indexMaxVolCurve);
@@ -9403,11 +9449,19 @@ public class AudioService extends IAudioService.Stub
* @param index minimum index expressed in "UI units", i.e. no 10x factor
*/
public void updateNoPermMinIndex(int index) {
+ boolean changedNoPermMinIndex =
+ cacheGetStreamMinMaxVolume() && (index * 10) != mIndexMinNoPerm;
mIndexMinNoPerm = index * 10;
if (mIndexMinNoPerm < mIndexMin) {
Log.e(TAG, "Invalid mIndexMinNoPerm for stream " + mStreamType);
mIndexMinNoPerm = mIndexMin;
}
+ if (changedNoPermMinIndex) {
+ if (DEBUG_VOL) {
+ Log.d(TAG, "Clear min volume cache from updateNoPermMinIndex");
+ }
+ AudioManager.clearVolumeCache(AudioManager.VOLUME_MIN_CACHING_API);
+ }
}
/**
@@ -9982,8 +10036,9 @@ public class AudioService extends IAudioService.Stub
* @return true if the mute state was changed
*/
public boolean mute(boolean state, boolean apply, String src) {
+ boolean changed;
synchronized (VolumeStreamState.class) {
- boolean changed = state != mIsMuted;
+ changed = state != mIsMuted;
if (changed) {
sMuteLogger.enqueue(
new AudioServiceEvents.StreamMuteEvent(mStreamType, state, src));
@@ -10001,8 +10056,16 @@ public class AudioService extends IAudioService.Stub
doMute();
}
}
- return changed;
}
+
+ if (cacheGetStreamVolume() && changed) {
+ if (DEBUG_VOL) {
+ Log.d(TAG, "Clear volume cache after changing mute state");
+ }
+ AudioManager.clearVolumeCache(AudioManager.VOLUME_CACHING_API);
+ }
+
+ return changed;
}
public void doMute() {
diff --git a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
index e2e06b63c7d6..57b5febf4df0 100644
--- a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
+++ b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
@@ -435,6 +435,49 @@ public final class PlaybackActivityMonitor
/**
* Update event for port
* @param portId Port id to update
+ * @param event the mute event containing info about the mute
+ * @param binderUid Calling binder uid
+ */
+ public void portMuteEvent(int portId, @PlayerMuteEvent int event, int binderUid) {
+ if (!UserHandle.isCore(binderUid)) {
+ Log.e(TAG, "Forbidden operation from uid " + binderUid);
+ return;
+ }
+
+ synchronized (mPlayerLock) {
+ int piid;
+ if (portToPiidSimplification()) {
+ int idxOfPiid = mPiidToPortId.indexOfValue(portId);
+ if (idxOfPiid < 0) {
+ Log.w(TAG, "No piid assigned for invalid/internal port id " + portId);
+ return;
+ }
+ piid = mPiidToPortId.keyAt(idxOfPiid);
+ } else {
+ piid = mPortIdToPiid.get(portId, PLAYER_PIID_INVALID);
+ if (piid == PLAYER_PIID_INVALID) {
+ Log.w(TAG, "No piid assigned for invalid/internal port id " + portId);
+ return;
+ }
+ }
+ final AudioPlaybackConfiguration apc = mPlayers.get(piid);
+ if (apc == null) {
+ Log.w(TAG, "No AudioPlaybackConfiguration assigned for piid " + piid);
+ return;
+ }
+
+ if (apc.getPlayerType()
+ == AudioPlaybackConfiguration.PLAYER_TYPE_JAM_SOUNDPOOL) {
+ // FIXME SoundPool not ready for state reporting
+ return;
+ }
+ mEventHandler.sendMessage(
+ mEventHandler.obtainMessage(MSG_IIL_UPDATE_PLAYER_MUTED_EVENT, piid, event, null));
+ }
+ }
+ /**
+ * Update event for port
+ * @param portId Port id to update
* @param event The new port event
* @param extras The values associated with this event
* @param binderUid Calling binder uid
@@ -479,15 +522,10 @@ public final class PlaybackActivityMonitor
return;
}
- if (event == AudioPlaybackConfiguration.PLAYER_UPDATE_MUTED) {
- mEventHandler.sendMessage(
- mEventHandler.obtainMessage(MSG_IIL_UPDATE_PLAYER_MUTED_EVENT, piid,
- portId,
- extras));
- } else if (event == AudioPlaybackConfiguration.PLAYER_UPDATE_FORMAT) {
+ if (event == AudioPlaybackConfiguration.PLAYER_UPDATE_FORMAT) {
mEventHandler.sendMessage(
mEventHandler.obtainMessage(MSG_IIL_UPDATE_PLAYER_FORMAT, piid,
- portId,
+ -1,
extras));
}
}
@@ -1695,9 +1733,7 @@ public final class PlaybackActivityMonitor
* event for player getting muted
* args:
* msg.arg1: piid
- * msg.arg2: port id
- * msg.obj: extras describing the mute reason
- * type: PersistableBundle
+ * msg.arg2: mute reason
*/
private static final int MSG_IIL_UPDATE_PLAYER_MUTED_EVENT = 2;
@@ -1705,7 +1741,6 @@ public final class PlaybackActivityMonitor
* event for player reporting playback format and spatialization status
* args:
* msg.arg1: piid
- * msg.arg2: port id
* msg.obj: extras describing the sample rate, channel mask, spatialized
* type: PersistableBundle
*/
@@ -1729,17 +1764,9 @@ public final class PlaybackActivityMonitor
break;
case MSG_IIL_UPDATE_PLAYER_MUTED_EVENT:
- // TODO: replace PersistableBundle with own struct
- PersistableBundle extras = (PersistableBundle) msg.obj;
- if (extras == null) {
- Log.w(TAG, "Received mute event with no extras");
- break;
- }
- @PlayerMuteEvent int eventValue = extras.getInt(EXTRA_PLAYER_EVENT_MUTE);
-
synchronized (mPlayerLock) {
int piid = msg.arg1;
-
+ @PlayerMuteEvent int eventValue = msg.arg2;
int[] eventValues = new int[1];
eventValues[0] = eventValue;
diff --git a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
index d435144b28c6..b9ce8c93dbde 100644
--- a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
+++ b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
@@ -21,6 +21,7 @@ import android.os.Build;
import android.os.SystemProperties;
import android.text.TextUtils;
import android.util.Slog;
+import android.window.DesktopExperienceFlags;
import com.android.server.display.feature.flags.Flags;
import com.android.server.display.utils.DebugUtils;
@@ -250,7 +251,7 @@ public class DisplayManagerFlags {
);
private final FlagState mEnableDisplayContentModeManagementFlagState = new FlagState(
Flags.FLAG_ENABLE_DISPLAY_CONTENT_MODE_MANAGEMENT,
- Flags::enableDisplayContentModeManagement
+ DesktopExperienceFlags.ENABLE_DISPLAY_CONTENT_MODE_MANAGEMENT::isTrue
);
private final FlagState mSubscribeGranularDisplayEvents = new FlagState(
diff --git a/services/core/java/com/android/server/display/plugin/PluginManager.java b/services/core/java/com/android/server/display/plugin/PluginManager.java
index d4099975cafa..cb0a4574361a 100644
--- a/services/core/java/com/android/server/display/plugin/PluginManager.java
+++ b/services/core/java/com/android/server/display/plugin/PluginManager.java
@@ -74,15 +74,17 @@ public class PluginManager {
/**
* Adds change listener for particular plugin type
*/
- public <T> void subscribe(PluginType<T> type, PluginChangeListener<T> listener) {
- mPluginStorage.addListener(type, listener);
+ public <T> void subscribe(PluginType<T> type, String uniqueDisplayId,
+ PluginChangeListener<T> listener) {
+ mPluginStorage.addListener(type, uniqueDisplayId, listener);
}
/**
* Removes change listener
*/
- public <T> void unsubscribe(PluginType<T> type, PluginChangeListener<T> listener) {
- mPluginStorage.removeListener(type, listener);
+ public <T> void unsubscribe(PluginType<T> type, String uniqueDisplayId,
+ PluginChangeListener<T> listener) {
+ mPluginStorage.removeListener(type, uniqueDisplayId, listener);
}
/**
diff --git a/services/core/java/com/android/server/display/plugin/PluginStorage.java b/services/core/java/com/android/server/display/plugin/PluginStorage.java
index dd3415fb614d..5102c2709329 100644
--- a/services/core/java/com/android/server/display/plugin/PluginStorage.java
+++ b/services/core/java/com/android/server/display/plugin/PluginStorage.java
@@ -20,10 +20,13 @@ import android.annotation.Nullable;
import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.tools.r8.keepanno.annotations.KeepForApi;
import java.io.PrintWriter;
import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
@@ -35,42 +38,97 @@ import java.util.Set;
public class PluginStorage {
private static final String TAG = "PluginStorage";
+ // Special ID used to indicate that given value is to be applied globally, rather than to a
+ // specific display. If both GLOBAL and specific display values are present - specific display
+ // value is selected.
+ @VisibleForTesting
+ static final String GLOBAL_ID = "GLOBAL";
+
private final Object mLock = new Object();
@GuardedBy("mLock")
- private final Map<PluginType<?>, Object> mValues = new HashMap<>();
+ private final Map<PluginType<?>, ValuesContainer<?>> mValues = new HashMap<>();
@GuardedBy("mLock")
private final Map<PluginType<?>, ListenersContainer<?>> mListeners = new HashMap<>();
@GuardedBy("mLock")
- private final PluginEventStorage mPluginEventStorage = new PluginEventStorage();
+ private final Map<String, PluginEventStorage> mPluginEventStorages = new HashMap<>();
+
+ /**
+ * Updates value in storage and forwards it to corresponding listeners for all displays
+ * that does not have display specific value.
+ * Should be called by OEM Plugin implementation in order to communicate with Framework
+ */
+ @KeepForApi
+ public <T> void updateGlobalValue(PluginType<T> type, @Nullable T value) {
+ updateValue(type, GLOBAL_ID, value);
+ }
/**
- * Updates value in storage and forwards it to corresponding listeners.
- * Should be called by OEM Plugin implementation in order to provide communicate with Framework
+ * Updates value in storage and forwards it to corresponding listeners for specific display.
+ * Should be called by OEM Plugin implementation in order to communicate with Framework
+ * @param type - plugin type, that need to be updated
+ * @param uniqueDisplayId - uniqueDisplayId that this type/value should be applied to
+ * @param value - plugin value for particular type and display
*/
@KeepForApi
- public <T> void updateValue(PluginType<T> type, @Nullable T value) {
- Slog.d(TAG, "updateValue, type=" + type.mName + "; value=" + value);
+ public <T> void updateValue(PluginType<T> type, String uniqueDisplayId, @Nullable T value) {
+ Slog.d(TAG, "updateValue, type=" + type.mName + "; value=" + value
+ + "; displayId=" + uniqueDisplayId);
Set<PluginManager.PluginChangeListener<T>> localListeners;
+ T valueToNotify;
synchronized (mLock) {
- mValues.put(type, value);
- mPluginEventStorage.onValueUpdated(type);
- ListenersContainer<T> container = getListenersContainerForTypeLocked(type);
- localListeners = new LinkedHashSet<>(container.mListeners);
+ ValuesContainer<T> valuesByType = getValuesContainerLocked(type);
+ valuesByType.updateValueLocked(uniqueDisplayId, value);
+ // if value was set to null, we might need to notify with GLOBAL value instead
+ valueToNotify = valuesByType.getValueLocked(uniqueDisplayId);
+
+ PluginEventStorage storage = mPluginEventStorages.computeIfAbsent(uniqueDisplayId,
+ d -> new PluginEventStorage());
+ storage.onValueUpdated(type);
+
+ localListeners = getListenersForUpdateLocked(type, uniqueDisplayId);
}
Slog.d(TAG, "updateValue, notifying listeners=" + localListeners);
- localListeners.forEach(l -> l.onChanged(value));
+ localListeners.forEach(l -> l.onChanged(valueToNotify));
+ }
+
+ @GuardedBy("mLock")
+ private <T> Set<PluginManager.PluginChangeListener<T>> getListenersForUpdateLocked(
+ PluginType<T> type, String uniqueDisplayId) {
+ ListenersContainer<T> listenersContainer = getListenersContainerLocked(type);
+ Set<PluginManager.PluginChangeListener<T>> localListeners = new LinkedHashSet<>();
+ // if GLOBAL value change we need to notify only listeners for displays that does not
+ // have display specific value
+ if (GLOBAL_ID.equals(uniqueDisplayId)) {
+ ValuesContainer<T> valuesContainer = getValuesContainerLocked(type);
+ Set<String> excludedDisplayIds = valuesContainer.getNonGlobalDisplaysLocked();
+ listenersContainer.mListeners.forEach((localDisplayId, listeners) -> {
+ if (!excludedDisplayIds.contains(localDisplayId)) {
+ localListeners.addAll(listeners);
+ }
+ });
+ } else {
+ localListeners.addAll(
+ listenersContainer.mListeners.getOrDefault(uniqueDisplayId, Set.of()));
+ }
+ return localListeners;
}
/**
* Adds listener for PluginType. If storage already has value for this type, listener will
* be notified immediately.
*/
- <T> void addListener(PluginType<T> type, PluginManager.PluginChangeListener<T> listener) {
+ <T> void addListener(PluginType<T> type, String uniqueDisplayId,
+ PluginManager.PluginChangeListener<T> listener) {
+ if (GLOBAL_ID.equals(uniqueDisplayId)) {
+ Slog.d(TAG, "addListener ignored for GLOBAL_ID, type=" + type.mName);
+ return;
+ }
T value = null;
synchronized (mLock) {
- ListenersContainer<T> container = getListenersContainerForTypeLocked(type);
- if (container.mListeners.add(listener)) {
- value = getValueForTypeLocked(type);
+ ListenersContainer<T> container = getListenersContainerLocked(type);
+ if (container.addListenerLocked(uniqueDisplayId, listener)) {
+ ValuesContainer<T> valuesContainer = getValuesContainerLocked(type);
+ value = valuesContainer.getValueLocked(uniqueDisplayId);
}
}
if (value != null) {
@@ -81,10 +139,15 @@ public class PluginStorage {
/**
* Removes listener
*/
- <T> void removeListener(PluginType<T> type, PluginManager.PluginChangeListener<T> listener) {
+ <T> void removeListener(PluginType<T> type, String uniqueDisplayId,
+ PluginManager.PluginChangeListener<T> listener) {
+ if (GLOBAL_ID.equals(uniqueDisplayId)) {
+ Slog.d(TAG, "removeListener ignored for GLOBAL_ID, type=" + type.mName);
+ return;
+ }
synchronized (mLock) {
- ListenersContainer<T> container = getListenersContainerForTypeLocked(type);
- container.mListeners.remove(listener);
+ ListenersContainer<T> container = getListenersContainerLocked(type);
+ container.removeListenerLocked(uniqueDisplayId, listener);
}
}
@@ -92,53 +155,106 @@ public class PluginStorage {
* Print the object's state and debug information into the given stream.
*/
void dump(PrintWriter pw) {
- Map<PluginType<?>, Object> localValues;
+ Map<PluginType<?>, Map<String, Object>> localValues = new HashMap<>();
@SuppressWarnings("rawtypes")
- Map<PluginType, Set> localListeners = new HashMap<>();
- List<PluginEventStorage.TimeFrame> timeFrames;
+ Map<PluginType, Map<String, Set>> localListeners = new HashMap<>();
+ Map<String, List<PluginEventStorage.TimeFrame>> timeFrames = new HashMap<>();
synchronized (mLock) {
- timeFrames = mPluginEventStorage.getTimeFrames();
- localValues = new HashMap<>(mValues);
- mListeners.forEach((type, container) -> localListeners.put(type, container.mListeners));
+ mPluginEventStorages.forEach((displayId, storage) -> {
+ timeFrames.put(displayId, storage.getTimeFrames());
+ });
+ mValues.forEach((type, valueContainer) -> {
+ localValues.put(type, new HashMap<>(valueContainer.mValues));
+ });
+ mListeners.forEach((type, container) -> {
+ localListeners.put(type, new HashMap<>(container.mListeners));
+ });
}
pw.println("PluginStorage:");
pw.println("values=" + localValues);
pw.println("listeners=" + localListeners);
pw.println("PluginEventStorage:");
- for (PluginEventStorage.TimeFrame timeFrame: timeFrames) {
- timeFrame.dump(pw);
+ for (Map.Entry<String, List<PluginEventStorage.TimeFrame>> timeFrameEntry :
+ timeFrames.entrySet()) {
+ pw.println("TimeFrames for displayId=" + timeFrameEntry.getKey());
+ for (PluginEventStorage.TimeFrame timeFrame : timeFrameEntry.getValue()) {
+ timeFrame.dump(pw);
+ }
}
}
@GuardedBy("mLock")
@SuppressWarnings("unchecked")
- private <T> T getValueForTypeLocked(PluginType<T> type) {
- Object value = mValues.get(type);
- if (value == null) {
- return null;
- } else if (type.mType == value.getClass()) {
- return (T) value;
+ private <T> ListenersContainer<T> getListenersContainerLocked(PluginType<T> type) {
+ ListenersContainer<?> container = mListeners.get(type);
+ if (container == null) {
+ ListenersContainer<T> lc = new ListenersContainer<>();
+ mListeners.put(type, lc);
+ return lc;
} else {
- Slog.d(TAG, "getValueForType: unexpected value type=" + value.getClass().getName()
- + ", expected=" + type.mType.getName());
- return null;
+ return (ListenersContainer<T>) container;
}
}
@GuardedBy("mLock")
@SuppressWarnings("unchecked")
- private <T> ListenersContainer<T> getListenersContainerForTypeLocked(PluginType<T> type) {
- ListenersContainer<?> container = mListeners.get(type);
+ private <T> ValuesContainer<T> getValuesContainerLocked(PluginType<T> type) {
+ ValuesContainer<?> container = mValues.get(type);
if (container == null) {
- ListenersContainer<T> lc = new ListenersContainer<>();
- mListeners.put(type, lc);
- return lc;
+ ValuesContainer<T> vc = new ValuesContainer<>();
+ mValues.put(type, vc);
+ return vc;
} else {
- return (ListenersContainer<T>) container;
+ return (ValuesContainer<T>) container;
}
}
private static final class ListenersContainer<T> {
- private final Set<PluginManager.PluginChangeListener<T>> mListeners = new LinkedHashSet<>();
+ private final Map<String, Set<PluginManager.PluginChangeListener<T>>> mListeners =
+ new LinkedHashMap<>();
+
+ private boolean addListenerLocked(
+ String uniqueDisplayId, PluginManager.PluginChangeListener<T> listener) {
+ Set<PluginManager.PluginChangeListener<T>> listenersForDisplay =
+ mListeners.computeIfAbsent(uniqueDisplayId, k -> new LinkedHashSet<>());
+ return listenersForDisplay.add(listener);
+ }
+
+ private void removeListenerLocked(String uniqueDisplayId,
+ PluginManager.PluginChangeListener<T> listener) {
+ Set<PluginManager.PluginChangeListener<T>> listenersForDisplay = mListeners.get(
+ uniqueDisplayId);
+ if (listenersForDisplay == null) {
+ return;
+ }
+
+ listenersForDisplay.remove(listener);
+
+ if (listenersForDisplay.isEmpty()) {
+ mListeners.remove(uniqueDisplayId);
+ }
+ }
+ }
+
+ private static final class ValuesContainer<T> {
+ private final Map<String, T> mValues = new HashMap<>();
+
+ private void updateValueLocked(String uniqueDisplayId, @Nullable T value) {
+ if (value == null) {
+ mValues.remove(uniqueDisplayId);
+ } else {
+ mValues.put(uniqueDisplayId, value);
+ }
+ }
+
+ private Set<String> getNonGlobalDisplaysLocked() {
+ Set<String> keys = new HashSet<>(mValues.keySet());
+ keys.remove(GLOBAL_ID);
+ return keys;
+ }
+
+ private @Nullable T getValueLocked(String displayId) {
+ return mValues.getOrDefault(displayId, mValues.get(GLOBAL_ID));
+ }
}
}
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index c0dbfa546a94..89f0d0edbf2b 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -710,7 +710,9 @@ public class HdmiControlService extends SystemService {
// Register ContentObserver to monitor the settings change.
registerContentObserver();
}
- mMhlController.setOption(OPTION_MHL_SERVICE_CONTROL, ENABLED);
+ if (mMhlController != null) {
+ mMhlController.setOption(OPTION_MHL_SERVICE_CONTROL, ENABLED);
+ }
}
@VisibleForTesting
diff --git a/services/core/java/com/android/server/infra/OWNERS b/services/core/java/com/android/server/infra/OWNERS
index 4fea05d295b6..0f0d382e28f8 100644
--- a/services/core/java/com/android/server/infra/OWNERS
+++ b/services/core/java/com/android/server/infra/OWNERS
@@ -1,3 +1,4 @@
# Bug component: 655446
srazdan@google.com
+reemabajwa@google.com
diff --git a/services/core/java/com/android/server/input/InputGestureManager.java b/services/core/java/com/android/server/input/InputGestureManager.java
index fd755e3cefe2..32b36bfb50e5 100644
--- a/services/core/java/com/android/server/input/InputGestureManager.java
+++ b/services/core/java/com/android/server/input/InputGestureManager.java
@@ -23,7 +23,6 @@ import static com.android.hardware.input.Flags.enableVoiceAccessKeyGestures;
import static com.android.hardware.input.Flags.keyboardA11yShortcutControl;
import static com.android.server.flags.Flags.newBugreportKeyboardShortcut;
import static com.android.window.flags.Flags.enableMoveToNextDisplayShortcut;
-import static com.android.window.flags.Flags.enableTaskResizingKeyboardShortcuts;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -37,6 +36,7 @@ import android.os.SystemProperties;
import android.util.IndentingPrintWriter;
import android.util.SparseArray;
import android.view.KeyEvent;
+import android.window.DesktopModeFlags;
import com.android.internal.annotations.GuardedBy;
@@ -186,21 +186,11 @@ final class InputGestureManager {
KeyGestureEvent.KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_LEFT
),
createKeyGesture(
- KeyEvent.KEYCODE_DPAD_LEFT,
- KeyEvent.META_CTRL_ON | KeyEvent.META_ALT_ON,
- KeyGestureEvent.KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_LEFT
- ),
- createKeyGesture(
KeyEvent.KEYCODE_DPAD_RIGHT,
KeyEvent.META_META_ON | KeyEvent.META_CTRL_ON,
KeyGestureEvent.KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_RIGHT
),
createKeyGesture(
- KeyEvent.KEYCODE_DPAD_RIGHT,
- KeyEvent.META_CTRL_ON | KeyEvent.META_ALT_ON,
- KeyGestureEvent.KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_RIGHT
- ),
- createKeyGesture(
KeyEvent.KEYCODE_SLASH,
KeyEvent.META_META_ON,
KeyGestureEvent.KEY_GESTURE_TYPE_OPEN_SHORTCUT_HELPER
@@ -243,7 +233,7 @@ final class InputGestureManager {
KeyEvent.META_META_ON | KeyEvent.META_ALT_ON,
KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_VOICE_ACCESS));
}
- if (enableTaskResizingKeyboardShortcuts()) {
+ if (DesktopModeFlags.ENABLE_TASK_RESIZING_KEYBOARD_SHORTCUTS.isTrue()) {
systemShortcuts.add(createKeyGesture(
KeyEvent.KEYCODE_LEFT_BRACKET,
KeyEvent.META_META_ON,
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubEndpointBroker.java b/services/core/java/com/android/server/location/contexthub/ContextHubEndpointBroker.java
index b9352edf9230..ddace179348c 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubEndpointBroker.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubEndpointBroker.java
@@ -42,6 +42,7 @@ import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
import java.util.Collection;
+import java.util.Optional;
import java.util.concurrent.atomic.AtomicBoolean;
/**
@@ -372,10 +373,12 @@ public class ContextHubEndpointBroker extends IContextHubEndpoint.Stub
/* package */ void onEndpointSessionOpenRequest(
int sessionId, HubEndpointInfo initiator, String serviceDescriptor) {
- boolean success =
+ Optional<Byte> error =
onEndpointSessionOpenRequestInternal(sessionId, initiator, serviceDescriptor);
- if (!success) {
- cleanupSessionResources(sessionId);
+ if (error.isPresent()) {
+ halCloseEndpointSessionNoThrow(sessionId, error.get());
+ onCloseEndpointSession(sessionId, error.get());
+ // Resource cleanup is done in onCloseEndpointSession
}
}
@@ -422,7 +425,7 @@ public class ContextHubEndpointBroker extends IContextHubEndpoint.Stub
}
}
- private boolean onEndpointSessionOpenRequestInternal(
+ private Optional<Byte> onEndpointSessionOpenRequestInternal(
int sessionId, HubEndpointInfo initiator, String serviceDescriptor) {
if (!hasEndpointPermissions(initiator)) {
Log.e(
@@ -431,22 +434,21 @@ public class ContextHubEndpointBroker extends IContextHubEndpoint.Stub
+ initiator
+ " doesn't have permission for "
+ mEndpointInfo);
- halCloseEndpointSessionNoThrow(sessionId, Reason.PERMISSION_DENIED);
- return false;
+ return Optional.of(Reason.PERMISSION_DENIED);
}
synchronized (mOpenSessionLock) {
if (hasSessionId(sessionId)) {
Log.e(TAG, "Existing session in onEndpointSessionOpenRequest: id=" + sessionId);
- halCloseEndpointSessionNoThrow(sessionId, Reason.UNSPECIFIED);
- return false;
+ return Optional.of(Reason.UNSPECIFIED);
}
mSessionInfoMap.put(sessionId, new SessionInfo(initiator, true));
}
- return invokeCallback(
+ boolean success = invokeCallback(
(consumer) ->
consumer.onSessionOpenRequest(sessionId, initiator, serviceDescriptor));
+ return success ? Optional.empty() : Optional.of(Reason.UNSPECIFIED);
}
private byte onMessageReceivedInternal(int sessionId, HubMessage message) {
diff --git a/services/core/java/com/android/server/media/SystemMediaRoute2Provider2.java b/services/core/java/com/android/server/media/SystemMediaRoute2Provider2.java
index 011659a616d3..3eb38a7029e6 100644
--- a/services/core/java/com/android/server/media/SystemMediaRoute2Provider2.java
+++ b/services/core/java/com/android/server/media/SystemMediaRoute2Provider2.java
@@ -17,6 +17,7 @@
package com.android.server.media;
import static android.media.MediaRoute2Info.FEATURE_LIVE_AUDIO;
+import static android.media.MediaRoute2Info.FEATURE_LIVE_VIDEO;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -217,6 +218,28 @@ import java.util.stream.Stream;
}
}
+ @Override
+ public void setRouteVolume(long requestId, String routeOriginalId, int volume) {
+ synchronized (mLock) {
+ var targetProviderProxyId = mOriginalRouteIdToProviderId.get(routeOriginalId);
+ var targetProviderProxyRecord = mProxyRecords.get(targetProviderProxyId);
+ // Holds the target route, if it's managed by a provider service. Holds null otherwise.
+ if (targetProviderProxyRecord != null) {
+ var serviceTargetRoute =
+ targetProviderProxyRecord.mNewOriginalIdToSourceOriginalIdMap.get(
+ routeOriginalId);
+ if (serviceTargetRoute != null) {
+ targetProviderProxyRecord.mProxy.setRouteVolume(
+ requestId, serviceTargetRoute, volume);
+ } else {
+ notifyRequestFailed(
+ requestId, MediaRoute2ProviderService.REASON_ROUTE_NOT_AVAILABLE);
+ }
+ }
+ }
+ super.setRouteVolume(requestId, routeOriginalId, volume);
+ }
+
/**
* Returns the uid that corresponds to the given name and user handle, or {@link
* Process#INVALID_UID} if a uid couldn't be found.
@@ -463,11 +486,18 @@ import java.util.stream.Stream;
}
String id =
asSystemRouteId(providerInfo.getUniqueId(), sourceRoute.getOriginalId());
- var newRoute =
- new MediaRoute2Info.Builder(id, sourceRoute.getName())
- .addFeature(FEATURE_LIVE_AUDIO)
- .build();
- routesMap.put(id, newRoute);
+ var newRouteBuilder = new MediaRoute2Info.Builder(id, sourceRoute);
+ if ((sourceRoute.getSupportedRoutingTypes()
+ & MediaRoute2Info.FLAG_ROUTING_TYPE_SYSTEM_AUDIO)
+ != 0) {
+ newRouteBuilder.addFeature(FEATURE_LIVE_AUDIO);
+ }
+ if ((sourceRoute.getSupportedRoutingTypes()
+ & MediaRoute2Info.FLAG_ROUTING_TYPE_SYSTEM_VIDEO)
+ != 0) {
+ newRouteBuilder.addFeature(FEATURE_LIVE_VIDEO);
+ }
+ routesMap.put(id, newRouteBuilder.build());
idMap.put(id, sourceRoute.getOriginalId());
}
return new ProviderProxyRecord(
diff --git a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
index c428f39fd9d0..34a6cb951d46 100644
--- a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
+++ b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
@@ -58,6 +58,7 @@ import android.media.projection.IMediaProjection;
import android.media.projection.IMediaProjectionCallback;
import android.media.projection.IMediaProjectionManager;
import android.media.projection.IMediaProjectionWatcherCallback;
+import android.media.projection.MediaProjectionEvent;
import android.media.projection.MediaProjectionInfo;
import android.media.projection.MediaProjectionManager;
import android.media.projection.ReviewGrantedConsentResult;
@@ -80,6 +81,7 @@ import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.DumpUtils;
+import com.android.media.projection.flags.Flags;
import com.android.server.LocalServices;
import com.android.server.SystemService;
import com.android.server.Watchdog;
@@ -177,9 +179,31 @@ public final class MediaProjectionManagerService extends SystemService
private void maybeStopMediaProjection(int reason) {
synchronized (mLock) {
- if (!mMediaProjectionStopController.isExemptFromStopping(mProjectionGrant, reason)) {
- Slog.d(TAG, "Content Recording: Stopping MediaProjection due to "
- + MediaProjectionStopController.stopReasonToString(reason));
+ if (mMediaProjectionStopController.isExemptFromStopping(mProjectionGrant, reason)) {
+ return;
+ }
+
+ if (Flags.showStopDialogPostCallEnd()
+ && mMediaProjectionStopController.isStopReasonCallEnd(reason)) {
+ MediaProjectionEvent event =
+ new MediaProjectionEvent(
+ MediaProjectionEvent
+ .PROJECTION_STARTED_DURING_CALL_AND_ACTIVE_POST_CALL,
+ System.currentTimeMillis());
+ Slog.d(
+ TAG,
+ "Scheduling event: "
+ + event.getEventType()
+ + " for reason: "
+ + MediaProjectionStopController.stopReasonToString(reason));
+
+ // Post the PROJECTION_STARTED_DURING_CALL_AND_ACTIVE_POST_CALL event with a delay.
+ mHandler.postDelayed(() -> dispatchEvent(event), 500);
+ } else {
+ Slog.d(
+ TAG,
+ "Stopping MediaProjection due to reason: "
+ + MediaProjectionStopController.stopReasonToString(reason));
mProjectionGrant.stop(StopReason.STOP_DEVICE_LOCKED);
}
}
@@ -388,6 +412,24 @@ public final class MediaProjectionManagerService extends SystemService
mCallbackDelegate.dispatchSession(projectionInfo, session);
}
+ private void dispatchEvent(@NonNull MediaProjectionEvent event) {
+ if (!Flags.showStopDialogPostCallEnd()) {
+ Slog.d(
+ TAG,
+ "Event dispatch skipped. Reason: Flag showStopDialogPostCallEnd "
+ + "is disabled. Event details: "
+ + event);
+ return;
+ }
+ MediaProjectionInfo projectionInfo;
+ ContentRecordingSession session;
+ synchronized (mLock) {
+ projectionInfo = mProjectionGrant != null ? mProjectionGrant.getProjectionInfo() : null;
+ session = mProjectionGrant != null ? mProjectionGrant.mSession : null;
+ }
+ mCallbackDelegate.dispatchEvent(event, projectionInfo, session);
+ }
+
/**
* Returns {@code true} when updating the current mirroring session on WM succeeded, and
* {@code false} otherwise.
@@ -1467,6 +1509,25 @@ public final class MediaProjectionManagerService extends SystemService
}
}
+ private void dispatchEvent(
+ @NonNull MediaProjectionEvent event,
+ @Nullable MediaProjectionInfo info,
+ @Nullable ContentRecordingSession session) {
+ if (!Flags.showStopDialogPostCallEnd()) {
+ Slog.d(
+ TAG,
+ "Event dispatch skipped. Reason: Flag showStopDialogPostCallEnd "
+ + "is disabled. Event details: "
+ + event);
+ return;
+ }
+ synchronized (mLock) {
+ for (IMediaProjectionWatcherCallback callback : mWatcherCallbacks.values()) {
+ mHandler.post(new WatcherEventCallback(callback, event, info, session));
+ }
+ }
+ }
+
public void dispatchSession(
@NonNull MediaProjectionInfo projectionInfo,
@Nullable ContentRecordingSession session) {
@@ -1593,6 +1654,41 @@ public final class MediaProjectionManagerService extends SystemService
}
}
+ private static final class WatcherEventCallback implements Runnable {
+ private final IMediaProjectionWatcherCallback mCallback;
+ private final MediaProjectionEvent mEvent;
+ private final MediaProjectionInfo mProjectionInfo;
+ private final ContentRecordingSession mSession;
+
+ WatcherEventCallback(
+ @NonNull IMediaProjectionWatcherCallback callback,
+ @NonNull MediaProjectionEvent event,
+ @Nullable MediaProjectionInfo projectionInfo,
+ @Nullable ContentRecordingSession session) {
+ mCallback = callback;
+ mEvent = event;
+ mProjectionInfo = projectionInfo;
+ mSession = session;
+ }
+
+ @Override
+ public void run() {
+ if (!Flags.showStopDialogPostCallEnd()) {
+ Slog.d(
+ TAG,
+ "Not running WatcherEventCallback. Reason: Flag "
+ + "showStopDialogPostCallEnd is disabled. "
+ );
+ return;
+ }
+ try {
+ mCallback.onMediaProjectionEvent(mEvent, mProjectionInfo, mSession);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Failed to notify MediaProjectionEvent change", e);
+ }
+ }
+ }
+
private static final class WatcherSessionCallback implements Runnable {
private final IMediaProjectionWatcherCallback mCallback;
private final MediaProjectionInfo mProjectionInfo;
diff --git a/services/core/java/com/android/server/media/projection/MediaProjectionStopController.java b/services/core/java/com/android/server/media/projection/MediaProjectionStopController.java
index c018e6bc1dc7..18f2f48b80a3 100644
--- a/services/core/java/com/android/server/media/projection/MediaProjectionStopController.java
+++ b/services/core/java/com/android/server/media/projection/MediaProjectionStopController.java
@@ -28,6 +28,7 @@ import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Binder;
import android.os.SystemClock;
+import android.os.UserHandle;
import android.provider.Settings;
import android.telecom.TelecomManager;
import android.telephony.TelephonyCallback;
@@ -38,6 +39,7 @@ import android.view.Display;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.SystemConfig;
+import java.util.List;
import java.util.function.Consumer;
/**
@@ -60,21 +62,35 @@ public class MediaProjectionStopController {
private final TelephonyManager mTelephonyManager;
private final AppOpsManager mAppOpsManager;
private final PackageManager mPackageManager;
- private final RoleManager mRoleManager;
+ private final RoleHolderProvider mRoleHolderProvider;
private final ContentResolver mContentResolver;
private boolean mIsInCall;
private long mLastCallStartTimeMillis;
+
+ @VisibleForTesting
+ interface RoleHolderProvider {
+ List<String> getRoleHoldersAsUser(String roleName, UserHandle user);
+ }
+
public MediaProjectionStopController(Context context, Consumer<Integer> stopReasonConsumer) {
+ this(context, stopReasonConsumer,
+ (roleName, user) -> context.getSystemService(RoleManager.class)
+ .getRoleHoldersAsUser(roleName, user));
+ }
+
+ @VisibleForTesting
+ MediaProjectionStopController(Context context, Consumer<Integer> stopReasonConsumer,
+ RoleHolderProvider roleHolderProvider) {
mStopReasonConsumer = stopReasonConsumer;
mKeyguardManager = context.getSystemService(KeyguardManager.class);
mTelecomManager = context.getSystemService(TelecomManager.class);
mTelephonyManager = context.getSystemService(TelephonyManager.class);
mAppOpsManager = context.getSystemService(AppOpsManager.class);
mPackageManager = context.getPackageManager();
- mRoleManager = context.getSystemService(RoleManager.class);
mContentResolver = context.getContentResolver();
+ mRoleHolderProvider = roleHolderProvider;
}
/**
@@ -95,6 +111,11 @@ public class MediaProjectionStopController {
}
}
+ /** Checks if the given stop reason corresponds to a call ending. */
+ public boolean isStopReasonCallEnd(int stopReason) {
+ return stopReason == STOP_REASON_CALL_END;
+ }
+
/**
* Checks whether the given projection grant is exempt from stopping restrictions.
*/
@@ -141,8 +162,9 @@ public class MediaProjectionStopController {
Slog.v(TAG, "Continuing MediaProjection for package with OP_PROJECT_MEDIA AppOp ");
return true;
}
- if (mRoleManager.getRoleHoldersAsUser(AssociationRequest.DEVICE_PROFILE_APP_STREAMING,
- projectionGrant.userHandle).contains(projectionGrant.packageName)) {
+ if (mRoleHolderProvider.getRoleHoldersAsUser(
+ AssociationRequest.DEVICE_PROFILE_APP_STREAMING, projectionGrant.userHandle)
+ .contains(projectionGrant.packageName)) {
Slog.v(TAG, "Continuing MediaProjection for package holding app streaming role.");
return true;
}
@@ -172,10 +194,6 @@ public class MediaProjectionStopController {
*/
public boolean isStartForbidden(
MediaProjectionManagerService.MediaProjection projectionGrant) {
- if (!android.companion.virtualdevice.flags.Flags.mediaProjectionKeyguardRestrictions()) {
- return false;
- }
-
if (!mKeyguardManager.isKeyguardLocked()) {
return false;
}
@@ -189,9 +207,6 @@ public class MediaProjectionStopController {
@VisibleForTesting
void onKeyguardLockedStateChanged(boolean isKeyguardLocked) {
if (!isKeyguardLocked) return;
- if (!android.companion.virtualdevice.flags.Flags.mediaProjectionKeyguardRestrictions()) {
- return;
- }
mStopReasonConsumer.accept(STOP_REASON_KEYGUARD);
}
diff --git a/services/core/java/com/android/server/media/quality/MediaQualityService.java b/services/core/java/com/android/server/media/quality/MediaQualityService.java
index 2a5b779546d5..f6c94a7d9a5a 100644
--- a/services/core/java/com/android/server/media/quality/MediaQualityService.java
+++ b/services/core/java/com/android/server/media/quality/MediaQualityService.java
@@ -29,11 +29,19 @@ import android.content.pm.PackageManager;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.hardware.tv.mediaquality.AmbientBacklightColorFormat;
+import android.hardware.tv.mediaquality.DolbyAudioProcessing;
+import android.hardware.tv.mediaquality.DtsVirtualX;
import android.hardware.tv.mediaquality.IMediaQuality;
+import android.hardware.tv.mediaquality.IPictureProfileAdjustmentListener;
+import android.hardware.tv.mediaquality.IPictureProfileChangedListener;
+import android.hardware.tv.mediaquality.ISoundProfileAdjustmentListener;
+import android.hardware.tv.mediaquality.ISoundProfileChangedListener;
+import android.hardware.tv.mediaquality.ParamCapability;
import android.hardware.tv.mediaquality.PictureParameter;
import android.hardware.tv.mediaquality.PictureParameters;
import android.hardware.tv.mediaquality.SoundParameter;
import android.hardware.tv.mediaquality.SoundParameters;
+import android.hardware.tv.mediaquality.VendorParamCapability;
import android.media.quality.AmbientBacklightEvent;
import android.media.quality.AmbientBacklightMetadata;
import android.media.quality.AmbientBacklightSettings;
@@ -103,6 +111,10 @@ public class MediaQualityService extends SystemService {
private final BiMap<Long, String> mPictureProfileTempIdMap;
private final BiMap<Long, String> mSoundProfileTempIdMap;
private IMediaQuality mMediaQuality;
+ private IPictureProfileAdjustmentListener mPpAdjustmentListener;
+ private ISoundProfileAdjustmentListener mSpAdjustmentListener;
+ private IPictureProfileChangedListener mPpChangedListener;
+ private ISoundProfileChangedListener mSpChangedListener;
private final HalAmbientBacklightCallback mHalAmbientBacklightCallback;
private final Map<String, AmbientBacklightCallbackRecord> mCallbackRecords = new HashMap<>();
private final PackageManager mPackageManager;
@@ -138,18 +150,104 @@ public class MediaQualityService extends SystemService {
@Override
public void onStart() {
IBinder binder = ServiceManager.getService(IMediaQuality.DESCRIPTOR + "/default");
- if (binder != null) {
- Slogf.d(TAG, "binder is not null");
- mMediaQuality = IMediaQuality.Stub.asInterface(binder);
- if (mMediaQuality != null) {
- try {
- mMediaQuality.setAmbientBacklightCallback(mHalAmbientBacklightCallback);
- } catch (RemoteException e) {
- Slog.e(TAG, "Failed to set ambient backlight detector callback", e);
+ if (binder == null) {
+ Slogf.d(TAG, "Binder is null");
+ return;
+ }
+ Slogf.d(TAG, "Binder is not null");
+
+ mPpAdjustmentListener = new IPictureProfileAdjustmentListener.Stub() {
+ @Override
+ public void onPictureProfileAdjusted(
+ android.hardware.tv.mediaquality.PictureProfile pictureProfile)
+ throws RemoteException {
+ // TODO
+ }
+
+ @Override
+ public void onParamCapabilityChanged(long pictureProfileId, ParamCapability[] caps)
+ throws RemoteException {
+ // TODO
+ }
+
+ @Override
+ public void onVendorParamCapabilityChanged(long pictureProfileId,
+ VendorParamCapability[] caps) throws RemoteException {
+ // TODO
+ }
+
+ @Override
+ public void requestPictureParameters(long pictureProfileId) throws RemoteException {
+ // TODO
+ }
+
+ @Override
+ public void onStreamStatusChanged(long pictureProfileId, byte status)
+ throws RemoteException {
+ // TODO
+ }
+
+ @Override
+ public int getInterfaceVersion() throws RemoteException {
+ return 0;
+ }
+
+ @Override
+ public String getInterfaceHash() throws RemoteException {
+ return null;
}
+ };
+ mSpAdjustmentListener = new ISoundProfileAdjustmentListener.Stub() {
+
+ @Override
+ public void onSoundProfileAdjusted(
+ android.hardware.tv.mediaquality.SoundProfile soundProfile)
+ throws RemoteException {
+ // TODO
+ }
+
+ @Override
+ public void onParamCapabilityChanged(long soundProfileId, ParamCapability[] caps)
+ throws RemoteException {
+ // TODO
+ }
+
+ @Override
+ public void onVendorParamCapabilityChanged(long soundProfileId,
+ VendorParamCapability[] caps) throws RemoteException {
+ // TODO
+ }
+
+ @Override
+ public void requestSoundParameters(long soundProfileId) throws RemoteException {
+ // TODO
+ }
+
+ @Override
+ public int getInterfaceVersion() throws RemoteException {
+ return 0;
+ }
+
+ @Override
+ public String getInterfaceHash() throws RemoteException {
+ return null;
+ }
+ };
+
+ mMediaQuality = IMediaQuality.Stub.asInterface(binder);
+ if (mMediaQuality != null) {
+ try {
+ mMediaQuality.setAmbientBacklightCallback(mHalAmbientBacklightCallback);
+ mMediaQuality.setPictureProfileAdjustmentListener(mPpAdjustmentListener);
+ mMediaQuality.setSoundProfileAdjustmentListener(mSpAdjustmentListener);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to set ambient backlight detector callback", e);
}
}
+ mPpChangedListener = IPictureProfileChangedListener.Stub.asInterface(binder);
+ mSpChangedListener = ISoundProfileChangedListener.Stub.asInterface(binder);
+
publishBinderService(Context.MEDIA_QUALITY_SERVICE, new BinderService());
}
@@ -185,6 +283,30 @@ public class MediaQualityService extends SystemService {
return pp;
}
+ private void notifyHalOnPictureProfileChange(Long dbId, PersistableBundle params) {
+ // TODO: only notify HAL when the profile is active / being used
+ try {
+ mPpChangedListener.onPictureProfileChanged(convertToHalPictureProfile(dbId,
+ params));
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to notify HAL on picture profile change.", e);
+ }
+ }
+
+ private android.hardware.tv.mediaquality.PictureProfile convertToHalPictureProfile(Long id,
+ PersistableBundle params) {
+ PictureParameters pictureParameters = new PictureParameters();
+ pictureParameters.pictureParameters = convertPersistableBundleToPictureParameterList(
+ params);
+
+ android.hardware.tv.mediaquality.PictureProfile toReturn =
+ new android.hardware.tv.mediaquality.PictureProfile();
+ toReturn.pictureProfileId = id;
+ toReturn.parameters = pictureParameters;
+
+ return toReturn;
+ }
+
@Override
public void updatePictureProfile(String id, PictureProfile pp, UserHandle user) {
Long dbId = mPictureProfileTempIdMap.getKey(id);
@@ -205,6 +327,7 @@ public class MediaQualityService extends SystemService {
null, values);
notifyOnPictureProfileUpdated(mPictureProfileTempIdMap.getValue(dbId),
getPictureProfile(dbId), Binder.getCallingUid(), Binder.getCallingPid());
+ notifyHalOnPictureProfileChange(dbId, pp.getParameters());
}
private boolean hasPermissionToUpdatePictureProfile(Long dbId, PictureProfile toUpdate) {
@@ -238,6 +361,7 @@ public class MediaQualityService extends SystemService {
notifyOnPictureProfileRemoved(mPictureProfileTempIdMap.getValue(dbId), toDelete,
Binder.getCallingUid(), Binder.getCallingPid());
mPictureProfileTempIdMap.remove(dbId);
+ notifyHalOnPictureProfileChange(dbId, null);
}
}
@@ -357,6 +481,10 @@ public class MediaQualityService extends SystemService {
private PictureParameter[] convertPersistableBundleToPictureParameterList(
PersistableBundle params) {
+ if (params == null) {
+ return null;
+ }
+
List<PictureParameter> pictureParams = new ArrayList<>();
if (params.containsKey(PictureQuality.PARAMETER_BRIGHTNESS)) {
pictureParams.add(PictureParameter.brightness(params.getLong(
@@ -461,7 +589,7 @@ public class MediaQualityService extends SystemService {
}
if (params.containsKey(PictureQuality.PARAMETER_AUTO_SUPER_RESOLUTION_ENABLED)) {
pictureParams.add(PictureParameter.autoSuperResolutionEnabled(params.getBoolean(
- PictureQuality.PARAMETER_AUTO_SUPER_RESOLUTION_ENABLED)));
+ PictureQuality.PARAMETER_AUTO_SUPER_RESOLUTION_ENABLED)));
}
if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_RED_GAIN)) {
pictureParams.add(PictureParameter.colorTemperatureRedGain(params.getInt(
@@ -475,63 +603,210 @@ public class MediaQualityService extends SystemService {
pictureParams.add(PictureParameter.colorTemperatureBlueGain(params.getInt(
PictureQuality.PARAMETER_COLOR_TUNER_BLUE_GAIN)));
}
-
- /**
- * TODO: add conversion for following after adding to MediaQualityContract
- *
- * PictureParameter.levelRange
- * PictureParameter.gamutMapping
- * PictureParameter.pcMode
- * PictureParameter.lowLatency
- * PictureParameter.vrr
- * PictureParameter.cvrr
- * PictureParameter.hdmiRgbRange
- * PictureParameter.colorSpace
- * PictureParameter.panelInitMaxLuminceNits
- * PictureParameter.panelInitMaxLuminceValid
- * PictureParameter.gamma
- * PictureParameter.colorTemperatureRedOffset
- * PictureParameter.colorTemperatureGreenOffset
- * PictureParameter.colorTemperatureBlueOffset
- * PictureParameter.elevenPointRed
- * PictureParameter.elevenPointGreen
- * PictureParameter.elevenPointBlue
- * PictureParameter.lowBlueLight
- * PictureParameter.LdMode
- * PictureParameter.osdRedGain
- * PictureParameter.osdGreenGain
- * PictureParameter.osdBlueGain
- * PictureParameter.osdRedOffset
- * PictureParameter.osdGreenOffset
- * PictureParameter.osdBlueOffset
- * PictureParameter.osdHue
- * PictureParameter.osdSaturation
- * PictureParameter.osdContrast
- * PictureParameter.colorTunerSwitch
- * PictureParameter.colorTunerHueRed
- * PictureParameter.colorTunerHueGreen
- * PictureParameter.colorTunerHueBlue
- * PictureParameter.colorTunerHueCyan
- * PictureParameter.colorTunerHueMagenta
- * PictureParameter.colorTunerHueYellow
- * PictureParameter.colorTunerHueFlesh
- * PictureParameter.colorTunerSaturationRed
- * PictureParameter.colorTunerSaturationGreen
- * PictureParameter.colorTunerSaturationBlue
- * PictureParameter.colorTunerSaturationCyan
- * PictureParameter.colorTunerSaturationMagenta
- * PictureParameter.colorTunerSaturationYellow
- * PictureParameter.colorTunerSaturationFlesh
- * PictureParameter.colorTunerLuminanceRed
- * PictureParameter.colorTunerLuminanceGreen
- * PictureParameter.colorTunerLuminanceBlue
- * PictureParameter.colorTunerLuminanceCyan
- * PictureParameter.colorTunerLuminanceMagenta
- * PictureParameter.colorTunerLuminanceYellow
- * PictureParameter.colorTunerLuminanceFlesh
- * PictureParameter.activeProfile
- * PictureParameter.pictureQualityEventType
- */
+ if (params.containsKey(PictureQuality.PARAMETER_LEVEL_RANGE)) {
+ pictureParams.add(PictureParameter.levelRange(
+ (byte) params.getInt(PictureQuality.PARAMETER_LEVEL_RANGE)));
+ }
+ if (params.containsKey(PictureQuality.PARAMETER_GAMUT_MAPPING)) {
+ pictureParams.add(PictureParameter.gamutMapping(params.getBoolean(
+ PictureQuality.PARAMETER_GAMUT_MAPPING)));
+ }
+ if (params.containsKey(PictureQuality.PARAMETER_PC_MODE)) {
+ pictureParams.add(PictureParameter.pcMode(params.getBoolean(
+ PictureQuality.PARAMETER_PC_MODE)));
+ }
+ if (params.containsKey(PictureQuality.PARAMETER_LOW_LATENCY)) {
+ pictureParams.add(PictureParameter.lowLatency(params.getBoolean(
+ PictureQuality.PARAMETER_LOW_LATENCY)));
+ }
+ if (params.containsKey(PictureQuality.PARAMETER_VRR)) {
+ pictureParams.add(PictureParameter.vrr(params.getBoolean(
+ PictureQuality.PARAMETER_VRR)));
+ }
+ if (params.containsKey(PictureQuality.PARAMETER_CVRR)) {
+ pictureParams.add(PictureParameter.cvrr(params.getBoolean(
+ PictureQuality.PARAMETER_CVRR)));
+ }
+ if (params.containsKey(PictureQuality.PARAMETER_HDMI_RGB_RANGE)) {
+ pictureParams.add(PictureParameter.hdmiRgbRange(
+ (byte) params.getInt(PictureQuality.PARAMETER_HDMI_RGB_RANGE)));
+ }
+ if (params.containsKey(PictureQuality.PARAMETER_COLOR_SPACE)) {
+ pictureParams.add(PictureParameter.colorSpace(
+ (byte) params.getInt(PictureQuality.PARAMETER_COLOR_SPACE)));
+ }
+ if (params.containsKey(PictureQuality.PARAMETER_PANEL_INIT_MAX_LUMINCE_NITS)) {
+ pictureParams.add(PictureParameter.panelInitMaxLuminceNits(
+ params.getInt(PictureQuality.PARAMETER_PANEL_INIT_MAX_LUMINCE_NITS)));
+ }
+ if (params.containsKey(PictureQuality.PARAMETER_PANEL_INIT_MAX_LUMINCE_VALID)) {
+ pictureParams.add(PictureParameter.panelInitMaxLuminceValid(
+ params.getBoolean(PictureQuality.PARAMETER_PANEL_INIT_MAX_LUMINCE_VALID)));
+ }
+ if (params.containsKey(PictureQuality.PARAMETER_GAMMA)) {
+ pictureParams.add(PictureParameter.gamma(
+ (byte) params.getInt(PictureQuality.PARAMETER_GAMMA)));
+ }
+ if (params.containsKey(PictureQuality.PARAMETER_COLOR_TEMPERATURE_RED_OFFSET)) {
+ pictureParams.add(PictureParameter.colorTemperatureRedOffset(params.getInt(
+ PictureQuality.PARAMETER_COLOR_TEMPERATURE_RED_OFFSET)));
+ }
+ if (params.containsKey(PictureQuality.PARAMETER_COLOR_TEMPERATURE_GREEN_OFFSET)) {
+ pictureParams.add(PictureParameter.colorTemperatureGreenOffset(params.getInt(
+ PictureQuality.PARAMETER_COLOR_TEMPERATURE_GREEN_OFFSET)));
+ }
+ if (params.containsKey(PictureQuality.PARAMETER_COLOR_TEMPERATURE_BLUE_OFFSET)) {
+ pictureParams.add(PictureParameter.colorTemperatureBlueOffset(params.getInt(
+ PictureQuality.PARAMETER_COLOR_TEMPERATURE_BLUE_OFFSET)));
+ }
+ if (params.containsKey(PictureQuality.PARAMETER_ELEVEN_POINT_RED)) {
+ pictureParams.add(PictureParameter.elevenPointRed(params.getIntArray(
+ PictureQuality.PARAMETER_ELEVEN_POINT_RED)));
+ }
+ if (params.containsKey(PictureQuality.PARAMETER_ELEVEN_POINT_GREEN)) {
+ pictureParams.add(PictureParameter.elevenPointGreen(params.getIntArray(
+ PictureQuality.PARAMETER_ELEVEN_POINT_GREEN)));
+ }
+ if (params.containsKey(PictureQuality.PARAMETER_ELEVEN_POINT_BLUE)) {
+ pictureParams.add(PictureParameter.elevenPointBlue(params.getIntArray(
+ PictureQuality.PARAMETER_ELEVEN_POINT_BLUE)));
+ }
+ if (params.containsKey(PictureQuality.PARAMETER_LOW_BLUE_LIGHT)) {
+ pictureParams.add(PictureParameter.lowBlueLight(
+ (byte) params.getInt(PictureQuality.PARAMETER_LOW_BLUE_LIGHT)));
+ }
+ if (params.containsKey(PictureQuality.PARAMETER_LD_MODE)) {
+ pictureParams.add(PictureParameter.LdMode(
+ (byte) params.getInt(PictureQuality.PARAMETER_LD_MODE)));
+ }
+ if (params.containsKey(PictureQuality.PARAMETER_OSD_RED_GAIN)) {
+ pictureParams.add(PictureParameter.osdRedGain(params.getInt(
+ PictureQuality.PARAMETER_OSD_RED_GAIN)));
+ }
+ if (params.containsKey(PictureQuality.PARAMETER_OSD_GREEN_GAIN)) {
+ pictureParams.add(PictureParameter.osdGreenGain(params.getInt(
+ PictureQuality.PARAMETER_OSD_GREEN_GAIN)));
+ }
+ if (params.containsKey(PictureQuality.PARAMETER_OSD_BLUE_GAIN)) {
+ pictureParams.add(PictureParameter.osdBlueGain(params.getInt(
+ PictureQuality.PARAMETER_OSD_BLUE_GAIN)));
+ }
+ if (params.containsKey(PictureQuality.PARAMETER_OSD_RED_OFFSET)) {
+ pictureParams.add(PictureParameter.osdRedOffset(params.getInt(
+ PictureQuality.PARAMETER_OSD_RED_OFFSET)));
+ }
+ if (params.containsKey(PictureQuality.PARAMETER_OSD_GREEN_OFFSET)) {
+ pictureParams.add(PictureParameter.osdGreenOffset(params.getInt(
+ PictureQuality.PARAMETER_OSD_GREEN_OFFSET)));
+ }
+ if (params.containsKey(PictureQuality.PARAMETER_OSD_BLUE_OFFSET)) {
+ pictureParams.add(PictureParameter.osdBlueOffset(params.getInt(
+ PictureQuality.PARAMETER_OSD_BLUE_OFFSET)));
+ }
+ if (params.containsKey(PictureQuality.PARAMETER_OSD_HUE)) {
+ pictureParams.add(PictureParameter.osdHue(params.getInt(
+ PictureQuality.PARAMETER_OSD_HUE)));
+ }
+ if (params.containsKey(PictureQuality.PARAMETER_OSD_SATURATION)) {
+ pictureParams.add(PictureParameter.osdSaturation(params.getInt(
+ PictureQuality.PARAMETER_OSD_SATURATION)));
+ }
+ if (params.containsKey(PictureQuality.PARAMETER_OSD_CONTRAST)) {
+ pictureParams.add(PictureParameter.osdContrast(params.getInt(
+ PictureQuality.PARAMETER_OSD_CONTRAST)));
+ }
+ if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_SWITCH)) {
+ pictureParams.add(PictureParameter.colorTunerSwitch(params.getBoolean(
+ PictureQuality.PARAMETER_COLOR_TUNER_SWITCH)));
+ }
+ if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_HUE_RED)) {
+ pictureParams.add(PictureParameter.colorTunerHueRed(params.getInt(
+ PictureQuality.PARAMETER_COLOR_TUNER_HUE_RED)));
+ }
+ if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_HUE_GREEN)) {
+ pictureParams.add(PictureParameter.colorTunerHueGreen(params.getInt(
+ PictureQuality.PARAMETER_COLOR_TUNER_HUE_GREEN)));
+ }
+ if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_HUE_BLUE)) {
+ pictureParams.add(PictureParameter.colorTunerHueBlue(params.getInt(
+ PictureQuality.PARAMETER_COLOR_TUNER_HUE_BLUE)));
+ }
+ if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_HUE_CYAN)) {
+ pictureParams.add(PictureParameter.colorTunerHueCyan(params.getInt(
+ PictureQuality.PARAMETER_COLOR_TUNER_HUE_CYAN)));
+ }
+ if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_HUE_MAGENTA)) {
+ pictureParams.add(PictureParameter.colorTunerHueMagenta(params.getInt(
+ PictureQuality.PARAMETER_COLOR_TUNER_HUE_MAGENTA)));
+ }
+ if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_HUE_YELLOW)) {
+ pictureParams.add(PictureParameter.colorTunerHueYellow(params.getInt(
+ PictureQuality.PARAMETER_COLOR_TUNER_HUE_YELLOW)));
+ }
+ if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_HUE_FLESH)) {
+ pictureParams.add(PictureParameter.colorTunerHueFlesh(params.getInt(
+ PictureQuality.PARAMETER_COLOR_TUNER_HUE_FLESH)));
+ }
+ if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_SATURATION_RED)) {
+ pictureParams.add(PictureParameter.colorTunerSaturationRed(params.getInt(
+ PictureQuality.PARAMETER_COLOR_TUNER_SATURATION_RED)));
+ }
+ if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_SATURATION_GREEN)) {
+ pictureParams.add(PictureParameter.colorTunerSaturationGreen(params.getInt(
+ PictureQuality.PARAMETER_COLOR_TUNER_SATURATION_GREEN)));
+ }
+ if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_SATURATION_BLUE)) {
+ pictureParams.add(PictureParameter.colorTunerSaturationBlue(params.getInt(
+ PictureQuality.PARAMETER_COLOR_TUNER_SATURATION_BLUE)));
+ }
+ if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_SATURATION_CYAN)) {
+ pictureParams.add(PictureParameter.colorTunerSaturationCyan(params.getInt(
+ PictureQuality.PARAMETER_COLOR_TUNER_SATURATION_CYAN)));
+ }
+ if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_SATURATION_MAGENTA)) {
+ pictureParams.add(PictureParameter.colorTunerSaturationMagenta(params.getInt(
+ PictureQuality.PARAMETER_COLOR_TUNER_SATURATION_MAGENTA)));
+ }
+ if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_SATURATION_YELLOW)) {
+ pictureParams.add(PictureParameter.colorTunerSaturationYellow(params.getInt(
+ PictureQuality.PARAMETER_COLOR_TUNER_SATURATION_YELLOW)));
+ }
+ if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_SATURATION_FLESH)) {
+ pictureParams.add(PictureParameter.colorTunerSaturationFlesh(params.getInt(
+ PictureQuality.PARAMETER_COLOR_TUNER_SATURATION_FLESH)));
+ }
+ if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_RED)) {
+ pictureParams.add(PictureParameter.colorTunerLuminanceRed(params.getInt(
+ PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_RED)));
+ }
+ if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_GREEN)) {
+ pictureParams.add(PictureParameter.colorTunerLuminanceGreen(params.getInt(
+ PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_GREEN)));
+ }
+ if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_BLUE)) {
+ pictureParams.add(PictureParameter.colorTunerLuminanceBlue(params.getInt(
+ PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_BLUE)));
+ }
+ if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_CYAN)) {
+ pictureParams.add(PictureParameter.colorTunerLuminanceCyan(params.getInt(
+ PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_CYAN)));
+ }
+ if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_MAGENTA)) {
+ pictureParams.add(PictureParameter.colorTunerLuminanceMagenta(params.getInt(
+ PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_MAGENTA)));
+ }
+ if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_YELLOW)) {
+ pictureParams.add(PictureParameter.colorTunerLuminanceYellow(params.getInt(
+ PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_YELLOW)));
+ }
+ if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_FLESH)) {
+ pictureParams.add(PictureParameter.colorTunerLuminanceFlesh(params.getInt(
+ PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_FLESH)));
+ }
+ if (params.containsKey(PictureQuality.PARAMETER_PICTURE_QUALITY_EVENT_TYPE)) {
+ pictureParams.add(PictureParameter.pictureQualityEventType(
+ (byte) params.getInt(PictureQuality.PARAMETER_PICTURE_QUALITY_EVENT_TYPE)));
+ }
return (PictureParameter[]) pictureParams.toArray();
}
@@ -606,6 +881,28 @@ public class MediaQualityService extends SystemService {
return sp;
}
+ private void notifyHalOnSoundProfileChange(Long dbId, PersistableBundle params) {
+ // TODO: only notify HAL when the profile is active / being used
+ try {
+ mSpChangedListener.onSoundProfileChanged(convertToHalSoundProfile(dbId, params));
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to notify HAL on sound profile change.", e);
+ }
+ }
+
+ private android.hardware.tv.mediaquality.SoundProfile convertToHalSoundProfile(Long id,
+ PersistableBundle params) {
+ SoundParameters soundParameters = new SoundParameters();
+ soundParameters.soundParameters = convertPersistableBundleToSoundParameterList(params);
+
+ android.hardware.tv.mediaquality.SoundProfile toReturn =
+ new android.hardware.tv.mediaquality.SoundProfile();
+ toReturn.soundProfileId = id;
+ toReturn.parameters = soundParameters;
+
+ return toReturn;
+ }
+
@Override
public void updateSoundProfile(String id, SoundProfile sp, UserHandle user) {
Long dbId = mSoundProfileTempIdMap.getKey(id);
@@ -625,6 +922,7 @@ public class MediaQualityService extends SystemService {
db.replace(mMediaQualityDbHelper.SOUND_QUALITY_TABLE_NAME, null, values);
notifyOnSoundProfileUpdated(mSoundProfileTempIdMap.getValue(dbId),
getSoundProfile(dbId), Binder.getCallingUid(), Binder.getCallingPid());
+ notifyHalOnSoundProfileChange(dbId, sp.getParameters());
}
private boolean hasPermissionToUpdateSoundProfile(Long dbId, SoundProfile sp) {
@@ -657,6 +955,7 @@ public class MediaQualityService extends SystemService {
notifyOnSoundProfileRemoved(mSoundProfileTempIdMap.getValue(dbId), toDelete,
Binder.getCallingUid(), Binder.getCallingPid());
mSoundProfileTempIdMap.remove(dbId);
+ notifyHalOnSoundProfileChange(dbId, null);
}
}
@@ -775,6 +1074,10 @@ public class MediaQualityService extends SystemService {
private SoundParameter[] convertPersistableBundleToSoundParameterList(
PersistableBundle params) {
+ //TODO: set EqualizerDetail
+ if (params == null) {
+ return null;
+ }
List<SoundParameter> soundParams = new ArrayList<>();
if (params.containsKey(SoundQuality.PARAMETER_BALANCE)) {
soundParams.add(SoundParameter.balance(params.getInt(
@@ -811,15 +1114,50 @@ public class MediaQualityService extends SystemService {
soundParams.add(SoundParameter.surroundSoundEnabled(params.getBoolean(
SoundQuality.PARAMETER_DIGITAL_OUTPUT_DELAY_MILLIS)));
}
- //TODO: equalizerDetail
- //TODO: downmixMode
- //TODO: enhancedAudioReturnChannelEnabled
- //TODO: dolbyAudioProcessing
- //TODO: dolbyDialogueEnhancer
- //TODO: dtsVirtualX
- //TODO: digitalOutput
- //TODO: activeProfile
- //TODO: soundStyle
+ if (params.containsKey(SoundQuality.PARAMETER_EARC)) {
+ soundParams.add(SoundParameter.enhancedAudioReturnChannelEnabled(params.getBoolean(
+ SoundQuality.PARAMETER_EARC)));
+ }
+ if (params.containsKey(SoundQuality.PARAMETER_DOWN_MIX_MODE)) {
+ soundParams.add(SoundParameter.downmixMode((byte) params.getInt(
+ SoundQuality.PARAMETER_DOWN_MIX_MODE)));
+ }
+ if (params.containsKey(SoundQuality.PARAMETER_SOUND_STYLE)) {
+ soundParams.add(SoundParameter.soundStyle((byte) params.getInt(
+ SoundQuality.PARAMETER_SOUND_STYLE)));
+ }
+ if (params.containsKey(SoundQuality.PARAMETER_DIGITAL_OUTPUT_MODE)) {
+ soundParams.add(SoundParameter.digitalOutput((byte) params.getInt(
+ SoundQuality.PARAMETER_DIGITAL_OUTPUT_MODE)));
+ }
+ if (params.containsKey(SoundQuality.PARAMETER_DIALOGUE_ENHANCER)) {
+ soundParams.add(SoundParameter.dolbyDialogueEnhancer((byte) params.getInt(
+ SoundQuality.PARAMETER_DIALOGUE_ENHANCER)));
+ }
+
+ DolbyAudioProcessing dab = new DolbyAudioProcessing();
+ dab.soundMode =
+ (byte) params.getInt(SoundQuality.PARAMETER_DOLBY_AUDIO_PROCESSING_SOUND_MODE);
+ dab.volumeLeveler =
+ params.getBoolean(SoundQuality.PARAMETER_DOLBY_AUDIO_PROCESSING_VOLUME_LEVELER);
+ dab.surroundVirtualizer = params.getBoolean(
+ SoundQuality.PARAMETER_DOLBY_AUDIO_PROCESSING_SURROUND_VIRTUALIZER);
+ dab.dolbyAtmos =
+ params.getBoolean(SoundQuality.PARAMETER_DOLBY_AUDIO_PROCESSING_DOLBY_ATMOS);
+ soundParams.add(SoundParameter.dolbyAudioProcessing(dab));
+
+ DtsVirtualX dts = new DtsVirtualX();
+ dts.tbHdx = params.getBoolean(SoundQuality.PARAMETER_DTS_VIRTUAL_X_TBHDX);
+ dts.limiter = params.getBoolean(SoundQuality.PARAMETER_DTS_VIRTUAL_X_LIMITER);
+ dts.truSurroundX = params.getBoolean(
+ SoundQuality.PARAMETER_DTS_VIRTUAL_X_TRU_SURROUND_X);
+ dts.truVolumeHd = params.getBoolean(SoundQuality.PARAMETER_DTS_VIRTUAL_X_TRU_VOLUME_HD);
+ dts.dialogClarity = params.getBoolean(
+ SoundQuality.PARAMETER_DTS_VIRTUAL_X_DIALOG_CLARITY);
+ dts.definition = params.getBoolean(SoundQuality.PARAMETER_DTS_VIRTUAL_X_DEFINITION);
+ dts.height = params.getBoolean(SoundQuality.PARAMETER_DTS_VIRTUAL_X_HEIGHT);
+ soundParams.add(SoundParameter.dtsVirtualX(dts));
+
return (SoundParameter[]) soundParams.toArray();
}
@@ -1472,7 +1810,13 @@ public class MediaQualityService extends SystemService {
RemoteCallbackList<IPictureProfileCallback> {
@Override
public void onCallbackDied(IPictureProfileCallback callback) {
- //todo
+ synchronized ("mPictureProfileLock") { //TODO: Change to lock
+ for (int i = 0; i < mUserStates.size(); i++) {
+ int userId = mUserStates.keyAt(i);
+ UserState userState = getOrCreateUserStateLocked(userId);
+ userState.mPictureProfileCallbackPidUidMap.remove(callback);
+ }
+ }
}
}
@@ -1480,7 +1824,13 @@ public class MediaQualityService extends SystemService {
RemoteCallbackList<ISoundProfileCallback> {
@Override
public void onCallbackDied(ISoundProfileCallback callback) {
- //todo
+ synchronized ("mSoundProfileLock") { //TODO: Change to lock
+ for (int i = 0; i < mUserStates.size(); i++) {
+ int userId = mUserStates.keyAt(i);
+ UserState userState = getOrCreateUserStateLocked(userId);
+ userState.mSoundProfileCallbackPidUidMap.remove(callback);
+ }
+ }
}
}
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index 7de2815eba6b..1d376b4bdfd5 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -3612,13 +3612,16 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
final long token = Binder.clearCallingIdentity();
try {
config = mCarrierConfigManager.getConfigForSubId(subId);
- tm = mContext.getSystemService(TelephonyManager.class);
+ tm = mContext.getSystemService(TelephonyManager.class).createForSubscriptionId(subId);
} finally {
Binder.restoreCallingIdentity(token);
}
- // First check: does caller have carrier privilege?
- if (tm != null && tm.hasCarrierPrivileges(subId)) {
+ // First check: does callingPackage have carrier privilege?
+ // Note that we can't call TelephonyManager.hasCarrierPrivileges() which will check if
+ // ourself has carrier privileges
+ if (tm != null && (tm.checkCarrierPrivilegesForPackage(callingPackage)
+ == TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS)) {
return;
}
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index daa1042bb255..b4a58ac72394 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -481,7 +481,8 @@ public class NotificationManagerService extends SystemService {
Adjustment.KEY_SENSITIVE_CONTENT,
Adjustment.KEY_RANKING_SCORE,
Adjustment.KEY_NOT_CONVERSATION,
- Adjustment.KEY_TYPE
+ Adjustment.KEY_TYPE,
+ Adjustment.KEY_SUMMARIZATION
};
static final Integer[] DEFAULT_ALLOWED_ADJUSTMENT_KEY_TYPES = new Integer[] {
@@ -631,6 +632,8 @@ public class NotificationManagerService extends SystemService {
// Minium number of sparse groups for a package before autogrouping them
private static final int AUTOGROUP_SPARSE_GROUPS_AT_COUNT = 3;
+ private static final Duration ZEN_BROADCAST_DELAY = Duration.ofMillis(250);
+
private IActivityManager mAm;
private ActivityTaskManagerInternal mAtm;
private ActivityManager mActivityManager;
@@ -3169,6 +3172,24 @@ public class NotificationManagerService extends SystemService {
sendRegisteredOnlyBroadcast(new Intent(action));
}
+ /**
+ * Schedules a broadcast to be sent to runtime receivers and DND-policy-access packages. The
+ * broadcast will be sent after {@link #ZEN_BROADCAST_DELAY}, unless a new broadcast is
+ * scheduled in the interim, in which case the previous one is dropped and the waiting period
+ * is <em>restarted</em>.
+ *
+ * <p>Note that this uses <em>equality of the {@link Intent#getAction}</em> as the criteria for
+ * deduplicating pending broadcasts, ignoring the extras and anything else. This is intentional
+ * so that e.g. rapidly changing some value A -> B -> C will only produce a broadcast for C
+ * (instead of every time because the extras are different).
+ */
+ private void sendZenBroadcastWithDelay(Intent intent) {
+ String token = "zen_broadcast:" + intent.getAction();
+ mHandler.removeCallbacksAndEqualMessages(token);
+ mHandler.postDelayed(() -> sendRegisteredOnlyBroadcast(intent), token,
+ ZEN_BROADCAST_DELAY.toMillis());
+ }
+
private void sendRegisteredOnlyBroadcast(Intent baseIntent) {
int[] userIds = mUmInternal.getProfileIds(mAmi.getCurrentUserId(), true);
if (Flags.nmBinderPerfReduceZenBroadcasts()) {
@@ -3362,14 +3383,25 @@ public class NotificationManagerService extends SystemService {
@GuardedBy("mNotificationLock")
private void updateEffectsSuppressorLocked() {
+ final long oldSuppressedEffects = mZenModeHelper.getSuppressedEffects();
final long updatedSuppressedEffects = calculateSuppressedEffects();
- if (updatedSuppressedEffects == mZenModeHelper.getSuppressedEffects()) return;
+ if (updatedSuppressedEffects == oldSuppressedEffects) return;
+
final List<ComponentName> suppressors = getSuppressors();
ZenLog.traceEffectsSuppressorChanged(
- mEffectsSuppressors, suppressors, updatedSuppressedEffects);
- mEffectsSuppressors = suppressors;
+ mEffectsSuppressors, suppressors, oldSuppressedEffects, updatedSuppressedEffects);
mZenModeHelper.setSuppressedEffects(updatedSuppressedEffects);
- sendRegisteredOnlyBroadcast(NotificationManager.ACTION_EFFECTS_SUPPRESSOR_CHANGED);
+
+ if (Flags.nmBinderPerfThrottleEffectsSuppressorBroadcast()) {
+ if (!suppressors.equals(mEffectsSuppressors)) {
+ mEffectsSuppressors = suppressors;
+ sendZenBroadcastWithDelay(
+ new Intent(NotificationManager.ACTION_EFFECTS_SUPPRESSOR_CHANGED));
+ }
+ } else {
+ mEffectsSuppressors = suppressors;
+ sendRegisteredOnlyBroadcast(NotificationManager.ACTION_EFFECTS_SUPPRESSOR_CHANGED);
+ }
}
private void exitIdle() {
@@ -3491,12 +3523,18 @@ public class NotificationManagerService extends SystemService {
}
private ArrayList<ComponentName> getSuppressors() {
- ArrayList<ComponentName> names = new ArrayList<ComponentName>();
+ ArrayList<ComponentName> names = new ArrayList<>();
for (int i = mListenersDisablingEffects.size() - 1; i >= 0; --i) {
ArraySet<ComponentName> serviceInfoList = mListenersDisablingEffects.valueAt(i);
for (ComponentName info : serviceInfoList) {
- names.add(info);
+ if (Flags.nmBinderPerfThrottleEffectsSuppressorBroadcast()) {
+ if (!names.contains(info)) {
+ names.add(info);
+ }
+ } else {
+ names.add(info);
+ }
}
}
@@ -5018,14 +5056,8 @@ public class NotificationManagerService extends SystemService {
}
@Override
- public ParceledListSlice<NotificationChannel> getNotificationChannels(
- String callingPkg, String targetPkg, int userId) {
- return getOrCreateNotificationChannels(callingPkg, targetPkg, userId, false);
- }
-
- @Override
- public ParceledListSlice<NotificationChannel> getOrCreateNotificationChannels(
- String callingPkg, String targetPkg, int userId, boolean createPrefsIfNeeded) {
+ public ParceledListSlice<NotificationChannel> getNotificationChannels(String callingPkg,
+ String targetPkg, int userId) {
if (canNotifyAsPackage(callingPkg, targetPkg, userId)
|| isCallingUidSystem()) {
int targetUid = -1;
@@ -5035,8 +5067,7 @@ public class NotificationManagerService extends SystemService {
/* ignore */
}
return mPreferencesHelper.getNotificationChannels(
- targetPkg, targetUid, false /* includeDeleted */, true,
- createPrefsIfNeeded);
+ targetPkg, targetUid, false /* includeDeleted */, true);
}
throw new SecurityException("Pkg " + callingPkg
+ " cannot read channels for " + targetPkg + " in " + userId);
@@ -7212,7 +7243,7 @@ public class NotificationManagerService extends SystemService {
if (!mAssistants.isAdjustmentAllowed(potentialKey)) {
toRemove.add(potentialKey);
}
- if (notificationClassification() && adjustments.containsKey(KEY_TYPE)) {
+ if (notificationClassification() && potentialKey.equals(KEY_TYPE)) {
mAssistants.setNasUnsupportedDefaults(r.getSbn().getNormalizedUserId());
if (!mAssistants.isAdjustmentKeyTypeAllowed(adjustments.getInt(KEY_TYPE))) {
toRemove.add(potentialKey);
@@ -8467,6 +8498,9 @@ public class NotificationManagerService extends SystemService {
(userId == USER_ALL) ? USER_SYSTEM : userId);
Notification.addFieldsFromContext(ai, notification);
+ // can't be set by an app
+ notification.extras.remove(Notification.EXTRA_SUMMARIZED_CONTENT);
+
if (notification.isForegroundService() && fgsPolicy == NOT_FOREGROUND_SERVICE) {
notification.flags &= ~FLAG_FOREGROUND_SERVICE;
}
@@ -10395,7 +10429,8 @@ public class NotificationManagerService extends SystemService {
r.getRankingScore(),
r.isConversation(),
r.getProposedImportance(),
- r.hasSensitiveContent());
+ r.hasSensitiveContent(),
+ r.getSummarization());
extractorDataBefore.put(r.getKey(), extractorData);
mRankingHelper.extractSignals(r);
}
@@ -11715,7 +11750,8 @@ public class NotificationManagerService extends SystemService {
: (record.getRankingScore() > 0 ? RANKING_PROMOTED : RANKING_DEMOTED),
record.getNotification().isBubbleNotification(),
record.getProposedImportance(),
- hasSensitiveContent
+ hasSensitiveContent,
+ record.getSummarization()
);
rankings.add(ranking);
}
diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java
index 81af0d8a6d80..52101e336920 100644
--- a/services/core/java/com/android/server/notification/NotificationRecord.java
+++ b/services/core/java/com/android/server/notification/NotificationRecord.java
@@ -25,6 +25,7 @@ import static android.app.NotificationManager.IMPORTANCE_HIGH;
import static android.app.NotificationManager.IMPORTANCE_LOW;
import static android.app.NotificationManager.IMPORTANCE_MIN;
import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED;
+import static android.service.notification.Adjustment.KEY_SUMMARIZATION;
import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEUTRAL;
import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_POSITIVE;
@@ -225,6 +226,8 @@ public final class NotificationRecord {
// type of the bundle if the notification was classified
private @Adjustment.Types int mBundleType = Adjustment.TYPE_OTHER;
+ private String mSummarization = null;
+
public NotificationRecord(Context context, StatusBarNotification sbn,
NotificationChannel channel) {
this.sbn = sbn;
@@ -589,6 +592,7 @@ public final class NotificationRecord {
pw.println(prefix + "shortcut=" + notification.getShortcutId()
+ " found valid? " + (mShortcutInfo != null));
pw.println(prefix + "mUserVisOverride=" + getPackageVisibilityOverride());
+ pw.println(prefix + "hasSummarization=" + (mSummarization != null));
}
private void dumpNotification(PrintWriter pw, String prefix, Notification notification,
@@ -811,6 +815,12 @@ public final class NotificationRecord {
Adjustment.KEY_TYPE,
mChannel.getId());
}
+ if ((android.app.Flags.nmSummarizationUi() || android.app.Flags.nmSummarization())
+ && signals.containsKey(KEY_SUMMARIZATION)) {
+ mSummarization = signals.getString(KEY_SUMMARIZATION);
+ EventLogTags.writeNotificationAdjusted(getKey(),
+ KEY_SUMMARIZATION, Boolean.toString(mSummarization != null));
+ }
if (!signals.isEmpty() && adjustment.getIssuer() != null) {
mAdjustmentIssuer = adjustment.getIssuer();
}
@@ -983,6 +993,13 @@ public final class NotificationRecord {
return null;
}
+ public String getSummarization() {
+ if ((android.app.Flags.nmSummarizationUi() || android.app.Flags.nmSummarization())) {
+ return mSummarization;
+ }
+ return null;
+ }
+
public boolean setIntercepted(boolean intercept) {
mIntercept = intercept;
mInterceptSet = true;
diff --git a/services/core/java/com/android/server/notification/NotificationRecordExtractorData.java b/services/core/java/com/android/server/notification/NotificationRecordExtractorData.java
index 3f4f7d3bbc38..9315ddc0d5b0 100644
--- a/services/core/java/com/android/server/notification/NotificationRecordExtractorData.java
+++ b/services/core/java/com/android/server/notification/NotificationRecordExtractorData.java
@@ -47,6 +47,7 @@ public final class NotificationRecordExtractorData {
private final boolean mIsConversation;
private final int mProposedImportance;
private final boolean mSensitiveContent;
+ private final String mSummarization;
NotificationRecordExtractorData(int position, int visibility, boolean showBadge,
boolean allowBubble, boolean isBubble, NotificationChannel channel, String groupKey,
@@ -54,7 +55,8 @@ public final class NotificationRecordExtractorData {
Integer userSentiment, Integer suppressVisually,
ArrayList<Notification.Action> systemSmartActions,
ArrayList<CharSequence> smartReplies, int importance, float rankingScore,
- boolean isConversation, int proposedImportance, boolean sensitiveContent) {
+ boolean isConversation, int proposedImportance, boolean sensitiveContent,
+ String summarization) {
mPosition = position;
mVisibility = visibility;
mShowBadge = showBadge;
@@ -73,6 +75,7 @@ public final class NotificationRecordExtractorData {
mIsConversation = isConversation;
mProposedImportance = proposedImportance;
mSensitiveContent = sensitiveContent;
+ mSummarization = summarization;
}
// Returns whether the provided NotificationRecord differs from the cached data in any way.
@@ -93,7 +96,8 @@ public final class NotificationRecordExtractorData {
|| !Objects.equals(mSmartReplies, r.getSmartReplies())
|| mImportance != r.getImportance()
|| mProposedImportance != r.getProposedImportance()
- || mSensitiveContent != r.hasSensitiveContent();
+ || mSensitiveContent != r.hasSensitiveContent()
+ || !Objects.equals(mSummarization, r.getSummarization());
}
// Returns whether the NotificationRecord has a change from this data for which we should
@@ -117,6 +121,7 @@ public final class NotificationRecordExtractorData {
|| !r.rankingScoreMatches(mRankingScore)
|| mIsConversation != r.isConversation()
|| mProposedImportance != r.getProposedImportance()
- || mSensitiveContent != r.hasSensitiveContent();
+ || mSensitiveContent != r.hasSensitiveContent()
+ || !Objects.equals(mSummarization, r.getSummarization());
}
}
diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
index 3b34dcd17705..14cc91b6305f 100644
--- a/services/core/java/com/android/server/notification/PreferencesHelper.java
+++ b/services/core/java/com/android/server/notification/PreferencesHelper.java
@@ -16,8 +16,8 @@
package com.android.server.notification;
-import static android.app.Flags.notificationClassificationUi;
import static android.app.AppOpsManager.OP_SYSTEM_ALERT_WINDOW;
+import static android.app.Flags.notificationClassificationUi;
import static android.app.NotificationChannel.DEFAULT_CHANNEL_ID;
import static android.app.NotificationChannel.NEWS_ID;
import static android.app.NotificationChannel.PLACEHOLDER_CONVERSATION_ID;
@@ -89,9 +89,10 @@ import android.util.SparseBooleanArray;
import android.util.StatsEvent;
import android.util.proto.ProtoOutputStream;
+import androidx.annotation.VisibleForTesting;
+
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
-import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags;
import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags;
import com.android.internal.logging.MetricsLogger;
@@ -1962,21 +1963,11 @@ public class PreferencesHelper implements RankingConfig {
@Override
public ParceledListSlice<NotificationChannel> getNotificationChannels(String pkg, int uid,
boolean includeDeleted, boolean includeBundles) {
- return getNotificationChannels(pkg, uid, includeDeleted, includeBundles, false);
- }
-
- protected ParceledListSlice<NotificationChannel> getNotificationChannels(String pkg, int uid,
- boolean includeDeleted, boolean includeBundles, boolean createPrefsIfNeeded) {
- if (createPrefsIfNeeded && !android.app.Flags.nmBinderPerfCacheChannels()) {
- Slog.wtf(TAG,
- "getNotificationChannels called with createPrefsIfNeeded=true and flag off");
- createPrefsIfNeeded = false;
- }
Objects.requireNonNull(pkg);
List<NotificationChannel> channels = new ArrayList<>();
synchronized (mLock) {
PackagePreferences r;
- if (createPrefsIfNeeded) {
+ if (android.app.Flags.nmBinderPerfCacheChannels()) {
r = getOrCreatePackagePreferencesLocked(pkg, uid);
} else {
r = getPackagePreferencesLocked(pkg, uid);
@@ -1997,6 +1988,18 @@ public class PreferencesHelper implements RankingConfig {
}
}
+ @VisibleForTesting(otherwise = VisibleForTesting.NONE)
+ // Gets the entire list of notification channels for this package, with no filtering and
+ // without creating package preferences. For testing only, specifically to confirm the
+ // notification channels of a removed/deleted package.
+ protected List<NotificationChannel> getRemovedPkgNotificationChannels(String pkg, int uid) {
+ PackagePreferences r = getPackagePreferencesLocked(pkg, uid);
+ if (r == null || r.channels == null) {
+ return new ArrayList<>();
+ }
+ return new ArrayList<>(r.channels.values());
+ }
+
/**
* Gets all notification channels associated with the given pkg and uid that can bypass dnd
*/
diff --git a/services/core/java/com/android/server/notification/ZenLog.java b/services/core/java/com/android/server/notification/ZenLog.java
index 7e853d9d2d0b..49f93b8b7c16 100644
--- a/services/core/java/com/android/server/notification/ZenLog.java
+++ b/services/core/java/com/android/server/notification/ZenLog.java
@@ -140,8 +140,9 @@ public class ZenLog {
}
public static void traceEffectsSuppressorChanged(List<ComponentName> oldSuppressors,
- List<ComponentName> newSuppressors, long suppressedEffects) {
- append(TYPE_SUPPRESSOR_CHANGED, "suppressed effects:" + suppressedEffects + ","
+ List<ComponentName> newSuppressors, long oldSuppressedEffects, long suppressedEffects) {
+ append(TYPE_SUPPRESSOR_CHANGED, "suppressed effects:"
+ + oldSuppressedEffects + "->" + suppressedEffects + ","
+ componentListToString(oldSuppressors) + "->"
+ componentListToString(newSuppressors));
}
diff --git a/services/core/java/com/android/server/notification/flags.aconfig b/services/core/java/com/android/server/notification/flags.aconfig
index 822ff48c831c..048f2b6b0cbc 100644
--- a/services/core/java/com/android/server/notification/flags.aconfig
+++ b/services/core/java/com/android/server/notification/flags.aconfig
@@ -182,6 +182,16 @@ flag {
}
flag {
+ name: "nm_binder_perf_throttle_effects_suppressor_broadcast"
+ namespace: "systemui"
+ description: "Delay sending the ACTION_EFFECTS_SUPPRESSOR_CHANGED broadcast if it changes too often"
+ bug: "371776935"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "fix_calling_uid_from_cps"
namespace: "systemui"
description: "Correctly checks zen rule ownership when a CPS notifies with a Condition"
diff --git a/services/core/java/com/android/server/pm/DexOptHelper.java b/services/core/java/com/android/server/pm/DexOptHelper.java
index 0b58c759b284..f011d283c8bb 100644
--- a/services/core/java/com/android/server/pm/DexOptHelper.java
+++ b/services/core/java/com/android/server/pm/DexOptHelper.java
@@ -96,6 +96,9 @@ import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.function.Predicate;
@@ -105,6 +108,11 @@ import java.util.function.Predicate;
public final class DexOptHelper {
private static final long SEVEN_DAYS_IN_MILLISECONDS = 7 * 24 * 60 * 60 * 1000;
+ @NonNull
+ private static final ThreadPoolExecutor sDexoptExecutor =
+ new ThreadPoolExecutor(1 /* corePoolSize */, 1 /* maximumPoolSize */,
+ 60 /* keepAliveTime */, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());
+
private static boolean sArtManagerLocalIsInitialized = false;
private final PackageManagerService mPm;
@@ -113,6 +121,11 @@ public final class DexOptHelper {
// used, to make it available to the onDexoptDone callback.
private volatile long mBootDexoptStartTime;
+ static {
+ // Recycle the thread if it's not used for `keepAliveTime`.
+ sDexoptExecutor.allowsCoreThreadTimeOut();
+ }
+
DexOptHelper(PackageManagerService pm) {
mPm = pm;
}
@@ -746,43 +759,11 @@ public final class DexOptHelper {
*/
static void performDexoptIfNeeded(InstallRequest installRequest, DexManager dexManager,
Context context, PackageManagerTracedLock.RawLock installLock) {
-
// Construct the DexoptOptions early to see if we should skip running dexopt.
- //
- // Do not run PackageDexOptimizer through the local performDexOpt
- // method because `pkg` may not be in `mPackages` yet.
- //
- // Also, don't fail application installs if the dexopt step fails.
DexoptOptions dexoptOptions = getDexoptOptionsByInstallRequest(installRequest, dexManager);
- // Check whether we need to dexopt the app.
- //
- // NOTE: it is IMPORTANT to call dexopt:
- // - after doRename which will sync the package data from AndroidPackage and
- // its corresponding ApplicationInfo.
- // - after installNewPackageLIF or replacePackageLIF which will update result with the
- // uid of the application (pkg.applicationInfo.uid).
- // This update happens in place!
- //
- // We only need to dexopt if the package meets ALL of the following conditions:
- // 1) it is not an instant app or if it is then dexopt is enabled via gservices.
- // 2) it is not debuggable.
- // 3) it is not on Incremental File System.
- //
- // Note that we do not dexopt instant apps by default. dexopt can take some time to
- // complete, so we skip this step during installation. Instead, we'll take extra time
- // the first time the instant app starts. It's preferred to do it this way to provide
- // continuous progress to the useur instead of mysteriously blocking somewhere in the
- // middle of running an instant app. The default behaviour can be overridden
- // via gservices.
- //
- // Furthermore, dexopt may be skipped, depending on the install scenario and current
- // state of the device.
- //
- // TODO(b/174695087): instantApp and onIncremental should be removed and their install
- // path moved to SCENARIO_FAST.
+ boolean performDexopt =
+ DexOptHelper.shouldPerformDexopt(installRequest, dexoptOptions, context);
- final boolean performDexopt = DexOptHelper.shouldPerformDexopt(installRequest,
- dexoptOptions, context);
if (performDexopt) {
// dexopt can take long, and ArtService doesn't require installd, so we release
// the lock here and re-acquire the lock after dexopt is finished.
@@ -791,6 +772,7 @@ public final class DexOptHelper {
}
try {
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "dexopt");
+ // Don't fail application installs if the dexopt step fails.
DexoptResult dexOptResult = DexOptHelper.dexoptPackageUsingArtService(
installRequest, dexoptOptions);
installRequest.onDexoptFinished(dexOptResult);
@@ -804,6 +786,41 @@ public final class DexOptHelper {
}
/**
+ * Same as above, but runs asynchronously.
+ */
+ static CompletableFuture<Void> performDexoptIfNeededAsync(InstallRequest installRequest,
+ DexManager dexManager, Context context) {
+ // Construct the DexoptOptions early to see if we should skip running dexopt.
+ DexoptOptions dexoptOptions = getDexoptOptionsByInstallRequest(installRequest, dexManager);
+ boolean performDexopt =
+ DexOptHelper.shouldPerformDexopt(installRequest, dexoptOptions, context);
+
+ if (performDexopt) {
+ return CompletableFuture
+ .runAsync(() -> {
+ try {
+ Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "dexopt");
+ // Don't fail application installs if the dexopt step fails.
+ // TODO(jiakaiz): Make this async in ART Service.
+ DexoptResult dexOptResult = DexOptHelper.dexoptPackageUsingArtService(
+ installRequest, dexoptOptions);
+ installRequest.onDexoptFinished(dexOptResult);
+ } finally {
+ Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
+ }
+ }, sDexoptExecutor)
+ .exceptionally((t) -> {
+ // This should never happen. A normal dexopt failure should result in a
+ // DexoptResult.DEXOPT_FAILED, not an exception.
+ Slog.wtf(TAG, "Dexopt encountered a fatal error", t);
+ return null;
+ });
+ } else {
+ return CompletableFuture.completedFuture(null);
+ }
+ }
+
+ /**
* Use ArtService to perform dexopt by the given InstallRequest.
*/
static DexoptResult dexoptPackageUsingArtService(InstallRequest installRequest,
@@ -840,6 +857,20 @@ public final class DexOptHelper {
*/
static boolean shouldPerformDexopt(InstallRequest installRequest, DexoptOptions dexoptOptions,
Context context) {
+ // We only need to dexopt if the package meets ALL of the following conditions:
+ // 1) it is not an instant app or if it is then dexopt is enabled via gservices.
+ // 2) it is not debuggable.
+ // 3) it is not on Incremental File System.
+ //
+ // Note that we do not dexopt instant apps by default. dexopt can take some time to
+ // complete, so we skip this step during installation. Instead, we'll take extra time
+ // the first time the instant app starts. It's preferred to do it this way to provide
+ // continuous progress to the user instead of mysteriously blocking somewhere in the
+ // middle of running an instant app. The default behaviour can be overridden
+ // via gservices.
+ //
+ // Furthermore, dexopt may be skipped, depending on the install scenario and current
+ // state of the device.
final boolean isApex = ((installRequest.getScanFlags() & SCAN_AS_APEX) != 0);
final boolean instantApp = ((installRequest.getScanFlags() & SCAN_AS_INSTANT_APP) != 0);
final PackageSetting ps = installRequest.getScannedPackageSetting();
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 8eb5b6f11cb2..0c2782393879 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -1158,6 +1158,7 @@ final class InstallPackageHelper {
return;
}
request.setKeepArtProfile(true);
+ // TODO(b/388159696): Use performDexoptIfNeededAsync.
DexOptHelper.performDexoptIfNeeded(request, mDexManager, mContext, null);
}
}
diff --git a/services/core/java/com/android/server/pm/UserManagerInternal.java b/services/core/java/com/android/server/pm/UserManagerInternal.java
index 14b0fc81fdd2..c62aaebf673b 100644
--- a/services/core/java/com/android/server/pm/UserManagerInternal.java
+++ b/services/core/java/com/android/server/pm/UserManagerInternal.java
@@ -584,6 +584,12 @@ public abstract class UserManagerInternal {
* Returns the user id of the main user, or {@link android.os.UserHandle#USER_NULL} if there is
* no main user.
*
+ * <p>NB: Features should ideally not limit functionality to the main user. Ideally, they
+ * should either work for all users or for all admin users. If a feature should only work for
+ * select users, its determination of which user should be done intelligently or be
+ * customizable. Not all devices support a main user, and the idea of singling out one user as
+ * special is contrary to overall multiuser goals.
+ *
* @see UserManager#isMainUser()
*/
public abstract @UserIdInt int getMainUserId();
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index a2c53e56b9c9..8cbccf5feead 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -3590,8 +3590,6 @@ public class UserManagerService extends IUserManager.Stub {
}
/**
- * @hide
- *
* Returns who set a user restriction on a user.
* Requires {@link android.Manifest.permission#MANAGE_USERS} permission.
* @param restrictionKey the string key representing the restriction
@@ -6275,9 +6273,6 @@ public class UserManagerService extends IUserManager.Stub {
}
}
- /**
- * @hide
- */
@Override
public @NonNull UserInfo createRestrictedProfileWithThrow(
@Nullable String name, @UserIdInt int parentUserId)
@@ -8504,7 +8499,6 @@ public class UserManagerService extends IUserManager.Stub {
}
/**
- * @hide
* Checks whether to show a notification for sounds (e.g., alarms, timers, etc.) from
* background users.
*/
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 7f511e1e2aa1..283979483e73 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -3801,10 +3801,6 @@ public class PhoneWindowManager implements WindowManagerPolicy {
true /* leftOrTop */);
notifyKeyGestureCompleted(event,
KeyGestureEvent.KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_LEFT);
- } else if (event.isAltPressed()) {
- setSplitscreenFocus(true /* leftOrTop */);
- notifyKeyGestureCompleted(event,
- KeyGestureEvent.KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_LEFT);
} else {
notifyKeyGestureCompleted(event,
KeyGestureEvent.KEY_GESTURE_TYPE_BACK);
@@ -3821,11 +3817,6 @@ public class PhoneWindowManager implements WindowManagerPolicy {
notifyKeyGestureCompleted(event,
KeyGestureEvent.KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_RIGHT);
return true;
- } else if (event.isAltPressed()) {
- setSplitscreenFocus(false /* leftOrTop */);
- notifyKeyGestureCompleted(event,
- KeyGestureEvent.KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_RIGHT);
- return true;
}
}
break;
@@ -4241,9 +4232,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
case KeyGestureEvent.KEY_GESTURE_TYPE_MULTI_WINDOW_NAVIGATION:
case KeyGestureEvent.KEY_GESTURE_TYPE_DESKTOP_MODE:
case KeyGestureEvent.KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_LEFT:
- case KeyGestureEvent.KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_LEFT:
case KeyGestureEvent.KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_RIGHT:
- case KeyGestureEvent.KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_RIGHT:
case KeyGestureEvent.KEY_GESTURE_TYPE_OPEN_SHORTCUT_HELPER:
case KeyGestureEvent.KEY_GESTURE_TYPE_BRIGHTNESS_UP:
case KeyGestureEvent.KEY_GESTURE_TYPE_BRIGHTNESS_DOWN:
@@ -4379,22 +4368,12 @@ public class PhoneWindowManager implements WindowManagerPolicy {
true /* leftOrTop */);
}
return true;
- case KeyGestureEvent.KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_LEFT:
- if (complete) {
- setSplitscreenFocus(true /* leftOrTop */);
- }
- return true;
case KeyGestureEvent.KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_RIGHT:
if (complete) {
moveFocusedTaskToStageSplit(getTargetDisplayIdForKeyGestureEvent(event),
false /* leftOrTop */);
}
return true;
- case KeyGestureEvent.KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_RIGHT:
- if (complete) {
- setSplitscreenFocus(false /* leftOrTop */);
- }
- return true;
case KeyGestureEvent.KEY_GESTURE_TYPE_OPEN_SHORTCUT_HELPER:
if (complete) {
toggleKeyboardShortcutsMenu(deviceId);
@@ -5084,13 +5063,6 @@ public class PhoneWindowManager implements WindowManagerPolicy {
}
}
- private void setSplitscreenFocus(boolean leftOrTop) {
- StatusBarManagerInternal statusbar = getStatusBarManagerInternal();
- if (statusbar != null) {
- statusbar.setSplitscreenFocus(leftOrTop);
- }
- }
-
void launchHomeFromHotKey(int displayId) {
launchHomeFromHotKey(displayId, true /* awakenFromDreams */, true /*respectKeyguard*/);
}
diff --git a/services/core/java/com/android/server/policy/SingleKeyGestureDetector.java b/services/core/java/com/android/server/policy/SingleKeyGestureDetector.java
index 441d3eaf2348..142d919da455 100644
--- a/services/core/java/com/android/server/policy/SingleKeyGestureDetector.java
+++ b/services/core/java/com/android/server/policy/SingleKeyGestureDetector.java
@@ -24,6 +24,8 @@ import android.util.Log;
import android.view.KeyEvent;
import android.view.ViewConfiguration;
+import com.android.hardware.input.Flags;
+
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -355,6 +357,19 @@ public final class SingleKeyGestureDetector {
}
if (event.getKeyCode() == mActiveRule.mKeyCode) {
+ if (Flags.abortSlowMultiPress()
+ && (event.getEventTime() - mLastDownTime
+ >= mActiveRule.getLongPressTimeoutMs())) {
+ // In this case, we are either on a first long press (but long press behavior is not
+ // supported for this rule), or, on a non-first press that is at least as long as
+ // the long-press duration. Thus, we will cancel the multipress gesture.
+ if (DEBUG) {
+ Log.d(TAG, "The duration of the press is too slow. Resetting.");
+ }
+ reset();
+ return false;
+ }
+
// key-up action should always be triggered if not processed by long press.
MessageObject object = new MessageObject(mActiveRule, mActiveRule.mKeyCode,
mKeyPressCounter, event);
diff --git a/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java b/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java
index 977c6db66106..a5185a2139db 100644
--- a/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java
+++ b/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java
@@ -38,6 +38,7 @@ import com.android.internal.os.CpuScalingPolicies;
import com.android.internal.os.MonotonicClock;
import com.android.internal.os.PowerProfile;
import com.android.internal.util.ArrayUtils;
+import com.android.server.power.optimization.Flags;
import com.android.server.power.stats.BatteryStatsImpl.BatteryStatsSession;
import java.io.PrintWriter;
@@ -351,7 +352,7 @@ public class BatteryUsageStatsProvider {
accumulatedStats.endMonotonicTime = endMonotonicTime;
accumulatedStats.builder.setStatsEndTimestamp(endWallClockTime);
- accumulatedStats.builder.setStatsDuration(endWallClockTime - startMonotonicTime);
+ accumulatedStats.builder.setStatsDuration(endMonotonicTime - startMonotonicTime);
mPowerAttributor.estimatePowerConsumption(accumulatedStats.builder, session.getHistory(),
startMonotonicTime, endMonotonicTime);
@@ -403,7 +404,10 @@ public class BatteryUsageStatsProvider {
}
if ((query.getFlags()
& BatteryUsageStatsQuery.FLAG_BATTERY_USAGE_STATS_INCLUDE_HISTORY) != 0) {
- batteryUsageStatsBuilder.setBatteryHistory(session.getHistory().copy());
+ batteryUsageStatsBuilder.setBatteryHistory(session.getHistory().copy(),
+ Flags.extendedBatteryHistoryContinuousCollectionEnabled()
+ ? query.getPreferredHistoryDurationMs()
+ : Long.MAX_VALUE);
}
mPowerAttributor.estimatePowerConsumption(batteryUsageStatsBuilder, session.getHistory(),
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
index 4827c9f414ad..0ed522805bef 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
@@ -56,12 +56,12 @@ public interface StatusBarManagerInternal {
void toggleKeyboardShortcutsMenu(int deviceId);
/**
- * Used by InputMethodManagerService to notify the IME status.
+ * Sets the new IME window status.
*
- * @param displayId The display to which the IME is bound to.
- * @param vis The IME visibility.
- * @param backDisposition The IME back disposition.
- * @param showImeSwitcher {@code true} when the IME switcher button should be shown.
+ * @param displayId The id of the display to which the IME is bound.
+ * @param vis The IME window visibility.
+ * @param backDisposition The IME back disposition mode.
+ * @param showImeSwitcher Whether the IME Switcher button should be shown.
*/
void setImeWindowStatus(int displayId, @ImeWindowVisibility int vis,
@BackDispositionMode int backDisposition, boolean showImeSwitcher);
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerInternal.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerInternal.java
index 25c07500b891..872ab595994b 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerInternal.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerInternal.java
@@ -28,6 +28,9 @@ public abstract class WallpaperManagerInternal {
*/
public abstract void onDisplayReady(int displayId);
+ /** Notifies when display stop showing system decorations and wallpaper. */
+ public abstract void onDisplayRemoveSystemDecorations(int displayId);
+
/** Notifies when the screen finished turning on and is visible to the user. */
public abstract void onScreenTurnedOn(int displayId);
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index a8deeeac311d..db530e728a1a 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -118,6 +118,7 @@ import android.service.wallpaper.WallpaperService;
import android.system.ErrnoException;
import android.system.Os;
import android.text.TextUtils;
+import android.util.ArraySet;
import android.util.EventLog;
import android.util.IntArray;
import android.util.Slog;
@@ -160,6 +161,7 @@ import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
+import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Predicate;
@@ -665,71 +667,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
@Override
public void onDisplayRemoved(int displayId) {
- synchronized (mLock) {
- if (enableConnectedDisplaysWallpaper()) {
- // There could be at most 2 wallpaper connections per display:
- // 1. system & lock are the same: mLastWallpaper
- // 2. system, lock are different: mLastWallpaper, mLastLockWallpaper
- // 3. fallback used as both system & lock wallpaper: mFallbackWallpaper
- // 4. fallback used as lock only wallpaper: mFallbackWallpaper,
- // mLastWallpaper
- // 5. fallback used as system only wallpaper: mFallbackWallpaper,
- // mLastLockWallpaper
- List<WallpaperData> pendingDisconnectWallpapers = new ArrayList<>();
- if (mLastWallpaper != null && mLastWallpaper.connection != null
- && mLastWallpaper.connection.containsDisplay(displayId)) {
- pendingDisconnectWallpapers.add(mLastWallpaper);
- }
- if (mLastLockWallpaper != null && mLastLockWallpaper.connection != null
- && mLastLockWallpaper.connection.containsDisplay(displayId)) {
- pendingDisconnectWallpapers.add(mLastLockWallpaper);
- }
- if (mFallbackWallpaper != null && mFallbackWallpaper.connection != null
- && mFallbackWallpaper.connection.containsDisplay(displayId)) {
- pendingDisconnectWallpapers.add(mFallbackWallpaper);
- }
- for (int i = 0; i < pendingDisconnectWallpapers.size(); i++) {
- WallpaperData wallpaper = pendingDisconnectWallpapers.get(i);
- DisplayConnector displayConnector =
- wallpaper.connection.getDisplayConnectorOrCreate(displayId);
- if (displayConnector == null) {
- Slog.w(TAG,
- "Fail to disconnect wallpaper upon display removal");
- return;
- }
- displayConnector.disconnectLocked(wallpaper.connection);
- wallpaper.connection.removeDisplayConnector(displayId);
- }
- } else {
- if (mLastWallpaper != null) {
- WallpaperData targetWallpaper = null;
- if (mLastWallpaper.connection != null
- && mLastWallpaper.connection.containsDisplay(displayId)) {
- targetWallpaper = mLastWallpaper;
- } else if (mFallbackWallpaper != null
- && mFallbackWallpaper.connection != null
- && mFallbackWallpaper.connection.containsDisplay(
- displayId)) {
- targetWallpaper = mFallbackWallpaper;
- }
- if (targetWallpaper == null) return;
- DisplayConnector connector =
- targetWallpaper.connection.getDisplayConnectorOrCreate(
- displayId);
- if (connector == null) return;
- connector.disconnectLocked(targetWallpaper.connection);
- targetWallpaper.connection.removeDisplayConnector(displayId);
- }
- }
-
- mWallpaperDisplayHelper.removeDisplayData(displayId);
-
- for (int i = mColorsChangedListeners.size() - 1; i >= 0; i--) {
- final SparseArray<RemoteCallbackList<IWallpaperManagerCallback>> callbacks =
- mColorsChangedListeners.valueAt(i);
- callbacks.delete(displayId);
- }
- }
+ onDisplayRemovedInternal(displayId);
}
@Override
@@ -772,6 +710,12 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
private final ComponentName mImageWallpaper;
/**
+ * Name of the component that is used when the user-selected wallpaper is incompatible with the
+ * display's resolution or aspect ratio.
+ */
+ @Nullable private final ComponentName mFallbackWallpaperComponent;
+
+ /**
* Default image wallpaper shall never changed after system service started, caching it when we
* first read the image file.
*/
@@ -799,12 +743,38 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
final WallpaperDisplayHelper mWallpaperDisplayHelper;
final WallpaperCropper mWallpaperCropper;
- private boolean supportsMultiDisplay(WallpaperConnection connection) {
- if (connection != null) {
- return connection.mInfo == null // This is image wallpaper
- || connection.mInfo.supportsMultipleDisplays();
+ // TODO(b/384519749): Remove this set after we introduce the aspect ratio check.
+ private final Set<Integer> mWallpaperCompatibleDisplaysForTest = new ArraySet<>();
+
+ private boolean isWallpaperCompatibleForDisplay(int displayId, WallpaperConnection connection) {
+ if (connection == null) {
+ return false;
}
- return false;
+ // Non image wallpaper.
+ if (connection.mInfo != null) {
+ return connection.mInfo.supportsMultipleDisplays();
+ }
+
+ // Image wallpaper
+ if (enableConnectedDisplaysWallpaper()) {
+ // TODO(b/384519749): check display's resolution and image wallpaper cropped image
+ // aspect ratio.
+ return displayId == DEFAULT_DISPLAY
+ || mWallpaperCompatibleDisplaysForTest.contains(displayId);
+ }
+ // When enableConnectedDisplaysWallpaper is off, we assume the image wallpaper supports all
+ // usable displays.
+ return true;
+ }
+
+ @VisibleForTesting
+ void addWallpaperCompatibleDisplayForTest(int displayId) {
+ mWallpaperCompatibleDisplaysForTest.add(displayId);
+ }
+
+ @VisibleForTesting
+ void removeWallpaperCompatibleDisplayForTest(int displayId) {
+ mWallpaperCompatibleDisplaysForTest.remove(displayId);
}
private void updateFallbackConnection() {
@@ -815,7 +785,9 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
Slog.w(TAG, "Fallback wallpaper connection has not been created yet!!");
return;
}
- if (supportsMultiDisplay(systemConnection)) {
+ // TODO(b/384520326) Passing DEFAULT_DISPLAY temporarily before we revamp the
+ // multi-display supports.
+ if (isWallpaperCompatibleForDisplay(DEFAULT_DISPLAY, systemConnection)) {
if (fallbackConnection.mDisplayConnector.size() != 0) {
fallbackConnection.forEachDisplayConnector(connector -> {
if (connector.mEngine != null) {
@@ -990,16 +962,14 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
private void initDisplayState() {
// Do not initialize fallback wallpaper
if (!mWallpaper.equals(mFallbackWallpaper)) {
- if (supportsMultiDisplay(this)) {
- // The system wallpaper is image wallpaper or it can supports multiple displays.
- appendConnectorWithCondition(display ->
- mWallpaperDisplayHelper.isUsableDisplay(display, mClientUid));
- } else {
- // The system wallpaper does not support multiple displays, so just attach it on
- // default display.
- mDisplayConnector.append(DEFAULT_DISPLAY,
- new DisplayConnector(DEFAULT_DISPLAY));
- }
+ appendConnectorWithCondition(display -> {
+ final int displayId = display.getDisplayId();
+ if (display.getDisplayId() == DEFAULT_DISPLAY) {
+ return true;
+ }
+ return mWallpaperDisplayHelper.isUsableDisplay(display, mClientUid)
+ && isWallpaperCompatibleForDisplay(displayId, /* connection= */ this);
+ });
}
}
@@ -1601,6 +1571,12 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
mShuttingDown = false;
mImageWallpaper = ComponentName.unflattenFromString(
context.getResources().getString(R.string.image_wallpaper_component));
+ if (enableConnectedDisplaysWallpaper()) {
+ mFallbackWallpaperComponent = ComponentName.unflattenFromString(
+ context.getResources().getString(R.string.fallback_wallpaper_component));
+ } else {
+ mFallbackWallpaperComponent = null;
+ }
ComponentName defaultComponent = WallpaperManager.getCmfDefaultWallpaperComponent(context);
mDefaultWallpaperComponent = defaultComponent == null ? mImageWallpaper : defaultComponent;
mWindowManagerInternal = LocalServices.getService(WindowManagerInternal.class);
@@ -1665,6 +1641,13 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
}
@Override
+ public void onDisplayRemoveSystemDecorations(int displayId) {
+ // The display mirroring starts. The handling logic is the same as when removing a
+ // display.
+ onDisplayRemovedInternal(displayId);
+ }
+
+ @Override
public void onScreenTurnedOn(int displayId) {
notifyScreenTurnedOn(displayId);
}
@@ -3613,6 +3596,13 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
if (componentName != null && !componentName.equals(mImageWallpaper)) {
// The requested component is not the static wallpaper service, so make sure it's
// actually a wallpaper service.
+ if (mFallbackWallpaperComponent != null
+ && componentName.equals(mFallbackWallpaperComponent)) {
+ // The fallback wallpaper does not declare WallpaperService.SERVICE_INTERFACE
+ // action in its intent filter to prevent it from being listed in the wallpaper
+ // picker. And thus, use explicit intent to query the metadata.
+ intent = new Intent().setComponent(mFallbackWallpaperComponent);
+ }
List<ResolveInfo> ris =
mIPackageManager.queryIntentServices(intent,
intent.resolveTypeIfNeeded(mContext.getContentResolver()),
@@ -3971,7 +3961,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
for (int i = 0; i < wallpapers.size(); i++) {
WallpaperData wallpaper = wallpapers.get(i);
- if (supportsMultiDisplay(wallpaper.connection)) {
+ if (isWallpaperCompatibleForDisplay(displayId, wallpaper.connection)) {
final DisplayConnector connector =
wallpaper.connection.getDisplayConnectorOrCreate(displayId);
if (connector != null) {
@@ -3993,7 +3983,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
}
mFallbackWallpaper.mWhich = useFallbackWallpaperWhich;
} else {
- if (supportsMultiDisplay(mLastWallpaper.connection)) {
+ if (isWallpaperCompatibleForDisplay(displayId, mLastWallpaper.connection)) {
final DisplayConnector connector =
mLastWallpaper.connection.getDisplayConnectorOrCreate(displayId);
if (connector == null) return;
@@ -4016,6 +4006,78 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
}
}
+ // This method may be called even if the display is not being removed from the system.
+ // This can be called when the display is removed, or when the display system decorations are
+ // removed to start mirroring.
+ private void onDisplayRemovedInternal(int displayId) {
+ synchronized (mLock) {
+ if (enableConnectedDisplaysWallpaper()) {
+ // There could be at most 2 wallpaper connections per display:
+ // 1. system & lock are the same: mLastWallpaper
+ // 2. system, lock are different: mLastWallpaper, mLastLockWallpaper
+ // 3. fallback used as both system & lock wallpaper: mFallbackWallpaper
+ // 4. fallback used as lock only wallpaper: mFallbackWallpaper,
+ // mLastWallpaper
+ // 5. fallback used as system only wallpaper: mFallbackWallpaper,
+ // mLastLockWallpaper
+ List<WallpaperData> pendingDisconnectWallpapers = new ArrayList<>();
+ if (mLastWallpaper != null && mLastWallpaper.connection != null
+ && mLastWallpaper.connection.containsDisplay(displayId)) {
+ pendingDisconnectWallpapers.add(mLastWallpaper);
+ }
+ if (mLastLockWallpaper != null && mLastLockWallpaper.connection != null
+ && mLastLockWallpaper.connection.containsDisplay(displayId)) {
+ pendingDisconnectWallpapers.add(mLastLockWallpaper);
+ }
+ if (mFallbackWallpaper != null && mFallbackWallpaper.connection != null
+ && mFallbackWallpaper.connection.containsDisplay(displayId)) {
+ pendingDisconnectWallpapers.add(mFallbackWallpaper);
+ }
+ for (int i = 0; i < pendingDisconnectWallpapers.size(); i++) {
+ WallpaperData wallpaper = pendingDisconnectWallpapers.get(i);
+ DisplayConnector displayConnector =
+ wallpaper.connection.getDisplayConnectorOrCreate(displayId);
+ if (displayConnector == null) {
+ Slog.w(TAG,
+ "Fail to disconnect wallpaper upon display removes system "
+ + "decorations");
+ return;
+ }
+ displayConnector.disconnectLocked(wallpaper.connection);
+ wallpaper.connection.removeDisplayConnector(displayId);
+ }
+ } else {
+ if (mLastWallpaper != null) {
+ WallpaperData targetWallpaper = null;
+ if (mLastWallpaper.connection != null
+ && mLastWallpaper.connection.containsDisplay(displayId)) {
+ targetWallpaper = mLastWallpaper;
+ } else if (mFallbackWallpaper != null
+ && mFallbackWallpaper.connection != null
+ && mFallbackWallpaper.connection.containsDisplay(
+ displayId)) {
+ targetWallpaper = mFallbackWallpaper;
+ }
+ if (targetWallpaper == null) return;
+ DisplayConnector connector =
+ targetWallpaper.connection.getDisplayConnectorOrCreate(
+ displayId);
+ if (connector == null) return;
+ connector.disconnectLocked(targetWallpaper.connection);
+ targetWallpaper.connection.removeDisplayConnector(displayId);
+ }
+ }
+
+ mWallpaperDisplayHelper.removeDisplayData(displayId);
+
+ for (int i = mColorsChangedListeners.size() - 1; i >= 0; i--) {
+ final SparseArray<RemoteCallbackList<IWallpaperManagerCallback>> callbacks =
+ mColorsChangedListeners.valueAt(i);
+ callbacks.delete(displayId);
+ }
+ }
+ }
+
void saveSettingsLocked(int userId) {
TimingsTraceAndSlog t = new TimingsTraceAndSlog(TAG);
t.traceBegin("WPMS.saveSettingsLocked-" + userId);
@@ -4100,8 +4162,14 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
mFallbackWallpaper.allowBackup = false;
mFallbackWallpaper.wallpaperId = makeWallpaperIdLocked();
mFallbackWallpaper.mBindSource = BindSource.INITIALIZE_FALLBACK;
- bindWallpaperComponentLocked(mDefaultWallpaperComponent, true, false,
- mFallbackWallpaper, null);
+ if (mFallbackWallpaperComponent == null) {
+ bindWallpaperComponentLocked(mDefaultWallpaperComponent, true, false,
+ mFallbackWallpaper, null);
+ } else {
+ mFallbackWallpaper.mWhich = FLAG_SYSTEM | FLAG_LOCK;
+ bindWallpaperComponentLocked(mFallbackWallpaperComponent, true, false,
+ mFallbackWallpaper, null);
+ }
}
}
diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java
index dd769173fb34..12f553426c80 100644
--- a/services/core/java/com/android/server/wm/AccessibilityController.java
+++ b/services/core/java/com/android/server/wm/AccessibilityController.java
@@ -58,7 +58,6 @@ import android.graphics.Matrix;
import android.graphics.Path;
import android.graphics.Point;
import android.graphics.Rect;
-import android.graphics.RectF;
import android.graphics.Region;
import android.os.Binder;
import android.os.Build;
@@ -69,7 +68,6 @@ import android.os.Looper;
import android.os.Message;
import android.os.Process;
import android.os.SystemClock;
-import android.util.ArraySet;
import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
@@ -79,7 +77,6 @@ import android.view.Display;
import android.view.MagnificationSpec;
import android.view.Surface;
import android.view.ViewConfiguration;
-import android.view.WindowInfo;
import android.view.WindowManager;
import android.view.WindowManager.TransitionFlags;
import android.view.WindowManager.TransitionType;
@@ -557,9 +554,6 @@ final class AccessibilityController {
private static final boolean DEBUG_WINDOW_TRANSITIONS = false;
private static final boolean DEBUG_DISPLAY_SIZE = false;
- private static final boolean DEBUG_LAYERS = false;
- private static final boolean DEBUG_RECTANGLE_REQUESTED = false;
- private static final boolean DEBUG_VIEWPORT_WINDOW = false;
private final Rect mTempRect1 = new Rect();
private final Rect mTempRect2 = new Rect();
@@ -579,8 +573,6 @@ final class AccessibilityController {
private final MagnificationCallbacks mCallbacks;
private final UserContextChangedNotifier mUserContextChangedNotifier;
- private final long mLongAnimationDuration;
-
private boolean mIsFullscreenMagnificationActivated = false;
private final Region mMagnificationRegion = new Region();
private final Region mOldMagnificationRegion = new Region();
@@ -593,7 +585,6 @@ final class AccessibilityController {
private final Point mScreenSize = new Point();
private final SparseArray<WindowState> mTempWindowStates =
new SparseArray<WindowState>();
- private final RectF mTempRectF = new RectF();
private final Matrix mTempMatrix = new Matrix();
DisplayMagnifier(WindowManagerService windowManagerService,
@@ -609,8 +600,6 @@ final class AccessibilityController {
mUserContextChangedNotifier = new UserContextChangedNotifier(mHandler);
mAccessibilityTracing =
AccessibilityController.getAccessibilityControllerInternal(mService);
- mLongAnimationDuration = mDisplayContext.getResources().getInteger(
- com.android.internal.R.integer.config_longAnimTime);
if (mDisplayContext.getResources().getConfiguration().isScreenRound()) {
mCircularPath = new Path();
@@ -840,10 +829,6 @@ final class AccessibilityController {
outMagnificationRegion.set(mMagnificationRegion);
}
- boolean isMagnifying() {
- return mMagnificationSpec.scale > 1.0f;
- }
-
void destroy() {
if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK)) {
mAccessibilityTracing.logTrace(LOG_TAG + ".destroy", FLAGS_MAGNIFICATION_CALLBACK);
@@ -1172,12 +1157,6 @@ final class AccessibilityController {
private static final boolean DEBUG = false;
- private final Set<IBinder> mTempBinderSet = new ArraySet<>();
-
- private final Region mTempRegion = new Region();
-
- private final Region mTempRegion2 = new Region();
-
private final WindowManagerService mService;
private final Handler mHandler;
@@ -1243,11 +1222,10 @@ final class AccessibilityController {
Slog.i(LOG_TAG, "computeChangedWindows()");
}
- List<WindowInfo> windows = null;
final List<AccessibilityWindow> visibleWindows = new ArrayList<>();
final Point screenSize = new Point();
final int topFocusedDisplayId;
- IBinder topFocusedWindowToken = null;
+ final IBinder topFocusedWindowToken;
synchronized (mService.mGlobalLock) {
final WindowState topFocusedWindowState = getTopFocusWindow();
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 1d12c561f118..89b46bc4eba4 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -46,6 +46,7 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.app.WindowConfiguration.activityTypeToString;
+import static android.app.WindowConfiguration.isFloating;
import static android.app.admin.DevicePolicyResources.Drawables.Source.PROFILE_SWITCH_ANIMATION;
import static android.app.admin.DevicePolicyResources.Drawables.Style.OUTLINE;
import static android.app.admin.DevicePolicyResources.Drawables.WORK_PROFILE_ICON;
@@ -3231,8 +3232,8 @@ final class ActivityRecord extends WindowToken {
* Returns {@code true} if the fixed orientation, aspect ratio, resizability of the application
* can be ignored.
*/
- static boolean canBeUniversalResizeable(ApplicationInfo appInfo, WindowManagerService wms,
- boolean isLargeScreen, boolean forActivity) {
+ static boolean canBeUniversalResizeable(@NonNull ApplicationInfo appInfo,
+ WindowManagerService wms, boolean isLargeScreen, boolean forActivity) {
if (appInfo.category == ApplicationInfo.CATEGORY_GAME) {
return false;
}
@@ -8376,6 +8377,7 @@ final class ActivityRecord extends WindowToken {
mConfigurationSeq = Math.max(++mConfigurationSeq, 1);
getResolvedOverrideConfiguration().seq = mConfigurationSeq;
+ // TODO(b/392069771): Move to AppCompatSandboxingPolicy.
// Sandbox max bounds by setting it to the activity bounds, if activity is letterboxed, or
// has or will have mAppCompatDisplayInsets for size compat. Also forces an activity to be
// sandboxed or not depending upon the configuration settings.
@@ -8404,6 +8406,20 @@ final class ActivityRecord extends WindowToken {
resolvedConfig.windowConfiguration.setMaxBounds(mTmpBounds);
}
+ // Sandbox activity bounds in freeform to app bounds to force app to display within the
+ // container. This prevents UI cropping when activities can draw below insets which are
+ // normally excluded from appBounds before targetSDK < 35
+ // (see ConfigurationContainer#applySizeOverrideIfNeeded).
+ if (isFloating(parentWindowingMode)) {
+ Rect appBounds = resolvedConfig.windowConfiguration.getAppBounds();
+ if (appBounds == null || appBounds.isEmpty()) {
+ // When there is no override bounds, the activity will inherit the bounds from
+ // parent.
+ appBounds = mResolveConfigHint.mParentAppBoundsOverride;
+ }
+ resolvedConfig.windowConfiguration.setBounds(appBounds);
+ }
+
applySizeOverrideIfNeeded(
mDisplayContent,
info.applicationInfo,
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index c7d4467a6e98..81c7807311dd 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -71,8 +71,6 @@ import static com.android.server.wm.ActivityRecord.State.RESUMED;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_PERMISSIONS_REVIEW;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_RESULTS;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_USER_LEAVING;
-import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_CONFIGURATION;
-import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_FOCUS;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_RESULTS;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_USER_LEAVING;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM;
@@ -91,9 +89,7 @@ import static com.android.server.wm.TaskFragment.EMBEDDING_DISALLOWED_NEW_TASK;
import static com.android.server.wm.TaskFragment.EMBEDDING_DISALLOWED_UNTRUSTED_HOST;
import static com.android.server.wm.WindowContainer.POSITION_TOP;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
-import static com.android.window.flags.Flags.balDontBringExistingBackgroundTaskStackToFg;
-import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
@@ -157,8 +153,6 @@ import com.android.server.wm.TaskFragment.EmbeddingCheckResult;
import com.android.wm.shell.Flags;
import java.io.PrintWriter;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
import java.text.DateFormat;
import java.util.Date;
import java.util.function.Supplier;
@@ -172,8 +166,6 @@ import java.util.function.Supplier;
class ActivityStarter {
private static final String TAG = TAG_WITH_CLASS_NAME ? "ActivityStarter" : TAG_ATM;
private static final String TAG_RESULTS = TAG + POSTFIX_RESULTS;
- private static final String TAG_FOCUS = TAG + POSTFIX_FOCUS;
- private static final String TAG_CONFIGURATION = TAG + POSTFIX_CONFIGURATION;
private static final String TAG_USER_LEAVING = TAG + POSTFIX_USER_LEAVING;
private static final int INVALID_LAUNCH_MODE = -1;
@@ -255,26 +247,7 @@ class ActivityStarter {
private boolean mIsTaskCleared;
private boolean mMovedToFront;
private boolean mNoAnimation;
-
- // TODO mAvoidMoveToFront before V is changed from a boolean to a int code mCanMoveToFrontCode
- // for the purpose of attribution of new BAL V feature. This should be reverted back to the
- // boolean flag post V.
- @IntDef(prefix = {"MOVE_TO_FRONT_"}, value = {
- MOVE_TO_FRONT_ALLOWED,
- MOVE_TO_FRONT_AVOID_PI_ONLY_CREATOR_ALLOWS,
- MOVE_TO_FRONT_AVOID_LEGACY,
- })
- @Retention(RetentionPolicy.SOURCE)
- public @interface MoveToFrontCode {}
-
- // Allows a task move to front.
- private static final int MOVE_TO_FRONT_ALLOWED = 0;
- // Avoid a task move to front because the Pending Intent that starts the activity only
- // its creator has the BAL privilege, its sender does not.
- private static final int MOVE_TO_FRONT_AVOID_PI_ONLY_CREATOR_ALLOWS = 1;
- // Avoid a task move to front because of all other legacy reasons.
- private static final int MOVE_TO_FRONT_AVOID_LEGACY = 2;
- private @MoveToFrontCode int mCanMoveToFrontCode = MOVE_TO_FRONT_ALLOWED;
+ private boolean mAvoidMoveToFront;
private boolean mFrozeTaskList;
private boolean mTransientLaunch;
// The task which was above the targetTask before starting this activity. null if the targetTask
@@ -771,7 +744,7 @@ class ActivityStarter {
mIsTaskCleared = starter.mIsTaskCleared;
mMovedToFront = starter.mMovedToFront;
mNoAnimation = starter.mNoAnimation;
- mCanMoveToFrontCode = starter.mCanMoveToFrontCode;
+ mAvoidMoveToFront = starter.mAvoidMoveToFront;
mFrozeTaskList = starter.mFrozeTaskList;
mVoiceSession = starter.mVoiceSession;
@@ -1711,14 +1684,6 @@ class ActivityStarter {
return result;
}
- private boolean avoidMoveToFront() {
- return mCanMoveToFrontCode != MOVE_TO_FRONT_ALLOWED;
- }
-
- private boolean avoidMoveToFrontPIOnlyCreatorAllows() {
- return mCanMoveToFrontCode == MOVE_TO_FRONT_AVOID_PI_ONLY_CREATOR_ALLOWS;
- }
-
/**
* If the start result is success, ensure that the configuration of the started activity matches
* the current display. Otherwise clean up unassociated containers to avoid leakage.
@@ -1768,7 +1733,7 @@ class ActivityStarter {
startedActivityRootTask.setAlwaysOnTop(true);
}
- if (isIndependentLaunch && !mDoResume && avoidMoveToFront() && !mTransientLaunch
+ if (isIndependentLaunch && !mDoResume && mAvoidMoveToFront && !mTransientLaunch
&& !started.shouldBeVisible(true /* ignoringKeyguard */)) {
Slog.i(TAG, "Abort " + transition + " of invisible launch " + started);
transition.abort();
@@ -1784,7 +1749,7 @@ class ActivityStarter {
currentTop, currentTop.mDisplayContent, false /* deferResume */);
}
- if (!avoidMoveToFront() && mDoResume
+ if (!mAvoidMoveToFront && mDoResume
&& !mService.getUserManagerInternal().isVisibleBackgroundFullUser(started.mUserId)
&& mRootWindowContainer.hasVisibleWindowAboveButDoesNotOwnNotificationShade(
started.launchedFromUid)) {
@@ -1934,19 +1899,17 @@ class ActivityStarter {
}
// When running transient transition, the transient launch target should keep on top.
// So disallow the transient hide activity to move itself to front, e.g. trampoline.
- if (!avoidMoveToFront() && (mService.mHomeProcess == null
+ if (!mAvoidMoveToFront && (mService.mHomeProcess == null
|| mService.mHomeProcess.mUid != realCallingUid)
&& (prevTopTask != null && prevTopTask.isActivityTypeHomeOrRecents())
&& r.mTransitionController.isTransientHide(targetTask)) {
- mCanMoveToFrontCode = MOVE_TO_FRONT_AVOID_LEGACY;
+ mAvoidMoveToFront = true;
}
// If the activity is started by sending a pending intent and only its creator has the
// privilege to allow BAL (its sender does not), avoid move it to the front. Only do
// this when it is not a new task and not already been marked as avoid move to front.
- // Guarded by a flag: balDontBringExistingBackgroundTaskStackToFg
- if (balDontBringExistingBackgroundTaskStackToFg() && !avoidMoveToFront()
- && balVerdict.onlyCreatorAllows()) {
- mCanMoveToFrontCode = MOVE_TO_FRONT_AVOID_PI_ONLY_CREATOR_ALLOWS;
+ if (!mAvoidMoveToFront && balVerdict.onlyCreatorAllows()) {
+ mAvoidMoveToFront = true;
}
mPriorAboveTask = TaskDisplayArea.getRootTaskAbove(targetTask.getRootTask());
}
@@ -2003,32 +1966,28 @@ class ActivityStarter {
// After activity is attached to task, but before actual start
recordTransientLaunchIfNeeded(mLastStartActivityRecord);
- if (mDoResume) {
- if (!avoidMoveToFront()) {
- mTargetRootTask.getRootTask().moveToFront("reuseOrNewTask", targetTask);
-
- final boolean launchBehindDream;
- if (com.android.window.flags.Flags.removeActivityStarterDreamCallback()) {
- final TaskDisplayArea tda = mTargetRootTask.getTaskDisplayArea();
- final Task top = (tda != null ? tda.getTopRootTask() : null);
- launchBehindDream = (top != null && top != mTargetRootTask)
- && top.getActivityType() == WindowConfiguration.ACTIVITY_TYPE_DREAM
- && top.getTopNonFinishingActivity() != null;
- } else {
- launchBehindDream = !mTargetRootTask.isTopRootTaskInDisplayArea()
- && mService.isDreaming()
- && !dreamStopping;
- }
+ if (!mAvoidMoveToFront && mDoResume) {
+ mTargetRootTask.getRootTask().moveToFront("reuseOrNewTask", targetTask);
- if (launchBehindDream) {
- // Launching underneath dream activity (fullscreen, always-on-top). Run the
- // launch--behind transition so the Activity gets created and starts
- // in visible state.
- mLaunchTaskBehind = true;
- r.mLaunchTaskBehind = true;
- }
+ final boolean launchBehindDream;
+ if (com.android.window.flags.Flags.removeActivityStarterDreamCallback()) {
+ final TaskDisplayArea tda = mTargetRootTask.getTaskDisplayArea();
+ final Task top = (tda != null ? tda.getTopRootTask() : null);
+ launchBehindDream = (top != null && top != mTargetRootTask)
+ && top.getActivityType() == WindowConfiguration.ACTIVITY_TYPE_DREAM
+ && top.getTopNonFinishingActivity() != null;
} else {
- logPIOnlyCreatorAllowsBAL();
+ launchBehindDream = !mTargetRootTask.isTopRootTaskInDisplayArea()
+ && mService.isDreaming()
+ && !dreamStopping;
+ }
+
+ if (launchBehindDream) {
+ // Launching underneath dream activity (fullscreen, always-on-top). Run the
+ // launch--behind transition so the Activity gets created and starts
+ // in visible state.
+ mLaunchTaskBehind = true;
+ r.mLaunchTaskBehind = true;
}
}
@@ -2089,13 +2048,9 @@ class ActivityStarter {
// root-task to the will not update the focused root-task. If starting the new
// activity now allows the task root-task to be focusable, then ensure that we
// now update the focused root-task accordingly.
- if (mTargetRootTask.isTopActivityFocusable()
+ if (!mAvoidMoveToFront && mTargetRootTask.isTopActivityFocusable()
&& !mRootWindowContainer.isTopDisplayFocusedRootTask(mTargetRootTask)) {
- if (!avoidMoveToFront()) {
- mTargetRootTask.moveToFront("startActivityInner");
- } else {
- logPIOnlyCreatorAllowsBAL();
- }
+ mTargetRootTask.moveToFront("startActivityInner");
}
mRootWindowContainer.resumeFocusedTasksTopActivities(
mTargetRootTask, mStartActivity, mOptions, mTransientLaunch);
@@ -2123,26 +2078,6 @@ class ActivityStarter {
return START_SUCCESS;
}
- // TODO (b/316135632) Post V release, remove this log method.
- private void logPIOnlyCreatorAllowsBAL() {
- if (!avoidMoveToFrontPIOnlyCreatorAllows()) return;
- String realCallingPackage =
- mService.mContext.getPackageManager().getNameForUid(mRealCallingUid);
- if (realCallingPackage == null) {
- realCallingPackage = "uid=" + mRealCallingUid;
- }
- Slog.wtf(TAG, "Without Android 15 BAL hardening this activity would be moved to the "
- + "foreground. The activity is started by a PendingIntent. However, only the "
- + "creator of the PendingIntent allows BAL while the sender does not allow BAL. "
- + "realCallingPackage: " + realCallingPackage
- + "; callingPackage: " + mRequest.callingPackage
- + "; mTargetRootTask:" + mTargetRootTask
- + "; mIntent: " + mIntent
- + "; mTargetRootTask.getTopNonFinishingActivity: "
- + mTargetRootTask.getTopNonFinishingActivity()
- + "; mTargetRootTask.getRootActivity: " + mTargetRootTask.getRootActivity());
- }
-
private void recordTransientLaunchIfNeeded(ActivityRecord r) {
if (r == null || !mTransientLaunch) return;
final TransitionController controller = r.mTransitionController;
@@ -2287,7 +2222,7 @@ class ActivityStarter {
}
if (!mSupervisor.getBackgroundActivityLaunchController().checkActivityAllowedToStart(
- mSourceRecord, r, newTask, avoidMoveToFront(), targetTask, mLaunchFlags, mBalCode,
+ mSourceRecord, r, newTask, mAvoidMoveToFront, targetTask, mLaunchFlags, mBalCode,
mCallingUid, mRealCallingUid, mPreferredTaskDisplayArea)) {
return START_ABORTED;
}
@@ -2635,7 +2570,7 @@ class ActivityStarter {
mIsTaskCleared = false;
mMovedToFront = false;
mNoAnimation = false;
- mCanMoveToFrontCode = MOVE_TO_FRONT_ALLOWED;
+ mAvoidMoveToFront = false;
mFrozeTaskList = false;
mTransientLaunch = false;
mPriorAboveTask = null;
@@ -2747,12 +2682,12 @@ class ActivityStarter {
// The caller specifies that we'd like to be avoided to be moved to the
// front, so be it!
mDoResume = false;
- mCanMoveToFrontCode = MOVE_TO_FRONT_AVOID_LEGACY;
+ mAvoidMoveToFront = true;
}
}
} else if (mOptions.getAvoidMoveToFront()) {
mDoResume = false;
- mCanMoveToFrontCode = MOVE_TO_FRONT_AVOID_LEGACY;
+ mAvoidMoveToFront = true;
}
mTransientLaunch = mOptions.getTransientLaunch();
final KeyguardController kc = mSupervisor.getKeyguardController();
@@ -2762,7 +2697,7 @@ class ActivityStarter {
if (mTransientLaunch && mDisplayLockAndOccluded
&& mService.getTransitionController().isShellTransitionsEnabled()) {
mDoResume = false;
- mCanMoveToFrontCode = MOVE_TO_FRONT_AVOID_LEGACY;
+ mAvoidMoveToFront = true;
}
mTargetRootTask = Task.fromWindowContainerToken(mOptions.getLaunchRootTask());
@@ -2819,7 +2754,7 @@ class ActivityStarter {
mNoAnimation = (mLaunchFlags & FLAG_ACTIVITY_NO_ANIMATION) != 0;
if (mBalCode == BAL_BLOCK && !mService.isBackgroundActivityStartsEnabled()) {
- mCanMoveToFrontCode = MOVE_TO_FRONT_AVOID_LEGACY;
+ mAvoidMoveToFront = true;
mDoResume = false;
}
}
@@ -3050,7 +2985,7 @@ class ActivityStarter {
differentTopTask = true;
}
- if (differentTopTask && !avoidMoveToFront()) {
+ if (differentTopTask && !mAvoidMoveToFront) {
mStartActivity.intent.addFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT);
// We really do want to push this one into the user's face, right now.
if (mLaunchTaskBehind && mSourceRecord != null) {
@@ -3094,9 +3029,6 @@ class ActivityStarter {
}
mOptions = null;
}
- if (differentTopTask) {
- logPIOnlyCreatorAllowsBAL();
- }
// Update the target's launch cookie and pending remote animation to those specified in the
// options if set.
if (mStartActivity.mLaunchCookie != null) {
@@ -3137,7 +3069,7 @@ class ActivityStarter {
}
private void setNewTask(Task taskToAffiliate) {
- final boolean toTop = !mLaunchTaskBehind && !avoidMoveToFront();
+ final boolean toTop = !mLaunchTaskBehind && !mAvoidMoveToFront;
final Task task = mTargetRootTask.reuseOrCreateTask(
mStartActivity.info, mIntent, mVoiceSession,
mVoiceInteractor, toTop, mStartActivity, mSourceRecord, mOptions);
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index d4f9c0901162..cf111cdbcc6a 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -2154,6 +2154,16 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
}
}
+ /**
+ * @return ehether the application could be universal resizeable on a large screen,
+ * ignoring any overrides
+ */
+ @Override
+ public boolean canBeUniversalResizeable(@NonNull ApplicationInfo appInfo) {
+ return ActivityRecord.canBeUniversalResizeable(appInfo, mWindowManager,
+ /* isLargeScreen */ true, /* forActivity */ false);
+ }
+
@Override
public void removeAllVisibleRecentTasks() {
mAmInternal.enforceCallingPermission(REMOVE_TASKS, "removeAllVisibleRecentTasks()");
diff --git a/services/core/java/com/android/server/wm/AppCompatOrientationPolicy.java b/services/core/java/com/android/server/wm/AppCompatOrientationPolicy.java
index 35fa39dab900..d994a1904a14 100644
--- a/services/core/java/com/android/server/wm/AppCompatOrientationPolicy.java
+++ b/services/core/java/com/android/server/wm/AppCompatOrientationPolicy.java
@@ -180,10 +180,7 @@ class AppCompatOrientationPolicy {
return true;
}
- final AppCompatCameraPolicy cameraPolicy = AppCompatCameraPolicy
- .getAppCompatCameraPolicy(mActivityRecord);
- if (cameraPolicy != null
- && cameraPolicy.isTreatmentEnabledForActivity(mActivityRecord)) {
+ if (AppCompatCameraPolicy.isTreatmentEnabledForActivity(mActivityRecord)) {
Slog.w(TAG, "Ignoring orientation update to "
+ screenOrientationToString(requestedOrientation)
+ " due to camera compat treatment for " + mActivityRecord);
diff --git a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
index 119709e86551..6df01f4b328b 100644
--- a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
+++ b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
@@ -45,12 +45,11 @@ import static com.android.server.wm.ActivityTaskManagerService.APP_SWITCH_FG_ONL
import static com.android.server.wm.ActivityTaskSupervisor.getApplicationLabel;
import static com.android.server.wm.PendingRemoteAnimationRegistry.TIMEOUT_MS;
import static com.android.window.flags.Flags.balAdditionalStartModes;
-import static com.android.window.flags.Flags.balDontBringExistingBackgroundTaskStackToFg;
import static com.android.window.flags.Flags.balImprovedMetrics;
import static com.android.window.flags.Flags.balRequireOptInByPendingIntentCreator;
import static com.android.window.flags.Flags.balShowToastsBlocked;
-import static com.android.window.flags.Flags.balStrictModeRo;
import static com.android.window.flags.Flags.balStrictModeGracePeriod;
+import static com.android.window.flags.Flags.balStrictModeRo;
import static java.lang.annotation.RetentionPolicy.SOURCE;
import static java.util.Objects.requireNonNull;
@@ -620,8 +619,6 @@ public class BackgroundActivityStartController {
// features
sb.append("; balRequireOptInByPendingIntentCreator: ")
.append(balRequireOptInByPendingIntentCreator());
- sb.append("; balDontBringExistingBackgroundTaskStackToFg: ")
- .append(balDontBringExistingBackgroundTaskStackToFg());
sb.append("]");
return sb.toString();
}
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index e1a50a93edcc..5b1619995529 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -1910,6 +1910,11 @@ public class DisplayPolicy {
if (statusBar != null) {
statusBar.onDisplayRemoveSystemDecorations(displayId);
}
+ final WallpaperManagerInternal wpMgr =
+ LocalServices.getService(WallpaperManagerInternal.class);
+ if (wpMgr != null) {
+ wpMgr.onDisplayRemoveSystemDecorations(displayId);
+ }
});
}
diff --git a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
index bcfaa3947e74..59ca79c7ffc3 100644
--- a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
@@ -361,7 +361,7 @@ final class ImeInsetsSourceProvider extends InsetsSourceProvider {
controlTarget = mDisplayContent.getImeHostOrFallback(
((InsetsControlTarget) imeInsetsTarget).getWindow());
- if (controlTarget != imeInsetsTarget) {
+ if (controlTarget != null && controlTarget != imeInsetsTarget) {
ImeTracker.forLogging().onProgress(statsToken,
ImeTracker.PHASE_WM_SET_REMOTE_TARGET_IME_VISIBILITY);
controlTarget.setImeInputTargetRequestedVisibility(imeVisible, statsToken);
diff --git a/services/core/java/com/android/server/wm/ScreenRecordingCallbackController.java b/services/core/java/com/android/server/wm/ScreenRecordingCallbackController.java
index efc68aac0323..00e1c01bbadb 100644
--- a/services/core/java/com/android/server/wm/ScreenRecordingCallbackController.java
+++ b/services/core/java/com/android/server/wm/ScreenRecordingCallbackController.java
@@ -22,6 +22,7 @@ import static com.android.internal.protolog.WmProtoLogGroups.WM_ERROR;
import android.media.projection.IMediaProjectionManager;
import android.media.projection.IMediaProjectionWatcherCallback;
+import android.media.projection.MediaProjectionEvent;
import android.media.projection.MediaProjectionInfo;
import android.os.Binder;
import android.os.IBinder;
@@ -84,6 +85,12 @@ public class ScreenRecordingCallbackController {
public void onRecordingSessionSet(MediaProjectionInfo mediaProjectionInfo,
ContentRecordingSession contentRecordingSession) {
}
+
+ @Override
+ public void onMediaProjectionEvent(
+ MediaProjectionEvent event,
+ MediaProjectionInfo mediaProjectionInfo,
+ ContentRecordingSession session) {}
}
ScreenRecordingCallbackController(WindowManagerService wms) {
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 37cc0d22c063..27683b2fcff2 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -1504,16 +1504,15 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
}
// Update the input-sink (touch-blocking) state now that the animation is finished.
- SurfaceControl.Transaction inputSinkTransaction = null;
+ boolean scheduleAnimation = false;
for (int i = 0; i < mParticipants.size(); ++i) {
final ActivityRecord ar = mParticipants.valueAt(i).asActivityRecord();
if (ar == null || !ar.isVisible() || ar.getParent() == null) continue;
- if (inputSinkTransaction == null) {
- inputSinkTransaction = ar.mWmService.mTransactionFactory.get();
- }
- ar.mActivityRecordInputSink.applyChangesToSurfaceIfChanged(inputSinkTransaction);
+ scheduleAnimation = true;
+ ar.mActivityRecordInputSink.applyChangesToSurfaceIfChanged(ar.getPendingTransaction());
}
- if (inputSinkTransaction != null) inputSinkTransaction.apply();
+ // To apply pending transactions.
+ if (scheduleAnimation) mController.mAtm.mWindowManager.scheduleAnimationLocked();
// Always schedule stop processing when transition finishes because activities don't
// stop while they are in a transition thus their stop could still be pending.
diff --git a/services/core/java/com/android/server/wm/WallpaperWindowToken.java b/services/core/java/com/android/server/wm/WallpaperWindowToken.java
index 0ecd0251ca94..3b79c54f1c73 100644
--- a/services/core/java/com/android/server/wm/WallpaperWindowToken.java
+++ b/services/core/java/com/android/server/wm/WallpaperWindowToken.java
@@ -89,8 +89,9 @@ class WallpaperWindowToken extends WindowToken {
// Similar to Task.prepareSurfaces, outside of transitions we need to apply visibility
// changes directly. In transitions the transition player will take care of applying the
// visibility change.
- if (!mTransitionController.inTransition(this)) {
- getSyncTransaction().setVisibility(mSurfaceControl, isVisible());
+ if (!mTransitionController.isCollecting(this)
+ && !mTransitionController.isPlayingTarget(this)) {
+ getPendingTransaction().setVisibility(mSurfaceControl, isVisible());
}
}
}
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index e761e024b3ca..883d8f95b612 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -1680,26 +1680,27 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
* Sets the specified orientation of this container. It percolates this change upward along the
* hierarchy to let each level of the hierarchy a chance to respond to it.
*
- * @param orientation the specified orientation. Needs to be one of {@link ScreenOrientation}.
+ * @param requestedOrientation the specified orientation. Needs to be one of
+ * {@link ScreenOrientation}.
* @param requestingContainer the container which orientation request has changed. Mostly used
* to ensure it gets correct configuration.
* @return the resolved override orientation of this window container.
*/
@ScreenOrientation
- int setOrientation(@ScreenOrientation int orientation,
+ int setOrientation(@ScreenOrientation int requestedOrientation,
@Nullable WindowContainer requestingContainer) {
- if (getOverrideOrientation() == orientation) {
- return orientation;
+ if (getOverrideOrientation() == requestedOrientation) {
+ return requestedOrientation;
}
- setOverrideOrientation(orientation);
+ setOverrideOrientation(requestedOrientation);
final WindowContainer parent = getParent();
if (parent == null) {
- return orientation;
+ return requestedOrientation;
}
// The derived class can return a result that is different from the given orientation.
- final int resolvedOrientation = getOverrideOrientation();
+ final int actualOverrideOrientation = getOverrideOrientation();
if (getConfiguration().orientation != getRequestedConfigurationOrientation(
- false /* forDisplay */, resolvedOrientation)
+ false /* forDisplay */, actualOverrideOrientation)
// Update configuration directly only if the change won't be dispatched from
// ancestor. This prevents from computing intermediate configuration when the
// parent also needs to be updated from the ancestor. E.g. the app requests
@@ -1707,12 +1708,12 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
// the task can be updated to portrait first so the configuration can be
// computed in a consistent environment.
&& (inMultiWindowMode()
- || !handlesOrientationChangeFromDescendant(orientation))) {
+ || !handlesOrientationChangeFromDescendant(requestedOrientation))) {
// Resolve the requested orientation.
onConfigurationChanged(parent.getConfiguration());
}
onDescendantOrientationChanged(requestingContainer);
- return resolvedOrientation;
+ return actualOverrideOrientation;
}
@ScreenOrientation
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 5de0e9b6ed93..3a1d652f82d4 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -309,6 +309,7 @@ import android.window.ActivityWindowInfo;
import android.window.AddToSurfaceSyncGroupResult;
import android.window.ClientWindowFrames;
import android.window.ConfigurationChangeSetting;
+import android.window.DesktopModeFlags;
import android.window.IGlobalDragListener;
import android.window.IScreenRecordingCallback;
import android.window.ISurfaceSyncGroupCompletedListener;
@@ -820,6 +821,8 @@ public class WindowManagerService extends IWindowManager.Stub
DEVELOPMENT_WM_DISPLAY_SETTINGS_PATH);
private final Uri mMaximumObscuringOpacityForTouchUri = Settings.Global.getUriFor(
Settings.Global.MAXIMUM_OBSCURING_OPACITY_FOR_TOUCH);
+ private final Uri mDevelopmentOverrideDesktopExperienceUri = Settings.Global.getUriFor(
+ Settings.Global.DEVELOPMENT_OVERRIDE_DESKTOP_EXPERIENCE_FEATURES);
public SettingsObserver() {
super(new Handler());
@@ -847,6 +850,8 @@ public class WindowManagerService extends IWindowManager.Stub
UserHandle.USER_ALL);
resolver.registerContentObserver(mMaximumObscuringOpacityForTouchUri, false, this,
UserHandle.USER_ALL);
+ resolver.registerContentObserver(mDevelopmentOverrideDesktopExperienceUri, false, this,
+ UserHandle.USER_ALL);
}
@Override
@@ -890,6 +895,11 @@ public class WindowManagerService extends IWindowManager.Stub
return;
}
+ if (mDevelopmentOverrideDesktopExperienceUri.equals(uri)) {
+ updateDevelopmentOverrideDesktopExperience();
+ return;
+ }
+
@UpdateAnimationScaleMode
final int mode;
if (mWindowAnimationScaleUri.equals(uri)) {
@@ -956,6 +966,16 @@ public class WindowManagerService extends IWindowManager.Stub
mAtmService.mForceResizableActivities = forceResizable;
}
+ void updateDevelopmentOverrideDesktopExperience() {
+ ContentResolver resolver = mContext.getContentResolver();
+ final int overrideDesktopMode = Settings.Global.getInt(resolver,
+ Settings.Global.DEVELOPMENT_OVERRIDE_DESKTOP_EXPERIENCE_FEATURES,
+ DesktopModeFlags.ToggleOverride.OVERRIDE_UNSET.getSetting());
+
+ SystemProperties.set(DesktopModeFlags.SYSTEM_PROPERTY_NAME,
+ Integer.toString(overrideDesktopMode));
+ }
+
void updateDevEnableNonResizableMultiWindow() {
ContentResolver resolver = mContext.getContentResolver();
final boolean devEnableNonResizableMultiWindow = Settings.Global.getInt(resolver,
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index a1755e4d9d3b..060f2e803ec9 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -756,40 +756,6 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
t.getTaskFragmentOrganizer());
}
}
- // Queue-up bounds-change transactions for tasks which are now organized. Do
- // this after hierarchy ops so we have the final organized state.
- entries = t.getChanges().entrySet().iterator();
- while (entries.hasNext()) {
- final Map.Entry<IBinder, WindowContainerTransaction.Change> entry = entries.next();
- final WindowContainer wc = WindowContainer.fromBinder(entry.getKey());
- if (wc == null || !wc.isAttached()) {
- Slog.e(TAG, "Attempt to operate on detached container: " + wc);
- continue;
- }
- final Task task = wc.asTask();
- final Rect surfaceBounds = entry.getValue().getBoundsChangeSurfaceBounds();
- if (task == null || !task.isAttached() || surfaceBounds == null) {
- continue;
- }
- if (!task.isOrganized()) {
- final Task parent = task.getParent() != null ? task.getParent().asTask() : null;
- // Also allow direct children of created-by-organizer tasks to be
- // controlled. In the future, these will become organized anyways.
- if (parent == null || !parent.mCreatedByOrganizer) {
- throw new IllegalArgumentException(
- "Can't manipulate non-organized task surface " + task);
- }
- }
- final SurfaceControl.Transaction sft = new SurfaceControl.Transaction();
- final SurfaceControl sc = task.getSurfaceControl();
- sft.setPosition(sc, surfaceBounds.left, surfaceBounds.top);
- if (surfaceBounds.isEmpty()) {
- sft.setWindowCrop(sc, null);
- } else {
- sft.setWindowCrop(sc, surfaceBounds.width(), surfaceBounds.height());
- }
- task.setMainWindowSizeChangeTransaction(sft);
- }
if ((effects & TRANSACT_EFFECTS_LIFECYCLE) != 0) {
mService.mTaskSupervisor.setDeferRootVisibilityUpdate(false /* deferUpdate */);
mService.mTaskSupervisor.endDeferResume();
diff --git a/services/core/jni/com_android_server_utils_AnrTimer.cpp b/services/core/jni/com_android_server_utils_AnrTimer.cpp
index 2add5b09f15b..24909ac6150b 100644
--- a/services/core/jni/com_android_server_utils_AnrTimer.cpp
+++ b/services/core/jni/com_android_server_utils_AnrTimer.cpp
@@ -101,6 +101,9 @@ nsecs_t now() {
return systemTime(SYSTEM_TIME_MONOTONIC);
}
+// The current process. This is cached here on startup.
+const pid_t sThisProcess = getpid();
+
// Return true if the process exists and false if we cannot know.
bool processExists(pid_t pid) {
char path[PATH_MAX];
@@ -726,7 +729,7 @@ class AnrTimerService::Timer {
uid(uid),
timeout(timeout),
extend(extend),
- freeze(pid != 0 && freeze),
+ freeze(freeze),
split(trace.earlyTimeout),
action(trace.action),
status(Running),
@@ -1188,8 +1191,11 @@ const char* AnrTimerService::statusString(Status s) {
}
AnrTimerService::timer_id_t AnrTimerService::start(int pid, int uid, nsecs_t timeout) {
+ // Use the freezer only if the pid is not 0 (a nonsense value) and the pid is not self.
+ // Freezing the current process is a fatal error.
+ bool useFreezer = freeze_ && (pid != 0) && (pid != sThisProcess);
AutoMutex _l(lock_);
- Timer t(pid, uid, timeout, extend_, freeze_, tracer_.getConfig(pid));
+ Timer t(pid, uid, timeout, extend_, useFreezer, tracer_.getConfig(pid));
insertLocked(t);
t.start();
counters_.started++;
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 25e9f8a38f89..c974d9e1dc87 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -1799,7 +1799,7 @@ public final class SystemServer implements Dumpable {
mSystemServiceManager.startService(LogcatManagerService.class);
t.traceEnd();
- if (!isWatch && !isTv && !isAutomotive
+ if (!isWatch && !isTv && !isAutomotive && !isDesktop
&& android.security.Flags.aflApi()) {
t.traceBegin("StartIntrusionDetectionService");
mSystemServiceManager.startService(IntrusionDetectionService.class);
diff --git a/services/supervision/java/com/android/server/supervision/SupervisionService.java b/services/supervision/java/com/android/server/supervision/SupervisionService.java
index 073ee31ddd60..195c65d6ec45 100644
--- a/services/supervision/java/com/android/server/supervision/SupervisionService.java
+++ b/services/supervision/java/com/android/server/supervision/SupervisionService.java
@@ -23,6 +23,7 @@ import static com.android.internal.util.Preconditions.checkCallAuthorization;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SuppressLint;
import android.annotation.UserIdInt;
import android.app.admin.DevicePolicyManager;
import android.app.admin.DevicePolicyManagerInternal;
@@ -51,7 +52,6 @@ import com.android.internal.util.DumpUtils;
import com.android.internal.util.IndentingPrintWriter;
import com.android.server.LocalServices;
import com.android.server.SystemService;
-import com.android.server.SystemService.TargetUser;
import com.android.server.pm.UserManagerInternal;
import java.io.FileDescriptor;
@@ -62,26 +62,28 @@ import java.util.List;
public class SupervisionService extends ISupervisionManager.Stub {
private static final String LOG_TAG = "SupervisionService";
- private final Context mContext;
-
// TODO(b/362756788): Does this need to be a LockGuard lock?
private final Object mLockDoNoUseDirectly = new Object();
@GuardedBy("getLockObject()")
private final SparseArray<SupervisionUserData> mUserData = new SparseArray<>();
- private final DevicePolicyManagerInternal mDpmInternal;
- private final PackageManager mPackageManager;
- private final UserManagerInternal mUserManagerInternal;
+ private final Context mContext;
+ private final Injector mInjector;
+ final SupervisionManagerInternal mInternal = new SupervisionManagerInternalImpl();
public SupervisionService(Context context) {
mContext = context.createAttributionContext(LOG_TAG);
- mDpmInternal = LocalServices.getService(DevicePolicyManagerInternal.class);
- mPackageManager = context.getPackageManager();
- mUserManagerInternal = LocalServices.getService(UserManagerInternal.class);
- mUserManagerInternal.addUserLifecycleListener(new UserLifecycleListener());
+ mInjector = new Injector(context);
+ mInjector.getUserManagerInternal().addUserLifecycleListener(new UserLifecycleListener());
}
+ /**
+ * Returns whether supervision is enabled for the given user.
+ *
+ * <p>Supervision is automatically enabled when the supervision app becomes the profile owner or
+ * explicitly enabled via an internal call to {@link #setSupervisionEnabledForUser}.
+ */
@Override
public boolean isSupervisionEnabledForUser(@UserIdInt int userId) {
if (UserHandle.getUserId(Binder.getCallingUid()) != userId) {
@@ -92,6 +94,20 @@ public class SupervisionService extends ISupervisionManager.Stub {
}
}
+ /**
+ * Returns the package name of the active supervision app or null if supervision is disabled.
+ */
+ @Override
+ @Nullable
+ public String getActiveSupervisionAppPackage(@UserIdInt int userId) {
+ if (UserHandle.getUserId(Binder.getCallingUid()) != userId) {
+ enforcePermission(INTERACT_ACROSS_USERS);
+ }
+ synchronized (getLockObject()) {
+ return getUserDataLocked(userId).supervisionAppPackage;
+ }
+ }
+
@Override
public void onShellCommand(
@Nullable FileDescriptor in,
@@ -114,7 +130,7 @@ public class SupervisionService extends ISupervisionManager.Stub {
pw.println("SupervisionService state:");
pw.increaseIndent();
- List<UserInfo> users = mUserManagerInternal.getUsers(false);
+ List<UserInfo> users = mInjector.getUserManagerInternal().getUsers(false);
synchronized (getLockObject()) {
for (var user : users) {
getUserDataLocked(user.id).dump(pw);
@@ -140,35 +156,54 @@ public class SupervisionService extends ISupervisionManager.Stub {
return data;
}
- void setSupervisionEnabledForUser(@UserIdInt int userId, boolean enabled) {
+ /**
+ * Sets supervision as enabled or disabled for the given user and, in case supervision is being
+ * enabled, the package of the active supervision app.
+ */
+ private void setSupervisionEnabledForUser(
+ @UserIdInt int userId, boolean enabled, @Nullable String supervisionAppPackage) {
synchronized (getLockObject()) {
- getUserDataLocked(userId).supervisionEnabled = enabled;
+ SupervisionUserData data = getUserDataLocked(userId);
+ data.supervisionEnabled = enabled;
+ data.supervisionAppPackage = enabled ? supervisionAppPackage : null;
}
}
- /** Ensures that supervision is enabled when supervision app is the profile owner. */
+ /** Ensures that supervision is enabled when the supervision app is the profile owner. */
private void syncStateWithDevicePolicyManager(@UserIdInt int userId) {
- if (isProfileOwner(userId)) {
- setSupervisionEnabledForUser(userId, true);
+ final DevicePolicyManagerInternal dpmInternal = mInjector.getDpmInternal();
+ final ComponentName po =
+ dpmInternal != null ? dpmInternal.getProfileOwnerAsUser(userId) : null;
+
+ if (po != null && po.getPackageName().equals(getSystemSupervisionPackage())) {
+ setSupervisionEnabledForUser(userId, true, po.getPackageName());
+ } else if (po != null && po.equals(getSupervisionProfileOwnerComponent())) {
+ // TODO(b/392071637): Consider not enabling supervision in case profile owner is given
+ // to the legacy supervision profile owner component.
+ setSupervisionEnabledForUser(userId, true, po.getPackageName());
} else {
// TODO(b/381428475): Avoid disabling supervision when the app is not the profile owner.
// This might only be possible after introducing specific and public APIs to enable
- // supervision.
- setSupervisionEnabledForUser(userId, false);
+ // and disable supervision.
+ setSupervisionEnabledForUser(userId, false, /* supervisionAppPackage= */ null);
}
}
- /** Returns whether the supervision app has profile owner status. */
- private boolean isProfileOwner(@UserIdInt int userId) {
- ComponentName profileOwner =
- mDpmInternal != null ? mDpmInternal.getProfileOwnerAsUser(userId) : null;
- return profileOwner != null && isSupervisionAppPackage(profileOwner.getPackageName());
+ /**
+ * Returns the {@link ComponentName} of the supervision profile owner component.
+ *
+ * <p>This component is used to give GMS Kids Module permission to supervise the device and may
+ * still be active during the transition to the {@code SYSTEM_SUPERVISION} role.
+ */
+ private ComponentName getSupervisionProfileOwnerComponent() {
+ return ComponentName.unflattenFromString(
+ mContext.getResources()
+ .getString(R.string.config_defaultSupervisionProfileOwnerComponent));
}
- /** Returns whether the given package name belongs to the supervision role holder. */
- private boolean isSupervisionAppPackage(String packageName) {
- return packageName.equals(
- mContext.getResources().getString(R.string.config_systemSupervision));
+ /** Returns the package assigned to the {@code SYSTEM_SUPERVISION} role. */
+ private String getSystemSupervisionPackage() {
+ return mContext.getResources().getString(R.string.config_systemSupervision);
}
/** Enforces that the caller has the given permission. */
@@ -177,6 +212,41 @@ public class SupervisionService extends ISupervisionManager.Stub {
mContext.checkCallingOrSelfPermission(permission) == PERMISSION_GRANTED);
}
+ /** Provides local services in a lazy manner. */
+ static class Injector {
+ private final Context mContext;
+ private DevicePolicyManagerInternal mDpmInternal;
+ private PackageManager mPackageManager;
+ private UserManagerInternal mUserManagerInternal;
+
+ Injector(Context context) {
+ mContext = context;
+ }
+
+ @Nullable
+ DevicePolicyManagerInternal getDpmInternal() {
+ if (mDpmInternal == null) {
+ mDpmInternal = LocalServices.getService(DevicePolicyManagerInternal.class);
+ }
+ return mDpmInternal;
+ }
+
+ PackageManager getPackageManager() {
+ if (mPackageManager == null) {
+ mPackageManager = mContext.getPackageManager();
+ }
+ return mPackageManager;
+ }
+
+ UserManagerInternal getUserManagerInternal() {
+ if (mUserManagerInternal == null) {
+ mUserManagerInternal = LocalServices.getService(UserManagerInternal.class);
+ }
+ return mUserManagerInternal;
+ }
+ }
+
+ /** Publishes local and binder services and allows the service to act during initialization. */
public static class Lifecycle extends SystemService {
private final SupervisionService mSupervisionService;
@@ -201,6 +271,7 @@ public class SupervisionService extends ISupervisionManager.Stub {
}
@VisibleForTesting
+ @SuppressLint("MissingPermission") // not needed for a service
void registerProfileOwnerListener() {
IntentFilter poIntentFilter = new IntentFilter();
poIntentFilter.addAction(DevicePolicyManager.ACTION_PROFILE_OWNER_CHANGED);
@@ -209,7 +280,7 @@ public class SupervisionService extends ISupervisionManager.Stub {
.registerReceiverForAllUsers(
new ProfileOwnerBroadcastReceiver(),
poIntentFilter,
- /* brodcastPermission= */ null,
+ /* broadcastPermission= */ null,
/* scheduler= */ null);
}
@@ -228,19 +299,22 @@ public class SupervisionService extends ISupervisionManager.Stub {
}
}
- final SupervisionManagerInternal mInternal = new SupervisionManagerInternalImpl();
-
+ /** Implementation of the local service, API used by other services. */
private final class SupervisionManagerInternalImpl extends SupervisionManagerInternal {
@Override
public boolean isActiveSupervisionApp(int uid) {
- String[] packages = mPackageManager.getPackagesForUid(uid);
- if (packages == null) {
+ int userId = UserHandle.getUserId(uid);
+ String supervisionAppPackage = getActiveSupervisionAppPackage(userId);
+ if (supervisionAppPackage == null) {
return false;
}
- for (var packageName : packages) {
- if (SupervisionService.this.isSupervisionAppPackage(packageName)) {
- int userId = UserHandle.getUserId(uid);
- return SupervisionService.this.isSupervisionEnabledForUser(userId);
+
+ String[] packages = mInjector.getPackageManager().getPackagesForUid(uid);
+ if (packages != null) {
+ for (var packageName : packages) {
+ if (supervisionAppPackage.equals(packageName)) {
+ return true;
+ }
}
}
return false;
@@ -253,7 +327,8 @@ public class SupervisionService extends ISupervisionManager.Stub {
@Override
public void setSupervisionEnabledForUser(@UserIdInt int userId, boolean enabled) {
- SupervisionService.this.setSupervisionEnabledForUser(userId, enabled);
+ SupervisionService.this.setSupervisionEnabledForUser(
+ userId, enabled, getSystemSupervisionPackage());
}
@Override
@@ -274,6 +349,7 @@ public class SupervisionService extends ISupervisionManager.Stub {
}
}
+ /** Deletes user data when the user gets removed. */
private final class UserLifecycleListener implements UserManagerInternal.UserLifecycleListener {
@Override
public void onUserRemoved(UserInfo user) {
diff --git a/services/supervision/java/com/android/server/supervision/SupervisionServiceShellCommand.java b/services/supervision/java/com/android/server/supervision/SupervisionServiceShellCommand.java
index 2adaae3943f1..976642bd563d 100644
--- a/services/supervision/java/com/android/server/supervision/SupervisionServiceShellCommand.java
+++ b/services/supervision/java/com/android/server/supervision/SupervisionServiceShellCommand.java
@@ -32,16 +32,18 @@ public class SupervisionServiceShellCommand extends ShellCommand {
return handleDefaultCommands(null);
}
switch (cmd) {
- case "enable": return setEnabled(true);
- case "disable": return setEnabled(false);
- default: return handleDefaultCommands(cmd);
+ case "enable":
+ return setEnabled(true);
+ case "disable":
+ return setEnabled(false);
+ default:
+ return handleDefaultCommands(cmd);
}
}
private int setEnabled(boolean enabled) {
- final var pw = getOutPrintWriter();
final var userId = UserHandle.parseUserArg(getNextArgRequired());
- mService.setSupervisionEnabledForUser(userId, enabled);
+ mService.mInternal.setSupervisionEnabledForUser(userId, enabled);
return 0;
}
diff --git a/services/supervision/java/com/android/server/supervision/SupervisionUserData.java b/services/supervision/java/com/android/server/supervision/SupervisionUserData.java
index 1dd48f581bf4..06acb91509a1 100644
--- a/services/supervision/java/com/android/server/supervision/SupervisionUserData.java
+++ b/services/supervision/java/com/android/server/supervision/SupervisionUserData.java
@@ -26,6 +26,7 @@ import android.util.IndentingPrintWriter;
public class SupervisionUserData {
public final @UserIdInt int userId;
public boolean supervisionEnabled;
+ @Nullable public String supervisionAppPackage;
public boolean supervisionLockScreenEnabled;
@Nullable public PersistableBundle supervisionLockScreenOptions;
@@ -38,6 +39,7 @@ public class SupervisionUserData {
pw.println("User " + userId + ":");
pw.increaseIndent();
pw.println("supervisionEnabled: " + supervisionEnabled);
+ pw.println("supervisionAppPackage: " + supervisionAppPackage);
pw.println("supervisionLockScreenEnabled: " + supervisionLockScreenEnabled);
pw.println("supervisionLockScreenOptions: " + supervisionLockScreenOptions);
pw.decreaseIndent();
diff --git a/services/tests/displayservicetests/src/com/android/server/display/plugin/PluginManagerTest.kt b/services/tests/displayservicetests/src/com/android/server/display/plugin/PluginManagerTest.kt
index 01061f16c279..d9224eaf66ca 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/plugin/PluginManagerTest.kt
+++ b/services/tests/displayservicetests/src/com/android/server/display/plugin/PluginManagerTest.kt
@@ -29,6 +29,7 @@ import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever
private val TEST_PLUGIN_TYPE = PluginType(Int::class.java, "test_type")
+private val DISPLAY_ID = "display_id"
@SmallTest
class PluginManagerTest {
@@ -62,18 +63,18 @@ class PluginManagerTest {
fun testSubscribe() {
val pluginManager = createPluginManager()
- pluginManager.subscribe(TEST_PLUGIN_TYPE, mockListener)
+ pluginManager.subscribe(TEST_PLUGIN_TYPE, DISPLAY_ID, mockListener)
- verify(testInjector.mockStorage).addListener(TEST_PLUGIN_TYPE, mockListener)
+ verify(testInjector.mockStorage).addListener(TEST_PLUGIN_TYPE, DISPLAY_ID, mockListener)
}
@Test
fun testUnsubscribe() {
val pluginManager = createPluginManager()
- pluginManager.unsubscribe(TEST_PLUGIN_TYPE, mockListener)
+ pluginManager.unsubscribe(TEST_PLUGIN_TYPE, DISPLAY_ID, mockListener)
- verify(testInjector.mockStorage).removeListener(TEST_PLUGIN_TYPE, mockListener)
+ verify(testInjector.mockStorage).removeListener(TEST_PLUGIN_TYPE, DISPLAY_ID, mockListener)
}
private fun createPluginManager(enabled: Boolean = true): PluginManager {
diff --git a/services/tests/displayservicetests/src/com/android/server/display/plugin/PluginStorageTest.kt b/services/tests/displayservicetests/src/com/android/server/display/plugin/PluginStorageTest.kt
index 218e34134e93..8eb3e9fbf9b0 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/plugin/PluginStorageTest.kt
+++ b/services/tests/displayservicetests/src/com/android/server/display/plugin/PluginStorageTest.kt
@@ -23,6 +23,8 @@ import org.junit.Test
private val TEST_PLUGIN_TYPE1 = PluginType(String::class.java, "test_type1")
private val TEST_PLUGIN_TYPE2 = PluginType(String::class.java, "test_type2")
+private val DISPLAY_ID_1 = "display_1"
+private val DISPLAY_ID_2 = "display_2"
@SmallTest
class PluginStorageTest {
@@ -33,9 +35,9 @@ class PluginStorageTest {
fun testUpdateValue() {
val type1Value = "value1"
val testChangeListener = TestPluginChangeListener<String>()
- storage.addListener(TEST_PLUGIN_TYPE1, testChangeListener)
+ storage.addListener(TEST_PLUGIN_TYPE1, DISPLAY_ID_1, testChangeListener)
- storage.updateValue(TEST_PLUGIN_TYPE1, type1Value)
+ storage.updateValue(TEST_PLUGIN_TYPE1, DISPLAY_ID_1, type1Value)
assertThat(testChangeListener.receivedValue).isEqualTo(type1Value)
}
@@ -44,9 +46,9 @@ class PluginStorageTest {
fun testAddListener() {
val type1Value = "value1"
val testChangeListener = TestPluginChangeListener<String>()
- storage.updateValue(TEST_PLUGIN_TYPE1, type1Value)
+ storage.updateValue(TEST_PLUGIN_TYPE1, DISPLAY_ID_1, type1Value)
- storage.addListener(TEST_PLUGIN_TYPE1, testChangeListener)
+ storage.addListener(TEST_PLUGIN_TYPE1, DISPLAY_ID_1, testChangeListener)
assertThat(testChangeListener.receivedValue).isEqualTo(type1Value)
}
@@ -55,10 +57,10 @@ class PluginStorageTest {
fun testRemoveListener() {
val type1Value = "value1"
val testChangeListener = TestPluginChangeListener<String>()
- storage.addListener(TEST_PLUGIN_TYPE1, testChangeListener)
- storage.removeListener(TEST_PLUGIN_TYPE1, testChangeListener)
+ storage.addListener(TEST_PLUGIN_TYPE1, DISPLAY_ID_1, testChangeListener)
+ storage.removeListener(TEST_PLUGIN_TYPE1, DISPLAY_ID_1, testChangeListener)
- storage.updateValue(TEST_PLUGIN_TYPE1, type1Value)
+ storage.updateValue(TEST_PLUGIN_TYPE1, DISPLAY_ID_1, type1Value)
assertThat(testChangeListener.receivedValue).isNull()
}
@@ -68,10 +70,10 @@ class PluginStorageTest {
val type1Value = "value1"
val type2Value = "value2"
val testChangeListener = TestPluginChangeListener<String>()
- storage.updateValue(TEST_PLUGIN_TYPE1, type1Value)
- storage.updateValue(TEST_PLUGIN_TYPE2, type2Value)
+ storage.updateValue(TEST_PLUGIN_TYPE1, DISPLAY_ID_1, type1Value)
+ storage.updateValue(TEST_PLUGIN_TYPE2, DISPLAY_ID_1, type2Value)
- storage.addListener(TEST_PLUGIN_TYPE1, testChangeListener)
+ storage.addListener(TEST_PLUGIN_TYPE1, DISPLAY_ID_1, testChangeListener)
assertThat(testChangeListener.receivedValue).isEqualTo(type1Value)
}
@@ -81,15 +83,62 @@ class PluginStorageTest {
val type1Value = "value1"
val testChangeListener1 = TestPluginChangeListener<String>()
val testChangeListener2 = TestPluginChangeListener<String>()
- storage.addListener(TEST_PLUGIN_TYPE1, testChangeListener1)
- storage.addListener(TEST_PLUGIN_TYPE2, testChangeListener2)
+ storage.addListener(TEST_PLUGIN_TYPE1, DISPLAY_ID_1, testChangeListener1)
+ storage.addListener(TEST_PLUGIN_TYPE2, DISPLAY_ID_1, testChangeListener2)
- storage.updateValue(TEST_PLUGIN_TYPE1, type1Value)
+ storage.updateValue(TEST_PLUGIN_TYPE1, DISPLAY_ID_1, type1Value)
assertThat(testChangeListener1.receivedValue).isEqualTo(type1Value)
assertThat(testChangeListener2.receivedValue).isNull()
}
+ @Test
+ fun testUpdateGlobal_noDisplaySpecificValue() {
+ val type1Value = "value1"
+ val testChangeListener1 = TestPluginChangeListener<String>()
+ val testChangeListener2 = TestPluginChangeListener<String>()
+ storage.addListener(TEST_PLUGIN_TYPE1, DISPLAY_ID_1, testChangeListener1)
+ storage.addListener(TEST_PLUGIN_TYPE1, DISPLAY_ID_2, testChangeListener2)
+
+ storage.updateGlobalValue(TEST_PLUGIN_TYPE1, type1Value)
+
+ assertThat(testChangeListener1.receivedValue).isEqualTo(type1Value)
+ assertThat(testChangeListener2.receivedValue).isEqualTo(type1Value)
+ }
+
+ @Test
+ fun testUpdateGlobal_withDisplaySpecificValue() {
+ val type1Value = "value1"
+ val type1GlobalValue = "value1Global"
+ val testChangeListener1 = TestPluginChangeListener<String>()
+ val testChangeListener2 = TestPluginChangeListener<String>()
+ storage.addListener(TEST_PLUGIN_TYPE1, DISPLAY_ID_1, testChangeListener1)
+ storage.addListener(TEST_PLUGIN_TYPE1, DISPLAY_ID_2, testChangeListener2)
+
+ storage.updateValue(TEST_PLUGIN_TYPE1, DISPLAY_ID_1, type1Value)
+ storage.updateGlobalValue(TEST_PLUGIN_TYPE1, type1GlobalValue)
+
+ assertThat(testChangeListener1.receivedValue).isEqualTo(type1Value)
+ assertThat(testChangeListener2.receivedValue).isEqualTo(type1GlobalValue)
+ }
+
+ @Test
+ fun testUpdateGlobal_withDisplaySpecificValueRemoved() {
+ val type1Value = "value1"
+ val type1GlobalValue = "value1Global"
+ val testChangeListener1 = TestPluginChangeListener<String>()
+ val testChangeListener2 = TestPluginChangeListener<String>()
+ storage.addListener(TEST_PLUGIN_TYPE1, DISPLAY_ID_1, testChangeListener1)
+ storage.addListener(TEST_PLUGIN_TYPE1, DISPLAY_ID_2, testChangeListener2)
+
+ storage.updateValue(TEST_PLUGIN_TYPE1, DISPLAY_ID_1, type1Value)
+ storage.updateGlobalValue(TEST_PLUGIN_TYPE1, type1GlobalValue)
+ storage.updateValue(TEST_PLUGIN_TYPE1, DISPLAY_ID_1, null)
+
+ assertThat(testChangeListener1.receivedValue).isEqualTo(type1GlobalValue)
+ assertThat(testChangeListener2.receivedValue).isEqualTo(type1GlobalValue)
+ }
+
private class TestPluginChangeListener<T> : PluginChangeListener<T> {
var receivedValue: T? = null
diff --git a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
index 8dc8c14f8948..cb52f1849b5b 100644
--- a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
@@ -151,6 +151,7 @@ import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.UserManager;
import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
import android.platform.test.annotations.Presubmit;
import android.platform.test.flag.junit.SetFlagsRule;
import android.platform.test.flag.util.FlagSetException;
@@ -192,7 +193,9 @@ import org.junit.runner.RunWith;
import org.mockito.Answers;
import org.mockito.ArgumentCaptor;
import org.mockito.ArgumentMatchers;
+import org.mockito.InOrder;
import org.mockito.Mock;
+import org.mockito.Mockito;
import org.mockito.quality.Strictness;
import org.mockito.stubbing.Answer;
@@ -435,6 +438,7 @@ public final class AlarmManagerServiceTest {
private void disableFlagsNotSetByAnnotation() {
try {
mSetFlagsRule.disableFlags(Flags.FLAG_START_USER_BEFORE_SCHEDULED_ALARMS);
+ mSetFlagsRule.disableFlags(Flags.FLAG_ACQUIRE_WAKELOCK_BEFORE_SEND);
} catch (FlagSetException fse) {
// Expected if the test about to be run requires this enabled.
}
@@ -948,6 +952,28 @@ public final class AlarmManagerServiceTest {
}
@Test
+ @EnableFlags(Flags.FLAG_ACQUIRE_WAKELOCK_BEFORE_SEND)
+ public void testWakelockOrdering() throws Exception {
+ final long triggerTime = mNowElapsedTest + 5000;
+ final PendingIntent alarmPi = getNewMockPendingIntent();
+ setTestAlarm(ELAPSED_REALTIME_WAKEUP, triggerTime, alarmPi);
+
+ mNowElapsedTest = mTestTimer.getElapsed();
+ mTestTimer.expire();
+
+ final InOrder inOrder = Mockito.inOrder(alarmPi, mWakeLock);
+ inOrder.verify(mWakeLock).acquire();
+
+ final ArgumentCaptor<PendingIntent.OnFinished> onFinishedCaptor =
+ ArgumentCaptor.forClass(PendingIntent.OnFinished.class);
+ inOrder.verify(alarmPi).send(eq(mMockContext), eq(0), any(Intent.class),
+ onFinishedCaptor.capture(), any(Handler.class), isNull(), any());
+ onFinishedCaptor.getValue().onSendFinished(alarmPi, null, 0, null, null);
+
+ inOrder.verify(mWakeLock).release();
+ }
+
+ @Test
public void testMinFuturityCoreUid() {
setDeviceConfigLong(KEY_MIN_FUTURITY, 10L);
assertEquals(10, mService.mConstants.MIN_FUTURITY);
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/PendingIntentControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/am/PendingIntentControllerTest.java
index 27eada013642..d6349fc0651f 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/PendingIntentControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/PendingIntentControllerTest.java
@@ -18,6 +18,7 @@ package com.android.server.am;
import static android.os.PowerWhitelistManager.REASON_NOTIFICATION_SERVICE;
import static android.os.PowerWhitelistManager.TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED;
+import static android.os.PowerWhitelistManager.TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_NOT_ALLOWED;
import static android.os.Process.INVALID_UID;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
@@ -31,9 +32,10 @@ import static com.android.server.am.PendingIntentRecord.CANCEL_REASON_SUPERSEDED
import static com.android.server.am.PendingIntentRecord.CANCEL_REASON_USER_STOPPED;
import static com.android.server.am.PendingIntentRecord.FLAG_ACTIVITY_SENDER;
import static com.android.server.am.PendingIntentRecord.cancelReasonToString;
+import static com.android.window.flags.Flags.balClearAllowlistDuration;
import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertNotNull;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.eq;
@@ -207,10 +209,17 @@ public class PendingIntentControllerTest {
PendingIntentRecord.TempAllowListDuration allowlistDurationLocked =
pir.getAllowlistDurationLocked(token);
assertEquals(1000, allowlistDurationLocked.duration);
+ assertEquals(TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED,
+ allowlistDurationLocked.type);
pir.clearAllowBgActivityStarts(token);
PendingIntentRecord.TempAllowListDuration allowlistDurationLockedAfterClear =
pir.getAllowlistDurationLocked(token);
- assertNull(allowlistDurationLockedAfterClear);
+ assertNotNull(allowlistDurationLockedAfterClear);
+ assertEquals(1000, allowlistDurationLockedAfterClear.duration);
+ assertEquals(balClearAllowlistDuration()
+ ? TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_NOT_ALLOWED
+ : TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED,
+ allowlistDurationLocked.type);
}
private void assertCancelReason(int expectedReason, int actualReason) {
diff --git a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java
index 1cf655675a30..b92afc5c0ca7 100644
--- a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java
@@ -144,6 +144,8 @@ public class WallpaperManagerServiceTests {
private static ComponentName sImageWallpaperComponentName;
private static ComponentName sDefaultWallpaperComponent;
+ private static ComponentName sFallbackWallpaperComponentName;
+
private IPackageManager mIpm = AppGlobals.getPackageManager();
@Mock
@@ -195,6 +197,8 @@ public class WallpaperManagerServiceTests {
sContext.getResources().getString(R.string.image_wallpaper_component));
// Mock default wallpaper as image wallpaper if there is no pre-defined default wallpaper.
sDefaultWallpaperComponent = WallpaperManager.getCmfDefaultWallpaperComponent(sContext);
+ sFallbackWallpaperComponentName = ComponentName.unflattenFromString(
+ sContext.getResources().getString(R.string.fallback_wallpaper_component));
if (sDefaultWallpaperComponent == null) {
sDefaultWallpaperComponent = sImageWallpaperComponentName;
@@ -205,6 +209,9 @@ public class WallpaperManagerServiceTests {
}
sContext.addMockService(sImageWallpaperComponentName, sWallpaperService);
+ if (sFallbackWallpaperComponentName != null) {
+ sContext.addMockService(sFallbackWallpaperComponentName, sWallpaperService);
+ }
}
@AfterClass
@@ -216,6 +223,7 @@ public class WallpaperManagerServiceTests {
LocalServices.removeServiceForTest(WindowManagerInternal.class);
sImageWallpaperComponentName = null;
sDefaultWallpaperComponent = null;
+ sFallbackWallpaperComponentName = null;
reset(sContext);
}
@@ -306,6 +314,7 @@ public class WallpaperManagerServiceTests {
* Tests that internal basic data should be correct after boot up.
*/
@Test
+ @DisableFlags(Flags.FLAG_ENABLE_CONNECTED_DISPLAYS_WALLPAPER)
public void testDataCorrectAfterBoot() {
mService.switchUser(USER_SYSTEM, null);
@@ -719,7 +728,7 @@ public class WallpaperManagerServiceTests {
final int testDisplayId = 2;
setUpDisplays(List.of(DEFAULT_DISPLAY, testDisplayId));
- // WHEN displayId, 2, is ready.
+ // WHEN display ID, 2, is ready.
WallpaperManagerInternal wallpaperManagerInternal = LocalServices.getService(
WallpaperManagerInternal.class);
wallpaperManagerInternal.onDisplayReady(testDisplayId);
@@ -759,7 +768,7 @@ public class WallpaperManagerServiceTests {
final int testDisplayId = 2;
setUpDisplays(List.of(DEFAULT_DISPLAY, testDisplayId));
- // WHEN displayId, 2, is ready.
+ // WHEN display ID, 2, is ready.
WallpaperManagerInternal wallpaperManagerInternal = LocalServices.getService(
WallpaperManagerInternal.class);
wallpaperManagerInternal.onDisplayReady(testDisplayId);
@@ -791,6 +800,40 @@ public class WallpaperManagerServiceTests {
/* info= */ any(),
/* description= */ any());
}
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_CONNECTED_DISPLAYS_WALLPAPER)
+ public void displayAdded_wallpaperIncompatibleForDisplay_shouldAttachFallbackWallpaperService()
+ throws Exception {
+ final int testUserId = USER_SYSTEM;
+ mService.switchUser(testUserId, null);
+ IWallpaperService mockIWallpaperService = mock(IWallpaperService.class);
+ mService.mFallbackWallpaper.connection.mService = mockIWallpaperService;
+ // GIVEN there are two displays: DEFAULT_DISPLAY, 2
+ final int testDisplayId = 2;
+ setUpDisplays(List.of(DEFAULT_DISPLAY, testDisplayId));
+ // GIVEN the wallpaper isn't compatible with display ID, 2
+ mService.removeWallpaperCompatibleDisplayForTest(testDisplayId);
+
+ // WHEN display ID, 2, is ready.
+ WallpaperManagerInternal wallpaperManagerInternal = LocalServices.getService(
+ WallpaperManagerInternal.class);
+ wallpaperManagerInternal.onDisplayReady(testDisplayId);
+
+ // Then there is a connection established for the fallback wallpaper for display ID, 2.
+ verify(mockIWallpaperService).attach(
+ /* connection= */ eq(mService.mFallbackWallpaper.connection),
+ /* windowToken= */ any(),
+ /* windowType= */ anyInt(),
+ /* isPreview= */ anyBoolean(),
+ /* reqWidth= */ anyInt(),
+ /* reqHeight= */ anyInt(),
+ /* padding= */ any(),
+ /* displayId= */ eq(testDisplayId),
+ /* which= */ eq(FLAG_SYSTEM | FLAG_LOCK),
+ /* info= */ any(),
+ /* description= */ any());
+ }
// Verify a secondary display added end
// Verify a secondary display removed started
@@ -810,7 +853,7 @@ public class WallpaperManagerServiceTests {
// GIVEN there are two displays: DEFAULT_DISPLAY, 2
final int testDisplayId = 2;
setUpDisplays(List.of(DEFAULT_DISPLAY, testDisplayId));
- // GIVEN wallpaper connections have been established for displayID, 2.
+ // GIVEN wallpaper connections have been established for display ID, 2.
WallpaperManagerInternal wallpaperManagerInternal = LocalServices.getService(
WallpaperManagerInternal.class);
wallpaperManagerInternal.onDisplayReady(testDisplayId);
@@ -818,11 +861,11 @@ public class WallpaperManagerServiceTests {
WallpaperManagerService.DisplayConnector displayConnector =
wallpaper.connection.getDisplayConnectorOrCreate(testDisplayId);
- // WHEN displayId, 2, is removed.
+ // WHEN display ID, 2, is removed.
DisplayListener displayListener = displayListenerCaptor.getValue();
displayListener.onDisplayRemoved(testDisplayId);
- // Then the wallpaper connection for displayId, 2, is detached.
+ // Then the wallpaper connection for display ID, 2, is detached.
verify(mockIWallpaperService).detach(eq(displayConnector.mToken));
}
@@ -848,26 +891,142 @@ public class WallpaperManagerServiceTests {
// GIVEN there are two displays: DEFAULT_DISPLAY, 2
final int testDisplayId = 2;
setUpDisplays(List.of(DEFAULT_DISPLAY, testDisplayId));
- // GIVEN wallpaper connections have been established for displayID, 2.
+ // GIVEN wallpaper connections have been established for display ID, 2.
WallpaperManagerInternal wallpaperManagerInternal = LocalServices.getService(
WallpaperManagerInternal.class);
wallpaperManagerInternal.onDisplayReady(testDisplayId);
- // Save displayConnectors for displayId 2 before display removal.
+ // Save displayConnectors for display ID, 2, before display removal.
WallpaperManagerService.DisplayConnector systemWallpaperDisplayConnector =
systemWallpaper.connection.getDisplayConnectorOrCreate(testDisplayId);
WallpaperManagerService.DisplayConnector lockWallpaperDisplayConnector =
lockWallpaper.connection.getDisplayConnectorOrCreate(testDisplayId);
+ // WHEN display ID, 2, is removed.
+ DisplayListener displayListener = displayListenerCaptor.getValue();
+ displayListener.onDisplayRemoved(testDisplayId);
+
+ // Then the system wallpaper connection for display ID, 2, is detached.
+ verify(mockIWallpaperService).detach(eq(systemWallpaperDisplayConnector.mToken));
+ // Then the lock wallpaper connection for display ID, 2, is detached.
+ verify(mockIWallpaperService).detach(eq(lockWallpaperDisplayConnector.mToken));
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_CONNECTED_DISPLAYS_WALLPAPER)
+ public void displayRemoved_fallbackWallpaper_shouldDetachFallbackWallpaperService()
+ throws Exception {
+ ArgumentCaptor<DisplayListener> displayListenerCaptor = ArgumentCaptor.forClass(
+ DisplayListener.class);
+ verify(mDisplayManager).registerDisplayListener(displayListenerCaptor.capture(), eq(null));
+ final int testUserId = USER_SYSTEM;
+ mService.switchUser(testUserId, null);
+ IWallpaperService mockIWallpaperService = mock(IWallpaperService.class);
+ mService.mFallbackWallpaper.connection.mService = mockIWallpaperService;
+ // GIVEN there are two displays: DEFAULT_DISPLAY, 2
+ final int testDisplayId = 2;
+ setUpDisplays(List.of(DEFAULT_DISPLAY, testDisplayId));
+ // GIVEN display ID, 2, is incompatible with the wallpaper.
+ mService.removeWallpaperCompatibleDisplayForTest(testDisplayId);
+ // GIVEN wallpaper connections have been established for display ID, 2.
+ WallpaperManagerInternal wallpaperManagerInternal = LocalServices.getService(
+ WallpaperManagerInternal.class);
+ wallpaperManagerInternal.onDisplayReady(testDisplayId);
+ // Save fallback wallpaper displayConnector for display ID, 2, before display removal.
+ WallpaperManagerService.DisplayConnector fallbackWallpaperConnector =
+ mService.mFallbackWallpaper.connection.getDisplayConnectorOrCreate(testDisplayId);
+
// WHEN displayId, 2, is removed.
DisplayListener displayListener = displayListenerCaptor.getValue();
displayListener.onDisplayRemoved(testDisplayId);
+ // Then the fallback wallpaper connection for display ID, 2, is detached.
+ verify(mockIWallpaperService).detach(eq(fallbackWallpaperConnector.mToken));
+ }
+ // Verify a secondary display removed ended
+
+ // Test fallback wallpaper after enabling connected display supports.
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_CONNECTED_DISPLAYS_WALLPAPER)
+ public void testFallbackWallpaperForConnectedDisplays() {
+ final WallpaperData fallbackData = mService.mFallbackWallpaper;
+
+ assertWithMessage(
+ "The connected wallpaper component should be fallback wallpaper component from "
+ + "config file.")
+ .that(fallbackData.connection.mWallpaper.getComponent())
+ .isEqualTo(sFallbackWallpaperComponentName);
+ assertWithMessage("Fallback wallpaper should support both lock & system.")
+ .that(fallbackData.mWhich)
+ .isEqualTo(FLAG_LOCK | FLAG_SYSTEM);
+ }
+
+ // Verify a secondary display removes system decorations started
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_CONNECTED_DISPLAYS_WALLPAPER)
+ public void displayRemoveSystemDecorations_sameSystemAndLockWallpaper_shouldDetachWallpaperServiceOnce()
+ throws Exception {
+ // GIVEN the same wallpaper used for the lock and system.
+ final int testUserId = USER_SYSTEM;
+ mService.switchUser(testUserId, null);
+ final WallpaperData wallpaper = mService.getCurrentWallpaperData(FLAG_SYSTEM, testUserId);
+ IWallpaperService mockIWallpaperService = mock(IWallpaperService.class);
+ wallpaper.connection.mService = mockIWallpaperService;
+ // GIVEN there are two displays: DEFAULT_DISPLAY, 2
+ final int testDisplayId = 2;
+ setUpDisplays(List.of(DEFAULT_DISPLAY, testDisplayId));
+ // GIVEN wallpaper connections have been established for displayID, 2.
+ WallpaperManagerInternal wallpaperManagerInternal = LocalServices.getService(
+ WallpaperManagerInternal.class);
+ wallpaperManagerInternal.onDisplayReady(testDisplayId);
+ // Save displayConnector for displayId 2 before display removal.
+ WallpaperManagerService.DisplayConnector displayConnector =
+ wallpaper.connection.getDisplayConnectorOrCreate(testDisplayId);
+
+ // WHEN displayId, 2, is removed.
+ wallpaperManagerInternal.onDisplayRemoveSystemDecorations(testDisplayId);
+
+ // Then the wallpaper connection for displayId, 2, is detached.
+ verify(mockIWallpaperService).detach(eq(displayConnector.mToken));
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_CONNECTED_DISPLAYS_WALLPAPER)
+ public void displayRemoveSystemDecorations_differentSystemAndLockWallpapers_shouldDetachWallpaperServiceTwice()
+ throws Exception {
+ // GIVEN different wallpapers used for the lock and system.
+ final int testUserId = USER_SYSTEM;
+ mService.switchUser(testUserId, null);
+ mService.setWallpaperComponent(sImageWallpaperComponentName, sContext.getOpPackageName(),
+ FLAG_LOCK, testUserId);
+ final WallpaperData systemWallpaper = mService.getCurrentWallpaperData(FLAG_SYSTEM,
+ testUserId);
+ final WallpaperData lockWallpaper = mService.getCurrentWallpaperData(FLAG_LOCK,
+ testUserId);
+ IWallpaperService mockIWallpaperService = mock(IWallpaperService.class);
+ systemWallpaper.connection.mService = mockIWallpaperService;
+ lockWallpaper.connection.mService = mockIWallpaperService;
+ // GIVEN there are two displays: DEFAULT_DISPLAY, 2
+ final int testDisplayId = 2;
+ setUpDisplays(List.of(DEFAULT_DISPLAY, testDisplayId));
+ // GIVEN wallpaper connections have been established for displayID, 2.
+ WallpaperManagerInternal wallpaperManagerInternal = LocalServices.getService(
+ WallpaperManagerInternal.class);
+ wallpaperManagerInternal.onDisplayReady(testDisplayId);
+ // Save displayConnectors for displayId 2 before display removal.
+ WallpaperManagerService.DisplayConnector systemWallpaperDisplayConnector =
+ systemWallpaper.connection.getDisplayConnectorOrCreate(testDisplayId);
+ WallpaperManagerService.DisplayConnector lockWallpaperDisplayConnector =
+ lockWallpaper.connection.getDisplayConnectorOrCreate(testDisplayId);
+
+ // WHEN displayId, 2, is removed.
+ wallpaperManagerInternal.onDisplayRemoveSystemDecorations(testDisplayId);
+
// Then the system wallpaper connection for displayId, 2, is detached.
verify(mockIWallpaperService).detach(eq(systemWallpaperDisplayConnector.mToken));
// Then the lock wallpaper connection for displayId, 2, is detached.
verify(mockIWallpaperService).detach(eq(lockWallpaperDisplayConnector.mToken));
}
- // Verify a secondary display removed ended
+ // Verify a secondary display removes system decorations ended
// Verify that after continue switch user from userId 0 to lastUserId, the wallpaper data for
// non-current user must not bind to wallpaper service.
@@ -893,11 +1052,11 @@ public class WallpaperManagerServiceTests {
final WallpaperData lastLockData = mService.mLastLockWallpaper;
assertWithMessage("Last wallpaper must not be null").that(lastLockData).isNotNull();
assertWithMessage("Last wallpaper component must be equals.")
- .that(expectedComponent)
- .isEqualTo(lastLockData.getComponent());
+ .that(lastLockData.getComponent())
+ .isEqualTo(expectedComponent);
assertWithMessage("The user id in last wallpaper should be the last switched user")
- .that(lastUserId)
- .isEqualTo(lastLockData.userId);
+ .that(lastLockData.userId)
+ .isEqualTo(lastUserId);
assertWithMessage("Must exist user data connection on last wallpaper data")
.that(lastLockData.connection)
.isNotNull();
@@ -946,6 +1105,7 @@ public class WallpaperManagerServiceTests {
doReturn(mockDisplay).when(mDisplayManager).getDisplay(eq(displayId));
doReturn(displayId).when(mockDisplay).getDisplayId();
doReturn(true).when(mockDisplay).hasAccess(anyInt());
+ mService.addWallpaperCompatibleDisplayForTest(displayId);
}
doReturn(mockDisplays).when(mDisplayManager).getDisplays();
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsProviderTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsProviderTest.java
index d427c9d9ee37..e94ef5bb4871 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsProviderTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsProviderTest.java
@@ -39,6 +39,8 @@ import android.os.Handler;
import android.os.Parcel;
import android.os.Process;
import android.os.UidBatteryConsumer;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.platform.test.ravenwood.RavenwoodRule;
import android.util.SparseLongArray;
@@ -49,6 +51,7 @@ import androidx.test.runner.AndroidJUnit4;
import com.android.internal.os.BatteryStatsHistoryIterator;
import com.android.internal.os.MonotonicClock;
import com.android.internal.os.PowerProfile;
+import com.android.server.power.optimization.Flags;
import com.android.server.power.stats.processor.MultiStatePowerAttributor;
import org.junit.Before;
@@ -59,6 +62,7 @@ import org.junit.runner.RunWith;
import java.io.File;
import java.io.IOException;
import java.util.List;
+import java.util.concurrent.TimeUnit;
@SmallTest
@RunWith(AndroidJUnit4.class)
@@ -68,11 +72,14 @@ public class BatteryUsageStatsProviderTest {
.setProvideMainThread(true)
.build();
+ @Rule(order = 1)
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
private static final int APP_UID = Process.FIRST_APPLICATION_UID + 42;
private static final long MINUTE_IN_MS = 60 * 1000;
private static final double PRECISION = 0.00001;
- @Rule(order = 1)
+ @Rule(order = 2)
public final BatteryUsageStatsRule mStatsRule =
new BatteryUsageStatsRule(12345)
.createTempDirectory()
@@ -868,4 +875,62 @@ public class BatteryUsageStatsProviderTest {
stats.close();
}
+
+ @Test
+ @EnableFlags(Flags.FLAG_EXTENDED_BATTERY_HISTORY_CONTINUOUS_COLLECTION_ENABLED)
+ public void testIncludeSubsetOfHistory() throws IOException {
+ MockBatteryStatsImpl batteryStats = mStatsRule.getBatteryStats();
+ batteryStats.getHistory().setMaxHistoryBufferSize(100);
+ synchronized (batteryStats) {
+ batteryStats.setRecordAllHistoryLocked(true);
+ }
+ batteryStats.forceRecordAllHistory();
+ batteryStats.setNoAutoReset(true);
+
+ long lastIncludedEventTimestamp = 0;
+ String tag = "work work work work work work work work work work work work work work work";
+ for (int i = 1; i < 50; i++) {
+ mStatsRule.advanceTime(TimeUnit.MINUTES.toMillis(9));
+ synchronized (batteryStats) {
+ batteryStats.noteJobStartLocked(tag, 42);
+ }
+ mStatsRule.advanceTime(TimeUnit.MINUTES.toMillis(1));
+ synchronized (batteryStats) {
+ batteryStats.noteJobFinishLocked(tag, 42, 0);
+ }
+ lastIncludedEventTimestamp = mMonotonicClock.monotonicTime();
+ }
+
+ BatteryUsageStatsProvider provider = new BatteryUsageStatsProvider(mContext,
+ mock(PowerAttributor.class), mStatsRule.getPowerProfile(),
+ mStatsRule.getCpuScalingPolicies(), mock(PowerStatsStore.class), 0, mMockClock,
+ mMonotonicClock);
+
+ BatteryUsageStatsQuery query = new BatteryUsageStatsQuery.Builder()
+ .includeBatteryHistory()
+ .setPreferredHistoryDurationMs(TimeUnit.MINUTES.toMillis(20))
+ .build();
+ final BatteryUsageStats stats = provider.getBatteryUsageStats(batteryStats, query);
+ Parcel parcel = Parcel.obtain();
+ stats.writeToParcel(parcel, 0);
+ stats.close();
+
+ parcel.setDataPosition(0);
+ BatteryUsageStats actual = BatteryUsageStats.CREATOR.createFromParcel(parcel);
+
+ long firstIncludedEventTimestamp = 0;
+ try (BatteryStatsHistoryIterator it = actual.iterateBatteryStatsHistory()) {
+ BatteryStats.HistoryItem item;
+ while ((item = it.next()) != null) {
+ if (item.eventCode == BatteryStats.HistoryItem.EVENT_JOB_START) {
+ firstIncludedEventTimestamp = item.time;
+ break;
+ }
+ }
+ }
+ actual.close();
+
+ assertThat(firstIncludedEventTimestamp)
+ .isAtLeast(lastIncludedEventTimestamp - TimeUnit.MINUTES.toMillis(30));
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityInputFilterTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityInputFilterTest.java
index ecc48bfc40e4..f55ca0c0b12b 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityInputFilterTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityInputFilterTest.java
@@ -57,6 +57,7 @@ import androidx.test.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
import com.android.server.LocalServices;
+import com.android.server.accessibility.autoclick.AutoclickController;
import com.android.server.accessibility.gestures.TouchExplorer;
import com.android.server.accessibility.magnification.FullScreenMagnificationGestureHandler;
import com.android.server.accessibility.magnification.MagnificationGestureHandler;
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
index 5602fb76e6f5..28e5be505556 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
@@ -68,7 +68,6 @@ import android.annotation.UserIdInt;
import android.app.PendingIntent;
import android.app.RemoteAction;
import android.app.admin.DevicePolicyManager;
-import android.app.ecm.EnhancedConfirmationManager;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
@@ -2120,23 +2119,6 @@ public class AccessibilityManagerServiceTest {
}
@Test
- @EnableFlags({android.permission.flags.Flags.FLAG_ENHANCED_CONFIRMATION_MODE_APIS_ENABLED,
- android.security.Flags.FLAG_EXTEND_ECM_TO_ALL_SETTINGS})
- public void isAccessibilityTargetAllowed_nonSystemUserId_useEcmWithNonSystemUserId() {
- String fakePackageName = "FAKE_PACKAGE_NAME";
- int uid = 0; // uid is not used in the actual implementation when flags are on
- int userId = mTestableContext.getUserId() + 1234;
- when(mDevicePolicyManager.getPermittedAccessibilityServices(userId)).thenReturn(
- List.of(fakePackageName));
- Context mockUserContext = mock(Context.class);
- mTestableContext.addMockUserContext(userId, mockUserContext);
-
- mA11yms.isAccessibilityTargetAllowed(fakePackageName, uid, userId);
-
- verify(mockUserContext).getSystemService(EnhancedConfirmationManager.class);
- }
-
- @Test
@EnableFlags(com.android.hardware.input.Flags.FLAG_ENABLE_TALKBACK_AND_MAGNIFIER_KEY_GESTURES)
public void handleKeyGestureEvent_toggleMagnifier() {
mFakePermissionEnforcer.grant(Manifest.permission.MANAGE_ACCESSIBILITY);
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AutoclickControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickControllerTest.java
index acce813ff659..f02bdae1d9e6 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AutoclickControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickControllerTest.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.server.accessibility;
+package com.android.server.accessibility.autoclick;
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
@@ -39,6 +39,8 @@ import android.view.MotionEvent;
import android.view.WindowManager;
import android.view.accessibility.AccessibilityManager;
+import com.android.server.accessibility.AccessibilityTraceManager;
+
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java
index 4ef602f1a64c..3511ae12497a 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java
@@ -58,9 +58,9 @@ import android.graphics.Rect;
import android.graphics.Region;
import android.hardware.display.DisplayManager;
import android.hardware.display.DisplayManagerInternal;
-import android.os.Looper;
import android.os.RemoteException;
import android.os.UserHandle;
+import android.os.test.TestLooper;
import android.provider.Settings;
import android.test.mock.MockContentResolver;
import android.testing.DexmakerShareClassLoaderRule;
@@ -173,6 +173,8 @@ public class MagnificationControllerTest {
@Mock
private Scroller mMockScroller;
+ private TestLooper mTestLooper;
+
// To mock package-private class
@Rule
public final DexmakerShareClassLoaderRule mDexmakerShareClassLoaderRule =
@@ -199,14 +201,16 @@ public class MagnificationControllerTest {
mMockResolver = new MockContentResolver();
mMockResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider());
- Looper looper = InstrumentationRegistry.getContext().getMainLooper();
- // Pretending ID of the Thread associated with looper as main thread ID in controller
- when(mContext.getMainLooper()).thenReturn(looper);
+ mTestLooper = new TestLooper();
+ when(mContext.getMainLooper()).thenReturn(
+ InstrumentationRegistry.getContext().getMainLooper());
when(mContext.getContentResolver()).thenReturn(mMockResolver);
when(mContext.getPackageManager()).thenReturn(mPackageManager);
Settings.Secure.putFloatForUser(mMockResolver,
Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE, DEFAULT_SCALE,
CURRENT_USER_ID);
+ Settings.Secure.putFloatForUser(mMockResolver, Settings.Secure.KEY_REPEAT_ENABLED, 1,
+ CURRENT_USER_ID);
mScaleProvider = spy(new MagnificationScaleProvider(mContext));
when(mControllerCtx.getContext()).thenReturn(mContext);
@@ -251,7 +255,7 @@ public class MagnificationControllerTest {
mMagnificationController = spy(new MagnificationController(mService, globalLock, mContext,
mScreenMagnificationController, mMagnificationConnectionManager, mScaleProvider,
- ConcurrentUtils.DIRECT_EXECUTOR));
+ ConcurrentUtils.DIRECT_EXECUTOR, mTestLooper.getLooper()));
mMagnificationController.setMagnificationCapabilities(
Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_ALL);
@@ -261,6 +265,7 @@ public class MagnificationControllerTest {
@After
public void tearDown() {
+ mTestLooper.dispatchAll();
FakeSettingsProvider.clearSettingsProvider();
}
@@ -880,6 +885,69 @@ public class MagnificationControllerTest {
}
@Test
+ public void magnificationCallbacks_panMagnificationContinuous() throws RemoteException {
+ setMagnificationEnabled(MODE_FULLSCREEN);
+ mMagnificationController.onPerformScaleAction(TEST_DISPLAY, 8.0f, false);
+ reset(mScreenMagnificationController);
+
+ DisplayMetrics metrics = new DisplayMetrics();
+ mDisplay.getMetrics(metrics);
+ float expectedStep = 27 * metrics.density;
+
+ float currentCenterX = mScreenMagnificationController.getCenterX(TEST_DISPLAY);
+ float currentCenterY = mScreenMagnificationController.getCenterY(TEST_DISPLAY);
+
+ // Start moving right using keyboard callbacks.
+ mMagnificationController.onPanMagnificationStart(TEST_DISPLAY,
+ MagnificationController.PAN_DIRECTION_RIGHT);
+
+ float newCenterX = mScreenMagnificationController.getCenterX(TEST_DISPLAY);
+ float newCenterY = mScreenMagnificationController.getCenterY(TEST_DISPLAY);
+ expect.that(currentCenterX).isLessThan(newCenterX);
+ expect.that(newCenterX - currentCenterX).isWithin(0.01f).of(expectedStep);
+ expect.that(currentCenterY).isEqualTo(newCenterY);
+
+ currentCenterX = newCenterX;
+ currentCenterY = newCenterY;
+
+ // Wait for the initial delay to occur.
+ advanceTime(MagnificationController.INITIAL_KEYBOARD_REPEAT_INTERVAL_MS + 1);
+
+ // It should have moved again after the handler was triggered.
+ newCenterX = mScreenMagnificationController.getCenterX(TEST_DISPLAY);
+ newCenterY = mScreenMagnificationController.getCenterY(TEST_DISPLAY);
+ expect.that(currentCenterX).isLessThan(newCenterX);
+ expect.that(newCenterX - currentCenterX).isWithin(0.01f).of(expectedStep);
+ expect.that(currentCenterY).isEqualTo(newCenterY);
+ currentCenterX = newCenterX;
+ currentCenterY = newCenterY;
+
+ // Wait for repeat delay to occur.
+ advanceTime(MagnificationController.KEYBOARD_REPEAT_INTERVAL_MS + 1);
+
+ // It should have moved a third time.
+ newCenterX = mScreenMagnificationController.getCenterX(TEST_DISPLAY);
+ newCenterY = mScreenMagnificationController.getCenterY(TEST_DISPLAY);
+ expect.that(currentCenterX).isLessThan(newCenterX);
+ expect.that(newCenterX - currentCenterX).isWithin(0.01f).of(expectedStep);
+ expect.that(currentCenterY).isEqualTo(newCenterY);
+ currentCenterX = newCenterX;
+ currentCenterY = newCenterY;
+
+ // Stop magnification pan.
+ mMagnificationController.onPanMagnificationStop(TEST_DISPLAY,
+ MagnificationController.PAN_DIRECTION_RIGHT);
+
+ // It should not move again, even after the appropriate delay.
+ advanceTime(MagnificationController.KEYBOARD_REPEAT_INTERVAL_MS + 1);
+
+ newCenterX = mScreenMagnificationController.getCenterX(TEST_DISPLAY);
+ newCenterY = mScreenMagnificationController.getCenterY(TEST_DISPLAY);
+ expect.that(newCenterX).isEqualTo(currentCenterX);
+ expect.that(newCenterY).isEqualTo(currentCenterY);
+ }
+
+ @Test
public void enableWindowMode_notifyMagnificationChanged() throws RemoteException {
setMagnificationEnabled(MODE_WINDOW);
@@ -1196,7 +1264,8 @@ public class MagnificationControllerTest {
assertEquals(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN, lastActivatedMode);
}
- @Test public void activateFullScreenMagnification_triggerCallback() throws RemoteException {
+ @Test
+ public void activateFullScreenMagnification_triggerCallback() throws RemoteException {
setMagnificationEnabled(MODE_FULLSCREEN);
verify(mMagnificationController).onFullScreenMagnificationActivationState(
eq(TEST_DISPLAY), eq(true));
@@ -1573,8 +1642,8 @@ public class MagnificationControllerTest {
float currentCenterX = mScreenMagnificationController.getCenterX(TEST_DISPLAY);
float currentCenterY = mScreenMagnificationController.getCenterY(TEST_DISPLAY);
- // Move right.
- mMagnificationController.panMagnificationByStep(TEST_DISPLAY,
+ // Move right using keyboard callbacks.
+ mMagnificationController.onPanMagnificationStart(TEST_DISPLAY,
MagnificationController.PAN_DIRECTION_RIGHT);
float newCenterX = mScreenMagnificationController.getCenterX(TEST_DISPLAY);
float newCenterY = mScreenMagnificationController.getCenterY(TEST_DISPLAY);
@@ -1582,11 +1651,13 @@ public class MagnificationControllerTest {
expect.that(newCenterX - currentCenterX).isWithin(0.01f).of(expectedStep);
expect.that(currentCenterY).isEqualTo(newCenterY);
+ mMagnificationController.onPanMagnificationStop(TEST_DISPLAY,
+ MagnificationController.PAN_DIRECTION_RIGHT);
currentCenterX = newCenterX;
currentCenterY = newCenterY;
// Move left.
- mMagnificationController.panMagnificationByStep(TEST_DISPLAY,
+ mMagnificationController.onPanMagnificationStart(TEST_DISPLAY,
MagnificationController.PAN_DIRECTION_LEFT);
newCenterX = mScreenMagnificationController.getCenterX(TEST_DISPLAY);
newCenterY = mScreenMagnificationController.getCenterY(TEST_DISPLAY);
@@ -1594,11 +1665,13 @@ public class MagnificationControllerTest {
expect.that(currentCenterX - newCenterX).isWithin(0.01f).of(expectedStep);
expect.that(currentCenterY).isEqualTo(newCenterY);
+ mMagnificationController.onPanMagnificationStop(TEST_DISPLAY,
+ MagnificationController.PAN_DIRECTION_LEFT);
currentCenterX = newCenterX;
currentCenterY = newCenterY;
// Move down.
- mMagnificationController.panMagnificationByStep(TEST_DISPLAY,
+ mMagnificationController.onPanMagnificationStart(TEST_DISPLAY,
MagnificationController.PAN_DIRECTION_DOWN);
newCenterX = mScreenMagnificationController.getCenterX(TEST_DISPLAY);
newCenterY = mScreenMagnificationController.getCenterY(TEST_DISPLAY);
@@ -1606,17 +1679,22 @@ public class MagnificationControllerTest {
expect.that(currentCenterY).isLessThan(newCenterY);
expect.that(newCenterY - currentCenterY).isWithin(0.1f).of(expectedStep);
+ mMagnificationController.onPanMagnificationStop(TEST_DISPLAY,
+ MagnificationController.PAN_DIRECTION_DOWN);
currentCenterX = newCenterX;
currentCenterY = newCenterY;
// Move up.
- mMagnificationController.panMagnificationByStep(TEST_DISPLAY,
+ mMagnificationController.onPanMagnificationStart(TEST_DISPLAY,
MagnificationController.PAN_DIRECTION_UP);
newCenterX = mScreenMagnificationController.getCenterX(TEST_DISPLAY);
newCenterY = mScreenMagnificationController.getCenterY(TEST_DISPLAY);
expect.that(currentCenterX).isEqualTo(newCenterX);
expect.that(currentCenterY).isGreaterThan(newCenterY);
expect.that(currentCenterY - newCenterY).isWithin(0.01f).of(expectedStep);
+
+ mMagnificationController.onPanMagnificationStop(TEST_DISPLAY,
+ MagnificationController.PAN_DIRECTION_UP);
}
private void testWindowMagnificationPanWithStepSize(float expectedStepDip)
@@ -1626,28 +1704,41 @@ public class MagnificationControllerTest {
final float expectedStep = expectedStepDip * metrics.density;
// Move right.
- mMagnificationController.panMagnificationByStep(TEST_DISPLAY,
+ mMagnificationController.onPanMagnificationStart(TEST_DISPLAY,
MagnificationController.PAN_DIRECTION_RIGHT);
verify(mMockConnection.getConnection()).moveWindowMagnifier(eq(TEST_DISPLAY),
floatThat(step -> Math.abs(step - expectedStep) < 0.0001), eq(0.0f));
+ mMagnificationController.onPanMagnificationStop(TEST_DISPLAY,
+ MagnificationController.PAN_DIRECTION_RIGHT);
// Move left.
- mMagnificationController.panMagnificationByStep(TEST_DISPLAY,
+ mMagnificationController.onPanMagnificationStart(TEST_DISPLAY,
MagnificationController.PAN_DIRECTION_LEFT);
verify(mMockConnection.getConnection()).moveWindowMagnifier(eq(TEST_DISPLAY),
floatThat(step -> Math.abs(expectedStep - step) < 0.0001), eq(0.0f));
+ mMagnificationController.onPanMagnificationStop(TEST_DISPLAY,
+ MagnificationController.PAN_DIRECTION_LEFT);
// Move down.
- mMagnificationController.panMagnificationByStep(TEST_DISPLAY,
+ mMagnificationController.onPanMagnificationStart(TEST_DISPLAY,
MagnificationController.PAN_DIRECTION_DOWN);
verify(mMockConnection.getConnection()).moveWindowMagnifier(eq(TEST_DISPLAY),
eq(0.0f), floatThat(step -> Math.abs(expectedStep - step) < 0.0001));
+ mMagnificationController.onPanMagnificationStop(TEST_DISPLAY,
+ MagnificationController.PAN_DIRECTION_DOWN);
// Move up.
- mMagnificationController.panMagnificationByStep(TEST_DISPLAY,
+ mMagnificationController.onPanMagnificationStart(TEST_DISPLAY,
MagnificationController.PAN_DIRECTION_UP);
verify(mMockConnection.getConnection()).moveWindowMagnifier(eq(TEST_DISPLAY),
eq(0.0f), floatThat(step -> Math.abs(expectedStep - step) < 0.0001));
+ mMagnificationController.onPanMagnificationStop(TEST_DISPLAY,
+ MagnificationController.PAN_DIRECTION_UP);
+ }
+
+ private void advanceTime(long timeMs) {
+ mTestLooper.moveTimeForward(timeMs);
+ mTestLooper.dispatchAll();
}
private static class WindowMagnificationMgrCallbackDelegate implements
diff --git a/services/tests/servicestests/src/com/android/server/audio/AbsoluteVolumeBehaviorTest.java b/services/tests/servicestests/src/com/android/server/audio/AbsoluteVolumeBehaviorTest.java
index ef9580c54de6..cbe2bfb26cd6 100644
--- a/services/tests/servicestests/src/com/android/server/audio/AbsoluteVolumeBehaviorTest.java
+++ b/services/tests/servicestests/src/com/android/server/audio/AbsoluteVolumeBehaviorTest.java
@@ -28,8 +28,10 @@ import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.Manifest;
import android.app.AppOpsManager;
import android.content.Context;
+import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.media.AudioDeviceAttributes;
import android.media.AudioDeviceInfo;
@@ -38,6 +40,7 @@ import android.media.AudioManager;
import android.media.AudioSystem;
import android.media.IAudioDeviceVolumeDispatcher;
import android.media.VolumeInfo;
+import android.os.IpcDataCache;
import android.os.PermissionEnforcer;
import android.os.RemoteException;
import android.os.test.TestLooper;
@@ -83,6 +86,7 @@ public class AbsoluteVolumeBehaviorTest {
@Before
public void setUp() throws Exception {
+ IpcDataCache.disableForTestMode();
mTestLooper = new TestLooper();
mContext = spy(ApplicationProvider.getApplicationContext());
@@ -93,6 +97,10 @@ public class AbsoluteVolumeBehaviorTest {
when(mResources.getBoolean(com.android.internal.R.bool.config_useFixedVolume))
.thenReturn(false);
+ when(mContext.checkCallingOrSelfPermission(
+ Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED))
+ .thenReturn(PackageManager.PERMISSION_GRANTED);
+
mSpyAudioSystem = spy(new NoOpAudioSystemAdapter());
mSystemServer = new NoOpSystemServerAdapter();
mSettingsAdapter = new NoOpSettingsAdapter();
diff --git a/services/tests/servicestests/src/com/android/server/audio/DeviceVolumeBehaviorTest.java b/services/tests/servicestests/src/com/android/server/audio/DeviceVolumeBehaviorTest.java
index 746645a8c585..541dbba67957 100644
--- a/services/tests/servicestests/src/com/android/server/audio/DeviceVolumeBehaviorTest.java
+++ b/services/tests/servicestests/src/com/android/server/audio/DeviceVolumeBehaviorTest.java
@@ -28,6 +28,7 @@ import android.media.AudioDeviceAttributes;
import android.media.AudioDeviceInfo;
import android.media.AudioManager;
import android.media.IDeviceVolumeBehaviorDispatcher;
+import android.os.IpcDataCache;
import android.os.PermissionEnforcer;
import android.os.test.TestLooper;
import android.platform.test.annotations.Presubmit;
@@ -69,6 +70,7 @@ public class DeviceVolumeBehaviorTest {
@Before
public void setUp() throws Exception {
+ IpcDataCache.disableForTestMode();
mTestLooper = new TestLooper();
mContext = InstrumentationRegistry.getTargetContext();
mAudioSystem = new NoOpAudioSystemAdapter();
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 6b41c434b80f..0bbae247d8bb 100644
--- a/services/tests/servicestests/src/com/android/server/audio/VolumeHelperTest.java
+++ b/services/tests/servicestests/src/com/android/server/audio/VolumeHelperTest.java
@@ -80,6 +80,7 @@ import android.media.AudioSystem;
import android.media.IDeviceVolumeBehaviorDispatcher;
import android.media.VolumeInfo;
import android.media.audiopolicy.AudioVolumeGroup;
+import android.os.IpcDataCache;
import android.os.Looper;
import android.os.PermissionEnforcer;
import android.os.test.TestLooper;
@@ -210,6 +211,8 @@ public class VolumeHelperTest {
@Before
public void setUp() throws Exception {
+ IpcDataCache.disableForTestMode();
+
mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
mTestLooper = new TestLooper();
diff --git a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java
index 3ced56a04138..a58a9cd2a28f 100644
--- a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java
@@ -34,7 +34,6 @@ import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.Display.INVALID_DISPLAY;
import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.Truth.assertWithMessage;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -53,15 +52,11 @@ import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
import static org.testng.Assert.assertThrows;
-import android.Manifest;
import android.annotation.SuppressLint;
import android.app.ActivityManagerInternal;
import android.app.ActivityOptions.LaunchCookie;
import android.app.AppOpsManager;
-import android.app.Instrumentation;
import android.app.KeyguardManager;
-import android.app.role.RoleManager;
-import android.companion.AssociationRequest;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
@@ -76,11 +71,9 @@ import android.media.projection.StopReason;
import android.os.Binder;
import android.os.IBinder;
import android.os.Looper;
-import android.os.Process;
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.test.TestLooper;
-import android.platform.test.annotations.EnableFlags;
import android.platform.test.annotations.Presubmit;
import android.platform.test.flag.junit.SetFlagsRule;
import android.provider.Settings;
@@ -99,7 +92,6 @@ import com.android.server.testutils.OffsettableClock;
import com.android.server.wm.WindowManagerInternal;
import org.junit.After;
-import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -110,7 +102,6 @@ import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
-import java.util.Objects;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@@ -292,8 +283,6 @@ public class MediaProjectionManagerServiceTest {
assertThat(stoppedCallback2).isFalse();
}
- @EnableFlags(android.companion.virtualdevice.flags
- .Flags.FLAG_MEDIA_PROJECTION_KEYGUARD_RESTRICTIONS)
@Test
public void testCreateProjection_keyguardLocked() throws Exception {
MediaProjectionManagerService.MediaProjection projection = startProjectionPreconditions();
@@ -308,8 +297,6 @@ public class MediaProjectionManagerServiceTest {
assertThat(mIMediaProjectionCallback.mLatch.await(5, TimeUnit.SECONDS)).isTrue();
}
- @EnableFlags(android.companion.virtualdevice.flags
- .Flags.FLAG_MEDIA_PROJECTION_KEYGUARD_RESTRICTIONS)
@Test
public void testCreateProjection_keyguardLocked_packageAllowlisted()
throws NameNotFoundException {
@@ -325,8 +312,6 @@ public class MediaProjectionManagerServiceTest {
assertThat(mService.getActiveProjectionInfo()).isNotNull();
}
- @EnableFlags(android.companion.virtualdevice.flags
- .Flags.FLAG_MEDIA_PROJECTION_KEYGUARD_RESTRICTIONS)
@Test
public void testCreateProjection_keyguardLocked_AppOpMediaProjection()
throws NameNotFoundException {
@@ -347,50 +332,6 @@ public class MediaProjectionManagerServiceTest {
assertThat(mService.getActiveProjectionInfo()).isNotNull();
}
- @EnableFlags(android.companion.virtualdevice.flags
- .Flags.FLAG_MEDIA_PROJECTION_KEYGUARD_RESTRICTIONS)
- @Test
- public void testCreateProjection_keyguardLocked_RoleHeld() {
- runWithRole(
- AssociationRequest.DEVICE_PROFILE_APP_STREAMING,
- () -> {
- try {
- mAppInfo.privateFlags |= PRIVATE_FLAG_PRIVILEGED;
- doReturn(mAppInfo)
- .when(mPackageManager)
- .getApplicationInfoAsUser(
- anyString(),
- any(ApplicationInfoFlags.class),
- any(UserHandle.class));
- MediaProjectionManagerService.MediaProjection projection =
- mService.createProjectionInternal(
- Process.myUid(),
- mContext.getPackageName(),
- TYPE_MIRRORING,
- /* isPermanentGrant= */ false,
- UserHandle.CURRENT,
- DEFAULT_DISPLAY);
- doReturn(true).when(mKeyguardManager).isKeyguardLocked();
- doReturn(PackageManager.PERMISSION_DENIED)
- .when(mPackageManager)
- .checkPermission(RECORD_SENSITIVE_CONTENT, projection.packageName);
-
- projection.start(mIMediaProjectionCallback);
- projection.notifyVirtualDisplayCreated(10);
-
- // The projection was started because it was allowed to capture the
- // keyguard.
- assertWithMessage("Failed to run projection")
- .that(mService.getActiveProjectionInfo())
- .isNotNull();
- } catch (NameNotFoundException e) {
- throw new RuntimeException(e);
- }
- });
- }
-
- @EnableFlags(android.companion.virtualdevice.flags
- .Flags.FLAG_MEDIA_PROJECTION_KEYGUARD_RESTRICTIONS)
@Test
public void testCreateProjection_keyguardLocked_screenshareProtectionsDisabled()
throws NameNotFoundException {
@@ -416,8 +357,6 @@ public class MediaProjectionManagerServiceTest {
}
}
- @EnableFlags(android.companion.virtualdevice.flags
- .Flags.FLAG_MEDIA_PROJECTION_KEYGUARD_RESTRICTIONS)
@Test
public void testCreateProjection_keyguardLocked_noDisplayCreated()
throws NameNotFoundException {
@@ -509,8 +448,6 @@ public class MediaProjectionManagerServiceTest {
assertThat(secondProjection).isNotEqualTo(projection);
}
- @EnableFlags(android.companion.virtualdevice.flags
- .Flags.FLAG_MEDIA_PROJECTION_KEYGUARD_RESTRICTIONS)
@Test
public void testReuseProjection_keyguardNotLocked_startConsentDialog()
throws NameNotFoundException {
@@ -527,8 +464,6 @@ public class MediaProjectionManagerServiceTest {
verify(mContext).startActivityAsUser(any(), any());
}
- @EnableFlags(android.companion.virtualdevice.flags
- .Flags.FLAG_MEDIA_PROJECTION_KEYGUARD_RESTRICTIONS)
@Test
public void testReuseProjection_keyguardLocked_noConsentDialog() throws NameNotFoundException {
MediaProjectionManagerService.MediaProjection projection = startProjectionPreconditions();
@@ -1302,48 +1237,6 @@ public class MediaProjectionManagerServiceTest {
return mService.getProjectionInternal(UID, PACKAGE_NAME);
}
- /**
- * Run the provided block giving the current context's package the provided role.
- */
- @SuppressWarnings("SameParameterValue")
- private void runWithRole(String role, Runnable block) {
- Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
- String packageName = mContext.getPackageName();
- UserHandle user = instrumentation.getTargetContext().getUser();
- RoleManager roleManager = Objects.requireNonNull(
- mContext.getSystemService(RoleManager.class));
- try {
- CountDownLatch latch = new CountDownLatch(1);
- instrumentation.getUiAutomation().adoptShellPermissionIdentity(
- Manifest.permission.MANAGE_ROLE_HOLDERS,
- Manifest.permission.BYPASS_ROLE_QUALIFICATION);
-
- roleManager.setBypassingRoleQualification(true);
- roleManager.addRoleHolderAsUser(role, packageName,
- /* flags= */ RoleManager.MANAGE_HOLDERS_FLAG_DONT_KILL_APP, user,
- mContext.getMainExecutor(), success -> {
- if (success) {
- latch.countDown();
- } else {
- Assert.fail("Couldn't set role for test (failure) " + role);
- }
- });
- assertWithMessage("Couldn't set role for test (timeout) : " + role)
- .that(latch.await(1, TimeUnit.SECONDS)).isTrue();
- block.run();
-
- } catch (InterruptedException e) {
- throw new RuntimeException(e);
- } finally {
- roleManager.removeRoleHolderAsUser(role, packageName,
- /* flags= */ RoleManager.MANAGE_HOLDERS_FLAG_DONT_KILL_APP, user,
- mContext.getMainExecutor(), (aBool) -> {});
- roleManager.setBypassingRoleQualification(false);
- instrumentation.getUiAutomation()
- .dropShellPermissionIdentity();
- }
- }
-
private static class FakeIMediaProjectionCallback extends IMediaProjectionCallback.Stub {
CountDownLatch mLatch = new CountDownLatch(1);
@Override
diff --git a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionStopControllerTest.java b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionStopControllerTest.java
index affcfc14034e..10ac0495d69a 100644
--- a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionStopControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionStopControllerTest.java
@@ -22,7 +22,6 @@ import static android.provider.Settings.Global.DISABLE_SCREEN_SHARE_PROTECTIONS_
import static android.view.Display.INVALID_DISPLAY;
import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.Truth.assertWithMessage;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -37,13 +36,10 @@ import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-import android.Manifest;
import android.annotation.SuppressLint;
import android.app.ActivityManagerInternal;
import android.app.AppOpsManager;
-import android.app.Instrumentation;
import android.app.KeyguardManager;
-import android.app.role.RoleManager;
import android.companion.AssociationRequest;
import android.content.Context;
import android.content.pm.ApplicationInfo;
@@ -69,7 +65,6 @@ import com.android.server.SystemConfig;
import com.android.server.wm.WindowManagerInternal;
import org.junit.After;
-import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -79,9 +74,7 @@ import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
-import java.util.Objects;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
+import java.util.List;
import java.util.function.Consumer;
/**
@@ -123,6 +116,8 @@ public class MediaProjectionStopControllerTest {
private KeyguardManager mKeyguardManager;
@Mock
private TelecomManager mTelecomManager;
+ @Mock
+ private MediaProjectionStopController.RoleHolderProvider mRoleManager;
private AppOpsManager mAppOpsManager;
@Mock
@@ -145,7 +140,7 @@ public class MediaProjectionStopControllerTest {
mContext.addMockSystemService(TelecomManager.class, mTelecomManager);
mContext.setMockPackageManager(mPackageManager);
- mStopController = new MediaProjectionStopController(mContext, mStopConsumer);
+ mStopController = new MediaProjectionStopController(mContext, mStopConsumer, mRoleManager);
mService = new MediaProjectionManagerService(mContext,
mMediaProjectionMetricsLoggerInjector);
@@ -170,8 +165,6 @@ public class MediaProjectionStopControllerTest {
}
@Test
- @EnableFlags(
- android.companion.virtualdevice.flags.Flags.FLAG_MEDIA_PROJECTION_KEYGUARD_RESTRICTIONS)
public void testMediaProjectionNotRestricted() throws Exception {
when(mKeyguardManager.isKeyguardLocked()).thenReturn(false);
@@ -180,8 +173,6 @@ public class MediaProjectionStopControllerTest {
}
@Test
- @EnableFlags(
- android.companion.virtualdevice.flags.Flags.FLAG_MEDIA_PROJECTION_KEYGUARD_RESTRICTIONS)
public void testMediaProjectionRestricted() throws Exception {
MediaProjectionManagerService.MediaProjection mediaProjection = createMediaProjection();
mediaProjection.notifyVirtualDisplayCreated(1);
@@ -239,21 +230,13 @@ public class MediaProjectionStopControllerTest {
@Test
public void testExemptFromStoppingHasAppStreamingRole() throws Exception {
- runWithRole(
- AssociationRequest.DEVICE_PROFILE_APP_STREAMING,
- () -> {
- try {
- MediaProjectionManagerService.MediaProjection mediaProjection =
- createMediaProjection();
- doReturn(PackageManager.PERMISSION_DENIED).when(
- mPackageManager).checkPermission(
- RECORD_SENSITIVE_CONTENT, mediaProjection.packageName);
- assertThat(mStopController.isExemptFromStopping(mediaProjection,
- MediaProjectionStopController.STOP_REASON_UNKNOWN)).isTrue();
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
- });
+ MediaProjectionManagerService.MediaProjection mediaProjection = createMediaProjection();
+ doReturn(PackageManager.PERMISSION_DENIED).when(mPackageManager).checkPermission(
+ RECORD_SENSITIVE_CONTENT, mediaProjection.packageName);
+ doReturn(List.of(mediaProjection.packageName)).when(mRoleManager).getRoleHoldersAsUser(
+ eq(AssociationRequest.DEVICE_PROFILE_APP_STREAMING), any(UserHandle.class));
+ assertThat(mStopController.isExemptFromStopping(mediaProjection,
+ MediaProjectionStopController.STOP_REASON_UNKNOWN)).isTrue();
}
@Test
@@ -300,8 +283,22 @@ public class MediaProjectionStopControllerTest {
}
@Test
- @EnableFlags(
- android.companion.virtualdevice.flags.Flags.FLAG_MEDIA_PROJECTION_KEYGUARD_RESTRICTIONS)
+ public void isStopReasonCallEnd_stopReasonCallEnd_returnsTrue() {
+ boolean result =
+ mStopController.isStopReasonCallEnd(
+ MediaProjectionStopController.STOP_REASON_CALL_END);
+ assertThat(result).isTrue();
+ }
+
+ @Test
+ public void isStopReasonCallEnd_stopReasonKeyguard_returnsFalse() {
+ boolean result =
+ mStopController.isStopReasonCallEnd(
+ MediaProjectionStopController.STOP_REASON_KEYGUARD);
+ assertThat(result).isFalse();
+ }
+
+ @Test
public void testKeyguardLockedStateChanged_unlocked() {
mStopController.onKeyguardLockedStateChanged(false);
@@ -309,8 +306,6 @@ public class MediaProjectionStopControllerTest {
}
@Test
- @EnableFlags(
- android.companion.virtualdevice.flags.Flags.FLAG_MEDIA_PROJECTION_KEYGUARD_RESTRICTIONS)
public void testKeyguardLockedStateChanged_locked() {
mStopController.onKeyguardLockedStateChanged(true);
@@ -422,47 +417,4 @@ public class MediaProjectionStopControllerTest {
MediaProjectionManager.TYPE_SCREEN_CAPTURE, false, mContext.getUser(),
INVALID_DISPLAY);
}
-
- /**
- * Run the provided block giving the current context's package the provided role.
- */
- @SuppressWarnings("SameParameterValue")
- private void runWithRole(String role, Runnable block) {
- Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
- String packageName = mContext.getPackageName();
- UserHandle user = instrumentation.getTargetContext().getUser();
- RoleManager roleManager = Objects.requireNonNull(
- mContext.getSystemService(RoleManager.class));
- try {
- CountDownLatch latch = new CountDownLatch(1);
- instrumentation.getUiAutomation().adoptShellPermissionIdentity(
- Manifest.permission.MANAGE_ROLE_HOLDERS,
- Manifest.permission.BYPASS_ROLE_QUALIFICATION);
-
- roleManager.setBypassingRoleQualification(true);
- roleManager.addRoleHolderAsUser(role, packageName,
- /* flags= */ RoleManager.MANAGE_HOLDERS_FLAG_DONT_KILL_APP, user,
- mContext.getMainExecutor(), success -> {
- if (success) {
- latch.countDown();
- } else {
- Assert.fail("Couldn't set role for test (failure) " + role);
- }
- });
- assertWithMessage("Couldn't set role for test (timeout) : " + role)
- .that(latch.await(1, TimeUnit.SECONDS)).isTrue();
- block.run();
-
- } catch (InterruptedException e) {
- throw new RuntimeException(e);
- } finally {
- roleManager.removeRoleHolderAsUser(role, packageName,
- /* flags= */ RoleManager.MANAGE_HOLDERS_FLAG_DONT_KILL_APP, user,
- mContext.getMainExecutor(), (aBool) -> {
- });
- roleManager.setBypassingRoleQualification(false);
- instrumentation.getUiAutomation()
- .dropShellPermissionIdentity();
- }
- }
}
diff --git a/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java b/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java
index 2f3bca031f46..fbb673a194b4 100644
--- a/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java
@@ -1762,7 +1762,8 @@ public final class DataManagerTest {
/* rankingAdjustment= */ 0,
/* isBubble= */ false,
/* proposedImportance= */ 0,
- /* sensitiveContent= */ false
+ /* sensitiveContent= */ false,
+ /* summarization = */ null
);
return true;
}).when(mRankingMap).getRanking(eq(key),
@@ -1806,7 +1807,8 @@ public final class DataManagerTest {
/* rankingAdjustment= */ 0,
/* isBubble= */ false,
/* proposedImportance= */ 0,
- /* sensitiveContent= */ false
+ /* sensitiveContent= */ false,
+ /* summarization = */ null
);
return true;
}).when(mRankingMap).getRanking(eq(CUSTOM_KEY),
diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest8.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest8.java
index 5c2132f0e0e9..1c0ee09ccc6f 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest8.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest8.java
@@ -336,7 +336,7 @@ public class ShortcutManagerTest8 extends BaseShortcutManagerTest {
checkRequestPinShortcut(makeResultIntent());
}
- public void testRequestPinShortcut_explicitTargetActivity() {
+ public void disabled_testRequestPinShortcut_explicitTargetActivity() {
setDefaultLauncher(USER_10, LAUNCHER_1);
setDefaultLauncher(USER_11, LAUNCHER_2);
@@ -475,7 +475,7 @@ public class ShortcutManagerTest8 extends BaseShortcutManagerTest {
}
- public void testRequestPinShortcut_dynamicExists() {
+ public void disabled_testRequestPinShortcut_dynamicExists() {
setDefaultLauncher(USER_10, LAUNCHER_1);
final Icon res32x32 = Icon.createWithResource(getTestContext(), R.drawable.black_32x32);
@@ -590,7 +590,7 @@ public class ShortcutManagerTest8 extends BaseShortcutManagerTest {
});
}
- public void testRequestPinShortcut_dynamicExists_alreadyPinned() {
+ public void disabled_testRequestPinShortcut_dynamicExists_alreadyPinned() {
setDefaultLauncher(USER_10, LAUNCHER_1);
final Icon res32x32 = Icon.createWithResource(getTestContext(), R.drawable.black_32x32);
@@ -848,7 +848,7 @@ public class ShortcutManagerTest8 extends BaseShortcutManagerTest {
});
}
- public void testRequestPinShortcut_dynamicExists_alreadyPinnedByAnother() {
+ public void disabled_testRequestPinShortcut_dynamicExists_alreadyPinnedByAnother() {
// Initially all launchers have the shortcut permission, until we call setDefaultLauncher().
runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
@@ -1041,7 +1041,7 @@ public class ShortcutManagerTest8 extends BaseShortcutManagerTest {
/**
* When trying to pin an existing shortcut, the new fields shouldn't override existing fields.
*/
- public void testRequestPinShortcut_dynamicExists_titleWontChange() {
+ public void disabled_testRequestPinShortcut_dynamicExists_titleWontChange() {
setDefaultLauncher(USER_10, LAUNCHER_1);
final Icon res32x32 = Icon.createWithResource(getTestContext(), R.drawable.black_32x32);
@@ -1173,7 +1173,7 @@ public class ShortcutManagerTest8 extends BaseShortcutManagerTest {
* The dynamic shortcut existed, but before accepting(), it's removed. Because the request
* has a partial shortcut, accept() should fail.
*/
- public void testRequestPinShortcut_dynamicExists_thenRemoved_error() {
+ public void disabled_testRequestPinShortcut_dynamicExists_thenRemoved_error() {
setDefaultLauncher(USER_10, LAUNCHER_1);
runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
@@ -1231,7 +1231,7 @@ public class ShortcutManagerTest8 extends BaseShortcutManagerTest {
* The dynamic shortcut existed, but before accepting(), it's removed. Because the request
* has all the mandatory fields, we can go ahead and still publish it.
*/
- public void testRequestPinShortcut_dynamicExists_thenRemoved_okay() {
+ public void disabled_testRequestPinShortcut_dynamicExists_thenRemoved_okay() {
setDefaultLauncher(USER_10, LAUNCHER_1);
runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
@@ -1404,7 +1404,7 @@ public class ShortcutManagerTest8 extends BaseShortcutManagerTest {
* The dynamic shortcut existed, but before accepting(), it's removed. Because the request
* has a partial shortcut, accept() should fail.
*/
- public void testRequestPinShortcut_dynamicExists_thenDisabled_error() {
+ public void disabled_testRequestPinShortcut_dynamicExists_thenDisabled_error() {
setDefaultLauncher(USER_10, LAUNCHER_1);
runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
diff --git a/services/tests/servicestests/src/com/android/server/supervision/SupervisionServiceTest.kt b/services/tests/servicestests/src/com/android/server/supervision/SupervisionServiceTest.kt
index 5862ac65eba9..af50effb7c8e 100644
--- a/services/tests/servicestests/src/com/android/server/supervision/SupervisionServiceTest.kt
+++ b/services/tests/servicestests/src/com/android/server/supervision/SupervisionServiceTest.kt
@@ -92,6 +92,21 @@ class SupervisionServiceTest {
simulateUserStarting(USER_ID)
assertThat(service.isSupervisionEnabledForUser(USER_ID)).isTrue()
+ assertThat(service.getActiveSupervisionAppPackage(USER_ID))
+ .isEqualTo(systemSupervisionPackage)
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_ENABLE_SYNC_WITH_DPM)
+ fun onUserStarting_legacyProfileOwnerComponent_enablesSupervision() {
+ whenever(mockDpmInternal.getProfileOwnerAsUser(USER_ID))
+ .thenReturn(supervisionProfileOwnerComponent)
+
+ simulateUserStarting(USER_ID)
+
+ assertThat(service.isSupervisionEnabledForUser(USER_ID)).isTrue()
+ assertThat(service.getActiveSupervisionAppPackage(USER_ID))
+ .isEqualTo(supervisionProfileOwnerComponent.packageName)
}
@Test
@@ -103,6 +118,7 @@ class SupervisionServiceTest {
simulateUserStarting(USER_ID, preCreated = true)
assertThat(service.isSupervisionEnabledForUser(USER_ID)).isFalse()
+ assertThat(service.getActiveSupervisionAppPackage(USER_ID)).isNull()
}
@Test
@@ -114,6 +130,7 @@ class SupervisionServiceTest {
simulateUserStarting(USER_ID)
assertThat(service.isSupervisionEnabledForUser(USER_ID)).isFalse()
+ assertThat(service.getActiveSupervisionAppPackage(USER_ID)).isNull()
}
@Test
@@ -125,6 +142,21 @@ class SupervisionServiceTest {
broadcastProfileOwnerChanged(USER_ID)
assertThat(service.isSupervisionEnabledForUser(USER_ID)).isTrue()
+ assertThat(service.getActiveSupervisionAppPackage(USER_ID))
+ .isEqualTo(systemSupervisionPackage)
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_ENABLE_SYNC_WITH_DPM)
+ fun profileOwnerChanged_legacyProfileOwnerComponent_enablesSupervision() {
+ whenever(mockDpmInternal.getProfileOwnerAsUser(USER_ID))
+ .thenReturn(supervisionProfileOwnerComponent)
+
+ broadcastProfileOwnerChanged(USER_ID)
+
+ assertThat(service.isSupervisionEnabledForUser(USER_ID)).isTrue()
+ assertThat(service.getActiveSupervisionAppPackage(USER_ID))
+ .isEqualTo(supervisionProfileOwnerComponent.packageName)
}
@Test
@@ -136,13 +168,14 @@ class SupervisionServiceTest {
broadcastProfileOwnerChanged(USER_ID)
assertThat(service.isSupervisionEnabledForUser(USER_ID)).isFalse()
+ assertThat(service.getActiveSupervisionAppPackage(USER_ID)).isNull()
}
@Test
fun isActiveSupervisionApp_supervisionUid_supervisionEnabled_returnsTrue() {
whenever(mockPackageManager.getPackagesForUid(APP_UID))
.thenReturn(arrayOf(systemSupervisionPackage))
- service.setSupervisionEnabledForUser(USER_ID, true)
+ service.mInternal.setSupervisionEnabledForUser(USER_ID, true)
assertThat(service.mInternal.isActiveSupervisionApp(APP_UID)).isTrue()
}
@@ -151,7 +184,7 @@ class SupervisionServiceTest {
fun isActiveSupervisionApp_supervisionUid_supervisionNotEnabled_returnsFalse() {
whenever(mockPackageManager.getPackagesForUid(APP_UID))
.thenReturn(arrayOf(systemSupervisionPackage))
- service.setSupervisionEnabledForUser(USER_ID, false)
+ service.mInternal.setSupervisionEnabledForUser(USER_ID, false)
assertThat(service.mInternal.isActiveSupervisionApp(APP_UID)).isFalse()
}
@@ -167,15 +200,15 @@ class SupervisionServiceTest {
fun setSupervisionEnabledForUser() {
assertThat(service.isSupervisionEnabledForUser(USER_ID)).isFalse()
- service.setSupervisionEnabledForUser(USER_ID, true)
+ service.mInternal.setSupervisionEnabledForUser(USER_ID, true)
assertThat(service.isSupervisionEnabledForUser(USER_ID)).isTrue()
- service.setSupervisionEnabledForUser(USER_ID, false)
+ service.mInternal.setSupervisionEnabledForUser(USER_ID, false)
assertThat(service.isSupervisionEnabledForUser(USER_ID)).isFalse()
}
@Test
- fun supervisionEnabledForUser_internal() {
+ fun setSupervisionEnabledForUser_internal() {
assertThat(service.isSupervisionEnabledForUser(USER_ID)).isFalse()
service.mInternal.setSupervisionEnabledForUser(USER_ID, true)
@@ -205,6 +238,13 @@ class SupervisionServiceTest {
private val systemSupervisionPackage: String
get() = context.getResources().getString(R.string.config_systemSupervision)
+ private val supervisionProfileOwnerComponent: ComponentName
+ get() =
+ context
+ .getResources()
+ .getString(R.string.config_defaultSupervisionProfileOwnerComponent)
+ .let(ComponentName::unflattenFromString)!!
+
private fun simulateUserStarting(userId: Int, preCreated: Boolean = false) {
val userInfo = UserInfo(userId, /* name= */ "tempUser", /* flags= */ 0)
userInfo.preCreated = preCreated
diff --git a/services/tests/uiservicestests/Android.bp b/services/tests/uiservicestests/Android.bp
index a63a38da3740..0eb20eb22380 100644
--- a/services/tests/uiservicestests/Android.bp
+++ b/services/tests/uiservicestests/Android.bp
@@ -20,6 +20,7 @@ android_test {
],
static_libs: [
+ "compatibility-device-util-axt-minus-dexmaker",
"frameworks-base-testutils",
"services.accessibility",
"services.core",
diff --git a/services/tests/uiservicestests/AndroidManifest.xml b/services/tests/uiservicestests/AndroidManifest.xml
index 4315254f68a9..69f17757b367 100644
--- a/services/tests/uiservicestests/AndroidManifest.xml
+++ b/services/tests/uiservicestests/AndroidManifest.xml
@@ -45,6 +45,8 @@
<provider android:name=".DummyProvider"
android:authorities="com.android.services.uitests" />
+ <activity android:name="android.app.ExampleActivity" />
+
</application>
<instrumentation
diff --git a/services/tests/uiservicestests/src/android/app/ExampleActivity.java b/services/tests/uiservicestests/src/android/app/ExampleActivity.java
new file mode 100644
index 000000000000..58395e4d75e1
--- /dev/null
+++ b/services/tests/uiservicestests/src/android/app/ExampleActivity.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app;
+
+public class ExampleActivity extends Activity {
+}
diff --git a/services/tests/uiservicestests/src/android/app/NotificationManagerZenTest.java b/services/tests/uiservicestests/src/android/app/NotificationManagerZenTest.java
new file mode 100644
index 000000000000..779fa1aa2f72
--- /dev/null
+++ b/services/tests/uiservicestests/src/android/app/NotificationManagerZenTest.java
@@ -0,0 +1,284 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app;
+
+import static android.app.NotificationManager.INTERRUPTION_FILTER_ALL;
+import static android.app.NotificationSystemUtil.runAsSystemUi;
+import static android.app.NotificationSystemUtil.toggleNotificationPolicyAccess;
+import static android.service.notification.Condition.STATE_FALSE;
+import static android.service.notification.Condition.STATE_TRUE;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.net.Uri;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
+import android.service.notification.Condition;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Map;
+
+@RunWith(AndroidJUnit4.class)
+public class NotificationManagerZenTest {
+
+ private Context mContext;
+ private NotificationManager mNotificationManager;
+
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
+ @Before
+ public void setUp() throws Exception {
+ mContext = ApplicationProvider.getApplicationContext();
+ mNotificationManager = mContext.getSystemService(NotificationManager.class);
+
+ toggleNotificationPolicyAccess(mContext, mContext.getPackageName(), true);
+ runAsSystemUi(() -> mNotificationManager.setInterruptionFilter(INTERRUPTION_FILTER_ALL));
+ removeAutomaticZenRules();
+ }
+
+ @After
+ public void tearDown() {
+ runAsSystemUi(() -> mNotificationManager.setInterruptionFilter(INTERRUPTION_FILTER_ALL));
+ removeAutomaticZenRules();
+ }
+
+ private void removeAutomaticZenRules() {
+ // Delete AZRs created by this test (query "as app", then delete "as system" so they are
+ // not preserved to be restored later).
+ Map<String, AutomaticZenRule> rules = mNotificationManager.getAutomaticZenRules();
+ runAsSystemUi(() -> {
+ for (String ruleId : rules.keySet()) {
+ mNotificationManager.removeAutomaticZenRule(ruleId);
+ }
+ });
+ }
+
+ @Test
+ @RequiresFlagsEnabled({Flags.FLAG_MODES_API, Flags.FLAG_MODES_UI})
+ public void setAutomaticZenRuleState_manualActivation() {
+ AutomaticZenRule ruleToCreate = createZenRule("rule");
+ String ruleId = mNotificationManager.addAutomaticZenRule(ruleToCreate);
+ Condition manualActivate = new Condition(ruleToCreate.getConditionId(), "manual-on",
+ STATE_TRUE, Condition.SOURCE_USER_ACTION);
+ Condition manualDeactivate = new Condition(ruleToCreate.getConditionId(), "manual-off",
+ STATE_FALSE, Condition.SOURCE_USER_ACTION);
+ Condition autoActivate = new Condition(ruleToCreate.getConditionId(), "auto-on",
+ STATE_TRUE);
+ Condition autoDeactivate = new Condition(ruleToCreate.getConditionId(), "auto-off",
+ STATE_FALSE);
+
+ // User manually activates -> it's active.
+ runAsSystemUi(
+ () -> mNotificationManager.setAutomaticZenRuleState(ruleId, manualActivate));
+ assertThat(mNotificationManager.getAutomaticZenRuleState(ruleId)).isEqualTo(STATE_TRUE);
+
+ // User manually deactivates -> it's inactive.
+ runAsSystemUi(
+ () -> mNotificationManager.setAutomaticZenRuleState(ruleId, manualDeactivate));
+ assertThat(mNotificationManager.getAutomaticZenRuleState(ruleId)).isEqualTo(STATE_FALSE);
+
+ // And app can activate and deactivate.
+ mNotificationManager.setAutomaticZenRuleState(ruleId, autoActivate);
+ assertThat(mNotificationManager.getAutomaticZenRuleState(ruleId)).isEqualTo(STATE_TRUE);
+ mNotificationManager.setAutomaticZenRuleState(ruleId, autoDeactivate);
+ assertThat(mNotificationManager.getAutomaticZenRuleState(ruleId)).isEqualTo(STATE_FALSE);
+ }
+
+ @Test
+ @RequiresFlagsEnabled({Flags.FLAG_MODES_API, Flags.FLAG_MODES_UI})
+ public void setAutomaticZenRuleState_manualDeactivation() {
+ AutomaticZenRule ruleToCreate = createZenRule("rule");
+ String ruleId = mNotificationManager.addAutomaticZenRule(ruleToCreate);
+ Condition manualActivate = new Condition(ruleToCreate.getConditionId(), "manual-on",
+ STATE_TRUE, Condition.SOURCE_USER_ACTION);
+ Condition manualDeactivate = new Condition(ruleToCreate.getConditionId(), "manual-off",
+ STATE_FALSE, Condition.SOURCE_USER_ACTION);
+ Condition autoActivate = new Condition(ruleToCreate.getConditionId(), "auto-on",
+ STATE_TRUE);
+ Condition autoDeactivate = new Condition(ruleToCreate.getConditionId(), "auto-off",
+ STATE_FALSE);
+
+ // App activates rule.
+ mNotificationManager.setAutomaticZenRuleState(ruleId, autoActivate);
+ assertThat(mNotificationManager.getAutomaticZenRuleState(ruleId)).isEqualTo(STATE_TRUE);
+
+ // User manually deactivates -> it's inactive.
+ runAsSystemUi(
+ () -> mNotificationManager.setAutomaticZenRuleState(ruleId, manualDeactivate));
+ assertThat(mNotificationManager.getAutomaticZenRuleState(ruleId)).isEqualTo(STATE_FALSE);
+
+ // User manually reactivates -> it's active.
+ runAsSystemUi(
+ () -> mNotificationManager.setAutomaticZenRuleState(ruleId, manualActivate));
+ assertThat(mNotificationManager.getAutomaticZenRuleState(ruleId)).isEqualTo(STATE_TRUE);
+
+ // That manual activation removed the override-deactivate, but didn't put an
+ // override-activate, so app can deactivate when its natural schedule ends.
+ mNotificationManager.setAutomaticZenRuleState(ruleId, autoDeactivate);
+ assertThat(mNotificationManager.getAutomaticZenRuleState(ruleId)).isEqualTo(STATE_FALSE);
+ }
+
+ @Test
+ @RequiresFlagsEnabled({Flags.FLAG_MODES_API, Flags.FLAG_MODES_UI})
+ public void setAutomaticZenRuleState_respectsManuallyActivated() {
+ AutomaticZenRule ruleToCreate = createZenRule("rule");
+ String ruleId = mNotificationManager.addAutomaticZenRule(ruleToCreate);
+ Condition manualActivate = new Condition(ruleToCreate.getConditionId(), "manual-on",
+ STATE_TRUE, Condition.SOURCE_USER_ACTION);
+ Condition autoActivate = new Condition(ruleToCreate.getConditionId(), "auto-on",
+ STATE_TRUE);
+ Condition autoDeactivate = new Condition(ruleToCreate.getConditionId(), "auto-off",
+ STATE_FALSE);
+
+ // App thinks rule should be inactive.
+ mNotificationManager.setAutomaticZenRuleState(ruleId, autoDeactivate);
+ assertThat(mNotificationManager.getAutomaticZenRuleState(ruleId)).isEqualTo(STATE_FALSE);
+
+ // Manually activate -> it's active.
+ runAsSystemUi(() -> mNotificationManager.setAutomaticZenRuleState(ruleId, manualActivate));
+ assertThat(mNotificationManager.getAutomaticZenRuleState(ruleId)).isEqualTo(STATE_TRUE);
+
+ // App says it should be inactive, but it's ignored.
+ mNotificationManager.setAutomaticZenRuleState(ruleId, autoDeactivate);
+ assertThat(mNotificationManager.getAutomaticZenRuleState(ruleId)).isEqualTo(STATE_TRUE);
+
+ // App says it should be active. No change now...
+ mNotificationManager.setAutomaticZenRuleState(ruleId, autoActivate);
+ assertThat(mNotificationManager.getAutomaticZenRuleState(ruleId)).isEqualTo(STATE_TRUE);
+
+ // ... but when the app wants to deactivate next time, it works.
+ mNotificationManager.setAutomaticZenRuleState(ruleId, autoDeactivate);
+ assertThat(mNotificationManager.getAutomaticZenRuleState(ruleId)).isEqualTo(STATE_FALSE);
+ }
+
+ @Test
+ @RequiresFlagsEnabled({Flags.FLAG_MODES_API, Flags.FLAG_MODES_UI})
+ public void setAutomaticZenRuleState_respectsManuallyDeactivated() {
+ AutomaticZenRule ruleToCreate = createZenRule("rule");
+ String ruleId = mNotificationManager.addAutomaticZenRule(ruleToCreate);
+ Condition manualDeactivate = new Condition(ruleToCreate.getConditionId(), "manual-off",
+ STATE_FALSE, Condition.SOURCE_USER_ACTION);
+ Condition autoActivate = new Condition(ruleToCreate.getConditionId(), "auto-on",
+ STATE_TRUE);
+ Condition autoDeactivate = new Condition(ruleToCreate.getConditionId(), "auto-off",
+ STATE_FALSE);
+
+ // App activates rule.
+ mNotificationManager.setAutomaticZenRuleState(ruleId, autoActivate);
+ assertThat(mNotificationManager.getAutomaticZenRuleState(ruleId)).isEqualTo(STATE_TRUE);
+
+ // User manually deactivates -> it's inactive.
+ runAsSystemUi(
+ () -> mNotificationManager.setAutomaticZenRuleState(ruleId, manualDeactivate));
+ assertThat(mNotificationManager.getAutomaticZenRuleState(ruleId)).isEqualTo(STATE_FALSE);
+
+ // App says it should be active, but it's ignored.
+ mNotificationManager.setAutomaticZenRuleState(ruleId, autoActivate);
+ assertThat(mNotificationManager.getAutomaticZenRuleState(ruleId)).isEqualTo(STATE_FALSE);
+
+ // App says it should be inactive. No change now...
+ mNotificationManager.setAutomaticZenRuleState(ruleId, autoDeactivate);
+ assertThat(mNotificationManager.getAutomaticZenRuleState(ruleId)).isEqualTo(STATE_FALSE);
+
+ // ... but when the app wants to activate next time, it works.
+ mNotificationManager.setAutomaticZenRuleState(ruleId, autoActivate);
+ assertThat(mNotificationManager.getAutomaticZenRuleState(ruleId)).isEqualTo(STATE_TRUE);
+ }
+
+ @Test
+ @RequiresFlagsEnabled({Flags.FLAG_MODES_API, Flags.FLAG_MODES_UI})
+ public void setAutomaticZenRuleState_manualActivationFromApp() {
+ AutomaticZenRule ruleToCreate = createZenRule("rule");
+ String ruleId = mNotificationManager.addAutomaticZenRule(ruleToCreate);
+ Condition manualActivate = new Condition(ruleToCreate.getConditionId(), "manual-off",
+ STATE_TRUE, Condition.SOURCE_USER_ACTION);
+ Condition manualDeactivate = new Condition(ruleToCreate.getConditionId(), "manual-off",
+ STATE_FALSE, Condition.SOURCE_USER_ACTION);
+ Condition autoActivate = new Condition(ruleToCreate.getConditionId(), "auto-on",
+ STATE_TRUE);
+ Condition autoDeactivate = new Condition(ruleToCreate.getConditionId(), "auto-off",
+ STATE_FALSE);
+
+ // App activates rule.
+ mNotificationManager.setAutomaticZenRuleState(ruleId, autoActivate);
+ assertThat(mNotificationManager.getAutomaticZenRuleState(ruleId)).isEqualTo(STATE_TRUE);
+
+ // User manually deactivates from SysUI -> it's inactive.
+ runAsSystemUi(
+ () -> mNotificationManager.setAutomaticZenRuleState(ruleId, manualDeactivate));
+ assertThat(mNotificationManager.getAutomaticZenRuleState(ruleId)).isEqualTo(STATE_FALSE);
+
+ // User manually activates from App -> it's active.
+ mNotificationManager.setAutomaticZenRuleState(ruleId, manualActivate);
+ assertThat(mNotificationManager.getAutomaticZenRuleState(ruleId)).isEqualTo(STATE_TRUE);
+
+ // And app can automatically deactivate it later.
+ mNotificationManager.setAutomaticZenRuleState(ruleId, autoDeactivate);
+ assertThat(mNotificationManager.getAutomaticZenRuleState(ruleId)).isEqualTo(STATE_FALSE);
+ }
+
+ @Test
+ @RequiresFlagsEnabled({Flags.FLAG_MODES_API, Flags.FLAG_MODES_UI})
+ public void setAutomaticZenRuleState_manualDeactivationFromApp() {
+ AutomaticZenRule ruleToCreate = createZenRule("rule");
+ String ruleId = mNotificationManager.addAutomaticZenRule(ruleToCreate);
+ Condition manualActivate = new Condition(ruleToCreate.getConditionId(), "manual-off",
+ STATE_TRUE, Condition.SOURCE_USER_ACTION);
+ Condition manualDeactivate = new Condition(ruleToCreate.getConditionId(), "manual-off",
+ STATE_FALSE, Condition.SOURCE_USER_ACTION);
+ Condition autoActivate = new Condition(ruleToCreate.getConditionId(), "auto-on",
+ STATE_TRUE);
+
+ // User manually activates from SysUI -> it's active.
+ runAsSystemUi(
+ () -> mNotificationManager.setAutomaticZenRuleState(ruleId, manualActivate));
+ assertThat(mNotificationManager.getAutomaticZenRuleState(ruleId)).isEqualTo(STATE_TRUE);
+
+ // User manually deactivates from App -> it's inactive.
+ mNotificationManager.setAutomaticZenRuleState(ruleId, manualDeactivate);
+ assertThat(mNotificationManager.getAutomaticZenRuleState(ruleId)).isEqualTo(STATE_FALSE);
+
+ // And app can automatically activate it later.
+ mNotificationManager.setAutomaticZenRuleState(ruleId, autoActivate);
+ assertThat(mNotificationManager.getAutomaticZenRuleState(ruleId)).isEqualTo(STATE_TRUE);
+ }
+
+ private AutomaticZenRule createZenRule(String name) {
+ return createZenRule(name, NotificationManager.INTERRUPTION_FILTER_PRIORITY);
+ }
+
+ private AutomaticZenRule createZenRule(String name, int filter) {
+ return new AutomaticZenRule(name, null,
+ new ComponentName(mContext, ExampleActivity.class),
+ new Uri.Builder().scheme("scheme")
+ .appendPath("path")
+ .appendQueryParameter("fake_rule", "fake_value")
+ .build(), null, filter, true);
+ }
+}
diff --git a/services/tests/uiservicestests/src/android/app/NotificationSystemUtil.java b/services/tests/uiservicestests/src/android/app/NotificationSystemUtil.java
new file mode 100644
index 000000000000..cf6e39b962ab
--- /dev/null
+++ b/services/tests/uiservicestests/src/android/app/NotificationSystemUtil.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app;
+
+import static org.junit.Assert.assertEquals;
+
+import android.Manifest;
+import android.content.Context;
+import android.os.ParcelFileDescriptor;
+
+import androidx.annotation.NonNull;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.compatibility.common.util.AmUtils;
+import com.android.compatibility.common.util.FileUtils;
+import com.android.compatibility.common.util.SystemUtil;
+import com.android.compatibility.common.util.ThrowingRunnable;
+
+import java.io.FileInputStream;
+import java.io.IOException;
+
+public class NotificationSystemUtil {
+
+ /**
+ * Runs a {@link ThrowingRunnable} as the Shell, while adopting SystemUI's permission (as
+ * checked by {@code NotificationManagerService#isCallerSystemOrSystemUi}).
+ */
+ protected static void runAsSystemUi(@NonNull ThrowingRunnable runnable) {
+ SystemUtil.runWithShellPermissionIdentity(
+ InstrumentationRegistry.getInstrumentation().getUiAutomation(),
+ runnable, Manifest.permission.STATUS_BAR_SERVICE);
+ }
+
+ static void toggleNotificationPolicyAccess(Context context, String packageName,
+ boolean on) throws IOException {
+
+ String command = " cmd notification " + (on ? "allow_dnd " : "disallow_dnd ") + packageName
+ + " " + context.getUserId();
+
+ runCommand(command, InstrumentationRegistry.getInstrumentation());
+ AmUtils.waitForBroadcastBarrier();
+
+ NotificationManager nm = context.getSystemService(NotificationManager.class);
+ assertEquals("Notification Policy Access Grant is "
+ + nm.isNotificationPolicyAccessGranted() + " not " + on + " for "
+ + packageName, on, nm.isNotificationPolicyAccessGranted());
+ }
+
+ private static void runCommand(String command, Instrumentation instrumentation)
+ throws IOException {
+ UiAutomation uiAutomation = instrumentation.getUiAutomation();
+ try (FileInputStream fis = new ParcelFileDescriptor.AutoCloseInputStream(
+ uiAutomation.executeShellCommand(command))) {
+ FileUtils.readInputStreamFully(fis);
+ }
+ }
+}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java
index 8fad01a7541d..2616ccbe474a 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java
@@ -247,7 +247,8 @@ public class NotificationListenerServiceTest extends UiServiceTestCase {
getRankingAdjustment(i),
isBubble(i),
getProposedImportance(i),
- hasSensitiveContent(i)
+ hasSensitiveContent(i),
+ getSummarization(i)
);
rankings[i] = ranking;
}
@@ -383,6 +384,13 @@ public class NotificationListenerServiceTest extends UiServiceTestCase {
return index % 3 == 0;
}
+ public static String getSummarization(int index) {
+ if ((android.app.Flags.nmSummarizationUi() || android.app.Flags.nmSummarization())) {
+ return "summary " + index;
+ }
+ return null;
+ }
+
private boolean isBubble(int index) {
return index % 4 == 0;
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index edfdb4ffec3a..a8fcadd9dd74 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -51,6 +51,7 @@ import static android.app.NotificationChannel.RECS_ID;
import static android.app.NotificationChannel.SOCIAL_MEDIA_ID;
import static android.app.NotificationChannel.USER_LOCKED_ALLOW_BUBBLE;
import static android.app.NotificationManager.ACTION_AUTOMATIC_ZEN_RULE_STATUS_CHANGED;
+import static android.app.NotificationManager.ACTION_EFFECTS_SUPPRESSOR_CHANGED;
import static android.app.NotificationManager.ACTION_INTERRUPTION_FILTER_CHANGED;
import static android.app.NotificationManager.AUTOMATIC_RULE_STATUS_ACTIVATED;
import static android.app.NotificationManager.BUBBLE_PREFERENCE_ALL;
@@ -123,7 +124,9 @@ import static android.service.notification.Flags.FLAG_REDACT_SENSITIVE_NOTIFICAT
import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_ALERTING;
import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_CONVERSATIONS;
import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_ONGOING;
+import static android.service.notification.NotificationListenerService.HINT_HOST_DISABLE_CALL_EFFECTS;
import static android.service.notification.NotificationListenerService.HINT_HOST_DISABLE_EFFECTS;
+import static android.service.notification.NotificationListenerService.HINT_HOST_DISABLE_NOTIFICATION_EFFECTS;
import static android.service.notification.NotificationListenerService.REASON_APP_CANCEL;
import static android.service.notification.NotificationListenerService.REASON_CANCEL;
import static android.service.notification.NotificationListenerService.REASON_LOCKDOWN;
@@ -163,6 +166,7 @@ import static junit.framework.Assert.fail;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertThrows;
+import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Matchers.anyBoolean;
import static org.mockito.Matchers.anyLong;
@@ -360,7 +364,6 @@ import org.junit.rules.TestRule;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.ArgumentMatcher;
-import org.mockito.ArgumentMatchers;
import org.mockito.InOrder;
import org.mockito.Mock;
import org.mockito.Mockito;
@@ -368,6 +371,9 @@ import org.mockito.MockitoAnnotations;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4;
+import platform.test.runner.parameterized.Parameters;
+
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
@@ -383,9 +389,6 @@ import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.function.Consumer;
-import platform.test.runner.parameterized.ParameterizedAndroidJunit4;
-import platform.test.runner.parameterized.Parameters;
-
@SmallTest
@RunWith(ParameterizedAndroidJunit4.class)
@RunWithLooper
@@ -600,7 +603,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
@Parameters(name = "{0}")
public static List<FlagsParameterization> getParams() {
- return FlagsParameterization.allCombinationsOf();
+ return FlagsParameterization.allCombinationsOf(
+ FLAG_NOTIFICATION_CLASSIFICATION);
}
public NotificationManagerServiceTest(FlagsParameterization flags) {
@@ -10889,6 +10893,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
Bundle signals = new Bundle();
signals.putInt(KEY_IMPORTANCE, IMPORTANCE_LOW);
signals.putInt(KEY_USER_SENTIMENT, USER_SENTIMENT_NEGATIVE);
+ signals.putInt(KEY_TYPE, TYPE_PROMOTION);
Adjustment adjustment = new Adjustment(r.getSbn().getPackageName(), r.getKey(), signals,
"", r.getUser().getIdentifier());
@@ -11413,8 +11418,14 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
verify(mContext).sendBroadcastAsUser(eqIntent(expected), eq(UserHandle.of(mUserId)));
}
+ private static Intent isIntentWithAction(String wantedAction) {
+ return argThat(
+ intent -> intent != null && wantedAction.equals(intent.getAction())
+ );
+ }
+
private static Intent eqIntent(Intent wanted) {
- return ArgumentMatchers.argThat(
+ return argThat(
new ArgumentMatcher<Intent>() {
@Override
public boolean matches(Intent argument) {
@@ -17491,6 +17502,66 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
}
@Test
+ @EnableFlags({Flags.FLAG_NM_BINDER_PERF_THROTTLE_EFFECTS_SUPPRESSOR_BROADCAST,
+ Flags.FLAG_NM_BINDER_PERF_REDUCE_ZEN_BROADCASTS})
+ public void requestHintsFromListener_changingEffectsButNotSuppressor_noBroadcast()
+ throws Exception {
+ // Note that NM_BINDER_PERF_REDUCE_ZEN_BROADCASTS is not strictly necessary; however each
+ // path will do slightly different calls so we force one of them to simplify the test.
+ when(mUmInternal.getProfileIds(anyInt(), anyBoolean())).thenReturn(new int[]{mUserId});
+ when(mListeners.checkServiceTokenLocked(any())).thenReturn(mListener);
+ INotificationListener token = mock(INotificationListener.class);
+ mService.isSystemUid = true;
+
+ mBinderService.requestHintsFromListener(token, HINT_HOST_DISABLE_CALL_EFFECTS);
+ mTestableLooper.moveTimeForward(500); // more than ZEN_BROADCAST_DELAY
+ waitForIdle();
+
+ verify(mContext, times(1)).sendBroadcastMultiplePermissions(
+ isIntentWithAction(ACTION_EFFECTS_SUPPRESSOR_CHANGED), any(), any(), any());
+
+ // Same suppressor suppresses something else.
+ mBinderService.requestHintsFromListener(token, HINT_HOST_DISABLE_NOTIFICATION_EFFECTS);
+ mTestableLooper.moveTimeForward(500); // more than ZEN_BROADCAST_DELAY
+ waitForIdle();
+
+ // Still 1 total calls (the previous one).
+ verify(mContext, times(1)).sendBroadcastMultiplePermissions(
+ isIntentWithAction(ACTION_EFFECTS_SUPPRESSOR_CHANGED), any(), any(), any());
+ }
+
+ @Test
+ @EnableFlags({Flags.FLAG_NM_BINDER_PERF_THROTTLE_EFFECTS_SUPPRESSOR_BROADCAST,
+ Flags.FLAG_NM_BINDER_PERF_REDUCE_ZEN_BROADCASTS})
+ public void requestHintsFromListener_changingSuppressor_throttlesBroadcast() throws Exception {
+ // Note that NM_BINDER_PERF_REDUCE_ZEN_BROADCASTS is not strictly necessary; however each
+ // path will do slightly different calls so we force one of them to simplify the test.
+ when(mUmInternal.getProfileIds(anyInt(), anyBoolean())).thenReturn(new int[]{mUserId});
+ when(mListeners.checkServiceTokenLocked(any())).thenReturn(mListener);
+ INotificationListener token = mock(INotificationListener.class);
+ mService.isSystemUid = true;
+
+ // Several updates in quick succession.
+ mBinderService.requestHintsFromListener(token, HINT_HOST_DISABLE_CALL_EFFECTS);
+ mBinderService.clearRequestedListenerHints(token);
+ mBinderService.requestHintsFromListener(token, HINT_HOST_DISABLE_NOTIFICATION_EFFECTS);
+ mBinderService.clearRequestedListenerHints(token);
+ mBinderService.requestHintsFromListener(token, HINT_HOST_DISABLE_CALL_EFFECTS);
+ mBinderService.clearRequestedListenerHints(token);
+ mBinderService.requestHintsFromListener(token, HINT_HOST_DISABLE_NOTIFICATION_EFFECTS);
+
+ // No broadcasts yet!
+ verify(mContext, never()).sendBroadcastMultiplePermissions(any(), any(), any(), any());
+
+ mTestableLooper.moveTimeForward(500); // more than ZEN_BROADCAST_DELAY
+ waitForIdle();
+
+ // Only one broadcast after idle time.
+ verify(mContext, times(1)).sendBroadcastMultiplePermissions(
+ isIntentWithAction(ACTION_EFFECTS_SUPPRESSOR_CHANGED), any(), any(), any());
+ }
+
+ @Test
@EnableFlags(android.service.notification.Flags.FLAG_NOTIFICATION_CLASSIFICATION)
public void testApplyAdjustment_keyType_validType() throws Exception {
final NotificationRecord r = generateNotificationRecord(mTestNotificationChannel);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordExtractorDataTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordExtractorDataTest.java
index 9fe0e49c4ab8..09ebc23a1d7b 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordExtractorDataTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordExtractorDataTest.java
@@ -29,12 +29,19 @@ import android.os.UserHandle;
import android.service.notification.Adjustment;
import android.service.notification.StatusBarNotification;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
+
import com.android.server.UiServiceTestCase;
+import org.junit.Rule;
import org.junit.Test;
public class NotificationRecordExtractorDataTest extends UiServiceTestCase {
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
@Test
public void testHasDiffs_noDiffs() {
NotificationRecord r = generateRecord();
@@ -57,7 +64,8 @@ public class NotificationRecordExtractorDataTest extends UiServiceTestCase {
r.getRankingScore(),
r.isConversation(),
r.getProposedImportance(),
- r.hasSensitiveContent());
+ r.hasSensitiveContent(),
+ r.getSummarization());
assertFalse(extractorData.hasDiffForRankingLocked(r, 1));
assertFalse(extractorData.hasDiffForLoggingLocked(r, 1));
@@ -85,7 +93,8 @@ public class NotificationRecordExtractorDataTest extends UiServiceTestCase {
r.getRankingScore(),
r.isConversation(),
r.getProposedImportance(),
- r.hasSensitiveContent());
+ r.hasSensitiveContent(),
+ r.getSummarization());
Bundle signals = new Bundle();
signals.putInt(Adjustment.KEY_IMPORTANCE_PROPOSAL, IMPORTANCE_HIGH);
@@ -119,7 +128,8 @@ public class NotificationRecordExtractorDataTest extends UiServiceTestCase {
r.getRankingScore(),
r.isConversation(),
r.getProposedImportance(),
- r.hasSensitiveContent());
+ r.hasSensitiveContent(),
+ r.getSummarization());
Bundle signals = new Bundle();
signals.putString(Adjustment.KEY_GROUP_KEY, "ranker_group");
@@ -154,7 +164,8 @@ public class NotificationRecordExtractorDataTest extends UiServiceTestCase {
r.getRankingScore(),
r.isConversation(),
r.getProposedImportance(),
- r.hasSensitiveContent());
+ r.hasSensitiveContent(),
+ r.getSummarization());
Bundle signals = new Bundle();
signals.putBoolean(Adjustment.KEY_SENSITIVE_CONTENT, true);
@@ -166,6 +177,42 @@ public class NotificationRecordExtractorDataTest extends UiServiceTestCase {
assertTrue(extractorData.hasDiffForLoggingLocked(r, 1));
}
+ @Test
+ @EnableFlags(android.app.Flags.FLAG_NM_SUMMARIZATION)
+ public void testHasDiffs_summarization() {
+ NotificationRecord r = generateRecord();
+
+ NotificationRecordExtractorData extractorData = new NotificationRecordExtractorData(
+ 1,
+ r.getPackageVisibilityOverride(),
+ r.canShowBadge(),
+ r.canBubble(),
+ r.getNotification().isBubbleNotification(),
+ r.getChannel(),
+ r.getGroupKey(),
+ r.getPeopleOverride(),
+ r.getSnoozeCriteria(),
+ r.getUserSentiment(),
+ r.getSuppressedVisualEffects(),
+ r.getSystemGeneratedSmartActions(),
+ r.getSmartReplies(),
+ r.getImportance(),
+ r.getRankingScore(),
+ r.isConversation(),
+ r.getProposedImportance(),
+ r.hasSensitiveContent(),
+ r.getSummarization());
+
+ Bundle signals = new Bundle();
+ signals.putString(Adjustment.KEY_SUMMARIZATION, "SUMMARIZED!");
+ Adjustment adjustment = new Adjustment("pkg", r.getKey(), signals, "", 0);
+ r.addAdjustment(adjustment);
+ r.applyAdjustments();
+
+ assertTrue(extractorData.hasDiffForRankingLocked(r, 1));
+ assertTrue(extractorData.hasDiffForLoggingLocked(r, 1));
+ }
+
private NotificationRecord generateRecord() {
NotificationChannel channel = new NotificationChannel("a", "a", IMPORTANCE_LOW);
final Notification.Builder builder = new Notification.Builder(getContext())
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
index 704b580a80b0..832ca51ae580 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
@@ -260,7 +260,7 @@ public class PreferencesHelperTest extends UiServiceTestCase {
return FlagsParameterization.allCombinationsOf(
android.app.Flags.FLAG_API_RICH_ONGOING,
FLAG_NOTIFICATION_CLASSIFICATION, FLAG_NOTIFICATION_CLASSIFICATION_UI,
- FLAG_MODES_UI);
+ FLAG_MODES_UI, android.app.Flags.FLAG_NM_BINDER_PERF_CACHE_CHANNELS);
}
public PreferencesHelperTest(FlagsParameterization flags) {
@@ -3381,13 +3381,12 @@ public class PreferencesHelperTest extends UiServiceTestCase {
// user 0 records remain
for (int i = 0; i < user0Uids.length; i++) {
assertEquals(1,
- mHelper.getNotificationChannels(PKG_N_MR1, user0Uids[i], false, true)
- .getList().size());
+ mHelper.getRemovedPkgNotificationChannels(PKG_N_MR1, user0Uids[i]).size());
}
// user 1 records are gone
for (int i = 0; i < user1Uids.length; i++) {
- assertEquals(0, mHelper.getNotificationChannels(PKG_N_MR1, user1Uids[i], false, true)
- .getList().size());
+ assertEquals(0,
+ mHelper.getRemovedPkgNotificationChannels(PKG_N_MR1, user1Uids[i]).size());
}
}
@@ -3402,8 +3401,7 @@ public class PreferencesHelperTest extends UiServiceTestCase {
assertTrue(mHelper.onPackagesChanged(true, USER_SYSTEM, new String[]{PKG_N_MR1},
new int[]{UID_N_MR1}));
- assertEquals(0, mHelper.getNotificationChannels(
- PKG_N_MR1, UID_N_MR1, true, true).getList().size());
+ assertEquals(0, mHelper.getRemovedPkgNotificationChannels(PKG_N_MR1, UID_N_MR1).size());
// Not deleted
mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel1, true, false,
@@ -3472,7 +3470,7 @@ public class PreferencesHelperTest extends UiServiceTestCase {
assertTrue(mHelper.canShowBadge(PKG_O, UID_O));
assertNull(mHelper.getNotificationDelegate(PKG_O, UID_O));
assertEquals(0, mHelper.getAppLockedFields(PKG_O, UID_O));
- assertEquals(0, mHelper.getNotificationChannels(PKG_O, UID_O, true, true).getList().size());
+ assertEquals(0, mHelper.getRemovedPkgNotificationChannels(PKG_O, UID_O).size());
assertEquals(0, mHelper.getNotificationChannelGroups(PKG_O, UID_O).size());
NotificationChannel channel = getChannel();
@@ -6836,38 +6834,11 @@ public class PreferencesHelperTest extends UiServiceTestCase {
}
@Test
- @EnableFlags(android.app.Flags.FLAG_NM_BINDER_PERF_CACHE_CHANNELS)
- public void testGetNotificationChannels_createIfNeeded() {
- // Test setup hasn't created any channels or read package preferences yet.
- // If we ask for notification channels _without_ creating, we should get no result.
- ParceledListSlice<NotificationChannel> channels = mHelper.getNotificationChannels(PKG_N_MR1,
- UID_N_MR1, false, false, /* createPrefsIfNeeded= */ false);
- assertThat(channels.getList().size()).isEqualTo(0);
-
- // If we ask it to create package preferences, we expect the default channel to be created
- // for N_MR1.
- channels = mHelper.getNotificationChannels(PKG_N_MR1, UID_N_MR1, false,
- false, /* createPrefsIfNeeded= */ true);
- assertThat(channels.getList().size()).isEqualTo(1);
- assertThat(channels.getList().getFirst().getId()).isEqualTo(
- NotificationChannel.DEFAULT_CHANNEL_ID);
- }
-
- @Test
@DisableFlags(android.app.Flags.FLAG_NM_BINDER_PERF_CACHE_CHANNELS)
public void testGetNotificationChannels_neverCreatesWhenFlagOff() {
- ParceledListSlice<NotificationChannel> channels;
- try {
- channels = mHelper.getNotificationChannels(PKG_N_MR1, UID_N_MR1, false,
- false, /* createPrefsIfNeeded= */ true);
- } catch (Exception e) {
- // Slog.wtf kicks in, presumably
- } finally {
- channels = mHelper.getNotificationChannels(PKG_N_MR1, UID_N_MR1, false,
- false, /* createPrefsIfNeeded= */ false);
- assertThat(channels.getList().size()).isEqualTo(0);
- }
-
+ ParceledListSlice<NotificationChannel> channels = mHelper.getNotificationChannels(PKG_N_MR1,
+ UID_N_MR1, false, false);
+ assertThat(channels.getList().size()).isEqualTo(0);
}
// Test version of PreferencesHelper whose only functional difference is that it does not
diff --git a/services/tests/wmtests/src/com/android/server/policy/CombinationKeyTests.java b/services/tests/wmtests/src/com/android/server/policy/CombinationKeyTests.java
index 038e1357159b..036e03c60091 100644
--- a/services/tests/wmtests/src/com/android/server/policy/CombinationKeyTests.java
+++ b/services/tests/wmtests/src/com/android/server/policy/CombinationKeyTests.java
@@ -23,6 +23,7 @@ import static com.android.server.policy.PhoneWindowManager.POWER_VOLUME_UP_BEHAV
import static com.android.server.policy.PhoneWindowManager.POWER_VOLUME_UP_BEHAVIOR_MUTE;
import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.Presubmit;
import android.view.ViewConfiguration;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -38,6 +39,7 @@ import org.junit.runner.RunWith;
* Build/Install/Run:
* atest WmTests:CombinationKeyTests
*/
+@Presubmit
@MediumTest
@RunWith(AndroidJUnit4.class)
@DisableFlags(com.android.hardware.input.Flags.FLAG_USE_KEY_GESTURE_EVENT_HANDLER_MULTI_KEY_GESTURES)
diff --git a/services/tests/wmtests/src/com/android/server/policy/DeferredKeyActionExecutorTests.java b/services/tests/wmtests/src/com/android/server/policy/DeferredKeyActionExecutorTests.java
index ca3787ec4546..bccdd67d33ed 100644
--- a/services/tests/wmtests/src/com/android/server/policy/DeferredKeyActionExecutorTests.java
+++ b/services/tests/wmtests/src/com/android/server/policy/DeferredKeyActionExecutorTests.java
@@ -19,6 +19,7 @@ package com.android.server.policy;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import android.platform.test.annotations.Presubmit;
import android.view.KeyEvent;
import org.junit.Before;
@@ -29,6 +30,7 @@ import org.junit.Test;
*
* <p>Build/Install/Run: atest WmTests:DeferredKeyActionExecutorTests
*/
+@Presubmit
public final class DeferredKeyActionExecutorTests {
private DeferredKeyActionExecutor mKeyActionExecutor;
diff --git a/services/tests/wmtests/src/com/android/server/policy/KeyCombinationManagerTests.java b/services/tests/wmtests/src/com/android/server/policy/KeyCombinationManagerTests.java
index 8b5f68a1e974..a912c177c863 100644
--- a/services/tests/wmtests/src/com/android/server/policy/KeyCombinationManagerTests.java
+++ b/services/tests/wmtests/src/com/android/server/policy/KeyCombinationManagerTests.java
@@ -29,6 +29,7 @@ import static org.testng.Assert.assertTrue;
import android.os.Handler;
import android.os.Looper;
import android.os.SystemClock;
+import android.platform.test.annotations.Presubmit;
import android.view.KeyEvent;
import androidx.test.filters.SmallTest;
@@ -45,7 +46,7 @@ import java.util.concurrent.TimeUnit;
* Build/Install/Run:
* atest KeyCombinationManagerTests
*/
-
+@Presubmit
@SmallTest
public class KeyCombinationManagerTests {
private KeyCombinationManager mKeyCombinationManager;
diff --git a/services/tests/wmtests/src/com/android/server/policy/KeyGestureEventTests.java b/services/tests/wmtests/src/com/android/server/policy/KeyGestureEventTests.java
index 85ef466b2477..16c786b52655 100644
--- a/services/tests/wmtests/src/com/android/server/policy/KeyGestureEventTests.java
+++ b/services/tests/wmtests/src/com/android/server/policy/KeyGestureEventTests.java
@@ -544,17 +544,6 @@ public class KeyGestureEventTests extends ShortcutKeyTestBase {
}
@Test
- public void testKeyGestureSplitscreenFocus() {
- Assert.assertTrue(sendKeyGestureEventComplete(
- KeyGestureEvent.KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_LEFT));
- mPhoneWindowManager.assertSetSplitscreenFocus(true);
-
- Assert.assertTrue(sendKeyGestureEventComplete(
- KeyGestureEvent.KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_RIGHT));
- mPhoneWindowManager.assertSetSplitscreenFocus(false);
- }
-
- @Test
public void testKeyGestureShortcutHelper() {
Assert.assertTrue(sendKeyGestureEventComplete(
KeyGestureEvent.KEY_GESTURE_TYPE_OPEN_SHORTCUT_HELPER));
diff --git a/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutManagerTests.java b/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutManagerTests.java
index 82a5add407f4..d961a6acc9c1 100644
--- a/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutManagerTests.java
+++ b/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutManagerTests.java
@@ -42,6 +42,7 @@ import android.os.Handler;
import android.os.Looper;
import android.os.UserHandle;
import android.platform.test.annotations.EnableFlags;
+import android.platform.test.annotations.Presubmit;
import android.platform.test.flag.junit.SetFlagsRule;
import android.view.KeyEvent;
import android.view.KeyboardShortcutGroup;
@@ -64,7 +65,7 @@ import java.util.Collections;
* Build/Install/Run:
* atest ModifierShortcutManagerTests
*/
-
+@Presubmit
@SmallTest
@EnableFlags(com.android.hardware.input.Flags.FLAG_MODIFIER_SHORTCUT_MANAGER_REFACTOR)
public class ModifierShortcutManagerTests {
@@ -127,7 +128,7 @@ public class ModifierShortcutManagerTests {
// Total valid shortcuts.
KeyboardShortcutGroup group =
mModifierShortcutManager.getApplicationLaunchKeyboardShortcuts(-1);
- assertEquals(13, group.getItems().size());
+ assertEquals(11, group.getItems().size());
// Total valid shift shortcuts.
assertEquals(3, group.getItems().stream()
diff --git a/services/tests/wmtests/src/com/android/server/policy/PhoneWindowManagerTests.java b/services/tests/wmtests/src/com/android/server/policy/PhoneWindowManagerTests.java
index af3dc1da4dcc..c73ce23fe6b5 100644
--- a/services/tests/wmtests/src/com/android/server/policy/PhoneWindowManagerTests.java
+++ b/services/tests/wmtests/src/com/android/server/policy/PhoneWindowManagerTests.java
@@ -48,6 +48,7 @@ import android.app.AppOpsManager;
import android.content.Context;
import android.hardware.input.InputManager;
import android.os.PowerManager;
+import android.platform.test.annotations.Presubmit;
import android.platform.test.flag.junit.SetFlagsRule;
import androidx.test.filters.SmallTest;
@@ -72,6 +73,7 @@ import org.junit.Test;
* Build/Install/Run:
* atest WmTests:PhoneWindowManagerTests
*/
+@Presubmit
@SmallTest
public class PhoneWindowManagerTests {
diff --git a/services/tests/wmtests/src/com/android/server/policy/PowerKeyGestureTests.java b/services/tests/wmtests/src/com/android/server/policy/PowerKeyGestureTests.java
index 33ccec3f785a..53e82bad818d 100644
--- a/services/tests/wmtests/src/com/android/server/policy/PowerKeyGestureTests.java
+++ b/services/tests/wmtests/src/com/android/server/policy/PowerKeyGestureTests.java
@@ -28,6 +28,7 @@ import static com.android.server.policy.PhoneWindowManager.SHORT_PRESS_POWER_GO_
import static org.junit.Assert.assertEquals;
import android.platform.test.annotations.EnableFlags;
+import android.platform.test.annotations.Presubmit;
import android.provider.Settings;
import android.view.Display;
@@ -40,6 +41,7 @@ import org.junit.Test;
* Build/Install/Run:
* atest WmTests:PowerKeyGestureTests
*/
+@Presubmit
public class PowerKeyGestureTests extends ShortcutKeyTestBase {
@Before
public void setUp() {
diff --git a/services/tests/wmtests/src/com/android/server/policy/SingleKeyGestureTests.java b/services/tests/wmtests/src/com/android/server/policy/SingleKeyGestureTests.java
index ff8b6d3c1962..6c6594220bad 100644
--- a/services/tests/wmtests/src/com/android/server/policy/SingleKeyGestureTests.java
+++ b/services/tests/wmtests/src/com/android/server/policy/SingleKeyGestureTests.java
@@ -23,6 +23,8 @@ import static android.view.KeyEvent.KEYCODE_POWER;
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+import static com.android.hardware.input.Flags.FLAG_ABORT_SLOW_MULTI_PRESS;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
@@ -35,9 +37,14 @@ import android.os.HandlerThread;
import android.os.Looper;
import android.os.Process;
import android.os.SystemClock;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.annotations.Presubmit;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.view.KeyEvent;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import java.util.concurrent.BlockingQueue;
@@ -51,7 +58,10 @@ import java.util.concurrent.TimeUnit;
* Build/Install/Run:
* atest WmTests:SingleKeyGestureTests
*/
+@Presubmit
public class SingleKeyGestureTests {
+ @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
private SingleKeyGestureDetector mDetector;
private int mMaxMultiPressCount = 3;
@@ -260,6 +270,44 @@ public class SingleKeyGestureTests {
}
@Test
+ @EnableFlags(FLAG_ABORT_SLOW_MULTI_PRESS)
+ public void testMultipress_noLongPressBehavior_longPressCancelsMultiPress()
+ throws InterruptedException {
+ mLongPressOnPowerBehavior = false;
+
+ pressKey(KEYCODE_POWER, 0 /* pressTime */);
+ pressKey(KEYCODE_POWER, mLongPressTime /* pressTime */);
+
+ assertFalse(mMultiPressed.await(mWaitTimeout, TimeUnit.MILLISECONDS));
+ }
+
+ @Test
+ @EnableFlags(FLAG_ABORT_SLOW_MULTI_PRESS)
+ public void testMultipress_noVeryLongPressBehavior_veryLongPressCancelsMultiPress()
+ throws InterruptedException {
+ mLongPressOnPowerBehavior = false;
+ mVeryLongPressOnPowerBehavior = false;
+
+ pressKey(KEYCODE_POWER, 0 /* pressTime */);
+ pressKey(KEYCODE_POWER, mVeryLongPressTime /* pressTime */);
+
+ assertFalse(mMultiPressed.await(mWaitTimeout, TimeUnit.MILLISECONDS));
+ }
+
+ @Test
+ @DisableFlags(FLAG_ABORT_SLOW_MULTI_PRESS)
+ public void testMultipress_flagDisabled_noLongPressBehavior_longPressDoesNotCancelMultiPress()
+ throws InterruptedException {
+ mLongPressOnPowerBehavior = false;
+ mExpectedMultiPressCount = 2;
+
+ pressKey(KEYCODE_POWER, 0 /* pressTime */);
+ pressKey(KEYCODE_POWER, mLongPressTime /* pressTime */);
+
+ assertTrue(mMultiPressed.await(mWaitTimeout, TimeUnit.MILLISECONDS));
+ }
+
+ @Test
public void testMultiPress() throws InterruptedException {
// Double presses.
mExpectedMultiPressCount = 2;
diff --git a/services/tests/wmtests/src/com/android/server/policy/StemKeyGestureTests.java b/services/tests/wmtests/src/com/android/server/policy/StemKeyGestureTests.java
index 3ea3235df0f4..833dd5d768e4 100644
--- a/services/tests/wmtests/src/com/android/server/policy/StemKeyGestureTests.java
+++ b/services/tests/wmtests/src/com/android/server/policy/StemKeyGestureTests.java
@@ -35,6 +35,7 @@ import android.app.ActivityTaskManager.RootTaskInfo;
import android.content.ComponentName;
import android.hardware.input.KeyGestureEvent;
import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
import android.provider.Settings;
import android.view.Display;
@@ -47,6 +48,7 @@ import org.junit.Test;
* Build/Install/Run:
* atest WmTests:StemKeyGestureTests
*/
+@Presubmit
public class StemKeyGestureTests extends ShortcutKeyTestBase {
private static final String TEST_TARGET_ACTIVITY = "com.android.server.policy/.TestActivity";
diff --git a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
index 4ff3d433632a..f88492477487 100644
--- a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
+++ b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
@@ -925,11 +925,6 @@ class TestPhoneWindowManager {
verify(mStatusBarManagerInternal).moveFocusedTaskToStageSplit(anyInt(), eq(leftOrTop));
}
- void assertSetSplitscreenFocus(boolean leftOrTop) {
- mTestLooper.dispatchAll();
- verify(mStatusBarManagerInternal).setSplitscreenFocus(eq(leftOrTop));
- }
-
void assertStatusBarStartAssist() {
mTestLooper.dispatchAll();
verify(mStatusBarManagerInternal).startAssist(any());
diff --git a/services/tests/wmtests/src/com/android/server/policy/WindowWakeUpPolicyTests.java b/services/tests/wmtests/src/com/android/server/policy/WindowWakeUpPolicyTests.java
index 3ca352cfa60d..9e59bced01f1 100644
--- a/services/tests/wmtests/src/com/android/server/policy/WindowWakeUpPolicyTests.java
+++ b/services/tests/wmtests/src/com/android/server/policy/WindowWakeUpPolicyTests.java
@@ -57,6 +57,7 @@ import android.content.res.Resources;
import android.os.PowerManager;
import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.EnableFlags;
+import android.platform.test.annotations.Presubmit;
import android.platform.test.flag.junit.SetFlagsRule;
import android.provider.Settings;
import android.view.Display;
@@ -83,6 +84,7 @@ import java.util.function.BooleanSupplier;
*
* <p>Build/Install/Run: atest WmTests:WindowWakeUpPolicyTests
*/
+@Presubmit
public final class WindowWakeUpPolicyTests {
@Rule public MockitoRule mMockitoRule = MockitoJUnit.rule();
@Rule public FakeSettingsProviderRule mSettingsProviderRule = FakeSettingsProvider.rule();
diff --git a/services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerTests.java
index 51706d72cb35..902a58379ae0 100644
--- a/services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerTests.java
@@ -589,8 +589,7 @@ public class BackgroundActivityStartControllerTests {
+ "realCallerApp: null; "
+ "balAllowedByPiSender: BSP.ALLOW_BAL; "
+ "realCallerStartMode: MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED; "
- + "balRequireOptInByPendingIntentCreator: true; "
- + "balDontBringExistingBackgroundTaskStackToFg: true]");
+ + "balRequireOptInByPendingIntentCreator: true]");
}
@Test
@@ -692,7 +691,6 @@ public class BackgroundActivityStartControllerTests {
+ "realCallerApp: null; "
+ "balAllowedByPiSender: BSP.ALLOW_FGS; "
+ "realCallerStartMode: MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED; "
- + "balRequireOptInByPendingIntentCreator: true; "
- + "balDontBringExistingBackgroundTaskStackToFg: true]");
+ + "balRequireOptInByPendingIntentCreator: 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 5ac3e483231c..7af4ede05363 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -4461,7 +4461,46 @@ public class SizeCompatTests extends WindowTestsBase {
// are aligned to the top of the parentAppBounds
assertEquals(new Rect(0, notchHeight, 1000, 1200), appBounds);
assertEquals(new Rect(0, 0, 1000, 1200), bounds);
+ }
+
+ @Test
+ @DisableCompatChanges({ActivityInfo.INSETS_DECOUPLED_CONFIGURATION_ENFORCED})
+ public void testInFreeform_boundsSandboxedToAppBounds() {
+ final int dw = 2800;
+ final int dh = 1400;
+ final int notchHeight = 100;
+ final DisplayContent display = new TestDisplayContent.Builder(mAtm, dw, dh)
+ .setNotch(notchHeight)
+ .build();
+ setUpApp(display);
+ prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT);
+ mTask.mDisplayContent.getDefaultTaskDisplayArea()
+ .setWindowingMode(WindowConfiguration.WINDOWING_MODE_FREEFORM);
+ mTask.setWindowingMode(WINDOWING_MODE_FREEFORM);
+ Rect appBounds = new Rect(0, 0, 1000, 500);
+ Rect bounds = new Rect(0, 0, 1000, 600);
+ mTask.getWindowConfiguration().setAppBounds(appBounds);
+ mTask.getWindowConfiguration().setBounds(bounds);
+ mActivity.onConfigurationChanged(mTask.getConfiguration());
+
+ // Bounds are sandboxed to appBounds in freeform.
+ assertDownScaled();
+ assertEquals(mActivity.getWindowConfiguration().getAppBounds(),
+ mActivity.getWindowConfiguration().getBounds());
+
+ // Exit freeform.
+ mTask.mDisplayContent.getDefaultTaskDisplayArea()
+ .setWindowingMode(WindowConfiguration.WINDOWING_MODE_FULLSCREEN);
+ mTask.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
+ mTask.getWindowConfiguration().setBounds(new Rect(0, 0, dw, dh));
+ mActivity.onConfigurationChanged(mTask.getConfiguration());
+ assertFitted();
+ appBounds = mActivity.getWindowConfiguration().getAppBounds();
+ bounds = mActivity.getWindowConfiguration().getBounds();
+ // Bounds are not sandboxed to appBounds.
+ assertNotEquals(appBounds, bounds);
+ assertEquals(notchHeight, appBounds.top - bounds.top);
}
@Test
diff --git a/services/usb/OWNERS b/services/usb/OWNERS
index 2dff392d4e34..259261252032 100644
--- a/services/usb/OWNERS
+++ b/services/usb/OWNERS
@@ -1,9 +1,9 @@
-anothermark@google.com
+vmartensson@google.com
+nkapron@google.com
febinthattil@google.com
-aprasath@google.com
+shubhankarm@google.com
badhri@google.com
elaurent@google.com
albertccwang@google.com
jameswei@google.com
howardyen@google.com
-kumarashishg@google.com \ No newline at end of file
diff --git a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
index b32379ae4b1e..4d9df4666016 100644
--- a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
+++ b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
@@ -415,4 +415,5 @@ interface ITelecomService {
*/
boolean hasForegroundServiceDelegation(in PhoneAccountHandle phoneAccountHandle,
String callingPackage);
+ void setMetricsTestMode(boolean enabled);
}
diff --git a/telephony/java/android/telephony/satellite/SatelliteSubscriberInfo.java b/telephony/java/android/telephony/satellite/SatelliteSubscriberInfo.java
index 9d9cac9702bb..d62fd63aeda6 100644
--- a/telephony/java/android/telephony/satellite/SatelliteSubscriberInfo.java
+++ b/telephony/java/android/telephony/satellite/SatelliteSubscriberInfo.java
@@ -22,8 +22,10 @@ import android.annotation.NonNull;
import android.annotation.SystemApi;
import android.os.Parcel;
import android.os.Parcelable;
+import android.telephony.Rlog;
import com.android.internal.telephony.flags.Flags;
+import com.android.internal.telephony.util.TelephonyUtils;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -220,7 +222,7 @@ public final class SatelliteSubscriberInfo implements Parcelable {
StringBuilder sb = new StringBuilder();
sb.append("SubscriberId:");
- sb.append(mSubscriberId);
+ sb.append(Rlog.pii(TelephonyUtils.IS_DEBUGGABLE, mSubscriberId));
sb.append(",");
sb.append("CarrierId:");
diff --git a/telephony/java/android/telephony/satellite/SatelliteSubscriberProvisionStatus.java b/telephony/java/android/telephony/satellite/SatelliteSubscriberProvisionStatus.java
index fb4f89ded547..75d3ec6d3fe6 100644
--- a/telephony/java/android/telephony/satellite/SatelliteSubscriberProvisionStatus.java
+++ b/telephony/java/android/telephony/satellite/SatelliteSubscriberProvisionStatus.java
@@ -21,8 +21,10 @@ import android.annotation.NonNull;
import android.annotation.SystemApi;
import android.os.Parcel;
import android.os.Parcelable;
+import android.telephony.Rlog;
import com.android.internal.telephony.flags.Flags;
+import com.android.internal.telephony.util.TelephonyUtils;
import java.util.Objects;
@@ -132,7 +134,7 @@ public final class SatelliteSubscriberProvisionStatus implements Parcelable {
StringBuilder sb = new StringBuilder();
sb.append("SatelliteSubscriberInfo:");
- sb.append(mSubscriberInfo);
+ sb.append(Rlog.pii(TelephonyUtils.IS_DEBUGGABLE, mSubscriberInfo));
sb.append(",");
sb.append("ProvisionStatus:");
diff --git a/telephony/java/com/android/internal/telephony/util/WorkerThread.java b/telephony/java/com/android/internal/telephony/util/WorkerThread.java
new file mode 100644
index 000000000000..f5b653656352
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/util/WorkerThread.java
@@ -0,0 +1,130 @@
+/*
+ * 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.telephony.util;
+
+import android.annotation.NonNull;
+import android.os.Handler;
+import android.os.HandlerExecutor;
+import android.os.HandlerThread;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
+
+/**
+ * Shared singleton worker thread for each process.
+ *
+ * This thread should be used for work that needs to be executed at standard priority
+ * but not on the main thread. This is suitable for handling asynchronous tasks that
+ * are ephemeral or require enough work that they shouldn't block the main thread, but
+ * should not block each other for more than around 100ms.
+ */
+public final class WorkerThread extends HandlerThread {
+ private static volatile WorkerThread sInstance;
+ private static volatile Handler sHandler;
+ private static volatile HandlerExecutor sHandlerExecutor;
+ private static final Object sLock = new Object();
+
+ private CountDownLatch mInitLock = new CountDownLatch(1);
+
+
+ private WorkerThread() {
+ super("android.telephony.worker");
+ }
+
+ private static void ensureThread() {
+ if (sInstance != null) return;
+ synchronized (sLock) {
+ if (sInstance != null) return;
+
+ final WorkerThread tmpThread = new WorkerThread();
+ tmpThread.start();
+
+ try {
+ tmpThread.mInitLock.await();
+ } catch (InterruptedException ignored) {
+ }
+
+
+ sHandler = new Handler(
+ tmpThread.getLooper(),
+ /* callback= */ null,
+ /* async= */ false,
+ /* shared= */ true);
+ sHandlerExecutor = new HandlerExecutor(sHandler);
+ sInstance = tmpThread; // Note: order matters here. sInstance must be assigned last.
+
+ }
+ }
+
+ @Override
+ protected void onLooperPrepared() {
+ mInitLock.countDown();
+ }
+
+ /**
+ * Get the worker thread directly.
+ *
+ * Users of this thread should take care not to block it for extended periods of
+ * time.
+ *
+ * @return a HandlerThread, never null
+ */
+ @NonNull public static HandlerThread get() {
+ ensureThread();
+ return sInstance;
+ }
+
+ /**
+ * Get a Handler that can process Runnables.
+ *
+ * @return a Handler, never null
+ */
+ @NonNull public static Handler getHandler() {
+ ensureThread();
+ return sHandler;
+ }
+
+ /**
+ * Get an Executor that can process Runnables
+ *
+ * @return an Executor, never null
+ */
+ @NonNull public static Executor getExecutor() {
+ ensureThread();
+ return sHandlerExecutor;
+ }
+
+ /**
+ * A method to reset the WorkerThread from scratch.
+ *
+ * This method should only be used for unit testing. In production it would have
+ * catastrophic consequences. Do not ever use this outside of tests.
+ */
+ @VisibleForTesting
+ public static void reset() {
+ synchronized (sLock) {
+ if (sInstance == null) return;
+ sInstance.quitSafely();
+ sInstance = null;
+ sHandler = null;
+ sHandlerExecutor = null;
+ ensureThread();
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/RecentTasksUtils.kt b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/RecentTasksUtils.kt
index aa262f9dfd6a..1a5fda7c8324 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/RecentTasksUtils.kt
+++ b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/RecentTasksUtils.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker.utils
+package com.android.server.wm.flicker.helpers
import android.app.Instrumentation
@@ -24,4 +24,4 @@ object RecentTasksUtils {
"dumpsys activity service SystemUIService WMShell recents clearAll"
)
}
-} \ No newline at end of file
+}
diff --git a/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt b/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt
index e0532633d40b..eef4e6f58463 100644
--- a/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt
+++ b/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt
@@ -467,30 +467,6 @@ class KeyGestureControllerTests {
intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
),
TestData(
- "CTRL + ALT + DPAD_LEFT -> Change Splitscreen Focus Left",
- intArrayOf(
- KeyEvent.KEYCODE_CTRL_LEFT,
- KeyEvent.KEYCODE_ALT_LEFT,
- KeyEvent.KEYCODE_DPAD_LEFT
- ),
- KeyGestureEvent.KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_LEFT,
- intArrayOf(KeyEvent.KEYCODE_DPAD_LEFT),
- KeyEvent.META_CTRL_ON or KeyEvent.META_ALT_ON,
- intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
- ),
- TestData(
- "CTRL + ALT + DPAD_RIGHT -> Change Splitscreen Focus Right",
- intArrayOf(
- KeyEvent.KEYCODE_CTRL_LEFT,
- KeyEvent.KEYCODE_ALT_LEFT,
- KeyEvent.KEYCODE_DPAD_RIGHT
- ),
- KeyGestureEvent.KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_RIGHT,
- intArrayOf(KeyEvent.KEYCODE_DPAD_RIGHT),
- KeyEvent.META_CTRL_ON or KeyEvent.META_ALT_ON,
- intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
- ),
- TestData(
"META + / -> Open Shortcut Helper",
intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_SLASH),
KeyGestureEvent.KEY_GESTURE_TYPE_OPEN_SHORTCUT_HELPER,
diff --git a/tests/Tracing/src/com/android/internal/protolog/ProtoLogViewerConfigReaderTest.java b/tests/Tracing/src/com/android/internal/protolog/ProtoLogViewerConfigReaderTest.java
index 9e029a8d5e57..72b1780ceb06 100644
--- a/tests/Tracing/src/com/android/internal/protolog/ProtoLogViewerConfigReaderTest.java
+++ b/tests/Tracing/src/com/android/internal/protolog/ProtoLogViewerConfigReaderTest.java
@@ -33,6 +33,7 @@ import org.junit.runners.JUnit4;
import perfetto.protos.ProtologCommon;
import java.io.File;
+import java.io.IOException;
@Presubmit
@RunWith(JUnit4.class)
@@ -159,4 +160,14 @@ public class ProtoLogViewerConfigReaderTest {
loadViewerConfig();
unloadViewerConfig();
}
+
+ @Test
+ public void testMessageHashIsAvailableInFile() throws IOException {
+ Truth.assertThat(mConfig.messageHashIsAvailableInFile(1)).isTrue();
+ Truth.assertThat(mConfig.messageHashIsAvailableInFile(2)).isTrue();
+ Truth.assertThat(mConfig.messageHashIsAvailableInFile(3)).isTrue();
+ Truth.assertThat(mConfig.messageHashIsAvailableInFile(4)).isTrue();
+ Truth.assertThat(mConfig.messageHashIsAvailableInFile(5)).isTrue();
+ Truth.assertThat(mConfig.messageHashIsAvailableInFile(6)).isFalse();
+ }
}
diff --git a/tests/testables/src/android/testing/OWNERS b/tests/testables/src/android/testing/OWNERS
new file mode 100644
index 000000000000..f31666b43654
--- /dev/null
+++ b/tests/testables/src/android/testing/OWNERS
@@ -0,0 +1,2 @@
+# MessageQueue-related classes
+per-file TestableLooper.java = mfasheh@google.com, shayba@google.com
diff --git a/tests/testables/src/android/testing/TestableLooper.java b/tests/testables/src/android/testing/TestableLooper.java
index be5c84c0353c..7d07d42b8042 100644
--- a/tests/testables/src/android/testing/TestableLooper.java
+++ b/tests/testables/src/android/testing/TestableLooper.java
@@ -16,11 +16,13 @@ package android.testing;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.Instrumentation;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;
import android.os.MessageQueue;
+import android.os.SystemClock;
import android.os.TestLooperManager;
import android.util.ArrayMap;
@@ -32,7 +34,7 @@ import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
-import java.lang.reflect.Field;
+import java.util.LinkedList;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -42,6 +44,12 @@ import java.util.concurrent.atomic.AtomicBoolean;
* and provide an easy annotation for use with tests.
*
* @see TestableLooperTest TestableLooperTest for examples.
+ *
+ * @deprecated Use {@link android.os.TestLooperManager} or {@link
+ * org.robolectric.shadows.ShadowLooper} instead.
+ * This class is not actively maintained.
+ * Both of the recommended alternatives allow fine control of execution.
+ * The Robolectric class also allows advancing time.
*/
public class TestableLooper {
@@ -50,9 +58,6 @@ public class TestableLooper {
* catch crashes.
*/
public static final boolean HOLD_MAIN_THREAD = false;
- private static final Field MESSAGE_QUEUE_MESSAGES_FIELD;
- private static final Field MESSAGE_NEXT_FIELD;
- private static final Field MESSAGE_WHEN_FIELD;
private Looper mLooper;
private MessageQueue mQueue;
@@ -61,19 +66,6 @@ public class TestableLooper {
private Handler mHandler;
private TestLooperManager mQueueWrapper;
- static {
- try {
- MESSAGE_QUEUE_MESSAGES_FIELD = MessageQueue.class.getDeclaredField("mMessages");
- MESSAGE_QUEUE_MESSAGES_FIELD.setAccessible(true);
- MESSAGE_NEXT_FIELD = Message.class.getDeclaredField("next");
- MESSAGE_NEXT_FIELD.setAccessible(true);
- MESSAGE_WHEN_FIELD = Message.class.getDeclaredField("when");
- MESSAGE_WHEN_FIELD.setAccessible(true);
- } catch (NoSuchFieldException e) {
- throw new RuntimeException("Failed to initialize TestableLooper", e);
- }
- }
-
public TestableLooper(Looper l) throws Exception {
this(acquireLooperManager(l), l);
}
@@ -216,29 +208,17 @@ public class TestableLooper {
}
public void moveTimeForward(long milliSeconds) {
- try {
- Message msg = getMessageLinkedList();
- while (msg != null) {
- long updatedWhen = msg.getWhen() - milliSeconds;
- if (updatedWhen < 0) {
- updatedWhen = 0;
- }
- MESSAGE_WHEN_FIELD.set(msg, updatedWhen);
- msg = (Message) MESSAGE_NEXT_FIELD.get(msg);
+ long futureWhen = SystemClock.uptimeMillis() + milliSeconds;
+ // Find messages in the queue enqueued within the future time, and execute them now.
+ while (true) {
+ Long peekWhen = mQueueWrapper.peekWhen();
+ if (peekWhen == null || peekWhen > futureWhen) {
+ break;
+ }
+ Message message = mQueueWrapper.poll();
+ if (message != null) {
+ mQueueWrapper.execute(message);
}
- } catch (IllegalAccessException e) {
- throw new RuntimeException("Access failed in TestableLooper: set - Message.when", e);
- }
- }
-
- private Message getMessageLinkedList() {
- try {
- MessageQueue queue = mLooper.getQueue();
- return (Message) MESSAGE_QUEUE_MESSAGES_FIELD.get(queue);
- } catch (IllegalAccessException e) {
- throw new RuntimeException(
- "Access failed in TestableLooper: get - MessageQueue.mMessages",
- e);
}
}
diff --git a/tests/utils/testutils/java/android/os/test/OWNERS b/tests/utils/testutils/java/android/os/test/OWNERS
index 3a9129e1bb69..6448261102fa 100644
--- a/tests/utils/testutils/java/android/os/test/OWNERS
+++ b/tests/utils/testutils/java/android/os/test/OWNERS
@@ -1 +1,4 @@
per-file FakePermissionEnforcer.java = file:/tests/EnforcePermission/OWNERS
+
+# MessageQueue-related classes
+per-file TestLooper.java = mfasheh@google.com, shayba@google.com
diff --git a/tests/utils/testutils/java/android/os/test/TestLooper.java b/tests/utils/testutils/java/android/os/test/TestLooper.java
index 56b0a25ed2dd..bca95917b9af 100644
--- a/tests/utils/testutils/java/android/os/test/TestLooper.java
+++ b/tests/utils/testutils/java/android/os/test/TestLooper.java
@@ -24,31 +24,38 @@ import android.os.Looper;
import android.os.Message;
import android.os.MessageQueue;
import android.os.SystemClock;
+import android.os.TestLooperManager;
import android.util.Log;
+import androidx.test.InstrumentationRegistry;
+
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
+import java.util.ArrayDeque;
+import java.util.Queue;
import java.util.concurrent.Executor;
/**
- * Creates a looper whose message queue can be manipulated
- * This allows testing code that uses a looper to dispatch messages in a deterministic manner
- * Creating a TestLooper will also install it as the looper for the current thread
+ * Creates a looper whose message queue can be manipulated This allows testing code that uses a
+ * looper to dispatch messages in a deterministic manner Creating a TestLooper will also install it
+ * as the looper for the current thread
+ *
+ * @deprecated Use {@link android.os.TestLooperManager} or {@link
+ * org.robolectric.shadows.ShadowLooper} instead.
+ * This class is not actively maintained.
+ * Both of the recommended alternatives allow fine control of execution.
+ * The Robolectric class also allows advancing time.
*/
public class TestLooper {
- protected final Looper mLooper;
+ private final Looper mLooper;
+ private final TestLooperManager mTestLooperManager;
+ private final Clock mClock;
private static final Constructor<Looper> LOOPER_CONSTRUCTOR;
private static final Field THREAD_LOCAL_LOOPER_FIELD;
- private static final Field MESSAGE_QUEUE_MESSAGES_FIELD;
- private static final Field MESSAGE_NEXT_FIELD;
- private static final Field MESSAGE_WHEN_FIELD;
- private static final Method MESSAGE_MARK_IN_USE_METHOD;
private static final String TAG = "TestLooper";
- private final Clock mClock;
private AutoDispatchThread mAutoDispatchThread;
@@ -58,14 +65,6 @@ public class TestLooper {
LOOPER_CONSTRUCTOR.setAccessible(true);
THREAD_LOCAL_LOOPER_FIELD = Looper.class.getDeclaredField("sThreadLocal");
THREAD_LOCAL_LOOPER_FIELD.setAccessible(true);
- MESSAGE_QUEUE_MESSAGES_FIELD = MessageQueue.class.getDeclaredField("mMessages");
- MESSAGE_QUEUE_MESSAGES_FIELD.setAccessible(true);
- MESSAGE_NEXT_FIELD = Message.class.getDeclaredField("next");
- MESSAGE_NEXT_FIELD.setAccessible(true);
- MESSAGE_WHEN_FIELD = Message.class.getDeclaredField("when");
- MESSAGE_WHEN_FIELD.setAccessible(true);
- MESSAGE_MARK_IN_USE_METHOD = Message.class.getDeclaredMethod("markInUse");
- MESSAGE_MARK_IN_USE_METHOD.setAccessible(true);
} catch (NoSuchFieldException | NoSuchMethodException e) {
throw new RuntimeException("Failed to initialize TestLooper", e);
}
@@ -100,6 +99,8 @@ public class TestLooper {
throw new RuntimeException("Reflection error constructing or accessing looper", e);
}
+ mTestLooperManager =
+ InstrumentationRegistry.getInstrumentation().acquireLooperManager(mLooper);
mClock = clock;
}
@@ -111,78 +112,61 @@ public class TestLooper {
return new HandlerExecutor(new Handler(getLooper()));
}
- private Message getMessageLinkedList() {
- try {
- MessageQueue queue = mLooper.getQueue();
- return (Message) MESSAGE_QUEUE_MESSAGES_FIELD.get(queue);
- } catch (IllegalAccessException e) {
- throw new RuntimeException("Access failed in TestLooper: get - MessageQueue.mMessages",
- e);
- }
- }
-
public void moveTimeForward(long milliSeconds) {
- try {
- Message msg = getMessageLinkedList();
- while (msg != null) {
- long updatedWhen = msg.getWhen() - milliSeconds;
- if (updatedWhen < 0) {
- updatedWhen = 0;
- }
- MESSAGE_WHEN_FIELD.set(msg, updatedWhen);
- msg = (Message) MESSAGE_NEXT_FIELD.get(msg);
+ // Drain all Messages from the queue.
+ Queue<Message> messages = new ArrayDeque<>();
+ while (true) {
+ Message message = mTestLooperManager.poll();
+ if (message == null) {
+ break;
}
- } catch (IllegalAccessException e) {
- throw new RuntimeException("Access failed in TestLooper: set - Message.when", e);
- }
- }
- private long currentTime() {
- return mClock.uptimeMillis();
- }
+ // Adjust the Message's delivery time.
+ long newWhen = message.when - milliSeconds;
+ if (newWhen < 0) {
+ newWhen = 0;
+ }
+ message.when = newWhen;
+ messages.add(message);
+ }
- private Message messageQueueNext() {
- try {
- long now = currentTime();
-
- Message prevMsg = null;
- Message msg = getMessageLinkedList();
- if (msg != null && msg.getTarget() == null) {
- // Stalled by a barrier. Find the next asynchronous message in
- // the queue.
- do {
- prevMsg = msg;
- msg = (Message) MESSAGE_NEXT_FIELD.get(msg);
- } while (msg != null && !msg.isAsynchronous());
+ // Repost all Messages back to the queuewith a new time.
+ while (true) {
+ Message message = messages.poll();
+ if (message == null) {
+ break;
}
- if (msg != null) {
- if (now >= msg.getWhen()) {
- // Got a message.
- if (prevMsg != null) {
- MESSAGE_NEXT_FIELD.set(prevMsg, MESSAGE_NEXT_FIELD.get(msg));
- } else {
- MESSAGE_QUEUE_MESSAGES_FIELD.set(mLooper.getQueue(),
- MESSAGE_NEXT_FIELD.get(msg));
- }
- MESSAGE_NEXT_FIELD.set(msg, null);
- MESSAGE_MARK_IN_USE_METHOD.invoke(msg);
- return msg;
- }
+
+ Runnable callback = message.getCallback();
+ Handler handler = message.getTarget();
+ long when = message.getWhen();
+
+ // The Message cannot be re-enqueued because it is marked in use.
+ // Make a copy of the Message and recycle the original.
+ // This resets {@link Message#isInUse()} but retains all other content.
+ {
+ Message newMessage = Message.obtain();
+ newMessage.copyFrom(message);
+ newMessage.setCallback(callback);
+ mTestLooperManager.recycle(message);
+ message = newMessage;
}
- } catch (IllegalAccessException | InvocationTargetException e) {
- throw new RuntimeException("Access failed in TestLooper", e);
+
+ // Send the Message back to its Handler to be re-enqueued.
+ handler.sendMessageAtTime(message, when);
}
+ }
- return null;
+ private long currentTime() {
+ return mClock.uptimeMillis();
}
/**
* @return true if there are pending messages in the message queue
*/
public synchronized boolean isIdle() {
- Message messageList = getMessageLinkedList();
-
- return messageList != null && currentTime() >= messageList.getWhen();
+ Long when = mTestLooperManager.peekWhen();
+ return when != null && currentTime() >= when;
}
/**
@@ -190,7 +174,7 @@ public class TestLooper {
*/
public synchronized Message nextMessage() {
if (isIdle()) {
- return messageQueueNext();
+ return mTestLooperManager.poll();
} else {
return null;
}
@@ -202,7 +186,7 @@ public class TestLooper {
*/
public synchronized void dispatchNext() {
assertTrue(isIdle());
- Message msg = messageQueueNext();
+ Message msg = mTestLooperManager.poll();
if (msg == null) {
return;
}
diff --git a/tools/aapt2/tools/finalize_res.py b/tools/aapt2/tools/finalize_res.py
index 0e4d865bc890..059f3b2087cf 100755
--- a/tools/aapt2/tools/finalize_res.py
+++ b/tools/aapt2/tools/finalize_res.py
@@ -38,13 +38,22 @@ Usage: $ANDROID_BUILD_TOP/frameworks/base/tools/aapt2/tools/finalize_res.py \
import re
import sys
+import subprocess
+from collections import defaultdict
resTypes = ["attr", "id", "style", "string", "dimen", "color", "array", "drawable", "layout",
"anim", "animator", "interpolator", "mipmap", "integer", "transition", "raw", "bool",
"fraction"]
+NO_FLAG_MAGIC_CONSTANT = "no_flag"
+
+_aconfig_map = {}
+_not_finalized = defaultdict(list)
_type_ids = {}
_type = ""
+_finalized_flags = defaultdict(list)
+_non_finalized_flags = defaultdict(list)
+
_lowest_staging_first_id = 0x01FFFFFF
@@ -53,13 +62,53 @@ _lowest_staging_first_id = 0x01FFFFFF
prefixed with removed_. The IDs are assigned without holes starting from the last ID for that
type currently finalized in public-final.xml.
"""
-def finalize_item(raw):
- name = raw.group(1)
- if re.match(r'_*removed.+', name):
- return ""
+def finalize_item(comment_and_item):
+ print("Processing:\n" + comment_and_item)
+ name = re.search('<public name="(.+?)"',comment_and_item, flags=re.DOTALL).group(1)
+ if re.match('removed_.+', name):
+ # Remove it from <staging-public-group> in public-staging.xml
+ # Include it as is in <staging-public-group-final> in public-final.xml
+ # Don't assign an id in public-final.xml
+ return ("", comment_and_item, "")
+
+ comment = re.search(' *<!--.+?-->\n', comment_and_item, flags=re.DOTALL).group(0)
+
+ match = re.search('<!-- @FlaggedApi\((.+?)\)', comment, flags=re.DOTALL)
+ if match:
+ flag = match.group(1)
+ else:
+ flag = NO_FLAG_MAGIC_CONSTANT
+
+ if flag.startswith("\""):
+ # Flag is a string value, just remove "
+ flag = flag.replace("\"", "")
+ else:
+ # Flag is a java constant, convert to string value
+ flag = flag.replace(".Flags.FLAG_", ".").lower()
+
+ if flag not in _aconfig_map:
+ raise Exception("Unknown flag: " + flag)
+
+ # READ_ONLY-ENABLED is a magic string from printflags output below
+ if _aconfig_map[flag] != "READ_ONLY-ENABLED":
+ _non_finalized_flags[flag].append(name)
+ # Keep it as is in <staging-public-group> in public-staging.xml
+ # Include as magic constant "removed_" in <staging-public-group-final> in public-final.xml
+ # Don't assign an id in public-final.xml
+ return (comment_and_item, " <public name=\"removed_\" />\n", "")
+
+ _finalized_flags[flag].append(name)
+
id = _type_ids[_type]
_type_ids[_type] += 1
- return ' <public type="%s" name="%s" id="%s" />\n' % (_type, name, '0x{0:0{1}x}'.format(id, 8))
+
+ # Removes one indentation step to align the comment with the item outside the
+ comment = re.sub("^ ", "", comment, flags=re.MULTILINE)
+
+ # Remove from <staging-public-group> in public-staging.xml
+ # Include as is in <staging-public-group-final> in public-final.xml
+ # Assign an id in public-final.xml
+ return ("", comment_and_item, comment + ' <public type="%s" name="%s" id="%s" />\n' % (_type, name, '0x{0:0{1}x}'.format(id, 8)))
"""
@@ -72,10 +121,26 @@ def finalize_group(raw):
_type = raw.group(1)
id = int(raw.group(2), 16)
_type_ids[_type] = _type_ids.get(_type, id)
- (res, count) = re.subn(' {0,4}<public name="(.+?)" */>\n', finalize_item, raw.group(3))
- if count > 0:
- res = raw.group(0).replace("staging-public-group",
- "staging-public-group-final") + '\n' + res
+
+
+ all = re.findall(' *<!--.*?<public name=".+?" */>\n', raw.group(3), flags=re.DOTALL)
+ res = ""
+ group_matches = ""
+ for match in all:
+ (staging_group, final_group, final_id_assignment) = finalize_item(match)
+
+ if staging_group:
+ _not_finalized[_type].append(staging_group)
+
+ if final_group:
+ group_matches += final_group
+
+ if final_id_assignment:
+ res += final_id_assignment
+
+ # Only add it to final.xml if new ids were actually assigned
+ if res:
+ res = '<staging-public-group-final type="%s" first-id="%s">\n%s </staging-public-group-final>\n\n%s' % (_type, raw.group(2), group_matches, res)
_lowest_staging_first_id = min(id, _lowest_staging_first_id)
return res
@@ -88,6 +153,15 @@ def collect_ids(raw):
id = int(m.group(2), 16)
_type_ids[type] = max(id + 1, _type_ids.get(type, 0))
+# This is a hack and assumes this script is run from the top directory
+output=subprocess.run("printflags --format='{fully_qualified_name} {permission}-{state}'", shell=True, capture_output=True, encoding="utf-8", check=True)
+for line in output.stdout.splitlines():
+ parts = line.split()
+ key = parts[0]
+ value = parts[1]
+ _aconfig_map[key]=value
+
+_aconfig_map[NO_FLAG_MAGIC_CONSTANT]="READ_ONLY-DISABLED"
with open(sys.argv[1], "r+") as stagingFile:
with open(sys.argv[2], "r+") as finalFile:
@@ -132,10 +206,25 @@ with open(sys.argv[1], "r+") as stagingFile:
nextId = _lowest_staging_first_id - 0x00010000
for resType in resTypes:
stagingFile.write(' <staging-public-group type="%s" first-id="%s">\n'
- ' </staging-public-group>\n\n' %
- (resType, '0x{0:0{1}x}'.format(nextId, 8)))
+ % (resType, '0x{0:0{1}x}'.format(nextId, 8)))
+ for item in _not_finalized[resType]:
+ stagingFile.write(item)
+ stagingFile.write(' </staging-public-group>\n\n')
nextId -= 0x00010000
# Close the resources tag and truncate, since the file will be shorter than the previous
stagingFile.write("</resources>\n")
stagingFile.truncate()
+
+
+print("\nFlags that had resources that were NOT finalized:")
+for flag in sorted(_non_finalized_flags.keys()):
+ print(f" {flag}")
+ for value in _non_finalized_flags[flag]:
+ print(f" {value}")
+
+print("\nFlags that had resources that were finalized:")
+for flag in sorted(_finalized_flags.keys()):
+ print(f" {flag}")
+ for value in _finalized_flags[flag]:
+ print(f" {value}")