summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--AconfigFlags.bp11
-rw-r--r--core/api/current.txt114
-rw-r--r--core/api/system-current.txt44
-rw-r--r--core/api/test-current.txt40
-rw-r--r--core/java/android/app/Activity.java18
-rw-r--r--core/java/android/app/ActivityThread.java31
-rw-r--r--core/java/android/app/ActivityThreadInternal.java3
-rw-r--r--core/java/android/app/ApplicationStartInfo.java89
-rw-r--r--core/java/android/app/AutomaticZenRule.java134
-rw-r--r--core/java/android/app/ConfigurationController.java2
-rw-r--r--core/java/android/app/ContextImpl.java20
-rw-r--r--core/java/android/app/INotificationManager.aidl2
-rw-r--r--core/java/android/app/Notification.java19
-rw-r--r--core/java/android/app/NotificationManager.java70
-rw-r--r--core/java/android/app/UiAutomation.java31
-rw-r--r--core/java/android/app/UiModeManager.java6
-rw-r--r--core/java/android/app/backup/BackupRestoreEventLogger.java17
-rw-r--r--core/java/android/app/jank/JankDataProcessor.java113
-rw-r--r--core/java/android/app/jank/JankTracker.java136
-rw-r--r--core/java/android/app/notification.aconfig10
-rw-r--r--core/java/android/app/supervision/ISupervisionManager.aidl3
-rw-r--r--core/java/android/app/supervision/SupervisionManager.java71
-rw-r--r--core/java/android/app/supervision/flags.aconfig8
-rw-r--r--core/java/android/companion/virtual/flags/flags.aconfig10
-rw-r--r--core/java/android/content/Context.java4
-rw-r--r--core/java/android/content/pm/PackageInstaller.java1
-rw-r--r--core/java/android/content/pm/PackageManager.java3
-rw-r--r--core/java/android/content/pm/multiuser.aconfig7
-rw-r--r--core/java/android/content/res/ApkAssets.java56
-rw-r--r--core/java/android/content/res/ResourceTimer.java56
-rw-r--r--core/java/android/content/res/XmlBlock.java32
-rw-r--r--core/java/android/hardware/display/ColorDisplayManager.java2
-rw-r--r--core/java/android/hardware/display/DisplayManager.java15
-rw-r--r--core/java/android/hardware/display/DisplayManagerGlobal.java23
-rw-r--r--core/java/android/hardware/fingerprint/FingerprintSensorConfigurations.java4
-rw-r--r--core/java/android/hardware/input/InputSettings.java4
-rw-r--r--core/java/android/os/CombinedMessageQueue/MessageQueue.java5
-rw-r--r--core/java/android/os/SystemVibratorManager.java2
-rw-r--r--core/java/android/os/flags.aconfig9
-rw-r--r--core/java/android/os/vibrator/VibratorFrequencyProfile.java22
-rw-r--r--core/java/android/provider/Settings.java3
-rw-r--r--core/java/android/security/FileIntegrityManager.java8
-rw-r--r--core/java/android/security/IFileIntegrityService.aidl2
-rw-r--r--core/java/android/service/notification/Condition.java52
-rw-r--r--core/java/android/service/notification/SystemZenRules.java3
-rw-r--r--core/java/android/service/notification/ZenAdapters.java8
-rw-r--r--core/java/android/service/notification/ZenDeviceEffects.java5
-rw-r--r--core/java/android/service/notification/ZenModeConfig.java537
-rw-r--r--core/java/android/service/notification/ZenModeDiff.java93
-rw-r--r--core/java/android/service/notification/ZenPolicy.java118
-rw-r--r--core/java/android/service/wallpaper/WallpaperService.java13
-rw-r--r--core/java/android/telephony/TelephonyCallback.java2
-rw-r--r--core/java/android/view/Display.java1
-rw-r--r--core/java/android/view/DisplayInfo.java27
-rw-r--r--core/java/android/view/NotificationHeaderView.java19
-rw-r--r--core/java/android/view/ViewRootImpl.java36
-rw-r--r--core/java/android/view/WindowManager.java10
-rw-r--r--core/java/android/view/accessibility/AccessibilityNodeInfo.java21
-rw-r--r--core/java/android/view/flags/refresh_rate_flags.aconfig7
-rw-r--r--core/java/android/view/inputmethod/InputMethodManager.java60
-rw-r--r--core/java/android/widget/RemoteViews.java16
-rw-r--r--core/java/android/window/DesktopModeFlags.java3
-rw-r--r--core/java/android/window/SystemUiContext.java69
-rw-r--r--core/java/android/window/TransitionInfo.java4
-rw-r--r--core/java/android/window/flags/lse_desktop_experience.aconfig31
-rw-r--r--core/java/com/android/internal/app/MediaRouteControllerContentManager.java109
-rw-r--r--core/java/com/android/internal/app/MediaRouteControllerDialog.java172
-rw-r--r--core/java/com/android/internal/app/OWNERS3
-rw-r--r--core/java/com/android/internal/security/VerityUtils.java3
-rw-r--r--core/java/com/android/internal/widget/LockPatternView.java38
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/CoreDocument.java3
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/Operations.java9
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/RemoteComposeBuffer.java14
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/DrawContent.java119
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/CanvasOperations.java175
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/Component.java20
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/LayoutComponent.java34
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/DrawContentOperation.java125
-rw-r--r--core/jni/android_content_res_ApkAssets.cpp108
-rw-r--r--core/jni/android_media_AudioSystem.cpp12
-rw-r--r--core/jni/com_android_internal_os_ZygoteCommandBuffer.cpp18
-rw-r--r--core/res/Android.bp1
-rw-r--r--core/res/AndroidManifest.xml176
-rw-r--r--core/res/res/drawable-w192dp/loader_horizontal_watch.xml97
-rw-r--r--core/res/res/drawable-w204dp/loader_horizontal_watch.xml104
-rw-r--r--core/res/res/drawable-w216dp/loader_horizontal_watch.xml105
-rw-r--r--core/res/res/drawable-w228dp/loader_horizontal_watch.xml103
-rw-r--r--core/res/res/drawable-w240dp/loader_horizontal_watch.xml104
-rw-r--r--core/res/res/drawable/ic_notification_summarization.xml3
-rw-r--r--core/res/res/drawable/loader_horizontal_watch.xml103
-rw-r--r--core/res/res/layout/media_route_controller_dialog.xml6
-rw-r--r--core/res/res/layout/notification_2025_conversation_header.xml2
-rw-r--r--core/res/res/layout/notification_2025_template_collapsed_base.xml1
-rw-r--r--core/res/res/layout/notification_2025_template_collapsed_media.xml1
-rw-r--r--core/res/res/layout/notification_2025_template_collapsed_messaging.xml1
-rw-r--r--core/res/res/layout/notification_2025_template_compact_heads_up_base.xml1
-rw-r--r--core/res/res/layout/notification_2025_template_compact_heads_up_messaging.xml1
-rw-r--r--core/res/res/values-w192dp/dimens_watch.xml28
-rw-r--r--core/res/res/values-w204dp-round-watch/dimens_watch.xml21
-rw-r--r--core/res/res/values-w216dp/dimens_watch.xml21
-rw-r--r--core/res/res/values-w228dp/dimens_watch.xml21
-rw-r--r--core/res/res/values-w240dp/dimens_material.xml4
-rw-r--r--core/res/res/values-watch/styles_device_defaults.xml3
-rw-r--r--core/res/res/values/config.xml9
-rw-r--r--core/res/res/values/dimens.xml3
-rw-r--r--core/res/res/values/dimens_watch.xml4
-rw-r--r--core/res/res/values/public-staging.xml16
-rw-r--r--core/res/res/values/strings.xml49
-rw-r--r--core/res/res/values/symbols.xml7
-rw-r--r--core/tests/coretests/Android.bp242
-rw-r--r--core/tests/coretests/src/android/app/AutomaticZenRuleTest.java11
-rw-r--r--core/tests/coretests/src/android/app/NotificationManagerTest.java8
-rw-r--r--core/tests/coretests/src/android/app/backup/BackupRestoreEventLoggerTest.java34
-rw-r--r--core/tests/coretests/src/android/app/backup/DataTypeResultTest.java117
-rw-r--r--core/tests/coretests/src/android/content/ContextTest.java51
-rw-r--r--core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java21
-rw-r--r--core/tests/coretests/src/android/service/notification/ConditionTest.java13
-rw-r--r--core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java7
-rw-r--r--core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java10
-rw-r--r--data/keyboards/Vendor_0957_Product_0001.kl2
-rw-r--r--graphics/java/android/graphics/FrameInfo.java7
-rw-r--r--graphics/java/android/graphics/HardwareRenderer.java12
-rw-r--r--graphics/java/android/graphics/RuntimeShader.java38
-rw-r--r--keystore/java/android/security/GateKeeper.java2
-rw-r--r--keystore/java/android/security/keystore/ArrayUtils.java2
-rw-r--r--keystore/java/android/security/keystore/Utils.java2
-rw-r--r--keystore/java/android/security/keystore2/KeyStore2ParameterUtils.java4
-rw-r--r--keystore/java/android/security/keystore2/KeymasterUtils.java46
-rw-r--r--libs/WindowManager/Shell/OWNERS2
-rw-r--r--libs/WindowManager/Shell/multivalentScreenshotTests/goldens/onDevice/foldable_inner/light_landscape_dragZones_bubble.pngbin0 -> 20393 bytes
-rw-r--r--libs/WindowManager/Shell/multivalentScreenshotTests/goldens/onDevice/foldable_inner/light_landscape_dragZones_bubbleBar.pngbin0 -> 20220 bytes
-rw-r--r--libs/WindowManager/Shell/multivalentScreenshotTests/goldens/onDevice/foldable_inner/light_landscape_dragZones_bubble_split_10_90.pngbin0 -> 20319 bytes
-rw-r--r--libs/WindowManager/Shell/multivalentScreenshotTests/goldens/onDevice/foldable_inner/light_landscape_dragZones_bubble_split_90_10.pngbin0 -> 20401 bytes
-rw-r--r--libs/WindowManager/Shell/multivalentScreenshotTests/goldens/onDevice/foldable_inner/light_landscape_dragZones_expandedView.pngbin0 -> 20635 bytes
-rw-r--r--libs/WindowManager/Shell/multivalentScreenshotTests/goldens/onDevice/foldable_inner/light_landscape_dragZones_expandedView_split_10_90.pngbin0 -> 20522 bytes
-rw-r--r--libs/WindowManager/Shell/multivalentScreenshotTests/goldens/onDevice/foldable_inner/light_landscape_dragZones_expandedView_split_90_10.pngbin0 -> 20569 bytes
-rw-r--r--libs/WindowManager/Shell/multivalentScreenshotTests/goldens/onDevice/foldable_inner/light_portrait_dragZones_bubble.pngbin0 -> 21304 bytes
-rw-r--r--libs/WindowManager/Shell/multivalentScreenshotTests/goldens/onDevice/foldable_inner/light_portrait_dragZones_bubbleBar.pngbin0 -> 21159 bytes
-rw-r--r--libs/WindowManager/Shell/multivalentScreenshotTests/goldens/onDevice/foldable_inner/light_portrait_dragZones_bubble_split_10_90.pngbin0 -> 21276 bytes
-rw-r--r--libs/WindowManager/Shell/multivalentScreenshotTests/goldens/onDevice/foldable_inner/light_portrait_dragZones_bubble_split_90_10.pngbin0 -> 21283 bytes
-rw-r--r--libs/WindowManager/Shell/multivalentScreenshotTests/goldens/onDevice/foldable_inner/light_portrait_dragZones_expandedView.pngbin0 -> 21318 bytes
-rw-r--r--libs/WindowManager/Shell/multivalentScreenshotTests/goldens/onDevice/tablet/light_landscape_dragZones_bubble.pngbin0 -> 20400 bytes
-rw-r--r--libs/WindowManager/Shell/multivalentScreenshotTests/goldens/onDevice/tablet/light_landscape_dragZones_bubbleBar.pngbin0 -> 20082 bytes
-rw-r--r--libs/WindowManager/Shell/multivalentScreenshotTests/goldens/onDevice/tablet/light_landscape_dragZones_expandedView.pngbin0 -> 20406 bytes
-rw-r--r--libs/WindowManager/Shell/multivalentScreenshotTests/goldens/onDevice/tablet/light_portrait_dragZones_bubble.pngbin0 -> 22270 bytes
-rw-r--r--libs/WindowManager/Shell/multivalentScreenshotTests/goldens/onDevice/tablet/light_portrait_dragZones_bubbleBar.pngbin0 -> 21917 bytes
-rw-r--r--libs/WindowManager/Shell/multivalentScreenshotTests/goldens/onDevice/tablet/light_portrait_dragZones_expandedView.pngbin0 -> 22462 bytes
-rw-r--r--libs/WindowManager/Shell/multivalentScreenshotTests/src/com/android/wm/shell/shared/bubbles/DragZoneFactoryScreenshotTest.kt170
-rw-r--r--libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_maximize_menu.xml54
-rw-r--r--libs/WindowManager/Shell/res/values/dimen.xml8
-rw-r--r--libs/WindowManager/Shell/shared/res/color/bubble_drop_target_background_color.xml20
-rw-r--r--libs/WindowManager/Shell/shared/res/drawable/bubble_drop_target_background.xml25
-rw-r--r--libs/WindowManager/Shell/shared/res/values/dimen.xml10
-rw-r--r--libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/TransitionUtil.java45
-rw-r--r--libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/animation/Interpolators.java11
-rw-r--r--libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/BubbleBarLocation.kt6
-rw-r--r--libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DragZone.kt24
-rw-r--r--libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DragZoneFactory.kt90
-rw-r--r--libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DropTargetManager.kt98
-rw-r--r--libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeCompatPolicy.kt3
-rw-r--r--libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java44
-rw-r--r--libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/split/SplitScreenConstants.java1
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java19
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleLogger.java6
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTransitions.java13
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/WindowContainerTransactionSupplier.kt35
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/split/CenterParallaxSpec.java39
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DismissingParallaxSpec.java75
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerSnapAlgorithm.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/split/FlexParallaxSpec.java172
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/split/NoParallaxSpec.java34
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/split/ParallaxSpec.java62
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/split/ResizingEffectPolicy.java177
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java8
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitState.java5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/events/ReachabilityGestureListener.kt66
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/events/ReachabilityGestureListenerFactory.kt43
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java16
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java8
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandler.kt3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandler.kt38
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeShellCommandHandler.kt26
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt9
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java7
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt256
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTaskChangeListener.kt6
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt368
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt12
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt26
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandler.java3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/desktopwallpaperactivity/DesktopWallpaperActivityTokenProvider.kt12
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/DeskTransition.kt3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/DesksOrganizer.kt6
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/DesksTransitionObserver.kt45
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/RootTaskDesksOrganizer.kt17
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/persistence/DesktopRepositoryInitializerImpl.kt4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionStarter.java7
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipDismissTargetHandler.java1
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java28
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java194
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransitionState.java3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/transition/PipExpandHandler.java331
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/transition/PipTransitionUtils.java133
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java15
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java62
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenWindowCreator.java14
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java30
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java65
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java93
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java323
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt63
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskOperations.java12
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/ReusableWindowDecorViewHost.kt9
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/SnapEventHandler.kt43
-rw-r--r--libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/EnterDesktopWithDrag.kt1
-rw-r--r--libs/WindowManager/Shell/tests/unittest/Android.bp1
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleTransitionsTest.java2
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayControllerTests.java186
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/WindowContainerTransactionSupplierTest.kt42
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/pip/PhonePipKeepClearAlgorithmTest.java (renamed from libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PhonePipKeepClearAlgorithmTest.java)7
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/pip/PhoneSizeSpecSourceTest.java (renamed from libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PhoneSizeSpecSourceTest.java)7
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/pip/PipBoundsAlgorithmTest.java (renamed from libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsAlgorithmTest.java)11
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/pip/PipBoundsStateTest.java (renamed from libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsStateTest.java)8
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/pip/PipDoubleTapHelperTest.java (renamed from libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipDoubleTapHelperTest.java)6
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/pip/PipSnapAlgorithmTest.java (renamed from libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipSnapAlgorithmTest.java)6
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/FlexParallaxSpecTests.java401
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/events/ReachabilityGestureListenerFactoryTest.kt131
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/events/ReachabilityGestureListenerTest.kt146
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandlerTest.kt137
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt170
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt488
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt34
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt37
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/desktopwallpaperactivity/DesktopWallpaperActivityTokenProviderTest.kt128
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/multidesks/DesksTransitionObserverTest.kt67
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/multidesks/RootTaskDesksOrganizerTest.kt102
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipSchedulerTest.java51
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipTouchStateTest.java148
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/transition/PipExpandHandlerTest.java188
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java64
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/bubbles/DropTargetManagerTest.kt210
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatusTest.kt46
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt6
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTestsBase.kt3
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java25
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragResizeInputListenerTest.kt207
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java44
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/common/viewhost/ReusableWindowDecorViewHostTest.kt61
-rw-r--r--libs/androidfw/ApkAssets.cpp9
-rw-r--r--libs/androidfw/AssetManager2.cpp5
-rw-r--r--libs/androidfw/AssetsProvider.cpp91
-rw-r--r--libs/androidfw/Idmap.cpp21
-rw-r--r--libs/androidfw/LocaleDataLookup.cpp10
-rw-r--r--libs/androidfw/ResourceTypes.cpp78
-rw-r--r--libs/androidfw/Util.cpp25
-rw-r--r--libs/androidfw/include/androidfw/ApkAssets.h2
-rw-r--r--libs/androidfw/include/androidfw/AssetsProvider.h29
-rw-r--r--libs/androidfw/include/androidfw/Idmap.h32
-rw-r--r--libs/androidfw/include/androidfw/ResourceTypes.h48
-rw-r--r--libs/androidfw/include/androidfw/misc.h6
-rw-r--r--libs/androidfw/misc.cpp69
-rw-r--r--libs/androidfw/tests/Idmap_test.cpp27
-rw-r--r--libs/hwui/aconfig/hwui_flags.aconfig18
-rw-r--r--libs/hwui/jni/Shader.cpp15
-rw-r--r--libs/hwui/jni/android_graphics_HardwareRenderer.cpp16
-rw-r--r--location/Android.bp1
-rw-r--r--media/java/android/media/AudioDeviceVolumeManager.java55
-rw-r--r--media/java/android/media/AudioSystem.java15
-rw-r--r--media/java/android/media/ExifInterface.java7
-rw-r--r--media/java/android/media/MediaCodec.java2
-rw-r--r--media/java/android/media/RingtoneManager.java20
-rw-r--r--media/java/android/media/Utils.java6
-rw-r--r--packages/LocalTransport/src/com/android/localtransport/LocalTransport.java148
-rw-r--r--packages/LocalTransport/src/com/android/localtransport/LocalTransportParameters.java31
-rw-r--r--packages/LocalTransport/src/com/android/localtransport/LocalTransportService.java31
-rw-r--r--packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/InstallLaunch.kt19
-rw-r--r--packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/UninstallLaunch.kt19
-rw-r--r--packages/PackageInstaller/src/com/android/packageinstaller/v2/viewmodel/InstallViewModel.kt25
-rw-r--r--packages/SettingsLib/CollapsingToolbarBaseActivity/Android.bp1
-rw-r--r--packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBindings.kt6
-rw-r--r--packages/SettingsLib/SettingsTheme/src/com/android/settingslib/widget/SettingsBasePreferenceFragment.kt11
-rw-r--r--packages/SettingsLib/SettingsTransition/Android.bp1
-rw-r--r--packages/SettingsLib/Spa/spa/Android.bp3
-rw-r--r--packages/SettingsLib/Spa/spa/build.gradle.kts1
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsTheme.kt7
-rw-r--r--packages/SettingsLib/Spa/tests/Android.bp4
-rw-r--r--packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/button/ActionButtonsTest.kt6
-rw-r--r--packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBarTest.kt6
-rw-r--r--packages/SettingsLib/TopIntroPreference/Android.bp1
-rw-r--r--packages/SettingsLib/res/drawable/ic_1x_mobiledata_updated.xml24
-rw-r--r--packages/SettingsLib/res/drawable/ic_3g_mobiledata_updated.xml24
-rw-r--r--packages/SettingsLib/res/drawable/ic_4g_lte_mobiledata_updated.xml24
-rw-r--r--packages/SettingsLib/res/drawable/ic_4g_lte_plus_mobiledata_updated.xml27
-rw-r--r--packages/SettingsLib/res/drawable/ic_4g_mobiledata_updated.xml25
-rw-r--r--packages/SettingsLib/res/drawable/ic_4g_plus_mobiledata_updated.xml27
-rw-r--r--packages/SettingsLib/res/drawable/ic_5g_e_mobiledata_updated.xml25
-rw-r--r--packages/SettingsLib/res/drawable/ic_5g_mobiledata_updated.xml24
-rw-r--r--packages/SettingsLib/res/drawable/ic_5g_plus_mobiledata_default_updated.xml27
-rw-r--r--packages/SettingsLib/res/drawable/ic_5g_plus_mobiledata_updated.xml27
-rw-r--r--packages/SettingsLib/res/drawable/ic_carrier_wifi_updated.xml30
-rw-r--r--packages/SettingsLib/res/drawable/ic_e_mobiledata_updated.xml24
-rw-r--r--packages/SettingsLib/res/drawable/ic_g_mobiledata_updated.xml24
-rw-r--r--packages/SettingsLib/res/drawable/ic_h_mobiledata_updated.xml24
-rw-r--r--packages/SettingsLib/res/drawable/ic_h_plus_mobiledata_updated.xml27
-rw-r--r--packages/SettingsLib/res/drawable/ic_lte_mobiledata_updated.xml24
-rw-r--r--packages/SettingsLib/res/drawable/ic_lte_plus_mobiledata_updated.xml27
-rw-r--r--packages/SettingsLib/res/drawable/ic_mobile_0_4_bar.xml26
-rw-r--r--packages/SettingsLib/res/drawable/ic_mobile_0_4_bar_error.xml71
-rw-r--r--packages/SettingsLib/res/drawable/ic_mobile_0_5_bar.xml28
-rw-r--r--packages/SettingsLib/res/drawable/ic_mobile_0_5_bar_error.xml80
-rw-r--r--packages/SettingsLib/res/drawable/ic_mobile_1_4_bar.xml22
-rw-r--r--packages/SettingsLib/res/drawable/ic_mobile_1_4_bar_error.xml54
-rw-r--r--packages/SettingsLib/res/drawable/ic_mobile_1_5_bar.xml26
-rw-r--r--packages/SettingsLib/res/drawable/ic_mobile_1_5_bar_error.xml78
-rw-r--r--packages/SettingsLib/res/drawable/ic_mobile_2_4_bar.xml22
-rw-r--r--packages/SettingsLib/res/drawable/ic_mobile_2_4_bar_error.xml67
-rw-r--r--packages/SettingsLib/res/drawable/ic_mobile_2_5_bar.xml26
-rw-r--r--packages/SettingsLib/res/drawable/ic_mobile_2_5_bar_error.xml76
-rw-r--r--packages/SettingsLib/res/drawable/ic_mobile_3_4_bar.xml20
-rw-r--r--packages/SettingsLib/res/drawable/ic_mobile_3_4_bar_error.xml65
-rw-r--r--packages/SettingsLib/res/drawable/ic_mobile_3_5_bar.xml24
-rw-r--r--packages/SettingsLib/res/drawable/ic_mobile_3_5_bar_error.xml74
-rw-r--r--packages/SettingsLib/res/drawable/ic_mobile_4_4_bar.xml18
-rw-r--r--packages/SettingsLib/res/drawable/ic_mobile_4_4_bar_error.xml63
-rw-r--r--packages/SettingsLib/res/drawable/ic_mobile_4_5_bar.xml22
-rw-r--r--packages/SettingsLib/res/drawable/ic_mobile_4_5_bar_error.xml72
-rw-r--r--packages/SettingsLib/res/drawable/ic_mobile_5_5_bar.xml20
-rw-r--r--packages/SettingsLib/res/drawable/ic_mobile_5_5_bar_error.xml70
-rw-r--r--packages/SettingsLib/res/values/strings.xml3
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java22
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java108
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/display/DisplayDensityUtils.java50
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java6
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/graph/SignalDrawable.java12
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/mobile/TelephonyIcons.java58
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/notification/data/repository/ZenModeRepository.kt24
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/notification/modes/EnableDndDialogFactory.java18
-rw-r--r--packages/SettingsLib/tests/integ/src/com/android/settingslib/display/DisplayDensityUtilsTest.java31
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java36
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java86
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/dream/DreamBackendTest.java55
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/data/repository/ZenModeRepositoryTest.kt2
-rw-r--r--packages/SystemUI/OWNERS12
-rw-r--r--packages/SystemUI/aconfig/Android.bp1
-rw-r--r--packages/SystemUI/aconfig/systemui.aconfig67
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewTransitionAnimatorController.kt57
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/animation/IViewTransitionRegistry.kt56
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt297
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/animation/ViewTransitionRegistry.kt38
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/animation/ViewTransitionToken.kt10
-rw-r--r--packages/SystemUI/compose/core/src/com/android/compose/animation/Expandable.kt270
-rw-r--r--packages/SystemUI/compose/core/src/com/android/compose/animation/ExpandableController.kt116
-rw-r--r--packages/SystemUI/compose/core/src/com/android/compose/ui/graphics/DrawInOverlay.kt13
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt3
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt38
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt7
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeOverlay.kt36
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeOverlay.kt64
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneTransitionLayoutDataSource.kt5
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt2
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt11
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt3
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/UserActionDistanceScopeImpl.kt6
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Content.kt3
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Transformation.kt9
-rw-r--r--packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt24
-rw-r--r--packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/OverlayTest.kt10
-rw-r--r--packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/PredictiveBackHandlerTest.kt2
-rw-r--r--packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt2
-rw-r--r--packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt4
-rw-r--r--packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/NestedElementTransformationTest.kt4
-rw-r--r--packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestContentScope.kt4
-rw-r--r--packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestTransition.kt10
-rw-r--r--packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt27
-rw-r--r--packages/SystemUI/customization/src/com/android/systemui/shared/clocks/SimpleDigitalHandLayerController.kt3
-rw-r--r--packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockTextView.kt186
-rw-r--r--packages/SystemUI/docs/qs-tiles.md167
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardMessageAreaControllerTest.java17
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java21
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/activity/data/repository/ActivityManagerRepositoryTest.kt113
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/animation/GhostedViewTransitionAnimatorControllerTest.kt60
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModelTest.kt4
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorTest.kt19
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/inputdevice/tutorial/domain/interactor/TutorialNotificationCoordinatorTest.kt80
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/source/TestShortcuts.kt2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutCustomizationViewModelTest.kt37
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractorTest.kt33
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractorTest.kt42
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt20
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/scenetransition/LockscreenSceneTransitionInteractorTest.kt39
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DozingToDreamingTransitionViewModelTest.kt54
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelTest.kt49
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelTest.kt7
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/media/dialog/MediaOutputAdapterLegacyTest.java (renamed from packages/SystemUI/multivalentTests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java)96
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayContentViewModelTest.kt37
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/domain/interactor/GridLayoutTypeInteractorTest.kt2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/domain/interactor/QSColumnsInteractorTest.kt2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/MediaInRowInLandscapeViewModelTest.kt4
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/QSColumnsViewModelTest.kt4
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt42
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/shade/data/repository/ShadeDisplaysRepositoryTest.kt25
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeModeInteractorImplTest.kt4
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/startable/ShadeStartableTest.kt3
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/shared/plugins/PluginInstanceTest.java2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/domain/interactor/SingleNotificationChipInteractorTest.kt67
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/domain/interactor/StatusBarNotificationChipsInteractorTest.kt260
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt183
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/featurepods/media/domain/interactor/MediaControlChipInteractorTest.kt117
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/featurepods/media/ui/viewmodel/MediaControlChipViewModelTest.kt65
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/AssistantFeedbackControllerTest.java2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/DynamicChildBindControllerTest.java2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/BundleEntryTest.kt75
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/HighPriorityProviderTest.java22
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/ColorizedFgsCoordinatorTest.kt54
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerTest.kt82
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManagerTest.kt118
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/emptyshade/ui/viewmodel/EmptyShadeViewModelTest.kt14
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerImplTest.kt90
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/people/PeopleNotificationIdentifierTest.kt162
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/domain/interactor/PromotedNotificationsInteractorTest.kt156
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java11
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.kt12
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.kt8
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImplTest.kt9
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/PromotedNotificationInfoTest.java11
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModelTest.kt5
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt7
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackTest.java37
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt14
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt14
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeHomeStatusBarViewModel.kt2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorTest.kt2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/theme/HardwareColorRule.java39
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/theme/HardwareColors.java30
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java194
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioSharingStreamSliderViewModelTest.kt63
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/wallpapers/data/repository/WallpaperRepositoryImplTest.kt8
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/wallpapers/domain/interactor/WallpaperFocalAreaInteractorTest.kt116
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/wallpapers/ui/viewmodel/WallpaperFocalAreaViewModelTest.kt148
-rw-r--r--packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockLogger.kt15
-rw-r--r--packages/SystemUI/res-keyguard/drawable/pin_bouncer_confirm.xml31
-rw-r--r--packages/SystemUI/res-keyguard/drawable/pin_bouncer_delete_filled.xml25
-rw-r--r--packages/SystemUI/res-keyguard/drawable/pin_bouncer_delete_outline.xml31
-rw-r--r--packages/SystemUI/res-keyguard/layout/keyguard_bouncer_message_area.xml2
-rw-r--r--packages/SystemUI/res-keyguard/layout/keyguard_password_motion_layout.xml1
-rw-r--r--packages/SystemUI/res-keyguard/layout/keyguard_password_view.xml1
-rw-r--r--packages/SystemUI/res-keyguard/layout/keyguard_pattern_motion_layout.xml1
-rw-r--r--packages/SystemUI/res-keyguard/layout/keyguard_pattern_view.xml1
-rw-r--r--packages/SystemUI/res-keyguard/layout/keyguard_pin_motion_layout.xml1
-rw-r--r--packages/SystemUI/res-keyguard/layout/keyguard_pin_view.xml1
-rw-r--r--packages/SystemUI/res-keyguard/values/dimens.xml4
-rw-r--r--packages/SystemUI/res/drawable/ic_legacy_speaker_mute.xml25
-rw-r--r--packages/SystemUI/res/drawable/ic_legacy_speaker_on.xml25
-rw-r--r--packages/SystemUI/res/drawable/ic_legacy_volume_ringer_vibrate.xml27
-rw-r--r--packages/SystemUI/res/drawable/ic_speaker_mute.xml23
-rw-r--r--packages/SystemUI/res/drawable/ic_speaker_on.xml23
-rw-r--r--packages/SystemUI/res/drawable/ic_volume_ringer_vibrate.xml47
-rw-r--r--packages/SystemUI/res/drawable/mobile_network_type_background_updated.xml30
-rw-r--r--packages/SystemUI/res/drawable/stat_sys_ringer_silent.xml2
-rw-r--r--packages/SystemUI/res/drawable/stat_sys_ringer_vibrate.xml2
-rw-r--r--packages/SystemUI/res/layout/bluetooth_tile_dialog.xml8
-rw-r--r--packages/SystemUI/res/layout/internet_connectivity_dialog.xml2
-rw-r--r--packages/SystemUI/res/layout/notification_2025_hybrid.xml1
-rw-r--r--packages/SystemUI/res/layout/notification_2025_hybrid_conversation.xml1
-rw-r--r--packages/SystemUI/res/layout/volume_ringer_drawer_legacy.xml6
-rw-r--r--packages/SystemUI/res/values/config.xml5
-rw-r--r--packages/SystemUI/res/values/dimens.xml27
-rw-r--r--packages/SystemUI/res/values/strings.xml11
-rw-r--r--packages/SystemUI/res/values/tiles_states_strings.xml10
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardMessageAreaController.java66
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java14
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java9
-rw-r--r--packages/SystemUI/src/com/android/keyguard/NumPadAnimator.java45
-rw-r--r--packages/SystemUI/src/com/android/keyguard/NumPadButton.java28
-rw-r--r--packages/SystemUI/src/com/android/keyguard/NumPadKey.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java94
-rw-r--r--packages/SystemUI/src/com/android/systemui/activity/data/model/AppVisibilityModel.kt29
-rw-r--r--packages/SystemUI/src/com/android/systemui/activity/data/repository/ActivityManagerRepository.kt46
-rw-r--r--packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothDetailsContentManager.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothDetailsContentViewModel.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactory.kt37
-rw-r--r--packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemInteractor.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/bouncer/shared/constants/KeyguardBouncerConstants.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/KeyguardBouncerViewBinder.kt1
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSceneRepository.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalTransitionKeys.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/domain/interactor/TutorialSchedulerInteractor.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/source/MultitaskingShortcutsSource.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutHelperCategoriesInteractor.kt31
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyboard/shortcut/extensions/ShortcutKeyExtensions.kt62
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutCustomizer.kt13
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/model/ShortcutCustomizationUiState.kt1
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutCustomizationViewModel.kt25
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/repository/LockscreenSceneTransitionRepository.kt14
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt21
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractor.kt67
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/scenetransition/LockscreenSceneTransitionInteractor.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModel.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToDreamingTransitionViewModel.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt26
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataManager.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessor.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt27
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/util/MediaViewModelListUpdateCallback.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapterBase.java (renamed from packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java)312
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapterLegacy.java (renamed from packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java)311
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialog.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/modes/shared/ModesUi.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayContentViewModel.kt22
-rw-r--r--packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogV2.kt113
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeViewModel.kt15
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tileimpl/SubtitleArrayMapping.kt1
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateLegacy.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/data/repository/SceneContainerRepository.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt84
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/domain/resolver/HomeSceneFamilyResolver.kt15
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt37
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/shared/logger/SceneLogger.kt45
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneDataSource.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneDataSourceDelegator.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt37
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayChangeLatencyTracker.kt13
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeDisplaysRepository.kt16
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/display/ShadeDisplayPolicy.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/display/StatusBarTouchShadeDisplayPolicy.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractor.kt28
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImpl.kt36
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeModeInteractor.kt14
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/shared/flag/ShadeWindowGoesAround.kt19
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/interactor/SingleNotificationChipInteractor.kt44
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/interactor/StatusBarNotificationChipsInteractor.kt60
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/model/NotificationChipModel.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModel.kt48
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/binder/OngoingActivityChipBinder.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/OngoingActivityChip.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/OngoingActivityChips.kt12
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/featurepods/media/MediaControlChipStartable.kt43
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/featurepods/media/domain/interactor/MediaControlChipInteractor.kt75
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/featurepods/media/ui/viewmodel/MediaControlChipViewModel.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/shared/model/PopupChipModel.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/ui/compose/StatusBarPopup.kt65
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/ui/compose/StatusBarPopupChip.kt16
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/ui/compose/StatusBarPopupChipsContainer.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/ui/viewmodel/StatusBarPopupChipsViewModel.kt74
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/BundleEntry.java63
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/EntryAdapter.java44
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifInflaterImpl.java14
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java42
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/PipelineEntry.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ColorizedFgsCoordinator.java54
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/HighPriorityProvider.java44
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManager.java15
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerImpl.java104
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManager.java28
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManagerImpl.java29
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationStackOptionalModule.kt41
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/AvalancheController.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerImpl.java38
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpTouchHelper.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleNotificationIdentifier.kt25
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/AODPromotedNotification.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/domain/interactor/AODPromotedNotificationInteractor.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/domain/interactor/PromotedNotificationsInteractor.kt143
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/AsyncRowInflater.kt90
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/BasicRowInflater.kt51
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java96
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java21
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java48
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinder.java21
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PromotedNotificationInfo.java14
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStage.java36
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowInflaterTask.java72
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/icon/AppIconProvider.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java14
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLogger.kt40
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/shared/model/ShadeScrimBounds.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java13
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt43
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileView.kt46
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/composable/StatusBarRoot.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModel.kt24
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java122
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/kotlin/SysUICoroutinesModule.kt16
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/settings/repository/SecureSettingsForUserRepository.kt35
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/settings/repository/SettingsForUserRepository.kt66
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/settings/repository/UserAwareSettingsRepository.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java13
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/binder/VolumeDialogRingerViewBinder.kt22
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioSharingStreamSliderViewModel.kt23
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/CastVolumeSliderViewModel.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/panel/shared/VolumePanelLogger.kt52
-rw-r--r--packages/SystemUI/src/com/android/systemui/wallpapers/data/repository/NoopWallpaperRepository.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/wallpapers/data/repository/WallpaperFocalAreaRepository.kt18
-rw-r--r--packages/SystemUI/src/com/android/systemui/wallpapers/data/repository/WallpaperRepository.kt125
-rw-r--r--packages/SystemUI/src/com/android/systemui/wallpapers/domain/interactor/WallpaperFocalAreaInteractor.kt62
-rw-r--r--packages/SystemUI/src/com/android/systemui/wallpapers/ui/viewmodel/WallpaperFocalAreaViewModel.kt30
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/OnTeardownRuleTest.kt (renamed from packages/SystemUI/multivalentTests/src/com/android/systemui/OnTeardownRuleTest.kt)0
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/accessibility/IMagnificationConnectionTest.java (renamed from packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/IMagnificationConnectionTest.java)0
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/animation/TextAnimatorTest.kt28
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt (renamed from packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt)0
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingDialogDelegateTest.kt (renamed from packages/SystemUI/multivalentTests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingDialogDelegateTest.kt)0
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothDetailsContentViewModelTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactoryTest.kt109
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemInteractorTest.kt17
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt (renamed from packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt)0
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/dump/LogEulogizerTest.kt (renamed from packages/SystemUI/multivalentTests/src/com/android/systemui/dump/LogEulogizerTest.kt)0
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/education/domain/ui/view/ContextualEduUiCoordinatorTest.kt (renamed from packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/ui/view/ContextualEduUiCoordinatorTest.kt)0
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModelTest.kt (renamed from packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModelTest.kt)0
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/lifecycle/HydratorTest.kt (renamed from packages/SystemUI/multivalentTests/src/com/android/systemui/lifecycle/HydratorTest.kt)0
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java3
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/QSFragmentComposeTest.kt (renamed from packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/QSFragmentComposeTest.kt)0
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/sensorprivacy/SensorUseStartedActivityTest.kt (renamed from packages/SystemUI/multivalentTests/src/com/android/systemui/sensorprivacy/SensorUseStartedActivityTest.kt)0
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt845
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplTest.java (renamed from packages/SystemUI/multivalentTests/src/com/android/systemui/shade/QuickSettingsControllerImplTest.java)0
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shadow/DoubleShadowTextClockTest.kt (renamed from packages/SystemUI/multivalentTests/src/com/android/systemui/shadow/DoubleShadowTextClockTest.kt)0
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shared/clocks/AnimatableClockViewTest.kt (renamed from packages/SystemUI/multivalentTests/src/com/android/systemui/shared/clocks/AnimatableClockViewTest.kt)55
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shared/condition/CombinedConditionTest.kt (renamed from packages/SystemUI/multivalentTests/src/com/android/systemui/shared/condition/CombinedConditionTest.kt)0
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shared/system/UncaughtExceptionPreHandlerTest.kt (renamed from packages/SystemUI/multivalentTests/src/com/android/systemui/shared/system/UncaughtExceptionPreHandlerTest.kt)0
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/featurepods/popups/ui/viewmodel/StatusBarPopupChipsViewModelTest.kt (renamed from packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/featurepods/popups/ui/viewmodel/StatusBarPopupChipsViewModelTest.kt)53
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/layout/StatusBarBoundsProviderTest.kt (renamed from packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/layout/StatusBarBoundsProviderTest.kt)0
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java148
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java (renamed from packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java)0
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/BigPictureIconManagerTest.kt (renamed from packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/BigPictureIconManagerTest.kt)0
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java51
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.kt38
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerWithScenesTest.kt16
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/RowImageInflaterTest.kt (renamed from packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/RowImageInflaterTest.kt)0
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.kt (renamed from packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.kt)0
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java107
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java (renamed from packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java)0
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt24
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TouchableRegionViewControllerTest.kt (renamed from packages/SystemUI/multivalentTests/src/com/android/systemui/temporarydisplay/TouchableRegionViewControllerTest.kt)0
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/unfold/FoldAodAnimationControllerTest.kt (renamed from packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/FoldAodAnimationControllerTest.kt)0
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldLatencyTrackerTest.kt (renamed from packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/UnfoldLatencyTrackerTest.kt)0
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProviderTest.kt (renamed from packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProviderTest.kt)0
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/unfold/progress/UnfoldRemoteFilterTest.kt (renamed from packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/progress/UnfoldRemoteFilterTest.kt)0
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/usb/UsbPermissionActivityTest.kt (renamed from packages/SystemUI/multivalentTests/src/com/android/systemui/usb/UsbPermissionActivityTest.kt)0
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/user/CreateUserActivityTest.kt (renamed from packages/SystemUI/multivalentTests/src/com/android/systemui/user/CreateUserActivityTest.kt)0
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/util/kotlin/FlowUtilTests.kt (renamed from packages/SystemUI/multivalentTests/src/com/android/systemui/util/kotlin/FlowUtilTests.kt)0
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/util/kotlin/PackageManagerExtComponentEnabledTest.kt (renamed from packages/SystemUI/multivalentTests/src/com/android/systemui/util/kotlin/PackageManagerExtComponentEnabledTest.kt)0
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/util/sensors/AsyncSensorManagerTest.java (renamed from packages/SystemUI/multivalentTests/src/com/android/systemui/util/sensors/AsyncSensorManagerTest.java)0
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/wallpapers/GradientColorWallpaperTest.kt (renamed from packages/SystemUI/multivalentTests/src/com/android/systemui/wallpapers/GradientColorWallpaperTest.kt)0
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/activity/data/repository/ActivityManagerRepositoryKosmos.kt56
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractorKosmos.kt4
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelKosmos.kt4
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/model/FakeSceneDataSource.kt6
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeDisplayChangeLatencyTrackerKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/ShadeDisplaysRepositoryKosmos.kt5
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractorKosmos.kt5
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/NotificationsShadeOverlayContentViewModelKosmos.kt4
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/featurepods/popups/ui/viewmodel/StatusBarPopupChipsViewModelKosmos.kt13
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/NotificationEntryBuilderKosmos.kt90
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/NotifPipelineKosmos.kt5
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractorKosmos.kt14
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/promoted/domain/interactor/AODPromotedNotificationInteractorKosmos.kt3
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/promoted/domain/interactor/PromotedNotificationsInteractorKosmos.kt33
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowBuilder.kt6
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinderKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelKosmos.kt4
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/data/repository/SecureSettingsForUserRepositoryKosmos.kt28
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/util/time/FakeSystemClockKosmos.kt3
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioSharingStreamSliderViewModelKosmos.kt4
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/CastVolumeSliderViewModelKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/wallpapers/data/repository/FakeWallpaperFocalAreaRepository.kt9
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/wallpapers/data/repository/FakeWallpaperRepository.kt8
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/wallpapers/data/repository/WallpaperRepositoryKosmos.kt3
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/wallpapers/domain/interactor/WallpaperFocalAreaInteractor.kt8
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/wallpapers/ui/viewmodel/WallpaperFocalAreaViewModel.kt6
-rw-r--r--ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodMethodCallLogger.java229
-rw-r--r--ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java17
-rw-r--r--ravenwood/runtime-jni/ravenwood_initializer.cpp77
-rw-r--r--ravenwood/texts/ravenwood-standard-options.txt4
-rw-r--r--ravenwood/tools/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostTestUtils.java43
-rw-r--r--ravenwood/tools/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt1
-rw-r--r--ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/DefaultHookInjectingFilter.kt51
-rw-r--r--ravenwood/tools/hoststubgen/test-tiny-framework/golden-output.RELEASE_TARGET_JAVA_21/13-hoststubgen-test-tiny-framework-host-ext-dump.txt120
-rw-r--r--ravenwood/tools/hoststubgen/test-tiny-framework/golden-output/13-hoststubgen-test-tiny-framework-host-ext-dump.txt230
-rw-r--r--services/accessibility/accessibility.aconfig7
-rw-r--r--services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java55
-rw-r--r--services/appfunctions/java/com/android/server/appfunctions/CallerValidator.java32
-rw-r--r--services/appfunctions/java/com/android/server/appfunctions/CallerValidatorImpl.java67
-rw-r--r--services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java56
-rw-r--r--services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java49
-rw-r--r--services/contextualsearch/java/com/android/server/contextualsearch/ContextualSearchManagerService.java19
-rw-r--r--services/core/java/com/android/server/UiModeManagerService.java15
-rw-r--r--services/core/java/com/android/server/am/AppStartInfoTracker.java40
-rw-r--r--services/core/java/com/android/server/appop/DiscreteOpsSqlRegistry.java4
-rw-r--r--services/core/java/com/android/server/audio/AudioDeviceBroker.java39
-rw-r--r--services/core/java/com/android/server/audio/AudioDeviceInventory.java73
-rw-r--r--services/core/java/com/android/server/audio/AudioService.java155
-rw-r--r--services/core/java/com/android/server/audio/AudioSystemAdapter.java4
-rw-r--r--services/core/java/com/android/server/audio/BtHelper.java29
-rw-r--r--services/core/java/com/android/server/biometrics/BiometricCameraManagerImpl.java18
-rw-r--r--services/core/java/com/android/server/display/DisplayManagerService.java61
-rw-r--r--services/core/java/com/android/server/display/LogicalDisplayMapper.java12
-rw-r--r--services/core/java/com/android/server/display/VirtualDisplayAdapter.java33
-rw-r--r--services/core/java/com/android/server/display/feature/DisplayManagerFlags.java14
-rw-r--r--services/core/java/com/android/server/display/feature/display_flags.aconfig14
-rw-r--r--services/core/java/com/android/server/dreams/DreamManagerService.java3
-rw-r--r--services/core/java/com/android/server/hdmi/AudioDeviceVolumeManagerWrapper.java8
-rw-r--r--services/core/java/com/android/server/hdmi/DefaultAudioDeviceVolumeManagerWrapper.java14
-rw-r--r--services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java3
-rw-r--r--services/core/java/com/android/server/hdmi/HdmiControlService.java8
-rw-r--r--services/core/java/com/android/server/input/InputManagerInternal.java38
-rw-r--r--services/core/java/com/android/server/input/InputManagerService.java60
-rw-r--r--services/core/java/com/android/server/input/NativeInputManagerService.java13
-rw-r--r--services/core/java/com/android/server/inputmethod/InputMethodManagerService.java29
-rw-r--r--services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java26
-rw-r--r--services/core/java/com/android/server/media/quality/MediaQualityService.java57
-rw-r--r--services/core/java/com/android/server/notification/ManagedServices.java293
-rw-r--r--services/core/java/com/android/server/notification/NotificationManagerService.java230
-rw-r--r--services/core/java/com/android/server/notification/NotificationRecordLogger.java25
-rw-r--r--services/core/java/com/android/server/notification/NotificationRecordLoggerImpl.java4
-rw-r--r--services/core/java/com/android/server/notification/NotificationShellCmd.java9
-rw-r--r--services/core/java/com/android/server/notification/PreferencesHelper.java65
-rw-r--r--services/core/java/com/android/server/notification/ZenModeEventLogger.java117
-rw-r--r--services/core/java/com/android/server/notification/ZenModeFiltering.java11
-rw-r--r--services/core/java/com/android/server/notification/ZenModeHelper.java622
-rw-r--r--services/core/java/com/android/server/notification/flags.aconfig7
-rw-r--r--services/core/java/com/android/server/pm/BackgroundInstallControlService.java35
-rw-r--r--services/core/java/com/android/server/pm/InstallPackageHelper.java59
-rw-r--r--services/core/java/com/android/server/pm/PackageInstallerService.java28
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerShellCommand.java11
-rw-r--r--services/core/java/com/android/server/pm/VerifyingSession.java3
-rw-r--r--services/core/java/com/android/server/policy/PhoneWindowManager.java35
-rw-r--r--services/core/java/com/android/server/power/PowerManagerService.java12
-rw-r--r--services/core/java/com/android/server/resources/ResourcesManagerShellCommand.java1
-rw-r--r--services/core/java/com/android/server/rollback/Rollback.java5
-rw-r--r--services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java17
-rw-r--r--services/core/java/com/android/server/rollback/RollbackStore.java12
-rw-r--r--services/core/java/com/android/server/security/AttestationVerificationPeerDeviceVerifier.java6
-rw-r--r--services/core/java/com/android/server/security/CertificateRevocationStatusManager.java45
-rw-r--r--services/core/java/com/android/server/security/FileIntegrityService.java5
-rw-r--r--services/core/java/com/android/server/wm/ActivityMetricsLogger.java3
-rw-r--r--services/core/java/com/android/server/wm/ActivityRecord.java76
-rw-r--r--services/core/java/com/android/server/wm/ActivityTaskSupervisor.java16
-rw-r--r--services/core/java/com/android/server/wm/AppCompatController.java8
-rw-r--r--services/core/java/com/android/server/wm/AppCompatLetterboxPolicy.java39
-rw-r--r--services/core/java/com/android/server/wm/AppCompatRoundedCorners.java8
-rw-r--r--services/core/java/com/android/server/wm/AppCompatSandboxingPolicy.java65
-rw-r--r--services/core/java/com/android/server/wm/AppCompatSizeCompatModePolicy.java8
-rw-r--r--services/core/java/com/android/server/wm/AppCompatUtils.java12
-rw-r--r--services/core/java/com/android/server/wm/AppTransition.java21
-rw-r--r--services/core/java/com/android/server/wm/BackNavigationController.java19
-rw-r--r--services/core/java/com/android/server/wm/ConfigurationContainer.java12
-rw-r--r--services/core/java/com/android/server/wm/DeferredDisplayUpdater.java8
-rw-r--r--services/core/java/com/android/server/wm/DesktopModeHelper.java38
-rw-r--r--services/core/java/com/android/server/wm/DesktopModeLaunchParamsModifier.java9
-rw-r--r--services/core/java/com/android/server/wm/DisplayContent.java108
-rw-r--r--services/core/java/com/android/server/wm/DisplayWindowSettings.java16
-rw-r--r--services/core/java/com/android/server/wm/KeyguardController.java24
-rw-r--r--services/core/java/com/android/server/wm/RootWindowContainer.java6
-rw-r--r--services/core/java/com/android/server/wm/Task.java5
-rw-r--r--services/core/java/com/android/server/wm/TaskFragment.java4
-rw-r--r--services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java2
-rw-r--r--services/core/java/com/android/server/wm/Transition.java5
-rw-r--r--services/core/java/com/android/server/wm/TransitionController.java13
-rw-r--r--services/core/java/com/android/server/wm/WallpaperController.java39
-rw-r--r--services/core/java/com/android/server/wm/WindowContainer.java74
-rw-r--r--services/core/java/com/android/server/wm/WindowOrganizerController.java2
-rw-r--r--services/core/java/com/android/server/wm/WindowState.java7
-rw-r--r--services/core/jni/com_android_server_input_InputManagerService.cpp37
-rw-r--r--services/java/com/android/server/SystemServer.java36
-rw-r--r--services/supervision/java/com/android/server/supervision/SupervisionService.java40
-rw-r--r--services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/InputMethodServiceTest.java500
-rw-r--r--services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/src/com/android/apps/inputmethod/simpleime/ims/InputMethodServiceWrapper.java6
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java11
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java68
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/crashrecovery/Android.bp8
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/rollback/Android.bp8
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/rollback/RollbackPackageHealthObserverTest.java22
-rw-r--r--services/tests/powerservicetests/src/com/android/server/power/NotifierTest.java36
-rw-r--r--services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java43
-rw-r--r--services/tests/powerstatstests/res/raw/battery-history.zipbin0 -> 272063 bytes
-rw-r--r--services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsProviderPerfTest.java179
-rw-r--r--services/tests/servicestests/src/com/android/server/audio/AudioDeviceInventoryTest.java4
-rw-r--r--services/tests/servicestests/src/com/android/server/audio/NoOpAudioSystemAdapter.java6
-rw-r--r--services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java6
-rw-r--r--services/tests/servicestests/src/com/android/server/hdmi/BaseAbsoluteVolumeBehaviorTest.java2
-rw-r--r--services/tests/servicestests/src/com/android/server/hdmi/BaseTvToAudioSystemAvbTest.java6
-rw-r--r--services/tests/servicestests/src/com/android/server/hdmi/FakeAudioFramework.java8
-rw-r--r--services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java2
-rw-r--r--services/tests/uiservicestests/Android.bp1
-rw-r--r--services/tests/uiservicestests/src/android/app/NotificationManagerZenTest.java12
-rw-r--r--services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java4
-rw-r--r--services/tests/uiservicestests/src/com/android/server/UiServiceTestCase.java4
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/DefaultDeviceEffectsApplierTest.java24
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java496
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java221
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java66
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/RankingHelperTest.java8
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/ZenAdaptersTest.java22
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/ZenDeviceEffectsTest.java7
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java57
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/ZenModeDiffTest.java88
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/ZenModeFilteringTest.java3
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java795
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/ZenPolicyTest.java35
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java2
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java10
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java26
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java4
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/AppCompatLetterboxPolicyTest.java60
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java8
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/DesktopModeHelperTest.java50
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java71
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java43
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java3
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java27
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java54
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowTracingPerfettoTest.java39
-rw-r--r--telephony/java/android/telephony/CarrierConfigManager.java24
-rw-r--r--telephony/java/android/telephony/NetworkRegistrationInfo.java3
-rw-r--r--telephony/java/android/telephony/ServiceState.java3
-rw-r--r--telephony/java/android/telephony/TelephonyDisplayInfo.java5
-rw-r--r--telephony/java/android/telephony/TelephonyManager.java6
-rw-r--r--telephony/java/android/telephony/data/ApnSetting.java2
-rw-r--r--telephony/java/android/telephony/satellite/SatelliteManager.java57
-rw-r--r--telephony/java/com/android/internal/telephony/ITelephony.aidl11
-rw-r--r--tests/AppJankTest/res/values/strings.xml3
-rw-r--r--tests/AppJankTest/src/android/app/jank/tests/IntegrationTests.java3
-rw-r--r--tests/AppJankTest/src/android/app/jank/tests/JankTrackerActivity.java26
-rw-r--r--tests/AttestationVerificationTest/src/com/android/server/security/CertificateRevocationStatusManagerTest.java66
-rw-r--r--tests/PackageWatchdog/Android.bp14
-rw-r--r--tests/PackageWatchdog/src/com/android/server/CrashRecoveryTest.java282
-rw-r--r--tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java248
-rw-r--r--tests/utils/testutils/java/android/os/test/TestLooper.java168
-rw-r--r--tests/vcn/Android.bp10
-rw-r--r--tests/vcn/AndroidManifest.xml5
-rw-r--r--tests/vcn/AndroidTest.xml10
-rw-r--r--tests/vcn/java/android/net/vcn/VcnCellUnderlyingNetworkTemplateTest.java13
-rw-r--r--tests/vcn/java/android/net/vcn/VcnConfigTest.java10
-rw-r--r--tests/vcn/java/android/net/vcn/VcnGatewayConnectionConfigTest.java10
-rw-r--r--tests/vcn/java/android/net/vcn/VcnManagerTest.java12
-rw-r--r--tests/vcn/java/android/net/vcn/VcnTransportInfoTest.java12
-rw-r--r--tests/vcn/java/android/net/vcn/VcnUnderlyingNetworkPolicyTest.java12
-rw-r--r--tests/vcn/java/android/net/vcn/VcnUnderlyingNetworkSpecifierTest.java10
-rw-r--r--tests/vcn/java/android/net/vcn/VcnUtilsTest.java12
-rw-r--r--tests/vcn/java/android/net/vcn/VcnWifiUnderlyingNetworkTemplateTest.java13
-rw-r--r--tests/vcn/java/android/net/vcn/persistablebundleutils/EapSessionConfigUtilsTest.java10
-rw-r--r--tests/vcn/java/android/net/vcn/persistablebundleutils/IkeIdentificationUtilsTest.java10
-rw-r--r--tests/vcn/java/android/net/vcn/persistablebundleutils/IkeSessionParamsUtilsTest.java9
-rw-r--r--tests/vcn/java/android/net/vcn/persistablebundleutils/IkeTrafficSelectorUtilsTest.java10
-rw-r--r--tests/vcn/java/android/net/vcn/persistablebundleutils/SaProposalUtilsTest.java10
-rw-r--r--tests/vcn/java/android/net/vcn/persistablebundleutils/TunnelConnectionParamsUtilsTest.java10
-rw-r--r--tests/vcn/java/android/net/vcn/persistablebundleutils/TunnelModeChildSessionParamsUtilsTest.java10
-rw-r--r--tests/vcn/java/android/net/vcn/util/MtuUtilsTest.java10
-rw-r--r--tests/vcn/java/android/net/vcn/util/PersistableBundleUtilsTest.java10
-rw-r--r--tests/vcn/java/com/android/server/VcnManagementServiceTest.java10
-rw-r--r--tests/vcn/java/com/android/server/vcn/TelephonySubscriptionTrackerTest.java10
-rw-r--r--tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java10
-rw-r--r--tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectingStateTest.java11
-rw-r--r--tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectedStateTest.java11
-rw-r--r--tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectingStateTest.java12
-rw-r--r--tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionRetryTimeoutStateTest.java12
-rw-r--r--tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTest.java10
-rw-r--r--tests/vcn/java/com/android/server/vcn/VcnNetworkProviderTest.java10
-rw-r--r--tests/vcn/java/com/android/server/vcn/VcnTest.java11
-rw-r--r--tests/vcn/java/com/android/server/vcn/routeselection/IpSecPacketLossDetectorTest.java11
-rw-r--r--tests/vcn/java/com/android/server/vcn/routeselection/NetworkPriorityClassifierTest.java12
-rw-r--r--tests/vcn/java/com/android/server/vcn/routeselection/UnderlyingNetworkControllerTest.java11
-rw-r--r--tests/vcn/java/com/android/server/vcn/routeselection/UnderlyingNetworkEvaluatorTest.java11
-rw-r--r--tools/aapt2/Debug.cpp28
896 files changed, 24660 insertions, 10049 deletions
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index e5c059ecbfb7..8b95679f318f 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -230,6 +230,17 @@ java_aconfig_library {
defaults: ["framework-minus-apex-aconfig-java-defaults"],
}
+java_aconfig_library {
+ name: "telephony_flags_core_java_exported_lib",
+ aconfig_declarations: "telephony_flags",
+ mode: "exported",
+ min_sdk_version: "30",
+ apex_available: [
+ "com.android.wifi",
+ ],
+ defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
+
cc_aconfig_library {
name: "telephony_flags_c_lib",
aconfig_declarations: "telephony_flags",
diff --git a/core/api/current.txt b/core/api/current.txt
index dc17fe79a4e0..f5dcf2de4c51 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -100,6 +100,9 @@ package android {
field public static final String EXECUTE_APP_ACTION = "android.permission.EXECUTE_APP_ACTION";
field @FlaggedApi("android.app.appfunctions.flags.enable_app_function_manager") public static final String EXECUTE_APP_FUNCTIONS = "android.permission.EXECUTE_APP_FUNCTIONS";
field public static final String EXPAND_STATUS_BAR = "android.permission.EXPAND_STATUS_BAR";
+ field @FlaggedApi("android.xr.xr_manifest_entries") public static final String EYE_TRACKING_COARSE = "android.permission.EYE_TRACKING_COARSE";
+ field @FlaggedApi("android.xr.xr_manifest_entries") public static final String EYE_TRACKING_FINE = "android.permission.EYE_TRACKING_FINE";
+ field @FlaggedApi("android.xr.xr_manifest_entries") public static final String FACE_TRACKING = "android.permission.FACE_TRACKING";
field public static final String FACTORY_TEST = "android.permission.FACTORY_TEST";
field public static final String FOREGROUND_SERVICE = "android.permission.FOREGROUND_SERVICE";
field public static final String FOREGROUND_SERVICE_CAMERA = "android.permission.FOREGROUND_SERVICE_CAMERA";
@@ -120,6 +123,8 @@ package android {
field public static final String GET_PACKAGE_SIZE = "android.permission.GET_PACKAGE_SIZE";
field @Deprecated public static final String GET_TASKS = "android.permission.GET_TASKS";
field public static final String GLOBAL_SEARCH = "android.permission.GLOBAL_SEARCH";
+ field @FlaggedApi("android.xr.xr_manifest_entries") public static final String HAND_TRACKING = "android.permission.HAND_TRACKING";
+ field @FlaggedApi("android.xr.xr_manifest_entries") public static final String HEAD_TRACKING = "android.permission.HEAD_TRACKING";
field public static final String HIDE_OVERLAY_WINDOWS = "android.permission.HIDE_OVERLAY_WINDOWS";
field public static final String HIGH_SAMPLING_RATE_SENSORS = "android.permission.HIGH_SAMPLING_RATE_SENSORS";
field public static final String INSTALL_LOCATION_PROVIDER = "android.permission.INSTALL_LOCATION_PROVIDER";
@@ -295,6 +300,8 @@ package android {
field public static final String REQUEST_PASSWORD_COMPLEXITY = "android.permission.REQUEST_PASSWORD_COMPLEXITY";
field @Deprecated public static final String RESTART_PACKAGES = "android.permission.RESTART_PACKAGES";
field public static final String RUN_USER_INITIATED_JOBS = "android.permission.RUN_USER_INITIATED_JOBS";
+ field @FlaggedApi("android.xr.xr_manifest_entries") public static final String SCENE_UNDERSTANDING_COARSE = "android.permission.SCENE_UNDERSTANDING_COARSE";
+ field @FlaggedApi("android.xr.xr_manifest_entries") public static final String SCENE_UNDERSTANDING_FINE = "android.permission.SCENE_UNDERSTANDING_FINE";
field public static final String SCHEDULE_EXACT_ALARM = "android.permission.SCHEDULE_EXACT_ALARM";
field public static final String SEND_RESPOND_VIA_MESSAGE = "android.permission.SEND_RESPOND_VIA_MESSAGE";
field public static final String SEND_SMS = "android.permission.SEND_SMS";
@@ -362,6 +369,8 @@ package android {
field public static final String SENSORS = "android.permission-group.SENSORS";
field public static final String SMS = "android.permission-group.SMS";
field public static final String STORAGE = "android.permission-group.STORAGE";
+ field @FlaggedApi("android.xr.xr_manifest_entries") public static final String XR_TRACKING = "android.permission-group.XR_TRACKING";
+ field @FlaggedApi("android.xr.xr_manifest_entries") public static final String XR_TRACKING_SENSITIVE = "android.permission-group.XR_TRACKING_SENSITIVE";
}
public final class R {
@@ -5479,37 +5488,37 @@ package android.app {
method public android.net.Uri getConditionId();
method @Nullable public android.content.ComponentName getConfigurationActivity();
method public long getCreationTime();
- method @FlaggedApi("android.app.modes_api") @Nullable public android.service.notification.ZenDeviceEffects getDeviceEffects();
- method @FlaggedApi("android.app.modes_api") @DrawableRes public int getIconResId();
+ method @Nullable public android.service.notification.ZenDeviceEffects getDeviceEffects();
+ method @DrawableRes public int getIconResId();
method public int getInterruptionFilter();
method public String getName();
method public android.content.ComponentName getOwner();
- method @FlaggedApi("android.app.modes_api") @Nullable public String getTriggerDescription();
- method @FlaggedApi("android.app.modes_api") public int getType();
+ method @Nullable public String getTriggerDescription();
+ method public int getType();
method @Nullable public android.service.notification.ZenPolicy getZenPolicy();
method public boolean isEnabled();
- method @FlaggedApi("android.app.modes_api") public boolean isManualInvocationAllowed();
+ method public boolean isManualInvocationAllowed();
method public void setConditionId(android.net.Uri);
method public void setConfigurationActivity(@Nullable android.content.ComponentName);
- method @FlaggedApi("android.app.modes_api") public void setDeviceEffects(@Nullable android.service.notification.ZenDeviceEffects);
+ method public void setDeviceEffects(@Nullable android.service.notification.ZenDeviceEffects);
method public void setEnabled(boolean);
method public void setInterruptionFilter(int);
method public void setName(String);
method public void setZenPolicy(@Nullable android.service.notification.ZenPolicy);
method public void writeToParcel(android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.app.AutomaticZenRule> CREATOR;
- field @FlaggedApi("android.app.modes_api") public static final int TYPE_BEDTIME = 3; // 0x3
- field @FlaggedApi("android.app.modes_api") public static final int TYPE_DRIVING = 4; // 0x4
- field @FlaggedApi("android.app.modes_api") public static final int TYPE_IMMERSIVE = 5; // 0x5
- field @FlaggedApi("android.app.modes_api") public static final int TYPE_MANAGED = 7; // 0x7
- field @FlaggedApi("android.app.modes_api") public static final int TYPE_OTHER = 0; // 0x0
- field @FlaggedApi("android.app.modes_api") public static final int TYPE_SCHEDULE_CALENDAR = 2; // 0x2
- field @FlaggedApi("android.app.modes_api") public static final int TYPE_SCHEDULE_TIME = 1; // 0x1
- field @FlaggedApi("android.app.modes_api") public static final int TYPE_THEATER = 6; // 0x6
- field @FlaggedApi("android.app.modes_api") public static final int TYPE_UNKNOWN = -1; // 0xffffffff
- }
-
- @FlaggedApi("android.app.modes_api") public static final class AutomaticZenRule.Builder {
+ field public static final int TYPE_BEDTIME = 3; // 0x3
+ field public static final int TYPE_DRIVING = 4; // 0x4
+ field public static final int TYPE_IMMERSIVE = 5; // 0x5
+ field public static final int TYPE_MANAGED = 7; // 0x7
+ field public static final int TYPE_OTHER = 0; // 0x0
+ field public static final int TYPE_SCHEDULE_CALENDAR = 2; // 0x2
+ field public static final int TYPE_SCHEDULE_TIME = 1; // 0x1
+ field public static final int TYPE_THEATER = 6; // 0x6
+ field public static final int TYPE_UNKNOWN = -1; // 0xffffffff
+ }
+
+ public static final class AutomaticZenRule.Builder {
ctor public AutomaticZenRule.Builder(@NonNull android.app.AutomaticZenRule);
ctor public AutomaticZenRule.Builder(@NonNull String, @NonNull android.net.Uri);
method @NonNull public android.app.AutomaticZenRule build();
@@ -7127,7 +7136,7 @@ package android.app {
public class NotificationManager {
method public String addAutomaticZenRule(android.app.AutomaticZenRule);
- method @FlaggedApi("android.app.modes_api") public boolean areAutomaticZenRulesUserManaged();
+ method public boolean areAutomaticZenRulesUserManaged();
method @Deprecated public boolean areBubblesAllowed();
method public boolean areBubblesEnabled();
method public boolean areNotificationsEnabled();
@@ -7147,7 +7156,7 @@ package android.app {
method public void deleteNotificationChannelGroup(String);
method public android.service.notification.StatusBarNotification[] getActiveNotifications();
method public android.app.AutomaticZenRule getAutomaticZenRule(String);
- method @FlaggedApi("android.app.modes_api") public int getAutomaticZenRuleState(@NonNull String);
+ method public int getAutomaticZenRuleState(@NonNull String);
method public java.util.Map<java.lang.String,android.app.AutomaticZenRule> getAutomaticZenRules();
method public int getBubblePreference();
method @NonNull public android.app.NotificationManager.Policy getConsolidatedNotificationPolicy();
@@ -7176,14 +7185,14 @@ package android.app {
field public static final String ACTION_APP_BLOCK_STATE_CHANGED = "android.app.action.APP_BLOCK_STATE_CHANGED";
field public static final String ACTION_AUTOMATIC_ZEN_RULE = "android.app.action.AUTOMATIC_ZEN_RULE";
field public static final String ACTION_AUTOMATIC_ZEN_RULE_STATUS_CHANGED = "android.app.action.AUTOMATIC_ZEN_RULE_STATUS_CHANGED";
- field @FlaggedApi("android.app.modes_api") public static final String ACTION_CONSOLIDATED_NOTIFICATION_POLICY_CHANGED = "android.app.action.CONSOLIDATED_NOTIFICATION_POLICY_CHANGED";
+ field public static final String ACTION_CONSOLIDATED_NOTIFICATION_POLICY_CHANGED = "android.app.action.CONSOLIDATED_NOTIFICATION_POLICY_CHANGED";
field public static final String ACTION_INTERRUPTION_FILTER_CHANGED = "android.app.action.INTERRUPTION_FILTER_CHANGED";
field public static final String ACTION_NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED = "android.app.action.NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED";
field public static final String ACTION_NOTIFICATION_CHANNEL_GROUP_BLOCK_STATE_CHANGED = "android.app.action.NOTIFICATION_CHANNEL_GROUP_BLOCK_STATE_CHANGED";
field public static final String ACTION_NOTIFICATION_POLICY_ACCESS_GRANTED_CHANGED = "android.app.action.NOTIFICATION_POLICY_ACCESS_GRANTED_CHANGED";
field public static final String ACTION_NOTIFICATION_POLICY_CHANGED = "android.app.action.NOTIFICATION_POLICY_CHANGED";
- field @FlaggedApi("android.app.modes_api") public static final int AUTOMATIC_RULE_STATUS_ACTIVATED = 4; // 0x4
- field @FlaggedApi("android.app.modes_api") public static final int AUTOMATIC_RULE_STATUS_DEACTIVATED = 5; // 0x5
+ field public static final int AUTOMATIC_RULE_STATUS_ACTIVATED = 4; // 0x4
+ field public static final int AUTOMATIC_RULE_STATUS_DEACTIVATED = 5; // 0x5
field public static final int AUTOMATIC_RULE_STATUS_DISABLED = 2; // 0x2
field public static final int AUTOMATIC_RULE_STATUS_ENABLED = 1; // 0x1
field public static final int AUTOMATIC_RULE_STATUS_REMOVED = 3; // 0x3
@@ -7197,7 +7206,7 @@ package android.app {
field public static final String EXTRA_BLOCKED_STATE = "android.app.extra.BLOCKED_STATE";
field public static final String EXTRA_NOTIFICATION_CHANNEL_GROUP_ID = "android.app.extra.NOTIFICATION_CHANNEL_GROUP_ID";
field public static final String EXTRA_NOTIFICATION_CHANNEL_ID = "android.app.extra.NOTIFICATION_CHANNEL_ID";
- field @FlaggedApi("android.app.modes_api") public static final String EXTRA_NOTIFICATION_POLICY = "android.app.extra.NOTIFICATION_POLICY";
+ field public static final String EXTRA_NOTIFICATION_POLICY = "android.app.extra.NOTIFICATION_POLICY";
field public static final int IMPORTANCE_DEFAULT = 3; // 0x3
field public static final int IMPORTANCE_HIGH = 4; // 0x4
field public static final int IMPORTANCE_LOW = 2; // 0x2
@@ -17612,6 +17621,7 @@ package android.graphics {
method public void setIntUniform(@NonNull String, int, int, int);
method public void setIntUniform(@NonNull String, int, int, int, int);
method public void setIntUniform(@NonNull String, @NonNull int[]);
+ method @FlaggedApi("com.android.graphics.hwui.flags.shader_color_space") public void setWorkingColorSpace(@Nullable android.graphics.ColorSpace);
}
@FlaggedApi("com.android.graphics.hwui.flags.runtime_color_filters_blenders") public class RuntimeXfermode extends android.graphics.Xfermode {
@@ -38271,7 +38281,7 @@ package android.provider {
field public static final String ACTION_APP_OPEN_BY_DEFAULT_SETTINGS = "android.settings.APP_OPEN_BY_DEFAULT_SETTINGS";
field public static final String ACTION_APP_SEARCH_SETTINGS = "android.settings.APP_SEARCH_SETTINGS";
field public static final String ACTION_APP_USAGE_SETTINGS = "android.settings.action.APP_USAGE_SETTINGS";
- field @FlaggedApi("android.app.modes_api") public static final String ACTION_AUTOMATIC_ZEN_RULE_SETTINGS = "android.settings.AUTOMATIC_ZEN_RULE_SETTINGS";
+ field public static final String ACTION_AUTOMATIC_ZEN_RULE_SETTINGS = "android.settings.AUTOMATIC_ZEN_RULE_SETTINGS";
field public static final String ACTION_AUTO_ROTATE_SETTINGS = "android.settings.AUTO_ROTATE_SETTINGS";
field public static final String ACTION_BATTERY_SAVER_SETTINGS = "android.settings.BATTERY_SAVER_SETTINGS";
field public static final String ACTION_BIOMETRIC_ENROLL = "android.settings.BIOMETRIC_ENROLL";
@@ -38332,7 +38342,7 @@ package android.provider {
field @FlaggedApi("com.android.media.flags.enable_privileged_routing_for_media_routing_control") public static final String ACTION_REQUEST_MEDIA_ROUTING_CONTROL = "android.settings.REQUEST_MEDIA_ROUTING_CONTROL";
field public static final String ACTION_REQUEST_SCHEDULE_EXACT_ALARM = "android.settings.REQUEST_SCHEDULE_EXACT_ALARM";
field public static final String ACTION_REQUEST_SET_AUTOFILL_SERVICE = "android.settings.REQUEST_SET_AUTOFILL_SERVICE";
- field @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public static final String ACTION_SATELLITE_SETTING = "android.settings.SATELLITE_SETTING";
+ field public static final String ACTION_SATELLITE_SETTING = "android.settings.SATELLITE_SETTING";
field public static final String ACTION_SEARCH_SETTINGS = "android.search.action.SEARCH_SETTINGS";
field public static final String ACTION_SECURITY_SETTINGS = "android.settings.SECURITY_SETTINGS";
field public static final String ACTION_SETTINGS = "android.settings.SETTINGS";
@@ -38365,7 +38375,7 @@ package android.provider {
field public static final String EXTRA_AIRPLANE_MODE_ENABLED = "airplane_mode_enabled";
field public static final String EXTRA_APP_PACKAGE = "android.provider.extra.APP_PACKAGE";
field public static final String EXTRA_AUTHORITIES = "authorities";
- field @FlaggedApi("android.app.modes_api") public static final String EXTRA_AUTOMATIC_ZEN_RULE_ID = "android.provider.extra.AUTOMATIC_ZEN_RULE_ID";
+ field public static final String EXTRA_AUTOMATIC_ZEN_RULE_ID = "android.provider.extra.AUTOMATIC_ZEN_RULE_ID";
field public static final String EXTRA_BATTERY_SAVER_MODE_ENABLED = "android.settings.extra.battery_saver_mode_enabled";
field public static final String EXTRA_BIOMETRIC_AUTHENTICATORS_ALLOWED = "android.provider.extra.BIOMETRIC_AUTHENTICATORS_ALLOWED";
field public static final String EXTRA_CHANNEL_FILTER_LIST = "android.provider.extra.CHANNEL_FILTER_LIST";
@@ -42157,9 +42167,9 @@ package android.service.notification {
public final class Condition implements android.os.Parcelable {
ctor public Condition(android.net.Uri, String, int);
- ctor @FlaggedApi("android.app.modes_api") public Condition(@Nullable android.net.Uri, @Nullable String, int, int);
+ ctor public Condition(@Nullable android.net.Uri, @Nullable String, int, int);
ctor public Condition(android.net.Uri, String, String, String, int, int, int);
- ctor @FlaggedApi("android.app.modes_api") public Condition(@Nullable android.net.Uri, @Nullable String, @Nullable String, @Nullable String, int, int, int, int);
+ ctor public Condition(@Nullable android.net.Uri, @Nullable String, @Nullable String, @Nullable String, int, int, int, int);
ctor public Condition(android.os.Parcel);
method public android.service.notification.Condition copy();
method public int describeContents();
@@ -42172,10 +42182,10 @@ package android.service.notification {
field public static final int FLAG_RELEVANT_ALWAYS = 2; // 0x2
field public static final int FLAG_RELEVANT_NOW = 1; // 0x1
field public static final String SCHEME = "condition";
- field @FlaggedApi("android.app.modes_api") public static final int SOURCE_CONTEXT = 3; // 0x3
- field @FlaggedApi("android.app.modes_api") public static final int SOURCE_SCHEDULE = 2; // 0x2
- field @FlaggedApi("android.app.modes_api") public static final int SOURCE_UNKNOWN = 0; // 0x0
- field @FlaggedApi("android.app.modes_api") public static final int SOURCE_USER_ACTION = 1; // 0x1
+ field public static final int SOURCE_CONTEXT = 3; // 0x3
+ field public static final int SOURCE_SCHEDULE = 2; // 0x2
+ field public static final int SOURCE_UNKNOWN = 0; // 0x0
+ field public static final int SOURCE_USER_ACTION = 1; // 0x1
field public static final int STATE_ERROR = 3; // 0x3
field public static final int STATE_FALSE = 0; // 0x0
field public static final int STATE_TRUE = 1; // 0x1
@@ -42185,7 +42195,7 @@ package android.service.notification {
field public final android.net.Uri id;
field public final String line1;
field public final String line2;
- field @FlaggedApi("android.app.modes_api") public final int source;
+ field public final int source;
field public final int state;
field public final String summary;
}
@@ -42356,7 +42366,7 @@ package android.service.notification {
field @NonNull public static final android.os.Parcelable.Creator<android.service.notification.StatusBarNotification> CREATOR;
}
- @FlaggedApi("android.app.modes_api") public final class ZenDeviceEffects implements android.os.Parcelable {
+ public final class ZenDeviceEffects implements android.os.Parcelable {
method public int describeContents();
method public boolean shouldDimWallpaper();
method public boolean shouldDisplayGrayscale();
@@ -42366,7 +42376,7 @@ package android.service.notification {
field @NonNull public static final android.os.Parcelable.Creator<android.service.notification.ZenDeviceEffects> CREATOR;
}
- @FlaggedApi("android.app.modes_api") public static final class ZenDeviceEffects.Builder {
+ public static final class ZenDeviceEffects.Builder {
ctor public ZenDeviceEffects.Builder();
ctor public ZenDeviceEffects.Builder(@NonNull android.service.notification.ZenDeviceEffects);
method @NonNull public android.service.notification.ZenDeviceEffects build();
@@ -42388,7 +42398,7 @@ package android.service.notification {
method public int getPriorityCategoryReminders();
method public int getPriorityCategoryRepeatCallers();
method public int getPriorityCategorySystem();
- method @FlaggedApi("android.app.modes_api") public int getPriorityChannelsAllowed();
+ method public int getPriorityChannelsAllowed();
method public int getPriorityConversationSenders();
method public int getPriorityMessageSenders();
method public int getVisualEffectAmbient();
@@ -42423,7 +42433,7 @@ package android.service.notification {
method @NonNull public android.service.notification.ZenPolicy.Builder allowEvents(boolean);
method @NonNull public android.service.notification.ZenPolicy.Builder allowMedia(boolean);
method @NonNull public android.service.notification.ZenPolicy.Builder allowMessages(int);
- method @FlaggedApi("android.app.modes_api") @NonNull public android.service.notification.ZenPolicy.Builder allowPriorityChannels(boolean);
+ method @NonNull public android.service.notification.ZenPolicy.Builder allowPriorityChannels(boolean);
method @NonNull public android.service.notification.ZenPolicy.Builder allowReminders(boolean);
method @NonNull public android.service.notification.ZenPolicy.Builder allowRepeatCallers(boolean);
method @NonNull public android.service.notification.ZenPolicy.Builder allowSystem(boolean);
@@ -44980,7 +44990,7 @@ package android.telephony {
field public static final String KEY_CARRIER_SETTINGS_ACTIVITY_COMPONENT_NAME_STRING = "carrier_settings_activity_component_name_string";
field public static final String KEY_CARRIER_SETTINGS_ENABLE_BOOL = "carrier_settings_enable_bool";
field @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public static final String KEY_CARRIER_SUPPORTED_SATELLITE_NOTIFICATION_HYSTERESIS_SEC_INT = "carrier_supported_satellite_notification_hysteresis_sec_int";
- field @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public static final String KEY_CARRIER_SUPPORTED_SATELLITE_SERVICES_PER_PROVIDER_BUNDLE = "carrier_supported_satellite_services_per_provider_bundle";
+ field public static final String KEY_CARRIER_SUPPORTED_SATELLITE_SERVICES_PER_PROVIDER_BUNDLE = "carrier_supported_satellite_services_per_provider_bundle";
field public static final String KEY_CARRIER_SUPPORTS_OPP_DATA_AUTO_PROVISIONING_BOOL = "carrier_supports_opp_data_auto_provisioning_bool";
field public static final String KEY_CARRIER_SUPPORTS_SS_OVER_UT_BOOL = "carrier_supports_ss_over_ut_bool";
field public static final String KEY_CARRIER_SUPPORTS_TETHERING_BOOL = "carrier_supports_tethering_bool";
@@ -45113,9 +45123,9 @@ package android.telephony {
field public static final String KEY_MMS_UA_PROF_URL_STRING = "uaProfUrl";
field public static final String KEY_MMS_USER_AGENT_STRING = "userAgent";
field public static final String KEY_MONTHLY_DATA_CYCLE_DAY_INT = "monthly_data_cycle_day_int";
- field @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public static final String KEY_NTN_LTE_RSRP_THRESHOLDS_INT_ARRAY = "ntn_lte_rsrp_thresholds_int_array";
- field @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public static final String KEY_NTN_LTE_RSRQ_THRESHOLDS_INT_ARRAY = "ntn_lte_rsrq_thresholds_int_array";
- field @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public static final String KEY_NTN_LTE_RSSNR_THRESHOLDS_INT_ARRAY = "ntn_lte_rssnr_thresholds_int_array";
+ field public static final String KEY_NTN_LTE_RSRP_THRESHOLDS_INT_ARRAY = "ntn_lte_rsrp_thresholds_int_array";
+ field public static final String KEY_NTN_LTE_RSRQ_THRESHOLDS_INT_ARRAY = "ntn_lte_rsrq_thresholds_int_array";
+ field public static final String KEY_NTN_LTE_RSSNR_THRESHOLDS_INT_ARRAY = "ntn_lte_rssnr_thresholds_int_array";
field public static final String KEY_ONLY_AUTO_SELECT_IN_HOME_NETWORK_BOOL = "only_auto_select_in_home_network";
field public static final String KEY_ONLY_SINGLE_DC_ALLOWED_INT_ARRAY = "only_single_dc_allowed_int_array";
field public static final String KEY_OPERATOR_SELECTION_EXPAND_BOOL = "operator_selection_expand_bool";
@@ -45131,7 +45141,7 @@ package android.telephony {
field public static final String KEY_OPPORTUNISTIC_NETWORK_MAX_BACKOFF_TIME_LONG = "opportunistic_network_max_backoff_time_long";
field public static final String KEY_OPPORTUNISTIC_NETWORK_PING_PONG_TIME_LONG = "opportunistic_network_ping_pong_time_long";
field @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public static final String KEY_OVERRIDE_WFC_ROAMING_MODE_WHILE_USING_NTN_BOOL = "override_wfc_roaming_mode_while_using_ntn_bool";
- field @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public static final String KEY_PARAMETERS_USED_FOR_NTN_LTE_SIGNAL_BAR_INT = "parameters_used_for_ntn_lte_signal_bar_int";
+ field public static final String KEY_PARAMETERS_USED_FOR_NTN_LTE_SIGNAL_BAR_INT = "parameters_used_for_ntn_lte_signal_bar_int";
field public static final String KEY_PING_TEST_BEFORE_DATA_SWITCH_BOOL = "ping_test_before_data_switch_bool";
field public static final String KEY_PREFER_2G_BOOL = "prefer_2g_bool";
field @FlaggedApi("com.android.internal.telephony.flags.hide_prefer_3g_item") public static final String KEY_PREFER_3G_VISIBILITY_BOOL = "prefer_3g_visibility_bool";
@@ -45160,13 +45170,14 @@ package android.telephony {
field public static final String KEY_RTT_SUPPORTED_WHILE_ROAMING_BOOL = "rtt_supported_while_roaming_bool";
field public static final String KEY_RTT_UPGRADE_SUPPORTED_BOOL = "rtt_upgrade_supported_bool";
field public static final String KEY_RTT_UPGRADE_SUPPORTED_FOR_DOWNGRADED_VT_CALL_BOOL = "rtt_upgrade_supported_for_downgraded_vt_call";
- field @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public static final String KEY_SATELLITE_ATTACH_SUPPORTED_BOOL = "satellite_attach_supported_bool";
- field @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public static final String KEY_SATELLITE_CONNECTION_HYSTERESIS_SEC_INT = "satellite_connection_hysteresis_sec_int";
+ field public static final String KEY_SATELLITE_ATTACH_SUPPORTED_BOOL = "satellite_attach_supported_bool";
+ field @FlaggedApi("com.android.internal.telephony.flags.satellite_25q4_apis") public static final String KEY_SATELLITE_CONNECTED_NOTIFICATION_THROTTLE_MILLIS_INT = "satellite_connected_notification_throttle_millis_int";
+ field public static final String KEY_SATELLITE_CONNECTION_HYSTERESIS_SEC_INT = "satellite_connection_hysteresis_sec_int";
field @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public static final String KEY_SATELLITE_DATA_SUPPORT_MODE_INT = "satellite_data_support_mode_int";
field @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public static final String KEY_SATELLITE_DISPLAY_NAME_STRING = "satellite_display_name_string";
field @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public static final String KEY_SATELLITE_ENTITLEMENT_APP_NAME_STRING = "satellite_entitlement_app_name_string";
- field @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public static final String KEY_SATELLITE_ENTITLEMENT_STATUS_REFRESH_DAYS_INT = "satellite_entitlement_status_refresh_days_int";
- field @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public static final String KEY_SATELLITE_ENTITLEMENT_SUPPORTED_BOOL = "satellite_entitlement_supported_bool";
+ field public static final String KEY_SATELLITE_ENTITLEMENT_STATUS_REFRESH_DAYS_INT = "satellite_entitlement_status_refresh_days_int";
+ field public static final String KEY_SATELLITE_ENTITLEMENT_SUPPORTED_BOOL = "satellite_entitlement_supported_bool";
field @FlaggedApi("com.android.internal.telephony.flags.carrier_roaming_nb_iot_ntn") public static final String KEY_SATELLITE_ESOS_SUPPORTED_BOOL = "satellite_esos_supported_bool";
field @FlaggedApi("com.android.internal.telephony.flags.satellite_25q4_apis") public static final String KEY_SATELLITE_IGNORE_DATA_ROAMING_SETTING_BOOL = "satellite_ignore_data_roaming_setting_bool";
field @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public static final String KEY_SATELLITE_INFORMATION_REDIRECT_URL_STRING = "satellite_information_redirect_url_string";
@@ -46473,7 +46484,7 @@ package android.telephony {
method public boolean isNetworkRegistered();
method public boolean isNetworkRoaming();
method public boolean isNetworkSearching();
- method @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public boolean isNonTerrestrialNetwork();
+ method public boolean isNonTerrestrialNetwork();
method @Deprecated public boolean isRegistered();
method @Deprecated public boolean isRoaming();
method @Deprecated public boolean isSearching();
@@ -46489,7 +46500,7 @@ package android.telephony {
field public static final int NR_STATE_RESTRICTED = 1; // 0x1
field public static final int SERVICE_TYPE_DATA = 2; // 0x2
field public static final int SERVICE_TYPE_EMERGENCY = 5; // 0x5
- field @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public static final int SERVICE_TYPE_MMS = 6; // 0x6
+ field public static final int SERVICE_TYPE_MMS = 6; // 0x6
field public static final int SERVICE_TYPE_SMS = 3; // 0x3
field public static final int SERVICE_TYPE_UNKNOWN = 0; // 0x0
field public static final int SERVICE_TYPE_VIDEO = 4; // 0x4
@@ -46710,7 +46721,7 @@ package android.telephony {
method public boolean getRoaming();
method public int getState();
method public boolean isSearching();
- method @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public boolean isUsingNonTerrestrialNetwork();
+ method public boolean isUsingNonTerrestrialNetwork();
method public void setIsManualSelection(boolean);
method public void setOperatorName(String, String, String);
method public void setRoaming(boolean);
@@ -47849,7 +47860,7 @@ package android.telephony.data {
field public static final int TYPE_MMS = 2; // 0x2
field @FlaggedApi("com.android.internal.telephony.flags.oem_paid_private") public static final int TYPE_OEM_PAID = 65536; // 0x10000
field @FlaggedApi("com.android.internal.telephony.flags.oem_paid_private") public static final int TYPE_OEM_PRIVATE = 131072; // 0x20000
- field @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public static final int TYPE_RCS = 32768; // 0x8000
+ field public static final int TYPE_RCS = 32768; // 0x8000
field public static final int TYPE_SUPL = 4; // 0x4
field public static final int TYPE_VSIM = 4096; // 0x1000
field public static final int TYPE_XCAP = 2048; // 0x800
@@ -48733,6 +48744,7 @@ package android.telephony.satellite {
@FlaggedApi("com.android.internal.telephony.flags.satellite_state_change_listener") public final class SatelliteManager {
method @FlaggedApi("com.android.internal.telephony.flags.satellite_state_change_listener") @RequiresPermission(anyOf={android.Manifest.permission.READ_BASIC_PHONE_STATE, "android.permission.READ_PRIVILEGED_PHONE_STATE", android.Manifest.permission.READ_PHONE_STATE, "carrier privileges"}) public void registerStateChangeListener(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.satellite.SatelliteStateChangeListener);
method @FlaggedApi("com.android.internal.telephony.flags.satellite_state_change_listener") @RequiresPermission(anyOf={android.Manifest.permission.READ_BASIC_PHONE_STATE, "android.permission.READ_PRIVILEGED_PHONE_STATE", android.Manifest.permission.READ_PHONE_STATE, "carrier privileges"}) public void unregisterStateChangeListener(@NonNull android.telephony.satellite.SatelliteStateChangeListener);
+ field @FlaggedApi("com.android.internal.telephony.flags.satellite_25q4_apis") public static final String PROPERTY_SATELLITE_DATA_OPTIMIZED = "android.telephony.PROPERTY_SATELLITE_DATA_OPTIMIZED";
}
@FlaggedApi("com.android.internal.telephony.flags.satellite_state_change_listener") public interface SatelliteStateChangeListener {
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 3b26e6b2b225..137c96714ae4 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -151,6 +151,8 @@ package android {
field @FlaggedApi("android.content.pm.emergency_install_permission") public static final String EMERGENCY_INSTALL_PACKAGES = "android.permission.EMERGENCY_INSTALL_PACKAGES";
field public static final String ENTER_CAR_MODE_PRIORITIZED = "android.permission.ENTER_CAR_MODE_PRIORITIZED";
field public static final String EXEMPT_FROM_AUDIO_RECORD_RESTRICTIONS = "android.permission.EXEMPT_FROM_AUDIO_RECORD_RESTRICTIONS";
+ field @FlaggedApi("android.xr.xr_manifest_entries") public static final String EYE_CALIBRATION = "android.permission.EYE_CALIBRATION";
+ field @FlaggedApi("android.xr.xr_manifest_entries") public static final String FACE_TRACKING_CALIBRATION = "android.permission.FACE_TRACKING_CALIBRATION";
field public static final String FORCE_BACK = "android.permission.FORCE_BACK";
field public static final String FORCE_STOP_PACKAGES = "android.permission.FORCE_STOP_PACKAGES";
field public static final String GET_APP_METADATA = "android.permission.GET_APP_METADATA";
@@ -168,6 +170,7 @@ package android {
field public static final String HARDWARE_TEST = "android.permission.HARDWARE_TEST";
field public static final String HDMI_CEC = "android.permission.HDMI_CEC";
field @Deprecated public static final String HIDE_NON_SYSTEM_OVERLAY_WINDOWS = "android.permission.HIDE_NON_SYSTEM_OVERLAY_WINDOWS";
+ field @FlaggedApi("android.xr.xr_manifest_entries") public static final String IMPORT_XR_ANCHOR = "android.permission.IMPORT_XR_ANCHOR";
field public static final String INJECT_EVENTS = "android.permission.INJECT_EVENTS";
field @FlaggedApi("android.content.pm.sdk_dependency_installer") public static final String INSTALL_DEPENDENCY_SHARED_LIBRARIES = "android.permission.INSTALL_DEPENDENCY_SHARED_LIBRARIES";
field public static final String INSTALL_DPC_PACKAGES = "android.permission.INSTALL_DPC_PACKAGES";
@@ -450,6 +453,7 @@ package android {
field public static final String WRITE_SECURITY_LOG = "android.permission.WRITE_SECURITY_LOG";
field public static final String WRITE_SMS = "android.permission.WRITE_SMS";
field @FlaggedApi("android.provider.user_keys") public static final String WRITE_VERIFICATION_STATE_E2EE_CONTACT_KEYS = "android.permission.WRITE_VERIFICATION_STATE_E2EE_CONTACT_KEYS";
+ field @FlaggedApi("android.xr.xr_manifest_entries") public static final String XR_TRACKING_IN_BACKGROUND = "android.permission.XR_TRACKING_IN_BACKGROUND";
}
public static final class Manifest.permission_group {
@@ -2934,6 +2938,14 @@ package android.app.smartspace.uitemplatedata {
}
+package android.app.supervision {
+
+ @FlaggedApi("android.app.supervision.flags.supervision_manager_apis") public class SupervisionManager {
+ method @FlaggedApi("android.app.supervision.flags.supervision_manager_apis") @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.QUERY_USERS}) public boolean isSupervisionEnabled();
+ }
+
+}
+
package android.app.time {
public final class Capabilities {
@@ -3797,6 +3809,7 @@ package android.content {
field public static final String SHARED_CONNECTIVITY_SERVICE = "shared_connectivity";
field public static final String SMARTSPACE_SERVICE = "smartspace";
field public static final String STATS_MANAGER = "stats";
+ field @FlaggedApi("android.app.supervision.flags.supervision_manager_apis") public static final String SUPERVISION_SERVICE = "supervision";
field public static final String SYSTEM_CONFIG_SERVICE = "system_config";
field public static final String SYSTEM_UPDATE_SERVICE = "system_update";
field @FlaggedApi("com.android.net.thread.platform.flags.thread_enabled_platform") public static final String THREAD_NETWORK_SERVICE = "thread_network";
@@ -4186,7 +4199,7 @@ package android.content.pm {
method public void setInstallAsInstantApp(boolean);
method public void setInstallAsVirtualPreload();
method public void setRequestDowngrade(boolean);
- method @FlaggedApi("android.content.pm.recoverability_detection") @RequiresPermission(android.Manifest.permission.MANAGE_ROLLBACKS) public void setRollbackImpactLevel(int);
+ method @RequiresPermission(android.Manifest.permission.MANAGE_ROLLBACKS) public void setRollbackImpactLevel(int);
method @FlaggedApi("android.content.pm.rollback_lifetime") @RequiresPermission(android.Manifest.permission.MANAGE_ROLLBACKS) public void setRollbackLifetimeMillis(long);
method @RequiresPermission(android.Manifest.permission.INSTALL_PACKAGES) public void setStaged();
}
@@ -4361,9 +4374,9 @@ package android.content.pm {
field public static final int ROLLBACK_DATA_POLICY_RESTORE = 0; // 0x0
field public static final int ROLLBACK_DATA_POLICY_RETAIN = 2; // 0x2
field public static final int ROLLBACK_DATA_POLICY_WIPE = 1; // 0x1
- field @FlaggedApi("android.content.pm.recoverability_detection") public static final int ROLLBACK_USER_IMPACT_HIGH = 1; // 0x1
- field @FlaggedApi("android.content.pm.recoverability_detection") public static final int ROLLBACK_USER_IMPACT_LOW = 0; // 0x0
- field @FlaggedApi("android.content.pm.recoverability_detection") public static final int ROLLBACK_USER_IMPACT_ONLY_MANUAL = 2; // 0x2
+ field public static final int ROLLBACK_USER_IMPACT_HIGH = 1; // 0x1
+ field public static final int ROLLBACK_USER_IMPACT_LOW = 0; // 0x0
+ field public static final int ROLLBACK_USER_IMPACT_ONLY_MANUAL = 2; // 0x2
field public static final int SYSTEM_APP_STATE_HIDDEN_UNTIL_INSTALLED_HIDDEN = 0; // 0x0
field public static final int SYSTEM_APP_STATE_HIDDEN_UNTIL_INSTALLED_VISIBLE = 1; // 0x1
field public static final int SYSTEM_APP_STATE_INSTALLED = 2; // 0x2
@@ -15272,7 +15285,7 @@ package android.telephony {
method @NonNull public android.telephony.NetworkRegistrationInfo.Builder setCellIdentity(@Nullable android.telephony.CellIdentity);
method @NonNull public android.telephony.NetworkRegistrationInfo.Builder setDomain(int);
method @NonNull public android.telephony.NetworkRegistrationInfo.Builder setEmergencyOnly(boolean);
- method @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") @NonNull public android.telephony.NetworkRegistrationInfo.Builder setIsNonTerrestrialNetwork(boolean);
+ method @NonNull public android.telephony.NetworkRegistrationInfo.Builder setIsNonTerrestrialNetwork(boolean);
method @NonNull public android.telephony.NetworkRegistrationInfo.Builder setRegisteredPlmn(@Nullable String);
method @NonNull public android.telephony.NetworkRegistrationInfo.Builder setRegistrationState(int);
method @NonNull public android.telephony.NetworkRegistrationInfo.Builder setRejectCause(int);
@@ -16409,7 +16422,7 @@ package android.telephony.data {
field public static final String TYPE_MMS_STRING = "mms";
field @FlaggedApi("com.android.internal.telephony.flags.oem_paid_private") public static final String TYPE_OEM_PAID_STRING = "oem_paid";
field @FlaggedApi("com.android.internal.telephony.flags.oem_paid_private") public static final String TYPE_OEM_PRIVATE_STRING = "oem_private";
- field @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public static final String TYPE_RCS_STRING = "rcs";
+ field public static final String TYPE_RCS_STRING = "rcs";
field public static final String TYPE_SUPL_STRING = "supl";
field public static final String TYPE_VSIM_STRING = "vsim";
field public static final String TYPE_XCAP_STRING = "xcap";
@@ -18576,12 +18589,13 @@ package android.telephony.satellite {
}
@FlaggedApi("com.android.internal.telephony.flags.satellite_state_change_listener") public final class SatelliteManager {
- method @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void addAttachRestrictionForCarrier(int, int, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
+ method @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void addAttachRestrictionForCarrier(int, int, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
method @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void deprovisionSatellite(@NonNull java.util.List<android.telephony.satellite.SatelliteSubscriberInfo>, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.telephony.satellite.SatelliteManager.SatelliteException>);
method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void deprovisionService(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
- method @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") @NonNull @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public java.util.Set<java.lang.Integer> getAttachRestrictionReasonsForCarrier(int);
+ method @NonNull @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public java.util.Set<java.lang.Integer> getAttachRestrictionReasonsForCarrier(int);
+ method @FlaggedApi("com.android.internal.telephony.flags.satellite_25q4_apis") @NonNull @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public java.util.List<java.lang.String> getSatelliteDataOptimizedApps();
method @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") @NonNull @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public int[] getSatelliteDisallowedReasons();
- method @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") @NonNull @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public java.util.List<java.lang.String> getSatellitePlmnsForCarrier(int);
+ method @NonNull @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public java.util.List<java.lang.String> getSatellitePlmnsForCarrier(int);
method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void pollPendingDatagrams(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
method @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void provisionSatellite(@NonNull java.util.List<android.telephony.satellite.SatelliteSubscriberInfo>, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.telephony.satellite.SatelliteManager.SatelliteException>);
method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void provisionService(@NonNull String, @NonNull byte[], @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
@@ -18594,11 +18608,11 @@ package android.telephony.satellite {
method @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void registerForSatelliteDisallowedReasonsChanged(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.satellite.SatelliteDisallowedReasonsCallback);
method @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public int registerForSelectedNbIotSatelliteSubscriptionChanged(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.satellite.SelectedNbIotSatelliteSubscriptionCallback);
method @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public int registerForSupportedStateChanged(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Boolean>);
- method @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void removeAttachRestrictionForCarrier(int, int, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
- method @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestAttachEnabledForCarrier(int, boolean, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
+ method @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void removeAttachRestrictionForCarrier(int, int, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
+ method @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestAttachEnabledForCarrier(int, boolean, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestCapabilities(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.telephony.satellite.SatelliteCapabilities,android.telephony.satellite.SatelliteManager.SatelliteException>);
method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestEnabled(@NonNull android.telephony.satellite.EnableRequestAttributes, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
- method @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestIsAttachEnabledForCarrier(int, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,android.telephony.satellite.SatelliteManager.SatelliteException>);
+ method @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestIsAttachEnabledForCarrier(int, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,android.telephony.satellite.SatelliteManager.SatelliteException>);
method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestIsCommunicationAllowedForCurrentLocation(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,android.telephony.satellite.SatelliteManager.SatelliteException>);
method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestIsDemoModeEnabled(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,android.telephony.satellite.SatelliteManager.SatelliteException>);
method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestIsEmergencyModeEnabled(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,android.telephony.satellite.SatelliteManager.SatelliteException>);
@@ -18642,15 +18656,15 @@ package android.telephony.satellite {
field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int DISPLAY_MODE_OPENED = 2; // 0x2
field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int DISPLAY_MODE_UNKNOWN = 0; // 0x0
field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE_SOS = 1; // 0x1
- field @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public static final int EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE_T911 = 2; // 0x2
+ field public static final int EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE_T911 = 2; // 0x2
field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int NT_RADIO_TECHNOLOGY_EMTC_NTN = 3; // 0x3
field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int NT_RADIO_TECHNOLOGY_NB_IOT_NTN = 1; // 0x1
field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int NT_RADIO_TECHNOLOGY_NR_NTN = 2; // 0x2
field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int NT_RADIO_TECHNOLOGY_PROPRIETARY = 4; // 0x4
field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int NT_RADIO_TECHNOLOGY_UNKNOWN = 0; // 0x0
field @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public static final String PROPERTY_SATELLITE_MANUAL_CONNECT_P2P_SUPPORT = "android.telephony.satellite.PROPERTY_SATELLITE_MANUAL_CONNECT_P2P_SUPPORT";
- field @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public static final int SATELLITE_COMMUNICATION_RESTRICTION_REASON_ENTITLEMENT = 2; // 0x2
- field @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public static final int SATELLITE_COMMUNICATION_RESTRICTION_REASON_GEOLOCATION = 1; // 0x1
+ field public static final int SATELLITE_COMMUNICATION_RESTRICTION_REASON_ENTITLEMENT = 2; // 0x2
+ field public static final int SATELLITE_COMMUNICATION_RESTRICTION_REASON_GEOLOCATION = 1; // 0x1
field @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public static final int SATELLITE_COMMUNICATION_RESTRICTION_REASON_USER = 0; // 0x0
field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE = 0; // 0x0
field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_FAILED = 7; // 0x7
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 9e9e3c2f13c1..975c2c27cb22 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -393,25 +393,25 @@ package android.app {
}
public class NotificationManager {
- method @FlaggedApi("android.app.modes_api") @NonNull public String addAutomaticZenRule(@NonNull android.app.AutomaticZenRule, boolean);
+ method @NonNull public String addAutomaticZenRule(@NonNull android.app.AutomaticZenRule, boolean);
method @FlaggedApi("android.service.notification.notification_classification") public void allowAssistantAdjustment(@NonNull String);
method public void cleanUpCallersAfter(long);
method @FlaggedApi("android.service.notification.notification_classification") public void disallowAssistantAdjustment(@NonNull String);
- method @FlaggedApi("android.app.modes_api") @NonNull public android.service.notification.ZenPolicy getDefaultZenPolicy();
+ method @NonNull public android.service.notification.ZenPolicy getDefaultZenPolicy();
method public android.content.ComponentName getEffectsSuppressor();
method @FlaggedApi("android.service.notification.notification_classification") @NonNull public java.util.Set<java.lang.String> getUnsupportedAdjustmentTypes();
method public boolean isNotificationPolicyAccessGrantedForPackage(@NonNull String);
- method @FlaggedApi("android.app.modes_api") public boolean removeAutomaticZenRule(@NonNull String, boolean);
+ method public boolean removeAutomaticZenRule(@NonNull String, boolean);
method @FlaggedApi("android.service.notification.notification_classification") public void setAssistantAdjustmentKeyTypeState(int, boolean);
method @FlaggedApi("android.app.api_rich_ongoing") public void setCanPostPromotedNotifications(@NonNull String, int, boolean);
method @RequiresPermission(android.Manifest.permission.MANAGE_NOTIFICATION_LISTENERS) public void setNotificationListenerAccessGranted(@NonNull android.content.ComponentName, boolean, boolean);
method @RequiresPermission(android.Manifest.permission.MANAGE_TOAST_RATE_LIMITING) public void setToastRateLimitingEnabled(boolean);
- method @FlaggedApi("android.app.modes_api") public boolean updateAutomaticZenRule(@NonNull String, @NonNull android.app.AutomaticZenRule, boolean);
+ method public boolean updateAutomaticZenRule(@NonNull String, @NonNull android.app.AutomaticZenRule, boolean);
method public void updateNotificationChannel(@NonNull String, int, @NonNull android.app.NotificationChannel);
}
public static class NotificationManager.Policy implements android.os.Parcelable {
- method @FlaggedApi("android.app.modes_api") public boolean allowPriorityChannels();
+ method public boolean allowPriorityChannels();
}
public final class PendingIntent implements android.os.Parcelable {
@@ -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 public boolean injectInputEvent(@NonNull android.view.InputEvent, boolean, boolean);
+ method @Deprecated 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);
@@ -490,15 +490,15 @@ package android.app {
}
public class UiModeManager {
- method @FlaggedApi("android.app.modes_api") @RequiresPermission(android.Manifest.permission.MODIFY_DAY_NIGHT_MODE) public int getAttentionModeThemeOverlay();
+ method @RequiresPermission(android.Manifest.permission.MODIFY_DAY_NIGHT_MODE) public int getAttentionModeThemeOverlay();
method public boolean isNightModeLocked();
method public boolean isUiModeLocked();
method @RequiresPermission(value=android.Manifest.permission.TOGGLE_AUTOMOTIVE_PROJECTION, conditional=true) public boolean releaseProjection(int);
method @RequiresPermission(value=android.Manifest.permission.TOGGLE_AUTOMOTIVE_PROJECTION, conditional=true) public boolean requestProjection(int);
- field @FlaggedApi("android.app.modes_api") public static final int MODE_ATTENTION_THEME_OVERLAY_DAY = 1002; // 0x3ea
- field @FlaggedApi("android.app.modes_api") public static final int MODE_ATTENTION_THEME_OVERLAY_NIGHT = 1001; // 0x3e9
- field @FlaggedApi("android.app.modes_api") public static final int MODE_ATTENTION_THEME_OVERLAY_OFF = 1000; // 0x3e8
- field @FlaggedApi("android.app.modes_api") public static final int MODE_ATTENTION_THEME_OVERLAY_UNKNOWN = -1; // 0xffffffff
+ field public static final int MODE_ATTENTION_THEME_OVERLAY_DAY = 1002; // 0x3ea
+ field public static final int MODE_ATTENTION_THEME_OVERLAY_NIGHT = 1001; // 0x3e9
+ field public static final int MODE_ATTENTION_THEME_OVERLAY_OFF = 1000; // 0x3e8
+ field public static final int MODE_ATTENTION_THEME_OVERLAY_UNKNOWN = -1; // 0xffffffff
field public static final int PROJECTION_TYPE_ALL = -1; // 0xffffffff
field public static final int PROJECTION_TYPE_AUTOMOTIVE = 1; // 0x1
field public static final int PROJECTION_TYPE_NONE = 0; // 0x0
@@ -850,6 +850,14 @@ package android.app.prediction {
}
+package android.app.supervision {
+
+ @FlaggedApi("android.app.supervision.flags.supervision_manager_apis") public class SupervisionManager {
+ method public void setSupervisionEnabled(boolean);
+ }
+
+}
+
package android.app.usage {
public class StorageStatsManager {
@@ -1716,7 +1724,7 @@ package android.hardware.display {
}
public final class ColorDisplayManager {
- method @FlaggedApi("android.app.modes_api") @RequiresPermission(android.Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS) public boolean isSaturationActivated();
+ method @RequiresPermission(android.Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS) public boolean isSaturationActivated();
}
public final class DisplayManager {
@@ -3237,16 +3245,16 @@ package android.service.notification {
method @Deprecated public boolean isBound();
}
- @FlaggedApi("android.app.modes_api") public final class ZenDeviceEffects implements android.os.Parcelable {
+ public final class ZenDeviceEffects implements android.os.Parcelable {
method @NonNull public java.util.Set<java.lang.String> getExtraEffects();
}
- @FlaggedApi("android.app.modes_api") public static final class ZenDeviceEffects.Builder {
+ public static final class ZenDeviceEffects.Builder {
method @NonNull public android.service.notification.ZenDeviceEffects.Builder setExtraEffects(@NonNull java.util.Set<java.lang.String>);
}
public final class ZenPolicy implements android.os.Parcelable {
- method @FlaggedApi("android.app.modes_api") @NonNull public android.service.notification.ZenPolicy overwrittenWith(@Nullable android.service.notification.ZenPolicy);
+ method @NonNull public android.service.notification.ZenPolicy overwrittenWith(@Nullable android.service.notification.ZenPolicy);
}
public static final class ZenPolicy.Builder {
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 252d23f69400..ee9c64f97382 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -292,13 +292,15 @@ import java.util.function.Consumer;
* to the user, it must be completely restarted and restored to its previous state.</li>
* </ul>
*
- * <p>The following diagram shows the important state paths of an Activity.
+ * <p>The following diagram shows the important state paths of an activity.
* The square rectangles represent callback methods you can implement to
- * perform operations when the Activity moves between states. The colored
- * ovals are major states the Activity can be in.</p>
+ * perform operations when the activity moves between states. The colored
+ * ovals are major states the activity can be in.</p>
*
- * <p><img src="../../../images/activity_lifecycle.png"
- * alt="State diagram for an Android Activity Lifecycle." border="0" /></p>
+ * <p><img class="invert"
+ * style="display: block; margin: auto;"
+ * src="../../../images/activity_lifecycle.png"
+ * alt="State diagram for the Android activity lifecycle." /></p>
*
* <p>There are three key loops you may be interested in monitoring within your
* activity:
@@ -505,7 +507,7 @@ import java.util.function.Consumer;
* changes.</p>
*
* <p>Unless you specify otherwise, a configuration change (such as a change
- * in screen orientation, language, input devices, etc) will cause your
+ * in screen orientation, language, input devices, etc.) will cause your
* current activity to be <em>destroyed</em>, going through the normal activity
* lifecycle process of {@link #onPause},
* {@link #onStop}, and {@link #onDestroy} as appropriate. If the activity
@@ -1838,7 +1840,7 @@ public class Activity extends ContextThemeWrapper
*
* <p>You can call {@link #finish} from within this function, in
* which case onDestroy() will be immediately called after {@link #onCreate} without any of the
- * rest of the activity lifecycle ({@link #onStart}, {@link #onResume}, {@link #onPause}, etc)
+ * rest of the activity lifecycle ({@link #onStart}, {@link #onResume}, {@link #onPause}, etc.)
* executing.
*
* <p><em>Derived classes must call through to the super class's
@@ -2132,7 +2134,7 @@ public class Activity extends ContextThemeWrapper
*
* <p>You can call {@link #finish} from within this function, in
* which case {@link #onStop} will be immediately called after {@link #onStart} without the
- * lifecycle transitions in-between ({@link #onResume}, {@link #onPause}, etc) executing.
+ * lifecycle transitions in-between ({@link #onResume}, {@link #onPause}, etc.) executing.
*
* <p><em>Derived classes must call through to the super class's
* implementation of this method. If they do not, an exception will be
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 2d7ed46fe64a..3e3ec162be09 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -104,6 +104,7 @@ import android.content.pm.ServiceInfo;
import android.content.res.AssetManager;
import android.content.res.CompatibilityInfo;
import android.content.res.Configuration;
+import android.content.res.ResourceTimer;
import android.content.res.Resources;
import android.content.res.ResourcesImpl;
import android.content.res.loader.ResourcesLoader;
@@ -386,7 +387,7 @@ public final class ActivityThread extends ClientTransactionHandler
@UnsupportedAppUsage
private ContextImpl mSystemContext;
@GuardedBy("this")
- private ArrayList<WeakReference<ContextImpl>> mDisplaySystemUiContexts;
+ private ArrayList<WeakReference<Context>> mDisplaySystemUiContexts;
@UnsupportedAppUsage
static volatile IPackageManager sPackageManager;
@@ -3203,7 +3204,7 @@ public final class ActivityThread extends ClientTransactionHandler
}
@NonNull
- public ContextImpl getSystemUiContext() {
+ public Context getSystemUiContext() {
return getSystemUiContext(DEFAULT_DISPLAY);
}
@@ -3213,7 +3214,7 @@ public final class ActivityThread extends ClientTransactionHandler
* @see ContextImpl#createSystemUiContext(ContextImpl, int)
*/
@NonNull
- public ContextImpl getSystemUiContext(int displayId) {
+ public Context getSystemUiContext(int displayId) {
synchronized (this) {
if (mDisplaySystemUiContexts == null) {
mDisplaySystemUiContexts = new ArrayList<>();
@@ -3221,7 +3222,7 @@ public final class ActivityThread extends ClientTransactionHandler
mDisplaySystemUiContexts.removeIf(contextRef -> contextRef.refersTo(null));
- ContextImpl context = getSystemUiContextNoCreateLocked(displayId);
+ Context context = getSystemUiContextNoCreateLocked(displayId);
if (context != null) {
return context;
}
@@ -3232,9 +3233,20 @@ public final class ActivityThread extends ClientTransactionHandler
}
}
+ /**
+ * Creates a {@code SystemUiContext} for testing.
+ * <p>
+ * DO NOT use it in production code.
+ */
+ @VisibleForTesting
+ @NonNull
+ public Context createSystemUiContextForTesting(int displayId) {
+ return ContextImpl.createSystemUiContext(getSystemContext(), displayId);
+ }
+
@Nullable
@Override
- public ContextImpl getSystemUiContextNoCreate() {
+ public Context getSystemUiContextNoCreate() {
synchronized (this) {
if (mDisplaySystemUiContexts == null) {
return null;
@@ -3245,9 +3257,9 @@ public final class ActivityThread extends ClientTransactionHandler
@GuardedBy("this")
@Nullable
- private ContextImpl getSystemUiContextNoCreateLocked(int displayId) {
+ private Context getSystemUiContextNoCreateLocked(int displayId) {
for (int i = 0; i < mDisplaySystemUiContexts.size(); i++) {
- ContextImpl context = mDisplaySystemUiContexts.get(i).get();
+ Context context = mDisplaySystemUiContexts.get(i).get();
if (context != null && context.getDisplayId() == displayId) {
return context;
}
@@ -3266,7 +3278,8 @@ public final class ActivityThread extends ClientTransactionHandler
public void installSystemApplicationInfo(ApplicationInfo info, ClassLoader classLoader) {
synchronized (this) {
getSystemContext().installSystemApplicationInfo(info, classLoader);
- getSystemUiContext().installSystemApplicationInfo(info, classLoader);
+ final ContextImpl sysUiContextImpl = ContextImpl.getImpl(getSystemUiContext());
+ sysUiContextImpl.installSystemApplicationInfo(info, classLoader);
// give ourselves a default profiler
mProfiler = new Profiler();
@@ -4758,6 +4771,7 @@ public final class ActivityThread extends ClientTransactionHandler
// frame.
final SurfaceControl.Transaction transaction = new SurfaceControl.Transaction();
transaction.hide(startingWindowLeash);
+ startingWindowLeash.release();
view.syncTransferSurfaceOnDraw();
@@ -5283,6 +5297,7 @@ public final class ActivityThread extends ClientTransactionHandler
Resources.dumpHistory(pw, "");
pw.flush();
+ ResourceTimer.dumpTimers(info.fd.getFileDescriptor(), "-refresh");
if (info.finishCallback != null) {
info.finishCallback.sendResult(null);
}
diff --git a/core/java/android/app/ActivityThreadInternal.java b/core/java/android/app/ActivityThreadInternal.java
index 72506b9fcdbb..70876da9183f 100644
--- a/core/java/android/app/ActivityThreadInternal.java
+++ b/core/java/android/app/ActivityThreadInternal.java
@@ -17,6 +17,7 @@
package android.app;
import android.content.ComponentCallbacks2;
+import android.content.Context;
import java.util.ArrayList;
@@ -28,7 +29,7 @@ import java.util.ArrayList;
interface ActivityThreadInternal {
ContextImpl getSystemContext();
- ContextImpl getSystemUiContextNoCreate();
+ Context getSystemUiContextNoCreate();
boolean isInDensityCompatMode();
diff --git a/core/java/android/app/ApplicationStartInfo.java b/core/java/android/app/ApplicationStartInfo.java
index 3214bd8f01fc..2e8031dd22fe 100644
--- a/core/java/android/app/ApplicationStartInfo.java
+++ b/core/java/android/app/ApplicationStartInfo.java
@@ -840,7 +840,9 @@ public final class ApplicationStartInfo implements Parcelable {
* @hide
*/
// LINT.IfChange(write_proto)
- public void writeToProto(ProtoOutputStream proto, long fieldId) throws IOException {
+ public void writeToProto(ProtoOutputStream proto, long fieldId,
+ ByteArrayOutputStream byteArrayOutputStream, ObjectOutputStream objectOutputStream,
+ TypedXmlSerializer typedXmlSerializer) throws IOException {
final long token = proto.start(fieldId);
proto.write(ApplicationStartInfoProto.PID, mPid);
proto.write(ApplicationStartInfoProto.REAL_UID, mRealUid);
@@ -850,38 +852,38 @@ public final class ApplicationStartInfo implements Parcelable {
proto.write(ApplicationStartInfoProto.STARTUP_STATE, mStartupState);
proto.write(ApplicationStartInfoProto.REASON, mReason);
if (mStartupTimestampsNs != null && mStartupTimestampsNs.size() > 0) {
- ByteArrayOutputStream timestampsBytes = new ByteArrayOutputStream();
- ObjectOutputStream timestampsOut = new ObjectOutputStream(timestampsBytes);
- TypedXmlSerializer serializer = Xml.resolveSerializer(timestampsOut);
- serializer.startDocument(null, true);
- serializer.startTag(null, PROTO_SERIALIZER_ATTRIBUTE_TIMESTAMPS);
+ byteArrayOutputStream = new ByteArrayOutputStream();
+ objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
+ typedXmlSerializer = Xml.resolveSerializer(objectOutputStream);
+ typedXmlSerializer.startDocument(null, true);
+ typedXmlSerializer.startTag(null, PROTO_SERIALIZER_ATTRIBUTE_TIMESTAMPS);
for (int i = 0; i < mStartupTimestampsNs.size(); i++) {
- serializer.startTag(null, PROTO_SERIALIZER_ATTRIBUTE_TIMESTAMP);
- serializer.attributeInt(null, PROTO_SERIALIZER_ATTRIBUTE_KEY,
+ typedXmlSerializer.startTag(null, PROTO_SERIALIZER_ATTRIBUTE_TIMESTAMP);
+ typedXmlSerializer.attributeInt(null, PROTO_SERIALIZER_ATTRIBUTE_KEY,
mStartupTimestampsNs.keyAt(i));
- serializer.attributeLong(null, PROTO_SERIALIZER_ATTRIBUTE_TS,
+ typedXmlSerializer.attributeLong(null, PROTO_SERIALIZER_ATTRIBUTE_TS,
mStartupTimestampsNs.valueAt(i));
- serializer.endTag(null, PROTO_SERIALIZER_ATTRIBUTE_TIMESTAMP);
+ typedXmlSerializer.endTag(null, PROTO_SERIALIZER_ATTRIBUTE_TIMESTAMP);
}
- serializer.endTag(null, PROTO_SERIALIZER_ATTRIBUTE_TIMESTAMPS);
- serializer.endDocument();
+ typedXmlSerializer.endTag(null, PROTO_SERIALIZER_ATTRIBUTE_TIMESTAMPS);
+ typedXmlSerializer.endDocument();
proto.write(ApplicationStartInfoProto.STARTUP_TIMESTAMPS,
- timestampsBytes.toByteArray());
- timestampsOut.close();
+ byteArrayOutputStream.toByteArray());
+ objectOutputStream.close();
}
proto.write(ApplicationStartInfoProto.START_TYPE, mStartType);
if (mStartIntent != null) {
- ByteArrayOutputStream intentBytes = new ByteArrayOutputStream();
- ObjectOutputStream intentOut = new ObjectOutputStream(intentBytes);
- TypedXmlSerializer serializer = Xml.resolveSerializer(intentOut);
- serializer.startDocument(null, true);
- serializer.startTag(null, PROTO_SERIALIZER_ATTRIBUTE_INTENT);
- mStartIntent.saveToXml(serializer);
- serializer.endTag(null, PROTO_SERIALIZER_ATTRIBUTE_INTENT);
- serializer.endDocument();
+ byteArrayOutputStream = new ByteArrayOutputStream();
+ objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
+ typedXmlSerializer = Xml.resolveSerializer(objectOutputStream);
+ typedXmlSerializer.startDocument(null, true);
+ typedXmlSerializer.startTag(null, PROTO_SERIALIZER_ATTRIBUTE_INTENT);
+ mStartIntent.saveToXml(typedXmlSerializer);
+ typedXmlSerializer.endTag(null, PROTO_SERIALIZER_ATTRIBUTE_INTENT);
+ typedXmlSerializer.endDocument();
proto.write(ApplicationStartInfoProto.START_INTENT,
- intentBytes.toByteArray());
- intentOut.close();
+ byteArrayOutputStream.toByteArray());
+ objectOutputStream.close();
}
proto.write(ApplicationStartInfoProto.LAUNCH_MODE, mLaunchMode);
proto.write(ApplicationStartInfoProto.WAS_FORCE_STOPPED, mWasForceStopped);
@@ -900,7 +902,9 @@ public final class ApplicationStartInfo implements Parcelable {
* @hide
*/
// LINT.IfChange(read_proto)
- public void readFromProto(ProtoInputStream proto, long fieldId)
+ public void readFromProto(ProtoInputStream proto, long fieldId,
+ ByteArrayInputStream byteArrayInputStream, ObjectInputStream objectInputStream,
+ TypedXmlPullParser typedXmlPullParser)
throws IOException, WireTypeMismatchException, ClassNotFoundException {
final long token = proto.start(fieldId);
while (proto.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
@@ -927,19 +931,21 @@ public final class ApplicationStartInfo implements Parcelable {
mReason = proto.readInt(ApplicationStartInfoProto.REASON);
break;
case (int) ApplicationStartInfoProto.STARTUP_TIMESTAMPS:
- ByteArrayInputStream timestampsBytes = new ByteArrayInputStream(proto.readBytes(
+ byteArrayInputStream = new ByteArrayInputStream(proto.readBytes(
ApplicationStartInfoProto.STARTUP_TIMESTAMPS));
- ObjectInputStream timestampsIn = new ObjectInputStream(timestampsBytes);
+ objectInputStream = new ObjectInputStream(byteArrayInputStream);
mStartupTimestampsNs = new ArrayMap<Integer, Long>();
try {
- TypedXmlPullParser parser = Xml.resolvePullParser(timestampsIn);
- XmlUtils.beginDocument(parser, PROTO_SERIALIZER_ATTRIBUTE_TIMESTAMPS);
- int depth = parser.getDepth();
- while (XmlUtils.nextElementWithin(parser, depth)) {
- if (PROTO_SERIALIZER_ATTRIBUTE_TIMESTAMP.equals(parser.getName())) {
- int key = parser.getAttributeInt(null,
+ typedXmlPullParser = Xml.resolvePullParser(objectInputStream);
+ XmlUtils.beginDocument(typedXmlPullParser,
+ PROTO_SERIALIZER_ATTRIBUTE_TIMESTAMPS);
+ int depth = typedXmlPullParser.getDepth();
+ while (XmlUtils.nextElementWithin(typedXmlPullParser, depth)) {
+ if (PROTO_SERIALIZER_ATTRIBUTE_TIMESTAMP.equals(
+ typedXmlPullParser.getName())) {
+ int key = typedXmlPullParser.getAttributeInt(null,
PROTO_SERIALIZER_ATTRIBUTE_KEY);
- long ts = parser.getAttributeLong(null,
+ long ts = typedXmlPullParser.getAttributeLong(null,
PROTO_SERIALIZER_ATTRIBUTE_TS);
mStartupTimestampsNs.put(key, ts);
}
@@ -947,23 +953,24 @@ public final class ApplicationStartInfo implements Parcelable {
} catch (XmlPullParserException e) {
// Timestamps lost
}
- timestampsIn.close();
+ objectInputStream.close();
break;
case (int) ApplicationStartInfoProto.START_TYPE:
mStartType = proto.readInt(ApplicationStartInfoProto.START_TYPE);
break;
case (int) ApplicationStartInfoProto.START_INTENT:
- ByteArrayInputStream intentBytes = new ByteArrayInputStream(proto.readBytes(
+ byteArrayInputStream = new ByteArrayInputStream(proto.readBytes(
ApplicationStartInfoProto.START_INTENT));
- ObjectInputStream intentIn = new ObjectInputStream(intentBytes);
+ objectInputStream = new ObjectInputStream(byteArrayInputStream);
try {
- TypedXmlPullParser parser = Xml.resolvePullParser(intentIn);
- XmlUtils.beginDocument(parser, PROTO_SERIALIZER_ATTRIBUTE_INTENT);
- mStartIntent = Intent.restoreFromXml(parser);
+ typedXmlPullParser = Xml.resolvePullParser(objectInputStream);
+ XmlUtils.beginDocument(typedXmlPullParser,
+ PROTO_SERIALIZER_ATTRIBUTE_INTENT);
+ mStartIntent = Intent.restoreFromXml(typedXmlPullParser);
} catch (XmlPullParserException e) {
// Intent lost
}
- intentIn.close();
+ objectInputStream.close();
break;
case (int) ApplicationStartInfoProto.LAUNCH_MODE:
mLaunchMode = proto.readInt(ApplicationStartInfoProto.LAUNCH_MODE);
diff --git a/core/java/android/app/AutomaticZenRule.java b/core/java/android/app/AutomaticZenRule.java
index 9d1d9c7b69de..fa977c93113a 100644
--- a/core/java/android/app/AutomaticZenRule.java
+++ b/core/java/android/app/AutomaticZenRule.java
@@ -19,7 +19,6 @@ package android.app;
import static com.android.internal.util.Preconditions.checkArgument;
import android.annotation.DrawableRes;
-import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -52,48 +51,40 @@ public final class AutomaticZenRule implements Parcelable {
* and the value returned if the true type was added in an API level higher than the calling
* app's targetSdk.
*/
- @FlaggedApi(Flags.FLAG_MODES_API)
public static final int TYPE_UNKNOWN = -1;
/**
* Rule is of a known type, but not one of the specific types.
*/
- @FlaggedApi(Flags.FLAG_MODES_API)
public static final int TYPE_OTHER = 0;
/**
* The type for rules triggered according to a time-based schedule.
*/
- @FlaggedApi(Flags.FLAG_MODES_API)
public static final int TYPE_SCHEDULE_TIME = 1;
/**
* The type for rules triggered by calendar events.
*/
- @FlaggedApi(Flags.FLAG_MODES_API)
public static final int TYPE_SCHEDULE_CALENDAR = 2;
/**
* The type for rules triggered by bedtime/sleeping, like time of day, or snore detection.
*
* <p>Only the 'Wellbeing' app may own rules of this type.
*/
- @FlaggedApi(Flags.FLAG_MODES_API)
public static final int TYPE_BEDTIME = 3;
/**
* The type for rules triggered by driving detection, like Bluetooth connections or vehicle
* sounds.
*/
- @FlaggedApi(Flags.FLAG_MODES_API)
public static final int TYPE_DRIVING = 4;
/**
* The type for rules triggered by the user entering an immersive activity, like opening an app
* using {@link WindowInsetsController#hide(int)}.
*/
- @FlaggedApi(Flags.FLAG_MODES_API)
public static final int TYPE_IMMERSIVE = 5;
/**
* The type for rules that have a {@link ZenPolicy} that implies that the
* device should not make sound and potentially hide some visual effects; may be triggered
* when entering a location where silence is requested, like a theater.
*/
- @FlaggedApi(Flags.FLAG_MODES_API)
public static final int TYPE_THEATER = 6;
/**
* The type for rules created and managed by a device owner. These rules may not be fully
@@ -101,7 +92,6 @@ public final class AutomaticZenRule implements Parcelable {
*
* <p>Only a 'Device Owner' app may own rules of this type.
*/
- @FlaggedApi(Flags.FLAG_MODES_API)
public static final int TYPE_MANAGED = 7;
/** @hide */
@@ -127,17 +117,14 @@ public final class AutomaticZenRule implements Parcelable {
/**
* @hide
*/
- @FlaggedApi(Flags.FLAG_MODES_API)
public static final int FIELD_NAME = 1 << 0;
/**
* @hide
*/
- @FlaggedApi(Flags.FLAG_MODES_API)
public static final int FIELD_INTERRUPTION_FILTER = 1 << 1;
/**
* @hide
*/
- @FlaggedApi(Flags.FLAG_MODES_API)
public static final int FIELD_ICON = 1 << 2;
private boolean enabled;
@@ -149,10 +136,8 @@ public final class AutomaticZenRule implements Parcelable {
private long creationTime;
private ZenPolicy mZenPolicy;
private ZenDeviceEffects mDeviceEffects;
- // TODO: b/310620812 - Remove this once FLAG_MODES_API is inlined.
- private boolean mModified = false;
private String mPkg;
- private int mType = Flags.modesApi() ? TYPE_UNKNOWN : 0;
+ private int mType = TYPE_UNKNOWN;
private int mIconResId;
private String mTriggerDescription;
private boolean mAllowManualInvocation;
@@ -229,8 +214,10 @@ public final class AutomaticZenRule implements Parcelable {
/**
* @hide
+ * @deprecated Do not add new usages; will be removed soon.
*/
- // TODO: b/310620812 - Remove when the flag is inlined (all system callers should use Builder).
+ // TODO: b/368247671 - Remove when modes_ui is inlined (remaining usages are in obsolete tests)
+ @Deprecated
public AutomaticZenRule(String name, ComponentName owner, ComponentName configurationActivity,
Uri conditionId, ZenPolicy policy, int interruptionFilter, boolean enabled,
long creationTime) {
@@ -251,15 +238,12 @@ public final class AutomaticZenRule implements Parcelable {
source.readParcelable(null, android.content.ComponentName.class));
creationTime = source.readLong();
mZenPolicy = source.readParcelable(null, ZenPolicy.class);
- mModified = source.readInt() == ENABLED;
mPkg = source.readString();
- if (Flags.modesApi()) {
- mDeviceEffects = source.readParcelable(null, ZenDeviceEffects.class);
- mAllowManualInvocation = source.readBoolean();
- mIconResId = source.readInt();
- mTriggerDescription = getTrimmedString(source.readString(), MAX_DESC_LENGTH);
- mType = source.readInt();
- }
+ mDeviceEffects = source.readParcelable(null, ZenDeviceEffects.class);
+ mAllowManualInvocation = source.readBoolean();
+ mIconResId = source.readInt();
+ mTriggerDescription = getTrimmedString(source.readString(), MAX_DESC_LENGTH);
+ mType = source.readInt();
}
/**
@@ -306,15 +290,6 @@ public final class AutomaticZenRule implements Parcelable {
}
/**
- * Returns whether this rule's name has been modified by the user.
- * @hide
- */
- // TODO: b/310620812 - Consider removing completely. Seems not be used anywhere except tests.
- public boolean isModified() {
- return mModified;
- }
-
- /**
* Gets the {@link ZenPolicy} applied if {@link #getInterruptionFilter()} is
* {@link NotificationManager#INTERRUPTION_FILTER_PRIORITY}.
*/
@@ -325,7 +300,6 @@ public final class AutomaticZenRule implements Parcelable {
/** Gets the {@link ZenDeviceEffects} of this rule. */
@Nullable
- @FlaggedApi(Flags.FLAG_MODES_API)
public ZenDeviceEffects getDeviceEffects() {
return mDeviceEffects;
}
@@ -378,14 +352,6 @@ public final class AutomaticZenRule implements Parcelable {
}
/**
- * Sets modified state of this rule.
- * @hide
- */
- public void setModified(boolean modified) {
- this.mModified = modified;
- }
-
- /**
* Sets the {@link ZenPolicy} applied if {@link #getInterruptionFilter()} is
* {@link NotificationManager#INTERRUPTION_FILTER_PRIORITY}.
*
@@ -404,7 +370,6 @@ public final class AutomaticZenRule implements Parcelable {
* <p>When updating an existing rule via {@link NotificationManager#updateAutomaticZenRule},
* a {@code null} value here means the previous set of effects is retained.
*/
- @FlaggedApi(Flags.FLAG_MODES_API)
public void setDeviceEffects(@Nullable ZenDeviceEffects deviceEffects) {
mDeviceEffects = deviceEffects;
}
@@ -458,7 +423,6 @@ public final class AutomaticZenRule implements Parcelable {
/**
* Gets the type of the rule.
*/
- @FlaggedApi(Flags.FLAG_MODES_API)
public @Type int getType() {
return mType;
}
@@ -467,7 +431,6 @@ public final class AutomaticZenRule implements Parcelable {
* Sets the type of the rule.
* @hide
*/
- @FlaggedApi(Flags.FLAG_MODES_API)
public void setType(@Type int type) {
mType = checkValidType(type);
}
@@ -476,7 +439,6 @@ public final class AutomaticZenRule implements Parcelable {
* Gets the user visible description of when this rule is active
* (see {@link Condition#STATE_TRUE}).
*/
- @FlaggedApi(Flags.FLAG_MODES_API)
public @Nullable String getTriggerDescription() {
return mTriggerDescription;
}
@@ -489,7 +451,6 @@ public final class AutomaticZenRule implements Parcelable {
* "When connected to [Car Name]".
* @hide
*/
- @FlaggedApi(Flags.FLAG_MODES_API)
public void setTriggerDescription(@Nullable String triggerDescription) {
mTriggerDescription = triggerDescription;
}
@@ -497,7 +458,6 @@ public final class AutomaticZenRule implements Parcelable {
/**
* Gets the resource id of the drawable icon for this rule.
*/
- @FlaggedApi(Flags.FLAG_MODES_API)
public @DrawableRes int getIconResId() {
return mIconResId;
}
@@ -506,7 +466,6 @@ public final class AutomaticZenRule implements Parcelable {
* Sets a resource id of a tintable vector drawable representing the rule in image form.
* @hide
*/
- @FlaggedApi(Flags.FLAG_MODES_API)
public void setIconResId(int iconResId) {
mIconResId = iconResId;
}
@@ -515,7 +474,6 @@ public final class AutomaticZenRule implements Parcelable {
* Gets whether this rule can be manually activated by the user even when the triggering
* condition for the rule is not met.
*/
- @FlaggedApi(Flags.FLAG_MODES_API)
public boolean isManualInvocationAllowed() {
return mAllowManualInvocation;
}
@@ -525,23 +483,18 @@ public final class AutomaticZenRule implements Parcelable {
* condition for the rule is not met.
* @hide
*/
- @FlaggedApi(Flags.FLAG_MODES_API)
public void setManualInvocationAllowed(boolean allowManualInvocation) {
mAllowManualInvocation = allowManualInvocation;
}
/** @hide */
- @FlaggedApi(Flags.FLAG_MODES_API)
public void validate() {
- if (Flags.modesApi()) {
- checkValidType(mType);
- if (mDeviceEffects != null) {
- mDeviceEffects.validate();
- }
+ checkValidType(mType);
+ if (mDeviceEffects != null) {
+ mDeviceEffects.validate();
}
}
- @FlaggedApi(Flags.FLAG_MODES_API)
@Type
private static int checkValidType(@Type int type) {
checkArgument(type >= TYPE_UNKNOWN && type <= TYPE_MANAGED,
@@ -571,39 +524,34 @@ public final class AutomaticZenRule implements Parcelable {
dest.writeParcelable(configurationActivity, 0);
dest.writeLong(creationTime);
dest.writeParcelable(mZenPolicy, 0);
- dest.writeInt(mModified ? ENABLED : DISABLED);
dest.writeString(mPkg);
- if (Flags.modesApi()) {
- dest.writeParcelable(mDeviceEffects, 0);
- dest.writeBoolean(mAllowManualInvocation);
- dest.writeInt(mIconResId);
- dest.writeString(mTriggerDescription);
- dest.writeInt(mType);
- }
+ dest.writeParcelable(mDeviceEffects, 0);
+ dest.writeBoolean(mAllowManualInvocation);
+ dest.writeInt(mIconResId);
+ dest.writeString(mTriggerDescription);
+ dest.writeInt(mType);
}
@Override
public String toString() {
- StringBuilder sb = new StringBuilder(AutomaticZenRule.class.getSimpleName()).append('[')
+ return new StringBuilder(AutomaticZenRule.class.getSimpleName())
+ .append('[')
.append("enabled=").append(enabled)
.append(",name=").append(name)
+ .append(",type=").append(mType)
.append(",interruptionFilter=").append(interruptionFilter)
.append(",pkg=").append(mPkg)
.append(",conditionId=").append(conditionId)
.append(",owner=").append(owner)
.append(",configActivity=").append(configurationActivity)
.append(",creationTime=").append(creationTime)
- .append(",mZenPolicy=").append(mZenPolicy);
-
- if (Flags.modesApi()) {
- sb.append(",deviceEffects=").append(mDeviceEffects)
- .append(",allowManualInvocation=").append(mAllowManualInvocation)
- .append(",iconResId=").append(mIconResId)
- .append(",triggerDescription=").append(mTriggerDescription)
- .append(",type=").append(mType);
- }
-
- return sb.append(']').toString();
+ .append(",mZenPolicy=").append(mZenPolicy)
+ .append(",deviceEffects=").append(mDeviceEffects)
+ .append(",allowManualInvocation=").append(mAllowManualInvocation)
+ .append(",iconResId=").append(mIconResId)
+ .append(",triggerDescription=").append(mTriggerDescription)
+ .append(']')
+ .toString();
}
/** @hide */
@@ -626,8 +574,7 @@ public final class AutomaticZenRule implements Parcelable {
if (!(o instanceof AutomaticZenRule)) return false;
if (o == this) return true;
final AutomaticZenRule other = (AutomaticZenRule) o;
- boolean finalEquals = other.enabled == enabled
- && other.mModified == mModified
+ return other.enabled == enabled
&& Objects.equals(other.name, name)
&& other.interruptionFilter == interruptionFilter
&& Objects.equals(other.conditionId, conditionId)
@@ -635,27 +582,19 @@ public final class AutomaticZenRule implements Parcelable {
&& Objects.equals(other.mZenPolicy, mZenPolicy)
&& Objects.equals(other.configurationActivity, configurationActivity)
&& Objects.equals(other.mPkg, mPkg)
- && other.creationTime == creationTime;
- if (Flags.modesApi()) {
- return finalEquals
- && Objects.equals(other.mDeviceEffects, mDeviceEffects)
- && other.mAllowManualInvocation == mAllowManualInvocation
- && other.mIconResId == mIconResId
- && Objects.equals(other.mTriggerDescription, mTriggerDescription)
- && other.mType == mType;
- }
- return finalEquals;
+ && other.creationTime == creationTime
+ && Objects.equals(other.mDeviceEffects, mDeviceEffects)
+ && other.mAllowManualInvocation == mAllowManualInvocation
+ && other.mIconResId == mIconResId
+ && Objects.equals(other.mTriggerDescription, mTriggerDescription)
+ && other.mType == mType;
}
@Override
public int hashCode() {
- if (Flags.modesApi()) {
- return Objects.hash(enabled, name, interruptionFilter, conditionId, owner,
- configurationActivity, mZenPolicy, mDeviceEffects, mModified, creationTime,
- mPkg, mAllowManualInvocation, mIconResId, mTriggerDescription, mType);
- }
return Objects.hash(enabled, name, interruptionFilter, conditionId, owner,
- configurationActivity, mZenPolicy, mModified, creationTime, mPkg);
+ configurationActivity, mZenPolicy, mDeviceEffects, creationTime,
+ mPkg, mAllowManualInvocation, mIconResId, mTriggerDescription, mType);
}
public static final @android.annotation.NonNull Parcelable.Creator<AutomaticZenRule> CREATOR
@@ -705,7 +644,6 @@ public final class AutomaticZenRule implements Parcelable {
return input;
}
- @FlaggedApi(Flags.FLAG_MODES_API)
public static final class Builder {
private String mName;
private ComponentName mOwner;
diff --git a/core/java/android/app/ConfigurationController.java b/core/java/android/app/ConfigurationController.java
index 62a50dbbd6f7..f491e3d274db 100644
--- a/core/java/android/app/ConfigurationController.java
+++ b/core/java/android/app/ConfigurationController.java
@@ -169,7 +169,7 @@ class ConfigurationController {
// Get theme outside of synchronization to avoid nested lock.
final Resources.Theme systemTheme = mActivityThread.getSystemContext().getTheme();
- final ContextImpl systemUiContext = mActivityThread.getSystemUiContextNoCreate();
+ final Context systemUiContext = mActivityThread.getSystemUiContextNoCreate();
final Resources.Theme systemUiTheme =
systemUiContext != null ? systemUiContext.getTheme() : null;
synchronized (mResourcesManager) {
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index d8aa8b3df622..0519695ff7fe 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -97,6 +97,7 @@ import android.util.Slog;
import android.view.Display;
import android.view.DisplayAdjustments;
import android.view.autofill.AutofillManager.AutofillClient;
+import android.window.SystemUiContext;
import android.window.WindowContext;
import android.window.WindowTokenClient;
import android.window.WindowTokenClientController;
@@ -3477,15 +3478,28 @@ class ContextImpl extends Context {
* {@link #createSystemContext(ActivityThread)}.
* @param displayId The ID of the display where the UI is shown.
*/
- static ContextImpl createSystemUiContext(ContextImpl systemContext, int displayId) {
+ static Context createSystemUiContext(ContextImpl systemContext, int displayId) {
+ // Step 1. Create a ContextImpl associated with its own resources.
final WindowTokenClient token = new WindowTokenClient();
final ContextImpl context = systemContext.createWindowContextBase(token, displayId);
- token.attachContext(context);
+
+ // Step 2. Create a SystemUiContext to wrap the ContextImpl, which enables to listen to
+ // its config updates.
+ final Context systemUiContext;
+ if (com.android.window.flags.Flags.trackSystemUiContextBeforeWms()) {
+ systemUiContext = new SystemUiContext(context);
+ context.setOuterContext(systemUiContext);
+ } else {
+ systemUiContext = context;
+ }
+ token.attachContext(systemUiContext);
+
+ // Step 3. Associate the SystemUiContext with the display specified with ID.
WindowTokenClientController.getInstance().attachToDisplayContent(token, displayId);
context.mContextType = CONTEXT_TYPE_SYSTEM_OR_SYSTEM_UI;
context.mOwnsToken = true;
- return context;
+ return systemUiContext;
}
@UnsupportedAppUsage
diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl
index b9255ecaf1b6..00df7246a300 100644
--- a/core/java/android/app/INotificationManager.aidl
+++ b/core/java/android/app/INotificationManager.aidl
@@ -225,8 +225,6 @@ interface INotificationManager
ZenPolicy getDefaultZenPolicy();
AutomaticZenRule getAutomaticZenRule(String id);
Map<String, AutomaticZenRule> getAutomaticZenRules();
- // TODO: b/310620812 - Remove getZenRules() when MODES_API is inlined.
- List<ZenModeConfig.ZenRule> getZenRules();
String addAutomaticZenRule(in AutomaticZenRule automaticZenRule, String pkg, boolean fromUser);
boolean updateAutomaticZenRule(String id, in AutomaticZenRule automaticZenRule, boolean fromUser);
boolean removeAutomaticZenRule(String id, boolean fromUser);
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 0ca4a329fd5a..5dca1c70a2e6 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -6002,7 +6002,7 @@ public class Notification implements Parcelable
// HUNS, which use a different layout that already accounts for that). Templates that
// have content that will be displayed under the small icon also use a different margin.
if (Flags.notificationsRedesignTemplates()
- && !p.mHeaderless && !p.mHasContentInLeftMargin) {
+ && !p.mHeaderless && !p.mSkipTopLineAlignment) {
int margin = getContentMarginTop(mContext,
R.dimen.notification_2025_content_margin_top);
contentView.setViewLayoutMargin(R.id.notification_main_column,
@@ -6594,13 +6594,8 @@ public class Notification implements Parcelable
int notifMargin = resources.getDimensionPixelSize(R.dimen.notification_2025_margin);
// Spacing between the text lines, scaling with the font size (originally in sp)
int spacing = resources.getDimensionPixelSize(spacingRes);
-
// Size of the text in the notification top line (originally in sp)
- int[] textSizeAttr = new int[] { android.R.attr.textSize };
- TypedArray typedArray = context.obtainStyledAttributes(
- R.style.TextAppearance_DeviceDefault_Notification_Info, textSizeAttr);
- int textSize = typedArray.getDimensionPixelSize(0 /* index */, -1 /* default */);
- typedArray.recycle();
+ int textSize = resources.getDimensionPixelSize(R.dimen.notification_subtext_size);
// Adding up all the values as pixels
return notifMargin + spacing + textSize;
@@ -9503,7 +9498,7 @@ public class Notification implements Parcelable
.hideLeftIcon(isOneToOne)
.hideRightIcon(hideRightIcons || isOneToOne)
.headerTextSecondary(isHeaderless ? null : conversationTitle)
- .hasContentInLeftMargin(true);
+ .skipTopLineAlignment(true);
RemoteViews contentView = mBuilder.applyStandardTemplateWithActions(
isConversationLayout
? mBuilder.getConversationLayoutResource()
@@ -14681,7 +14676,7 @@ public class Notification implements Parcelable
Icon mPromotedPicture;
boolean mCallStyleActions;
boolean mAllowTextWithProgress;
- boolean mHasContentInLeftMargin;
+ boolean mSkipTopLineAlignment;
int mTitleViewId;
int mTextViewId;
@Nullable CharSequence mTitle;
@@ -14707,7 +14702,7 @@ public class Notification implements Parcelable
mPromotedPicture = null;
mCallStyleActions = false;
mAllowTextWithProgress = false;
- mHasContentInLeftMargin = false;
+ mSkipTopLineAlignment = false;
mTitleViewId = R.id.title;
mTextViewId = R.id.text;
mTitle = null;
@@ -14774,8 +14769,8 @@ public class Notification implements Parcelable
return this;
}
- public StandardTemplateParams hasContentInLeftMargin(boolean hasContentInLeftMargin) {
- mHasContentInLeftMargin = hasContentInLeftMargin;
+ public StandardTemplateParams skipTopLineAlignment(boolean skipTopLineAlignment) {
+ mSkipTopLineAlignment = skipTopLineAlignment;
return this;
}
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index 00f896deae4b..726999a08322 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -356,7 +356,6 @@ public class NotificationManager {
* a DND component, the rule owner should activate any extra behavior that's part of that mode
* in response to this broadcast.
*/
- @FlaggedApi(Flags.FLAG_MODES_API)
public static final int AUTOMATIC_RULE_STATUS_ACTIVATED = 4;
/**
@@ -367,7 +366,6 @@ public class NotificationManager {
* longer met) and then {@link Condition#STATE_TRUE} when the trigger criteria is freshly met,
* or when the user re-activates it.
*/
- @FlaggedApi(Flags.FLAG_MODES_API)
public static final int AUTOMATIC_RULE_STATUS_DEACTIVATED = 5;
/**
@@ -415,7 +413,6 @@ public class NotificationManager {
* <p>This broadcast is only sent to registered receivers and receivers in packages that have
* been granted Notification Policy access (see {@link #isNotificationPolicyAccessGranted()}).
*/
- @FlaggedApi(Flags.FLAG_MODES_API)
@SdkConstant(SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String ACTION_CONSOLIDATED_NOTIFICATION_POLICY_CHANGED =
"android.app.action.CONSOLIDATED_NOTIFICATION_POLICY_CHANGED";
@@ -425,7 +422,6 @@ public class NotificationManager {
* {@link #ACTION_CONSOLIDATED_NOTIFICATION_POLICY_CHANGED} containing the new
* {@link Policy} value.
*/
- @FlaggedApi(Flags.FLAG_MODES_API)
public static final String EXTRA_NOTIFICATION_POLICY =
"android.app.extra.NOTIFICATION_POLICY";
@@ -1726,9 +1722,8 @@ public class NotificationManager {
* rule management to system settings/uis via
* {@link Settings#ACTION_AUTOMATIC_ZEN_RULE_SETTINGS}.
*/
- @FlaggedApi(Flags.FLAG_MODES_API)
public boolean areAutomaticZenRulesUserManaged() {
- if (Flags.modesApi() && Flags.modesUi()) {
+ if (Flags.modesUi()) {
PackageManager pm = mContext.getPackageManager();
return !pm.hasSystemFeature(PackageManager.FEATURE_WATCH)
&& !pm.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)
@@ -1748,21 +1743,7 @@ public class NotificationManager {
public Map<String, AutomaticZenRule> getAutomaticZenRules() {
INotificationManager service = service();
try {
- if (Flags.modesApi()) {
- return service.getAutomaticZenRules();
- } else {
- List<ZenModeConfig.ZenRule> rules = service.getZenRules();
- Map<String, AutomaticZenRule> ruleMap = new HashMap<>();
- for (ZenModeConfig.ZenRule rule : rules) {
- AutomaticZenRule azr = new AutomaticZenRule(rule.name, rule.component,
- rule.configurationActivity, rule.conditionId, rule.zenPolicy,
- zenModeToInterruptionFilter(rule.zenMode), rule.enabled,
- rule.creationTime);
- azr.setPackageName(rule.pkg);
- ruleMap.put(rule.id, azr);
- }
- return ruleMap;
- }
+ return service.getAutomaticZenRules();
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -1804,7 +1785,6 @@ public class NotificationManager {
/** @hide */
@TestApi
- @FlaggedApi(Flags.FLAG_MODES_API)
@NonNull
public String addAutomaticZenRule(@NonNull AutomaticZenRule automaticZenRule,
boolean fromUser) {
@@ -1840,7 +1820,6 @@ public class NotificationManager {
/** @hide */
@TestApi
- @FlaggedApi(Flags.FLAG_MODES_API)
public boolean updateAutomaticZenRule(@NonNull String id,
@NonNull AutomaticZenRule automaticZenRule, boolean fromUser) {
INotificationManager service = service();
@@ -1860,7 +1839,6 @@ public class NotificationManager {
* @param id The id of the rule
* @return the state of the rule.
*/
- @FlaggedApi(Flags.FLAG_MODES_API)
@Condition.State
public int getAutomaticZenRuleState(@NonNull String id) {
INotificationManager service = service();
@@ -1935,7 +1913,6 @@ public class NotificationManager {
/** @hide */
@TestApi
- @FlaggedApi(Flags.FLAG_MODES_API)
public boolean removeAutomaticZenRule(@NonNull String id, boolean fromUser) {
INotificationManager service = service();
try {
@@ -2326,7 +2303,6 @@ public class NotificationManager {
* @hide
*/
@TestApi
- @FlaggedApi(Flags.FLAG_MODES_API)
public @NonNull ZenPolicy getDefaultZenPolicy() {
INotificationManager service = service();
try {
@@ -2693,7 +2669,7 @@ public class NotificationManager {
/**
* @hide
*/
- public static final int STATE_CHANNELS_BYPASSING_DND = 1 << 0;
+ public static final int STATE_HAS_PRIORITY_CHANNELS = 1 << 0;
/**
* Whether the policy indicates that even priority channels are NOT permitted to bypass DND.
@@ -2918,7 +2894,7 @@ public class NotificationManager {
@Override
public String toString() {
- StringBuilder sb = new StringBuilder().append("NotificationManager.Policy[")
+ return new StringBuilder().append("NotificationManager.Policy[")
.append("priorityCategories=")
.append(priorityCategoriesToString(priorityCategories))
.append(",priorityCallSenders=")
@@ -2928,24 +2904,19 @@ public class NotificationManager {
.append(",priorityConvSenders=")
.append(conversationSendersToString(priorityConversationSenders))
.append(",suppressedVisualEffects=")
- .append(suppressedEffectsToString(suppressedVisualEffects));
- if (Flags.modesApi()) {
- sb.append(",hasPriorityChannels=");
- } else {
- sb.append(",areChannelsBypassingDnd=");
- }
- sb.append((state == STATE_UNSET
- ? "unset"
- : ((state & STATE_CHANNELS_BYPASSING_DND) != 0)
- ? "true"
- : "false"));
- if (Flags.modesApi()) {
- sb.append(",allowPriorityChannels=")
- .append((state == STATE_UNSET
- ? "unset"
- : (allowPriorityChannels() ? "true" : "false")));
- }
- return sb.append("]").toString();
+ .append(suppressedEffectsToString(suppressedVisualEffects))
+ .append(",hasPriorityChannels=")
+ .append((state == STATE_UNSET
+ ? "unset"
+ : ((state & STATE_HAS_PRIORITY_CHANNELS) != 0)
+ ? "true"
+ : "false"))
+ .append(",allowPriorityChannels=")
+ .append((state == STATE_UNSET
+ ? "unset"
+ : (allowPriorityChannels() ? "true" : "false")))
+ .append("]")
+ .toString();
}
/** @hide */
@@ -3220,7 +3191,6 @@ public class NotificationManager {
}
/** @hide **/
- @FlaggedApi(Flags.FLAG_MODES_API)
@TestApi // so CTS tests can read this state without having to use implementation detail
public boolean allowPriorityChannels() {
if (state == STATE_UNSET) {
@@ -3230,17 +3200,15 @@ public class NotificationManager {
}
/** @hide */
- @FlaggedApi(Flags.FLAG_MODES_API)
public boolean hasPriorityChannels() {
- return (state & STATE_CHANNELS_BYPASSING_DND) != 0;
+ return (state & STATE_HAS_PRIORITY_CHANNELS) != 0;
}
/** @hide **/
- @FlaggedApi(Flags.FLAG_MODES_API)
public static int policyState(boolean hasPriorityChannels, boolean allowPriorityChannels) {
int state = 0;
if (hasPriorityChannels) {
- state |= STATE_CHANNELS_BYPASSING_DND;
+ state |= STATE_HAS_PRIORITY_CHANNELS;
}
if (!allowPriorityChannels) {
state |= STATE_PRIORITY_CHANNELS_BLOCKED;
diff --git a/core/java/android/app/UiAutomation.java b/core/java/android/app/UiAutomation.java
index 7b63ab80964d..361532613047 100644
--- a/core/java/android/app/UiAutomation.java
+++ b/core/java/android/app/UiAutomation.java
@@ -122,7 +122,7 @@ public final class UiAutomation {
private static final String LOG_TAG = UiAutomation.class.getSimpleName();
private static final boolean DEBUG = false;
- private static final boolean VERBOSE = Build.IS_DEBUGGABLE;
+ private static final boolean VERBOSE = false;
private static final int CONNECTION_ID_UNDEFINED = -1;
@@ -956,10 +956,9 @@ public final class UiAutomation {
* <p>
* <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.
+ * @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 */);
@@ -972,15 +971,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
+ @SuppressLint("UnflaggedApi") // @FlaggedApi breaks previously released @TestApi, b/395889250
public boolean injectInputEvent(@NonNull InputEvent event, boolean sync,
boolean waitForAnimations) {
try {
@@ -1003,9 +1008,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
+ @SuppressLint("UnflaggedApi") // @FlaggedApi breaks previously released @TestApi, b/395889250
public void injectInputEventToInputFilter(@NonNull InputEvent event) {
try {
mUiAutomationConnection.injectInputEventToInputFilter(event);
diff --git a/core/java/android/app/UiModeManager.java b/core/java/android/app/UiModeManager.java
index f6c789d51aee..33466dd79be1 100644
--- a/core/java/android/app/UiModeManager.java
+++ b/core/java/android/app/UiModeManager.java
@@ -312,7 +312,6 @@ public class UiModeManager {
* #getAttentionModeThemeOverlay()}: Keeps night mode as set by {@link #setNightMode(int)}.
* @hide
*/
- @FlaggedApi(Flags.FLAG_MODES_API)
@TestApi
public static final int MODE_ATTENTION_THEME_OVERLAY_OFF = 1000;
@@ -321,7 +320,6 @@ public class UiModeManager {
* #getAttentionModeThemeOverlay()}: Maintains night mode always on.
* @hide
*/
- @FlaggedApi(Flags.FLAG_MODES_API)
@TestApi
public static final int MODE_ATTENTION_THEME_OVERLAY_NIGHT = 1001;
@@ -330,7 +328,6 @@ public class UiModeManager {
* #getAttentionModeThemeOverlay()}: Maintains night mode always off (Light).
* @hide
*/
- @FlaggedApi(Flags.FLAG_MODES_API)
@TestApi
public static final int MODE_ATTENTION_THEME_OVERLAY_DAY = 1002;
@@ -338,7 +335,6 @@ public class UiModeManager {
* Constant for {@link #getAttentionModeThemeOverlay()}: Error communication with server.
* @hide
*/
- @FlaggedApi(Flags.FLAG_MODES_API)
@TestApi
public static final int MODE_ATTENTION_THEME_OVERLAY_UNKNOWN = -1;
@@ -940,7 +936,6 @@ public class UiModeManager {
* {@code AttentionModeThemeOverlayType}.
* @hide
*/
- @FlaggedApi(Flags.FLAG_MODES_API)
@RequiresPermission(android.Manifest.permission.MODIFY_DAY_NIGHT_MODE)
public void setAttentionModeThemeOverlay(
@AttentionModeThemeOverlayType int attentionModeThemeOverlayType) {
@@ -967,7 +962,6 @@ public class UiModeManager {
*
* @hide
*/
- @FlaggedApi(Flags.FLAG_MODES_API)
@TestApi
@RequiresPermission(android.Manifest.permission.MODIFY_DAY_NIGHT_MODE)
public @AttentionModeThemeOverlayReturnType int getAttentionModeThemeOverlay() {
diff --git a/core/java/android/app/backup/BackupRestoreEventLogger.java b/core/java/android/app/backup/BackupRestoreEventLogger.java
index 112c5fd808ef..8bde3a5f2efa 100644
--- a/core/java/android/app/backup/BackupRestoreEventLogger.java
+++ b/core/java/android/app/backup/BackupRestoreEventLogger.java
@@ -34,9 +34,11 @@ import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
/**
* Class to log B&R stats for each data type that is backed up and restored by the calling app.
@@ -325,6 +327,21 @@ public final class BackupRestoreEventLogger {
}
}
+ /** @hide */
+ public static String toString(DataTypeResult result) {
+ Objects.requireNonNull(result, "result cannot be null");
+ StringBuilder string = new StringBuilder("type=").append(result.mDataType)
+ .append(", successCount=").append(result.mSuccessCount)
+ .append(", failCount=").append(result.mFailCount);
+ if (!result.mErrors.isEmpty()) {
+ string.append(", errors=").append(result.mErrors);
+ }
+ if (result.mMetadataHash != null) {
+ string.append(", metadataHash=").append(Arrays.toString(result.mMetadataHash));
+ }
+ return string.toString();
+ }
+
/**
* Encapsulate logging results for a single data type.
*/
diff --git a/core/java/android/app/jank/JankDataProcessor.java b/core/java/android/app/jank/JankDataProcessor.java
index b4c293eeb695..7718d159896e 100644
--- a/core/java/android/app/jank/JankDataProcessor.java
+++ b/core/java/android/app/jank/JankDataProcessor.java
@@ -34,11 +34,13 @@ import java.util.List;
/**
* This class is responsible for associating frames received from SurfaceFlinger to active widget
* states and logging those states back to the platform.
+ *
* @hide
*/
@FlaggedApi(Flags.FLAG_DETAILED_APP_JANK_METRICS_API)
public class JankDataProcessor {
-
+ private static final String TAG = "JankDataProcessor";
+ private static final boolean DEBUG_LOGGING = false;
private static final int MAX_IN_MEMORY_STATS = 25;
private static final int LOG_BATCH_FREQUENCY = 50;
private int mCurrentBatchCount = 0;
@@ -54,9 +56,10 @@ public class JankDataProcessor {
/**
* Called once per batch of JankData.
- * @param jankData data received from SurfaceFlinger to be processed
+ *
+ * @param jankData data received from SurfaceFlinger to be processed
* @param activityName name of the activity that is tracking jank metrics.
- * @param appUid the uid of the app.
+ * @param appUid the uid of the app.
*/
public void processJankData(List<JankData> jankData, String activityName, int appUid) {
// add all the previous and active states to the pending states list.
@@ -211,8 +214,6 @@ public class JankDataProcessor {
* clear any pending widget states.
*/
public void logMetricCounts() {
- //TODO b/374607503 when api changes are in add enum mapping for category and state.
-
try {
mPendingJankStats.values().forEach(stat -> {
FrameworkStatsLog.write(
@@ -221,15 +222,16 @@ public class JankDataProcessor {
/*activity name*/ stat.getActivityName(),
/*widget id*/ stat.getWidgetId(),
/*refresh rate*/ stat.getRefreshRate(),
- /*widget category*/ 0,
- /*widget state*/ 0,
+ /*widget category*/ widgetCategoryToInt(stat.getWidgetCategory()),
+ /*widget state*/ widgetStateToInt(stat.getWidgetState()),
/*total frames*/ stat.getTotalFrames(),
/*janky frames*/ stat.getJankyFrames(),
- /*histogram*/ stat.mFrameOverrunBuckets);
+ /*histogram*/ stat.getFrameOverrunBuckets());
Log.d(stat.mActivityName, stat.toString());
// return the pending stat to the pool it will be reset the next time its
// used.
mPendingJankStatsPool.release(stat);
+
}
);
// All stats have been recorded and added back to the pool for reuse, clear the pending
@@ -241,6 +243,96 @@ public class JankDataProcessor {
}
}
+ private int widgetCategoryToInt(String widgetCategory) {
+ switch (widgetCategory) {
+ case AppJankStats.WIDGET_CATEGORY_SCROLL -> {
+ return FrameworkStatsLog
+ .JANK_FRAME_COUNT_BY_WIDGET_REPORTED__WIDGET_STATE__SCROLLING;
+ }
+ case AppJankStats.WIDGET_CATEGORY_ANIMATION -> {
+ return FrameworkStatsLog
+ .JANK_FRAME_COUNT_BY_WIDGET_REPORTED__WIDGET_TYPE__ANIMATION;
+ }
+ case AppJankStats.WIDGET_CATEGORY_MEDIA -> {
+ return FrameworkStatsLog
+ .JANK_FRAME_COUNT_BY_WIDGET_REPORTED__WIDGET_TYPE__MEDIA;
+ }
+ case AppJankStats.WIDGET_CATEGORY_NAVIGATION -> {
+ return FrameworkStatsLog
+ .JANK_FRAME_COUNT_BY_WIDGET_REPORTED__WIDGET_TYPE__NAVIGATION;
+ }
+ case AppJankStats.WIDGET_CATEGORY_KEYBOARD -> {
+ return FrameworkStatsLog
+ .JANK_FRAME_COUNT_BY_WIDGET_REPORTED__WIDGET_TYPE__KEYBOARD;
+ }
+ case AppJankStats.WIDGET_CATEGORY_OTHER -> {
+ return FrameworkStatsLog
+ .JANK_FRAME_COUNT_BY_WIDGET_REPORTED__WIDGET_TYPE__OTHER;
+ }
+ default -> {
+ if (DEBUG_LOGGING) {
+ Log.d(TAG, "Default Category Logged: "
+ + AppJankStats.WIDGET_CATEGORY_UNSPECIFIED);
+ }
+ return FrameworkStatsLog
+ .JANK_FRAME_COUNT_BY_WIDGET_REPORTED__WIDGET_TYPE__WIDGET_CATEGORY_UNSPECIFIED;
+ }
+ }
+ }
+
+ private int widgetStateToInt(String widgetState) {
+ switch (widgetState) {
+ case AppJankStats.WIDGET_STATE_NONE -> {
+ return FrameworkStatsLog
+ .JANK_FRAME_COUNT_BY_WIDGET_REPORTED__WIDGET_STATE__NONE;
+ }
+ case AppJankStats.WIDGET_STATE_SCROLLING -> {
+ return FrameworkStatsLog
+ .JANK_FRAME_COUNT_BY_WIDGET_REPORTED__WIDGET_STATE__SCROLLING;
+ }
+ case AppJankStats.WIDGET_STATE_FLINGING -> {
+ return FrameworkStatsLog
+ .JANK_FRAME_COUNT_BY_WIDGET_REPORTED__WIDGET_STATE__FLINGING;
+ }
+ case AppJankStats.WIDGET_STATE_SWIPING -> {
+ return FrameworkStatsLog
+ .JANK_FRAME_COUNT_BY_WIDGET_REPORTED__WIDGET_STATE__SWIPING;
+ }
+ case AppJankStats.WIDGET_STATE_DRAGGING -> {
+ return FrameworkStatsLog
+ .JANK_FRAME_COUNT_BY_WIDGET_REPORTED__WIDGET_STATE__DRAGGING;
+ }
+ case AppJankStats.WIDGET_STATE_ZOOMING -> {
+ return FrameworkStatsLog
+ .JANK_FRAME_COUNT_BY_WIDGET_REPORTED__WIDGET_STATE__ZOOMING;
+ }
+ case AppJankStats.WIDGET_STATE_ANIMATING -> {
+ return FrameworkStatsLog
+ .JANK_FRAME_COUNT_BY_WIDGET_REPORTED__WIDGET_STATE__ANIMATING;
+ }
+ case AppJankStats.WIDGET_STATE_PLAYBACK -> {
+ return FrameworkStatsLog
+ .JANK_FRAME_COUNT_BY_WIDGET_REPORTED__WIDGET_STATE__PLAYBACK;
+ }
+ case AppJankStats.WIDGET_STATE_TAPPING -> {
+ return FrameworkStatsLog
+ .JANK_FRAME_COUNT_BY_WIDGET_REPORTED__WIDGET_STATE__TAPPING;
+ }
+ case AppJankStats.WIDGET_STATE_PREDICTIVE_BACK -> {
+ return FrameworkStatsLog
+ .JANK_FRAME_COUNT_BY_WIDGET_REPORTED__WIDGET_STATE__PREDICTIVE_BACK;
+ }
+ default -> {
+ if (DEBUG_LOGGING) {
+ Log.d(TAG, "Default State Logged: "
+ + AppJankStats.WIDGET_STATE_UNSPECIFIED);
+ }
+ return FrameworkStatsLog
+ .JANK_FRAME_COUNT_BY_WIDGET_REPORTED__WIDGET_STATE__WIDGET_STATE_UNSPECIFIED;
+ }
+ }
+ }
+
public static final class PendingJankStat {
private static final int NANOS_PER_MS = 1000000;
public long processedVsyncId = -1;
@@ -268,7 +360,7 @@ public class JankDataProcessor {
private int mRefreshRate;
- private static final int[] sFrameOverrunHistogramBounds = {
+ private static final int[] sFrameOverrunHistogramBounds = {
Integer.MIN_VALUE, -200, -150, -100, -90, -80, -70, -60, -50, -40, -30, -25, -20,
-18, -16, -14, -12, -10, -8, -6, -4, -2, 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 25,
30, 40, 50, 60, 70, 80, 90, 100, 150, 200, 300, 400, 500, 600, 700, 800, 900, 1000,
@@ -279,6 +371,7 @@ public class JankDataProcessor {
// Histogram of frame duration overruns encoded in predetermined buckets.
public PendingJankStat() {
}
+
public long getProcessedVsyncId() {
return processedVsyncId;
}
@@ -422,4 +515,4 @@ public class JankDataProcessor {
}
}
-}
+} \ No newline at end of file
diff --git a/core/java/android/app/jank/JankTracker.java b/core/java/android/app/jank/JankTracker.java
index a04f96a9f6e3..9c85b09f6be3 100644
--- a/core/java/android/app/jank/JankTracker.java
+++ b/core/java/android/app/jank/JankTracker.java
@@ -20,6 +20,7 @@ import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.os.Handler;
import android.os.HandlerThread;
+import android.util.Log;
import android.view.AttachedSurfaceControl;
import android.view.Choreographer;
import android.view.SurfaceControl;
@@ -30,16 +31,22 @@ import com.android.internal.annotations.VisibleForTesting;
import java.util.ArrayList;
import java.util.HashMap;
+import java.util.List;
/**
* This class is responsible for registering callbacks that will receive JankData batches.
* It handles managing the background thread that JankData will be processed on. As well as acting
* as an intermediary between widgets and the state tracker, routing state changes to the tracker.
+ *
* @hide
*/
@FlaggedApi(Flags.FLAG_DETAILED_APP_JANK_METRICS_API)
public class JankTracker {
-
+ private static final boolean DEBUG = false;
+ private static final String DEBUG_KEY = "JANKTRACKER";
+ // How long to delay the JankData listener registration.
+ //TODO b/394956095 see if this can be reduced or eliminated.
+ private static final int REGISTRATION_DELAY_MS = 1000;
// Tracks states reported by widgets.
private StateTracker mStateTracker;
// Processes JankData batches and associates frames to widget states.
@@ -49,9 +56,6 @@ public class JankTracker {
private HandlerThread mHandlerThread = new HandlerThread("AppJankTracker");
private Handler mHandler = null;
- // Needed so we know when the view is attached to a window.
- private ViewTreeObserver mViewTreeObserver;
-
// Handle to a registered OnJankData listener.
private SurfaceControl.OnJankDataListenerRegistration mJankDataListenerRegistration;
@@ -76,6 +80,40 @@ public class JankTracker {
*/
private boolean mListenersRegistered = false;
+ @FlaggedApi(com.android.window.flags.Flags.FLAG_JANK_API)
+ private final SurfaceControl.OnJankDataListener mJankDataListener =
+ new SurfaceControl.OnJankDataListener() {
+ @Override
+ public void onJankDataAvailable(
+ @androidx.annotation.NonNull List<SurfaceControl.JankData> jankData) {
+ if (mJankDataProcessor == null) return;
+ mJankDataProcessor.processJankData(jankData, mActivityName, mAppUid);
+ }
+ };
+
+ private final ViewTreeObserver.OnWindowAttachListener mOnWindowAttachListener =
+ new ViewTreeObserver.OnWindowAttachListener() {
+ @Override
+ public void onWindowAttached() {
+ getHandler().postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ mDecorView.getViewTreeObserver()
+ .removeOnWindowAttachListener(mOnWindowAttachListener);
+ registerForJankData();
+ }
+ }, REGISTRATION_DELAY_MS);
+ }
+
+ // Leave this empty. Only need to know when the DecorView is attached to the Window
+ // in order to get a handle to AttachedSurfaceControl. There is no need to tie
+ // anything to when the view is detached as all un-registration code is tied to
+ // the lifecycle of the enclosing activity.
+ @Override
+ public void onWindowDetached() {
+
+ }
+ };
public JankTracker(Choreographer choreographer, View decorView) {
mStateTracker = new StateTracker(choreographer);
@@ -108,9 +146,10 @@ public class JankTracker {
/**
* Will add the widget category, id and state as a UI state to associate frames to it.
+ *
* @param widgetCategory preselected general widget category
- * @param widgetId developer defined widget id if available.
- * @param widgetState the current active widget state.
+ * @param widgetId developer defined widget id if available.
+ * @param widgetState the current active widget state.
*/
public void addUiState(String widgetCategory, String widgetId, String widgetState) {
if (!shouldTrack()) return;
@@ -121,9 +160,10 @@ public class JankTracker {
/**
* Will remove the widget category, id and state as a ui state and no longer attribute frames
* to it.
+ *
* @param widgetCategory preselected general widget category
- * @param widgetId developer defined widget id if available.
- * @param widgetState no longer active widget state.
+ * @param widgetId developer defined widget id if available.
+ * @param widgetState no longer active widget state.
*/
public void removeUiState(String widgetCategory, String widgetId, String widgetState) {
if (!shouldTrack()) return;
@@ -133,10 +173,11 @@ public class JankTracker {
/**
* Call to update a jank state to a different state.
+ *
* @param widgetCategory preselected general widget category.
- * @param widgetId developer defined widget id if available.
- * @param currentState current state of the widget.
- * @param nextState the state the widget will be in.
+ * @param widgetId developer defined widget id if available.
+ * @param currentState current state of the widget.
+ * @param nextState the state the widget will be in.
*/
public void updateUiState(String widgetCategory, String widgetId, String currentState,
String nextState) {
@@ -150,10 +191,11 @@ public class JankTracker {
*/
public void enableAppJankTracking() {
// Add the activity as a state, this will ensure we track frames to the activity without the
- // need of a decorated widget to be used.
+ // need for a decorated widget to be used.
// TODO b/376116199 replace "NONE" with UNSPECIFIED once the API changes are merged.
mStateTracker.putState("NONE", mActivityName, "NONE");
mTrackingEnabled = true;
+ registerForJankData();
}
/**
@@ -163,10 +205,12 @@ public class JankTracker {
mTrackingEnabled = false;
// TODO b/376116199 replace "NONE" with UNSPECIFIED once the API changes are merged.
mStateTracker.removeState("NONE", mActivityName, "NONE");
+ unregisterForJankData();
}
/**
* Retrieve all pending widget states, this is intended for testing purposes only.
+ *
* @param stateDataList the ArrayList that will be populated with the pending states.
*/
@VisibleForTesting
@@ -190,16 +234,35 @@ public class JankTracker {
@VisibleForTesting
public void forceListenerRegistration() {
mSurfaceControl = mDecorView.getRootSurfaceControl();
- registerForJankData();
- // TODO b/376116199 Check if registration is good.
- mListenersRegistered = true;
+ registerJankDataListener();
+ }
+
+ private void unregisterForJankData() {
+ if (mJankDataListenerRegistration == null) return;
+
+ if (com.android.window.flags.Flags.jankApi()) {
+ mJankDataListenerRegistration.release();
+ }
+ mJankDataListenerRegistration = null;
+ mListenersRegistered = false;
}
private void registerForJankData() {
- if (mSurfaceControl == null) return;
- /*
- TODO b/376115668 Register for JankData batches from new JankTracking API
- */
+ if (mDecorView == null) return;
+
+ mSurfaceControl = mDecorView.getRootSurfaceControl();
+
+ if (mSurfaceControl == null || mListenersRegistered) return;
+
+ // Wait a short time before registering the listener. During development it was observed
+ // that if a listener is registered too quickly after a hot or warm start no data is
+ // received b/394956095.
+ getHandler().postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ registerJankDataListener();
+ }
+ }, REGISTRATION_DELAY_MS);
}
/**
@@ -218,23 +281,30 @@ public class JankTracker {
*/
private void registerWindowListeners() {
if (mDecorView == null) return;
- mViewTreeObserver = mDecorView.getViewTreeObserver();
- mViewTreeObserver.addOnWindowAttachListener(new ViewTreeObserver.OnWindowAttachListener() {
- @Override
- public void onWindowAttached() {
- getHandler().postDelayed(new Runnable() {
- @Override
- public void run() {
- forceListenerRegistration();
- }
- }, 1000);
+ mDecorView.getViewTreeObserver().addOnWindowAttachListener(mOnWindowAttachListener);
+ }
+
+ private void registerJankDataListener() {
+ if (mSurfaceControl == null) {
+ if (DEBUG) {
+ Log.d(DEBUG_KEY, "SurfaceControl is Null");
}
+ return;
+ }
- @Override
- public void onWindowDetached() {
- // TODO b/376116199 do we un-register the callback or just not process the data.
+ if (com.android.window.flags.Flags.jankApi()) {
+ mJankDataListenerRegistration = mSurfaceControl.registerOnJankDataListener(
+ mHandlerThread.getThreadExecutor(), mJankDataListener);
+
+ if (mJankDataListenerRegistration
+ == SurfaceControl.OnJankDataListenerRegistration.NONE) {
+ if (DEBUG) {
+ Log.d(DEBUG_KEY, "OnJankDataListenerRegistration is assigned NONE");
+ }
+ return;
}
- });
+ mListenersRegistered = true;
+ }
}
private Handler getHandler() {
diff --git a/core/java/android/app/notification.aconfig b/core/java/android/app/notification.aconfig
index 9d8ab03982e6..8e6b88c66408 100644
--- a/core/java/android/app/notification.aconfig
+++ b/core/java/android/app/notification.aconfig
@@ -26,6 +26,7 @@ flag {
bug: "378660052"
}
+# Flag for finalized API: In Nextfood but exported (and therefore must stay).
flag {
name: "modes_api"
is_exported: true
@@ -82,6 +83,13 @@ flag {
}
flag {
+ name: "modes_cleanup_implicit"
+ namespace: "systemui"
+ description: "Deletes implicit modes if never customized and not used for some time. Depends on MODES_UI"
+ bug: "394087495"
+}
+
+flag {
name: "api_tvextender"
is_exported: true
namespace: "systemui"
@@ -321,7 +329,7 @@ flag {
name: "no_sbnholder"
namespace: "systemui"
description: "removes sbnholder from NLS"
- bug: "362981561"
+ bug: "378128805"
}
flag {
diff --git a/core/java/android/app/supervision/ISupervisionManager.aidl b/core/java/android/app/supervision/ISupervisionManager.aidl
index e583302e4d3b..2f67a8abcd17 100644
--- a/core/java/android/app/supervision/ISupervisionManager.aidl
+++ b/core/java/android/app/supervision/ISupervisionManager.aidl
@@ -16,11 +16,14 @@
package android.app.supervision;
+import android.content.Intent;
+
/**
* Internal IPC interface to the supervision service.
* {@hide}
*/
interface ISupervisionManager {
+ Intent createConfirmSupervisionCredentialsIntent();
boolean isSupervisionEnabledForUser(int userId);
void setSupervisionEnabledForUser(int userId, boolean enabled);
String getActiveSupervisionAppPackage(int userId);
diff --git a/core/java/android/app/supervision/SupervisionManager.java b/core/java/android/app/supervision/SupervisionManager.java
index d30705536045..0270edf080a9 100644
--- a/core/java/android/app/supervision/SupervisionManager.java
+++ b/core/java/android/app/supervision/SupervisionManager.java
@@ -16,13 +16,22 @@
package android.app.supervision;
+import static android.Manifest.permission.INTERACT_ACROSS_USERS;
+import static android.Manifest.permission.MANAGE_USERS;
+import static android.Manifest.permission.QUERY_USERS;
+
+import android.annotation.FlaggedApi;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
import android.annotation.SystemService;
+import android.annotation.TestApi;
import android.annotation.UserHandleAware;
import android.annotation.UserIdInt;
+import android.app.supervision.flags.Flags;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
+import android.content.Intent;
import android.os.RemoteException;
/**
@@ -31,6 +40,8 @@ import android.os.RemoteException;
* @hide
*/
@SystemService(Context.SUPERVISION_SERVICE)
+@SystemApi
+@FlaggedApi(Flags.FLAG_SUPERVISION_MANAGER_APIS)
public class SupervisionManager {
private final Context mContext;
@Nullable private final ISupervisionManager mService;
@@ -47,7 +58,8 @@ public class SupervisionManager {
*
* @hide
*/
- public static final String ACTION_ENABLE_SUPERVISION = "android.app.action.ENABLE_SUPERVISION";
+ public static final String ACTION_ENABLE_SUPERVISION =
+ "android.app.supervision.action.ENABLE_SUPERVISION";
/**
* Activity action: ask the human user to disable supervision for this user. Only the app that
@@ -62,7 +74,7 @@ public class SupervisionManager {
* @hide
*/
public static final String ACTION_DISABLE_SUPERVISION =
- "android.app.action.DISABLE_SUPERVISION";
+ "android.app.supervision.action.DISABLE_SUPERVISION";
/** @hide */
@UnsupportedAppUsage
@@ -72,11 +84,46 @@ public class SupervisionManager {
}
/**
+ * Creates an {@link Intent} that can be used with {@link Context#startActivity(Intent)} to
+ * launch the activity to verify supervision credentials.
+ *
+ * <p>A valid {@link Intent} is always returned if supervision is enabled at the time this API
+ * is called, the launched activity still need to perform validity checks as the supervision
+ * state can change when the activity is launched. A null intent is returned if supervision is
+ * disabled at the time of this API call.
+ *
+ * <p>A result code of {@link android.app.Activity#RESULT_OK} indicates successful verification
+ * of the supervision credentials.
+ *
+ * @hide
+ */
+ @RequiresPermission(value = android.Manifest.permission.QUERY_USERS)
+ @Nullable
+ public Intent createConfirmSupervisionCredentialsIntent() {
+ if (mService != null) {
+ try {
+ Intent result = mService.createConfirmSupervisionCredentialsIntent();
+ if (result != null) {
+ result.prepareToEnterProcess(
+ Intent.LOCAL_FLAG_FROM_SYSTEM, mContext.getAttributionSource());
+ }
+ return result;
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ return null;
+ }
+
+ /**
* Returns whether the device is supervised.
*
* @hide
*/
- @UserHandleAware
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_SUPERVISION_MANAGER_APIS)
+ @RequiresPermission(anyOf = {MANAGE_USERS, QUERY_USERS})
+ @UserHandleAware(requiresPermissionIfNotCaller = INTERACT_ACROSS_USERS)
public boolean isSupervisionEnabled() {
return isSupervisionEnabledForUser(mContext.getUserId());
}
@@ -84,14 +131,10 @@ public class SupervisionManager {
/**
* Returns whether the device is supervised.
*
- * <p>The caller must be from the same user as the target or hold the {@link
- * android.Manifest.permission#INTERACT_ACROSS_USERS} permission.
- *
* @hide
*/
- @RequiresPermission(
- value = android.Manifest.permission.INTERACT_ACROSS_USERS,
- conditional = true)
+ @RequiresPermission(anyOf = {MANAGE_USERS, QUERY_USERS})
+ @UserHandleAware(requiresPermissionIfNotCaller = INTERACT_ACROSS_USERS)
public boolean isSupervisionEnabledForUser(@UserIdInt int userId) {
if (mService != null) {
try {
@@ -108,7 +151,8 @@ public class SupervisionManager {
*
* @hide
*/
- @UserHandleAware
+ @TestApi
+ @UserHandleAware(requiresPermissionIfNotCaller = INTERACT_ACROSS_USERS)
public void setSupervisionEnabled(boolean enabled) {
setSupervisionEnabledForUser(mContext.getUserId(), enabled);
}
@@ -116,14 +160,9 @@ public class SupervisionManager {
/**
* Sets whether the device is supervised for a given user.
*
- * <p>The caller must be from the same user as the target or hold the {@link
- * android.Manifest.permission#INTERACT_ACROSS_USERS} permission.
- *
* @hide
*/
- @RequiresPermission(
- value = android.Manifest.permission.INTERACT_ACROSS_USERS,
- conditional = true)
+ @UserHandleAware(requiresPermissionIfNotCaller = INTERACT_ACROSS_USERS)
public void setSupervisionEnabledForUser(@UserIdInt int userId, boolean enabled) {
if (mService != null) {
try {
diff --git a/core/java/android/app/supervision/flags.aconfig b/core/java/android/app/supervision/flags.aconfig
index 232883cbfe00..94de03877fd7 100644
--- a/core/java/android/app/supervision/flags.aconfig
+++ b/core/java/android/app/supervision/flags.aconfig
@@ -64,3 +64,11 @@ flag {
description: "Flag that enables the Supervision pin recovery screen with Supervision settings entry point"
bug: "390500290"
}
+
+flag {
+ name: "supervision_manager_apis"
+ is_exported: true
+ namespace: "supervision"
+ description: "Flag that enables system APIs in Supervision Manager"
+ bug: "382034839"
+}
diff --git a/core/java/android/companion/virtual/flags/flags.aconfig b/core/java/android/companion/virtual/flags/flags.aconfig
index fcdb02ab5da2..ba1473cf5ed7 100644
--- a/core/java/android/companion/virtual/flags/flags.aconfig
+++ b/core/java/android/companion/virtual/flags/flags.aconfig
@@ -120,6 +120,16 @@ flag {
}
flag {
+ name: "correct_virtual_display_power_state"
+ namespace: "virtual_devices"
+ description: "Fix the virtual display power state"
+ bug: "371125136"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "vdm_settings"
namespace: "virtual_devices"
description: "Show virtual devices in Settings"
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 3391e79b2ae4..55d78f9b8c48 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -17,8 +17,8 @@
package android.content;
import static android.app.appfunctions.flags.Flags.FLAG_ENABLE_APP_FUNCTION_MANAGER;
-import static android.content.flags.Flags.FLAG_ENABLE_BIND_PACKAGE_ISOLATED_PROCESS;
import static android.app.ondeviceintelligence.flags.Flags.FLAG_ENABLE_ON_DEVICE_INTELLIGENCE_MODULE;
+import static android.content.flags.Flags.FLAG_ENABLE_BIND_PACKAGE_ISOLATED_PROCESS;
import static android.security.Flags.FLAG_SECURE_LOCKDOWN;
import android.annotation.AttrRes;
@@ -6858,6 +6858,8 @@ public abstract class Context {
* @see android.app.supervision.SupervisionManager
* @hide
*/
+ @SystemApi
+ @FlaggedApi(android.app.supervision.flags.Flags.FLAG_SUPERVISION_MANAGER_APIS)
public static final String SUPERVISION_SERVICE = "supervision";
/**
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index d64ef75a8f7c..4bbbad4d1667 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -3250,7 +3250,6 @@ public class PackageInstaller {
*/
@SystemApi
@RequiresPermission(android.Manifest.permission.MANAGE_ROLLBACKS)
- @FlaggedApi(Flags.FLAG_RECOVERABILITY_DETECTION)
public void setRollbackImpactLevel(@PackageManager.RollbackImpactLevel int impactLevel) {
if ((installFlags & PackageManager.INSTALL_ENABLE_ROLLBACK) == 0) {
throw new IllegalArgumentException(
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 6ae2df2cd7a2..f91b2474fdac 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -1619,7 +1619,6 @@ public abstract class PackageManager {
* @hide
*/
@SystemApi
- @FlaggedApi(android.content.pm.Flags.FLAG_RECOVERABILITY_DETECTION)
public static final int ROLLBACK_USER_IMPACT_LOW = 0;
/**
@@ -1629,7 +1628,6 @@ public abstract class PackageManager {
* @hide
*/
@SystemApi
- @FlaggedApi(android.content.pm.Flags.FLAG_RECOVERABILITY_DETECTION)
public static final int ROLLBACK_USER_IMPACT_HIGH = 1;
/**
@@ -1638,7 +1636,6 @@ public abstract class PackageManager {
* @hide
*/
@SystemApi
- @FlaggedApi(android.content.pm.Flags.FLAG_RECOVERABILITY_DETECTION)
public static final int ROLLBACK_USER_IMPACT_ONLY_MANUAL = 2;
/** @hide */
diff --git a/core/java/android/content/pm/multiuser.aconfig b/core/java/android/content/pm/multiuser.aconfig
index e6082d0df1f8..5c904c15e706 100644
--- a/core/java/android/content/pm/multiuser.aconfig
+++ b/core/java/android/content/pm/multiuser.aconfig
@@ -622,3 +622,10 @@ flag {
description: "Add API to logout user"
bug: "350045389"
}
+
+flag {
+ name: "enable_moving_content_into_private_space"
+ namespace: "profile_experiences"
+ description: "Enable moving content into the Private Space"
+ bug: "360066001"
+}
diff --git a/core/java/android/content/res/ApkAssets.java b/core/java/android/content/res/ApkAssets.java
index 075457885586..f538e9ffffdd 100644
--- a/core/java/android/content/res/ApkAssets.java
+++ b/core/java/android/content/res/ApkAssets.java
@@ -25,6 +25,7 @@ import android.content.res.loader.ResourcesProvider;
import android.ravenwood.annotation.RavenwoodClassLoadHook;
import android.ravenwood.annotation.RavenwoodKeepWholeClass;
import android.text.TextUtils;
+import android.util.Log;
import com.android.internal.annotations.GuardedBy;
@@ -50,6 +51,7 @@ import java.util.Objects;
@RavenwoodKeepWholeClass
@RavenwoodClassLoadHook(RavenwoodClassLoadHook.LIBANDROID_LOADING_HOOK)
public final class ApkAssets {
+ private static final boolean DEBUG = false;
/**
* The apk assets contains framework resource values specified by the system.
@@ -134,6 +136,17 @@ public final class ApkAssets {
@Nullable
private final AssetsProvider mAssets;
+ @NonNull
+ private String mName;
+
+ private static final int UPTODATE_FALSE = 0;
+ private static final int UPTODATE_TRUE = 1;
+ private static final int UPTODATE_ALWAYS_TRUE = 2;
+
+ // Start with the only value that may change later and would force a native call to
+ // double check it.
+ private int mPreviousUpToDateResult = UPTODATE_TRUE;
+
/**
* Creates a new ApkAssets instance from the given path on disk.
*
@@ -304,7 +317,7 @@ public final class ApkAssets {
private ApkAssets(@FormatType int format, @NonNull String path, @PropertyFlags int flags,
@Nullable AssetsProvider assets) throws IOException {
- this(format, flags, assets);
+ this(format, flags, assets, path);
Objects.requireNonNull(path, "path");
mNativePtr = nativeLoad(format, path, flags, assets);
mStringBlock = new StringBlock(nativeGetStringBlock(mNativePtr), true /*useSparse*/);
@@ -313,7 +326,7 @@ public final class ApkAssets {
private ApkAssets(@FormatType int format, @NonNull FileDescriptor fd,
@NonNull String friendlyName, @PropertyFlags int flags, @Nullable AssetsProvider assets)
throws IOException {
- this(format, flags, assets);
+ this(format, flags, assets, friendlyName);
Objects.requireNonNull(fd, "fd");
Objects.requireNonNull(friendlyName, "friendlyName");
mNativePtr = nativeLoadFd(format, fd, friendlyName, flags, assets);
@@ -323,7 +336,7 @@ public final class ApkAssets {
private ApkAssets(@FormatType int format, @NonNull FileDescriptor fd,
@NonNull String friendlyName, long offset, long length, @PropertyFlags int flags,
@Nullable AssetsProvider assets) throws IOException {
- this(format, flags, assets);
+ this(format, flags, assets, friendlyName);
Objects.requireNonNull(fd, "fd");
Objects.requireNonNull(friendlyName, "friendlyName");
mNativePtr = nativeLoadFdOffsets(format, fd, friendlyName, offset, length, flags, assets);
@@ -331,16 +344,17 @@ public final class ApkAssets {
}
private ApkAssets(@PropertyFlags int flags, @Nullable AssetsProvider assets) {
- this(FORMAT_APK, flags, assets);
+ this(FORMAT_APK, flags, assets, "empty");
mNativePtr = nativeLoadEmpty(flags, assets);
mStringBlock = null;
}
private ApkAssets(@FormatType int format, @PropertyFlags int flags,
- @Nullable AssetsProvider assets) {
+ @Nullable AssetsProvider assets, @NonNull String name) {
mFlags = flags;
mAssets = assets;
mIsOverlay = format == FORMAT_IDMAP;
+ if (DEBUG) mName = name;
}
@UnsupportedAppUsage
@@ -421,13 +435,41 @@ public final class ApkAssets {
}
}
+ private static double intervalMs(long beginNs, long endNs) {
+ return (endNs - beginNs) / 1000000.0;
+ }
+
/**
* Returns false if the underlying APK was changed since this ApkAssets was loaded.
*/
public boolean isUpToDate() {
+ // This function is performance-critical - it's called multiple times on every Resources
+ // object creation, and on few other cache accesses - so it's important to avoid the native
+ // call when we know for sure what it will return (which is the case for both ALWAYS_TRUE
+ // and FALSE).
+ if (mPreviousUpToDateResult != UPTODATE_TRUE) {
+ return mPreviousUpToDateResult == UPTODATE_ALWAYS_TRUE;
+ }
+ final long beforeTs, afterLockTs, afterNativeTs, afterUnlockTs;
+ if (DEBUG) beforeTs = System.nanoTime();
+ final int res;
synchronized (this) {
- return nativeIsUpToDate(mNativePtr);
+ if (DEBUG) afterLockTs = System.nanoTime();
+ res = nativeIsUpToDate(mNativePtr);
+ if (DEBUG) afterNativeTs = System.nanoTime();
+ }
+ if (DEBUG) {
+ afterUnlockTs = System.nanoTime();
+ if (afterUnlockTs - beforeTs >= 10L * 1000000) {
+ Log.d("ApkAssets", "isUpToDate(" + mName + ") took "
+ + intervalMs(beforeTs, afterUnlockTs)
+ + " ms: " + intervalMs(beforeTs, afterLockTs)
+ + " / " + intervalMs(afterLockTs, afterNativeTs)
+ + " / " + intervalMs(afterNativeTs, afterUnlockTs));
+ }
}
+ mPreviousUpToDateResult = res;
+ return res != UPTODATE_FALSE;
}
public boolean isSystem() {
@@ -487,7 +529,7 @@ public final class ApkAssets {
private static native @NonNull String nativeGetAssetPath(long ptr);
private static native @NonNull String nativeGetDebugName(long ptr);
private static native long nativeGetStringBlock(long ptr);
- @CriticalNative private static native boolean nativeIsUpToDate(long ptr);
+ @CriticalNative private static native int nativeIsUpToDate(long ptr);
private static native long nativeOpenXml(long ptr, @NonNull String fileName) throws IOException;
private static native @Nullable OverlayableInfo nativeGetOverlayableInfo(long ptr,
String overlayableName) throws IOException;
diff --git a/core/java/android/content/res/ResourceTimer.java b/core/java/android/content/res/ResourceTimer.java
index d51f64ce8106..2d1bf4d9d296 100644
--- a/core/java/android/content/res/ResourceTimer.java
+++ b/core/java/android/content/res/ResourceTimer.java
@@ -17,13 +17,10 @@
package android.content.res;
import android.annotation.NonNull;
-import android.annotation.Nullable;
-
import android.app.AppProtoEnums;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
-import android.os.ParcelFileDescriptor;
import android.os.Process;
import android.os.SystemClock;
import android.text.TextUtils;
@@ -33,6 +30,7 @@ import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.FastPrintWriter;
import com.android.internal.util.FrameworkStatsLog;
+import java.io.FileDescriptor;
import java.io.FileOutputStream;
import java.io.PrintWriter;
import java.util.Arrays;
@@ -277,38 +275,40 @@ public final class ResourceTimer {
* Update the metrics information and dump it.
* @hide
*/
- public static void dumpTimers(@NonNull ParcelFileDescriptor pfd, @Nullable String[] args) {
- FileOutputStream fout = new FileOutputStream(pfd.getFileDescriptor());
- PrintWriter pw = new FastPrintWriter(fout);
- synchronized (sLock) {
- if (!sEnabled || (sConfig == null)) {
+ public static void dumpTimers(@NonNull FileDescriptor fd, String... args) {
+ try (PrintWriter pw = new FastPrintWriter(new FileOutputStream(fd))) {
+ pw.println("\nDumping ResourceTimers");
+
+ final boolean enabled;
+ synchronized (sLock) {
+ enabled = sEnabled && sConfig != null;
+ }
+ if (!enabled) {
pw.println(" Timers are not enabled in this process");
- pw.flush();
return;
}
- }
- // Look for the --refresh switch. If the switch is present, then sTimers is updated.
- // Otherwise, the current value of sTimers is displayed.
- boolean refresh = Arrays.asList(args).contains("-refresh");
-
- synchronized (sLock) {
- update(refresh);
- long runtime = sLastUpdated - sProcessStart;
- pw.format(" config runtime=%d proc=%s\n", runtime, Process.myProcessName());
- for (int i = 0; i < sTimers.length; i++) {
- Timer t = sTimers[i];
- if (t.count != 0) {
- String name = sConfig.timers[i];
- pw.format(" stats timer=%s cnt=%d avg=%d min=%d max=%d pval=%s "
- + "largest=%s\n",
- name, t.count, t.total / t.count, t.mintime, t.maxtime,
- packedString(t.percentile),
- packedString(t.largest));
+ // Look for the --refresh switch. If the switch is present, then sTimers is updated.
+ // Otherwise, the current value of sTimers is displayed.
+ boolean refresh = Arrays.asList(args).contains("-refresh");
+
+ synchronized (sLock) {
+ update(refresh);
+ long runtime = sLastUpdated - sProcessStart;
+ pw.format(" config runtime=%d proc=%s\n", runtime, Process.myProcessName());
+ for (int i = 0; i < sTimers.length; i++) {
+ Timer t = sTimers[i];
+ if (t.count != 0) {
+ String name = sConfig.timers[i];
+ pw.format(" stats timer=%s cnt=%d avg=%d min=%d max=%d pval=%s "
+ + "largest=%s\n",
+ name, t.count, t.total / t.count, t.mintime, t.maxtime,
+ packedString(t.percentile),
+ packedString(t.largest));
+ }
}
}
}
- pw.flush();
}
// Enable (or disabled) the runtime timers. Note that timers are disabled by default. This
diff --git a/core/java/android/content/res/XmlBlock.java b/core/java/android/content/res/XmlBlock.java
index 40c532498fbc..36fa05905814 100644
--- a/core/java/android/content/res/XmlBlock.java
+++ b/core/java/android/content/res/XmlBlock.java
@@ -29,6 +29,8 @@ import android.ravenwood.annotation.RavenwoodKeepWholeClass;
import android.util.TypedValue;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.pm.pkg.component.AconfigFlags;
+import com.android.internal.pm.pkg.parsing.ParsingPackageUtils;
import com.android.internal.util.XmlUtils;
import dalvik.annotation.optimization.CriticalNative;
@@ -50,6 +52,7 @@ import java.io.Reader;
@RavenwoodClassLoadHook(RavenwoodClassLoadHook.LIBANDROID_LOADING_HOOK)
public final class XmlBlock implements AutoCloseable {
private static final boolean DEBUG=false;
+ public static final String ANDROID_RESOURCES = "http://schemas.android.com/apk/res/android";
@UnsupportedAppUsage
public XmlBlock(byte[] data) {
@@ -343,6 +346,23 @@ public final class XmlBlock implements AutoCloseable {
if (ev == ERROR_BAD_DOCUMENT) {
throw new XmlPullParserException("Corrupt XML binary file");
}
+ if (useLayoutReadwrite() && ev == START_TAG) {
+ AconfigFlags flags = ParsingPackageUtils.getAconfigFlags();
+ if (flags.skipCurrentElement(/* pkg= */ null, this)) {
+ int depth = 1;
+ while (depth > 0) {
+ int ev2 = nativeNext(mParseState);
+ if (ev2 == ERROR_BAD_DOCUMENT) {
+ throw new XmlPullParserException("Corrupt XML binary file");
+ } else if (ev2 == START_TAG) {
+ depth++;
+ } else if (ev2 == END_TAG) {
+ depth--;
+ }
+ }
+ return next();
+ }
+ }
if (mDecNextDepth) {
mDepth--;
mDecNextDepth = false;
@@ -368,6 +388,18 @@ public final class XmlBlock implements AutoCloseable {
}
return ev;
}
+
+ // Until ravenwood supports AconfigFlags, we just don't do layoutReadwriteFlags().
+ @android.ravenwood.annotation.RavenwoodReplace(
+ bug = 396458006, blockedBy = AconfigFlags.class)
+ private static boolean useLayoutReadwrite() {
+ return Flags.layoutReadwriteFlags();
+ }
+
+ private static boolean useLayoutReadwrite$ravenwood() {
+ return false;
+ }
+
public void require(int type, String namespace, String name) throws XmlPullParserException,IOException {
if (type != getEventType()
|| (namespace != null && !namespace.equals( getNamespace () ) )
diff --git a/core/java/android/hardware/display/ColorDisplayManager.java b/core/java/android/hardware/display/ColorDisplayManager.java
index 0d9db1fa3c91..7debab946bc0 100644
--- a/core/java/android/hardware/display/ColorDisplayManager.java
+++ b/core/java/android/hardware/display/ColorDisplayManager.java
@@ -17,7 +17,6 @@
package android.hardware.display;
import android.Manifest;
-import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.IntRange;
import android.annotation.NonNull;
@@ -401,7 +400,6 @@ public final class ColorDisplayManager {
* @hide
*/
@TestApi
- @FlaggedApi(android.app.Flags.FLAG_MODES_API)
@RequiresPermission(Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS)
public boolean isSaturationActivated() {
return mManager.isSaturationActivated();
diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java
index d8919160320a..7850e377ec4d 100644
--- a/core/java/android/hardware/display/DisplayManager.java
+++ b/core/java/android/hardware/display/DisplayManager.java
@@ -612,6 +612,7 @@ public final class DisplayManager {
PRIVATE_EVENT_TYPE_DISPLAY_BRIGHTNESS,
PRIVATE_EVENT_TYPE_HDR_SDR_RATIO_CHANGED,
PRIVATE_EVENT_TYPE_DISPLAY_CONNECTION_CHANGED,
+ PRIVATE_EVENT_TYPE_DISPLAY_COMMITTED_STATE_CHANGED
})
@Retention(RetentionPolicy.SOURCE)
public @interface PrivateEventType {}
@@ -677,7 +678,7 @@ public final class DisplayManager {
* through the {@link DisplayListener#onDisplayChanged} callback method. New brightness
* values can be retrieved via {@link android.view.Display#getBrightnessInfo()}.
*
- * @see #registerDisplayListener(DisplayListener, Handler, long)
+ * @see #registerDisplayListener(DisplayListener, Handler, long, long)
*
* @hide
*/
@@ -690,7 +691,7 @@ public final class DisplayManager {
*
* Requires that {@link Display#isHdrSdrRatioAvailable()} is true.
*
- * @see #registerDisplayListener(DisplayListener, Handler, long)
+ * @see #registerDisplayListener(DisplayListener, Handler, long, long)
*
* @hide
*/
@@ -699,11 +700,19 @@ public final class DisplayManager {
/**
* Event type to register for a display's connection changed.
*
- * @see #registerDisplayListener(DisplayListener, Handler, long)
+ * @see #registerDisplayListener(DisplayListener, Handler, long, long)
* @hide
*/
public static final long PRIVATE_EVENT_TYPE_DISPLAY_CONNECTION_CHANGED = 1L << 2;
+ /**
+ * Event type to register for a display's committed state changes.
+ *
+ * @see #registerDisplayListener(DisplayListener, Handler, long, long)
+ * @hide
+ */
+ public static final long PRIVATE_EVENT_TYPE_DISPLAY_COMMITTED_STATE_CHANGED = 1L << 3;
+
/** @hide */
public DisplayManager(Context context) {
diff --git a/core/java/android/hardware/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java
index 339dbf2c2029..a7d610e54e2c 100644
--- a/core/java/android/hardware/display/DisplayManagerGlobal.java
+++ b/core/java/android/hardware/display/DisplayManagerGlobal.java
@@ -113,7 +113,8 @@ public final class DisplayManagerGlobal {
EVENT_DISPLAY_CONNECTED,
EVENT_DISPLAY_DISCONNECTED,
EVENT_DISPLAY_REFRESH_RATE_CHANGED,
- EVENT_DISPLAY_STATE_CHANGED
+ EVENT_DISPLAY_STATE_CHANGED,
+ EVENT_DISPLAY_COMMITTED_STATE_CHANGED
})
@Retention(RetentionPolicy.SOURCE)
public @interface DisplayEvent {}
@@ -128,6 +129,8 @@ public final class DisplayManagerGlobal {
public static final int EVENT_DISPLAY_DISCONNECTED = 7;
public static final int EVENT_DISPLAY_REFRESH_RATE_CHANGED = 8;
public static final int EVENT_DISPLAY_STATE_CHANGED = 9;
+ public static final int EVENT_DISPLAY_COMMITTED_STATE_CHANGED = 10;
+
@LongDef(prefix = {"INTERNAL_EVENT_FLAG_"}, flag = true, value = {
INTERNAL_EVENT_FLAG_DISPLAY_ADDED,
@@ -139,6 +142,7 @@ public final class DisplayManagerGlobal {
INTERNAL_EVENT_FLAG_DISPLAY_REFRESH_RATE,
INTERNAL_EVENT_FLAG_DISPLAY_STATE,
INTERNAL_EVENT_FLAG_TOPOLOGY_UPDATED,
+ INTERNAL_EVENT_FLAG_DISPLAY_COMMITTED_STATE_CHANGED
})
@Retention(RetentionPolicy.SOURCE)
public @interface InternalEventFlag {}
@@ -152,6 +156,8 @@ public final class DisplayManagerGlobal {
public static final long INTERNAL_EVENT_FLAG_DISPLAY_REFRESH_RATE = 1L << 6;
public static final long INTERNAL_EVENT_FLAG_DISPLAY_STATE = 1L << 7;
public static final long INTERNAL_EVENT_FLAG_TOPOLOGY_UPDATED = 1L << 8;
+ public static final long INTERNAL_EVENT_FLAG_DISPLAY_COMMITTED_STATE_CHANGED = 1L << 9;
+
@UnsupportedAppUsage
private static DisplayManagerGlobal sInstance;
@@ -1550,6 +1556,12 @@ public final class DisplayManagerGlobal {
mListener.onDisplayChanged(displayId);
}
break;
+ case EVENT_DISPLAY_COMMITTED_STATE_CHANGED:
+ if ((mInternalEventFlagsMask
+ & INTERNAL_EVENT_FLAG_DISPLAY_COMMITTED_STATE_CHANGED) != 0) {
+ mListener.onDisplayChanged(displayId);
+ }
+ break;
}
if (DEBUG) {
Trace.endSection();
@@ -1710,6 +1722,8 @@ public final class DisplayManagerGlobal {
return "EVENT_DISPLAY_REFRESH_RATE_CHANGED";
case EVENT_DISPLAY_STATE_CHANGED:
return "EVENT_DISPLAY_STATE_CHANGED";
+ case EVENT_DISPLAY_COMMITTED_STATE_CHANGED:
+ return "EVENT_DISPLAY_COMMITTED_STATE_CHANGED";
}
return "UNKNOWN";
}
@@ -1756,6 +1770,13 @@ public final class DisplayManagerGlobal {
& DisplayManager.PRIVATE_EVENT_TYPE_DISPLAY_CONNECTION_CHANGED) != 0) {
baseEventMask |= INTERNAL_EVENT_FLAG_DISPLAY_CONNECTION_CHANGED;
}
+
+ if (Flags.committedStateSeparateEvent()) {
+ if ((privateEventFlags
+ & DisplayManager.PRIVATE_EVENT_TYPE_DISPLAY_COMMITTED_STATE_CHANGED) != 0) {
+ baseEventMask |= INTERNAL_EVENT_FLAG_DISPLAY_COMMITTED_STATE_CHANGED;
+ }
+ }
return baseEventMask;
}
diff --git a/core/java/android/hardware/fingerprint/FingerprintSensorConfigurations.java b/core/java/android/hardware/fingerprint/FingerprintSensorConfigurations.java
index 48c5887d80d0..586830c8d189 100644
--- a/core/java/android/hardware/fingerprint/FingerprintSensorConfigurations.java
+++ b/core/java/android/hardware/fingerprint/FingerprintSensorConfigurations.java
@@ -224,6 +224,10 @@ public class FingerprintSensorConfigurations implements Parcelable {
} catch (RemoteException e) {
Log.d(TAG, "Unable to get sensor properties!");
}
+
+ if (props == null) {
+ props = new SensorProps[]{};
+ }
return props;
}
}
diff --git a/core/java/android/hardware/input/InputSettings.java b/core/java/android/hardware/input/InputSettings.java
index 3d4b8854b01f..7c82abe083c2 100644
--- a/core/java/android/hardware/input/InputSettings.java
+++ b/core/java/android/hardware/input/InputSettings.java
@@ -386,7 +386,7 @@ public class InputSettings {
*/
public static boolean isTouchpadAccelerationEnabled(@NonNull Context context) {
if (!isPointerAccelerationFeatureFlagEnabled()) {
- return false;
+ return true;
}
return Settings.System.getIntForUser(context.getContentResolver(),
@@ -833,7 +833,7 @@ public class InputSettings {
*/
public static boolean isMousePointerAccelerationEnabled(@NonNull Context context) {
if (!isPointerAccelerationFeatureFlagEnabled()) {
- return false;
+ return true;
}
return Settings.System.getIntForUser(context.getContentResolver(),
diff --git a/core/java/android/os/CombinedMessageQueue/MessageQueue.java b/core/java/android/os/CombinedMessageQueue/MessageQueue.java
index 349a2f0a181d..5886fa45153e 100644
--- a/core/java/android/os/CombinedMessageQueue/MessageQueue.java
+++ b/core/java/android/os/CombinedMessageQueue/MessageQueue.java
@@ -178,11 +178,6 @@ public final class MessageQueue {
// We can lift these restrictions in the future after we've made it possible for test
// authors to test Looper and MessageQueue without resorting to reflection.
-
- // Holdback study.
- if (sIsProcessAllowedToUseConcurrent && Flags.messageQueueForceLegacy()) {
- sIsProcessAllowedToUseConcurrent = false;
- }
}
@RavenwoodReplace
diff --git a/core/java/android/os/SystemVibratorManager.java b/core/java/android/os/SystemVibratorManager.java
index f9935d2870b0..1c3dd9eda5ce 100644
--- a/core/java/android/os/SystemVibratorManager.java
+++ b/core/java/android/os/SystemVibratorManager.java
@@ -217,7 +217,7 @@ public class SystemVibratorManager extends VibratorManager {
new VendorVibrationSessionCallbackDelegate(executor, callback);
if (mService == null) {
Log.w(TAG, "Failed to start vibration session; no vibrator manager service.");
- callbackDelegate.onFinished(VendorVibrationSession.STATUS_UNKNOWN_ERROR);
+ callbackDelegate.onFinished(VendorVibrationSession.STATUS_UNSUPPORTED);
return;
}
try {
diff --git a/core/java/android/os/flags.aconfig b/core/java/android/os/flags.aconfig
index 9e7c9f6e5417..d3c677bf8af2 100644
--- a/core/java/android/os/flags.aconfig
+++ b/core/java/android/os/flags.aconfig
@@ -4,15 +4,6 @@ container: "system"
# keep-sorted start block=yes newline_separated=yes
flag {
- # Holdback study for concurrent MessageQueue.
- # Do not promote beyond trunkfood.
- namespace: "system_performance"
- name: "message_queue_force_legacy"
- description: "Whether to holdback concurrent MessageQueue (force legacy)."
- bug: "336880969"
-}
-
-flag {
name: "adpf_gpu_report_actual_work_duration"
is_exported: true
namespace: "game"
diff --git a/core/java/android/os/vibrator/VibratorFrequencyProfile.java b/core/java/android/os/vibrator/VibratorFrequencyProfile.java
index 2b5f9bf2a22e..a8ed81846663 100644
--- a/core/java/android/os/vibrator/VibratorFrequencyProfile.java
+++ b/core/java/android/os/vibrator/VibratorFrequencyProfile.java
@@ -51,8 +51,7 @@ public final class VibratorFrequencyProfile {
Preconditions.checkArgument(!frequencyProfile.isEmpty(),
"Frequency profile must not be empty");
mFrequencyProfile = frequencyProfile;
- mFrequenciesOutputAcceleration = generateFrequencyToAccelerationMap(
- frequencyProfile.getFrequenciesHz(), frequencyProfile.getOutputAccelerationsGs());
+ mFrequenciesOutputAcceleration = generateFrequencyToAccelerationMap(mFrequencyProfile);
}
/**
@@ -133,18 +132,21 @@ public final class VibratorFrequencyProfile {
}
private static SparseArray<Float> generateFrequencyToAccelerationMap(
- float[] frequencies, float[] accelerations) {
- SparseArray<Float> sparseArray = new SparseArray<>(frequencies.length);
-
+ VibratorInfo.FrequencyProfile frequencyProfile) {
+ float[] frequencies = frequencyProfile.getFrequenciesHz();
+ SparseArray<Float> frequencyToAcceleration = new SparseArray<>(frequencies.length);
+ int lastFrequency = -1;
for (int i = 0; i < frequencies.length; i++) {
int frequency = (int) frequencies[i];
- float acceleration = accelerations[i];
-
- sparseArray.put(frequency,
- Math.min(acceleration, sparseArray.get(frequency, Float.MAX_VALUE)));
+ if (frequency == lastFrequency) {
+ continue; // Skip duplicate frequencies
+ }
+ float acceleration = frequencyProfile.getOutputAccelerationGs(frequency);
+ frequencyToAcceleration.put(frequency, acceleration);
+ lastFrequency = frequency;
}
- return sparseArray;
+ return frequencyToAcceleration;
}
}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 670709846d4c..4ebfe53cab58 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -153,7 +153,6 @@ public final class Settings {
* Output: Nothing.
*/
@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
- @FlaggedApi(com.android.internal.telephony.flags.Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG)
public static final String ACTION_SATELLITE_SETTING = "android.settings.SATELLITE_SETTING";
/**
@@ -2119,7 +2118,6 @@ public final class Settings {
* <p>
* Output: Nothing.
*/
- @FlaggedApi(android.app.Flags.FLAG_MODES_API)
@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
public static final String ACTION_AUTOMATIC_ZEN_RULE_SETTINGS
= "android.settings.AUTOMATIC_ZEN_RULE_SETTINGS";
@@ -2129,7 +2127,6 @@ public final class Settings {
* <p>
* This must be passed as an extra field to the {@link #ACTION_AUTOMATIC_ZEN_RULE_SETTINGS}.
*/
- @FlaggedApi(android.app.Flags.FLAG_MODES_API)
public static final String EXTRA_AUTOMATIC_ZEN_RULE_ID
= "android.provider.extra.AUTOMATIC_ZEN_RULE_ID";
diff --git a/core/java/android/security/FileIntegrityManager.java b/core/java/android/security/FileIntegrityManager.java
index 9e02ecd19aee..903f8170104e 100644
--- a/core/java/android/security/FileIntegrityManager.java
+++ b/core/java/android/security/FileIntegrityManager.java
@@ -65,13 +65,7 @@ public final class FileIntegrityManager {
* other fs-verity APIs.
*/
public boolean isApkVeritySupported() {
- try {
- // Go through the service just to avoid exposing the vendor controlled system property
- // to all apps.
- return mService.isApkVeritySupported();
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
+ return VerityUtils.isFsVeritySupported();
}
/**
diff --git a/core/java/android/security/IFileIntegrityService.aidl b/core/java/android/security/IFileIntegrityService.aidl
index c6def239d59a..5a1a6a0ea6d9 100644
--- a/core/java/android/security/IFileIntegrityService.aidl
+++ b/core/java/android/security/IFileIntegrityService.aidl
@@ -24,8 +24,6 @@ import android.os.IInstalld;
* @hide
*/
interface IFileIntegrityService {
- boolean isApkVeritySupported();
-
IInstalld.IFsveritySetupAuthToken createAuthToken(in ParcelFileDescriptor authFd);
@EnforcePermission("SETUP_FSVERITY")
diff --git a/core/java/android/service/notification/Condition.java b/core/java/android/service/notification/Condition.java
index 6e771f8f0ffe..c375cfb900ac 100644
--- a/core/java/android/service/notification/Condition.java
+++ b/core/java/android/service/notification/Condition.java
@@ -18,11 +18,9 @@ package android.service.notification;
import static com.android.internal.util.Preconditions.checkArgument;
-import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.app.Flags;
import android.content.Context;
import android.net.Uri;
import android.os.Parcel;
@@ -105,20 +103,15 @@ public final class Condition implements Parcelable {
public @interface Source {}
/** The state is changing due to an unknown reason. */
- @FlaggedApi(Flags.FLAG_MODES_API)
public static final int SOURCE_UNKNOWN = 0;
/** The state is changing due to an explicit user action. */
- @FlaggedApi(Flags.FLAG_MODES_API)
public static final int SOURCE_USER_ACTION = 1;
/** The state is changing due to an automatic schedule (alarm, set time, etc). */
- @FlaggedApi(Flags.FLAG_MODES_API)
public static final int SOURCE_SCHEDULE = 2;
/** The state is changing due to a change in context (such as detected driving or sleeping). */
- @FlaggedApi(Flags.FLAG_MODES_API)
public static final int SOURCE_CONTEXT = 3;
/** The source of, or reason for, the state change represented by this Condition. **/
- @FlaggedApi(Flags.FLAG_MODES_API)
public final @Source int source; // default = SOURCE_UNKNOWN
/**
@@ -145,7 +138,6 @@ public final class Condition implements Parcelable {
* @param state whether the mode should be activated or deactivated
* @param source the source of, or reason for, the state change represented by this Condition
*/
- @FlaggedApi(Flags.FLAG_MODES_API)
public Condition(@Nullable Uri id, @Nullable String summary, @State int state,
@Source int source) {
this(id, summary, "", "", -1, state, source, FLAG_RELEVANT_ALWAYS);
@@ -168,7 +160,6 @@ public final class Condition implements Parcelable {
* @param source the source of, or reason for, the state change represented by this Condition
* @param flags flags on this condition
*/
- @FlaggedApi(Flags.FLAG_MODES_API)
public Condition(@Nullable Uri id, @Nullable String summary, @Nullable String line1,
@Nullable String line2, int icon, @State int state, @Source int source,
int flags) {
@@ -195,15 +186,13 @@ public final class Condition implements Parcelable {
source.readString(),
source.readInt(),
source.readInt(),
- Flags.modesApi() ? source.readInt() : SOURCE_UNKNOWN,
+ source.readInt(),
source.readInt());
}
/** @hide */
public void validate() {
- if (Flags.modesApi()) {
- checkValidSource(source);
- }
+ checkValidSource(source);
}
private static boolean isValidState(int state) {
@@ -211,11 +200,9 @@ public final class Condition implements Parcelable {
}
private static int checkValidSource(@Source int source) {
- if (Flags.modesApi()) {
- checkArgument(source >= SOURCE_UNKNOWN && source <= SOURCE_CONTEXT,
- "Condition source must be one of SOURCE_UNKNOWN, SOURCE_USER_ACTION, "
- + "SOURCE_SCHEDULE, or SOURCE_CONTEXT");
- }
+ checkArgument(source >= SOURCE_UNKNOWN && source <= SOURCE_CONTEXT,
+ "Condition source must be one of SOURCE_UNKNOWN, SOURCE_USER_ACTION, "
+ + "SOURCE_SCHEDULE, or SOURCE_CONTEXT");
return source;
}
@@ -227,25 +214,21 @@ public final class Condition implements Parcelable {
dest.writeString(line2);
dest.writeInt(icon);
dest.writeInt(state);
- if (Flags.modesApi()) {
- dest.writeInt(this.source);
- }
+ dest.writeInt(this.source);
dest.writeInt(this.flags);
}
@Override
public String toString() {
- StringBuilder sb = new StringBuilder(Condition.class.getSimpleName()).append('[')
+ return new StringBuilder(Condition.class.getSimpleName()).append('[')
.append("state=").append(stateToString(state))
.append(",id=").append(id)
.append(",summary=").append(summary)
.append(",line1=").append(line1)
.append(",line2=").append(line2)
- .append(",icon=").append(icon);
- if (Flags.modesApi()) {
- sb.append(",source=").append(sourceToString(source));
- }
- return sb.append(",flags=").append(flags)
+ .append(",icon=").append(icon)
+ .append(",source=").append(sourceToString(source))
+ .append(",flags=").append(flags)
.append(']').toString();
}
@@ -279,7 +262,6 @@ public final class Condition implements Parcelable {
* Provides a human-readable string version of the Source enum.
* @hide
*/
- @FlaggedApi(Flags.FLAG_MODES_API)
public static @NonNull String sourceToString(@Source int source) {
if (source == SOURCE_UNKNOWN) return "SOURCE_UNKNOWN";
if (source == SOURCE_USER_ACTION) return "SOURCE_USER_ACTION";
@@ -301,25 +283,19 @@ public final class Condition implements Parcelable {
if (!(o instanceof Condition)) return false;
if (o == this) return true;
final Condition other = (Condition) o;
- boolean finalEquals = Objects.equals(other.id, id)
+ return Objects.equals(other.id, id)
&& Objects.equals(other.summary, summary)
&& Objects.equals(other.line1, line1)
&& Objects.equals(other.line2, line2)
&& other.icon == icon
&& other.state == state
- && other.flags == flags;
- if (Flags.modesApi()) {
- return finalEquals && other.source == source;
- }
- return finalEquals;
+ && other.flags == flags
+ && other.source == source;
}
@Override
public int hashCode() {
- if (Flags.modesApi()) {
- return Objects.hash(id, summary, line1, line2, icon, state, source, flags);
- }
- return Objects.hash(id, summary, line1, line2, icon, state, flags);
+ return Objects.hash(id, summary, line1, line2, icon, state, source, flags);
}
@Override
diff --git a/core/java/android/service/notification/SystemZenRules.java b/core/java/android/service/notification/SystemZenRules.java
index f11ce1621f93..fbee06e113fc 100644
--- a/core/java/android/service/notification/SystemZenRules.java
+++ b/core/java/android/service/notification/SystemZenRules.java
@@ -16,7 +16,6 @@
package android.service.notification;
-import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.StringRes;
@@ -47,7 +46,6 @@ public final class SystemZenRules {
public static final String PACKAGE_ANDROID = "android";
/** Updates existing system-owned rules to use the new Modes fields (type, etc). */
- @FlaggedApi(Flags.FLAG_MODES_API)
public static void maybeUpgradeRules(Context context, ZenModeConfig config) {
for (ZenRule rule : config.automaticRules.values()) {
if (isSystemOwnedRule(rule)) {
@@ -69,7 +67,6 @@ public final class SystemZenRules {
return PACKAGE_ANDROID.equals(rule.pkg);
}
- @FlaggedApi(Flags.FLAG_MODES_API)
private static void upgradeSystemProviderRule(Context context, ZenRule rule) {
ScheduleInfo scheduleInfo = ZenModeConfig.tryParseScheduleConditionId(rule.conditionId);
if (scheduleInfo != null) {
diff --git a/core/java/android/service/notification/ZenAdapters.java b/core/java/android/service/notification/ZenAdapters.java
index a122b7155b18..4f53bfa841ef 100644
--- a/core/java/android/service/notification/ZenAdapters.java
+++ b/core/java/android/service/notification/ZenAdapters.java
@@ -17,7 +17,6 @@
package android.service.notification;
import android.annotation.NonNull;
-import android.app.Flags;
import android.app.NotificationManager.Policy;
/**
@@ -50,7 +49,8 @@ public class ZenAdapters {
: ZenPolicy.PEOPLE_TYPE_NONE)
.allowReminders(policy.allowReminders())
.allowRepeatCallers(policy.allowRepeatCallers())
- .allowSystem(policy.allowSystem());
+ .allowSystem(policy.allowSystem())
+ .allowPriorityChannels(policy.allowPriorityChannels());
if (policy.suppressedVisualEffects != Policy.SUPPRESSED_EFFECTS_UNSET) {
zenPolicyBuilder.showBadges(policy.showBadges())
@@ -62,10 +62,6 @@ public class ZenAdapters {
.showStatusBarIcons(policy.showStatusBarIcons());
}
- if (Flags.modesApi()) {
- zenPolicyBuilder.allowPriorityChannels(policy.allowPriorityChannels());
- }
-
return zenPolicyBuilder.build();
}
diff --git a/core/java/android/service/notification/ZenDeviceEffects.java b/core/java/android/service/notification/ZenDeviceEffects.java
index 06bd2555c2f8..d88fb3e35b1e 100644
--- a/core/java/android/service/notification/ZenDeviceEffects.java
+++ b/core/java/android/service/notification/ZenDeviceEffects.java
@@ -16,12 +16,10 @@
package android.service.notification;
-import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.TestApi;
-import android.app.Flags;
import android.os.Parcel;
import android.os.Parcelable;
@@ -37,7 +35,6 @@ import java.util.Set;
* Represents the set of device effects (affecting display and device behavior in general) that
* are applied whenever an {@link android.app.AutomaticZenRule} is active.
*/
-@FlaggedApi(Flags.FLAG_MODES_API)
public final class ZenDeviceEffects implements Parcelable {
/**
@@ -157,7 +154,6 @@ public final class ZenDeviceEffects implements Parcelable {
}
/** @hide */
- @FlaggedApi(Flags.FLAG_MODES_API)
public void validate() {
int extraEffectsLength = 0;
for (String extraEffect : mExtraEffects) {
@@ -435,7 +431,6 @@ public final class ZenDeviceEffects implements Parcelable {
}
/** Builder class for {@link ZenDeviceEffects} objects. */
- @FlaggedApi(Flags.FLAG_MODES_API)
public static final class Builder {
private boolean mGrayscale;
diff --git a/core/java/android/service/notification/ZenModeConfig.java b/core/java/android/service/notification/ZenModeConfig.java
index 4f459aa9131a..6f94c1b2d274 100644
--- a/core/java/android/service/notification/ZenModeConfig.java
+++ b/core/java/android/service/notification/ZenModeConfig.java
@@ -228,7 +228,7 @@ public class ZenModeConfig implements Parcelable {
private static final boolean DEFAULT_ALLOW_CONV = true;
private static final int DEFAULT_ALLOW_CONV_FROM = ZenPolicy.CONVERSATION_SENDERS_IMPORTANT;
private static final boolean DEFAULT_ALLOW_PRIORITY_CHANNELS = true;
- private static final boolean DEFAULT_CHANNELS_BYPASSING_DND = false;
+ private static final boolean DEFAULT_HAS_PRIORITY_CHANNELS = false;
// Default setting here is 010011101 = 157
private static final int DEFAULT_SUPPRESSED_VISUAL_EFFECTS =
SUPPRESSED_EFFECT_SCREEN_OFF | SUPPRESSED_EFFECT_FULL_SCREEN_INTENT
@@ -242,9 +242,6 @@ public class ZenModeConfig implements Parcelable {
public static final int XML_VERSION_MODES_API = 11;
public static final int XML_VERSION_MODES_UI = 12;
- // TODO: b/310620812, b/344831624 - Update XML_VERSION and update default_zen_config.xml
- // accordingly when modes_api / modes_ui are inlined.
- private static final int XML_VERSION_PRE_MODES = 10;
public static final String ZEN_TAG = "zen";
private static final String ZEN_ATT_VERSION = "version";
private static final String ZEN_ATT_USER = "user";
@@ -269,7 +266,7 @@ public class ZenModeConfig implements Parcelable {
private static final String DISALLOW_TAG = "disallow";
private static final String DISALLOW_ATT_VISUAL_EFFECTS = "visualEffects";
private static final String STATE_TAG = "state";
- private static final String STATE_ATT_CHANNELS_BYPASSING_DND = "areChannelsBypassingDnd";
+ private static final String STATE_HAS_PRIORITY_CHANNELS = "areChannelsBypassingDnd";
// zen policy visual effects attributes
private static final String SHOW_ATT_FULL_SCREEN_INTENT = "showFullScreenIntent";
@@ -303,7 +300,6 @@ public class ZenModeConfig implements Parcelable {
private static final String RULE_ATT_CONDITION_ID = "conditionId";
private static final String RULE_ATT_CREATION_TIME = "creationTime";
private static final String RULE_ATT_ENABLER = "enabler";
- private static final String RULE_ATT_MODIFIED = "modified";
private static final String RULE_ATT_ALLOW_MANUAL = "userInvokable";
private static final String RULE_ATT_TYPE = "type";
private static final String RULE_ATT_USER_MODIFIED_FIELDS = "userModifiedFields";
@@ -313,6 +309,7 @@ public class ZenModeConfig implements Parcelable {
private static final String RULE_ATT_DISABLED_ORIGIN = "disabledOrigin";
private static final String RULE_ATT_LEGACY_SUPPRESSED_EFFECTS = "legacySuppressedEffects";
private static final String RULE_ATT_CONDITION_OVERRIDE = "conditionOverride";
+ private static final String RULE_ATT_LAST_ACTIVATION = "lastActivation";
private static final String DEVICE_EFFECT_DISPLAY_GRAYSCALE = "zdeDisplayGrayscale";
private static final String DEVICE_EFFECT_SUPPRESS_AMBIENT_DISPLAY =
@@ -348,11 +345,11 @@ public class ZenModeConfig implements Parcelable {
public int allowConversationsFrom = DEFAULT_ALLOW_CONV_FROM;
public int user = UserHandle.USER_SYSTEM;
public int suppressedVisualEffects = DEFAULT_SUPPRESSED_VISUAL_EFFECTS;
- // Note that when the modes_api flag is true, the areChannelsBypassingDnd boolean only tracks
- // whether the current user has any priority channels. These channels may bypass DND when
- // allowPriorityChannels is true.
- // TODO: b/310620812 - Rename to be more accurate when modes_api flag is inlined.
- public boolean areChannelsBypassingDnd = DEFAULT_CHANNELS_BYPASSING_DND;
+ /**
+ * Whether the current user has any priority channels. These channels may bypass DND when
+ * {@link #allowPriorityChannels} is true.
+ */
+ public boolean hasPriorityChannels = DEFAULT_HAS_PRIORITY_CHANNELS;
public boolean allowPriorityChannels = DEFAULT_ALLOW_PRIORITY_CHANNELS;
public int version;
@@ -384,22 +381,18 @@ public class ZenModeConfig implements Parcelable {
user = source.readInt();
manualRule = source.readParcelable(null, ZenRule.class);
readRulesFromParcel(automaticRules, source);
- if (Flags.modesApi()) {
- readRulesFromParcel(deletedRules, source);
- }
+ readRulesFromParcel(deletedRules, source);
if (!Flags.modesUi()) {
allowAlarms = source.readInt() == 1;
allowMedia = source.readInt() == 1;
allowSystem = source.readInt() == 1;
suppressedVisualEffects = source.readInt();
}
- areChannelsBypassingDnd = source.readInt() == 1;
+ hasPriorityChannels = source.readInt() == 1;
if (!Flags.modesUi()) {
allowConversations = source.readBoolean();
allowConversationsFrom = source.readInt();
- if (Flags.modesApi()) {
- allowPriorityChannels = source.readBoolean();
- }
+ allowPriorityChannels = source.readBoolean();
}
}
@@ -493,22 +486,18 @@ public class ZenModeConfig implements Parcelable {
dest.writeInt(user);
dest.writeParcelable(manualRule, 0);
writeRulesToParcel(automaticRules, dest);
- if (Flags.modesApi()) {
- writeRulesToParcel(deletedRules, dest);
- }
+ writeRulesToParcel(deletedRules, dest);
if (!Flags.modesUi()) {
dest.writeInt(allowAlarms ? 1 : 0);
dest.writeInt(allowMedia ? 1 : 0);
dest.writeInt(allowSystem ? 1 : 0);
dest.writeInt(suppressedVisualEffects);
}
- dest.writeInt(areChannelsBypassingDnd ? 1 : 0);
+ dest.writeInt(hasPriorityChannels ? 1 : 0);
if (!Flags.modesUi()) {
dest.writeBoolean(allowConversations);
dest.writeInt(allowConversationsFrom);
- if (Flags.modesApi()) {
- dest.writeBoolean(allowPriorityChannels);
- }
+ dest.writeBoolean(allowPriorityChannels);
}
}
@@ -549,17 +538,13 @@ public class ZenModeConfig implements Parcelable {
(allowConversationsFrom))
.append("\nsuppressedVisualEffects=").append(suppressedVisualEffects);
}
- if (Flags.modesApi()) {
- sb.append("\nhasPriorityChannels=").append(areChannelsBypassingDnd);
- sb.append(",allowPriorityChannels=").append(allowPriorityChannels);
- } else {
- sb.append("\nareChannelsBypassingDnd=").append(areChannelsBypassingDnd);
- }
+
+ sb.append("\nhasPriorityChannels=").append(hasPriorityChannels);
+ sb.append(",allowPriorityChannels=").append(allowPriorityChannels);
sb.append(",\nautomaticRules=").append(rulesToString(automaticRules));
sb.append(",\nmanualRule=").append(manualRule);
- if (Flags.modesApi()) {
- sb.append(",\ndeletedRules=").append(rulesToString(deletedRules));
- }
+ sb.append(",\ndeletedRules=").append(rulesToString(deletedRules));
+
return sb.append(']').toString();
}
@@ -854,7 +839,7 @@ public class ZenModeConfig implements Parcelable {
final ZenModeConfig other = (ZenModeConfig) o;
// The policy fields that live on config are compared directly because the fields will
// contain data until MODES_UI is rolled out/cleaned up.
- boolean eq = other.allowAlarms == allowAlarms
+ return other.allowAlarms == allowAlarms
&& other.allowMedia == allowMedia
&& other.allowSystem == allowSystem
&& other.allowCalls == allowCalls
@@ -868,35 +853,23 @@ public class ZenModeConfig implements Parcelable {
&& Objects.equals(other.automaticRules, automaticRules)
&& Objects.equals(other.manualRule, manualRule)
&& other.suppressedVisualEffects == suppressedVisualEffects
- && other.areChannelsBypassingDnd == areChannelsBypassingDnd
+ && other.hasPriorityChannels == hasPriorityChannels
&& other.allowConversations == allowConversations
- && other.allowConversationsFrom == allowConversationsFrom;
- if (Flags.modesApi()) {
- return eq
- && Objects.equals(other.deletedRules, deletedRules)
- && other.allowPriorityChannels == allowPriorityChannels;
- }
- return eq;
+ && other.allowConversationsFrom == allowConversationsFrom
+ && Objects.equals(other.deletedRules, deletedRules)
+ && other.allowPriorityChannels == allowPriorityChannels;
}
@Override
public int hashCode() {
// The policy fields that live on config are compared directly because the fields will
// contain data until MODES_UI is rolled out/cleaned up.
- if (Flags.modesApi()) {
- return Objects.hash(allowAlarms, allowMedia, allowSystem, allowCalls,
- allowRepeatCallers, allowMessages,
- allowCallsFrom, allowMessagesFrom, allowReminders, allowEvents,
- user, automaticRules, manualRule,
- suppressedVisualEffects, areChannelsBypassingDnd, allowConversations,
- allowConversationsFrom, allowPriorityChannels);
- }
return Objects.hash(allowAlarms, allowMedia, allowSystem, allowCalls,
allowRepeatCallers, allowMessages,
allowCallsFrom, allowMessagesFrom, allowReminders, allowEvents,
user, automaticRules, manualRule,
- suppressedVisualEffects, areChannelsBypassingDnd, allowConversations,
- allowConversationsFrom);
+ suppressedVisualEffects, hasPriorityChannels, allowConversations,
+ allowConversationsFrom, allowPriorityChannels);
}
private static String toDayList(int[] days) {
@@ -952,10 +925,8 @@ public class ZenModeConfig implements Parcelable {
public static int getCurrentXmlVersion() {
if (Flags.modesUi()) {
return XML_VERSION_MODES_UI;
- } else if (Flags.modesApi()) {
- return XML_VERSION_MODES_API;
} else {
- return XML_VERSION_PRE_MODES;
+ return XML_VERSION_MODES_API;
}
}
@@ -1006,10 +977,8 @@ public class ZenModeConfig implements Parcelable {
rt.allowMedia = safeBoolean(parser, ALLOW_ATT_MEDIA,
DEFAULT_ALLOW_MEDIA);
rt.allowSystem = safeBoolean(parser, ALLOW_ATT_SYSTEM, DEFAULT_ALLOW_SYSTEM);
- if (Flags.modesApi()) {
- rt.allowPriorityChannels = safeBoolean(parser, ALLOW_ATT_CHANNELS,
- DEFAULT_ALLOW_PRIORITY_CHANNELS);
- }
+ rt.allowPriorityChannels = safeBoolean(parser, ALLOW_ATT_CHANNELS,
+ DEFAULT_ALLOW_PRIORITY_CHANNELS);
// migrate old suppressed visual effects fields, if they still exist in the xml
Boolean allowWhenScreenOff = unsafeBoolean(parser, ALLOW_ATT_SCREEN_OFF);
@@ -1054,13 +1023,12 @@ public class ZenModeConfig implements Parcelable {
} else {
readRuleCount++;
}
- } else if (AUTOMATIC_TAG.equals(tag)
- || (Flags.modesApi() && AUTOMATIC_DELETED_TAG.equals(tag))) {
+ } else if (AUTOMATIC_TAG.equals(tag) || AUTOMATIC_DELETED_TAG.equals(tag)) {
final String id = parser.getAttributeValue(null, RULE_ATT_ID);
if (id != null) {
final ZenRule automaticRule = readRuleXml(parser);
automaticRule.id = id;
- if (Flags.modesApi() && AUTOMATIC_DELETED_TAG.equals(tag)) {
+ if (AUTOMATIC_DELETED_TAG.equals(tag)) {
String deletedRuleKey = deletedRuleKey(automaticRule);
if (deletedRuleKey != null) {
rt.deletedRules.put(deletedRuleKey, automaticRule);
@@ -1071,8 +1039,8 @@ public class ZenModeConfig implements Parcelable {
}
}
} else if (STATE_TAG.equals(tag)) {
- rt.areChannelsBypassingDnd = safeBoolean(parser,
- STATE_ATT_CHANNELS_BYPASSING_DND, DEFAULT_CHANNELS_BYPASSING_DND);
+ rt.hasPriorityChannels = safeBoolean(parser,
+ STATE_HAS_PRIORITY_CHANNELS, DEFAULT_HAS_PRIORITY_CHANNELS);
}
}
if (type == XmlPullParser.END_TAG && ZEN_TAG.equals(tag)) {
@@ -1149,9 +1117,7 @@ public class ZenModeConfig implements Parcelable {
out.attributeBoolean(null, ALLOW_ATT_SYSTEM, allowSystem);
out.attributeBoolean(null, ALLOW_ATT_CONV, allowConversations);
out.attributeInt(null, ALLOW_ATT_CONV_FROM, allowConversationsFrom);
- if (Flags.modesApi()) {
- out.attributeBoolean(null, ALLOW_ATT_CHANNELS, allowPriorityChannels);
- }
+ out.attributeBoolean(null, ALLOW_ATT_CHANNELS, allowPriorityChannels);
out.endTag(null, ALLOW_TAG);
out.startTag(null, DISALLOW_TAG);
@@ -1174,7 +1140,7 @@ public class ZenModeConfig implements Parcelable {
out.endTag(null, AUTOMATIC_TAG);
writtenRuleCount++;
}
- if (Flags.modesApi() && !forBackup) {
+ if (!forBackup) {
for (int i = 0; i < deletedRules.size(); i++) {
final ZenRule deletedRule = deletedRules.valueAt(i);
out.startTag(null, AUTOMATIC_DELETED_TAG);
@@ -1185,7 +1151,7 @@ public class ZenModeConfig implements Parcelable {
}
out.startTag(null, STATE_TAG);
- out.attributeBoolean(null, STATE_ATT_CHANNELS_BYPASSING_DND, areChannelsBypassingDnd);
+ out.attributeBoolean(null, STATE_HAS_PRIORITY_CHANNELS, hasPriorityChannels);
out.endTag(null, STATE_TAG);
out.endTag(null, ZEN_TAG);
@@ -1212,39 +1178,29 @@ public class ZenModeConfig implements Parcelable {
rt.creationTime = safeLong(parser, RULE_ATT_CREATION_TIME, 0);
rt.enabler = parser.getAttributeValue(null, RULE_ATT_ENABLER);
rt.condition = readConditionXml(parser);
-
- if (!Flags.modesApi() && rt.zenMode != ZEN_MODE_IMPORTANT_INTERRUPTIONS
- && Condition.isValidId(rt.conditionId, SYSTEM_AUTHORITY)) {
- // all default rules and user created rules updated to zenMode important interruptions
- Slog.i(TAG, "Updating zenMode of automatic rule " + rt.name);
- rt.zenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS;
- }
- rt.modified = safeBoolean(parser, RULE_ATT_MODIFIED, false);
rt.zenPolicy = readZenPolicyXml(parser);
- if (Flags.modesApi()) {
- rt.zenDeviceEffects = readZenDeviceEffectsXml(parser);
- rt.allowManualInvocation = safeBoolean(parser, RULE_ATT_ALLOW_MANUAL, false);
- rt.iconResName = parser.getAttributeValue(null, RULE_ATT_ICON);
- rt.triggerDescription = parser.getAttributeValue(null, RULE_ATT_TRIGGER_DESC);
- rt.type = safeInt(parser, RULE_ATT_TYPE, AutomaticZenRule.TYPE_UNKNOWN);
- rt.userModifiedFields = safeInt(parser, RULE_ATT_USER_MODIFIED_FIELDS, 0);
- rt.zenPolicyUserModifiedFields = safeInt(parser, POLICY_USER_MODIFIED_FIELDS, 0);
- rt.zenDeviceEffectsUserModifiedFields = safeInt(parser,
- DEVICE_EFFECT_USER_MODIFIED_FIELDS, 0);
- Long deletionInstant = tryParseLong(
- parser.getAttributeValue(null, RULE_ATT_DELETION_INSTANT), null);
- if (deletionInstant != null) {
- rt.deletionInstant = Instant.ofEpochMilli(deletionInstant);
- }
- if (Flags.modesUi()) {
- rt.disabledOrigin = safeInt(parser, RULE_ATT_DISABLED_ORIGIN,
- ORIGIN_UNKNOWN);
- rt.legacySuppressedEffects = safeInt(parser,
- RULE_ATT_LEGACY_SUPPRESSED_EFFECTS, 0);
- rt.conditionOverride = safeInt(parser, RULE_ATT_CONDITION_OVERRIDE,
- ZenRule.OVERRIDE_NONE);
+ rt.zenDeviceEffects = readZenDeviceEffectsXml(parser);
+ rt.allowManualInvocation = safeBoolean(parser, RULE_ATT_ALLOW_MANUAL, false);
+ rt.iconResName = parser.getAttributeValue(null, RULE_ATT_ICON);
+ rt.triggerDescription = parser.getAttributeValue(null, RULE_ATT_TRIGGER_DESC);
+ rt.type = safeInt(parser, RULE_ATT_TYPE, AutomaticZenRule.TYPE_UNKNOWN);
+ rt.userModifiedFields = safeInt(parser, RULE_ATT_USER_MODIFIED_FIELDS, 0);
+ rt.zenPolicyUserModifiedFields = safeInt(parser, POLICY_USER_MODIFIED_FIELDS, 0);
+ rt.zenDeviceEffectsUserModifiedFields = safeInt(parser,
+ DEVICE_EFFECT_USER_MODIFIED_FIELDS, 0);
+ rt.deletionInstant = safeInstant(parser, RULE_ATT_DELETION_INSTANT, null);
+ if (Flags.modesUi()) {
+ rt.disabledOrigin = safeInt(parser, RULE_ATT_DISABLED_ORIGIN,
+ ORIGIN_UNKNOWN);
+ rt.legacySuppressedEffects = safeInt(parser,
+ RULE_ATT_LEGACY_SUPPRESSED_EFFECTS, 0);
+ rt.conditionOverride = safeInt(parser, RULE_ATT_CONDITION_OVERRIDE,
+ ZenRule.OVERRIDE_NONE);
+ if (Flags.modesCleanupImplicit()) {
+ rt.lastActivation = safeInstant(parser, RULE_ATT_LAST_ACTIVATION, null);
}
}
+
return rt;
}
@@ -1278,38 +1234,42 @@ public class ZenModeConfig implements Parcelable {
if (rule.zenPolicy != null) {
writeZenPolicyXml(rule.zenPolicy, out);
}
- if (Flags.modesApi() && rule.zenDeviceEffects != null) {
+ if (rule.zenDeviceEffects != null) {
writeZenDeviceEffectsXml(rule.zenDeviceEffects, out);
}
- out.attributeBoolean(null, RULE_ATT_MODIFIED, rule.modified);
- if (Flags.modesApi()) {
- out.attributeBoolean(null, RULE_ATT_ALLOW_MANUAL, rule.allowManualInvocation);
- if (rule.iconResName != null) {
- out.attribute(null, RULE_ATT_ICON, rule.iconResName);
- }
- if (rule.triggerDescription != null) {
- out.attribute(null, RULE_ATT_TRIGGER_DESC, rule.triggerDescription);
- }
- out.attributeInt(null, RULE_ATT_TYPE, rule.type);
- out.attributeInt(null, RULE_ATT_USER_MODIFIED_FIELDS, rule.userModifiedFields);
- out.attributeInt(null, POLICY_USER_MODIFIED_FIELDS, rule.zenPolicyUserModifiedFields);
- out.attributeInt(null, DEVICE_EFFECT_USER_MODIFIED_FIELDS,
- rule.zenDeviceEffectsUserModifiedFields);
- if (rule.deletionInstant != null) {
- out.attributeLong(null, RULE_ATT_DELETION_INSTANT,
- rule.deletionInstant.toEpochMilli());
+ out.attributeBoolean(null, RULE_ATT_ALLOW_MANUAL, rule.allowManualInvocation);
+ if (rule.iconResName != null) {
+ out.attribute(null, RULE_ATT_ICON, rule.iconResName);
+ }
+ if (rule.triggerDescription != null) {
+ out.attribute(null, RULE_ATT_TRIGGER_DESC, rule.triggerDescription);
+ }
+ out.attributeInt(null, RULE_ATT_TYPE, rule.type);
+ out.attributeInt(null, RULE_ATT_USER_MODIFIED_FIELDS, rule.userModifiedFields);
+ out.attributeInt(null, POLICY_USER_MODIFIED_FIELDS, rule.zenPolicyUserModifiedFields);
+ out.attributeInt(null, DEVICE_EFFECT_USER_MODIFIED_FIELDS,
+ rule.zenDeviceEffectsUserModifiedFields);
+ writeXmlAttributeInstant(out, RULE_ATT_DELETION_INSTANT, rule.deletionInstant);
+ if (Flags.modesUi()) {
+ out.attributeInt(null, RULE_ATT_DISABLED_ORIGIN, rule.disabledOrigin);
+ out.attributeInt(null, RULE_ATT_LEGACY_SUPPRESSED_EFFECTS,
+ rule.legacySuppressedEffects);
+ if (rule.conditionOverride == ZenRule.OVERRIDE_ACTIVATE && !forBackup) {
+ out.attributeInt(null, RULE_ATT_CONDITION_OVERRIDE, rule.conditionOverride);
}
- if (Flags.modesUi()) {
- out.attributeInt(null, RULE_ATT_DISABLED_ORIGIN, rule.disabledOrigin);
- out.attributeInt(null, RULE_ATT_LEGACY_SUPPRESSED_EFFECTS,
- rule.legacySuppressedEffects);
- if (rule.conditionOverride == ZenRule.OVERRIDE_ACTIVATE && !forBackup) {
- out.attributeInt(null, RULE_ATT_CONDITION_OVERRIDE, rule.conditionOverride);
- }
+ if (Flags.modesCleanupImplicit()) {
+ writeXmlAttributeInstant(out, RULE_ATT_LAST_ACTIVATION, rule.lastActivation);
}
}
}
+ private static void writeXmlAttributeInstant(TypedXmlSerializer out, String att,
+ @Nullable Instant instant) throws IOException {
+ if (instant != null) {
+ out.attributeLong(null, att, instant.toEpochMilli());
+ }
+ }
+
public static Condition readConditionXml(TypedXmlPullParser parser) {
final Uri id = safeUri(parser, CONDITION_ATT_ID);
if (id == null) return null;
@@ -1320,12 +1280,8 @@ public class ZenModeConfig implements Parcelable {
final int state = safeInt(parser, CONDITION_ATT_STATE, -1);
final int flags = safeInt(parser, CONDITION_ATT_FLAGS, -1);
try {
- if (Flags.modesApi()) {
- final int source = safeInt(parser, CONDITION_ATT_SOURCE, Condition.SOURCE_UNKNOWN);
- return new Condition(id, summary, line1, line2, icon, state, source, flags);
- } else {
- return new Condition(id, summary, line1, line2, icon, state, flags);
- }
+ final int source = safeInt(parser, CONDITION_ATT_SOURCE, Condition.SOURCE_UNKNOWN);
+ return new Condition(id, summary, line1, line2, icon, state, source, flags);
} catch (IllegalArgumentException e) {
Slog.w(TAG, "Unable to read condition xml", e);
return null;
@@ -1339,9 +1295,7 @@ public class ZenModeConfig implements Parcelable {
out.attribute(null, CONDITION_ATT_LINE2, c.line2);
out.attributeInt(null, CONDITION_ATT_ICON, c.icon);
out.attributeInt(null, CONDITION_ATT_STATE, c.state);
- if (Flags.modesApi()) {
- out.attributeInt(null, CONDITION_ATT_SOURCE, c.source);
- }
+ out.attributeInt(null, CONDITION_ATT_SOURCE, c.source);
out.attributeInt(null, CONDITION_ATT_FLAGS, c.flags);
}
@@ -1363,12 +1317,11 @@ public class ZenModeConfig implements Parcelable {
final int system = safeInt(parser, ALLOW_ATT_SYSTEM, ZenPolicy.STATE_UNSET);
final int events = safeInt(parser, ALLOW_ATT_EVENTS, ZenPolicy.STATE_UNSET);
final int reminders = safeInt(parser, ALLOW_ATT_REMINDERS, ZenPolicy.STATE_UNSET);
- if (Flags.modesApi()) {
- final int channels = safeInt(parser, ALLOW_ATT_CHANNELS, ZenPolicy.STATE_UNSET);
- if (channels != ZenPolicy.STATE_UNSET) {
- builder.allowPriorityChannels(channels == STATE_ALLOW);
- policySet = true;
- }
+ final int channels = safeInt(parser, ALLOW_ATT_CHANNELS, ZenPolicy.STATE_UNSET);
+
+ if (channels != ZenPolicy.STATE_UNSET) {
+ builder.allowPriorityChannels(channels == STATE_ALLOW);
+ policySet = true;
}
if (calls != ZenPolicy.PEOPLE_TYPE_UNSET) {
@@ -1478,10 +1431,7 @@ public class ZenModeConfig implements Parcelable {
writeZenPolicyState(SHOW_ATT_AMBIENT, policy.getVisualEffectAmbient(), out);
writeZenPolicyState(SHOW_ATT_NOTIFICATION_LIST, policy.getVisualEffectNotificationList(),
out);
-
- if (Flags.modesApi()) {
- writeZenPolicyState(ALLOW_ATT_CHANNELS, policy.getPriorityChannelsAllowed(), out);
- }
+ writeZenPolicyState(ALLOW_ATT_CHANNELS, policy.getPriorityChannelsAllowed(), out);
}
private static void writeZenPolicyState(String attr, int val, TypedXmlSerializer out)
@@ -1495,7 +1445,7 @@ public class ZenModeConfig implements Parcelable {
if (val != ZenPolicy.CONVERSATION_SENDERS_UNSET) {
out.attributeInt(null, attr, val);
}
- } else if (Flags.modesApi() && Objects.equals(attr, ALLOW_ATT_CHANNELS)) {
+ } else if (Objects.equals(attr, ALLOW_ATT_CHANNELS)) {
if (val != ZenPolicy.STATE_UNSET) {
out.attributeInt(null, attr, val);
}
@@ -1506,7 +1456,6 @@ public class ZenModeConfig implements Parcelable {
}
}
- @FlaggedApi(Flags.FLAG_MODES_API)
@Nullable
private static ZenDeviceEffects readZenDeviceEffectsXml(TypedXmlPullParser parser) {
ZenDeviceEffects deviceEffects =
@@ -1539,7 +1488,6 @@ public class ZenModeConfig implements Parcelable {
return deviceEffects.hasEffects() ? deviceEffects : null;
}
- @FlaggedApi(Flags.FLAG_MODES_API)
private static void writeZenDeviceEffectsXml(ZenDeviceEffects deviceEffects,
TypedXmlSerializer out) throws IOException {
writeBooleanIfTrue(out, DEVICE_EFFECT_DISPLAY_GRAYSCALE,
@@ -1659,6 +1607,19 @@ public class ZenModeConfig implements Parcelable {
return values;
}
+ @Nullable
+ private static Instant safeInstant(TypedXmlPullParser parser, String att,
+ @Nullable Instant defValue) {
+ final String strValue = parser.getAttributeValue(null, att);
+ if (!TextUtils.isEmpty(strValue)) {
+ Long longValue = tryParseLong(strValue, null);
+ if (longValue != null) {
+ return Instant.ofEpochMilli(longValue);
+ }
+ }
+ return defValue;
+ }
+
@Override
public int describeContents() {
return 0;
@@ -1732,9 +1693,7 @@ public class ZenModeConfig implements Parcelable {
(suppressedVisualEffects & Policy.SUPPRESSED_EFFECT_NOTIFICATION_LIST) == 0);
}
- if (Flags.modesApi()) {
- builder.allowPriorityChannels(allowPriorityChannels);
- }
+ builder.allowPriorityChannels(allowPriorityChannels);
return builder.build();
}
@@ -1860,12 +1819,9 @@ public class ZenModeConfig implements Parcelable {
suppressedVisualEffects |= Policy.SUPPRESSED_EFFECT_NOTIFICATION_LIST;
}
- int state = defaultPolicy.state;
- if (Flags.modesApi()) {
- state = Policy.policyState(defaultPolicy.hasPriorityChannels(),
- ZenPolicy.stateToBoolean(zenPolicy.getPriorityChannelsAllowed(),
- DEFAULT_ALLOW_PRIORITY_CHANNELS));
- }
+ int state = Policy.policyState(defaultPolicy.hasPriorityChannels(),
+ ZenPolicy.stateToBoolean(zenPolicy.getPriorityChannelsAllowed(),
+ DEFAULT_ALLOW_PRIORITY_CHANNELS));
return new NotificationManager.Policy(priorityCategories, callSenders,
messageSenders, suppressedVisualEffects, state, conversationSenders);
@@ -1930,7 +1886,7 @@ public class ZenModeConfig implements Parcelable {
priorityMessageSenders = peopleTypeToPrioritySenders(
manualRule.zenPolicy.getPriorityMessageSenders(), DEFAULT_SOURCE);
- state = Policy.policyState(areChannelsBypassingDnd,
+ state = Policy.policyState(hasPriorityChannels,
manualRule.zenPolicy.getPriorityChannelsAllowed() != STATE_DISALLOW);
boolean suppressFullScreenIntent = !manualRule.zenPolicy.isVisualEffectAllowed(
@@ -2030,10 +1986,7 @@ public class ZenModeConfig implements Parcelable {
priorityConversationSenders = zenPolicyConversationSendersToNotificationPolicy(
getAllowConversationsFrom(), priorityConversationSenders);
- state = areChannelsBypassingDnd ? Policy.STATE_CHANNELS_BYPASSING_DND : 0;
- if (Flags.modesApi()) {
- state = Policy.policyState(areChannelsBypassingDnd, allowPriorityChannels);
- }
+ state = Policy.policyState(hasPriorityChannels, allowPriorityChannels);
suppressedVisualEffects = getSuppressedVisualEffects();
}
@@ -2114,13 +2067,11 @@ public class ZenModeConfig implements Parcelable {
policy.priorityConversationSenders,
allowConversationsFrom);
if (policy.state != Policy.STATE_UNSET) {
- if (Flags.modesApi()) {
- setAllowPriorityChannels(policy.allowPriorityChannels());
- }
+ setAllowPriorityChannels(policy.allowPriorityChannels());
}
}
if (policy.state != Policy.STATE_UNSET) {
- areChannelsBypassingDnd = (policy.state & Policy.STATE_CHANNELS_BYPASSING_DND) != 0;
+ hasPriorityChannels = (policy.state & Policy.STATE_HAS_PRIORITY_CHANNELS) != 0;
}
}
@@ -2618,8 +2569,9 @@ public class ZenModeConfig implements Parcelable {
@UnsupportedAppUsage
public boolean enabled;
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ // TODO: b/368247671 - Obsolete with MODES_UI; delete when the flag is inlined
@Deprecated
- public boolean snoozing; // user manually disabled this instance. Obsolete with MODES_UI
+ public boolean snoozing; // user manually disabled this instance.
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
public String name; // required for automatic
@UnsupportedAppUsage
@@ -2635,9 +2587,7 @@ public class ZenModeConfig implements Parcelable {
// package name, only used for manual rules when they have turned DND on.
public String enabler;
public ZenPolicy zenPolicy;
- @FlaggedApi(Flags.FLAG_MODES_API)
@Nullable public ZenDeviceEffects zenDeviceEffects;
- public boolean modified; // rule has been modified from initial creation
public String pkg;
@AutomaticZenRule.Type
public int type = AutomaticZenRule.TYPE_UNKNOWN;
@@ -2668,6 +2618,18 @@ public class ZenModeConfig implements Parcelable {
@ConditionOverride
int conditionOverride = OVERRIDE_NONE;
+ /**
+ * Last time at which the rule was activated (for any reason, including overrides).
+ * If {@code null}, the rule has never been activated since its creation.
+ *
+ * <p>Note that this was previously untracked, so it will also be {@code null} for rules
+ * created before we started tracking and never activated since -- make sure to account for
+ * it, for example by falling back to {@link #creationTime} in logic involving this field.
+ */
+ @Nullable
+ @FlaggedApi(Flags.FLAG_MODES_CLEANUP_IMPLICIT)
+ public Instant lastActivation;
+
public ZenRule() { }
public ZenRule(Parcel source) {
@@ -2689,46 +2651,45 @@ public class ZenModeConfig implements Parcelable {
enabler = source.readString();
}
zenPolicy = source.readParcelable(null, android.service.notification.ZenPolicy.class);
- if (Flags.modesApi()) {
- zenDeviceEffects = source.readParcelable(null, ZenDeviceEffects.class);
- }
- modified = source.readInt() == 1;
+ zenDeviceEffects = source.readParcelable(null, ZenDeviceEffects.class);
pkg = source.readString();
- if (Flags.modesApi()) {
- allowManualInvocation = source.readBoolean();
- iconResName = source.readString();
- triggerDescription = source.readString();
- type = source.readInt();
- userModifiedFields = source.readInt();
- zenPolicyUserModifiedFields = source.readInt();
- zenDeviceEffectsUserModifiedFields = source.readInt();
- if (source.readInt() == 1) {
- deletionInstant = Instant.ofEpochMilli(source.readLong());
- }
- if (Flags.modesUi()) {
- disabledOrigin = source.readInt();
- legacySuppressedEffects = source.readInt();
- conditionOverride = source.readInt();
+ allowManualInvocation = source.readBoolean();
+ iconResName = source.readString();
+ triggerDescription = source.readString();
+ type = source.readInt();
+ userModifiedFields = source.readInt();
+ zenPolicyUserModifiedFields = source.readInt();
+ zenDeviceEffectsUserModifiedFields = source.readInt();
+ if (source.readInt() == 1) {
+ deletionInstant = Instant.ofEpochMilli(source.readLong());
+ }
+ if (Flags.modesUi()) {
+ disabledOrigin = source.readInt();
+ legacySuppressedEffects = source.readInt();
+ conditionOverride = source.readInt();
+ if (Flags.modesCleanupImplicit()) {
+ if (source.readInt() == 1) {
+ lastActivation = Instant.ofEpochMilli(source.readLong());
+ }
}
}
}
/**
- * Whether this ZenRule can be updated by an app. In general, rules that have been
- * customized by the user cannot be further updated by an app, with some exceptions:
+ * Whether this ZenRule has been customized by the user in any way.
+
+ * <p>In general, rules that have been customized by the user cannot be further updated by
+ * an app, with some exceptions:
* <ul>
* <li>Non user-configurable fields, like type, icon, configurationActivity, etc.
* <li>Name, if the name was not specifically modified by the user (to support language
* switches).
* </ul>
*/
- @FlaggedApi(Flags.FLAG_MODES_API)
- public boolean canBeUpdatedByApp() {
- // The rule is considered updateable if its bitmask has no user modifications, and
- // the bitmasks of the policy and device effects have no modification.
- return userModifiedFields == 0
- && zenPolicyUserModifiedFields == 0
- && zenDeviceEffectsUserModifiedFields == 0;
+ public boolean isUserModified() {
+ return userModifiedFields != 0
+ || zenPolicyUserModifiedFields != 0
+ || zenDeviceEffectsUserModifiedFields != 0;
}
@Override
@@ -2765,29 +2726,32 @@ public class ZenModeConfig implements Parcelable {
dest.writeInt(0);
}
dest.writeParcelable(zenPolicy, 0);
- if (Flags.modesApi()) {
- dest.writeParcelable(zenDeviceEffects, 0);
- }
- dest.writeInt(modified ? 1 : 0);
+ dest.writeParcelable(zenDeviceEffects, 0);
dest.writeString(pkg);
- if (Flags.modesApi()) {
- dest.writeBoolean(allowManualInvocation);
- dest.writeString(iconResName);
- dest.writeString(triggerDescription);
- dest.writeInt(type);
- dest.writeInt(userModifiedFields);
- dest.writeInt(zenPolicyUserModifiedFields);
- dest.writeInt(zenDeviceEffectsUserModifiedFields);
- if (deletionInstant != null) {
- dest.writeInt(1);
- dest.writeLong(deletionInstant.toEpochMilli());
- } else {
- dest.writeInt(0);
- }
- if (Flags.modesUi()) {
- dest.writeInt(disabledOrigin);
- dest.writeInt(legacySuppressedEffects);
- dest.writeInt(conditionOverride);
+ dest.writeBoolean(allowManualInvocation);
+ dest.writeString(iconResName);
+ dest.writeString(triggerDescription);
+ dest.writeInt(type);
+ dest.writeInt(userModifiedFields);
+ dest.writeInt(zenPolicyUserModifiedFields);
+ dest.writeInt(zenDeviceEffectsUserModifiedFields);
+ if (deletionInstant != null) {
+ dest.writeInt(1);
+ dest.writeLong(deletionInstant.toEpochMilli());
+ } else {
+ dest.writeInt(0);
+ }
+ if (Flags.modesUi()) {
+ dest.writeInt(disabledOrigin);
+ dest.writeInt(legacySuppressedEffects);
+ dest.writeInt(conditionOverride);
+ if (Flags.modesCleanupImplicit()) {
+ if (lastActivation != null) {
+ dest.writeInt(1);
+ dest.writeLong(lastActivation.toEpochMilli());
+ } else {
+ dest.writeInt(0);
+ }
}
}
}
@@ -2816,34 +2780,33 @@ public class ZenModeConfig implements Parcelable {
.append(",creationTime=").append(creationTime)
.append(",enabler=").append(enabler)
.append(",zenPolicy=").append(zenPolicy)
- .append(",modified=").append(modified)
- .append(",condition=").append(condition);
-
- if (Flags.modesApi()) {
- sb.append(",deviceEffects=").append(zenDeviceEffects)
- .append(",allowManualInvocation=").append(allowManualInvocation)
- .append(",iconResName=").append(iconResName)
- .append(",triggerDescription=").append(triggerDescription)
- .append(",type=").append(type);
- if (userModifiedFields != 0) {
- sb.append(",userModifiedFields=")
- .append(AutomaticZenRule.fieldsToString(userModifiedFields));
- }
- if (zenPolicyUserModifiedFields != 0) {
- sb.append(",zenPolicyUserModifiedFields=")
- .append(ZenPolicy.fieldsToString(zenPolicyUserModifiedFields));
- }
- if (zenDeviceEffectsUserModifiedFields != 0) {
- sb.append(",zenDeviceEffectsUserModifiedFields=")
- .append(ZenDeviceEffects.fieldsToString(
- zenDeviceEffectsUserModifiedFields));
- }
- if (deletionInstant != null) {
- sb.append(",deletionInstant=").append(deletionInstant);
- }
- if (Flags.modesUi()) {
- sb.append(",disabledOrigin=").append(disabledOrigin);
- sb.append(",legacySuppressedEffects=").append(legacySuppressedEffects);
+ .append(",condition=").append(condition)
+ .append(",deviceEffects=").append(zenDeviceEffects)
+ .append(",allowManualInvocation=").append(allowManualInvocation)
+ .append(",iconResName=").append(iconResName)
+ .append(",triggerDescription=").append(triggerDescription)
+ .append(",type=").append(type);
+ if (userModifiedFields != 0) {
+ sb.append(",userModifiedFields=")
+ .append(AutomaticZenRule.fieldsToString(userModifiedFields));
+ }
+ if (zenPolicyUserModifiedFields != 0) {
+ sb.append(",zenPolicyUserModifiedFields=")
+ .append(ZenPolicy.fieldsToString(zenPolicyUserModifiedFields));
+ }
+ if (zenDeviceEffectsUserModifiedFields != 0) {
+ sb.append(",zenDeviceEffectsUserModifiedFields=")
+ .append(ZenDeviceEffects.fieldsToString(
+ zenDeviceEffectsUserModifiedFields));
+ }
+ if (deletionInstant != null) {
+ sb.append(",deletionInstant=").append(deletionInstant);
+ }
+ if (Flags.modesUi()) {
+ sb.append(",disabledOrigin=").append(disabledOrigin);
+ sb.append(",legacySuppressedEffects=").append(legacySuppressedEffects);
+ if (Flags.modesCleanupImplicit()) {
+ sb.append(",lastActivation=").append(lastActivation);
}
}
@@ -2869,7 +2832,7 @@ public class ZenModeConfig implements Parcelable {
proto.write(ZenRuleProto.CREATION_TIME_MS, creationTime);
proto.write(ZenRuleProto.ENABLED, enabled);
proto.write(ZenRuleProto.ENABLER, enabler);
- if (Flags.modesApi() && Flags.modesUi()) {
+ if (Flags.modesUi()) {
proto.write(ZenRuleProto.IS_SNOOZING, conditionOverride == OVERRIDE_DEACTIVATE);
} else {
proto.write(ZenRuleProto.IS_SNOOZING, snoozing);
@@ -2887,7 +2850,6 @@ public class ZenModeConfig implements Parcelable {
if (zenPolicy != null) {
zenPolicy.dumpDebug(proto, ZenRuleProto.ZEN_POLICY);
}
- proto.write(ZenRuleProto.MODIFIED, modified);
proto.end(token);
}
@@ -2908,26 +2870,25 @@ public class ZenModeConfig implements Parcelable {
&& Objects.equals(other.enabler, enabler)
&& Objects.equals(other.zenPolicy, zenPolicy)
&& Objects.equals(other.pkg, pkg)
- && other.modified == modified;
+ && Objects.equals(other.zenDeviceEffects, zenDeviceEffects)
+ && other.allowManualInvocation == allowManualInvocation
+ && Objects.equals(other.iconResName, iconResName)
+ && Objects.equals(other.triggerDescription, triggerDescription)
+ && other.type == type
+ && other.userModifiedFields == userModifiedFields
+ && other.zenPolicyUserModifiedFields == zenPolicyUserModifiedFields
+ && other.zenDeviceEffectsUserModifiedFields
+ == zenDeviceEffectsUserModifiedFields
+ && Objects.equals(other.deletionInstant, deletionInstant);
- if (Flags.modesApi()) {
+ if (Flags.modesUi()) {
finalEquals = finalEquals
- && Objects.equals(other.zenDeviceEffects, zenDeviceEffects)
- && other.allowManualInvocation == allowManualInvocation
- && Objects.equals(other.iconResName, iconResName)
- && Objects.equals(other.triggerDescription, triggerDescription)
- && other.type == type
- && other.userModifiedFields == userModifiedFields
- && other.zenPolicyUserModifiedFields == zenPolicyUserModifiedFields
- && other.zenDeviceEffectsUserModifiedFields
- == zenDeviceEffectsUserModifiedFields
- && Objects.equals(other.deletionInstant, deletionInstant);
-
- if (Flags.modesUi()) {
+ && other.disabledOrigin == disabledOrigin
+ && other.legacySuppressedEffects == legacySuppressedEffects
+ && other.conditionOverride == conditionOverride;
+ if (Flags.modesCleanupImplicit()) {
finalEquals = finalEquals
- && other.disabledOrigin == disabledOrigin
- && other.legacySuppressedEffects == legacySuppressedEffects
- && other.conditionOverride == conditionOverride;
+ && Objects.equals(other.lastActivation, lastActivation);
}
}
@@ -2936,26 +2897,32 @@ public class ZenModeConfig implements Parcelable {
@Override
public int hashCode() {
- if (Flags.modesApi()) {
- if (Flags.modesUi()) {
+ if (Flags.modesUi()) {
+ if (Flags.modesCleanupImplicit()) {
return Objects.hash(enabled, snoozing, name, zenMode, conditionId, condition,
component, configurationActivity, pkg, id, enabler, zenPolicy,
- zenDeviceEffects, modified, allowManualInvocation, iconResName,
+ zenDeviceEffects, allowManualInvocation, iconResName,
triggerDescription, type, userModifiedFields,
zenPolicyUserModifiedFields, zenDeviceEffectsUserModifiedFields,
deletionInstant, disabledOrigin, legacySuppressedEffects,
- conditionOverride);
+ conditionOverride, lastActivation);
} else {
return Objects.hash(enabled, snoozing, name, zenMode, conditionId, condition,
component, configurationActivity, pkg, id, enabler, zenPolicy,
- zenDeviceEffects, modified, allowManualInvocation, iconResName,
+ zenDeviceEffects, allowManualInvocation, iconResName,
triggerDescription, type, userModifiedFields,
zenPolicyUserModifiedFields, zenDeviceEffectsUserModifiedFields,
- deletionInstant);
+ deletionInstant, disabledOrigin, legacySuppressedEffects,
+ conditionOverride);
}
+ } else {
+ return Objects.hash(enabled, snoozing, name, zenMode, conditionId, condition,
+ component, configurationActivity, pkg, id, enabler, zenPolicy,
+ zenDeviceEffects, allowManualInvocation, iconResName,
+ triggerDescription, type, userModifiedFields,
+ zenPolicyUserModifiedFields, zenDeviceEffectsUserModifiedFields,
+ deletionInstant);
}
- return Objects.hash(enabled, snoozing, name, zenMode, conditionId, condition,
- component, configurationActivity, pkg, id, enabler, zenPolicy, modified);
}
/** Returns a deep copy of the {@link ZenRule}. */
@@ -2971,7 +2938,7 @@ public class ZenModeConfig implements Parcelable {
}
public boolean isActive() {
- if (Flags.modesApi() && Flags.modesUi()) {
+ if (Flags.modesUi()) {
if (!enabled || getPkg() == null) {
return false;
} else if (conditionOverride == OVERRIDE_ACTIVATE) {
@@ -2989,7 +2956,7 @@ public class ZenModeConfig implements Parcelable {
@VisibleForTesting(otherwise = VisibleForTesting.NONE)
@ConditionOverride
public int getConditionOverride() {
- if (Flags.modesApi() && Flags.modesUi()) {
+ if (Flags.modesUi()) {
return conditionOverride;
} else {
return snoozing ? OVERRIDE_DEACTIVATE : OVERRIDE_NONE;
@@ -2997,7 +2964,7 @@ public class ZenModeConfig implements Parcelable {
}
public void setConditionOverride(@ConditionOverride int value) {
- if (Flags.modesApi() && Flags.modesUi()) {
+ if (Flags.modesUi()) {
conditionOverride = value;
} else {
if (value == OVERRIDE_ACTIVATE) {
@@ -3026,7 +2993,7 @@ public class ZenModeConfig implements Parcelable {
* manual deactivation (which used to be called "snoozing").
*/
public void reconsiderConditionOverride() {
- if (Flags.modesApi() && Flags.modesUi()) {
+ if (Flags.modesUi()) {
if (conditionOverride == OVERRIDE_ACTIVATE && isTrueOrUnknown()) {
resetConditionOverride();
} else if (conditionOverride == OVERRIDE_DEACTIVATE && !isTrueOrUnknown()) {
@@ -3085,11 +3052,8 @@ public class ZenModeConfig implements Parcelable {
& NotificationManager.Policy.PRIORITY_CATEGORY_REPEAT_CALLERS) != 0;
boolean allowConversations = (policy.priorityConversationSenders
& Policy.PRIORITY_CATEGORY_CONVERSATIONS) != 0;
- boolean areChannelsBypassingDnd = (policy.state & Policy.STATE_CHANNELS_BYPASSING_DND) != 0;
- if (Flags.modesApi()) {
- areChannelsBypassingDnd = policy.hasPriorityChannels()
- && policy.allowPriorityChannels();
- }
+ boolean areChannelsBypassingDnd =
+ policy.hasPriorityChannels() && policy.allowPriorityChannels();
boolean allowSystem = (policy.priorityCategories & Policy.PRIORITY_CATEGORY_SYSTEM) != 0;
return !allowReminders && !allowCalls && !allowMessages && !allowEvents
&& !allowRepeatCallers && !areChannelsBypassingDnd && !allowSystem
@@ -3129,15 +3093,12 @@ public class ZenModeConfig implements Parcelable {
&& !policy.isCategoryAllowed(PRIORITY_CATEGORY_EVENTS, false)
&& !policy.isCategoryAllowed(PRIORITY_CATEGORY_REPEAT_CALLERS, false)
&& !policy.isCategoryAllowed(PRIORITY_CATEGORY_SYSTEM, false)
- && !(config.areChannelsBypassingDnd && policy.getPriorityChannelsAllowed()
+ && !(config.hasPriorityChannels && policy.getPriorityChannelsAllowed()
== STATE_ALLOW);
} else {
- boolean areChannelsBypassingDnd = config.areChannelsBypassingDnd;
- if (Flags.modesApi()) {
- areChannelsBypassingDnd = config.areChannelsBypassingDnd
- && config.isAllowPriorityChannels();
- }
+ boolean areChannelsBypassingDnd = config.hasPriorityChannels
+ && config.isAllowPriorityChannels();
return !config.isAllowReminders() && !config.isAllowCalls() && !config.isAllowMessages()
&& !config.isAllowEvents() && !config.isAllowRepeatCallers()
&& !areChannelsBypassingDnd && !config.isAllowSystem();
diff --git a/core/java/android/service/notification/ZenModeDiff.java b/core/java/android/service/notification/ZenModeDiff.java
index 31acd248dcc0..c159e4016095 100644
--- a/core/java/android/service/notification/ZenModeDiff.java
+++ b/core/java/android/service/notification/ZenModeDiff.java
@@ -16,7 +16,6 @@
package android.service.notification;
-import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.Nullable;
import android.app.Flags;
@@ -249,7 +248,7 @@ public class ZenModeDiff {
public static final String FIELD_ALLOW_MESSAGES_FROM = "allowMessagesFrom";
public static final String FIELD_ALLOW_CONVERSATIONS_FROM = "allowConversationsFrom";
public static final String FIELD_SUPPRESSED_VISUAL_EFFECTS = "suppressedVisualEffects";
- public static final String FIELD_ARE_CHANNELS_BYPASSING_DND = "areChannelsBypassingDnd";
+ public static final String FIELD_HAS_PRIORITY_CHANNELS = "hasPriorityChannels";
public static final String FIELD_ALLOW_PRIORITY_CHANNELS = "allowPriorityChannels";
private static final Set<String> PEOPLE_TYPE_FIELDS =
Set.of(FIELD_ALLOW_CALLS_FROM, FIELD_ALLOW_MESSAGES_FROM);
@@ -323,15 +322,13 @@ public class ZenModeDiff {
addField(FIELD_SUPPRESSED_VISUAL_EFFECTS,
new FieldDiff<>(from.suppressedVisualEffects, to.suppressedVisualEffects));
}
- if (from.areChannelsBypassingDnd != to.areChannelsBypassingDnd) {
- addField(FIELD_ARE_CHANNELS_BYPASSING_DND,
- new FieldDiff<>(from.areChannelsBypassingDnd, to.areChannelsBypassingDnd));
+ if (from.hasPriorityChannels != to.hasPriorityChannels) {
+ addField(FIELD_HAS_PRIORITY_CHANNELS,
+ new FieldDiff<>(from.hasPriorityChannels, to.hasPriorityChannels));
}
- if (Flags.modesApi()) {
- if (from.allowPriorityChannels != to.allowPriorityChannels) {
- addField(FIELD_ALLOW_PRIORITY_CHANNELS,
- new FieldDiff<>(from.allowPriorityChannels, to.allowPriorityChannels));
- }
+ if (from.allowPriorityChannels != to.allowPriorityChannels) {
+ addField(FIELD_ALLOW_PRIORITY_CHANNELS,
+ new FieldDiff<>(from.allowPriorityChannels, to.allowPriorityChannels));
}
// Compare automatic and manual rules
@@ -491,7 +488,6 @@ public class ZenModeDiff {
public static final String FIELD_ENABLER = "enabler";
public static final String FIELD_ZEN_POLICY = "zenPolicy";
public static final String FIELD_ZEN_DEVICE_EFFECTS = "zenDeviceEffects";
- public static final String FIELD_MODIFIED = "modified";
public static final String FIELD_PKG = "pkg";
public static final String FIELD_ALLOW_MANUAL = "allowManualInvocation";
public static final String FIELD_ICON_RES = "iconResName";
@@ -532,7 +528,7 @@ public class ZenModeDiff {
if (from.enabled != to.enabled) {
addField(FIELD_ENABLED, new FieldDiff<>(from.enabled, to.enabled));
}
- if (Flags.modesApi() && Flags.modesUi()) {
+ if (Flags.modesUi()) {
if (from.conditionOverride != to.conditionOverride) {
addField(FIELD_CONDITION_OVERRIDE,
new FieldDiff<>(from.conditionOverride, to.conditionOverride));
@@ -572,51 +568,40 @@ public class ZenModeDiff {
if (!Objects.equals(from.enabler, to.enabler)) {
addField(FIELD_ENABLER, new FieldDiff<>(from.enabler, to.enabler));
}
- if (android.app.Flags.modesApi()) {
- PolicyDiff policyDiff = new PolicyDiff(from.zenPolicy, to.zenPolicy);
- if (policyDiff.hasDiff()) {
- addField(FIELD_ZEN_POLICY, new FieldDiff<>(from.zenPolicy, to.zenPolicy,
- policyDiff));
- }
- } else {
- if (!Objects.equals(from.zenPolicy, to.zenPolicy)) {
- addField(FIELD_ZEN_POLICY, new FieldDiff<>(from.zenPolicy, to.zenPolicy));
- }
- }
- if (from.modified != to.modified) {
- addField(FIELD_MODIFIED, new FieldDiff<>(from.modified, to.modified));
+ PolicyDiff policyDiff = new PolicyDiff(from.zenPolicy, to.zenPolicy);
+ if (policyDiff.hasDiff()) {
+ addField(FIELD_ZEN_POLICY, new FieldDiff<>(from.zenPolicy, to.zenPolicy,
+ policyDiff));
}
if (!Objects.equals(from.pkg, to.pkg)) {
addField(FIELD_PKG, new FieldDiff<>(from.pkg, to.pkg));
}
- if (android.app.Flags.modesApi()) {
- DeviceEffectsDiff deviceEffectsDiff = new DeviceEffectsDiff(from.zenDeviceEffects,
- to.zenDeviceEffects);
- if (deviceEffectsDiff.hasDiff()) {
- addField(FIELD_ZEN_DEVICE_EFFECTS,
- new FieldDiff<>(from.zenDeviceEffects, to.zenDeviceEffects,
- deviceEffectsDiff));
- }
- if (!Objects.equals(from.triggerDescription, to.triggerDescription)) {
- addField(FIELD_TRIGGER_DESCRIPTION,
- new FieldDiff<>(from.triggerDescription, to.triggerDescription));
- }
- if (from.type != to.type) {
- addField(FIELD_TYPE, new FieldDiff<>(from.type, to.type));
- }
- if (from.allowManualInvocation != to.allowManualInvocation) {
- addField(FIELD_ALLOW_MANUAL,
- new FieldDiff<>(from.allowManualInvocation, to.allowManualInvocation));
- }
- if (!Objects.equals(from.iconResName, to.iconResName)) {
- addField(FIELD_ICON_RES, new FieldDiff<>(from.iconResName, to.iconResName));
- }
- if (android.app.Flags.modesUi()) {
- if (from.legacySuppressedEffects != to.legacySuppressedEffects) {
- addField(FIELD_LEGACY_SUPPRESSED_EFFECTS,
- new FieldDiff<>(from.legacySuppressedEffects,
- to.legacySuppressedEffects));
- }
+ DeviceEffectsDiff deviceEffectsDiff = new DeviceEffectsDiff(from.zenDeviceEffects,
+ to.zenDeviceEffects);
+ if (deviceEffectsDiff.hasDiff()) {
+ addField(FIELD_ZEN_DEVICE_EFFECTS,
+ new FieldDiff<>(from.zenDeviceEffects, to.zenDeviceEffects,
+ deviceEffectsDiff));
+ }
+ if (!Objects.equals(from.triggerDescription, to.triggerDescription)) {
+ addField(FIELD_TRIGGER_DESCRIPTION,
+ new FieldDiff<>(from.triggerDescription, to.triggerDescription));
+ }
+ if (from.type != to.type) {
+ addField(FIELD_TYPE, new FieldDiff<>(from.type, to.type));
+ }
+ if (from.allowManualInvocation != to.allowManualInvocation) {
+ addField(FIELD_ALLOW_MANUAL,
+ new FieldDiff<>(from.allowManualInvocation, to.allowManualInvocation));
+ }
+ if (!Objects.equals(from.iconResName, to.iconResName)) {
+ addField(FIELD_ICON_RES, new FieldDiff<>(from.iconResName, to.iconResName));
+ }
+ if (android.app.Flags.modesUi()) {
+ if (from.legacySuppressedEffects != to.legacySuppressedEffects) {
+ addField(FIELD_LEGACY_SUPPRESSED_EFFECTS,
+ new FieldDiff<>(from.legacySuppressedEffects,
+ to.legacySuppressedEffects));
}
}
}
@@ -702,7 +687,6 @@ public class ZenModeDiff {
* Diff class representing a change between two
* {@link android.service.notification.ZenDeviceEffects}.
*/
- @FlaggedApi(Flags.FLAG_MODES_API)
public static class DeviceEffectsDiff extends BaseDiff {
public static final String FIELD_GRAYSCALE = "mGrayscale";
public static final String FIELD_SUPPRESS_AMBIENT_DISPLAY = "mSuppressAmbientDisplay";
@@ -843,7 +827,6 @@ public class ZenModeDiff {
/**
* Diff class representing a change between two {@link android.service.notification.ZenPolicy}.
*/
- @FlaggedApi(Flags.FLAG_MODES_API)
public static class PolicyDiff extends BaseDiff {
public static final String FIELD_PRIORITY_CATEGORY_REMINDERS =
"mPriorityCategories_Reminders";
diff --git a/core/java/android/service/notification/ZenPolicy.java b/core/java/android/service/notification/ZenPolicy.java
index 4cff67e24a0f..6b98c4144f91 100644
--- a/core/java/android/service/notification/ZenPolicy.java
+++ b/core/java/android/service/notification/ZenPolicy.java
@@ -16,13 +16,11 @@
package android.service.notification;
-import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SuppressLint;
import android.annotation.TestApi;
-import android.app.Flags;
import android.app.Notification;
import android.app.NotificationChannel;
import android.os.Parcel;
@@ -78,91 +76,74 @@ public final class ZenPolicy implements Parcelable {
* the same time.
* @hide
*/
- @FlaggedApi(Flags.FLAG_MODES_API)
public static final int FIELD_MESSAGES = 1 << 0;
/**
* Covers modifications to CALL_SENDERS and PRIORITY_CATEGORY_CALLS, which are set at
* the same time.
* @hide
*/
- @FlaggedApi(Flags.FLAG_MODES_API)
public static final int FIELD_CALLS = 1 << 1;
/**
* Covers modifications to CONVERSATION_SENDERS and PRIORITY_CATEGORY_CONVERSATIONS, which are
* set at the same time.
* @hide
*/
- @FlaggedApi(Flags.FLAG_MODES_API)
public static final int FIELD_CONVERSATIONS = 1 << 2;
/**
* @hide
*/
- @FlaggedApi(Flags.FLAG_MODES_API)
public static final int FIELD_ALLOW_CHANNELS = 1 << 3;
/**
* @hide
*/
- @FlaggedApi(Flags.FLAG_MODES_API)
public static final int FIELD_PRIORITY_CATEGORY_REMINDERS = 1 << 4;
/**
* @hide
*/
- @FlaggedApi(Flags.FLAG_MODES_API)
public static final int FIELD_PRIORITY_CATEGORY_EVENTS = 1 << 5;
/**
* @hide
*/
- @FlaggedApi(Flags.FLAG_MODES_API)
public static final int FIELD_PRIORITY_CATEGORY_REPEAT_CALLERS = 1 << 6;
/**
* @hide
*/
- @FlaggedApi(Flags.FLAG_MODES_API)
public static final int FIELD_PRIORITY_CATEGORY_ALARMS = 1 << 7;
/**
* @hide
*/
- @FlaggedApi(Flags.FLAG_MODES_API)
public static final int FIELD_PRIORITY_CATEGORY_MEDIA = 1 << 8;
/**
* @hide
*/
- @FlaggedApi(Flags.FLAG_MODES_API)
public static final int FIELD_PRIORITY_CATEGORY_SYSTEM = 1 << 9;
/**
* @hide
*/
- @FlaggedApi(Flags.FLAG_MODES_API)
public static final int FIELD_VISUAL_EFFECT_FULL_SCREEN_INTENT = 1 << 10;
/**
* @hide
*/
- @FlaggedApi(Flags.FLAG_MODES_API)
public static final int FIELD_VISUAL_EFFECT_LIGHTS = 1 << 11;
/**
* @hide
*/
- @FlaggedApi(Flags.FLAG_MODES_API)
public static final int FIELD_VISUAL_EFFECT_PEEK = 1 << 12;
/**
* @hide
*/
- @FlaggedApi(Flags.FLAG_MODES_API)
public static final int FIELD_VISUAL_EFFECT_STATUS_BAR = 1 << 13;
/**
* @hide
*/
- @FlaggedApi(Flags.FLAG_MODES_API)
public static final int FIELD_VISUAL_EFFECT_BADGE = 1 << 14;
/**
* @hide
*/
- @FlaggedApi(Flags.FLAG_MODES_API)
public static final int FIELD_VISUAL_EFFECT_AMBIENT = 1 << 15;
/**
* @hide
*/
- @FlaggedApi(Flags.FLAG_MODES_API)
public static final int FIELD_VISUAL_EFFECT_NOTIFICATION_LIST = 1 << 16;
private List<Integer> mPriorityCategories;
@@ -170,7 +151,6 @@ public final class ZenPolicy implements Parcelable {
private @PeopleType int mPriorityMessages = PEOPLE_TYPE_UNSET;
private @PeopleType int mPriorityCalls = PEOPLE_TYPE_UNSET;
private @ConversationSenders int mConversationSenders = CONVERSATION_SENDERS_UNSET;
- @FlaggedApi(Flags.FLAG_MODES_API)
private @ChannelType int mAllowChannels = CHANNEL_POLICY_UNSET;
/** @hide */
@@ -358,7 +338,6 @@ public final class ZenPolicy implements Parcelable {
*
* @hide
*/
- @FlaggedApi(Flags.FLAG_MODES_API)
public static final int CHANNEL_POLICY_UNSET = 0;
/**
@@ -367,7 +346,6 @@ public final class ZenPolicy implements Parcelable {
*
* @hide
*/
- @FlaggedApi(Flags.FLAG_MODES_API)
public static final int CHANNEL_POLICY_PRIORITY = 1;
/**
@@ -376,7 +354,6 @@ public final class ZenPolicy implements Parcelable {
*
* @hide
*/
- @FlaggedApi(Flags.FLAG_MODES_API)
public static final int CHANNEL_POLICY_NONE = 2;
/** @hide */
@@ -386,7 +363,6 @@ public final class ZenPolicy implements Parcelable {
}
/** @hide */
- @FlaggedApi(Flags.FLAG_MODES_API)
public ZenPolicy(List<Integer> priorityCategories, List<Integer> visualEffects,
@PeopleType int priorityMessages, @PeopleType int priorityCalls,
@ConversationSenders int conversationSenders, @ChannelType int allowChannels) {
@@ -409,7 +385,6 @@ public final class ZenPolicy implements Parcelable {
*
* @hide
*/
- @FlaggedApi(Flags.FLAG_MODES_API)
public static ZenPolicy getBasePolicyInterruptionFilterAlarms() {
return new ZenPolicy.Builder()
.disallowAllSounds()
@@ -430,7 +405,6 @@ public final class ZenPolicy implements Parcelable {
*
* @hide
*/
- @FlaggedApi(Flags.FLAG_MODES_API)
public static ZenPolicy getBasePolicyInterruptionFilterNone() {
return new ZenPolicy.Builder()
.disallowAllSounds()
@@ -628,7 +602,6 @@ public final class ZenPolicy implements Parcelable {
* channels may bypass; if {@link #STATE_DISALLOW}, then even notifications from channels
* with {@link NotificationChannel#canBypassDnd()} will be intercepted.
*/
- @FlaggedApi(Flags.FLAG_MODES_API)
public @State int getPriorityChannelsAllowed() {
switch (mAllowChannels) {
case CHANNEL_POLICY_PRIORITY:
@@ -695,14 +668,10 @@ public final class ZenPolicy implements Parcelable {
* Builds the current ZenPolicy.
*/
public @NonNull ZenPolicy build() {
- if (Flags.modesApi()) {
- return new ZenPolicy(new ArrayList<>(mZenPolicy.mPriorityCategories),
- new ArrayList<>(mZenPolicy.mVisualEffects),
- mZenPolicy.mPriorityMessages, mZenPolicy.mPriorityCalls,
- mZenPolicy.mConversationSenders, mZenPolicy.mAllowChannels);
- } else {
- return mZenPolicy.copy();
- }
+ return new ZenPolicy(new ArrayList<>(mZenPolicy.mPriorityCategories),
+ new ArrayList<>(mZenPolicy.mVisualEffects),
+ mZenPolicy.mPriorityMessages, mZenPolicy.mPriorityCalls,
+ mZenPolicy.mConversationSenders, mZenPolicy.mAllowChannels);
}
/**
@@ -1054,7 +1023,6 @@ public final class ZenPolicy implements Parcelable {
* Set whether priority channels are permitted to break through DND.
*/
@SuppressLint("BuilderSetStyle")
- @FlaggedApi(Flags.FLAG_MODES_API)
public @NonNull Builder allowPriorityChannels(boolean allow) {
mZenPolicy.mAllowChannels = allow ? CHANNEL_POLICY_PRIORITY : CHANNEL_POLICY_NONE;
return this;
@@ -1079,38 +1047,21 @@ public final class ZenPolicy implements Parcelable {
dest.writeInt(mPriorityMessages);
dest.writeInt(mPriorityCalls);
dest.writeInt(mConversationSenders);
- if (Flags.modesApi()) {
- dest.writeInt(mAllowChannels);
- }
+ dest.writeInt(mAllowChannels);
}
public static final @NonNull Creator<ZenPolicy> CREATOR =
new Creator<ZenPolicy>() {
@Override
public ZenPolicy createFromParcel(Parcel source) {
- ZenPolicy policy;
- if (Flags.modesApi()) {
- policy = new ZenPolicy(
- trimList(source.readArrayList(Integer.class.getClassLoader(),
- Integer.class), NUM_PRIORITY_CATEGORIES),
- trimList(source.readArrayList(Integer.class.getClassLoader(),
- Integer.class), NUM_VISUAL_EFFECTS),
- source.readInt(), source.readInt(), source.readInt(),
- source.readInt()
+ return new ZenPolicy(
+ trimList(source.readArrayList(Integer.class.getClassLoader(),
+ Integer.class), NUM_PRIORITY_CATEGORIES),
+ trimList(source.readArrayList(Integer.class.getClassLoader(),
+ Integer.class), NUM_VISUAL_EFFECTS),
+ source.readInt(), source.readInt(), source.readInt(),
+ source.readInt()
);
- } else {
- policy = new ZenPolicy();
- policy.mPriorityCategories =
- trimList(source.readArrayList(Integer.class.getClassLoader(),
- Integer.class), NUM_PRIORITY_CATEGORIES);
- policy.mVisualEffects =
- trimList(source.readArrayList(Integer.class.getClassLoader(),
- Integer.class), NUM_VISUAL_EFFECTS);
- policy.mPriorityMessages = source.readInt();
- policy.mPriorityCalls = source.readInt();
- policy.mConversationSenders = source.readInt();
- }
- return policy;
}
@Override
@@ -1121,18 +1072,16 @@ public final class ZenPolicy implements Parcelable {
@Override
public String toString() {
- StringBuilder sb = new StringBuilder(ZenPolicy.class.getSimpleName())
+ return new StringBuilder(ZenPolicy.class.getSimpleName())
.append('{')
.append("priorityCategories=[").append(priorityCategoriesToString())
.append("], visualEffects=[").append(visualEffectsToString())
.append("], priorityCallsSenders=").append(peopleTypeToString(mPriorityCalls))
.append(", priorityMessagesSenders=").append(peopleTypeToString(mPriorityMessages))
.append(", priorityConversationSenders=").append(
- conversationTypeToString(mConversationSenders));
- if (Flags.modesApi()) {
- sb.append(", allowChannels=").append(channelTypeToString(mAllowChannels));
- }
- return sb.append('}').toString();
+ conversationTypeToString(mConversationSenders))
+ .append(", allowChannels=").append(channelTypeToString(mAllowChannels))
+ .append('}').toString();
}
/** @hide */
@@ -1325,7 +1274,6 @@ public final class ZenPolicy implements Parcelable {
/**
* @hide
*/
- @FlaggedApi(Flags.FLAG_MODES_API)
public static String channelTypeToString(@ChannelType int channelType) {
switch (channelType) {
case CHANNEL_POLICY_UNSET:
@@ -1344,25 +1292,18 @@ public final class ZenPolicy implements Parcelable {
if (o == this) return true;
final ZenPolicy other = (ZenPolicy) o;
- boolean eq = Objects.equals(other.mPriorityCategories, mPriorityCategories)
+ return Objects.equals(other.mPriorityCategories, mPriorityCategories)
&& Objects.equals(other.mVisualEffects, mVisualEffects)
&& other.mPriorityCalls == mPriorityCalls
&& other.mPriorityMessages == mPriorityMessages
- && other.mConversationSenders == mConversationSenders;
- if (Flags.modesApi()) {
- return eq && other.mAllowChannels == mAllowChannels;
- }
- return eq;
+ && other.mConversationSenders == mConversationSenders
+ && other.mAllowChannels == mAllowChannels;
}
@Override
public int hashCode() {
- if (Flags.modesApi()) {
- return Objects.hash(mPriorityCategories, mVisualEffects, mPriorityCalls,
- mPriorityMessages, mConversationSenders, mAllowChannels);
- }
- return Objects.hash(mPriorityCategories, mVisualEffects, mPriorityCalls, mPriorityMessages,
- mConversationSenders);
+ return Objects.hash(mPriorityCategories, mVisualEffects, mPriorityCalls,
+ mPriorityMessages, mConversationSenders, mAllowChannels);
}
private @State int getZenPolicyPriorityCategoryState(@PriorityCategory int
@@ -1480,13 +1421,10 @@ public final class ZenPolicy implements Parcelable {
}
}
- // apply allowed channels
- if (Flags.modesApi()) {
- // if no channels are allowed, can't newly allow them
- if (mAllowChannels != CHANNEL_POLICY_NONE
- && policyToApply.mAllowChannels != CHANNEL_POLICY_UNSET) {
- mAllowChannels = policyToApply.mAllowChannels;
- }
+ // apply allowed channels -> if no channels are allowed, can't newly allow them
+ if (mAllowChannels != CHANNEL_POLICY_NONE
+ && policyToApply.mAllowChannels != CHANNEL_POLICY_UNSET) {
+ mAllowChannels = policyToApply.mAllowChannels;
}
}
@@ -1499,7 +1437,6 @@ public final class ZenPolicy implements Parcelable {
* @hide
*/
@TestApi
- @FlaggedApi(Flags.FLAG_MODES_API)
public @NonNull ZenPolicy overwrittenWith(@Nullable ZenPolicy newPolicy) {
ZenPolicy result = this.copy();
@@ -1596,10 +1533,7 @@ public final class ZenPolicy implements Parcelable {
proto.write(DNDPolicyProto.ALLOW_CALLS_FROM, getPriorityCallSenders());
proto.write(DNDPolicyProto.ALLOW_MESSAGES_FROM, getPriorityMessageSenders());
proto.write(DNDPolicyProto.ALLOW_CONVERSATIONS_FROM, getPriorityConversationSenders());
-
- if (Flags.modesApi()) {
- proto.write(DNDPolicyProto.ALLOW_CHANNELS, getPriorityChannelsAllowed());
- }
+ proto.write(DNDPolicyProto.ALLOW_CHANNELS, getPriorityChannelsAllowed());
proto.flush();
return bytes.toByteArray();
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index 464756842caf..41a64e22e058 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -1595,8 +1595,17 @@ public abstract class WallpaperService extends Service {
mWindow.setSession(mSession);
mLayout.packageName = getPackageName();
- mIWallpaperEngine.mDisplayManager.registerDisplayListener(mDisplayListener,
- mCaller.getHandler());
+ if (com.android.server.display.feature.flags.Flags
+ .displayListenerPerformanceImprovements()
+ && com.android.server.display.feature.flags.Flags
+ .committedStateSeparateEvent()) {
+ mIWallpaperEngine.mDisplayManager.registerDisplayListener(mDisplayListener,
+ mCaller.getHandler(), DisplayManager.EVENT_TYPE_DISPLAY_CHANGED,
+ DisplayManager.PRIVATE_EVENT_TYPE_DISPLAY_COMMITTED_STATE_CHANGED);
+ } else {
+ mIWallpaperEngine.mDisplayManager.registerDisplayListener(mDisplayListener,
+ mCaller.getHandler());
+ }
mDisplay = mIWallpaperEngine.mDisplay;
// Use window context of TYPE_WALLPAPER so client can access UI resources correctly.
mDisplayContext = createDisplayContext(mDisplay)
diff --git a/core/java/android/telephony/TelephonyCallback.java b/core/java/android/telephony/TelephonyCallback.java
index d05fa9e43cd4..daa4e8936e95 100644
--- a/core/java/android/telephony/TelephonyCallback.java
+++ b/core/java/android/telephony/TelephonyCallback.java
@@ -2330,8 +2330,6 @@ public class TelephonyCallback {
}
public void onCarrierRoamingNtnModeChanged(boolean active) {
- if (!Flags.carrierEnabledSatelliteFlag()) return;
-
CarrierRoamingNtnListener listener =
(CarrierRoamingNtnListener) mTelephonyCallbackWeakRef.get();
if (listener == null) return;
diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java
index ca0959af3ff8..231aa6816908 100644
--- a/core/java/android/view/Display.java
+++ b/core/java/android/view/Display.java
@@ -1599,7 +1599,6 @@ public final class Display {
mGlobal.registerDisplayListener(toRegister, executor,
DisplayManagerGlobal
.INTERNAL_EVENT_FLAG_DISPLAY_BASIC_CHANGED
- | DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_REFRESH_RATE
| DisplayManagerGlobal
.INTERNAL_EVENT_FLAG_DISPLAY_HDR_SDR_RATIO_CHANGED,
ActivityThread.currentPackageName());
diff --git a/core/java/android/view/DisplayInfo.java b/core/java/android/view/DisplayInfo.java
index ecdbaa3cd2f4..d880072aa404 100644
--- a/core/java/android/view/DisplayInfo.java
+++ b/core/java/android/view/DisplayInfo.java
@@ -44,6 +44,7 @@ import android.util.SparseArray;
import android.util.proto.ProtoOutputStream;
import com.android.internal.display.BrightnessSynchronizer;
+import com.android.server.display.feature.flags.Flags;
import java.util.Arrays;
import java.util.Objects;
@@ -447,18 +448,20 @@ public final class DisplayInfo implements Parcelable {
}
public boolean equals(DisplayInfo other) {
- return equals(other, /* compareRefreshRate */ true);
+ return equals(other, /* compareOnlyBasicChanges */ false);
}
/**
* Compares if the two DisplayInfo objects are equal or not
* @param other The other DisplayInfo against which the comparison is to be done
- * @param compareRefreshRate Indicates if the refresh rate is also to be considered in
- * comparison
+ * @param compareOnlyBasicChanges Indicates if the changes to be compared are the ones which
+ * could lead to an emission of
+ * {@link android.hardware.display.DisplayManager.EVENT_TYPE_DISPLAY_CHANGED}
+ * event
* @return
*/
- public boolean equals(DisplayInfo other, boolean compareRefreshRate) {
- boolean isEqualWithoutRefreshRate = other != null
+ public boolean equals(DisplayInfo other, boolean compareOnlyBasicChanges) {
+ boolean isEqualWithOnlyBasicChanges = other != null
&& layerStack == other.layerStack
&& flags == other.flags
&& type == other.type
@@ -494,7 +497,6 @@ public final class DisplayInfo implements Parcelable {
&& physicalXDpi == other.physicalXDpi
&& physicalYDpi == other.physicalYDpi
&& state == other.state
- && committedState == other.committedState
&& ownerUid == other.ownerUid
&& Objects.equals(ownerPackageName, other.ownerPackageName)
&& removeMode == other.removeMode
@@ -512,14 +514,19 @@ public final class DisplayInfo implements Parcelable {
thermalBrightnessThrottlingDataId, other.thermalBrightnessThrottlingDataId)
&& canHostTasks == other.canHostTasks;
- if (compareRefreshRate) {
- return isEqualWithoutRefreshRate
+ if (!Flags.committedStateSeparateEvent()) {
+ isEqualWithOnlyBasicChanges = isEqualWithOnlyBasicChanges
+ && (committedState == other.committedState);
+ }
+ if (!compareOnlyBasicChanges) {
+ return isEqualWithOnlyBasicChanges
&& (getRefreshRate() == other.getRefreshRate())
&& appVsyncOffsetNanos == other.appVsyncOffsetNanos
&& presentationDeadlineNanos == other.presentationDeadlineNanos
- && (modeId == other.modeId);
+ && (modeId == other.modeId)
+ && (committedState == other.committedState);
}
- return isEqualWithoutRefreshRate;
+ return isEqualWithOnlyBasicChanges;
}
@Override
diff --git a/core/java/android/view/NotificationHeaderView.java b/core/java/android/view/NotificationHeaderView.java
index 73cd5ecd39ef..df680c054f56 100644
--- a/core/java/android/view/NotificationHeaderView.java
+++ b/core/java/android/view/NotificationHeaderView.java
@@ -32,7 +32,6 @@ import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.util.AttributeSet;
-import android.util.TypedValue;
import android.widget.FrameLayout;
import android.widget.RelativeLayout;
import android.widget.RemoteViews;
@@ -266,20 +265,14 @@ public class NotificationHeaderView extends RelativeLayout {
? R.style.TextAppearance_DeviceDefault_Notification_Title
: R.style.TextAppearance_DeviceDefault_Notification_Info;
// Most of the time, we're showing text in the minimized state
- if (findViewById(R.id.header_text) instanceof TextView headerText) {
- headerText.setTextAppearance(styleResId);
- if (notificationsRedesignTemplates()) {
- // TODO: b/378660052 - When inlining the redesign flag, this should be updated
- // directly in TextAppearance_DeviceDefault_Notification_Title so we won't need to
- // override it here.
- float textSize = getContext().getResources().getDimension(
- R.dimen.notification_2025_title_text_size);
- headerText.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize);
- }
+ View headerText = findViewById(R.id.header_text);
+ if (headerText instanceof TextView) {
+ ((TextView) headerText).setTextAppearance(styleResId);
}
// If there's no summary or text, we show the app name instead of nothing
- if (findViewById(R.id.app_name_text) instanceof TextView appNameText) {
- appNameText.setTextAppearance(styleResId);
+ View appNameText = findViewById(R.id.app_name_text);
+ if (appNameText instanceof TextView) {
+ ((TextView) appNameText).setTextAppearance(styleResId);
}
}
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 80b4f2caabbb..6b6147a3749d 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -118,6 +118,7 @@ import static android.view.flags.Flags.addSchandleToVriSurface;
import static android.view.flags.Flags.disableDrawWakeLock;
import static android.view.flags.Flags.sensitiveContentAppProtection;
import static android.view.flags.Flags.sensitiveContentPrematureProtectionRemovedFix;
+import static android.view.flags.Flags.toolkitFrameRateDebug;
import static android.view.flags.Flags.toolkitFrameRateFunctionEnablingReadOnly;
import static android.view.flags.Flags.toolkitFrameRateTouchBoost25q1;
import static android.view.flags.Flags.toolkitFrameRateTypingReadOnly;
@@ -538,6 +539,11 @@ public final class ViewRootImpl implements ViewParent,
private static boolean sAlwaysAssignFocus;
/**
+ * whether we pre-initialized the Buffer Allocator
+ */
+ private static boolean sPreInitializedBufferAllocator = false;
+
+ /**
* This list must only be modified by the main thread.
*/
final ArrayList<WindowCallbacks> mWindowCallbacks = new ArrayList<>();
@@ -1222,6 +1228,7 @@ public final class ViewRootImpl implements ViewParent,
com.android.graphics.surfaceflinger.flags.Flags.vrrBugfix24q4();
private static final boolean sEnableVrr = ViewProperties.vrr_enabled().orElse(true);
private static final boolean sToolkitInitialTouchBoostFlagValue = toolkitInitialTouchBoost();
+ private static boolean sToolkitFrameRateDebugFlagValue = toolkitFrameRateDebug();
static {
sToolkitSetFrameRateReadOnlyFlagValue = toolkitSetFrameRateReadOnly();
@@ -1342,6 +1349,11 @@ public final class ViewRootImpl implements ViewParent,
com.android.server.display.feature.flags.Flags.subscribeGranularDisplayEvents();
mSendPerfHintOnTouch = adpfViewrootimplActionDownBoost();
+
+ if (!sPreInitializedBufferAllocator) {
+ preInitBufferAllocator();
+ sPreInitializedBufferAllocator = true;
+ }
}
public static void addFirstDrawHandler(Runnable callback) {
@@ -13129,6 +13141,11 @@ public final class ViewRootImpl implements ViewParent,
if (sToolkitFrameRateFunctionEnablingReadOnlyFlagValue) {
mFrameRateTransaction.setFrameRateCategory(mSurfaceControl,
frameRateCategory, false).applyAsyncUnsafe();
+
+ if (sToolkitFrameRateDebugFlagValue) {
+ Log.v(mTag, "### ViewRootImpl setFrameRateCategory '"
+ + categoryToString(frameRateCategory) + "'");
+ }
}
mLastPreferredFrameRateCategory = frameRateCategory;
}
@@ -13191,8 +13208,15 @@ public final class ViewRootImpl implements ViewParent,
if (preferredFrameRate > 0) {
mFrameRateTransaction.setFrameRate(mSurfaceControl, preferredFrameRate,
mFrameRateCompatibility);
+ if (sToolkitFrameRateDebugFlagValue) {
+ Log.v(mTag, "### ViewRootImpl setFrameRate '"
+ + preferredFrameRate + "'");
+ }
} else {
mFrameRateTransaction.clearFrameRate(mSurfaceControl);
+ if (sToolkitFrameRateDebugFlagValue) {
+ Log.v(mTag, "### ViewRootImpl setFrameRate 0 Hz");
+ }
}
mFrameRateTransaction.applyAsyncUnsafe();
}
@@ -13246,6 +13270,12 @@ public final class ViewRootImpl implements ViewParent,
// mFrameRateCategoryView = view == null ? "-" : view.getClass().getSimpleName();
}
mDrawnThisFrame = true;
+
+ if (sToolkitFrameRateDebugFlagValue) {
+ String viewName = view == null ? "-" : view.getClass().getSimpleName();
+ Log.v(mTag, "### View: " + viewName + " votes '"
+ + categoryToString(frameRateCategory) + "'");
+ }
}
/**
@@ -13562,4 +13592,10 @@ public final class ViewRootImpl implements ViewParent,
sProtoLogInitialized = true;
}
}
+
+ private void preInitBufferAllocator() {
+ if (com.android.graphics.hwui.flags.Flags.earlyPreinitBufferAllocator()) {
+ ThreadedRenderer.preInitBufferAllocator();
+ }
+ }
}
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index db699d7bfb06..93eed370004b 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -625,12 +625,6 @@ public interface WindowManager extends ViewManager {
int TRANSIT_FLAG_PHYSICAL_DISPLAY_SWITCH = (1 << 14); // 0x4000
/**
- * Transition flag: Indicates that aod is showing hidden by entering doze
- * @hide
- */
- int TRANSIT_FLAG_AOD_APPEARING = (1 << 15); // 0x8000
-
- /**
* @hide
*/
@IntDef(flag = true, prefix = { "TRANSIT_FLAG_" }, value = {
@@ -649,7 +643,6 @@ public interface WindowManager extends ViewManager {
TRANSIT_FLAG_KEYGUARD_OCCLUDING,
TRANSIT_FLAG_KEYGUARD_UNOCCLUDING,
TRANSIT_FLAG_PHYSICAL_DISPLAY_SWITCH,
- TRANSIT_FLAG_AOD_APPEARING,
})
@Retention(RetentionPolicy.SOURCE)
@interface TransitionFlags {}
@@ -666,8 +659,7 @@ public interface WindowManager extends ViewManager {
(TRANSIT_FLAG_KEYGUARD_GOING_AWAY
| TRANSIT_FLAG_KEYGUARD_APPEARING
| TRANSIT_FLAG_KEYGUARD_OCCLUDING
- | TRANSIT_FLAG_KEYGUARD_UNOCCLUDING
- | TRANSIT_FLAG_AOD_APPEARING);
+ | TRANSIT_FLAG_KEYGUARD_UNOCCLUDING);
/**
* Remove content mode: Indicates remove content mode is currently not defined.
diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
index 578b7b6a63fa..ede0b3cf8cce 100644
--- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java
+++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
@@ -2684,9 +2684,10 @@ public class AccessibilityNodeInfo implements Parcelable {
* <p><b>Note:</b> The start and end {@link SelectionPosition} of the provided {@link Selection}
* should be constructed with {@code this} node or a descendant of it.
*
- * <p><b>Note:</b> {@link AccessibilityNodeInfo#setFocusable} and {@link
- * AccessibilityNodeInfo#setFocused} should both be called with {@code true} before setting the
- * selection in order to make {@code this} node a candidate to contain a selection.
+ * <p><b>Note:</b> {@link AccessibilityNodeInfo#setFocusable} and
+ * {@link AccessibilityNodeInfo#setFocused} should both be called with {@code true}
+ * before setting the selection in order to make {@code this} node a candidate to
+ * contain a selection.
*
* <p><b>Note:</b> Cannot be called from an AccessibilityService. This class is made immutable
* before being delivered to an AccessibilityService.
@@ -2706,12 +2707,10 @@ public class AccessibilityNodeInfo implements Parcelable {
* Gets the extended selection, which is a representation of selection that spans multiple nodes
* that exist within the subtree of the node defining selection.
*
- * <p><b>Note:</b> The start and end {@link SelectionPosition} of the provided {@link Selection}
- * should be constructed with {@code this} node or a descendant of it.
- *
- * <p><b>Note:</b> In order for a node to be a candidate to contain a selection, {@link
- * AccessibilityNodeInfo#isFocusable()} ()} and {@link AccessibilityNodeInfo#isFocused()} should
- * both be return with {@code true}.
+ * <p><b>Note:</b> Nodes that are candidates to contain a selection should return
+ * {@code true} from {@link #isFocusable()} and {@link #isFocused()}.
+ * The start and end {@link SelectionPosition}s of this {@link Selection}
+ * should exist within {@code this} node or its descendants.
*
* @return The extended selection within the node's subtree, or {@code null} if no selection
* exists.
@@ -5840,8 +5839,8 @@ public class AccessibilityNodeInfo implements Parcelable {
/**
* Instantiates a new SelectionPosition.
*
- * @param view The {@link View} containing the virtual descendant associated with the
- * selection position.
+ * @param view The {@link View} containing the text associated with this selection
+ * position.
* @param offset The offset for a selection position within {@code view}'s text content,
* which should be a value between 0 and the length of {@code view}'s text.
*/
diff --git a/core/java/android/view/flags/refresh_rate_flags.aconfig b/core/java/android/view/flags/refresh_rate_flags.aconfig
index 3bc2205f8e1c..18fa0f353f36 100644
--- a/core/java/android/view/flags/refresh_rate_flags.aconfig
+++ b/core/java/android/view/flags/refresh_rate_flags.aconfig
@@ -143,4 +143,11 @@ flag {
namespace: "toolkit"
description: "Feature flag to update initial touch boost logic"
bug: "393004744"
+}
+
+flag {
+ name: "toolkit_frame_rate_debug"
+ namespace: "toolkit"
+ description: "Feature flag to ennable ARR debug message"
+ bug: "394614443"
} \ No newline at end of file
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 56f0415b40cc..421001f385d4 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -466,6 +466,26 @@ public final class InputMethodManager {
private static final long USE_ASYNC_SHOW_HIDE_METHOD = 352594277L; // This is a bug id.
/**
+ * Always return {@code true} when {@link #hideSoftInputFromWindow(IBinder, int)} and
+ * {@link #hideSoftInputFromWindow(IBinder, int, ResultReceiver, int, ImeTracker.Token)} is
+ * called.
+ * <p>
+ * Apps can incorrectly rely on the return value of
+ * {@link #hideSoftInputFromWindow(IBinder, int)} to guess whether the IME was hidden.
+ * However, it was never a guarantee that the IME will actually hide.
+ * Therefore, it was recommended using the {@link View.OnApplyWindowInsetsListener}.
+ * <p>
+ * Starting Android {@link Build.VERSION_CODES.BAKLAVA}, the return value will always be
+ * true, independent from whether the request was eventually sent to system server or not.
+ * Apps that need to know when the IME visibility changes should use
+ * {@link View.OnApplyWindowInsetsListener}. For apps that target earlier releases, it
+ * returns an estimate ({@code true} if the IME was showing before).
+ */
+ @ChangeId
+ @EnabledSince(targetSdkVersion = Build.VERSION_CODES.BAKLAVA)
+ private static final long ALWAYS_RETURN_TRUE_HIDE_SOFT_INPUT_FROM_WINDOW = 395521150L; // bug id
+
+ /**
* If {@code true}, avoid calling the
* {@link com.android.server.inputmethod.InputMethodManagerService InputMethodManagerService}
* by skipping the call to {@link IInputMethodManager#startInputOrWindowGainedFocus}
@@ -2531,9 +2551,14 @@ public final class InputMethodManager {
*
* @param windowToken The token of the window that is making the request,
* as returned by {@link View#getWindowToken() View.getWindowToken()}.
- * @return {@code true} if a request was sent to system_server, {@code false} otherwise. Note:
- * this does not return result of the request. For result use {@link ResultReceiver} in
- * {@link #hideSoftInputFromWindow(IBinder, int, ResultReceiver)} instead.
+ * @return <p>For apps targeting Android {@link Build.VERSION_CODES.BAKLAVA}, onwards, it
+ * will always return {@code true}. To see when the IME is hidden, use
+ * {@link View.OnApplyWindowInsetsListener} and verify the provided {@link WindowInsets} for
+ * the visibility of IME.
+ *
+ * <p>For apps targeting releases before Android Baklava: returns {@code true} if a request
+ * was sent to system_server, {@code false} otherwise. Note: This does not return the result
+ * of that request (i.e. whether the IME was actually hidden).
*/
public boolean hideSoftInputFromWindow(IBinder windowToken, @HideFlags int flags) {
return hideSoftInputFromWindow(windowToken, flags, null);
@@ -2563,7 +2588,8 @@ public final class InputMethodManager {
* {@link #RESULT_UNCHANGED_HIDDEN}, {@link #RESULT_SHOWN}, or
* {@link #RESULT_HIDDEN}.
* @return {@code true} if a request was sent to system_server, {@code false} otherwise. Note:
- * this does not return result of the request. For result use {@param resultReceiver} instead.
+ * This does not return the result of that request (i.e. whether the IME was actually hidden).
+ * For result use {@param resultReceiver} instead.
*
* @deprecated The {@link ResultReceiver} is not a reliable way of determining whether the
* Input Method is actually shown or hidden. If result is needed, use
@@ -2603,7 +2629,10 @@ public final class InputMethodManager {
ImeTracker.forLogging().onFailed(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED);
ImeTracker.forLatency().onHideFailed(statsToken,
ImeTracker.PHASE_CLIENT_VIEW_SERVED, ActivityThread::currentApplication);
- return false;
+ // with the flag enabled and targeting Android Baklava onwards, the return value
+ // should be always true (was false before).
+ return Flags.refactorInsetsController() && CompatChanges.isChangeEnabled(
+ ALWAYS_RETURN_TRUE_HIDE_SOFT_INPUT_FROM_WINDOW);
}
ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED);
@@ -2618,15 +2647,18 @@ public final class InputMethodManager {
// under us. The current input has been closed before (from checkFocus).
ImeTracker.forLogging().onFailed(statsToken,
ImeTracker.PHASE_CLIENT_VIEW_HANDLER_AVAILABLE);
- return false;
+ // with the flag enabled and targeting Android Baklava onwards, the
+ // return value should be always true (was false before).
+ return Flags.refactorInsetsController() && CompatChanges.isChangeEnabled(
+ ALWAYS_RETURN_TRUE_HIDE_SOFT_INPUT_FROM_WINDOW);
}
ImeTracker.forLogging().onProgress(statsToken,
ImeTracker.PHASE_CLIENT_VIEW_HANDLER_AVAILABLE);
+ final boolean imeReqVisible =
+ (viewRootImpl.getInsetsController().getRequestedVisibleTypes()
+ & WindowInsets.Type.ime()) != 0;
if (resultReceiver != null) {
- final boolean imeReqVisible =
- (viewRootImpl.getInsetsController().getRequestedVisibleTypes()
- & WindowInsets.Type.ime()) != 0;
resultReceiver.send(
!imeReqVisible ? InputMethodManager.RESULT_UNCHANGED_HIDDEN
: InputMethodManager.RESULT_HIDDEN, null);
@@ -2642,8 +2674,16 @@ public final class InputMethodManager {
viewRootImpl.getInsetsController().hide(WindowInsets.Type.ime(),
false /* fromIme */, statsToken);
}
+ if (!CompatChanges.isChangeEnabled(
+ ALWAYS_RETURN_TRUE_HIDE_SOFT_INPUT_FROM_WINDOW)) {
+ // if the IME was not visible before, the additional hide won't change
+ // anything.
+ return imeReqVisible;
+ }
}
- return true;
+ // Targeting Android Baklava onwards, this method will always return true.
+ return CompatChanges.isChangeEnabled(
+ ALWAYS_RETURN_TRUE_HIDE_SOFT_INPUT_FROM_WINDOW);
} else {
return IInputMethodManagerGlobalInvoker.hideSoftInput(mClient, windowToken,
statsToken, flags, resultReceiver, reason, mAsyncShowHideMethodEnabled);
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index 0f5476f58f74..0a5c14e3a08b 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -8565,12 +8565,16 @@ public class RemoteViews implements Parcelable, Filter {
return context;
}
try {
- // 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));
+ ApplicationInfo sanitizedApplication = mApplication;
+ try {
+ // Use PackageManager as the source of truth for application information, rather
+ // than the parceled ApplicationInfo provided by the app.
+ sanitizedApplication = context.getPackageManager().getApplicationInfoAsUser(
+ mApplication.packageName, 0, UserHandle.getUserId(mApplication.uid));
+ } catch(SecurityException se) {
+ Log.d(LOG_TAG, "Unable to fetch appInfo for " + mApplication.packageName);
+ }
+
Context applicationContext = context.createApplicationContext(
sanitizedApplication,
Context.CONTEXT_RESTRICTED);
diff --git a/core/java/android/window/DesktopModeFlags.java b/core/java/android/window/DesktopModeFlags.java
index d43469fa76ca..b44620f12bbb 100644
--- a/core/java/android/window/DesktopModeFlags.java
+++ b/core/java/android/window/DesktopModeFlags.java
@@ -96,10 +96,12 @@ public enum DesktopModeFlags {
ENABLE_DESKTOP_WINDOWING_TASK_LIMIT(Flags::enableDesktopWindowingTaskLimit, true),
ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY(Flags::enableDesktopWindowingWallpaperActivity,
true),
+ ENABLE_DRAG_RESIZE_SET_UP_IN_BG_THREAD(Flags::enableDragResizeSetUpInBgThread, false),
ENABLE_FULLY_IMMERSIVE_IN_DESKTOP(Flags::enableFullyImmersiveInDesktop, true),
ENABLE_HANDLE_INPUT_FIX(Flags::enableHandleInputFix, true),
ENABLE_HOLD_TO_DRAG_APP_HANDLE(Flags::enableHoldToDragAppHandle, true),
ENABLE_MINIMIZE_BUTTON(Flags::enableMinimizeButton, true),
+ ENABLE_MODALS_FULLSCREEN_WITH_PERMISSIONS(Flags::enableModalsFullscreenWithPermission, false),
ENABLE_RESIZING_METRICS(Flags::enableResizingMetrics, true),
ENABLE_RESTORE_TO_PREVIOUS_SIZE_FROM_DESKTOP_IMMERSIVE(
Flags::enableRestoreToPreviousSizeFromDesktopImmersive, true),
@@ -114,6 +116,7 @@ public enum DesktopModeFlags {
ENABLE_WINDOWING_SCALED_RESIZING(Flags::enableWindowingScaledResizing, true),
ENABLE_WINDOWING_TRANSITION_HANDLERS_OBSERVERS(
Flags::enableWindowingTransitionHandlersObservers, false),
+ EXCLUDE_CAPTION_FROM_APP_BOUNDS(Flags::excludeCaptionFromAppBounds, false),
INCLUDE_TOP_TRANSPARENT_FULLSCREEN_TASK_IN_DESKTOP_HEURISTIC(
Flags::includeTopTransparentFullscreenTaskInDesktopHeuristic, true)
// go/keep-sorted end
diff --git a/core/java/android/window/SystemUiContext.java b/core/java/android/window/SystemUiContext.java
new file mode 100644
index 000000000000..1e9a7203c09f
--- /dev/null
+++ b/core/java/android/window/SystemUiContext.java
@@ -0,0 +1,69 @@
+/*
+ * 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.window;
+
+import android.annotation.NonNull;
+import android.content.ComponentCallbacks;
+import android.content.ComponentCallbacksController;
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.res.Configuration;
+
+import com.android.window.flags.Flags;
+
+/**
+ * System Context to be used for UI. This Context has resources that can be themed.
+ *
+ * @see android.app.ActivityThread#getSystemUiContext(int)
+ *
+ * @hide
+ */
+public class SystemUiContext extends ContextWrapper implements ConfigurationDispatcher {
+
+ private final ComponentCallbacksController mCallbacksController =
+ new ComponentCallbacksController();
+
+ public SystemUiContext(Context base) {
+ super(base);
+ if (!Flags.trackSystemUiContextBeforeWms()) {
+ throw new UnsupportedOperationException("SystemUiContext can only be used after"
+ + " flag is enabled.");
+ }
+ }
+
+ @Override
+ public void registerComponentCallbacks(@NonNull ComponentCallbacks callback) {
+ mCallbacksController.registerCallbacks(callback);
+ }
+
+ @Override
+ public void unregisterComponentCallbacks(@NonNull ComponentCallbacks callback) {
+ mCallbacksController.unregisterCallbacks(callback);
+ }
+
+ /** Dispatch {@link Configuration} to each {@link ComponentCallbacks}. */
+ @Override
+ public void dispatchConfigurationChanged(@NonNull Configuration newConfig) {
+ mCallbacksController.dispatchConfigurationChanged(newConfig);
+ }
+
+ @Override
+ public boolean shouldReportPrivateChanges() {
+ // We should report all config changes to update fields obtained from resources.
+ return true;
+ }
+}
diff --git a/core/java/android/window/TransitionInfo.java b/core/java/android/window/TransitionInfo.java
index cf21e50e0a19..4f34aa36a204 100644
--- a/core/java/android/window/TransitionInfo.java
+++ b/core/java/android/window/TransitionInfo.java
@@ -29,7 +29,6 @@ import static android.view.Display.INVALID_DISPLAY;
import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_UNSPECIFIED;
import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_CLOSE;
-import static android.view.WindowManager.TRANSIT_FLAG_AOD_APPEARING;
import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_APPEARING;
import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY;
import static android.view.WindowManager.TRANSIT_NONE;
@@ -406,8 +405,7 @@ public final class TransitionInfo implements Parcelable {
*/
public boolean hasChangesOrSideEffects() {
return !mChanges.isEmpty() || isKeyguardGoingAway()
- || (mFlags & TRANSIT_FLAG_KEYGUARD_APPEARING) != 0
- || (mFlags & TRANSIT_FLAG_AOD_APPEARING) != 0;
+ || (mFlags & TRANSIT_FLAG_KEYGUARD_APPEARING) != 0;
}
/**
diff --git a/core/java/android/window/flags/lse_desktop_experience.aconfig b/core/java/android/window/flags/lse_desktop_experience.aconfig
index 3266ad4d93ae..891c5e382a58 100644
--- a/core/java/android/window/flags/lse_desktop_experience.aconfig
+++ b/core/java/android/window/flags/lse_desktop_experience.aconfig
@@ -16,6 +16,17 @@ flag {
}
flag {
+ name: "enable_modals_fullscreen_with_permission"
+ namespace: "lse_desktop_experience"
+ description: "Uses permissions to understand if modal fullscreen is allowed for /n"
+ "transparent activities."
+ bug: "394714626"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "include_top_transparent_fullscreen_task_in_desktop_heuristic"
namespace: "lse_desktop_experience"
description: "Whether to include any top transparent fullscreen task launched in desktop /n"
@@ -79,6 +90,16 @@ flag {
}
flag {
+ name: "enable_drag_resize_set_up_in_bg_thread"
+ namespace: "lse_desktop_experience"
+ description: "Enables setting up the drag-resize input listener in a bg thread"
+ bug: "396445663"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "enable_desktop_windowing_wallpaper_activity"
namespace: "lse_desktop_experience"
description: "Enables desktop wallpaper activity to show wallpaper in the desktop mode"
@@ -165,6 +186,16 @@ flag {
}
flag {
+ name: "enable_camera_compat_track_task_and_app_bugfix"
+ namespace: "lse_desktop_experience"
+ description: "Whether to use taskId and app process to track camera apps, and notify the policies only on first camera open and final close"
+ bug: "380840084"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "enable_task_stack_observer_in_shell"
namespace: "lse_desktop_experience"
description: "Introduces a new observer in shell to track the task stack."
diff --git a/core/java/com/android/internal/app/MediaRouteControllerContentManager.java b/core/java/com/android/internal/app/MediaRouteControllerContentManager.java
new file mode 100644
index 000000000000..83ae7edd796b
--- /dev/null
+++ b/core/java/com/android/internal/app/MediaRouteControllerContentManager.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.app;
+
+import android.content.Context;
+import android.media.MediaRouter;
+import android.view.View;
+import android.widget.LinearLayout;
+import android.widget.SeekBar;
+
+import com.android.internal.R;
+
+/**
+ * This class manages the content display within the media route controller UI.
+ */
+public class MediaRouteControllerContentManager {
+ // Time to wait before updating the volume when the user lets go of the seek bar
+ // to allow the route provider time to propagate the change and publish a new
+ // route descriptor.
+ private static final int VOLUME_UPDATE_DELAY_MILLIS = 250;
+
+ private final MediaRouter.RouteInfo mRoute;
+
+ private LinearLayout mVolumeLayout;
+ private SeekBar mVolumeSlider;
+ private boolean mVolumeSliderTouched;
+
+ public MediaRouteControllerContentManager(Context context) {
+ MediaRouter mRouter = context.getSystemService(MediaRouter.class);
+ mRoute = mRouter.getSelectedRoute();
+ }
+
+ /**
+ * Starts binding all the views (volume layout, slider, etc.) using the
+ * given container view.
+ */
+ public void bindViews(View containerView) {
+ mVolumeLayout = containerView.findViewById(R.id.media_route_volume_layout);
+ mVolumeSlider = containerView.findViewById(R.id.media_route_volume_slider);
+ mVolumeSlider.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
+ private final Runnable mStopTrackingTouch = new Runnable() {
+ @Override
+ public void run() {
+ if (mVolumeSliderTouched) {
+ mVolumeSliderTouched = false;
+ updateVolume();
+ }
+ }
+ };
+
+ @Override
+ public void onStartTrackingTouch(SeekBar seekBar) {
+ if (mVolumeSliderTouched) {
+ mVolumeSlider.removeCallbacks(mStopTrackingTouch);
+ } else {
+ mVolumeSliderTouched = true;
+ }
+ }
+
+ @Override
+ public void onStopTrackingTouch(SeekBar seekBar) {
+ // Defer resetting mVolumeSliderTouched to allow the media route provider
+ // a little time to settle into its new state and publish the final
+ // volume update.
+ mVolumeSlider.postDelayed(mStopTrackingTouch, VOLUME_UPDATE_DELAY_MILLIS);
+ }
+
+ @Override
+ public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
+ if (fromUser) {
+ mRoute.requestSetVolume(progress);
+ }
+ }
+ });
+ }
+
+ /**
+ * Updates the volume layout and slider.
+ */
+ public void updateVolume() {
+ if (!mVolumeSliderTouched) {
+ if (isVolumeControlAvailable()) {
+ mVolumeLayout.setVisibility(View.VISIBLE);
+ mVolumeSlider.setMax(mRoute.getVolumeMax());
+ mVolumeSlider.setProgress(mRoute.getVolume());
+ } else {
+ mVolumeLayout.setVisibility(View.GONE);
+ }
+ }
+ }
+
+ private boolean isVolumeControlAvailable() {
+ return mRoute.getVolumeHandling() == MediaRouter.RouteInfo.PLAYBACK_VOLUME_VARIABLE;
+ }
+}
diff --git a/core/java/com/android/internal/app/MediaRouteControllerDialog.java b/core/java/com/android/internal/app/MediaRouteControllerDialog.java
index 61e63d1b30de..c79f3c7bf76f 100644
--- a/core/java/com/android/internal/app/MediaRouteControllerDialog.java
+++ b/core/java/com/android/internal/app/MediaRouteControllerDialog.java
@@ -16,13 +16,10 @@
package com.android.internal.app;
-import com.android.internal.R;
-
import android.app.AlertDialog;
import android.app.MediaRouteActionProvider;
import android.app.MediaRouteButton;
import android.content.Context;
-import android.content.DialogInterface;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.drawable.AnimationDrawable;
@@ -35,9 +32,8 @@ import android.os.Bundle;
import android.util.TypedValue;
import android.view.KeyEvent;
import android.view.View;
-import android.widget.FrameLayout;
-import android.widget.LinearLayout;
-import android.widget.SeekBar;
+
+import com.android.internal.R;
/**
* This class implements the route controller dialog for {@link MediaRouter}.
@@ -51,166 +47,56 @@ import android.widget.SeekBar;
* TODO: Move this back into the API, as in the support library media router.
*/
public class MediaRouteControllerDialog extends AlertDialog {
- // Time to wait before updating the volume when the user lets go of the seek bar
- // to allow the route provider time to propagate the change and publish a new
- // route descriptor.
- private static final int VOLUME_UPDATE_DELAY_MILLIS = 250;
-
private final MediaRouter mRouter;
private final MediaRouterCallback mCallback;
private final MediaRouter.RouteInfo mRoute;
- private boolean mCreated;
private Drawable mMediaRouteButtonDrawable;
private int[] mMediaRouteConnectingState = { R.attr.state_checked, R.attr.state_enabled };
private int[] mMediaRouteOnState = { R.attr.state_activated, R.attr.state_enabled };
private Drawable mCurrentIconDrawable;
- private boolean mVolumeControlEnabled = true;
- private LinearLayout mVolumeLayout;
- private SeekBar mVolumeSlider;
- private boolean mVolumeSliderTouched;
-
- private View mControlView;
private boolean mAttachedToWindow;
+ private final MediaRouteControllerContentManager mContentManager;
+
public MediaRouteControllerDialog(Context context, int theme) {
super(context, theme);
+ mContentManager = new MediaRouteControllerContentManager(context);
mRouter = (MediaRouter) context.getSystemService(Context.MEDIA_ROUTER_SERVICE);
mCallback = new MediaRouterCallback();
mRoute = mRouter.getSelectedRoute();
}
- /**
- * Gets the route that this dialog is controlling.
- */
- public MediaRouter.RouteInfo getRoute() {
- return mRoute;
- }
-
- /**
- * Provides the subclass an opportunity to create a view that will
- * be included within the body of the dialog to offer additional media controls
- * for the currently playing content.
- *
- * @param savedInstanceState The dialog's saved instance state.
- * @return The media control view, or null if none.
- */
- public View onCreateMediaControlView(Bundle savedInstanceState) {
- return null;
- }
-
- /**
- * Gets the media control view that was created by {@link #onCreateMediaControlView(Bundle)}.
- *
- * @return The media control view, or null if none.
- */
- public View getMediaControlView() {
- return mControlView;
- }
-
- /**
- * Sets whether to enable the volume slider and volume control using the volume keys
- * when the route supports it.
- * <p>
- * The default value is true.
- * </p>
- */
- public void setVolumeControlEnabled(boolean enable) {
- if (mVolumeControlEnabled != enable) {
- mVolumeControlEnabled = enable;
- if (mCreated) {
- updateVolume();
- }
- }
- }
-
- /**
- * Returns whether to enable the volume slider and volume control using the volume keys
- * when the route supports it.
- */
- public boolean isVolumeControlEnabled() {
- return mVolumeControlEnabled;
- }
-
@Override
protected void onCreate(Bundle savedInstanceState) {
setTitle(mRoute.getName());
Resources res = getContext().getResources();
setButton(BUTTON_NEGATIVE, res.getString(R.string.media_route_controller_disconnect),
- new OnClickListener() {
- @Override
- public void onClick(DialogInterface dialogInterface, int id) {
- if (mRoute.isSelected()) {
- if (mRoute.isBluetooth()) {
- mRouter.getDefaultRoute().select();
- } else {
- mRouter.getFallbackRoute().select();
- }
+ (dialogInterface, id) -> {
+ if (mRoute.isSelected()) {
+ if (mRoute.isBluetooth()) {
+ mRouter.getDefaultRoute().select();
+ } else {
+ mRouter.getFallbackRoute().select();
}
- dismiss();
}
+ dismiss();
});
View customView = getLayoutInflater().inflate(R.layout.media_route_controller_dialog, null);
setView(customView, 0, 0, 0, 0);
super.onCreate(savedInstanceState);
+ mContentManager.bindViews(customView);
+
View customPanelView = getWindow().findViewById(R.id.customPanel);
if (customPanelView != null) {
customPanelView.setMinimumHeight(0);
}
- mVolumeLayout = (LinearLayout) customView.findViewById(R.id.media_route_volume_layout);
- mVolumeSlider = (SeekBar) customView.findViewById(R.id.media_route_volume_slider);
- mVolumeSlider.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
- private final Runnable mStopTrackingTouch = new Runnable() {
- @Override
- public void run() {
- if (mVolumeSliderTouched) {
- mVolumeSliderTouched = false;
- updateVolume();
- }
- }
- };
-
- @Override
- public void onStartTrackingTouch(SeekBar seekBar) {
- if (mVolumeSliderTouched) {
- mVolumeSlider.removeCallbacks(mStopTrackingTouch);
- } else {
- mVolumeSliderTouched = true;
- }
- }
-
- @Override
- public void onStopTrackingTouch(SeekBar seekBar) {
- // Defer resetting mVolumeSliderTouched to allow the media route provider
- // a little time to settle into its new state and publish the final
- // volume update.
- mVolumeSlider.postDelayed(mStopTrackingTouch, VOLUME_UPDATE_DELAY_MILLIS);
- }
-
- @Override
- public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
- if (fromUser) {
- mRoute.requestSetVolume(progress);
- }
- }
- });
mMediaRouteButtonDrawable = obtainMediaRouteButtonDrawable();
- mCreated = true;
- if (update()) {
- mControlView = onCreateMediaControlView(savedInstanceState);
- FrameLayout controlFrame =
- (FrameLayout) customView.findViewById(R.id.media_route_control_frame);
- if (mControlView != null) {
- controlFrame.addView(mControlView);
- controlFrame.setVisibility(View.VISIBLE);
- } else {
- controlFrame.setVisibility(View.GONE);
- }
- }
+ update();
}
@Override
@@ -249,20 +135,18 @@ public class MediaRouteControllerDialog extends AlertDialog {
return super.onKeyUp(keyCode, event);
}
- private boolean update() {
+ private void update() {
if (!mRoute.isSelected() || mRoute.isDefault()) {
dismiss();
- return false;
}
setTitle(mRoute.getName());
- updateVolume();
+ mContentManager.updateVolume();
Drawable icon = getIconDrawable();
if (icon != mCurrentIconDrawable) {
mCurrentIconDrawable = icon;
- if (icon instanceof AnimationDrawable) {
- AnimationDrawable animDrawable = (AnimationDrawable) icon;
+ if (icon instanceof AnimationDrawable animDrawable) {
if (!mAttachedToWindow && !mRoute.isConnecting()) {
// When the route is already connected before the view is attached, show the
// last frame of the connected animation immediately.
@@ -276,7 +160,6 @@ public class MediaRouteControllerDialog extends AlertDialog {
}
setIcon(icon);
}
- return true;
}
private Drawable obtainMediaRouteButtonDrawable() {
@@ -306,23 +189,6 @@ public class MediaRouteControllerDialog extends AlertDialog {
}
}
- private void updateVolume() {
- if (!mVolumeSliderTouched) {
- if (isVolumeControlAvailable()) {
- mVolumeLayout.setVisibility(View.VISIBLE);
- mVolumeSlider.setMax(mRoute.getVolumeMax());
- mVolumeSlider.setProgress(mRoute.getVolume());
- } else {
- mVolumeLayout.setVisibility(View.GONE);
- }
- }
- }
-
- private boolean isVolumeControlAvailable() {
- return mVolumeControlEnabled && mRoute.getVolumeHandling() ==
- MediaRouter.RouteInfo.PLAYBACK_VOLUME_VARIABLE;
- }
-
private final class MediaRouterCallback extends MediaRouter.SimpleCallback {
@Override
public void onRouteUnselected(MediaRouter router, int type, RouteInfo info) {
@@ -337,7 +203,7 @@ public class MediaRouteControllerDialog extends AlertDialog {
@Override
public void onRouteVolumeChanged(MediaRouter router, MediaRouter.RouteInfo route) {
if (route == mRoute) {
- updateVolume();
+ mContentManager.updateVolume();
}
}
diff --git a/core/java/com/android/internal/app/OWNERS b/core/java/com/android/internal/app/OWNERS
index 52f18fb80c27..0bba24d4ce19 100644
--- a/core/java/com/android/internal/app/OWNERS
+++ b/core/java/com/android/internal/app/OWNERS
@@ -20,3 +20,6 @@ per-file *VisualQuery* = file:/core/java/android/service/voice/OWNERS
# System language settings
per-file *Locale* = file:platform/packages/apps/Settings:/src/com/android/settings/localepicker/OWNERS
+
+# Media
+per-file *MediaRoute* = file:/packages/SystemUI/src/com/android/systemui/media/dialog/OWNERS
diff --git a/core/java/com/android/internal/security/VerityUtils.java b/core/java/com/android/internal/security/VerityUtils.java
index 37500766a4ac..ac186d0a26b5 100644
--- a/core/java/com/android/internal/security/VerityUtils.java
+++ b/core/java/com/android/internal/security/VerityUtils.java
@@ -56,8 +56,7 @@ public abstract class VerityUtils {
private static final int HASH_SIZE_BYTES = 32;
public static boolean isFsVeritySupported() {
- return Build.VERSION.DEVICE_INITIAL_SDK_INT >= Build.VERSION_CODES.R
- || SystemProperties.getInt("ro.apk_verity.mode", 0) == 2;
+ return Build.VERSION.DEVICE_INITIAL_SDK_INT >= Build.VERSION_CODES.R;
}
/** Enables fs-verity for the file without signature. */
diff --git a/core/java/com/android/internal/widget/LockPatternView.java b/core/java/com/android/internal/widget/LockPatternView.java
index 0ec55f958f38..1f907602cb9b 100644
--- a/core/java/com/android/internal/widget/LockPatternView.java
+++ b/core/java/com/android/internal/widget/LockPatternView.java
@@ -87,10 +87,10 @@ public class LockPatternView extends View {
private static final int CELL_ACTIVATE = 0;
private static final int CELL_DEACTIVATE = 1;
- private final int mDotSize;
- private final int mDotSizeActivated;
+ private int mDotSize;
+ private int mDotSizeActivated;
private final float mDotHitFactor;
- private final int mPathWidth;
+ private int mPathWidth;
private final int mLineFadeOutAnimationDurationMs;
private final int mLineFadeOutAnimationDelayMs;
private final int mFadePatternAnimationDurationMs;
@@ -1341,6 +1341,38 @@ public class LockPatternView extends View {
invalidate();
}
+ /**
+ * Change dot colors
+ */
+ public void setDotColors(int dotColor, int dotActivatedColor) {
+ mDotColor = dotColor;
+ mDotActivatedColor = dotActivatedColor;
+ invalidate();
+ }
+
+ /**
+ * Keeps dot activated until the next dot gets activated.
+ */
+ public void setKeepDotActivated(boolean keepDotActivated) {
+ mKeepDotActivated = keepDotActivated;
+ }
+
+ /**
+ * Set dot sizes in dp
+ */
+ public void setDotSizes(int dotSizeDp, int dotSizeActivatedDp) {
+ mDotSize = dotSizeDp;
+ mDotSizeActivated = dotSizeActivatedDp;
+ }
+
+ /**
+ * Set the stroke width of the pattern line.
+ */
+ public void setPathWidth(int pathWidthDp) {
+ mPathWidth = pathWidthDp;
+ mPathPaint.setStrokeWidth(mPathWidth);
+ }
+
private float getCenterXForColumn(int column) {
return mPaddingLeft + column * mSquareWidth + mSquareWidth / 2f;
}
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 ca355c41f7a9..b8503da2c09b 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/CoreDocument.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/CoreDocument.java
@@ -65,7 +65,7 @@ public class CoreDocument implements Serializable {
// We also keep a more fine-grained BUILD number, exposed as
// ID_API_LEVEL = DOCUMENT_API_LEVEL + BUILD
- static final float BUILD = 0.1f;
+ static final float BUILD = 0.2f;
@NonNull ArrayList<Operation> mOperations = new ArrayList<>();
@@ -742,6 +742,7 @@ public class CoreDocument implements Serializable {
if (op instanceof Component) {
mComponentMap.put(((Component) op).getComponentId(), (Component) op);
registerVariables(context, ((Component) op).getList());
+ ((Component) op).registerVariables(context);
}
if (op instanceof ComponentValue) {
ComponentValue v = (ComponentValue) op;
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 9bb8d9f39975..09ec40271f4d 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/Operations.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/Operations.java
@@ -35,6 +35,7 @@ import com.android.internal.widget.remotecompose.core.operations.DrawBitmapFontT
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;
+import com.android.internal.widget.remotecompose.core.operations.DrawContent;
import com.android.internal.widget.remotecompose.core.operations.DrawLine;
import com.android.internal.widget.remotecompose.core.operations.DrawOval;
import com.android.internal.widget.remotecompose.core.operations.DrawPath;
@@ -81,6 +82,7 @@ import com.android.internal.widget.remotecompose.core.operations.Theme;
import com.android.internal.widget.remotecompose.core.operations.TimeAttribute;
import com.android.internal.widget.remotecompose.core.operations.TouchExpression;
import com.android.internal.widget.remotecompose.core.operations.layout.CanvasContent;
+import com.android.internal.widget.remotecompose.core.operations.layout.CanvasOperations;
import com.android.internal.widget.remotecompose.core.operations.layout.ClickModifierOperation;
import com.android.internal.widget.remotecompose.core.operations.layout.ComponentStart;
import com.android.internal.widget.remotecompose.core.operations.layout.ContainerEnd;
@@ -105,6 +107,7 @@ import com.android.internal.widget.remotecompose.core.operations.layout.modifier
import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.BorderModifierOperation;
import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ClipRectModifierOperation;
import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ComponentVisibilityOperation;
+import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.DrawContentOperation;
import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.GraphicsLayerModifierOperation;
import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.HeightInModifierOperation;
import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.HeightModifierOperation;
@@ -172,6 +175,7 @@ public class Operations {
public static final int DATA_PATH = 123;
public static final int DRAW_PATH = 124;
public static final int DRAW_TWEEN_PATH = 125;
+ public static final int DRAW_CONTENT = 139;
public static final int MATRIX_SCALE = 126;
public static final int MATRIX_TRANSLATE = 127;
public static final int MATRIX_SKEW = 128;
@@ -215,6 +219,8 @@ public class Operations {
public static final int ATTRIBUTE_TEXT = 170;
public static final int ATTRIBUTE_IMAGE = 171;
public static final int ATTRIBUTE_TIME = 172;
+ public static final int CANVAS_OPERATIONS = 173;
+ public static final int MODIFIER_DRAW_CONTENT = 174;
///////////////////////////////////////// ======================
@@ -366,6 +372,7 @@ public class Operations {
map.put(MODIFIER_SCROLL, ScrollModifierOperation::read);
map.put(MODIFIER_MARQUEE, MarqueeModifierOperation::read);
map.put(MODIFIER_RIPPLE, RippleModifierOperation::read);
+ map.put(MODIFIER_DRAW_CONTENT, DrawContentOperation::read);
map.put(CONTAINER_END, ContainerEnd::read);
@@ -393,6 +400,7 @@ public class Operations {
map.put(LAYOUT_TEXT, TextLayout::read);
map.put(LAYOUT_STATE, StateLayout::read);
+ map.put(DRAW_CONTENT, DrawContent::read);
map.put(COMPONENT_VALUE, ComponentValue::read);
map.put(DRAW_ARC, DrawArc::read);
@@ -409,6 +417,7 @@ public class Operations {
map.put(PARTICLE_LOOP, ParticlesLoop::read);
map.put(FUNCTION_CALL, FloatFunctionCall::read);
map.put(FUNCTION_DEFINE, FloatFunctionDefine::read);
+ map.put(CANVAS_OPERATIONS, CanvasOperations::read);
map.put(ACCESSIBILITY_SEMANTICS, CoreSemantics::read);
map.put(ATTRIBUTE_IMAGE, ImageAttribute::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 39f85f600310..e75bd30b381d 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeBuffer.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeBuffer.java
@@ -38,6 +38,7 @@ import com.android.internal.widget.remotecompose.core.operations.DrawBitmapFontT
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;
+import com.android.internal.widget.remotecompose.core.operations.DrawContent;
import com.android.internal.widget.remotecompose.core.operations.DrawLine;
import com.android.internal.widget.remotecompose.core.operations.DrawOval;
import com.android.internal.widget.remotecompose.core.operations.DrawPath;
@@ -84,6 +85,7 @@ import com.android.internal.widget.remotecompose.core.operations.TimeAttribute;
import com.android.internal.widget.remotecompose.core.operations.TouchExpression;
import com.android.internal.widget.remotecompose.core.operations.Utils;
import com.android.internal.widget.remotecompose.core.operations.layout.CanvasContent;
+import com.android.internal.widget.remotecompose.core.operations.layout.CanvasOperations;
import com.android.internal.widget.remotecompose.core.operations.layout.ComponentStart;
import com.android.internal.widget.remotecompose.core.operations.layout.ContainerEnd;
import com.android.internal.widget.remotecompose.core.operations.layout.ImpulseOperation;
@@ -1887,7 +1889,7 @@ public class RemoteComposeBuffer {
}
/** Add a component end tag */
- public void addComponentEnd() {
+ public void addContainerEnd() {
ContainerEnd.apply(mBuffer);
}
@@ -2231,6 +2233,11 @@ public class RemoteComposeBuffer {
LayoutComponentContent.apply(mBuffer, mLastComponentId);
}
+ /** Add a canvas operations start tag */
+ public void addCanvasOperationsStart() {
+ CanvasOperations.apply(mBuffer);
+ }
+
/**
* Add a component width value
*
@@ -2427,4 +2434,9 @@ public class RemoteComposeBuffer {
TimeAttribute.apply(mBuffer, id, timeId, attribute, args);
return Utils.asNan(id);
}
+
+ /** In the context of a component draw modifier, draw the content of the component */
+ public void drawComponentContent() {
+ DrawContent.apply(mBuffer);
+ }
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawContent.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawContent.java
new file mode 100644
index 000000000000..e2e22acbeb8f
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawContent.java
@@ -0,0 +1,119 @@
+/*
+ * 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 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.WireBuffer;
+import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder;
+import com.android.internal.widget.remotecompose.core.operations.layout.LayoutComponent;
+import com.android.internal.widget.remotecompose.core.serialize.MapSerializer;
+import com.android.internal.widget.remotecompose.core.serialize.Serializable;
+
+import java.util.List;
+
+/** The DrawContent command */
+public class DrawContent extends PaintOperation implements Serializable {
+ private static final int OP_CODE = Operations.DRAW_CONTENT;
+ private static final String CLASS_NAME = "DrawContent";
+ private @Nullable LayoutComponent mComponent;
+
+ @Override
+ public void write(@NonNull WireBuffer buffer) {
+ apply(buffer);
+ }
+
+ /**
+ * Set the component to be painted
+ *
+ * @param component
+ */
+ public void setComponent(LayoutComponent component) {
+ mComponent = component;
+ }
+
+ @NonNull
+ @Override
+ public String toString() {
+ return "DrawContent;";
+ }
+
+ /**
+ * 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) {
+ DrawContent op = new DrawContent();
+ 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;
+ }
+
+ /**
+ * add a draw content operation to the buffer
+ *
+ * @param buffer the buffer to add to
+ */
+ public static void apply(@NonNull WireBuffer buffer) {
+ buffer.start(Operations.DRAW_CONTENT);
+ }
+
+ /**
+ * 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, CLASS_NAME)
+ .description("Draw the component content");
+ }
+
+ @Override
+ public void paint(@NonNull PaintContext context) {
+ if (mComponent != null) {
+ mComponent.drawContent(context);
+ }
+ }
+
+ @Override
+ public void serialize(MapSerializer serializer) {
+ serializer.add("type", CLASS_NAME);
+ }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/CanvasOperations.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/CanvasOperations.java
new file mode 100644
index 000000000000..3e7f1d304315
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/CanvasOperations.java
@@ -0,0 +1,175 @@
+/*
+ * 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;
+
+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.operations.ComponentValue;
+import com.android.internal.widget.remotecompose.core.operations.DrawContent;
+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.List;
+
+/** Represents a list of canvas operations. */
+public class CanvasOperations extends PaintOperation
+ implements VariableSupport, Container, Serializable {
+ private static final int OP_CODE = Operations.CANVAS_OPERATIONS;
+ private static final String CLASS_NAME = "CanvasOperations";
+
+ @NonNull public ArrayList<Operation> mList = new ArrayList<>();
+ @Nullable LayoutComponent mComponent;
+
+ /** The constructor */
+ public CanvasOperations() {}
+
+ @Override
+ public void registerListening(RemoteContext context) {
+ for (Operation operation : mList) {
+ if (operation instanceof VariableSupport) {
+ VariableSupport variableSupport = (VariableSupport) operation;
+ variableSupport.registerListening(context);
+ }
+ if (operation instanceof ComponentValue) {
+ ComponentValue v = (ComponentValue) operation;
+ mComponent.addComponentValue(v);
+ }
+ }
+ }
+
+ @Override
+ public void updateVariables(RemoteContext context) {
+ for (Operation operation : mList) {
+ if (operation instanceof VariableSupport) {
+ VariableSupport variableSupport = (VariableSupport) operation;
+ variableSupport.updateVariables(context);
+ }
+ }
+ }
+
+ /**
+ * The returns a list to be filled
+ *
+ * @return list to be filled
+ */
+ @NonNull
+ public ArrayList<Operation> getList() {
+ return mList;
+ }
+
+ @Override
+ public void write(@NonNull WireBuffer buffer) {
+ apply(buffer);
+ }
+
+ @NonNull
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder(CLASS_NAME + "\n");
+ for (Operation operation : mList) {
+ builder.append(" ");
+ builder.append(operation);
+ builder.append("\n");
+ }
+ return builder.toString();
+ }
+
+ @NonNull
+ @Override
+ public String deepToString(@NonNull String indent) {
+ return (indent != null ? indent : "") + toString();
+ }
+
+ @Override
+ public void paint(@NonNull PaintContext context) {
+ RemoteContext remoteContext = context.getContext();
+ for (Operation op : mList) {
+ if (op instanceof VariableSupport && op.isDirty()) {
+ ((VariableSupport) op).updateVariables(context.getContext());
+ }
+ remoteContext.incrementOpCount();
+ op.apply(context.getContext());
+ }
+ }
+
+ /**
+ * The name of the class
+ *
+ * @return the name
+ */
+ @NonNull
+ public static String name() {
+ return "Loop";
+ }
+
+ /**
+ * Apply this operation to the buffer
+ *
+ * @param buffer
+ */
+ 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 CanvasOperations());
+ }
+
+ /**
+ * 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("Operations", OP_CODE, name())
+ .description("Impulse Process that runs a list of operations");
+ }
+
+ @Override
+ public void serialize(MapSerializer serializer) {
+ serializer.add("type", CLASS_NAME).add("list", mList);
+ }
+
+ /**
+ * Set layout component
+ *
+ * @param layoutComponent
+ */
+ public void setComponent(LayoutComponent layoutComponent) {
+ mComponent = layoutComponent;
+ for (Operation op : mList) {
+ if (op instanceof DrawContent) {
+ ((DrawContent) op).setComponent(layoutComponent);
+ }
+ }
+ }
+}
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 e332e4be4c8d..c73643682b55 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
@@ -329,6 +329,15 @@ public class Component extends PaintOperation
mAnimationSpec = animationSpec;
}
+ /**
+ * If the component contains variables beside mList, make sure to register them here
+ *
+ * @param context
+ */
+ public void registerVariables(RemoteContext context) {
+ // Nothing here
+ }
+
public enum Visibility {
GONE,
VISIBLE,
@@ -976,6 +985,17 @@ public class Component extends PaintOperation
}
}
+ /** Extract CanvasOperations if present */
+ public @Nullable CanvasOperations getCanvasOperations(LayoutComponent layoutComponent) {
+ for (Operation op : mList) {
+ if (op instanceof CanvasOperations) {
+ ((CanvasOperations) op).setComponent(layoutComponent);
+ return (CanvasOperations) op;
+ }
+ }
+ return null;
+ }
+
/**
* Extract child TextData elements
*
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 10cbd4ca2a50..7e2a4ccec222 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
@@ -75,6 +75,7 @@ public class LayoutComponent extends Component {
protected ArrayList<Component> mChildrenComponents = new ArrayList<>(); // members are not null
protected boolean mChildrenHaveZIndex = false;
+ private CanvasOperations mDrawContentOperations;
public LayoutComponent(
@Nullable Component parent,
@@ -138,6 +139,7 @@ public class LayoutComponent extends Component {
mChildrenComponents.clear();
LayoutComponentContent content = (LayoutComponentContent) op;
content.getComponents(mChildrenComponents);
+ mDrawContentOperations = content.getCanvasOperations(this);
if (USE_IMAGE_TEMP_FIX) {
if (mChildrenComponents.isEmpty() && !mContent.mList.isEmpty()) {
CanvasContent canvasContent =
@@ -315,6 +317,31 @@ public class LayoutComponent extends Component {
}
@Override
+ public void paint(@NonNull PaintContext context) {
+ if (mDrawContentOperations != null) {
+ context.save();
+ context.translate(mX, mY);
+ mDrawContentOperations.paint(context);
+ context.restore();
+ return;
+ }
+ super.paint(context);
+ }
+
+ /**
+ * Paint the component content. Used by the DrawContent operation. (back out mX/mY -- TODO:
+ * refactor paintingComponent instead, to not include mX/mY etc.)
+ *
+ * @param context painting context
+ */
+ public void drawContent(@NonNull PaintContext context) {
+ context.save();
+ context.translate(-mX, -mY);
+ paintingComponent(context);
+ context.restore();
+ }
+
+ @Override
public void paintingComponent(@NonNull PaintContext context) {
Component prev = context.getContext().mLastComponent;
RemoteContext remoteContext = context.getContext();
@@ -514,4 +541,11 @@ public class LayoutComponent extends Component {
return null;
}
+
+ @Override
+ public void registerVariables(RemoteContext context) {
+ if (mDrawContentOperations != null) {
+ mDrawContentOperations.registerListening(context);
+ }
+ }
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/DrawContentOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/DrawContentOperation.java
new file mode 100644
index 000000000000..d7abdbae4962
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/DrawContentOperation.java
@@ -0,0 +1,125 @@
+/*
+ * 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 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.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.operations.layout.Component;
+import com.android.internal.widget.remotecompose.core.operations.layout.DecoratorComponent;
+import com.android.internal.widget.remotecompose.core.operations.layout.LayoutComponent;
+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.SerializeTags;
+
+import java.util.List;
+
+/** Represent a drawing of a component */
+public class DrawContentOperation extends Operation
+ implements ModifierOperation, VariableSupport, DecoratorComponent {
+ private static final int OP_CODE = Operations.MODIFIER_DRAW_CONTENT;
+
+ private LayoutComponent mParent;
+
+ public DrawContentOperation() {}
+
+ @NonNull
+ @Override
+ public String toString() {
+ return "DrawContentOperation()";
+ }
+
+ /**
+ * Returns the serialized name for this operation
+ *
+ * @return the serialized name
+ */
+ @NonNull
+ public String serializedName() {
+ return "DRAW_CONTENT";
+ }
+
+ @Override
+ public void serializeToString(int indent, @NonNull StringSerializer serializer) {
+ serializer.append(indent, serializedName());
+ }
+
+ @Override
+ public void apply(@NonNull RemoteContext context) {}
+
+ @NonNull
+ @Override
+ public String deepToString(@NonNull String indent) {
+ return (indent != null ? indent : "") + toString();
+ }
+
+ @Override
+ public void write(@NonNull WireBuffer buffer) {}
+
+ /**
+ * 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 DrawContentOperation());
+ }
+
+ /**
+ * 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, "ComponentVisibility")
+ .description("This operation represents a draw of a component");
+ }
+
+ @Override
+ public void registerListening(@NonNull RemoteContext context) {}
+
+ @Override
+ public void updateVariables(@NonNull RemoteContext context) {}
+
+ public void setParent(@Nullable LayoutComponent parent) {
+ mParent = parent;
+ }
+
+ @Override
+ public void layout(
+ @NonNull RemoteContext context, Component component, float width, float height) {}
+
+ @Override
+ public void serialize(MapSerializer serializer) {
+ serializer.addTags(SerializeTags.MODIFIER).add("type", "DrawContentOperation");
+ }
+}
diff --git a/core/jni/android_content_res_ApkAssets.cpp b/core/jni/android_content_res_ApkAssets.cpp
index 1e7bfe32ba79..66c65d0ac1aa 100644
--- a/core/jni/android_content_res_ApkAssets.cpp
+++ b/core/jni/android_content_res_ApkAssets.cpp
@@ -111,9 +111,8 @@ static void DeleteGuardedApkAssets(Guarded<AssetManager2::ApkAssetsPtr>& apk_ass
class LoaderAssetsProvider : public AssetsProvider {
public:
static std::unique_ptr<AssetsProvider> Create(JNIEnv* env, jobject assets_provider) {
- return (!assets_provider) ? EmptyAssetsProvider::Create()
- : std::unique_ptr<AssetsProvider>(new LoaderAssetsProvider(
- env, assets_provider));
+ return std::unique_ptr<AssetsProvider>{
+ assets_provider ? new LoaderAssetsProvider(env, assets_provider) : nullptr};
}
bool ForEachFile(const std::string& /* root_path */,
@@ -129,8 +128,8 @@ class LoaderAssetsProvider : public AssetsProvider {
return debug_name_;
}
- bool IsUpToDate() const override {
- return true;
+ UpToDate IsUpToDate() const override {
+ return UpToDate::Always;
}
~LoaderAssetsProvider() override {
@@ -212,7 +211,7 @@ class LoaderAssetsProvider : public AssetsProvider {
auto string_result = static_cast<jstring>(env->CallObjectMethod(
assets_provider_, gAssetsProviderOffsets.toString));
ScopedUtfChars str(env, string_result);
- debug_name_ = std::string(str.c_str(), str.size());
+ debug_name_ = std::string(str.c_str());
}
// The global reference to the AssetsProvider
@@ -233,9 +232,9 @@ static jlong NativeLoad(JNIEnv* env, jclass /*clazz*/, const format_type_t forma
AssetManager2::ApkAssetsPtr apk_assets;
switch (format) {
case FORMAT_APK: {
- auto assets = MultiAssetsProvider::Create(std::move(loader_assets),
- ZipAssetsProvider::Create(path.c_str(),
- property_flags));
+ auto assets = AssetsProvider::CreateWithOverride(ZipAssetsProvider::Create(path.c_str(),
+ property_flags),
+ std::move(loader_assets));
apk_assets = ApkAssets::Load(std::move(assets), property_flags);
break;
}
@@ -243,15 +242,17 @@ static jlong NativeLoad(JNIEnv* env, jclass /*clazz*/, const format_type_t forma
apk_assets = ApkAssets::LoadOverlay(path.c_str(), property_flags);
break;
case FORMAT_ARSC:
- apk_assets = ApkAssets::LoadTable(AssetsProvider::CreateAssetFromFile(path.c_str()),
- std::move(loader_assets),
- property_flags);
- break;
+ apk_assets =
+ ApkAssets::LoadTable(AssetsProvider::CreateAssetFromFile(path.c_str()),
+ AssetsProvider::CreateFromNullable(std::move(loader_assets)),
+ property_flags);
+ break;
case FORMAT_DIRECTORY: {
- auto assets = MultiAssetsProvider::Create(std::move(loader_assets),
- DirectoryAssetsProvider::Create(path.c_str()));
- apk_assets = ApkAssets::Load(std::move(assets), property_flags);
- break;
+ auto assets =
+ AssetsProvider::CreateWithOverride(DirectoryAssetsProvider::Create(path.c_str()),
+ std::move(loader_assets));
+ apk_assets = ApkAssets::Load(std::move(assets), property_flags);
+ break;
}
default:
const std::string error_msg = base::StringPrintf("Unsupported format type %d", format);
@@ -308,18 +309,21 @@ static jlong NativeLoadFromFd(JNIEnv* env, jclass /*clazz*/, const format_type_t
switch (format) {
case FORMAT_APK: {
auto assets =
- MultiAssetsProvider::Create(std::move(loader_assets),
- ZipAssetsProvider::Create(std::move(dup_fd),
- friendly_name_utf8.c_str(),
- property_flags));
+ AssetsProvider::CreateWithOverride(ZipAssetsProvider::Create(std::move(dup_fd),
+ friendly_name_utf8
+ .c_str(),
+ property_flags),
+ std::move(loader_assets));
apk_assets = ApkAssets::Load(std::move(assets), property_flags);
break;
}
case FORMAT_ARSC:
- apk_assets = ApkAssets::LoadTable(
- AssetsProvider::CreateAssetFromFd(std::move(dup_fd), nullptr /* path */),
- std::move(loader_assets), property_flags);
- break;
+ apk_assets =
+ ApkAssets::LoadTable(AssetsProvider::CreateAssetFromFd(std::move(dup_fd),
+ nullptr /* path */),
+ AssetsProvider::CreateFromNullable(std::move(loader_assets)),
+ property_flags);
+ break;
default:
const std::string error_msg = base::StringPrintf("Unsupported format type %d", format);
jniThrowException(env, "java/lang/IllegalArgumentException", error_msg.c_str());
@@ -375,23 +379,28 @@ static jlong NativeLoadFromFdOffset(JNIEnv* env, jclass /*clazz*/, const format_
switch (format) {
case FORMAT_APK: {
auto assets =
- MultiAssetsProvider::Create(std::move(loader_assets),
- ZipAssetsProvider::Create(std::move(dup_fd),
- friendly_name_utf8.c_str(),
- property_flags,
- static_cast<off64_t>(offset),
- static_cast<off64_t>(
- length)));
+ AssetsProvider::CreateWithOverride(ZipAssetsProvider::Create(std::move(dup_fd),
+ friendly_name_utf8
+ .c_str(),
+ property_flags,
+ static_cast<off64_t>(
+ offset),
+ static_cast<off64_t>(
+ length)),
+ std::move(loader_assets));
apk_assets = ApkAssets::Load(std::move(assets), property_flags);
break;
}
case FORMAT_ARSC:
- apk_assets = ApkAssets::LoadTable(
- AssetsProvider::CreateAssetFromFd(std::move(dup_fd), nullptr /* path */,
- static_cast<off64_t>(offset),
- static_cast<off64_t>(length)),
- std::move(loader_assets), property_flags);
- break;
+ apk_assets =
+ ApkAssets::LoadTable(AssetsProvider::CreateAssetFromFd(std::move(dup_fd),
+ nullptr /* path */,
+ static_cast<off64_t>(offset),
+ static_cast<off64_t>(
+ length)),
+ AssetsProvider::CreateFromNullable(std::move(loader_assets)),
+ property_flags);
+ break;
default:
const std::string error_msg = base::StringPrintf("Unsupported format type %d", format);
jniThrowException(env, "java/lang/IllegalArgumentException", error_msg.c_str());
@@ -408,13 +417,16 @@ static jlong NativeLoadFromFdOffset(JNIEnv* env, jclass /*clazz*/, const format_
}
static jlong NativeLoadEmpty(JNIEnv* env, jclass /*clazz*/, jint flags, jobject assets_provider) {
- auto apk_assets = ApkAssets::Load(LoaderAssetsProvider::Create(env, assets_provider), flags);
- if (apk_assets == nullptr) {
- const std::string error_msg =
- base::StringPrintf("Failed to load empty assets with provider %p", (void*)assets_provider);
- jniThrowException(env, "java/io/IOException", error_msg.c_str());
- return 0;
- }
+ auto apk_assets = ApkAssets::Load(AssetsProvider::CreateFromNullable(
+ LoaderAssetsProvider::Create(env, assets_provider)),
+ flags);
+ if (apk_assets == nullptr) {
+ const std::string error_msg =
+ base::StringPrintf("Failed to load empty assets with provider %p",
+ (void*)assets_provider);
+ jniThrowException(env, "java/io/IOException", error_msg.c_str());
+ return 0;
+ }
return CreateGuardedApkAssets(std::move(apk_assets));
}
@@ -443,10 +455,10 @@ static jlong NativeGetStringBlock(JNIEnv* /*env*/, jclass /*clazz*/, jlong ptr)
return reinterpret_cast<jlong>(apk_assets->GetLoadedArsc()->GetStringPool());
}
-static jboolean NativeIsUpToDate(CRITICAL_JNI_PARAMS_COMMA jlong ptr) {
+static jint NativeIsUpToDate(CRITICAL_JNI_PARAMS_COMMA jlong ptr) {
auto scoped_apk_assets = ScopedLock(ApkAssetsFromLong(ptr));
auto apk_assets = scoped_apk_assets->get();
- return apk_assets->IsUpToDate() ? JNI_TRUE : JNI_FALSE;
+ return (jint)apk_assets->IsUpToDate();
}
static jlong NativeOpenXml(JNIEnv* env, jclass /*clazz*/, jlong ptr, jstring file_name) {
@@ -558,7 +570,7 @@ static const JNINativeMethod gApkAssetsMethods[] = {
{"nativeGetDebugName", "(J)Ljava/lang/String;", (void*)NativeGetDebugName},
{"nativeGetStringBlock", "(J)J", (void*)NativeGetStringBlock},
// @CriticalNative
- {"nativeIsUpToDate", "(J)Z", (void*)NativeIsUpToDate},
+ {"nativeIsUpToDate", "(J)I", (void*)NativeIsUpToDate},
{"nativeOpenXml", "(JLjava/lang/String;)J", (void*)NativeOpenXml},
{"nativeGetOverlayableInfo", "(JLjava/lang/String;)Landroid/content/om/OverlayableInfo;",
(void*)NativeGetOverlayableInfo},
diff --git a/core/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp
index 2ba6bc4912c3..b679688959b1 100644
--- a/core/jni/android_media_AudioSystem.cpp
+++ b/core/jni/android_media_AudioSystem.cpp
@@ -664,14 +664,16 @@ static void android_media_AudioSystem_vol_range_init_req_callback()
static jint android_media_AudioSystem_setDeviceConnectionState(JNIEnv *env, jobject thiz,
jint state, jobject jParcel,
- jint codec) {
+ jint codec, jboolean deviceSwitch) {
int status;
if (Parcel *parcel = parcelForJavaObject(env, jParcel); parcel != nullptr) {
android::media::audio::common::AudioPort port{};
if (status_t statusOfParcel = port.readFromParcel(parcel); statusOfParcel == OK) {
- status = check_AudioSystem_Command(
- AudioSystem::setDeviceConnectionState(static_cast<audio_policy_dev_state_t>(state),
- port, static_cast<audio_format_t>(codec)));
+ status = check_AudioSystem_Command(
+ AudioSystem::setDeviceConnectionState(static_cast<audio_policy_dev_state_t>(
+ state),
+ port, static_cast<audio_format_t>(codec),
+ deviceSwitch));
} else {
ALOGE("Failed to read from parcel: %s", statusToString(statusOfParcel).c_str());
status = kAudioStatusError;
@@ -3457,7 +3459,7 @@ static const JNINativeMethod gMethods[] = {
MAKE_AUDIO_SYSTEM_METHOD(newAudioSessionId),
MAKE_AUDIO_SYSTEM_METHOD(newAudioPlayerId),
MAKE_AUDIO_SYSTEM_METHOD(newAudioRecorderId),
- MAKE_JNI_NATIVE_METHOD("setDeviceConnectionState", "(ILandroid/os/Parcel;I)I",
+ MAKE_JNI_NATIVE_METHOD("setDeviceConnectionState", "(ILandroid/os/Parcel;IZ)I",
android_media_AudioSystem_setDeviceConnectionState),
MAKE_AUDIO_SYSTEM_METHOD(getDeviceConnectionState),
MAKE_AUDIO_SYSTEM_METHOD(handleDeviceConfigChange),
diff --git a/core/jni/com_android_internal_os_ZygoteCommandBuffer.cpp b/core/jni/com_android_internal_os_ZygoteCommandBuffer.cpp
index e0cc055a62a6..c4259f41e380 100644
--- a/core/jni/com_android_internal_os_ZygoteCommandBuffer.cpp
+++ b/core/jni/com_android_internal_os_ZygoteCommandBuffer.cpp
@@ -266,16 +266,24 @@ class NativeCommandBuffer {
}
// Picky version of atoi(). No sign or unexpected characters allowed. Return -1 on failure.
static int digitsVal(char* start, char* end) {
+ constexpr int vmax = std::numeric_limits<int>::max();
int result = 0;
- if (end - start > 6) {
- return -1;
- }
for (char* dp = start; dp < end; ++dp) {
if (*dp < '0' || *dp > '9') {
- ALOGW("Argument failed integer format check");
+ ALOGW("Argument contains non-integer characters");
+ return -1;
+ }
+ int digit = *dp - '0';
+ if (result > vmax / 10) {
+ ALOGW("Argument exceeds int limit");
+ return -1;
+ }
+ result *= 10;
+ if (result > vmax - digit) {
+ ALOGW("Argument exceeds int limit");
return -1;
}
- result = 10 * result + (*dp - '0');
+ result += digit;
}
return result;
}
diff --git a/core/res/Android.bp b/core/res/Android.bp
index be4fb8bdecfb..1199d77d04c6 100644
--- a/core/res/Android.bp
+++ b/core/res/Android.bp
@@ -174,6 +174,7 @@ android_app {
"android.media.tv.flags-aconfig",
"android.security.flags-aconfig",
"device_policy_aconfig_flags",
+ "android.xr.flags-aconfig",
"com.android.hardware.input.input-aconfig",
"aconfig_trade_in_mode_flags",
"art-aconfig-flags",
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 6f70d889657b..78526ad4a06b 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -5230,6 +5230,182 @@
android:protectionLevel="signature|privileged" />
<!-- ==================================== -->
+ <!-- Permissions for XR perception data -->
+ <!-- ==================================== -->
+ <eat-comment />
+
+ <!-- Used for permissions that are associated with accessing XR
+ tracked information about the person using the device and the
+ environment around them.
+
+ @FlaggedApi(android.xr.Flags.FLAG_XR_MANIFEST_ENTRIES) -->
+ <permission-group android:name="android.permission-group.XR_TRACKING"
+ android:label="@string/permgrouplab_xr_tracking"
+ android:description="@string/permgroupdesc_xr_tracking"
+ android:priority="100"
+ android:featureFlag="android.xr.xr_manifest_entries" />
+
+ <!-- Allows an application to get approximate eye gaze.
+
+ <p>Protection level: dangerous
+
+ @FlaggedApi(android.xr.Flags.FLAG_XR_MANIFEST_ENTRIES) -->
+ <permission android:name="android.permission.EYE_TRACKING_COARSE"
+ android:protectionLevel="dangerous"
+ android:permissionGroup="android.permission-group.UNDEFINED"
+ android:label="@string/permlab_eye_tracking_coarse"
+ android:description="@string/permdesc_eye_tracking_coarse"
+ android:featureFlag="android.xr.xr_manifest_entries" />
+
+ <!-- Allows an application to get face tracking data.
+
+ <p>Protection level: dangerous
+
+ @FlaggedApi(android.xr.Flags.FLAG_XR_MANIFEST_ENTRIES) -->
+ <permission android:name="android.permission.FACE_TRACKING"
+ android:protectionLevel="dangerous"
+ android:permissionGroup="android.permission-group.UNDEFINED"
+ android:label="@string/permlab_face_tracking"
+ android:description="@string/permdesc_face_tracking"
+ android:featureFlag="android.xr.xr_manifest_entries" />
+
+ <!-- Allows an application to get hand tracking data.
+
+ <p>Protection level: dangerous
+
+ @FlaggedApi(android.xr.Flags.FLAG_XR_MANIFEST_ENTRIES) -->
+ <permission android:name="android.permission.HAND_TRACKING"
+ android:protectionLevel="dangerous"
+ android:permissionGroup="android.permission-group.UNDEFINED"
+ android:label="@string/permlab_hand_tracking"
+ android:description="@string/permdesc_hand_tracking"
+ android:featureFlag="android.xr.xr_manifest_entries" />
+
+ <!-- Allows an application to get data derived by sensing the
+ user's environment.
+
+ <p>Protection level: dangerous
+
+ @FlaggedApi(android.xr.Flags.FLAG_XR_MANIFEST_ENTRIES) -->
+ <permission android:name="android.permission.SCENE_UNDERSTANDING_COARSE"
+ android:protectionLevel="dangerous"
+ android:permissionGroup="android.permission-group.UNDEFINED"
+ android:description="@string/permdesc_scene_understanding_coarse"
+ android:label="@string/permlab_scene_understanding_coarse"
+ android:featureFlag="android.xr.xr_manifest_entries" />
+
+ <!-- Used for permissions that are associated with accessing
+ particularly sensitive XR tracking data.
+
+ @FlaggedApi(android.xr.Flags.FLAG_XR_MANIFEST_ENTRIES) -->
+ <permission-group android:name="android.permission-group.XR_TRACKING_SENSITIVE"
+ android:label="@string/permgrouplab_xr_tracking_sensitive"
+ android:description="@string/permgroupdesc_xr_tracking_sensitive"
+ android:priority="100"
+ android:featureFlag="android.xr.xr_manifest_entries" />
+
+ <!-- Allows an application to get precise eye gaze data.
+
+ <p>Protection level: dangerous
+
+ @FlaggedApi(android.xr.Flags.FLAG_XR_MANIFEST_ENTRIES) -->
+ <permission android:name="android.permission.EYE_TRACKING_FINE"
+ android:protectionLevel="dangerous"
+ android:permissionGroup="android.permission-group.UNDEFINED"
+ android:label="@string/permlab_eye_tracking_fine"
+ android:description="@string/permdesc_eye_tracking_fine"
+ android:featureFlag="android.xr.xr_manifest_entries" />
+
+ <!-- Allows an application to get head tracking data. Unmanaged
+ activities (OpenXR activities with the manifest property
+ "android.window.PROPERTY_XR_ACTIVITY_START_MODE" set to
+ "XR_ACTIVITY_START_MODE_FULL_SPACE_UNMANAGED") do not require
+ this permission to get head tracking data.
+
+ {@see https://developer.android.com/develop/xr/get-started#property_activity_xr_start_mode_property}
+
+ <p>Protection level: dangerous
+
+ @FlaggedApi(android.xr.Flags.FLAG_XR_MANIFEST_ENTRIES) -->
+ <permission android:name="android.permission.HEAD_TRACKING"
+ android:protectionLevel="dangerous"
+ android:permissionGroup="android.permission-group.UNDEFINED"
+ android:label="@string/permlab_head_tracking"
+ android:description="@string/permdesc_head_tracking"
+ android:featureFlag="android.xr.xr_manifest_entries" />
+
+ <!-- Allows an application to get highly precise data derived by sensing the
+ user's environment, such as a depth map.
+
+ <p>Protection level: dangerous
+
+ @FlaggedApi(android.xr.Flags.FLAG_XR_MANIFEST_ENTRIES) -->
+ <permission android:name="android.permission.SCENE_UNDERSTANDING_FINE"
+ android:protectionLevel="dangerous"
+ android:permissionGroup="android.permission-group.UNDEFINED"
+ android:description="@string/permdesc_scene_understanding_fine"
+ android:label="@string/permlab_scene_understanding_fine"
+ android:featureFlag="android.xr.xr_manifest_entries" />
+
+ <!-- Allows an application to trigger Eye Calibration, which
+ calibrates for IPD (inter-pupillary distance) adjustment and
+ eye tracking.
+
+ <p>Protection level: signature|privileged
+
+ @SystemApi
+ @FlaggedApi(android.xr.Flags.FLAG_XR_MANIFEST_ENTRIES)
+ @hide -->
+ <permission android:name="android.permission.EYE_CALIBRATION"
+ android:protectionLevel="signature|privileged"
+ android:featureFlag="android.xr.xr_manifest_entries" />
+
+ <!-- Allows an application to trigger Face Tracking Calibration.
+
+ <p>Protection level: signature|privileged
+
+ @SystemApi
+ @FlaggedApi(android.xr.Flags.FLAG_XR_MANIFEST_ENTRIES)
+ @hide -->
+ <permission android:name="android.permission.FACE_TRACKING_CALIBRATION"
+ android:protectionLevel="signature|privileged"
+ android:featureFlag="android.xr.xr_manifest_entries" />
+
+ <!-- Allows an application to import an anchor created and
+ exported by another application.
+
+ <p>Protection level: signature|privileged
+
+ @SystemApi
+ @FlaggedApi(android.xr.Flags.FLAG_XR_MANIFEST_ENTRIES)
+ @hide -->
+ <permission android:name="android.permission.IMPORT_XR_ANCHOR"
+ android:protectionLevel="signature|privileged"
+ android:featureFlag="android.xr.xr_manifest_entries" />
+
+ <!-- Allows an application to access XR tracking data while in the
+ background. Without this permission, XR tracking data such as
+ head tracking, hand tracking, eye tracking, or face tracking
+ is only available to an activity it is in the
+ foreground. With this permission, such data is also available
+ to services and to activities that are in the background.
+
+ <p>This permission must be granted in addition to the
+ corresponding permission such as {@link #HEAD_TRACKING} or
+ {@link #FACE_TRACKING} for the data being accessed.
+
+ <p>Protection level: normal|appop
+
+ @SystemApi
+ @FlaggedApi(android.xr.Flags.FLAG_XR_MANIFEST_ENTRIES)
+ @hide -->
+ <permission android:name="android.permission.XR_TRACKING_IN_BACKGROUND"
+ android:protectionLevel="normal|appop"
+ android:description="@string/permdesc_xr_tracking_in_background"
+ android:label="@string/permlab_xr_tracking_in_background"
+ android:featureFlag="android.xr.xr_manifest_entries" />
+
+ <!-- ==================================== -->
<!-- Private permissions -->
<!-- ==================================== -->
<eat-comment />
diff --git a/core/res/res/drawable-w192dp/loader_horizontal_watch.xml b/core/res/res/drawable-w192dp/loader_horizontal_watch.xml
new file mode 100644
index 000000000000..18cea6e0d87d
--- /dev/null
+++ b/core/res/res/drawable-w192dp/loader_horizontal_watch.xml
@@ -0,0 +1,97 @@
+<!--
+ ~ Copyright (C) 2025 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt">
+ <aapt:attr name="android:drawable">
+ <vector android:height="15dp" android:width="67dp" android:viewportHeight="15" android:viewportWidth="67">
+ <group android:name="_R_G">
+ <group android:name="_R_G_L_1_G" android:translateX="33.5" android:translateY="7.5">
+ <path android:name="_R_G_L_1_G_D_0_P_0" android:fillColor="#000000" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M33.5 -7.5 C33.5,-7.5 33.5,7.5 33.5,7.5 C33.5,7.5 -33.5,7.5 -33.5,7.5 C-33.5,7.5 -33.5,-7.5 -33.5,-7.5 C-33.5,-7.5 33.5,-7.5 33.5,-7.5c "/>
+ </group>
+ <group android:name="_R_G_L_0_G" android:translateX="-296.5" android:translateY="-62.5" android:pivotX="330" android:pivotY="70" android:scaleX="0.1" android:scaleY="0.1">
+ <group android:name="_R_G_L_0_G_L_6_G" android:translateX="-224.84700000000004" android:translateY="-321.948" android:pivotX="555.09" android:pivotY="-329" android:rotation="28.9" android:scaleY="0">
+ <path android:name="_R_G_L_0_G_L_6_G_D_0_P_0" android:fillColor="#303030" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M194.88 359 C190,359 185.05,357.81 180.48,355.3 C59.86,289.14 -41.55,191.9 -112.79,74.11 C-186.14,-47.16 -224.91,-186.55 -224.91,-329 C-224.91,-345.57 -211.48,-359 -194.91,-359 C-178.34,-359 -164.91,-345.57 -164.91,-329 C-164.91,-197.5 -129.13,-68.84 -61.45,43.06 C4.33,151.82 97.97,241.6 209.33,302.69 C223.86,310.66 229.18,328.9 221.21,343.42 C215.75,353.37 205.48,359 194.88,359c "/>
+ </group>
+ <group android:name="_R_G_L_0_G_L_5_G" android:translateX="744.323" android:translateY="-277.96299999999997" android:pivotX="-414.08" android:pivotY="-372.985" android:rotation="28.9">
+ <path android:name="_R_G_L_0_G_L_5_G_D_0_P_0" android:fillColor="#303030" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M-335.95 402.99 C-351.13,402.99 -364.16,391.5 -365.76,376.07 C-367.46,359.59 -355.49,344.85 -339.01,343.14 C-162.93,324.91 -0.15,242.33 119.34,110.62 C239.66,-22.01 305.92,-193.76 305.92,-372.98 C305.92,-389.55 319.35,-402.98 335.92,-402.98 C352.49,-402.98 365.92,-389.55 365.92,-372.98 C365.92,-178.82 294.13,7.24 163.78,150.93 C34.34,293.61 -142.03,383.07 -332.83,402.82 C-333.88,402.93 -334.92,402.99 -335.95,402.99c "/>
+ </group>
+ <group android:name="_R_G_L_0_G_L_2_G" android:translateX="185.385" android:translateY="70.09100000000001" android:pivotX="144.858" android:pivotY="-721.039" android:rotation="28.9" android:scaleY="0">
+ <path android:name="_R_G_L_0_G_L_2_G_D_0_P_0" android:fillColor="#ffffff" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M144.62 58.96 C144.61,58.96 144.61,58.96 144.6,58.96 C40.39,58.93 -60.82,38.66 -156.19,-1.28 C-171.48,-7.68 -178.68,-25.26 -172.28,-40.54 C-165.88,-55.82 -148.3,-63.02 -133.02,-56.62 C-45.02,-19.77 48.4,-1.07 144.63,-1.04 C161.19,-1.03 174.62,12.4 174.62,28.97 C174.61,45.53 161.18,58.96 144.62,58.96c "/>
+ </group>
+ <group android:name="_R_G_L_0_G_L_0_G" android:translateX="330" android:translateY="70">
+ <path android:name="_R_G_L_0_G_L_0_G_D_0_P_0" android:fillColor="#000000" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M-660 -313 C-660,-313 -660,313 -660,313 C-660,313 660,313 660,313 C660,313 660,-313 660,-313 C660,-313 -660,-313 -660,-313c M300.74 -1.16 C205.46,38.62 103.22,59.09 -0.03,59.05 C-103.28,59.01 -205.51,38.48 -300.76,-1.37 C-316.05,-7.76 -323.26,-25.34 -316.86,-40.62 C-310.47,-55.91 -292.9,-63.12 -277.61,-56.72 C-189.68,-19.94 -95.32,-0.98 -0.01,-0.95 C95.3,-0.92 189.67,-19.81 277.63,-56.53 C292.92,-62.91 310.49,-55.69 316.87,-40.4 C323.25,-25.11 316.03,-7.54 300.74,-1.16c "/>
+ </group>
+ </group>
+ </group>
+ <group android:name="time_group"/>
+ </vector>
+ </aapt:attr>
+ <target android:name="_R_G_L_0_G_L_6_G">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator android:repeatCount="infinite" android:propertyName="rotation" android:duration="983" android:startOffset="0" android:valueFrom="28.9" android:valueTo="-51.4" android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.402,0.135 0.202,0.848 1.0,1.0"/>
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_0_G_L_5_G">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator android:repeatCount="infinite" android:propertyName="rotation" android:duration="983" android:startOffset="0" android:valueFrom="28.9" android:valueTo="-51.4" android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.402,0.135 0.202,0.848 1.0,1.0"/>
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_0_G_L_5_G">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator android:repeatCount="infinite" android:propertyName="scaleY" android:duration="0" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_0_G_L_2_G">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator android:repeatCount="infinite" android:propertyName="rotation" android:duration="983" android:startOffset="0" android:valueFrom="28.9" android:valueTo="-51.4" android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.402,0.135 0.202,0.848 1.0,1.0"/>
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_0_G_L_2_G">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator android:repeatCount="infinite" android:propertyName="scaleY" android:duration="0" android:startOffset="133" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="time_group">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator android:repeatCount="infinite" android:propertyName="translateX" android:duration="1000" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/>
+ </set>
+ </aapt:attr>
+ </target>
+</animated-vector>
+
diff --git a/core/res/res/drawable-w204dp/loader_horizontal_watch.xml b/core/res/res/drawable-w204dp/loader_horizontal_watch.xml
new file mode 100644
index 000000000000..fbc6eab320eb
--- /dev/null
+++ b/core/res/res/drawable-w204dp/loader_horizontal_watch.xml
@@ -0,0 +1,104 @@
+<!--
+ ~ Copyright (C) 2025 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt">
+ <aapt:attr name="android:drawable">
+ <vector android:height="15dp" android:width="70dp" android:viewportHeight="15" android:viewportWidth="70">
+ <group android:name="_R_G">
+ <group android:name="_R_G_L_1_G" android:translateX="35" android:translateY="7.5">
+ <path android:name="_R_G_L_1_G_D_0_P_0" android:fillColor="#000000" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M35 -7.5 C35,-7.5 35,7.5 35,7.5 C35,7.5 -35,7.5 -35,7.5 C-35,7.5 -35,-7.5 -35,-7.5 C-35,-7.5 35,-7.5 35,-7.5c "/>
+ </group>
+ <group android:name="_R_G_L_0_G" android:translateX="-310" android:translateY="-64" android:pivotX="345" android:pivotY="71.5" android:scaleX="0.1" android:scaleY="0.1">
+ <group android:name="_R_G_L_0_G_L_6_G" android:translateX="-239.44799999999998" android:translateY="-341.45" android:pivotX="584.448" android:pivotY="-346.55" android:rotation="28.8" android:scaleY="0">
+ <path android:name="_R_G_L_0_G_L_6_G_D_0_P_0" android:fillColor="#303030" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M205.28 376.55 C200.4,376.55 195.46,375.36 190.88,372.85 C64.08,303.29 -42.54,201.07 -117.44,77.24 C-194.55,-50.25 -235.31,-196.79 -235.31,-346.55 C-235.31,-363.12 -221.88,-376.55 -205.31,-376.55 C-188.74,-376.55 -175.31,-363.12 -175.31,-346.55 C-175.31,-207.74 -137.54,-71.93 -66.1,46.19 C3.34,160.99 102.18,255.76 219.73,320.24 C234.26,328.21 239.58,346.45 231.61,360.97 C226.15,370.92 215.88,376.55 205.28,376.55c "/>
+ </group>
+ <group android:name="_R_G_L_0_G_L_5_G" android:translateX="781.413" android:translateY="-295.124" android:pivotX="-436.413" android:pivotY="-392.876" android:rotation="28.8">
+ <path android:name="_R_G_L_0_G_L_5_G_D_0_P_0" android:fillColor="#303030" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M-353.86 422.88 C-369.04,422.88 -382.07,411.4 -383.67,395.97 C-385.37,379.49 -373.4,364.74 -356.92,363.03 C-171.06,343.79 0.76,256.62 126.89,117.59 C253.89,-22.41 323.83,-203.7 323.83,-392.88 C323.83,-409.44 337.26,-422.88 353.83,-422.88 C370.4,-422.88 383.83,-409.44 383.83,-392.88 C383.83,-188.76 308.36,6.84 171.32,157.9 C35.25,307.89 -150.15,401.94 -350.74,422.72 C-351.79,422.82 -352.83,422.88 -353.86,422.88c "/>
+ </group>
+ <group android:name="_R_G_L_0_G_L_2_G" android:translateX="192.671" android:translateY="71.49599999999998" android:pivotX="152.329" android:pivotY="-759.496" android:rotation="28.8" android:scaleY="0">
+ <path android:name="_R_G_L_0_G_L_2_G_D_0_P_0" android:fillColor="#ffffff" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M152.33 60.5 C152.33,60.5 152.32,60.5 152.32,60.5 C42.76,60.47 -63.64,39.16 -163.91,-2.82 C-179.19,-9.22 -186.39,-26.8 -179.99,-42.08 C-173.59,-57.36 -156.02,-64.57 -140.73,-58.16 C-47.84,-19.27 50.77,0.47 152.34,0.5 C168.91,0.51 182.33,13.94 182.33,30.51 C182.32,47.08 168.89,60.5 152.33,60.5c "/>
+ </group>
+ <group android:name="_R_G_L_0_G_L_0_G" android:translateX="345" android:translateY="71.5">
+ <path android:name="_R_G_L_0_G_L_0_G_D_0_P_0" android:fillColor="#000000" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M-579 -259.5 C-579,-259.5 -579,259.5 -579,259.5 C-579,259.5 579,259.5 579,259.5 C579,259.5 579,-259.5 579,-259.5 C579,-259.5 -579,-259.5 -579,-259.5c M316.17 -2.8 C216,39.02 108.52,60.54 -0.03,60.5 C-108.58,60.46 -216.04,38.87 -316.18,-3.02 C-331.47,-9.41 -338.68,-26.99 -332.28,-42.27 C-325.89,-57.56 -308.32,-64.76 -293.03,-58.37 C-200.22,-19.55 -100.61,0.46 -0.01,0.5 C100.6,0.54 200.22,-19.41 293.06,-58.17 C308.35,-64.55 325.92,-57.33 332.3,-42.04 C338.68,-26.75 331.46,-9.18 316.17,-2.8c "/>
+ </group>
+ </group>
+ </group>
+ <group android:name="time_group"/>
+ </vector>
+ </aapt:attr>
+ <target android:name="_R_G_L_0_G_L_6_G">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator android:repeatCount="infinite" android:propertyName="rotation" android:duration="983" android:startOffset="0" android:valueFrom="28.8" android:valueTo="-51.4" android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.402,0.136 0.202,0.847 1.0,1.0"/>
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_0_G_L_6_G">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator android:repeatCount="infinite" android:propertyName="scaleY" android:duration="0" android:startOffset="333" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_0_G_L_5_G">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator android:repeatCount="infinite" android:propertyName="rotation" android:duration="983" android:startOffset="0" android:valueFrom="28.8" android:valueTo="-51.4" android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.402,0.136 0.202,0.847 1.0,1.0"/>
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_0_G_L_5_G">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator android:repeatCount="infinite" android:propertyName="scaleY" android:duration="0" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_0_G_L_2_G">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator android:repeatCount="infinite" android:propertyName="rotation" android:duration="983" android:startOffset="0" android:valueFrom="28.8" android:valueTo="-51.4" android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.402,0.136 0.202,0.847 1.0,1.0"/>
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_0_G_L_2_G">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator android:repeatCount="infinite" android:propertyName="scaleY" android:duration="0" android:startOffset="133" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="time_group">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator android:repeatCount="infinite" android:propertyName="translateX" android:duration="1000" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/>
+ </set>
+ </aapt:attr>
+ </target>
+</animated-vector>
+
diff --git a/core/res/res/drawable-w216dp/loader_horizontal_watch.xml b/core/res/res/drawable-w216dp/loader_horizontal_watch.xml
new file mode 100644
index 000000000000..ed4b7ea0ff02
--- /dev/null
+++ b/core/res/res/drawable-w216dp/loader_horizontal_watch.xml
@@ -0,0 +1,105 @@
+<!--
+ ~ Copyright (C) 2025 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt">
+ <aapt:attr name="android:drawable">
+ <vector android:height="16dp" android:width="74dp" android:viewportHeight="16" android:viewportWidth="74">
+ <group android:name="_R_G">
+ <group android:name="_R_G_L_1_G" android:translateX="37" android:translateY="8">
+ <path android:name="_R_G_L_1_G_D_0_P_0" android:fillColor="#000000" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M37 -8 C37,-8 37,8 37,8 C37,8 -37,8 -37,8 C-37,8 -37,-8 -37,-8 C-37,-8 37,-8 37,-8c "/>
+ </group>
+ <group android:name="_R_G_L_0_G" android:translateX="-328" android:translateY="-65.5" android:pivotX="365" android:pivotY="73.5" android:scaleX="0.1" android:scaleY="0.1">
+ <group android:name="_R_G_L_0_G_L_6_G" android:translateX="-256.447" android:translateY="-365.014" android:pivotX="621.447" android:pivotY="-368.486" android:rotation="28.8" android:scaleY="0">
+ <path android:name="_R_G_L_0_G_L_6_G_D_0_P_0" android:fillColor="#303030" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M218.28 398.49 C213.4,398.49 208.46,397.3 203.88,394.78 C69.34,320.99 -43.78,212.53 -123.25,81.15 C-205.06,-54.11 -248.31,-209.59 -248.31,-368.49 C-248.31,-385.05 -234.88,-398.49 -218.31,-398.49 C-201.74,-398.49 -188.31,-385.05 -188.31,-368.49 C-188.31,-220.54 -148.06,-75.8 -71.91,50.09 C2.1,172.45 107.45,273.45 232.73,342.18 C247.26,350.15 252.58,368.38 244.61,382.91 C239.15,392.86 228.88,398.49 218.28,398.49c "/>
+ </group>
+ <group android:name="_R_G_L_0_G_L_5_G" android:translateX="829.0260000000001" android:translateY="-315.759" android:pivotX="-464.026" android:pivotY="-417.741" android:rotation="28.8">
+ <path android:name="_R_G_L_0_G_L_5_G_D_0_P_0" android:fillColor="#303030" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M-376.25 447.74 C-391.43,447.74 -404.46,436.26 -406.05,420.83 C-407.76,404.35 -395.78,389.61 -379.3,387.9 C-181.22,367.38 1.9,274.48 136.32,126.3 C271.67,-22.9 346.22,-216.12 346.22,-417.74 C346.22,-434.31 359.65,-447.74 376.22,-447.74 C392.79,-447.74 406.22,-434.31 406.22,-417.74 C406.22,-201.18 326.15,6.35 180.76,166.61 C36.39,325.75 -160.31,425.54 -373.12,447.58 C-374.17,447.69 -375.22,447.74 -376.25,447.74c "/>
+ </group>
+ <group android:name="_R_G_L_0_G_L_2_G" android:translateX="203.029" android:translateY="74.06899999999996" android:pivotX="161.971" android:pivotY="-807.569" android:rotation="28.8" android:scaleY="0">
+ <path android:name="_R_G_L_0_G_L_2_G_D_0_P_0" android:fillColor="#ffffff" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M161.97 62.43 C161.97,62.43 161.96,62.43 161.96,62.43 C45.71,62.4 -67.17,39.79 -173.55,-4.75 C-188.83,-11.15 -196.03,-28.72 -189.63,-44.01 C-183.24,-59.29 -165.66,-66.49 -150.38,-60.09 C-51.37,-18.64 53.72,2.4 161.98,2.43 C178.55,2.44 191.98,15.87 191.97,32.44 C191.97,49 178.54,62.43 161.97,62.43c "/>
+ </group>
+ <group android:name="_R_G_L_0_G_L_0_G" android:translateX="365" android:translateY="73.5">
+ <path android:name="_R_G_L_0_G_L_0_G_D_0_P_0" android:fillColor="#000000" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M-609 -244.5 C-609,-244.5 -609,244.5 -609,244.5 C-609,244.5 609,244.5 609,244.5 C609,244.5 609,-244.5 609,-244.5 C609,-244.5 -609,-244.5 -609,-244.5c M335.44 -4.16 C229.17,40.21 115.13,63.04 -0.04,63 C-115.21,62.96 -229.22,40.05 -335.47,-4.39 C-350.76,-10.79 -357.95,-28.36 -351.56,-43.65 C-345.17,-58.93 -327.59,-66.14 -312.31,-59.74 C-213.39,-18.36 -107.24,2.96 -0.02,3 C107.21,3.04 213.38,-18.22 312.33,-59.53 C327.62,-65.91 345.19,-58.69 351.57,-43.4 C357.95,-28.11 350.73,-10.54 335.44,-4.16c "/>
+ </group>
+ </group>
+ </group>
+ <group android:name="time_group"/>
+ </vector>
+ </aapt:attr>
+ <target android:name="_R_G_L_0_G_L_6_G">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator android:repeatCount="infinite" android:propertyName="rotation" android:duration="983" android:startOffset="0" android:valueFrom="28.8" android:valueTo="-51.3" android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.402,0.136 0.202,0.847 1.0,1.0"/>
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_0_G_L_6_G">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator android:repeatCount="infinite" android:propertyName="scaleY" android:duration="0" android:startOffset="333" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_0_G_L_5_G">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator android:repeatCount="infinite" android:propertyName="rotation" android:duration="983" android:startOffset="0" android:valueFrom="28.8" android:valueTo="-51.3" android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.402,0.136 0.202,0.847 1.0,1.0"/>
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_0_G_L_5_G">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator android:repeatCount="infinite" android:propertyName="scaleY" android:duration="0" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_0_G_L_2_G">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator android:repeatCount="infinite" android:propertyName="rotation" android:duration="983" android:startOffset="0" android:valueFrom="28.8" android:valueTo="-51.3" android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.402,0.136 0.202,0.847 1.0,1.0"/>
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_0_G_L_2_G">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator android:repeatCount="infinite" android:propertyName="scaleY" android:duration="0" android:startOffset="133" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="time_group">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator android:repeatCount="infinite" android:propertyName="translateX" android:duration="1000" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/>
+ </set>
+ </aapt:attr>
+ </target>
+</animated-vector>
+
+
diff --git a/core/res/res/drawable-w228dp/loader_horizontal_watch.xml b/core/res/res/drawable-w228dp/loader_horizontal_watch.xml
new file mode 100644
index 000000000000..6b86c634d554
--- /dev/null
+++ b/core/res/res/drawable-w228dp/loader_horizontal_watch.xml
@@ -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, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt">
+ <aapt:attr name="android:drawable">
+ <vector android:height="14dp" android:width="76dp" android:viewportHeight="14" android:viewportWidth="76">
+ <group android:name="_R_G">
+ <group android:name="_R_G_L_1_G" android:translateX="39" android:translateY="8">
+ <path android:name="_R_G_L_1_G_D_0_P_0" android:fillColor="#000000" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M39 -8 C39,-8 39,8 39,8 C39,8 -39,8 -39,8 C-39,8 -39,-8 -39,-8 C-39,-8 39,-8 39,-8c "/>
+ </group>
+ <group android:name="_R_G_L_0_G" android:translateX="-345" android:translateY="-67" android:pivotX="384" android:pivotY="75" android:scaleX="0.1" android:scaleY="0.1">
+ <group android:name="_R_G_L_0_G_L_6_G" android:translateX="-274.19" android:translateY="-390.077" android:pivotX="658.448" android:pivotY="-390.423" android:rotation="28.7" android:scaleY="0">
+ <path android:name="_R_G_L_0_G_L_6_G_D_0_P_0" android:fillColor="#303030" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M231.28 420.42 C226.4,420.42 221.45,419.23 216.88,416.72 C74.61,338.68 -45.02,224 -129.06,85.06 C-171.54,14.82 -204.38,-60.73 -226.66,-139.5 C-249.65,-220.76 -261.31,-305.18 -261.31,-390.42 C-261.31,-406.99 -247.88,-420.42 -231.31,-420.42 C-214.74,-420.42 -201.31,-406.99 -201.31,-390.42 C-201.31,-310.71 -190.42,-231.78 -168.93,-155.83 C-148.11,-82.23 -117.42,-11.63 -77.72,54 C0.86,183.92 112.71,291.15 245.73,364.12 C260.26,372.08 265.58,390.32 257.61,404.85 C252.15,414.79 241.88,420.42 231.28,420.42c "/>
+ </group>
+ <group android:name="_R_G_L_0_G_L_5_G" android:translateX="875.8979999999999" android:translateY="-337.894" android:pivotX="-491.64" android:pivotY="-442.606" android:rotation="28.7">
+ <path android:name="_R_G_L_0_G_L_5_G_D_0_P_0" android:fillColor="#303030" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M-398.64 472.61 C-413.82,472.61 -426.84,461.13 -428.44,445.7 C-430.15,429.22 -418.17,414.47 -401.69,412.77 C-191.38,390.98 3.04,292.33 145.75,135.01 C289.46,-23.4 368.6,-228.54 368.6,-442.61 C368.6,-459.17 382.04,-472.61 398.6,-472.61 C415.17,-472.61 428.6,-459.17 428.6,-442.61 C428.6,-213.6 343.93,5.85 190.19,175.33 C37.53,343.61 -170.48,449.13 -395.51,472.44 C-396.56,472.55 -397.6,472.61 -398.64,472.61c "/>
+ </group>
+ <group android:name="_R_G_L_0_G_L_2_G" android:translateX="212.64499999999998" android:translateY="75.14200000000005" android:pivotX="171.613" android:pivotY="-855.642" android:rotation="28.7" android:scaleY="0">
+ <path android:name="_R_G_L_0_G_L_2_G_D_0_P_0" android:fillColor="#ffffff" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M171.61 64.36 C171.61,64.36 171.61,64.36 171.61,64.36 C48.68,64.32 -70.7,40.42 -183.19,-6.68 C-198.47,-13.07 -205.68,-30.65 -199.28,-45.93 C-192.88,-61.22 -175.3,-68.42 -160.02,-62.02 C-54.9,-18.01 56.68,4.33 171.62,4.36 C188.19,4.36 201.62,17.8 201.61,34.36 C201.61,50.93 188.18,64.36 171.61,64.36c "/>
+ </group>
+ <group android:name="_R_G_L_0_G_L_0_G" android:translateX="384" android:translateY="75">
+ <path android:name="_R_G_L_0_G_L_0_G_D_0_P_0" android:fillColor="#000000" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M-611 -259 C-611,-259 -611,259 -611,259 C-611,259 611,259 611,259 C611,259 611,-259 611,-259 C611,-259 -611,-259 -611,-259c M354.66 -6.52 C242.36,40.4 121.76,64.54 -0.04,64.5 C-121.84,64.46 -242.44,40.23 -354.74,-6.76 C-370.04,-13.16 -377.24,-30.73 -370.84,-46.02 C-364.44,-61.3 -346.94,-68.51 -331.64,-62.12 C-226.54,-18.18 -113.84,4.46 -0.04,4.5 C113.76,4.54 226.56,-18.02 331.56,-61.89 C346.86,-68.27 364.46,-61.05 370.86,-45.76 C377.26,-30.47 369.96,-12.9 354.66,-6.52c "/>
+ </group>
+ </group>
+ </group>
+ <group android:name="time_group"/>
+ </vector>
+ </aapt:attr>
+ <target android:name="_R_G_L_0_G_L_6_G">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator android:repeatCount="infinite" android:propertyName="rotation" android:duration="983" android:startOffset="0" android:valueFrom="28.7" android:valueTo="-51.4" android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.402,0.136 0.202,0.847 1.0,1.0"/>
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_0_G_L_6_G">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator android:repeatCount="infinite" android:propertyName="scaleY" android:duration="0" android:startOffset="333" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_0_G_L_5_G">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator android:repeatCount="infinite" android:propertyName="rotation" android:duration="983" android:startOffset="0" android:valueFrom="28.7" android:valueTo="-51.4" android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.402,0.136 0.202,0.847 1.0,1.0"/>
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_0_G_L_5_G">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator android:repeatCount="infinite" android:propertyName="scaleY" android:duration="0" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_0_G_L_2_G">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator android:repeatCount="infinite" android:propertyName="rotation" android:duration="983" android:startOffset="0" android:valueFrom="28.7" android:valueTo="-51.4" android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.402,0.136 0.202,0.847 1.0,1.0"/>
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_0_G_L_2_G">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator android:repeatCount="infinite" android:propertyName="scaleY" android:duration="0" android:startOffset="133" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="time_group">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator android:repeatCount="infinite" android:propertyName="translateX" android:duration="1000" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/>
+ </set>
+ </aapt:attr>
+ </target>
+</animated-vector>
diff --git a/core/res/res/drawable-w240dp/loader_horizontal_watch.xml b/core/res/res/drawable-w240dp/loader_horizontal_watch.xml
new file mode 100644
index 000000000000..ad60bbdc420c
--- /dev/null
+++ b/core/res/res/drawable-w240dp/loader_horizontal_watch.xml
@@ -0,0 +1,104 @@
+<!--
+ ~ Copyright (C) 2025 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt">
+ <aapt:attr name="android:drawable">
+ <vector android:height="17dp" android:width="82dp" android:viewportHeight="17" android:viewportWidth="82">
+ <group android:name="_R_G">
+ <group android:name="_R_G_L_1_G" android:translateX="41" android:translateY="8.5">
+ <path android:name="_R_G_L_1_G_D_0_P_0" android:fillColor="#000000" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M41 -8.5 C41,-8.5 41,8.5 41,8.5 C41,8.5 -41,8.5 -41,8.5 C-41,8.5 -41,-8.5 -41,-8.5 C-41,-8.5 41,-8.5 41,-8.5c "/>
+ </group>
+ <group android:name="_R_G_L_0_G" android:translateX="-362.5" android:translateY="-69" android:pivotX="403.5" android:pivotY="77.5" android:scaleX="0.1" android:scaleY="0.1">
+ <group android:name="_R_G_L_0_G_L_6_G" android:translateX="-291.64799999999997" android:translateY="-414.141" android:pivotX="695.448" android:pivotY="-412.359" android:rotation="28.7" android:scaleY="0">
+ <path android:name="_R_G_L_0_G_L_6_G_D_0_P_0" android:fillColor="#303030" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M244.28 442.36 C239.4,442.36 234.45,441.17 229.88,438.66 C79.87,356.38 -46.26,235.46 -134.87,88.96 C-179.66,14.91 -214.28,-64.74 -237.78,-147.79 C-262.02,-233.47 -274.31,-322.49 -274.31,-412.36 C-274.31,-428.93 -260.88,-442.36 -244.31,-442.36 C-227.74,-442.36 -214.31,-428.93 -214.31,-412.36 C-214.31,-328.01 -202.78,-244.49 -180.05,-164.12 C-158.01,-86.24 -125.54,-11.54 -83.53,57.91 C-0.38,195.38 117.97,308.85 258.73,386.05 C273.26,394.02 278.58,412.26 270.61,426.78 C265.15,436.73 254.88,442.36 244.28,442.36c "/>
+ </group>
+ <group android:name="_R_G_L_0_G_L_5_G" android:translateX="923.0530000000001" android:translateY="-359.029" android:pivotX="-519.253" android:pivotY="-467.471" android:rotation="28.7">
+ <path android:name="_R_G_L_0_G_L_5_G_D_0_P_0" android:fillColor="#303030" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M-421.02 497.47 C-436.2,497.47 -449.23,485.99 -450.83,470.56 C-452.53,454.08 -440.56,439.34 -424.08,437.63 C-201.54,414.57 4.18,310.19 155.19,143.73 C229.54,61.77 287.7,-31.75 328.04,-134.22 C369.81,-240.3 390.99,-352.42 390.99,-467.47 C390.99,-484.04 404.42,-497.47 420.99,-497.47 C437.56,-497.47 450.99,-484.04 450.99,-467.47 C450.99,-226.02 361.72,5.35 199.63,184.04 C38.67,361.47 -180.63,472.73 -417.89,497.31 C-418.94,497.42 -419.99,497.47 -421.02,497.47c "/>
+ </group>
+ <group android:name="_R_G_L_0_G_L_2_G" android:translateX="222.54600000000002" android:translateY="77.21400000000006" android:pivotX="181.254" android:pivotY="-903.714" android:rotation="28.7" android:scaleY="0">
+ <path android:name="_R_G_L_0_G_L_2_G_D_0_P_0" android:fillColor="#ffffff" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M181.26 66.28 C181.25,66.28 181.25,66.28 181.25,66.28 C51.64,66.25 -74.22,41.06 -192.83,-8.6 C-208.12,-15 -215.32,-32.58 -208.92,-47.86 C-202.52,-63.15 -184.94,-70.35 -169.66,-63.95 C-58.42,-17.38 59.64,6.25 181.26,6.28 C197.83,6.29 211.26,19.72 211.26,36.29 C211.25,52.86 197.82,66.28 181.26,66.28c "/>
+ </group>
+ <group android:name="_R_G_L_0_G_L_0_G" android:translateX="403.5" android:translateY="77.5">
+ <path android:name="_R_G_L_0_G_L_0_G_D_0_P_0" android:fillColor="#000000" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M-630.5 -255.5 C-630.5,-255.5 -630.5,255.5 -630.5,255.5 C-630.5,255.5 630.5,255.5 630.5,255.5 C630.5,255.5 630.5,-255.5 630.5,-255.5 C630.5,-255.5 -630.5,-255.5 -630.5,-255.5c M374 -8.88 C255.5,40.59 128.4,66.04 0,66 C-128.4,65.95 -255.6,40.42 -374,-9.14 C-389.3,-15.53 -396.5,-33.11 -390.1,-48.39 C-383.7,-63.68 -366.2,-70.88 -350.9,-64.49 C-239.7,-18 -120.5,5.96 0,6 C120.4,6.04 239.7,-17.84 350.9,-64.25 C366.2,-70.63 383.7,-63.41 390.1,-48.12 C396.5,-32.83 389.3,-15.26 374,-8.88c "/>
+ </group>
+ </group>
+ </group>
+ <group android:name="time_group"/>
+ </vector>
+ </aapt:attr>
+ <target android:name="_R_G_L_0_G_L_6_G">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator android:repeatCount="infinite" android:propertyName="rotation" android:duration="983" android:startOffset="0" android:valueFrom="28.7" android:valueTo="-51.4" android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.402,0.136 0.202,0.847 1.0,1.0"/>
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_0_G_L_6_G">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator android:repeatCount="infinite" android:propertyName="scaleY" android:duration="0" android:startOffset="333" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_0_G_L_5_G">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator android:repeatCount="infinite" android:propertyName="rotation" android:duration="983" android:startOffset="0" android:valueFrom="28.7" android:valueTo="-51.4" android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.402,0.136 0.202,0.847 1.0,1.0"/>
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_0_G_L_5_G">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator android:repeatCount="infinite" android:propertyName="scaleY" android:duration="0" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_0_G_L_2_G">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator android:repeatCount="infinite" android:propertyName="rotation" android:duration="983" android:startOffset="0" android:valueFrom="28.7" android:valueTo="-51.4" android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.402,0.136 0.202,0.847 1.0,1.0"/>
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_0_G_L_2_G">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator android:repeatCount="infinite" android:propertyName="scaleY" android:duration="0" android:startOffset="133" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="time_group">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator android:repeatCount="infinite" android:propertyName="translateX" android:duration="1000" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/>
+ </set>
+ </aapt:attr>
+ </target>
+</animated-vector>
+
diff --git a/core/res/res/drawable/ic_notification_summarization.xml b/core/res/res/drawable/ic_notification_summarization.xml
index de905fa10728..d476872a0e20 100644
--- a/core/res/res/drawable/ic_notification_summarization.xml
+++ b/core/res/res/drawable/ic_notification_summarization.xml
@@ -19,5 +19,6 @@ Copyright (C) 2025 The Android Open Source Project
android:tint="?android:attr/colorControlNormal"
android:viewportHeight="960"
android:viewportWidth="960">
- <path android:fillColor="#ffffff" android:pathData="M354,673L480,597L606,674L573,530L684,434L538,421L480,285L422,420L276,433L387,530L354,673ZM233,840L298,559L80,370L368,345L480,80L592,345L880,370L662,559L727,840L480,691L233,840ZM480,490L480,490L480,490L480,490L480,490L480,490L480,490L480,490L480,490L480,490Z"/>
+ <path android:fillColor="#ffffff"
+ android:pathData="M120,840L120,760L600,760L600,840L120,840ZM120,640L120,560L840,560L840,640L120,640ZM120,440L120,360L560,360L560,440L120,440ZM700,480Q700,388 636,324Q572,260 480,260Q572,260 636,196Q700,132 700,40Q700,132 764,196Q828,260 920,260Q828,260 764,324Q700,388 700,480Z"/>
</vector> \ No newline at end of file
diff --git a/core/res/res/drawable/loader_horizontal_watch.xml b/core/res/res/drawable/loader_horizontal_watch.xml
new file mode 100644
index 000000000000..6b86c634d554
--- /dev/null
+++ b/core/res/res/drawable/loader_horizontal_watch.xml
@@ -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, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt">
+ <aapt:attr name="android:drawable">
+ <vector android:height="14dp" android:width="76dp" android:viewportHeight="14" android:viewportWidth="76">
+ <group android:name="_R_G">
+ <group android:name="_R_G_L_1_G" android:translateX="39" android:translateY="8">
+ <path android:name="_R_G_L_1_G_D_0_P_0" android:fillColor="#000000" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M39 -8 C39,-8 39,8 39,8 C39,8 -39,8 -39,8 C-39,8 -39,-8 -39,-8 C-39,-8 39,-8 39,-8c "/>
+ </group>
+ <group android:name="_R_G_L_0_G" android:translateX="-345" android:translateY="-67" android:pivotX="384" android:pivotY="75" android:scaleX="0.1" android:scaleY="0.1">
+ <group android:name="_R_G_L_0_G_L_6_G" android:translateX="-274.19" android:translateY="-390.077" android:pivotX="658.448" android:pivotY="-390.423" android:rotation="28.7" android:scaleY="0">
+ <path android:name="_R_G_L_0_G_L_6_G_D_0_P_0" android:fillColor="#303030" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M231.28 420.42 C226.4,420.42 221.45,419.23 216.88,416.72 C74.61,338.68 -45.02,224 -129.06,85.06 C-171.54,14.82 -204.38,-60.73 -226.66,-139.5 C-249.65,-220.76 -261.31,-305.18 -261.31,-390.42 C-261.31,-406.99 -247.88,-420.42 -231.31,-420.42 C-214.74,-420.42 -201.31,-406.99 -201.31,-390.42 C-201.31,-310.71 -190.42,-231.78 -168.93,-155.83 C-148.11,-82.23 -117.42,-11.63 -77.72,54 C0.86,183.92 112.71,291.15 245.73,364.12 C260.26,372.08 265.58,390.32 257.61,404.85 C252.15,414.79 241.88,420.42 231.28,420.42c "/>
+ </group>
+ <group android:name="_R_G_L_0_G_L_5_G" android:translateX="875.8979999999999" android:translateY="-337.894" android:pivotX="-491.64" android:pivotY="-442.606" android:rotation="28.7">
+ <path android:name="_R_G_L_0_G_L_5_G_D_0_P_0" android:fillColor="#303030" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M-398.64 472.61 C-413.82,472.61 -426.84,461.13 -428.44,445.7 C-430.15,429.22 -418.17,414.47 -401.69,412.77 C-191.38,390.98 3.04,292.33 145.75,135.01 C289.46,-23.4 368.6,-228.54 368.6,-442.61 C368.6,-459.17 382.04,-472.61 398.6,-472.61 C415.17,-472.61 428.6,-459.17 428.6,-442.61 C428.6,-213.6 343.93,5.85 190.19,175.33 C37.53,343.61 -170.48,449.13 -395.51,472.44 C-396.56,472.55 -397.6,472.61 -398.64,472.61c "/>
+ </group>
+ <group android:name="_R_G_L_0_G_L_2_G" android:translateX="212.64499999999998" android:translateY="75.14200000000005" android:pivotX="171.613" android:pivotY="-855.642" android:rotation="28.7" android:scaleY="0">
+ <path android:name="_R_G_L_0_G_L_2_G_D_0_P_0" android:fillColor="#ffffff" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M171.61 64.36 C171.61,64.36 171.61,64.36 171.61,64.36 C48.68,64.32 -70.7,40.42 -183.19,-6.68 C-198.47,-13.07 -205.68,-30.65 -199.28,-45.93 C-192.88,-61.22 -175.3,-68.42 -160.02,-62.02 C-54.9,-18.01 56.68,4.33 171.62,4.36 C188.19,4.36 201.62,17.8 201.61,34.36 C201.61,50.93 188.18,64.36 171.61,64.36c "/>
+ </group>
+ <group android:name="_R_G_L_0_G_L_0_G" android:translateX="384" android:translateY="75">
+ <path android:name="_R_G_L_0_G_L_0_G_D_0_P_0" android:fillColor="#000000" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M-611 -259 C-611,-259 -611,259 -611,259 C-611,259 611,259 611,259 C611,259 611,-259 611,-259 C611,-259 -611,-259 -611,-259c M354.66 -6.52 C242.36,40.4 121.76,64.54 -0.04,64.5 C-121.84,64.46 -242.44,40.23 -354.74,-6.76 C-370.04,-13.16 -377.24,-30.73 -370.84,-46.02 C-364.44,-61.3 -346.94,-68.51 -331.64,-62.12 C-226.54,-18.18 -113.84,4.46 -0.04,4.5 C113.76,4.54 226.56,-18.02 331.56,-61.89 C346.86,-68.27 364.46,-61.05 370.86,-45.76 C377.26,-30.47 369.96,-12.9 354.66,-6.52c "/>
+ </group>
+ </group>
+ </group>
+ <group android:name="time_group"/>
+ </vector>
+ </aapt:attr>
+ <target android:name="_R_G_L_0_G_L_6_G">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator android:repeatCount="infinite" android:propertyName="rotation" android:duration="983" android:startOffset="0" android:valueFrom="28.7" android:valueTo="-51.4" android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.402,0.136 0.202,0.847 1.0,1.0"/>
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_0_G_L_6_G">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator android:repeatCount="infinite" android:propertyName="scaleY" android:duration="0" android:startOffset="333" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_0_G_L_5_G">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator android:repeatCount="infinite" android:propertyName="rotation" android:duration="983" android:startOffset="0" android:valueFrom="28.7" android:valueTo="-51.4" android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.402,0.136 0.202,0.847 1.0,1.0"/>
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_0_G_L_5_G">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator android:repeatCount="infinite" android:propertyName="scaleY" android:duration="0" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_0_G_L_2_G">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator android:repeatCount="infinite" android:propertyName="rotation" android:duration="983" android:startOffset="0" android:valueFrom="28.7" android:valueTo="-51.4" android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.402,0.136 0.202,0.847 1.0,1.0"/>
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_0_G_L_2_G">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator android:repeatCount="infinite" android:propertyName="scaleY" android:duration="0" android:startOffset="133" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="time_group">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator android:repeatCount="infinite" android:propertyName="translateX" android:duration="1000" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/>
+ </set>
+ </aapt:attr>
+ </target>
+</animated-vector>
diff --git a/core/res/res/layout/media_route_controller_dialog.xml b/core/res/res/layout/media_route_controller_dialog.xml
index 24a25353f40d..a5cd83be9f6c 100644
--- a/core/res/res/layout/media_route_controller_dialog.xml
+++ b/core/res/res/layout/media_route_controller_dialog.xml
@@ -41,11 +41,5 @@
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp" />
</LinearLayout>
-
- <!-- Optional content view section. -->
- <FrameLayout android:id="@+id/media_route_control_frame"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:visibility="gone" />
</LinearLayout>
</ScrollView>
diff --git a/core/res/res/layout/notification_2025_conversation_header.xml b/core/res/res/layout/notification_2025_conversation_header.xml
index 75bd244cbbf4..1bde17358825 100644
--- a/core/res/res/layout/notification_2025_conversation_header.xml
+++ b/core/res/res/layout/notification_2025_conversation_header.xml
@@ -29,7 +29,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Title"
- android:textSize="@dimen/notification_2025_title_text_size"
+ android:textSize="16sp"
android:singleLine="true"
android:layout_weight="1"
/>
diff --git a/core/res/res/layout/notification_2025_template_collapsed_base.xml b/core/res/res/layout/notification_2025_template_collapsed_base.xml
index 054583297d37..d29b7af9e24e 100644
--- a/core/res/res/layout/notification_2025_template_collapsed_base.xml
+++ b/core/res/res/layout/notification_2025_template_collapsed_base.xml
@@ -102,7 +102,6 @@
android:singleLine="true"
android:textAlignment="viewStart"
android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Title"
- android:textSize="@dimen/notification_2025_title_text_size"
/>
<include layout="@layout/notification_2025_top_line_views" />
diff --git a/core/res/res/layout/notification_2025_template_collapsed_media.xml b/core/res/res/layout/notification_2025_template_collapsed_media.xml
index 9959b666b3bf..5beab508aecf 100644
--- a/core/res/res/layout/notification_2025_template_collapsed_media.xml
+++ b/core/res/res/layout/notification_2025_template_collapsed_media.xml
@@ -104,7 +104,6 @@
android:singleLine="true"
android:textAlignment="viewStart"
android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Title"
- android:textSize="@dimen/notification_2025_title_text_size"
/>
<include layout="@layout/notification_2025_top_line_views" />
diff --git a/core/res/res/layout/notification_2025_template_collapsed_messaging.xml b/core/res/res/layout/notification_2025_template_collapsed_messaging.xml
index 85ca124de8ff..d7c3263904d4 100644
--- a/core/res/res/layout/notification_2025_template_collapsed_messaging.xml
+++ b/core/res/res/layout/notification_2025_template_collapsed_messaging.xml
@@ -130,7 +130,6 @@
android:singleLine="true"
android:textAlignment="viewStart"
android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Title"
- android:textSize="@dimen/notification_2025_title_text_size"
/>
<include layout="@layout/notification_2025_top_line_views" />
diff --git a/core/res/res/layout/notification_2025_template_compact_heads_up_base.xml b/core/res/res/layout/notification_2025_template_compact_heads_up_base.xml
index 11fc48668ad7..52bc7b8ea3bb 100644
--- a/core/res/res/layout/notification_2025_template_compact_heads_up_base.xml
+++ b/core/res/res/layout/notification_2025_template_compact_heads_up_base.xml
@@ -69,7 +69,6 @@
android:singleLine="true"
android:textAlignment="viewStart"
android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Title"
- android:textSize="@dimen/notification_2025_title_text_size"
/>
<include layout="@layout/notification_2025_top_line_views" />
</NotificationTopLineView>
diff --git a/core/res/res/layout/notification_2025_template_compact_heads_up_messaging.xml b/core/res/res/layout/notification_2025_template_compact_heads_up_messaging.xml
index bf70a5eff47e..cf9ff6bef6f8 100644
--- a/core/res/res/layout/notification_2025_template_compact_heads_up_messaging.xml
+++ b/core/res/res/layout/notification_2025_template_compact_heads_up_messaging.xml
@@ -90,7 +90,6 @@
android:singleLine="true"
android:textAlignment="viewStart"
android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Title"
- android:textSize="@dimen/notification_2025_title_text_size"
/>
<include layout="@layout/notification_2025_top_line_views" />
</NotificationTopLineView>
diff --git a/core/res/res/values-w192dp/dimens_watch.xml b/core/res/res/values-w192dp/dimens_watch.xml
new file mode 100644
index 000000000000..c6bf767ab6b8
--- /dev/null
+++ b/core/res/res/values-w192dp/dimens_watch.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.
+ -->
+<resources>
+ <!-- 16.7% of display size -->
+ <dimen name="base_error_dialog_top_padding">32dp</dimen>
+ <!-- 5.2% of display size -->
+ <dimen name="base_error_dialog_padding">10dp</dimen>
+ <!-- 20.83% of display size -->
+ <dimen name="base_error_dialog_bottom_padding">40dp</dimen>
+
+ <!-- watch's indeterminate progress bar dimens based on the current screen size -->
+ <dimen name="loader_horizontal_min_width_watch">67dp</dimen>
+ <dimen name="loader_horizontal_min_height_watch">15dp</dimen>
+</resources>
diff --git a/core/res/res/values-w204dp-round-watch/dimens_watch.xml b/core/res/res/values-w204dp-round-watch/dimens_watch.xml
new file mode 100644
index 000000000000..3509474c5c2e
--- /dev/null
+++ b/core/res/res/values-w204dp-round-watch/dimens_watch.xml
@@ -0,0 +1,21 @@
+<?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.
+ -->
+<resources>
+ <!-- watch's indeterminate progress bar dimens based on the current screen size -->
+ <dimen name="loader_horizontal_min_width_watch">70dp</dimen>
+ <dimen name="loader_horizontal_min_height_watch">15dp</dimen>
+</resources>
diff --git a/core/res/res/values-w216dp/dimens_watch.xml b/core/res/res/values-w216dp/dimens_watch.xml
new file mode 100644
index 000000000000..e14ce5e98f55
--- /dev/null
+++ b/core/res/res/values-w216dp/dimens_watch.xml
@@ -0,0 +1,21 @@
+<!--
+ ~ Copyright (C) 2025 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources>
+ <!-- watch's indeterminate progress bar dimens based on the current screen size -->
+ <dimen name="loader_horizontal_min_width_watch">72dp</dimen>
+ <dimen name="loader_horizontal_min_height_watch">16dp</dimen>
+</resources> \ No newline at end of file
diff --git a/core/res/res/values-w228dp/dimens_watch.xml b/core/res/res/values-w228dp/dimens_watch.xml
new file mode 100644
index 000000000000..3c6265690c3c
--- /dev/null
+++ b/core/res/res/values-w228dp/dimens_watch.xml
@@ -0,0 +1,21 @@
+<?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.
+ -->
+<resources>
+ <!-- watch's indeterminate progress bar dimens based on the current screen size -->
+ <dimen name="loader_horizontal_min_width">76dp</dimen>
+ <dimen name="loader_horizontal_min_height">14dp</dimen>
+</resources>
diff --git a/core/res/res/values-w240dp/dimens_material.xml b/core/res/res/values-w240dp/dimens_material.xml
index bd26c8bf84df..e30aea46aec7 100644
--- a/core/res/res/values-w240dp/dimens_material.xml
+++ b/core/res/res/values-w240dp/dimens_material.xml
@@ -21,4 +21,8 @@
<dimen name="screen_percentage_12">28.8dp</dimen>
<dimen name="screen_percentage_15">36dp</dimen>
<dimen name="screen_percentage_3646">87.5dp</dimen>
+
+ <!-- watch's indeterminate progress bar dimens based on the current screen size -->
+ <dimen name="progress_indeterminate_horizontal_min_width_watch">80dp</dimen>
+ <dimen name="progress_indeterminate_horizontal_min_height_watch">17dp</dimen>
</resources>
diff --git a/core/res/res/values-watch/styles_device_defaults.xml b/core/res/res/values-watch/styles_device_defaults.xml
index fb7dbb0660c5..eeb66e7cf6a8 100644
--- a/core/res/res/values-watch/styles_device_defaults.xml
+++ b/core/res/res/values-watch/styles_device_defaults.xml
@@ -42,5 +42,8 @@
<item name="indeterminateOnly">false</item>
<!-- Use Wear Material3 ring shape as default determinate drawable -->
<item name="progressDrawable">@drawable/progress_ring_watch</item>
+ <item name="indeterminateDrawable">@drawable/loader_horizontal_watch</item>
+ <item name="android:minWidth">@dimen/loader_horizontal_min_width_watch</item>
+ <item name="android:minHeight">@dimen/loader_horizontal_min_height_watch</item>
</style>
</resources>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 1a311d572e0b..2188469bdf03 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -2643,6 +2643,15 @@
<!-- MMS user agent prolfile url -->
<string name="config_mms_user_agent_profile_url" translatable="false"></string>
+ <!-- The default list of possible CMF Names|Style|ColorSource. This array can be
+ overridden device-specific resources. A wildcard (fallback) must be supplied.
+ Name - Read from `ro.boot.hardware.color` sysprop. Fallback (*) required.
+ Styles - frameworks/libs/systemui/monet/src/com/android/systemui/monet/Style.java
+ Color - `home_wallpaper` (for color extraction) or a hexadecimal int (#FFcc99) -->
+ <string-array name="theming_defaults">
+ <item>*|TONAL_SPOT|home_wallpaper</item>
+ </string-array>
+
<!-- National Language Identifier codes for the following two config items.
(from 3GPP TS 23.038 V9.1.1 Table 6.2.1.2.4.1):
0 - reserved
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index e9d87e4b5f5b..9acb2427aaab 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -580,9 +580,6 @@
<dimen name="notification_text_size">14sp</dimen>
<!-- Size of notification text titles (see TextAppearance.StatusBar.EventContent.Title) -->
<dimen name="notification_title_text_size">14sp</dimen>
- <!-- Size of notification text titles, 2025 redesign version (see TextAppearance.StatusBar.EventContent.Title) -->
- <!-- TODO: b/378660052 - When inlining the redesign flag, this should be updated directly in TextAppearance.DeviceDefault.Notification.Title -->
- <dimen name="notification_2025_title_text_size">16sp</dimen>
<!-- Size of big notification text titles (see TextAppearance.StatusBar.EventContent.BigTitle) -->
<dimen name="notification_big_title_text_size">16sp</dimen>
<!-- Size of smaller notification text (see TextAppearance.StatusBar.EventContent.Line2, Info, Time) -->
diff --git a/core/res/res/values/dimens_watch.xml b/core/res/res/values/dimens_watch.xml
index 2aae98715973..19845916984b 100644
--- a/core/res/res/values/dimens_watch.xml
+++ b/core/res/res/values/dimens_watch.xml
@@ -61,4 +61,8 @@
<dimen name="disabled_alpha_wear_material3">0.12</dimen>
<!-- Alpha transparency applied to elements which are considered primary (e.g. primary text) -->
<dimen name="primary_content_alpha_wear_material3">0.38</dimen>
+
+ <!-- watch's indeterminate progress bar dimens -->
+ <dimen name="loader_horizontal_min_width">68dp</dimen>
+ <dimen name="loader_horizontal_min_height">13dp</dimen>
</resources>
diff --git a/core/res/res/values/public-staging.xml b/core/res/res/values/public-staging.xml
index e3137e2e77e3..ac1e841d3143 100644
--- a/core/res/res/values/public-staging.xml
+++ b/core/res/res/values/public-staging.xml
@@ -116,14 +116,14 @@
<public name="alternateLauncherIcons"/>
<!-- @FlaggedApi(android.content.pm.Flags.FLAG_CHANGE_LAUNCHER_BADGING) -->
<public name="alternateLauncherLabels"/>
- <!-- @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"/>
+ <!-- @hide Wrongly added here. -->
+ <public name="removed_pointerIconVectorFill"/>
+ <!-- @hide Wrongly added here. -->
+ <public name="removed_pointerIconVectorFillInverse"/>
+ <!-- @hide Wrongly added here. -->
+ <public name="removed_pointerIconVectorStroke"/>
+ <!-- @hide Wrongly added here. -->
+ <public name="removed_pointerIconVectorStrokeInverse"/>
</staging-public-group>
<staging-public-group type="id" first-id="0x01b20000">
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index abbba9d1bffa..7a93ca1e9ac6 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -1011,6 +1011,16 @@
<!-- Description of a category of application permissions, listed so the user can choose whether they want to allow the application to do this. [CHAR LIMIT=NONE]-->
<string name="permgroupdesc_notifications">show notifications</string>
+ <!-- Title of a category of application permissions, listed so the user can choose whether they want to allow the application to do this. [CHAR LIMIT=40]-->
+ <string name="permgrouplab_xr_tracking">XR tracking data</string>
+ <!-- Description of a category of application permissions, listed so the user can choose whether they want to allow the application to do this. [CHAR LIMIT=NONE]-->
+ <string name="permgroupdesc_xr_tracking">access XR data about you and the environment around you</string>
+
+ <!-- Title of a category of application permissions, listed so the user can choose whether they want to allow the application to do this. [CHAR LIMIT=40]-->
+ <string name="permgrouplab_xr_tracking_sensitive">sensitive XR tracking data</string>
+ <!-- Description of a category of application permissions, listed so the user can choose whether they want to allow the application to do this. [CHAR LIMIT=NONE]-->
+ <string name="permgroupdesc_xr_tracking_sensitive">access sensitive tracking data, such as eye gaze</string>
+
<!-- Title for the capability of an accessibility service to retrieve window content. -->
<string name="capability_title_canRetrieveWindowContent">Retrieve window content</string>
<!-- Description for the capability of an accessibility service to retrieve window content. -->
@@ -1875,6 +1885,45 @@
<!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permdesc_mediaLocation">Allows the app to read locations from your media collection.</string>
+ <string name="permlab_eye_tracking_coarse">track your approximate eye gaze</string>
+ <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permdesc_eye_tracking_coarse">Allows the app to track your approximate eye gaze.</string>
+
+ <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permlab_eye_tracking_fine">track where you are looking</string>
+ <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permdesc_eye_tracking_fine">Allows the app to access precise eye gaze data.</string>
+
+ <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permlab_face_tracking">track your face</string>
+ <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permdesc_face_tracking">Allows the app to access face tracking data.</string>
+
+ <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permlab_hand_tracking">track your hands</string>
+ <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permdesc_hand_tracking">Allows the app to access hand tracking data.</string>
+
+ <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permlab_head_tracking">track your head</string>
+ <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permdesc_head_tracking">Allows the app to access head tracking data.</string>
+
+ <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permlab_scene_understanding_coarse">understand your immediate environment</string>
+ <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permdesc_scene_understanding_coarse">Allows the app to access tracking data about the environment directly around you.</string>
+
+ <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permlab_scene_understanding_fine">understand your immediate environment at high detail</string>
+ <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permdesc_scene_understanding_fine">Allows the app to access tracking data about the environment directly around you with very high detail.</string>
+
+ <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permlab_xr_tracking_in_background">access XR data while not in the foreground</string>
+ <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permdesc_xr_tracking_in_background">Allows the app to access XR data while not in the foreground.</string>
+
<!-- Name for an app setting that lets the user authenticate for that app using biometrics (e.g. fingerprint or face). [CHAR LIMIT=30] -->
<string name="biometric_app_setting_name">Use biometrics</string>
<!-- Name for an app setting that lets the user authenticate for that app using biometrics (e.g. fingerprint or face) or their screen lock credential (i.e. PIN, pattern, or password). [CHAR LIMIT=70] -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index c62732d36038..2c68bd294397 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -575,7 +575,6 @@
<java-symbol type="dimen" name="notification_text_size" />
<java-symbol type="dimen" name="notification_title_text_size" />
<java-symbol type="dimen" name="notification_subtext_size" />
- <java-symbol type="dimen" name="notification_2025_title_text_size" />
<java-symbol type="dimen" name="notification_top_pad" />
<java-symbol type="dimen" name="notification_top_pad_narrow" />
<java-symbol type="dimen" name="notification_top_pad_large_text" />
@@ -1727,10 +1726,12 @@
<java-symbol type="style" name="PointerIconVectorStyleFillBlue" />
<java-symbol type="style" name="PointerIconVectorStyleFillPurple" />
<java-symbol type="attr" name="pointerIconVectorFill" />
+ <java-symbol type="attr" name="pointerIconVectorFillInverse" />
<java-symbol type="style" name="PointerIconVectorStyleStrokeWhite" />
<java-symbol type="style" name="PointerIconVectorStyleStrokeBlack" />
<java-symbol type="style" name="PointerIconVectorStyleStrokeNone" />
<java-symbol type="attr" name="pointerIconVectorStroke" />
+ <java-symbol type="attr" name="pointerIconVectorStrokeInverse" />
<java-symbol type="style" name="TextAppearance.DeviceDefault.Notification.Title" />
<java-symbol type="style" name="TextAppearance.DeviceDefault.Notification.Info" />
@@ -1742,7 +1743,6 @@
<java-symbol type="id" name="media_route_list" />
<java-symbol type="id" name="media_route_volume_layout" />
<java-symbol type="id" name="media_route_volume_slider" />
- <java-symbol type="id" name="media_route_control_frame" />
<java-symbol type="id" name="media_route_extended_settings_button" />
<java-symbol type="id" name="media_route_progress_bar" />
<java-symbol type="string" name="media_route_chooser_title" />
@@ -5904,6 +5904,9 @@
<java-symbol type="drawable" name="ic_notification_summarization" />
<java-symbol type="dimen" name="notification_collapsed_height_with_summarization" />
+ <!-- Device CMF Theming Settings -->
+ <java-symbol type="array" name="theming_defaults" />
+
<!-- Advanced Protection Service USB feature -->
<java-symbol type="string" name="usb_apm_usb_plugged_in_when_locked_notification_title" />
<java-symbol type="string" name="usb_apm_usb_plugged_in_when_locked_notification_text" />
diff --git a/core/tests/coretests/Android.bp b/core/tests/coretests/Android.bp
index c06ad64cc0f5..4c49ff849d49 100644
--- a/core/tests/coretests/Android.bp
+++ b/core/tests/coretests/Android.bp
@@ -10,8 +10,8 @@ package {
filegroup {
name: "FrameworksCoreTests-aidl",
srcs: [
- "src/**/I*.aidl",
"aidl/**/I*.aidl",
+ "src/**/I*.aidl",
],
visibility: ["//visibility:private"],
}
@@ -19,13 +19,13 @@ filegroup {
filegroup {
name: "FrameworksCoreTests-helpers",
srcs: [
- "DisabledTestApp/src/**/*.java",
- "EnabledTestApp/src/**/*.java",
+ "AppThatCallsBinderMethods/src/**/*.kt",
+ "BinderDeathRecipientHelperApp/src/**/*.java",
"BinderFrozenStateChangeCallbackTestApp/src/**/*.java",
"BinderProxyCountingTestApp/src/**/*.java",
"BinderProxyCountingTestService/src/**/*.java",
- "BinderDeathRecipientHelperApp/src/**/*.java",
- "AppThatCallsBinderMethods/src/**/*.kt",
+ "DisabledTestApp/src/**/*.java",
+ "EnabledTestApp/src/**/*.java",
],
visibility: ["//visibility:private"],
}
@@ -45,11 +45,11 @@ android_test {
defaults: ["FrameworksCoreTests-resources"],
srcs: [
- "src/**/*.java",
- "src/**/*.kt",
+ ":FrameworksCoreTestDoubles-sources",
":FrameworksCoreTests-aidl",
":FrameworksCoreTests-helpers",
- ":FrameworksCoreTestDoubles-sources",
+ "src/**/*.java",
+ "src/**/*.kt",
],
aidl: {
@@ -65,74 +65,74 @@ android_test {
"-c fa",
],
static_libs: [
- "collector-device-lib-platform",
- "frameworks-base-testutils",
- "core-test-rules", // for libcore.dalvik.system.CloseGuardSupport
- "core-tests-support",
- "cts-input-lib",
+ "TestParameterInjector",
"android-common",
- "frameworks-core-util-lib",
- "mockwebserver",
- "guava",
- "guava-android-testlib",
"android.app.usage.flags-aconfig-java",
+ "android.content.res.flags-aconfig-java",
+ "android.security.flags-aconfig-java",
"android.view.accessibility.flags-aconfig-java",
"androidx.core_core",
"androidx.core_core-ktx",
"androidx.test.core",
"androidx.test.espresso.core",
"androidx.test.ext.junit",
- "androidx.test.runner",
"androidx.test.rules",
+ "androidx.test.runner",
+ "androidx.test.uiautomator_uiautomator",
+ "collector-device-lib-platform",
+ "com.android.text.flags-aconfig-java",
+ "core-test-rules", // for libcore.dalvik.system.CloseGuardSupport
+ "core-tests-support",
+ "cts-input-lib",
+ "device-time-shell-utils",
"flag-junit",
+ "flag-junit",
+ "flickerlib-parsers",
+ "flickerlib-trace_processor_shell",
+ "frameworks-base-testutils",
+ "frameworks-core-util-lib",
+ "guava",
+ "guava-android-testlib",
"junit-params",
"kotlin-test",
+ "mockito-kotlin2",
+ "mockito-target-extended-minus-junit4",
"mockito-target-minus-junit4",
- "androidx.test.uiautomator_uiautomator",
+ "mockwebserver",
+ "perfetto_trace_java_protos",
+ "platform-compat-test-rules",
"platform-parametric-runner-lib",
"platform-test-annotations",
- "platform-compat-test-rules",
- "truth",
"print-test-util-lib",
- "testng",
+ "ravenwood-junit",
"servicestests-utils",
- "device-time-shell-utils",
"testables",
- "com.android.text.flags-aconfig-java",
- "flag-junit",
- "ravenwood-junit",
- "perfetto_trace_java_protos",
- "flickerlib-parsers",
- "flickerlib-trace_processor_shell",
- "mockito-target-extended-minus-junit4",
- "TestParameterInjector",
- "android.content.res.flags-aconfig-java",
- "android.security.flags-aconfig-java",
- "mockito-kotlin2",
+ "testng",
+ "truth",
],
libs: [
- "android.test.runner.stubs",
- "org.apache.http.legacy.stubs",
"android.test.base.stubs",
"android.test.mock.stubs",
- "framework",
+ "android.test.runner.stubs",
+ "android.view.flags-aconfig-java",
"ext",
+ "framework",
"framework-res",
- "android.view.flags-aconfig-java",
+ "org.apache.http.legacy.stubs",
],
jni_libs: [
+ "libAppOpsTest_jni",
"libpowermanagertest_jni",
"libviewRootImplTest_jni",
"libworksourceparceltest_jni",
- "libAppOpsTest_jni",
],
sdk_version: "core_platform",
test_suites: [
- "device-tests",
- "device-platinum-tests",
"automotive-tests",
+ "device-platinum-tests",
+ "device-tests",
],
certificate: "platform",
@@ -141,21 +141,21 @@ android_test {
java_resources: [":FrameworksCoreTests_unit_test_cert_der"],
data: [
+ ":AppThatCallsBinderMethods",
+ ":AppThatUsesAppOps",
":BinderDeathRecipientHelperApp1",
":BinderDeathRecipientHelperApp2",
- ":com.android.cts.helpers.aosp",
":BinderFrozenStateChangeCallbackTestApp",
":BinderProxyCountingTestApp",
":BinderProxyCountingTestService",
- ":AppThatUsesAppOps",
- ":AppThatCallsBinderMethods",
- ":HelloWorldSdk1",
- ":HelloWorldUsingSdk1AndSdk1",
- ":HelloWorldUsingSdk1And2",
- ":HelloWorldUsingSdkMalformedNegativeVersion",
":CtsStaticSharedLibConsumerApp1",
":CtsStaticSharedLibConsumerApp3",
":CtsStaticSharedLibProviderApp1",
+ ":HelloWorldSdk1",
+ ":HelloWorldUsingSdk1And2",
+ ":HelloWorldUsingSdk1AndSdk1",
+ ":HelloWorldUsingSdkMalformedNegativeVersion",
+ ":com.android.cts.helpers.aosp",
],
}
@@ -170,8 +170,8 @@ android_app {
// FrameworksCoreTestsRavenwood references the .aapt.srcjar
use_resource_processor: false,
libs: [
- "framework-res",
"android.test.runner.stubs",
+ "framework-res",
"org.apache.http.legacy.stubs",
],
uses_libs: [
@@ -231,16 +231,16 @@ android_library {
static_libs: [
"androidx.test.espresso.core",
"androidx.test.ext.junit",
- "androidx.test.runner",
"androidx.test.rules",
+ "androidx.test.runner",
"mockito-target-minus-junit4",
"truth",
],
libs: [
- "android.test.runner.stubs.system",
"android.test.base.stubs.system",
"android.test.mock.stubs.system",
+ "android.test.runner.stubs.system",
"framework",
"framework-res",
],
@@ -249,43 +249,43 @@ android_library {
android_ravenwood_test {
name: "FrameworksCoreTestsRavenwood",
libs: [
- "android.test.runner.stubs.system",
"android.test.base.stubs.system",
+ "android.test.runner.stubs.system",
],
static_libs: [
- "core-test-rules", // for libcore.dalvik.system.CloseGuardSupport
+ "androidx.annotation_annotation",
"androidx.core_core",
"androidx.core_core-ktx",
- "androidx.annotation_annotation",
- "androidx.test.rules",
"androidx.test.ext.junit",
+ "androidx.test.rules",
"androidx.test.uiautomator_uiautomator",
"compatibility-device-util-axt-ravenwood",
+ "core-test-rules", // for libcore.dalvik.system.CloseGuardSupport
"flag-junit",
- "platform-test-annotations",
- "perfetto_trace_java_protos",
"flag-junit",
+ "perfetto_trace_java_protos",
+ "platform-test-annotations",
"testng",
],
srcs: [
"src/android/app/ActivityManagerTest.java",
+ "src/android/app/PropertyInvalidatedCacheTests.java",
"src/android/colormodel/CamTest.java",
"src/android/content/ContextTest.java",
+ "src/android/content/TestComponentCallbacks2.java",
"src/android/content/pm/PackageManagerTest.java",
"src/android/content/pm/UserInfoTest.java",
- "src/android/app/PropertyInvalidatedCacheTests.java",
- "src/android/database/CursorWindowTest.java",
- "src/android/os/**/*.java",
"src/android/content/res/*.java",
"src/android/content/res/*.kt",
+ "src/android/database/CursorWindowTest.java",
+ "src/android/os/**/*.java",
"src/android/telephony/PinResultTest.java",
"src/android/util/**/*.java",
"src/android/view/DisplayAdjustmentsTests.java",
- "src/android/view/DisplayTest.java",
"src/android/view/DisplayInfoTest.java",
+ "src/android/view/DisplayTest.java",
"src/com/android/internal/logging/**/*.java",
"src/com/android/internal/os/**/*.java",
- "src/com/android/internal/util/**/*.java",
"src/com/android/internal/power/EnergyConsumerStatsTest.java",
"src/com/android/internal/ravenwood/**/*.java",
@@ -293,10 +293,12 @@ android_ravenwood_test {
// to avoid having a dependency to FrameworksCoreTests.
// This way, when updating source files and running this test, we don't need to
// rebuild the entire FrameworksCoreTests, which would be slow.
- ":FrameworksCoreTests-resonly{.aapt.srcjar}",
+ "src/com/android/internal/util/**/*.java",
+
+ ":FrameworksCoreTestDoubles-sources",
":FrameworksCoreTests-aidl",
":FrameworksCoreTests-helpers",
- ":FrameworksCoreTestDoubles-sources",
+ ":FrameworksCoreTests-resonly{.aapt.srcjar}",
],
exclude_srcs: [
"src/android/content/res/FontScaleConverterActivityTest.java",
@@ -320,8 +322,8 @@ test_module_config {
base: "FrameworksCoreTests",
test_suites: [
"automotive-tests",
- "device-tests",
"device-platinum-tests",
+ "device-tests",
],
include_annotations: ["android.platform.test.annotations.Presubmit"],
}
@@ -331,12 +333,12 @@ test_module_config {
base: "FrameworksCoreTests",
test_suites: [
"automotive-tests",
- "device-tests",
"device-platinum-tests",
+ "device-tests",
],
include_filters: [
- "com.android.internal.inputmethod",
"android.view.inputmethod",
+ "com.android.internal.inputmethod",
],
exclude_annotations: ["androidx.test.filters.FlakyTest"],
}
@@ -346,8 +348,8 @@ test_module_config {
base: "FrameworksCoreTests",
test_suites: [
"automotive-tests",
- "device-tests",
"device-platinum-tests",
+ "device-tests",
],
include_filters: ["android.content.ContextTest"],
}
@@ -357,8 +359,8 @@ test_module_config {
base: "FrameworksCoreTests",
test_suites: [
"automotive-tests",
- "device-tests",
"device-platinum-tests",
+ "device-tests",
],
include_filters: ["android.app.KeyguardManagerTest"],
}
@@ -368,8 +370,8 @@ test_module_config {
base: "FrameworksCoreTests",
test_suites: [
"automotive-tests",
- "device-tests",
"device-platinum-tests",
+ "device-tests",
],
include_filters: ["android.app.PropertyInvalidatedCacheTests"],
}
@@ -379,12 +381,12 @@ test_module_config {
base: "FrameworksCoreTests",
test_suites: [
"automotive-tests",
- "device-tests",
"device-platinum-tests",
+ "device-tests",
],
include_filters: [
- "android.content.ContextTest",
"android.content.ComponentCallbacksControllerTest",
+ "android.content.ContextTest",
"android.content.ContextWrapperTest",
],
}
@@ -394,8 +396,8 @@ test_module_config {
base: "FrameworksCoreTests",
test_suites: [
"automotive-tests",
- "device-tests",
"device-platinum-tests",
+ "device-tests",
],
include_filters: ["android.database.sqlite.SQLiteRawStatementTest"],
}
@@ -405,8 +407,8 @@ test_module_config {
base: "FrameworksCoreTests",
test_suites: [
"automotive-tests",
- "device-tests",
"device-platinum-tests",
+ "device-tests",
],
include_filters: ["android.net"],
include_annotations: ["android.platform.test.annotations.Presubmit"],
@@ -417,8 +419,8 @@ test_module_config {
base: "FrameworksCoreTests",
test_suites: [
"automotive-tests",
- "device-tests",
"device-platinum-tests",
+ "device-tests",
],
include_filters: ["com.android.internal.os.BatteryStatsTests"],
exclude_annotations: ["com.android.internal.os.SkipPresubmit"],
@@ -429,8 +431,8 @@ test_module_config {
base: "FrameworksCoreTests",
test_suites: [
"automotive-tests",
- "device-tests",
"device-platinum-tests",
+ "device-tests",
],
include_filters: ["android.os.EnvironmentTest"],
}
@@ -440,12 +442,12 @@ test_module_config {
base: "FrameworksCoreTests",
test_suites: [
"automotive-tests",
- "device-tests",
"device-platinum-tests",
+ "device-tests",
],
include_filters: [
- "com.android.internal.util.FastDataTest",
"android.util.CharsetUtilsTest",
+ "com.android.internal.util.FastDataTest",
],
}
@@ -454,12 +456,12 @@ test_module_config {
base: "FrameworksCoreTests",
test_suites: [
"automotive-tests",
- "device-tests",
"device-platinum-tests",
+ "device-tests",
],
include_filters: [
- "android.util.XmlTest",
"android.util.BinaryXmlTest",
+ "android.util.XmlTest",
],
}
@@ -468,8 +470,8 @@ test_module_config {
base: "FrameworksCoreTests",
test_suites: [
"automotive-tests",
- "device-tests",
"device-platinum-tests",
+ "device-tests",
],
include_filters: ["android.util.apk.SourceStampVerifierTest"],
}
@@ -479,8 +481,8 @@ test_module_config {
base: "FrameworksCoreTests",
test_suites: [
"automotive-tests",
- "device-tests",
"device-platinum-tests",
+ "device-tests",
],
include_filters: ["android.view.textclassifier"],
exclude_annotations: ["androidx.test.filters.FlakyTest"],
@@ -491,13 +493,13 @@ test_module_config {
base: "FrameworksCoreTests",
test_suites: [
"automotive-tests",
- "device-tests",
"device-platinum-tests",
+ "device-tests",
],
include_filters: ["com.android.internal.app."],
exclude_filters: [
- "com.android.internal.app.WindowDecorActionBarTest",
"com.android.internal.app.IntentForwarderActivityTest",
+ "com.android.internal.app.WindowDecorActionBarTest",
],
}
@@ -506,8 +508,8 @@ test_module_config {
base: "FrameworksCoreTests",
test_suites: [
"automotive-tests",
- "device-tests",
"device-platinum-tests",
+ "device-tests",
],
include_filters: ["com.android.internal.content."],
}
@@ -517,8 +519,8 @@ test_module_config {
base: "FrameworksCoreTests",
test_suites: [
"automotive-tests",
- "device-tests",
"device-platinum-tests",
+ "device-tests",
],
include_filters: ["com.android.internal.infra."],
}
@@ -528,8 +530,8 @@ test_module_config {
base: "FrameworksCoreTests",
test_suites: [
"automotive-tests",
- "device-tests",
"device-platinum-tests",
+ "device-tests",
],
include_filters: ["com.android.internal.jank"],
}
@@ -539,16 +541,16 @@ test_module_config {
base: "FrameworksCoreTests",
test_suites: [
"automotive-tests",
- "device-tests",
"device-platinum-tests",
+ "device-tests",
],
include_filters: [
- "android.os.BinderProxyTest",
"android.os.BinderDeathRecipientTest",
"android.os.BinderFrozenStateChangeNotificationTest",
"android.os.BinderProxyCountingTest",
- "android.os.BinderUncaughtExceptionHandlerTest",
+ "android.os.BinderProxyTest",
"android.os.BinderThreadPriorityTest",
+ "android.os.BinderUncaughtExceptionHandlerTest",
"android.os.BinderWorkSourceTest",
"android.os.ParcelNullabilityTest",
"android.os.ParcelTest",
@@ -562,13 +564,13 @@ test_module_config {
base: "FrameworksCoreTests",
test_suites: [
"automotive-tests",
- "device-tests",
"device-platinum-tests",
+ "device-tests",
],
include_filters: [
- "com.android.internal.os.KernelCpuUidClusterTimeReaderTest",
- "com.android.internal.os.KernelCpuUidBpfMapReaderTest",
"com.android.internal.os.KernelCpuUidActiveTimeReaderTest",
+ "com.android.internal.os.KernelCpuUidBpfMapReaderTest",
+ "com.android.internal.os.KernelCpuUidClusterTimeReaderTest",
"com.android.internal.os.KernelCpuUidFreqTimeReaderTest",
"com.android.internal.os.KernelSingleUidTimeReaderTest",
],
@@ -579,8 +581,8 @@ test_module_config {
base: "FrameworksCoreTests",
test_suites: [
"automotive-tests",
- "device-tests",
"device-platinum-tests",
+ "device-tests",
],
include_filters: ["com.android.server.power.stats.BstatsCpuTimesValidationTest"],
}
@@ -590,8 +592,8 @@ test_module_config {
base: "FrameworksCoreTests",
test_suites: [
"automotive-tests",
- "device-tests",
"device-platinum-tests",
+ "device-tests",
],
include_filters: ["com.android.internal.security."],
include_annotations: ["android.platform.test.annotations.Presubmit"],
@@ -602,8 +604,8 @@ test_module_config {
base: "FrameworksCoreTests",
test_suites: [
"automotive-tests",
- "device-tests",
"device-platinum-tests",
+ "device-tests",
],
include_filters: ["com.android.internal.util.LatencyTrackerTest"],
}
@@ -613,8 +615,8 @@ test_module_config {
base: "FrameworksCoreTests",
test_suites: [
"automotive-tests",
- "device-tests",
"device-platinum-tests",
+ "device-tests",
],
include_filters: ["android.content.ContentCaptureOptionsTest"],
}
@@ -624,8 +626,8 @@ test_module_config {
base: "FrameworksCoreTests",
test_suites: [
"automotive-tests",
- "device-tests",
"device-platinum-tests",
+ "device-tests",
],
include_filters: ["android.content.integrity."],
}
@@ -635,8 +637,8 @@ test_module_config {
base: "FrameworksCoreTests",
test_suites: [
"automotive-tests",
- "device-tests",
"device-platinum-tests",
+ "device-tests",
],
include_filters: ["android.content.pm."],
include_annotations: ["android.platform.test.annotations.Presubmit"],
@@ -647,8 +649,8 @@ test_module_config {
base: "FrameworksCoreTests",
test_suites: [
"automotive-tests",
- "device-tests",
"device-platinum-tests",
+ "device-tests",
],
include_filters: ["android.content.pm."],
include_annotations: ["android.platform.test.annotations.Postsubmit"],
@@ -659,14 +661,14 @@ test_module_config {
base: "FrameworksCoreTests",
test_suites: [
"automotive-tests",
- "device-tests",
"device-platinum-tests",
+ "device-tests",
],
include_filters: ["android.content.res."],
include_annotations: ["android.platform.test.annotations.Presubmit"],
exclude_annotations: [
- "androidx.test.filters.FlakyTest",
"android.platform.test.annotations.Postsubmit",
+ "androidx.test.filters.FlakyTest",
"org.junit.Ignore",
],
}
@@ -676,8 +678,8 @@ test_module_config {
base: "FrameworksCoreTests",
test_suites: [
"automotive-tests",
- "device-tests",
"device-platinum-tests",
+ "device-tests",
],
include_filters: ["android.content.res."],
include_annotations: ["android.platform.test.annotations.Postsubmit"],
@@ -688,17 +690,17 @@ test_module_config {
base: "FrameworksCoreTests",
test_suites: [
"automotive-tests",
- "device-tests",
"device-platinum-tests",
+ "device-tests",
],
include_filters: [
+ "android.service.controls",
+ "android.service.controls.actions",
+ "android.service.controls.templates",
"android.service.euicc",
"android.service.notification",
"android.service.quicksettings",
"android.service.settings.suggestions",
- "android.service.controls.templates",
- "android.service.controls.actions",
- "android.service.controls",
],
exclude_annotations: ["org.junit.Ignore"],
}
@@ -708,8 +710,8 @@ test_module_config {
base: "FrameworksCoreTests",
test_suites: [
"automotive-tests",
- "device-tests",
"device-platinum-tests",
+ "device-tests",
],
include_filters: ["android.view.contentcapture"],
}
@@ -719,8 +721,8 @@ test_module_config {
base: "FrameworksCoreTests",
test_suites: [
"automotive-tests",
- "device-tests",
"device-platinum-tests",
+ "device-tests",
],
include_filters: ["android.view.contentprotection"],
}
@@ -730,8 +732,8 @@ test_module_config {
base: "FrameworksCoreTests",
test_suites: [
"automotive-tests",
- "device-tests",
"device-platinum-tests",
+ "device-tests",
],
include_filters: ["com.android.internal.content."],
include_annotations: ["android.platform.test.annotations.Presubmit"],
@@ -742,8 +744,8 @@ test_module_config {
base: "FrameworksCoreTests",
test_suites: [
"automotive-tests",
- "device-tests",
"device-platinum-tests",
+ "device-tests",
],
include_filters: ["android.graphics.drawable.IconTest"],
}
@@ -753,13 +755,13 @@ test_module_config {
base: "FrameworksCoreTests",
test_suites: [
"automotive-tests",
- "device-tests",
"device-platinum-tests",
+ "device-tests",
],
include_filters: [
- "com.android.internal.accessibility",
"android.accessibilityservice",
"android.view.accessibility",
+ "com.android.internal.accessibility",
],
}
@@ -768,8 +770,8 @@ test_module_config {
base: "FrameworksCoreTests",
test_suites: [
"automotive-tests",
- "device-tests",
"device-platinum-tests",
+ "device-tests",
],
include_filters: ["android.app.usage"],
}
@@ -779,8 +781,8 @@ test_module_config {
base: "FrameworksCoreTests",
test_suites: [
"automotive-tests",
- "device-tests",
"device-platinum-tests",
+ "device-tests",
],
include_filters: ["com.android.internal.util.FastDataTest"],
}
@@ -790,8 +792,8 @@ test_module_config {
base: "FrameworksCoreTests",
test_suites: [
"automotive-tests",
- "device-tests",
"device-platinum-tests",
+ "device-tests",
],
include_filters: ["android.hardware.input"],
}
@@ -801,12 +803,12 @@ test_module_config {
base: "FrameworksCoreTests",
test_suites: [
"automotive-tests",
- "device-tests",
"device-platinum-tests",
+ "device-tests",
],
include_filters: [
- "android.view.VerifiedMotionEventTest",
"android.view.VerifiedKeyEventTest",
+ "android.view.VerifiedMotionEventTest",
],
}
@@ -839,8 +841,8 @@ test_module_config {
base: "FrameworksCoreTests",
test_suites: [
"automotive-tests",
- "device-tests",
"device-platinum-tests",
+ "device-tests",
],
include_filters: [
"com.android.internal.jank.FrameTrackerTest",
@@ -854,8 +856,8 @@ test_module_config {
base: "FrameworksCoreTests",
test_suites: [
"automotive-tests",
- "device-tests",
"device-platinum-tests",
+ "device-tests",
],
include_annotations: ["android.platform.test.annotations.PlatinumTest"],
}
diff --git a/core/tests/coretests/src/android/app/AutomaticZenRuleTest.java b/core/tests/coretests/src/android/app/AutomaticZenRuleTest.java
index 5765562e2383..6fe3b6ca0c6c 100644
--- a/core/tests/coretests/src/android/app/AutomaticZenRuleTest.java
+++ b/core/tests/coretests/src/android/app/AutomaticZenRuleTest.java
@@ -26,7 +26,6 @@ import static org.junit.Assert.assertThrows;
import android.content.ComponentName;
import android.net.Uri;
import android.os.Parcel;
-import android.platform.test.annotations.EnableFlags;
import android.platform.test.annotations.Presubmit;
import android.platform.test.flag.junit.SetFlagsRule;
@@ -112,7 +111,6 @@ public class AutomaticZenRuleTest {
}
@Test
- @EnableFlags(Flags.FLAG_MODES_API)
public void testLongInputsFromParcel() {
// Create a rule with long fields, set directly via reflection so that we can confirm that
// a rule with too-long fields that comes in via a parcel has its fields truncated directly.
@@ -169,7 +167,6 @@ public class AutomaticZenRuleTest {
}
@Test
- @EnableFlags(Flags.FLAG_MODES_API)
public void builderConstructor_nullInputs_throws() {
assertThrows(NullPointerException.class,
() -> new AutomaticZenRule.Builder(null, Uri.parse("condition")));
@@ -178,7 +175,6 @@ public class AutomaticZenRuleTest {
}
@Test
- @EnableFlags(Flags.FLAG_MODES_API)
public void constructor_defaultTypeUnknown() {
AutomaticZenRule rule = new AutomaticZenRule("name", new ComponentName("pkg", "cps"), null,
Uri.parse("conditionId"), null, NotificationManager.INTERRUPTION_FILTER_PRIORITY,
@@ -188,7 +184,6 @@ public class AutomaticZenRuleTest {
}
@Test
- @EnableFlags(Flags.FLAG_MODES_API)
public void builder_defaultsAreSensible() {
AutomaticZenRule rule = new AutomaticZenRule.Builder("name",
Uri.parse("conditionId")).build();
@@ -200,7 +195,6 @@ public class AutomaticZenRuleTest {
}
@Test
- @EnableFlags(Flags.FLAG_MODES_API)
public void validate_builderWithValidType_succeeds() throws Exception {
AutomaticZenRule rule = new AutomaticZenRule.Builder("rule", Uri.parse("uri"))
.setType(AutomaticZenRule.TYPE_BEDTIME)
@@ -209,14 +203,12 @@ public class AutomaticZenRuleTest {
}
@Test
- @EnableFlags(Flags.FLAG_MODES_API)
public void validate_builderWithoutType_succeeds() throws Exception {
AutomaticZenRule rule = new AutomaticZenRule.Builder("rule", Uri.parse("uri")).build();
rule.validate(); // No exception.
}
@Test
- @EnableFlags(Flags.FLAG_MODES_API)
public void validate_constructorWithoutType_succeeds() throws Exception {
AutomaticZenRule rule = new AutomaticZenRule("rule", new ComponentName("pkg", "cps"),
new ComponentName("pkg", "activity"), Uri.parse("condition"), null,
@@ -225,7 +217,6 @@ public class AutomaticZenRuleTest {
}
@Test
- @EnableFlags(Flags.FLAG_MODES_API)
public void validate_invalidType_throws() throws Exception {
AutomaticZenRule rule = new AutomaticZenRule.Builder("rule", Uri.parse("uri")).build();
@@ -238,7 +229,6 @@ public class AutomaticZenRuleTest {
}
@Test
- @EnableFlags(Flags.FLAG_MODES_API)
public void setType_invalidType_throws() {
AutomaticZenRule rule = new AutomaticZenRule.Builder("rule", Uri.parse("uri")).build();
@@ -246,7 +236,6 @@ public class AutomaticZenRuleTest {
}
@Test
- @EnableFlags(Flags.FLAG_MODES_API)
public void setTypeBuilder_invalidType_throws() {
AutomaticZenRule.Builder builder = new AutomaticZenRule.Builder("rule", Uri.parse("uri"));
diff --git a/core/tests/coretests/src/android/app/NotificationManagerTest.java b/core/tests/coretests/src/android/app/NotificationManagerTest.java
index d816039d0d3c..250b9ce8d89d 100644
--- a/core/tests/coretests/src/android/app/NotificationManagerTest.java
+++ b/core/tests/coretests/src/android/app/NotificationManagerTest.java
@@ -521,7 +521,7 @@ public class NotificationManagerTest {
}
@Test
- @EnableFlags({Flags.FLAG_MODES_API, Flags.FLAG_MODES_UI})
+ @EnableFlags(Flags.FLAG_MODES_UI)
public void areAutomaticZenRulesUserManaged_handheld_isTrue() {
PackageManager pm = mock(PackageManager.class);
when(pm.hasSystemFeature(any())).thenReturn(false);
@@ -531,7 +531,7 @@ public class NotificationManagerTest {
}
@Test
- @EnableFlags({Flags.FLAG_MODES_API, Flags.FLAG_MODES_UI})
+ @EnableFlags(Flags.FLAG_MODES_UI)
public void areAutomaticZenRulesUserManaged_auto_isFalse() {
PackageManager pm = mock(PackageManager.class);
when(pm.hasSystemFeature(eq(PackageManager.FEATURE_AUTOMOTIVE))).thenReturn(true);
@@ -541,7 +541,7 @@ public class NotificationManagerTest {
}
@Test
- @EnableFlags({Flags.FLAG_MODES_API, Flags.FLAG_MODES_UI})
+ @EnableFlags(Flags.FLAG_MODES_UI)
public void areAutomaticZenRulesUserManaged_tv_isFalse() {
PackageManager pm = mock(PackageManager.class);
when(pm.hasSystemFeature(eq(PackageManager.FEATURE_LEANBACK))).thenReturn(true);
@@ -551,7 +551,7 @@ public class NotificationManagerTest {
}
@Test
- @EnableFlags({Flags.FLAG_MODES_API, Flags.FLAG_MODES_UI})
+ @EnableFlags(Flags.FLAG_MODES_UI)
public void areAutomaticZenRulesUserManaged_watch_isFalse() {
PackageManager pm = mock(PackageManager.class);
when(pm.hasSystemFeature(eq(PackageManager.FEATURE_WATCH))).thenReturn(true);
diff --git a/core/tests/coretests/src/android/app/backup/BackupRestoreEventLoggerTest.java b/core/tests/coretests/src/android/app/backup/BackupRestoreEventLoggerTest.java
index 7b41217d6200..ab2e77e57b47 100644
--- a/core/tests/coretests/src/android/app/backup/BackupRestoreEventLoggerTest.java
+++ b/core/tests/coretests/src/android/app/backup/BackupRestoreEventLoggerTest.java
@@ -23,6 +23,8 @@ import static com.google.common.truth.Truth.assertThat;
import static junit.framework.Assert.fail;
+import static org.junit.Assert.assertThrows;
+
import android.app.backup.BackupRestoreEventLogger.DataTypeResult;
import android.os.Parcel;
import android.platform.test.annotations.Presubmit;
@@ -32,6 +34,8 @@ import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.android.server.backup.Flags;
+import com.google.common.truth.Expect;
+
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -42,6 +46,7 @@ import java.security.MessageDigest;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
+import java.util.Map;
import java.util.Optional;
@Presubmit
@@ -64,6 +69,9 @@ public class BackupRestoreEventLoggerTest {
@Rule
public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+ @Rule
+ public final Expect expect = Expect.create();
+
@Before
public void setUp() throws Exception {
mHashDigest = MessageDigest.getInstance("SHA-256");
@@ -366,6 +374,32 @@ public class BackupRestoreEventLoggerTest {
assertThat(mLogger.getLoggingResults()).isEmpty();
}
+ @Test
+ public void testDataTypeResultToString_nullArgs() {
+ assertThrows(NullPointerException.class, () -> BackupRestoreEventLogger.toString(null));
+ }
+
+ @Test
+ public void testDataTypeResultToString_typeOnly() {
+ DataTypeResult result = new DataTypeResult("The Type is Bond, James Bond!");
+
+ expect.withMessage("toString()")
+ .that(BackupRestoreEventLogger.toString(result)).isEqualTo(
+ "type=The Type is Bond, James Bond!, successCount=0, failCount=0");
+ }
+
+ @Test
+ public void testDataTypeResultToString_allFields() {
+ DataTypeResult result = DataTypeResultTest.createDataTypeResult(
+ "The Type is Bond, James Bond!", /* successCount= */ 42, /* failCount= */ 108,
+ Map.of("D'OH!", 666, "", 0), new byte[] { 4, 8, 15, 16, 23, 42 });
+
+ expect.withMessage("toString()")
+ .that(BackupRestoreEventLogger.toString(result)).isEqualTo(
+ "type=The Type is Bond, James Bond!, successCount=42, failCount=108, "
+ + "errors={=0, D'OH!=666}, metadataHash=[4, 8, 15, 16, 23, 42]");
+ }
+
private static DataTypeResult getResultForDataType(
BackupRestoreEventLogger logger, String dataType) {
Optional<DataTypeResult> result = getResultForDataTypeIfPresent(logger, dataType);
diff --git a/core/tests/coretests/src/android/app/backup/DataTypeResultTest.java b/core/tests/coretests/src/android/app/backup/DataTypeResultTest.java
new file mode 100644
index 000000000000..cf9e9c6e2f95
--- /dev/null
+++ b/core/tests/coretests/src/android/app/backup/DataTypeResultTest.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.app.backup;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.app.backup.BackupRestoreEventLogger.DataTypeResult;
+import android.os.Bundle;
+import android.os.Parcel;
+
+import com.google.common.truth.Expect;
+
+import org.junit.Rule;
+import org.junit.Test;
+
+import java.util.Map;
+
+public final class DataTypeResultTest {
+
+ @Rule
+ public final Expect expect = Expect.create();
+
+ @Test
+ public void testGetters_defaultConstructorFields() {
+ var result = new DataTypeResult("The Type is Bond, James Bond!");
+
+ expect.withMessage("getDataType()").that(result.getDataType())
+ .isEqualTo("The Type is Bond, James Bond!");
+ expect.withMessage("getSuccessCount()").that(result.getSuccessCount()).isEqualTo(0);
+ expect.withMessage("getFailCount()").that(result.getFailCount()).isEqualTo(0);
+ expect.withMessage("getErrorsCount()").that(result.getErrors()).isEmpty();
+ expect.withMessage("getMetadataHash()").that(result.getMetadataHash()).isNull();
+ expect.withMessage("describeContents()").that(result.describeContents()).isEqualTo(0);
+ }
+
+ @Test
+ public void testGetters_allFields() {
+ DataTypeResult result = createDataTypeResult("The Type is Bond, James Bond!",
+ /* successCount= */ 42, /* failCount= */ 108, Map.of("D'OH!", 666),
+ new byte[] { 4, 8, 15, 16, 23, 42 });
+
+ expect.withMessage("getDataType()").that(result.getDataType())
+ .isEqualTo("The Type is Bond, James Bond!");
+ expect.withMessage("getSuccessCount()").that(result.getSuccessCount()).isEqualTo(42);
+ expect.withMessage("getFailCount()").that(result.getFailCount()).isEqualTo(108);
+ expect.withMessage("getErrorsCount()").that(result.getErrors()).containsExactly("D'OH!",
+ 666);
+ expect.withMessage("getMetadataHash()").that(result.getMetadataHash()).asList()
+ .containsExactly((byte) 4, (byte) 8, (byte) 15, (byte) 16, (byte) 23, (byte) 42)
+ .inOrder();
+ expect.withMessage("describeContents()").that(result.describeContents()).isEqualTo(0);
+ }
+
+ @Test
+ public void testParcelMethods() {
+ DataTypeResult original = createDataTypeResult("The Type is Bond, James Bond!",
+ /* successCount= */ 42, /* failCount= */ 108, Map.of("D'OH!", 666),
+ new byte[] { 4, 8, 15, 16, 23, 42 });
+ Parcel parcel = Parcel.obtain();
+ try {
+ original.writeToParcel(parcel, /* flags= */ 0);
+
+ parcel.setDataPosition(0);
+ var clone = DataTypeResult.CREATOR.createFromParcel(parcel);
+ assertWithMessage("createFromParcel()").that(clone).isNotNull();
+
+ expect.withMessage("getDataType()").that(clone.getDataType())
+ .isEqualTo(original.getDataType());
+ expect.withMessage("getSuccessCount()").that(clone.getSuccessCount())
+ .isEqualTo(original.getSuccessCount());
+ expect.withMessage("getFailCount()").that(clone.getFailCount())
+ .isEqualTo(original.getFailCount());
+ expect.withMessage("getErrorsCount()").that(clone.getErrors())
+ .containsExactlyEntriesIn(original.getErrors()).inOrder();
+ expect.withMessage("getMetadataHash()").that(clone.getMetadataHash())
+ .isEqualTo(original.getMetadataHash());
+ expect.withMessage("describeContents()").that(clone.describeContents()).isEqualTo(0);
+ } finally {
+ parcel.recycle();
+ }
+ }
+
+ static DataTypeResult createDataTypeResult(String dataType, int successCount, int failCount,
+ Map<String, Integer> errors, byte... metadataHash) {
+ Parcel parcel = Parcel.obtain();
+ try {
+ parcel.writeString(dataType);
+ parcel.writeInt(successCount);
+ parcel.writeInt(failCount);
+ Bundle errorsBundle = new Bundle();
+ errors.entrySet()
+ .forEach(entry -> errorsBundle.putInt(entry.getKey(), entry.getValue()));
+ parcel.writeBundle(errorsBundle);
+ parcel.writeByteArray(metadataHash);
+
+ parcel.setDataPosition(0);
+ var result = DataTypeResult.CREATOR.createFromParcel(parcel);
+ assertWithMessage("createFromParcel()").that(result).isNotNull();
+ return result;
+ } finally {
+ parcel.recycle();
+ }
+ }
+}
diff --git a/core/tests/coretests/src/android/content/ContextTest.java b/core/tests/coretests/src/android/content/ContextTest.java
index a02af788d496..2505500411b5 100644
--- a/core/tests/coretests/src/android/content/ContextTest.java
+++ b/core/tests/coretests/src/android/content/ContextTest.java
@@ -23,6 +23,7 @@ import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -35,17 +36,24 @@ import android.graphics.PixelFormat;
import android.hardware.display.DisplayManager;
import android.hardware.display.VirtualDisplay;
import android.media.ImageReader;
+import android.os.Looper;
import android.os.UserHandle;
+import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.DisabledOnRavenwood;
+import android.platform.test.annotations.EnableFlags;
import android.platform.test.annotations.Presubmit;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.platform.test.ravenwood.RavenwoodRule;
import android.view.Display;
+import android.window.WindowTokenClient;
import androidx.test.core.app.ApplicationProvider;
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.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -61,6 +69,9 @@ public class ContextTest {
@Rule
public final RavenwoodRule mRavenwood = new RavenwoodRule.Builder().build();
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
@Test
public void testInstrumentationContext() {
// Confirm that we have a valid Context
@@ -280,4 +291,44 @@ public class ContextTest {
return appContext.createDisplayContext(display)
.createWindowContext(TYPE_APPLICATION_OVERLAY, null /* options */);
}
+
+ @Test
+ @DisabledOnRavenwood(blockedBy = Context.class)
+ @DisableFlags(Flags.FLAG_TRACK_SYSTEM_UI_CONTEXT_BEFORE_WMS)
+ public void testSysUiContextRegisterComponentCallbacks_disableFlag() {
+ Looper.prepare();
+
+ // Use createSystemActivityThreadForTesting to initialize
+ // systemUiContext#getApplicationContext.
+ final Context systemUiContext = ActivityThread.createSystemActivityThreadForTesting()
+ .getSystemUiContext();
+ final TestComponentCallbacks2 callbacks = new TestComponentCallbacks2();
+ systemUiContext.registerComponentCallbacks(callbacks);
+
+ final WindowTokenClient windowTokenClient =
+ (WindowTokenClient) systemUiContext.getWindowContextToken();
+ windowTokenClient.onConfigurationChanged(Configuration.EMPTY, DEFAULT_DISPLAY);
+
+ assertWithMessage("ComponentCallbacks should delegate to the app Context "
+ + "if the flag is disabled.").that(callbacks.mConfiguration).isNull();
+ }
+
+ @Test
+ @DisabledOnRavenwood(blockedBy = Context.class)
+ @EnableFlags(Flags.FLAG_TRACK_SYSTEM_UI_CONTEXT_BEFORE_WMS)
+ public void testSysUiContextRegisterComponentCallbacks_enableFlag() {
+ final Context systemUiContext = ActivityThread.currentActivityThread()
+ .createSystemUiContextForTesting(DEFAULT_DISPLAY);
+ final TestComponentCallbacks2 callbacks = new TestComponentCallbacks2();
+ final Configuration config = Configuration.EMPTY;
+
+ systemUiContext.registerComponentCallbacks(callbacks);
+
+ final WindowTokenClient windowTokenClient =
+ (WindowTokenClient) systemUiContext.getWindowContextToken();
+ windowTokenClient.onConfigurationChanged(config, DEFAULT_DISPLAY);
+
+ assertWithMessage("ComponentCallbacks should delegate to SystemUiContext "
+ + "if the flag is enabled.").that(callbacks.mConfiguration).isEqualTo(config);
+ }
}
diff --git a/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java b/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java
index dc2f0a69375d..9383807ec761 100644
--- a/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java
+++ b/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java
@@ -33,6 +33,7 @@ import android.content.Context;
import android.os.Handler;
import android.os.RemoteException;
import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresFlagsDisabled;
import android.platform.test.annotations.RequiresFlagsEnabled;
import android.platform.test.flag.junit.CheckFlagsRule;
import android.platform.test.flag.junit.DeviceFlagsValueProvider;
@@ -348,6 +349,26 @@ public class DisplayManagerGlobalTest {
DisplayManager.PRIVATE_EVENT_TYPE_DISPLAY_BRIGHTNESS));
}
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_COMMITTED_STATE_SEPARATE_EVENT)
+ public void test_mapPrivateEventCommittedStateChanged_flagEnabled() {
+ // Test public flags mapping
+ assertEquals(DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_COMMITTED_STATE_CHANGED,
+ mDisplayManagerGlobal
+ .mapFiltersToInternalEventFlag(0,
+ DisplayManager.PRIVATE_EVENT_TYPE_DISPLAY_COMMITTED_STATE_CHANGED));
+ }
+
+ @Test
+ @RequiresFlagsDisabled(Flags.FLAG_COMMITTED_STATE_SEPARATE_EVENT)
+ public void test_mapPrivateEventCommittedStateChanged_flagDisabled() {
+ // Test public flags mapping
+ assertEquals(0,
+ mDisplayManagerGlobal
+ .mapFiltersToInternalEventFlag(0,
+ DisplayManager.PRIVATE_EVENT_TYPE_DISPLAY_COMMITTED_STATE_CHANGED));
+ }
+
private void waitForHandler() {
mHandler.runWithScissors(() -> {
}, 0);
diff --git a/core/tests/coretests/src/android/service/notification/ConditionTest.java b/core/tests/coretests/src/android/service/notification/ConditionTest.java
index e94273e1ada7..65c108a827ef 100644
--- a/core/tests/coretests/src/android/service/notification/ConditionTest.java
+++ b/core/tests/coretests/src/android/service/notification/ConditionTest.java
@@ -23,10 +23,8 @@ import static junit.framework.Assert.fail;
import static org.junit.Assert.assertThrows;
-import android.app.Flags;
import android.net.Uri;
import android.os.Parcel;
-import android.platform.test.annotations.EnableFlags;
import android.platform.test.flag.junit.SetFlagsRule;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -113,7 +111,6 @@ public class ConditionTest {
@Test
public void testLongFields_inConstructors() {
- mSetFlagsRule.enableFlags(Flags.FLAG_MODES_API);
String longString = Strings.repeat("A", 65536);
Uri longUri = Uri.parse("uri://" + Strings.repeat("A", 65530));
@@ -136,7 +133,6 @@ public class ConditionTest {
@Test
public void testLongFields_viaParcel() throws Exception {
- mSetFlagsRule.enableFlags(Flags.FLAG_MODES_API);
// Set fields via reflection to force them to be long, then parcel and unparcel to make sure
// it gets truncated upon unparcelling.
Condition cond = new Condition(Uri.parse("uri://placeholder"), "placeholder",
@@ -170,8 +166,6 @@ public class ConditionTest {
@Test
public void testEquals() {
- mSetFlagsRule.enableFlags(Flags.FLAG_MODES_API);
-
Condition cond1 = new Condition(Uri.parse("uri://placeholder"), "placeholder",
Condition.STATE_TRUE, Condition.SOURCE_USER_ACTION);
Condition cond2 = new Condition(Uri.parse("uri://placeholder"), "placeholder",
@@ -186,8 +180,6 @@ public class ConditionTest {
@Test
public void testParcelConstructor() {
- mSetFlagsRule.enableFlags(Flags.FLAG_MODES_API);
-
Condition cond = new Condition(Uri.parse("uri://placeholder"), "placeholder",
Condition.STATE_TRUE, Condition.SOURCE_USER_ACTION);
@@ -200,28 +192,24 @@ public class ConditionTest {
}
@Test
- @EnableFlags(Flags.FLAG_MODES_API)
public void constructor_unspecifiedSource_succeeds() {
new Condition(Uri.parse("id"), "Summary", Condition.STATE_TRUE);
// No exception.
}
@Test
- @EnableFlags(Flags.FLAG_MODES_API)
public void constructor_validSource_succeeds() {
new Condition(Uri.parse("id"), "Summary", Condition.STATE_TRUE, Condition.SOURCE_CONTEXT);
// No exception.
}
@Test
- @EnableFlags(Flags.FLAG_MODES_API)
public void constructor_invalidSource_throws() {
assertThrows(IllegalArgumentException.class,
() -> new Condition(Uri.parse("uri"), "Summary", Condition.STATE_TRUE, 1000));
}
@Test
- @EnableFlags(Flags.FLAG_MODES_API)
public void constructor_parcelWithInvalidSource_throws() {
Condition original = new Condition(Uri.parse("condition"), "Summary", Condition.STATE_TRUE,
Condition.SOURCE_SCHEDULE);
@@ -237,7 +225,6 @@ public class ConditionTest {
}
@Test
- @EnableFlags(Flags.FLAG_MODES_API)
public void validate_invalidSource_throws() throws Exception {
Condition condition = new Condition(Uri.parse("condition"), "Summary", Condition.STATE_TRUE,
Condition.SOURCE_SCHEDULE);
diff --git a/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java b/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java
index 9f398ecf5492..b7d6ab56d6b3 100644
--- a/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java
+++ b/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java
@@ -79,6 +79,7 @@ import android.graphics.drawable.Icon;
import android.metrics.LogMaker;
import android.net.Uri;
import android.os.UserHandle;
+import android.os.UserManager;
import android.provider.DeviceConfig;
import android.service.chooser.ChooserTarget;
import android.util.Pair;
@@ -3178,7 +3179,11 @@ public class ChooserActivityTest {
}
private void markWorkProfileUserAvailable() {
- ChooserActivityOverrideData.getInstance().workProfileUserHandle = UserHandle.of(10);
+ if (UserManager.isHeadlessSystemUserMode()) {
+ ChooserActivityOverrideData.getInstance().workProfileUserHandle = UserHandle.of(11);
+ } else {
+ ChooserActivityOverrideData.getInstance().workProfileUserHandle = UserHandle.of(10);
+ }
}
private void markCloneProfileUserAvailable() {
diff --git a/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java b/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java
index dcea9120e2a5..be7f84e3ea8f 100644
--- a/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java
+++ b/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java
@@ -155,12 +155,12 @@ public class ChooserWrapperActivity extends ChooserActivity implements IChooserW
@Override
protected ResolverListController createListController(UserHandle userHandle) {
- if (userHandle == UserHandle.SYSTEM) {
- when(sOverrides.resolverListController.getUserHandle()).thenReturn(UserHandle.SYSTEM);
- return sOverrides.resolverListController;
+ if (userHandle.equals(sOverrides.workProfileUserHandle)) {
+ when(sOverrides.workResolverListController.getUserHandle()).thenReturn(userHandle);
+ return sOverrides.workResolverListController;
}
- when(sOverrides.workResolverListController.getUserHandle()).thenReturn(userHandle);
- return sOverrides.workResolverListController;
+ when(sOverrides.resolverListController.getUserHandle()).thenReturn(userHandle);
+ return sOverrides.resolverListController;
}
@Override
diff --git a/data/keyboards/Vendor_0957_Product_0001.kl b/data/keyboards/Vendor_0957_Product_0001.kl
index 87cb942602f7..0241f36e9ae6 100644
--- a/data/keyboards/Vendor_0957_Product_0001.kl
+++ b/data/keyboards/Vendor_0957_Product_0001.kl
@@ -72,6 +72,8 @@ key usage 0x000c009F NOTIFICATION
key usage 0x000c008D GUIDE
key usage 0x000c0089 TV
+key usage 0x000c0187 FEATURED_APP_1 WAKE #FreeTv
+
key usage 0x000c009C CHANNEL_UP
key usage 0x000c009D CHANNEL_DOWN
diff --git a/graphics/java/android/graphics/FrameInfo.java b/graphics/java/android/graphics/FrameInfo.java
index 7d236d203201..3b8f46630344 100644
--- a/graphics/java/android/graphics/FrameInfo.java
+++ b/graphics/java/android/graphics/FrameInfo.java
@@ -93,10 +93,12 @@ public final class FrameInfo {
// Interval between two consecutive frames
public static final int FRAME_INTERVAL = 11;
+ // Workload target deadline for a frame
+ public static final int WORKLOAD_TARGET = 12;
+
// Must be the last one
// This value must be in sync with `UI_THREAD_FRAME_INFO_SIZE` in FrameInfo.h
- // In calculating size, + 1 for Flags, and + 1 for WorkloadTarget from FrameInfo.h
- private static final int FRAME_INFO_SIZE = FRAME_INTERVAL + 2;
+ private static final int FRAME_INFO_SIZE = WORKLOAD_TARGET + 1;
/** checkstyle */
public void setVsync(long intendedVsync, long usedVsync, long frameTimelineVsyncId,
@@ -108,6 +110,7 @@ public final class FrameInfo {
frameInfo[FRAME_DEADLINE] = frameDeadline;
frameInfo[FRAME_START_TIME] = frameStartTime;
frameInfo[FRAME_INTERVAL] = frameInterval;
+ frameInfo[WORKLOAD_TARGET] = frameDeadline - intendedVsync;
}
/** checkstyle */
diff --git a/graphics/java/android/graphics/HardwareRenderer.java b/graphics/java/android/graphics/HardwareRenderer.java
index 940cd93c53f2..65854dd51a91 100644
--- a/graphics/java/android/graphics/HardwareRenderer.java
+++ b/graphics/java/android/graphics/HardwareRenderer.java
@@ -1467,6 +1467,18 @@ public class HardwareRenderer {
public static native void preload();
/**
+ * Initialize the Buffer Allocator singleton
+ *
+ * This takes 10-20ms on low-resourced devices, so doing it on-demand when an app
+ * tries to render its first frame causes drawFrames to be blocked for buffer
+ * allocation due to just initializing the allocator.
+ *
+ * Should only be called when a buffer is expected to be used.
+ * @hide
+ */
+ public static native void preInitBufferAllocator();
+
+ /**
* @hide
*/
protected static native boolean isWebViewOverlaysEnabled();
diff --git a/graphics/java/android/graphics/RuntimeShader.java b/graphics/java/android/graphics/RuntimeShader.java
index 3543e991924e..db2376e008f5 100644
--- a/graphics/java/android/graphics/RuntimeShader.java
+++ b/graphics/java/android/graphics/RuntimeShader.java
@@ -20,6 +20,7 @@ import android.annotation.ColorInt;
import android.annotation.ColorLong;
import android.annotation.FlaggedApi;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.util.ArrayMap;
import android.view.Window;
@@ -76,6 +77,7 @@ import libcore.util.NativeAllocationRegistry;
* Additionally, if the shader is invoked by another using {@link #setInputShader(String, Shader)},
* then that parent shader may modify the input coordinates arbitrarily.</p>
*
+ * <a id="agsl-and-color-spaces"/>
* <h3>AGSL and Color Spaces</h3>
* <p>Android Graphics and by extension {@link RuntimeShader} are color managed. The working
* {@link ColorSpace} for an AGSL shader is defined to be the color space of the destination, which
@@ -267,6 +269,8 @@ public class RuntimeShader extends Shader {
private ArrayMap<String, ColorFilter> mColorFilterUniforms = new ArrayMap<>();
private ArrayMap<String, RuntimeXfermode> mXfermodeUniforms = new ArrayMap<>();
+ private ColorSpace mWorkingColorSpace = null;
+
/**
* Creates a new RuntimeShader.
@@ -286,6 +290,35 @@ public class RuntimeShader extends Shader {
}
/**
+ * Sets the working color space for this shader. That is, the shader will be evaluated
+ * in the given colorspace before being converted to the output destination's colorspace.
+ *
+ * <p>By default the RuntimeShader is evaluated in the context of the
+ * <a href="#agsl-and-color-spaces">destination colorspace</a>. By calling this method
+ * that can be overridden to force the shader to be evaluated in the given colorspace first
+ * before then being color converted to the destination colorspace.</p>
+ *
+ * @param colorSpace The ColorSpace to evaluate in. Must be an {@link ColorSpace#getModel() RGB}
+ * ColorSpace. Passing null restores default behavior of working in the
+ * destination colorspace.
+ * @throws IllegalArgumentException If the colorspace is not RGB
+ */
+ @FlaggedApi(Flags.FLAG_SHADER_COLOR_SPACE)
+ public void setWorkingColorSpace(@Nullable ColorSpace colorSpace) {
+ if (colorSpace != null && colorSpace.getModel() != ColorSpace.Model.RGB) {
+ throw new IllegalArgumentException("ColorSpace must be RGB, given " + colorSpace);
+ }
+ if (mWorkingColorSpace != colorSpace) {
+ mWorkingColorSpace = colorSpace;
+ if (mWorkingColorSpace != null) {
+ // Just to enforce this can be resolved instead of erroring out later
+ mWorkingColorSpace.getNativeInstance();
+ }
+ discardNativeInstance();
+ }
+ }
+
+ /**
* Sets the uniform color value corresponding to this shader. If the shader does not have a
* uniform with that name or if the uniform is declared with a type other than vec3 or vec4 and
* corresponding layout(color) annotation then an IllegalArgumentException is thrown.
@@ -578,7 +611,8 @@ public class RuntimeShader extends Shader {
/** @hide */
@Override
protected long createNativeInstance(long nativeMatrix, boolean filterFromPaint) {
- return nativeCreateShader(mNativeInstanceRuntimeShaderBuilder, nativeMatrix);
+ return nativeCreateShader(mNativeInstanceRuntimeShaderBuilder, nativeMatrix,
+ mWorkingColorSpace != null ? mWorkingColorSpace.getNativeInstance() : 0);
}
/** @hide */
@@ -589,6 +623,8 @@ public class RuntimeShader extends Shader {
private static native long nativeGetFinalizer();
private static native long nativeCreateBuilder(String agsl);
private static native long nativeCreateShader(long shaderBuilder, long matrix);
+ private static native long nativeCreateShader(long shaderBuilder, long matrix,
+ long colorSpacePtr);
private static native void nativeUpdateUniforms(
long shaderBuilder, String uniformName, float[] uniforms, boolean isColor);
private static native void nativeUpdateUniforms(
diff --git a/keystore/java/android/security/GateKeeper.java b/keystore/java/android/security/GateKeeper.java
index 464714fe2895..c2792e1f2394 100644
--- a/keystore/java/android/security/GateKeeper.java
+++ b/keystore/java/android/security/GateKeeper.java
@@ -28,7 +28,7 @@ import android.service.gatekeeper.IGateKeeperService;
*
* @hide
*/
-public abstract class GateKeeper {
+public final class GateKeeper {
public static final long INVALID_SECURE_USER_ID = 0;
diff --git a/keystore/java/android/security/keystore/ArrayUtils.java b/keystore/java/android/security/keystore/ArrayUtils.java
index f22b6041800f..6472ca9957d0 100644
--- a/keystore/java/android/security/keystore/ArrayUtils.java
+++ b/keystore/java/android/security/keystore/ArrayUtils.java
@@ -23,7 +23,7 @@ import java.util.function.Consumer;
/**
* @hide
*/
-public abstract class ArrayUtils {
+public final class ArrayUtils {
private ArrayUtils() {}
public static String[] nullToEmpty(String[] array) {
diff --git a/keystore/java/android/security/keystore/Utils.java b/keystore/java/android/security/keystore/Utils.java
index e58b1ccb5370..c38ce8e86a15 100644
--- a/keystore/java/android/security/keystore/Utils.java
+++ b/keystore/java/android/security/keystore/Utils.java
@@ -23,7 +23,7 @@ import java.util.Date;
*
* @hide
*/
-abstract class Utils {
+public final class Utils {
private Utils() {}
static Date cloneIfNotNull(Date value) {
diff --git a/keystore/java/android/security/keystore2/KeyStore2ParameterUtils.java b/keystore/java/android/security/keystore2/KeyStore2ParameterUtils.java
index 1394bd443f03..9d306ce1ed38 100644
--- a/keystore/java/android/security/keystore2/KeyStore2ParameterUtils.java
+++ b/keystore/java/android/security/keystore2/KeyStore2ParameterUtils.java
@@ -38,7 +38,9 @@ import java.util.function.Consumer;
/**
* @hide
*/
-public abstract class KeyStore2ParameterUtils {
+public final class KeyStore2ParameterUtils {
+
+ private KeyStore2ParameterUtils() {}
/**
* This function constructs a {@link KeyParameter} expressing a boolean value.
diff --git a/keystore/java/android/security/keystore2/KeymasterUtils.java b/keystore/java/android/security/keystore2/KeymasterUtils.java
index 614e3684c417..02f3f578d03e 100644
--- a/keystore/java/android/security/keystore2/KeymasterUtils.java
+++ b/keystore/java/android/security/keystore2/KeymasterUtils.java
@@ -16,13 +16,10 @@
package android.security.keystore2;
-import android.security.keymaster.KeymasterArguments;
import android.security.keymaster.KeymasterDefs;
-import android.security.keystore.KeyProperties;
import java.security.AlgorithmParameters;
import java.security.NoSuchAlgorithmException;
-import java.security.ProviderException;
import java.security.spec.ECGenParameterSpec;
import java.security.spec.ECParameterSpec;
import java.security.spec.InvalidParameterSpecException;
@@ -30,7 +27,7 @@ import java.security.spec.InvalidParameterSpecException;
/**
* @hide
*/
-public abstract class KeymasterUtils {
+public final class KeymasterUtils {
private KeymasterUtils() {}
@@ -86,47 +83,6 @@ public abstract class KeymasterUtils {
}
}
- /**
- * Adds {@code KM_TAG_MIN_MAC_LENGTH} tag, if necessary, to the keymaster arguments for
- * generating or importing a key. This tag may only be needed for symmetric keys (e.g., HMAC,
- * AES-GCM).
- */
- public static void addMinMacLengthAuthorizationIfNecessary(KeymasterArguments args,
- int keymasterAlgorithm,
- int[] keymasterBlockModes,
- int[] keymasterDigests) {
- switch (keymasterAlgorithm) {
- case KeymasterDefs.KM_ALGORITHM_AES:
- if (com.android.internal.util.ArrayUtils.contains(
- keymasterBlockModes, KeymasterDefs.KM_MODE_GCM)) {
- // AES GCM key needs the minimum length of AEAD tag specified.
- args.addUnsignedInt(KeymasterDefs.KM_TAG_MIN_MAC_LENGTH,
- AndroidKeyStoreAuthenticatedAESCipherSpi.GCM
- .MIN_SUPPORTED_TAG_LENGTH_BITS);
- }
- break;
- case KeymasterDefs.KM_ALGORITHM_HMAC:
- // HMAC key needs the minimum length of MAC set to the output size of the associated
- // digest. This is because we do not offer a way to generate shorter MACs and
- // don't offer a way to verify MACs (other than by generating them).
- if (keymasterDigests.length != 1) {
- throw new ProviderException(
- "Unsupported number of authorized digests for HMAC key: "
- + keymasterDigests.length
- + ". Exactly one digest must be authorized");
- }
- int keymasterDigest = keymasterDigests[0];
- int digestOutputSizeBits = getDigestOutputSizeBits(keymasterDigest);
- if (digestOutputSizeBits == -1) {
- throw new ProviderException(
- "HMAC key authorized for unsupported digest: "
- + KeyProperties.Digest.fromKeymaster(keymasterDigest));
- }
- args.addUnsignedInt(KeymasterDefs.KM_TAG_MIN_MAC_LENGTH, digestOutputSizeBits);
- break;
- }
- }
-
static String getEcCurveFromKeymaster(int ecCurve) {
switch (ecCurve) {
case android.hardware.security.keymint.EcCurve.P_224:
diff --git a/libs/WindowManager/Shell/OWNERS b/libs/WindowManager/Shell/OWNERS
index ab2f3ef94eb6..68970e68de07 100644
--- a/libs/WindowManager/Shell/OWNERS
+++ b/libs/WindowManager/Shell/OWNERS
@@ -3,5 +3,5 @@ pbdr@google.com
pragyabajoria@google.com
# Give submodule owners in shell resource approval
-per-file res*/*/*.xml = atsjenk@google.com, hwwang@google.com, lbill@google.com, madym@google.com, vaniadesmonda@google.com, pbdr@google.com, mpodolian@google.com, liranb@google.com, pragyabajoria@google.com, uysalorhan@google.com, gsennton@google.com, mattsziklay@google.com, mdehaini@google.com
+per-file res*/*/*.xml = atsjenk@google.com, hwwang@google.com, lbill@google.com, madym@google.com, vaniadesmonda@google.com, pbdr@google.com, mpodolian@google.com, liranb@google.com, pragyabajoria@google.com, uysalorhan@google.com, gsennton@google.com, mattsziklay@google.com, mdehaini@google.com, peanutbutter@google.com, jeremysim@google.com
per-file res*/*/tv_*.xml = bronger@google.com
diff --git a/libs/WindowManager/Shell/multivalentScreenshotTests/goldens/onDevice/foldable_inner/light_landscape_dragZones_bubble.png b/libs/WindowManager/Shell/multivalentScreenshotTests/goldens/onDevice/foldable_inner/light_landscape_dragZones_bubble.png
new file mode 100644
index 000000000000..15198748eea5
--- /dev/null
+++ b/libs/WindowManager/Shell/multivalentScreenshotTests/goldens/onDevice/foldable_inner/light_landscape_dragZones_bubble.png
Binary files differ
diff --git a/libs/WindowManager/Shell/multivalentScreenshotTests/goldens/onDevice/foldable_inner/light_landscape_dragZones_bubbleBar.png b/libs/WindowManager/Shell/multivalentScreenshotTests/goldens/onDevice/foldable_inner/light_landscape_dragZones_bubbleBar.png
new file mode 100644
index 000000000000..99673f6400e9
--- /dev/null
+++ b/libs/WindowManager/Shell/multivalentScreenshotTests/goldens/onDevice/foldable_inner/light_landscape_dragZones_bubbleBar.png
Binary files differ
diff --git a/libs/WindowManager/Shell/multivalentScreenshotTests/goldens/onDevice/foldable_inner/light_landscape_dragZones_bubble_split_10_90.png b/libs/WindowManager/Shell/multivalentScreenshotTests/goldens/onDevice/foldable_inner/light_landscape_dragZones_bubble_split_10_90.png
new file mode 100644
index 000000000000..ba4ebab75a7e
--- /dev/null
+++ b/libs/WindowManager/Shell/multivalentScreenshotTests/goldens/onDevice/foldable_inner/light_landscape_dragZones_bubble_split_10_90.png
Binary files differ
diff --git a/libs/WindowManager/Shell/multivalentScreenshotTests/goldens/onDevice/foldable_inner/light_landscape_dragZones_bubble_split_90_10.png b/libs/WindowManager/Shell/multivalentScreenshotTests/goldens/onDevice/foldable_inner/light_landscape_dragZones_bubble_split_90_10.png
new file mode 100644
index 000000000000..b3ff64401a96
--- /dev/null
+++ b/libs/WindowManager/Shell/multivalentScreenshotTests/goldens/onDevice/foldable_inner/light_landscape_dragZones_bubble_split_90_10.png
Binary files differ
diff --git a/libs/WindowManager/Shell/multivalentScreenshotTests/goldens/onDevice/foldable_inner/light_landscape_dragZones_expandedView.png b/libs/WindowManager/Shell/multivalentScreenshotTests/goldens/onDevice/foldable_inner/light_landscape_dragZones_expandedView.png
new file mode 100644
index 000000000000..534e320a0596
--- /dev/null
+++ b/libs/WindowManager/Shell/multivalentScreenshotTests/goldens/onDevice/foldable_inner/light_landscape_dragZones_expandedView.png
Binary files differ
diff --git a/libs/WindowManager/Shell/multivalentScreenshotTests/goldens/onDevice/foldable_inner/light_landscape_dragZones_expandedView_split_10_90.png b/libs/WindowManager/Shell/multivalentScreenshotTests/goldens/onDevice/foldable_inner/light_landscape_dragZones_expandedView_split_10_90.png
new file mode 100644
index 000000000000..67c9f49171ba
--- /dev/null
+++ b/libs/WindowManager/Shell/multivalentScreenshotTests/goldens/onDevice/foldable_inner/light_landscape_dragZones_expandedView_split_10_90.png
Binary files differ
diff --git a/libs/WindowManager/Shell/multivalentScreenshotTests/goldens/onDevice/foldable_inner/light_landscape_dragZones_expandedView_split_90_10.png b/libs/WindowManager/Shell/multivalentScreenshotTests/goldens/onDevice/foldable_inner/light_landscape_dragZones_expandedView_split_90_10.png
new file mode 100644
index 000000000000..a0fb4902a6f3
--- /dev/null
+++ b/libs/WindowManager/Shell/multivalentScreenshotTests/goldens/onDevice/foldable_inner/light_landscape_dragZones_expandedView_split_90_10.png
Binary files differ
diff --git a/libs/WindowManager/Shell/multivalentScreenshotTests/goldens/onDevice/foldable_inner/light_portrait_dragZones_bubble.png b/libs/WindowManager/Shell/multivalentScreenshotTests/goldens/onDevice/foldable_inner/light_portrait_dragZones_bubble.png
new file mode 100644
index 000000000000..27b35d447868
--- /dev/null
+++ b/libs/WindowManager/Shell/multivalentScreenshotTests/goldens/onDevice/foldable_inner/light_portrait_dragZones_bubble.png
Binary files differ
diff --git a/libs/WindowManager/Shell/multivalentScreenshotTests/goldens/onDevice/foldable_inner/light_portrait_dragZones_bubbleBar.png b/libs/WindowManager/Shell/multivalentScreenshotTests/goldens/onDevice/foldable_inner/light_portrait_dragZones_bubbleBar.png
new file mode 100644
index 000000000000..11528a028a8f
--- /dev/null
+++ b/libs/WindowManager/Shell/multivalentScreenshotTests/goldens/onDevice/foldable_inner/light_portrait_dragZones_bubbleBar.png
Binary files differ
diff --git a/libs/WindowManager/Shell/multivalentScreenshotTests/goldens/onDevice/foldable_inner/light_portrait_dragZones_bubble_split_10_90.png b/libs/WindowManager/Shell/multivalentScreenshotTests/goldens/onDevice/foldable_inner/light_portrait_dragZones_bubble_split_10_90.png
new file mode 100644
index 000000000000..ef9937700c08
--- /dev/null
+++ b/libs/WindowManager/Shell/multivalentScreenshotTests/goldens/onDevice/foldable_inner/light_portrait_dragZones_bubble_split_10_90.png
Binary files differ
diff --git a/libs/WindowManager/Shell/multivalentScreenshotTests/goldens/onDevice/foldable_inner/light_portrait_dragZones_bubble_split_90_10.png b/libs/WindowManager/Shell/multivalentScreenshotTests/goldens/onDevice/foldable_inner/light_portrait_dragZones_bubble_split_90_10.png
new file mode 100644
index 000000000000..f0cf08bfcf4e
--- /dev/null
+++ b/libs/WindowManager/Shell/multivalentScreenshotTests/goldens/onDevice/foldable_inner/light_portrait_dragZones_bubble_split_90_10.png
Binary files differ
diff --git a/libs/WindowManager/Shell/multivalentScreenshotTests/goldens/onDevice/foldable_inner/light_portrait_dragZones_expandedView.png b/libs/WindowManager/Shell/multivalentScreenshotTests/goldens/onDevice/foldable_inner/light_portrait_dragZones_expandedView.png
new file mode 100644
index 000000000000..bbaafb39845a
--- /dev/null
+++ b/libs/WindowManager/Shell/multivalentScreenshotTests/goldens/onDevice/foldable_inner/light_portrait_dragZones_expandedView.png
Binary files differ
diff --git a/libs/WindowManager/Shell/multivalentScreenshotTests/goldens/onDevice/tablet/light_landscape_dragZones_bubble.png b/libs/WindowManager/Shell/multivalentScreenshotTests/goldens/onDevice/tablet/light_landscape_dragZones_bubble.png
new file mode 100644
index 000000000000..38ebf3f3201a
--- /dev/null
+++ b/libs/WindowManager/Shell/multivalentScreenshotTests/goldens/onDevice/tablet/light_landscape_dragZones_bubble.png
Binary files differ
diff --git a/libs/WindowManager/Shell/multivalentScreenshotTests/goldens/onDevice/tablet/light_landscape_dragZones_bubbleBar.png b/libs/WindowManager/Shell/multivalentScreenshotTests/goldens/onDevice/tablet/light_landscape_dragZones_bubbleBar.png
new file mode 100644
index 000000000000..2e4fd51e3932
--- /dev/null
+++ b/libs/WindowManager/Shell/multivalentScreenshotTests/goldens/onDevice/tablet/light_landscape_dragZones_bubbleBar.png
Binary files differ
diff --git a/libs/WindowManager/Shell/multivalentScreenshotTests/goldens/onDevice/tablet/light_landscape_dragZones_expandedView.png b/libs/WindowManager/Shell/multivalentScreenshotTests/goldens/onDevice/tablet/light_landscape_dragZones_expandedView.png
new file mode 100644
index 000000000000..a1ba9fb50d6a
--- /dev/null
+++ b/libs/WindowManager/Shell/multivalentScreenshotTests/goldens/onDevice/tablet/light_landscape_dragZones_expandedView.png
Binary files differ
diff --git a/libs/WindowManager/Shell/multivalentScreenshotTests/goldens/onDevice/tablet/light_portrait_dragZones_bubble.png b/libs/WindowManager/Shell/multivalentScreenshotTests/goldens/onDevice/tablet/light_portrait_dragZones_bubble.png
new file mode 100644
index 000000000000..51bb15e10d30
--- /dev/null
+++ b/libs/WindowManager/Shell/multivalentScreenshotTests/goldens/onDevice/tablet/light_portrait_dragZones_bubble.png
Binary files differ
diff --git a/libs/WindowManager/Shell/multivalentScreenshotTests/goldens/onDevice/tablet/light_portrait_dragZones_bubbleBar.png b/libs/WindowManager/Shell/multivalentScreenshotTests/goldens/onDevice/tablet/light_portrait_dragZones_bubbleBar.png
new file mode 100644
index 000000000000..b643e2a69b2c
--- /dev/null
+++ b/libs/WindowManager/Shell/multivalentScreenshotTests/goldens/onDevice/tablet/light_portrait_dragZones_bubbleBar.png
Binary files differ
diff --git a/libs/WindowManager/Shell/multivalentScreenshotTests/goldens/onDevice/tablet/light_portrait_dragZones_expandedView.png b/libs/WindowManager/Shell/multivalentScreenshotTests/goldens/onDevice/tablet/light_portrait_dragZones_expandedView.png
new file mode 100644
index 000000000000..e6eeab7129be
--- /dev/null
+++ b/libs/WindowManager/Shell/multivalentScreenshotTests/goldens/onDevice/tablet/light_portrait_dragZones_expandedView.png
Binary files differ
diff --git a/libs/WindowManager/Shell/multivalentScreenshotTests/src/com/android/wm/shell/shared/bubbles/DragZoneFactoryScreenshotTest.kt b/libs/WindowManager/Shell/multivalentScreenshotTests/src/com/android/wm/shell/shared/bubbles/DragZoneFactoryScreenshotTest.kt
new file mode 100644
index 000000000000..24f43d347163
--- /dev/null
+++ b/libs/WindowManager/Shell/multivalentScreenshotTests/src/com/android/wm/shell/shared/bubbles/DragZoneFactoryScreenshotTest.kt
@@ -0,0 +1,170 @@
+/*
+ * 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.shared.bubbles
+
+import android.content.Context
+import android.graphics.Color
+import android.graphics.drawable.Drawable
+import android.graphics.drawable.GradientDrawable
+import android.view.View
+import android.view.WindowManager
+import android.widget.FrameLayout
+import androidx.annotation.ColorInt
+import androidx.core.graphics.blue
+import androidx.core.graphics.green
+import androidx.core.graphics.red
+import androidx.core.graphics.toColorInt
+import androidx.test.core.app.ApplicationProvider.getApplicationContext
+import com.android.wm.shell.shared.bubbles.DragZoneFactory.DesktopWindowModeChecker
+import com.android.wm.shell.shared.bubbles.DragZoneFactory.SplitScreenModeChecker
+import com.android.wm.shell.shared.bubbles.DragZoneFactory.SplitScreenModeChecker.SplitScreenMode
+import com.android.wm.shell.testing.goldenpathmanager.WMShellGoldenPathManager
+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 platform.test.screenshot.DeviceEmulationSpec
+import platform.test.screenshot.Displays
+import platform.test.screenshot.ViewScreenshotTestRule
+import platform.test.screenshot.ViewScreenshotTestRule.Mode
+import platform.test.screenshot.getEmulatedDevicePathConfig
+
+@RunWith(ParameterizedAndroidJunit4::class)
+class DragZoneFactoryScreenshotTest(private val param: Param) {
+ companion object {
+ @Parameters(name = "{0}")
+ @JvmStatic
+ fun getTestSpecs(): List<Param> {
+ val params = mutableListOf<Param>()
+ val draggedObjects =
+ listOf(
+ DraggedObject.Bubble(BubbleBarLocation.LEFT),
+ DraggedObject.BubbleBar(BubbleBarLocation.LEFT),
+ DraggedObject.ExpandedView(BubbleBarLocation.LEFT),
+ )
+ DeviceEmulationSpec.forDisplays(Displays.Tablet, isDarkTheme = false).forEach { tablet
+ ->
+ draggedObjects.forEach { draggedObject ->
+ params.add(Param(tablet, draggedObject, SplitScreenMode.NONE))
+ }
+ }
+ DeviceEmulationSpec.forDisplays(Displays.FoldableInner, isDarkTheme = false).forEach {
+ foldable ->
+ draggedObjects.forEach { draggedObject ->
+ params.add(Param(foldable, draggedObject, SplitScreenMode.NONE))
+ val isBubble = draggedObject is DraggedObject.Bubble
+ val isExpandedView = draggedObject is DraggedObject.ExpandedView
+ val addMoreSplitModes = isBubble || (isExpandedView && foldable.isLandscape)
+ if (addMoreSplitModes) {
+ params.add(Param(foldable, draggedObject, SplitScreenMode.SPLIT_10_90))
+ params.add(Param(foldable, draggedObject, SplitScreenMode.SPLIT_90_10))
+ }
+ }
+ }
+ return params
+ }
+ }
+
+ class Param(
+ val emulationSpec: DeviceEmulationSpec,
+ val draggedObject: DraggedObject,
+ val splitScreenMode: SplitScreenMode
+ ) {
+ private val draggedObjectName =
+ when (draggedObject) {
+ is DraggedObject.Bubble -> "bubble"
+ is DraggedObject.BubbleBar -> "bubbleBar"
+ is DraggedObject.ExpandedView -> "expandedView"
+ }
+
+ private val splitScreenModeName =
+ when (splitScreenMode) {
+ SplitScreenMode.NONE -> ""
+ SplitScreenMode.SPLIT_50_50 -> "_split_50_50"
+ SplitScreenMode.SPLIT_10_90 -> "_split_10_90"
+ SplitScreenMode.SPLIT_90_10 -> "_split_90_10"
+ }
+
+ val testName = "$draggedObjectName$splitScreenModeName"
+
+ override fun toString() = "${emulationSpec}_$testName"
+ }
+
+ @get:Rule
+ val screenshotRule =
+ ViewScreenshotTestRule(
+ param.emulationSpec,
+ WMShellGoldenPathManager(getEmulatedDevicePathConfig(param.emulationSpec))
+ )
+
+ private val context = getApplicationContext<Context>()
+
+ @Test
+ fun dragZones() {
+ screenshotRule.screenshotTest("dragZones_${param.testName}", mode = Mode.MatchSize) {
+ activity ->
+ activity.actionBar?.hide()
+ val dragZoneFactory = createDragZoneFactory()
+ val dragZones = dragZoneFactory.createSortedDragZones(param.draggedObject)
+ val container = FrameLayout(context)
+ dragZones.forEach { zone -> container.addZoneView(zone) }
+ container
+ }
+ }
+
+ private fun createDragZoneFactory(): DragZoneFactory {
+ val deviceConfig =
+ DeviceConfig.create(context, context.getSystemService(WindowManager::class.java)!!)
+ val splitScreenModeChecker = SplitScreenModeChecker { param.splitScreenMode }
+ val desktopWindowModeChecker = DesktopWindowModeChecker { true }
+ return DragZoneFactory(
+ context,
+ deviceConfig,
+ splitScreenModeChecker,
+ desktopWindowModeChecker
+ )
+ }
+
+ private fun FrameLayout.addZoneView(zone: DragZone) {
+ val view = View(context)
+ this.addView(view, 0)
+ view.layoutParams = FrameLayout.LayoutParams(zone.bounds.width(), zone.bounds.height())
+ view.background = createZoneDrawable(zone.color)
+ view.x = zone.bounds.left.toFloat()
+ view.y = zone.bounds.top.toFloat()
+ }
+
+ private fun createZoneDrawable(@ColorInt color: Int): Drawable {
+ val shape = GradientDrawable()
+ shape.shape = GradientDrawable.RECTANGLE
+ shape.setColor(Color.argb(128, color.red, color.green, color.blue))
+ shape.setStroke(2, color)
+ return shape
+ }
+
+ private val DragZone.color: Int
+ @ColorInt
+ get() =
+ when (this) {
+ is DragZone.Bubble -> "#3F5C8B".toColorInt()
+ is DragZone.Dismiss -> "#8B3F3F".toColorInt()
+ is DragZone.Split -> "#89B675".toColorInt()
+ is DragZone.FullScreen -> "#4ED075".toColorInt()
+ is DragZone.DesktopWindow -> "#EC928E".toColorInt()
+ }
+}
diff --git a/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_maximize_menu.xml b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_maximize_menu.xml
index 8d7e5fd95957..d50a14cf5dae 100644
--- a/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_maximize_menu.xml
+++ b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_maximize_menu.xml
@@ -18,23 +18,28 @@
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
android:id="@+id/maximize_menu"
android:layout_width="wrap_content"
- android:layout_height="@dimen/desktop_mode_maximize_menu_height"
+ android:layout_height="wrap_content"
android:background="@drawable/desktop_mode_maximize_menu_background"
android:elevation="1dp">
<LinearLayout
android:id="@+id/container"
android:layout_width="wrap_content"
- android:layout_height="@dimen/desktop_mode_maximize_menu_height"
+ android:layout_height="wrap_content"
android:orientation="horizontal"
- android:padding="16dp"
+ android:paddingHorizontal="12dp"
+ android:paddingVertical="16dp"
+ android:measureWithLargestChild="true"
android:gravity="center">
<LinearLayout
android:id="@+id/maximize_menu_immersive_toggle_container"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:orientation="vertical">
+ android:layout_weight="1"
+ android:orientation="vertical"
+ android:layout_marginStart="4dp"
+ android:layout_marginEnd="4dp">
<Button
android:layout_width="94dp"
@@ -44,21 +49,22 @@
android:stateListAnimator="@null"
android:importantForAccessibility="yes"
android:contentDescription="@string/desktop_mode_maximize_menu_immersive_button_text"
- android:layout_marginEnd="8dp"
android:layout_marginBottom="4dp"
android:alpha="0"/>
<TextView
android:id="@+id/maximize_menu_immersive_toggle_button_text"
- android:layout_width="94dp"
- android:layout_height="18dp"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
android:textSize="11sp"
- android:layout_marginBottom="76dp"
+ android:lineHeight="16sp"
android:gravity="center"
android:fontFamily="google-sans-text"
+ android:textFontWeight="500"
android:importantForAccessibility="no"
android:text="@string/desktop_mode_maximize_menu_immersive_button_text"
android:textColor="@androidprv:color/materialColorOnSurface"
+ android:singleLine="true"
android:alpha="0"/>
</LinearLayout>
@@ -66,7 +72,11 @@
android:id="@+id/maximize_menu_size_toggle_container"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:orientation="vertical">
+ android:layout_weight="1"
+ android:orientation="vertical"
+ android:gravity="center_horizontal"
+ android:layout_marginStart="4dp"
+ android:layout_marginEnd="4dp">
<Button
android:layout_width="94dp"
@@ -81,15 +91,17 @@
<TextView
android:id="@+id/maximize_menu_size_toggle_button_text"
- android:layout_width="94dp"
- android:layout_height="18dp"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
android:textSize="11sp"
- android:layout_marginBottom="76dp"
+ android:lineHeight="16sp"
android:gravity="center"
android:fontFamily="google-sans-text"
+ android:textFontWeight="500"
android:importantForAccessibility="no"
android:text="@string/desktop_mode_maximize_menu_maximize_text"
android:textColor="@androidprv:color/materialColorOnSurface"
+ android:singleLine="true"
android:alpha="0"/>
</LinearLayout>
@@ -97,7 +109,11 @@
android:id="@+id/maximize_menu_snap_container"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:orientation="vertical">
+ android:layout_weight="1"
+ android:orientation="vertical"
+ android:gravity="center_horizontal"
+ android:layout_marginStart="4dp"
+ android:layout_marginEnd="4dp">
<LinearLayout
android:id="@+id/maximize_menu_snap_menu_layout"
android:layout_width="wrap_content"
@@ -106,7 +122,6 @@
android:padding="4dp"
android:background="@drawable/desktop_mode_maximize_menu_layout_background"
android:layout_marginBottom="4dp"
- android:layout_marginStart="8dp"
android:alpha="0">
<Button
android:id="@+id/maximize_menu_snap_left_button"
@@ -131,16 +146,17 @@
</LinearLayout>
<TextView
android:id="@+id/maximize_menu_snap_window_text"
- android:layout_width="94dp"
- android:layout_height="18dp"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
android:textSize="11sp"
- android:layout_marginBottom="76dp"
- android:layout_gravity="center"
+ android:lineHeight="16sp"
android:gravity="center"
android:importantForAccessibility="no"
android:fontFamily="google-sans-text"
+ android:textFontWeight="500"
android:text="@string/desktop_mode_maximize_menu_snap_text"
android:textColor="@androidprv:color/materialColorOnSurface"
+ android:singleLine="true"
android:alpha="0"/>
</LinearLayout>
</LinearLayout>
@@ -150,6 +166,6 @@
<View
android:id="@+id/maximize_menu_overlay"
android:layout_width="match_parent"
- android:layout_height="@dimen/desktop_mode_maximize_menu_height"/>
+ android:layout_height="match_parent"/>
</FrameLayout>
diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml
index e395341a5792..f5f3f0fe52eb 100644
--- a/libs/WindowManager/Shell/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/res/values/dimen.xml
@@ -498,14 +498,6 @@
<!-- The default minimum allowed window height when resizing a window in desktop mode. -->
<dimen name="desktop_mode_minimum_window_height">352dp</dimen>
- <!-- The width of the maximize menu in desktop mode, depending on the number of options -->
- <dimen name="desktop_mode_maximize_menu_width_one_options">126dp</dimen>
- <dimen name="desktop_mode_maximize_menu_width_two_options">228dp</dimen>
- <dimen name="desktop_mode_maximize_menu_width_three_options">330dp</dimen>
-
- <!-- The height of the maximize menu in desktop mode. -->
- <dimen name="desktop_mode_maximize_menu_height">114dp</dimen>
-
<!-- The padding of the maximize menu in desktop mode. -->
<dimen name="desktop_mode_menu_padding">16dp</dimen>
diff --git a/libs/WindowManager/Shell/shared/res/color/bubble_drop_target_background_color.xml b/libs/WindowManager/Shell/shared/res/color/bubble_drop_target_background_color.xml
new file mode 100644
index 000000000000..975d25b25953
--- /dev/null
+++ b/libs/WindowManager/Shell/shared/res/color/bubble_drop_target_background_color.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.
+ -->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
+ <item android:alpha="0.35" android:color="@androidprv:color/materialColorPrimaryContainer" />
+</selector>
diff --git a/libs/WindowManager/Shell/shared/res/drawable/bubble_drop_target_background.xml b/libs/WindowManager/Shell/shared/res/drawable/bubble_drop_target_background.xml
new file mode 100644
index 000000000000..89546f9b0807
--- /dev/null
+++ b/libs/WindowManager/Shell/shared/res/drawable/bubble_drop_target_background.xml
@@ -0,0 +1,25 @@
+<?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.
+ -->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ android:shape="rectangle">
+ <corners android:radius="28dp" />
+ <solid android:color="@color/bubble_drop_target_background_color" />
+ <stroke
+ android:width="1dp"
+ android:color="@androidprv:color/materialColorPrimaryContainer" />
+</shape>
diff --git a/libs/WindowManager/Shell/shared/res/values/dimen.xml b/libs/WindowManager/Shell/shared/res/values/dimen.xml
index d280083ae7f5..11a6f32d7454 100644
--- a/libs/WindowManager/Shell/shared/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/shared/res/values/dimen.xml
@@ -36,4 +36,14 @@
<dimen name="drag_zone_v_split_from_expanded_view_height_tablet">285dp</dimen>
<dimen name="drag_zone_v_split_from_expanded_view_height_fold_tall">150dp</dimen>
<dimen name="drag_zone_v_split_from_expanded_view_height_fold_short">100dp</dimen>
+
+ <!-- Bubble drop target dimensions -->
+ <dimen name="drop_target_elevation">1dp</dimen>
+ <dimen name="drop_target_full_screen_padding">20dp</dimen>
+ <dimen name="drop_target_desktop_window_padding_small">100dp</dimen>
+ <dimen name="drop_target_desktop_window_padding_large">130dp</dimen>
+ <dimen name="drop_target_expanded_view_width">364</dimen>
+ <dimen name="drop_target_expanded_view_height">578</dimen>
+ <dimen name="drop_target_expanded_view_padding_bottom">108</dimen>
+ <dimen name="drop_target_expanded_view_padding_horizontal">24</dimen>
</resources> \ No newline at end of file
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 4d00c74155a8..851987269c10 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
@@ -21,6 +21,7 @@ import static android.view.RemoteAnimationTarget.MODE_CHANGING;
import static android.view.RemoteAnimationTarget.MODE_CLOSING;
import static android.view.RemoteAnimationTarget.MODE_OPENING;
import static android.view.WindowManager.LayoutParams.INVALID_WINDOW_TYPE;
+import static android.view.WindowManager.LayoutParams.LAST_SYSTEM_WINDOW;
import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_CLOSE;
@@ -55,9 +56,15 @@ import java.util.function.Predicate;
public class TransitionUtil {
/** Flag applied to a transition change to identify it as a divider bar for animation. */
public static final int FLAG_IS_DIVIDER_BAR = FLAG_FIRST_CUSTOM;
+ public static final int FLAG_IS_DIM_LAYER = FLAG_FIRST_CUSTOM << 1;
/** Flag applied to a transition change to identify it as a desktop wallpaper activity. */
- public static final int FLAG_IS_DESKTOP_WALLPAPER_ACTIVITY = FLAG_FIRST_CUSTOM << 1;
+ public static final int FLAG_IS_DESKTOP_WALLPAPER_ACTIVITY = FLAG_FIRST_CUSTOM << 2;
+
+ /**
+ * Applied to a {@link RemoteAnimationTarget} to identify dim layers for animation in Launcher.
+ */
+ public static final int TYPE_SPLIT_SCREEN_DIM_LAYER = LAST_SYSTEM_WINDOW + 1;
/** @return true if the transition was triggered by opening something vs closing something */
public static boolean isOpeningType(@WindowManager.TransitionType int type) {
@@ -117,6 +124,11 @@ public class TransitionUtil {
return isNonApp(change) && change.hasFlags(FLAG_IS_DIVIDER_BAR);
}
+ /** Returns `true` if `change` is an app's dim layer. */
+ public static boolean isDimLayer(TransitionInfo.Change change) {
+ return isNonApp(change) && change.hasFlags(FLAG_IS_DIM_LAYER);
+ }
+
/** Returns `true` if `change` is only re-ordering. */
public static boolean isOrderOnly(TransitionInfo.Change change) {
return change.getMode() == TRANSIT_CHANGE
@@ -231,6 +243,14 @@ public class TransitionUtil {
t.setLayer(leash, Integer.MAX_VALUE);
return;
}
+ if (isDimLayer(change)) {
+ // When a dim layer gets reparented onto the transition root, we need to zero out its
+ // position so that it's in line with everything else on the transition root. Also,
+ // we need to set a crop because we don't want it applying MATCH_PARENT on the whole
+ // root surface.
+ t.setPosition(leash, 0, 0);
+ t.setCrop(leash, change.getEndAbsBounds());
+ }
// Put all the OPEN/SHOW on top
if ((change.getFlags() & FLAG_IS_WALLPAPER) != 0) {
@@ -284,14 +304,19 @@ public class TransitionUtil {
// Copied Transitions setup code (which expects bottom-to-top order, so we swap here)
setupLeash(leashSurface, change, info.getChanges().size() - order, info, t);
t.reparent(change.getLeash(), leashSurface);
- t.setAlpha(change.getLeash(), 1.0f);
- t.show(change.getLeash());
+ if (!isDimLayer(change)) {
+ // Most leashes going onto the transition root should have their alpha set here to make
+ // them visible. But dim layers should be left untouched (their alpha value is their
+ // actual dim value).
+ t.setAlpha(change.getLeash(), 1.0f);
+ }
if (!isDividerBar(change)) {
// For divider, don't modify its inner leash position when creating the outer leash
// for the transition. In case the position being wrong after the transition finished.
t.setPosition(change.getLeash(), 0, 0);
}
t.setLayer(change.getLeash(), 0);
+ t.show(change.getLeash());
return leashSurface;
}
@@ -333,6 +358,9 @@ public class TransitionUtil {
if (isDividerBar(change)) {
return getDividerTarget(change, leash);
}
+ if (isDimLayer(change)) {
+ return getDimLayerTarget(change, leash);
+ }
int taskId;
boolean isNotInRecents;
@@ -439,6 +467,17 @@ public class TransitionUtil {
TYPE_DOCK_DIVIDER);
}
+ private static RemoteAnimationTarget getDimLayerTarget(TransitionInfo.Change change,
+ SurfaceControl leash) {
+ return new RemoteAnimationTarget(-1 /* taskId */, newModeToLegacyMode(change.getMode()),
+ leash, false /* isTranslucent */, null /* clipRect */,
+ null /* contentInsets */, Integer.MAX_VALUE /* prefixOrderIndex */,
+ new android.graphics.Point(0, 0) /* position */, change.getStartAbsBounds(),
+ change.getStartAbsBounds(), new WindowConfiguration(), true, null /* startLeash */,
+ null /* startBounds */, null /* taskInfo */, false /* allowEnterPip */,
+ TYPE_SPLIT_SCREEN_DIM_LAYER);
+ }
+
/**
* Finds the "correct" root idx for a change. The change's end display is prioritized, then
* the start display. If there is no display, it will fallback on the 0th root in the
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/animation/Interpolators.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/animation/Interpolators.java
index f45dc3a1e892..e92c1eb81e89 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/animation/Interpolators.java
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/animation/Interpolators.java
@@ -93,10 +93,21 @@ public class Interpolators {
public static final PathInterpolator SLOWDOWN_INTERPOLATOR =
new PathInterpolator(0.5f, 1f, 0.5f, 1f);
+ /**
+ * An interpolator used for dimming a task as it travels offscreen, or towards a distant dismiss
+ * point. A sharp rise, followed by a steady middle, and ending with another sharp rise.
+ */
public static final PathInterpolator DIM_INTERPOLATOR =
new PathInterpolator(.23f, .87f, .52f, -0.11f);
/**
+ * An interpolator used for dimming a task very quickly. Roughly approximates one of the "sharp
+ * rises" of {@link #DIM_INTERPOLATOR}.
+ */
+ public static final PathInterpolator FAST_DIM_INTERPOLATOR =
+ new PathInterpolator(0.23f, 0.87f, 0.83f, 0.83f);
+
+ /**
* Use this interpolator for animating progress values coming from the back callback to get
* the predictive-back-typical decelerate motion.
*
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/BubbleBarLocation.kt b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/BubbleBarLocation.kt
index 481fc7fcb869..6acd9dbe8b91 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/BubbleBarLocation.kt
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/BubbleBarLocation.kt
@@ -70,7 +70,8 @@ enum class BubbleBarLocation : Parcelable {
UpdateSource.A11Y_ACTION_BAR,
UpdateSource.A11Y_ACTION_BUBBLE,
UpdateSource.A11Y_ACTION_EXP_VIEW,
- UpdateSource.APP_ICON_DRAG
+ UpdateSource.APP_ICON_DRAG,
+ UpdateSource.DRAG_TASK,
)
@Retention(AnnotationRetention.SOURCE)
annotation class UpdateSource {
@@ -95,6 +96,9 @@ enum class BubbleBarLocation : Parcelable {
/** Location changed from dragging the application icon to the bubble bar */
const val APP_ICON_DRAG = 7
+
+ /** Location changed from dragging a running task to the bubble bar */
+ const val DRAG_TASK = 8
}
}
}
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DragZone.kt b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DragZone.kt
index 5d346c047123..6eff75c9a479 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DragZone.kt
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DragZone.kt
@@ -31,29 +31,41 @@ sealed interface DragZone {
/** The bounds of this drag zone. */
val bounds: Rect
+ /** The bounds of the drop target associated with this drag zone. */
+ val dropTarget: Rect?
fun contains(x: Int, y: Int) = bounds.contains(x, y)
/** Represents the bubble drag area on the screen. */
- sealed class Bubble(override val bounds: Rect) : DragZone {
- data class Left(override val bounds: Rect, val dropTarget: Rect) : Bubble(bounds)
- data class Right(override val bounds: Rect, val dropTarget: Rect) : Bubble(bounds)
+ sealed class Bubble(override val bounds: Rect, override val dropTarget: Rect) : DragZone {
+ data class Left(override val bounds: Rect, override val dropTarget: Rect) :
+ Bubble(bounds, dropTarget)
+
+ data class Right(override val bounds: Rect, override val dropTarget: Rect) :
+ Bubble(bounds, dropTarget)
}
/** Represents dragging to Desktop Window. */
- data class DesktopWindow(override val bounds: Rect, val dropTarget: Rect) : DragZone
+ data class DesktopWindow(override val bounds: Rect, override val dropTarget: Rect) : DragZone
/** Represents dragging to Full Screen. */
- data class FullScreen(override val bounds: Rect, val dropTarget: Rect) : DragZone
+ data class FullScreen(override val bounds: Rect, override val dropTarget: Rect) : DragZone
/** Represents dragging to dismiss. */
- data class Dismiss(override val bounds: Rect) : DragZone
+ data class Dismiss(override val bounds: Rect) : DragZone {
+ override val dropTarget: Rect? = null
+ }
/** Represents dragging to enter Split or replace a Split app. */
sealed class Split(override val bounds: Rect) : DragZone {
+ override val dropTarget: Rect? = null
+
data class Left(override val bounds: Rect) : Split(bounds)
+
data class Right(override val bounds: Rect) : Split(bounds)
+
data class Top(override val bounds: Rect) : Split(bounds)
+
data class Bottom(override val bounds: Rect) : Split(bounds)
}
}
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DragZoneFactory.kt b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DragZoneFactory.kt
index 909e9d2c4428..1a80b0f29aa9 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DragZoneFactory.kt
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DragZoneFactory.kt
@@ -18,6 +18,7 @@ package com.android.wm.shell.shared.bubbles
import android.content.Context
import android.graphics.Rect
+import android.util.TypedValue
import androidx.annotation.DimenRes
import com.android.wm.shell.shared.R
import com.android.wm.shell.shared.bubbles.DragZoneFactory.SplitScreenModeChecker.SplitScreenMode
@@ -50,6 +51,60 @@ class DragZoneFactory(
private var vSplitFromExpandedViewDragZoneHeightFoldTall = 0
private var vSplitFromExpandedViewDragZoneHeightFoldShort = 0
+ private var fullScreenDropTargetPadding = 0
+ private var desktopWindowDropTargetPaddingSmall = 0
+ private var desktopWindowDropTargetPaddingLarge = 0
+ private var expandedViewDropTargetWidth = 0
+ private var expandedViewDropTargetHeight = 0
+ private var expandedViewDropTargetPaddingBottom = 0
+ private var expandedViewDropTargetPaddingHorizontal = 0
+
+ private val fullScreenDropTarget: Rect
+ get() =
+ Rect(windowBounds).apply {
+ inset(fullScreenDropTargetPadding, fullScreenDropTargetPadding)
+ }
+
+ private val desktopWindowDropTarget: Rect
+ get() =
+ Rect(windowBounds).apply {
+ if (deviceConfig.isLandscape) {
+ inset(
+ /* dx= */ desktopWindowDropTargetPaddingLarge,
+ /* dy= */ desktopWindowDropTargetPaddingSmall
+ )
+ } else {
+ inset(
+ /* dx= */ desktopWindowDropTargetPaddingSmall,
+ /* dy= */ desktopWindowDropTargetPaddingLarge
+ )
+ }
+ }
+
+ private val expandedViewDropTargetLeft: Rect
+ get() =
+ Rect(
+ expandedViewDropTargetPaddingHorizontal,
+ windowBounds.bottom -
+ expandedViewDropTargetPaddingBottom -
+ expandedViewDropTargetHeight,
+ expandedViewDropTargetWidth + expandedViewDropTargetPaddingHorizontal,
+ windowBounds.bottom - expandedViewDropTargetPaddingBottom
+ )
+
+ private val expandedViewDropTargetRight: Rect
+ get() =
+ Rect(
+ windowBounds.right -
+ expandedViewDropTargetPaddingHorizontal -
+ expandedViewDropTargetWidth,
+ windowBounds.bottom -
+ expandedViewDropTargetPaddingBottom -
+ expandedViewDropTargetHeight,
+ windowBounds.right - expandedViewDropTargetPaddingHorizontal,
+ windowBounds.bottom - expandedViewDropTargetPaddingBottom
+ )
+
init {
onConfigurationUpdated()
}
@@ -88,11 +143,32 @@ class DragZoneFactory(
context.resolveDimension(R.dimen.drag_zone_v_split_from_expanded_view_height_fold_tall)
vSplitFromExpandedViewDragZoneHeightFoldShort =
context.resolveDimension(R.dimen.drag_zone_v_split_from_expanded_view_height_fold_short)
+ fullScreenDropTargetPadding =
+ context.resolveDimension(R.dimen.drop_target_full_screen_padding)
+ desktopWindowDropTargetPaddingSmall =
+ context.resolveDimension(R.dimen.drop_target_desktop_window_padding_small)
+ desktopWindowDropTargetPaddingLarge =
+ context.resolveDimension(R.dimen.drop_target_desktop_window_padding_large)
+
+ // TODO b/393172431: Use the shared xml resources once we can easily access them from
+ // launcher
+ expandedViewDropTargetWidth = 364.dpToPx()
+ expandedViewDropTargetHeight = 578.dpToPx()
+ expandedViewDropTargetPaddingBottom = 108.dpToPx()
+ expandedViewDropTargetPaddingHorizontal = 24.dpToPx()
}
private fun Context.resolveDimension(@DimenRes dimension: Int) =
resources.getDimensionPixelSize(dimension)
+ private fun Int.dpToPx() =
+ TypedValue.applyDimension(
+ TypedValue.COMPLEX_UNIT_DIP,
+ this.toFloat(),
+ context.resources.displayMetrics
+ )
+ .toInt()
+
/**
* Creates the list of drag zones for the dragged object.
*
@@ -155,7 +231,7 @@ class DragZoneFactory(
DragZone.Bubble.Left(
bounds =
Rect(0, windowBounds.bottom - dragZoneSize, dragZoneSize, windowBounds.bottom),
- dropTarget = Rect(0, 0, 0, 0),
+ dropTarget = expandedViewDropTargetLeft,
),
DragZone.Bubble.Right(
bounds =
@@ -165,7 +241,7 @@ class DragZoneFactory(
windowBounds.right,
windowBounds.bottom,
),
- dropTarget = Rect(0, 0, 0, 0),
+ dropTarget = expandedViewDropTargetRight,
)
)
}
@@ -174,7 +250,7 @@ class DragZoneFactory(
return listOf(
DragZone.Bubble.Left(
bounds = Rect(0, 0, windowBounds.right / 2, windowBounds.bottom),
- dropTarget = Rect(0, 0, 0, 0),
+ dropTarget = expandedViewDropTargetLeft,
),
DragZone.Bubble.Right(
bounds =
@@ -184,7 +260,7 @@ class DragZoneFactory(
windowBounds.right,
windowBounds.bottom,
),
- dropTarget = Rect(0, 0, 0, 0),
+ dropTarget = expandedViewDropTargetRight,
)
)
}
@@ -198,7 +274,7 @@ class DragZoneFactory(
windowBounds.right / 2 + fullScreenDragZoneWidth / 2,
fullScreenDragZoneHeight
),
- dropTarget = Rect(0, 0, 0, 0)
+ dropTarget = fullScreenDropTarget
)
}
@@ -223,7 +299,7 @@ class DragZoneFactory(
windowBounds.bottom / 2 + desktopWindowDragZoneHeight / 2
)
},
- dropTarget = Rect(0, 0, 0, 0)
+ dropTarget = desktopWindowDropTarget
)
}
@@ -236,7 +312,7 @@ class DragZoneFactory(
windowBounds.right / 2 + desktopWindowFromExpandedViewDragZoneWidth / 2,
windowBounds.bottom / 2 + desktopWindowFromExpandedViewDragZoneHeight / 2
),
- dropTarget = Rect(0, 0, 0, 0)
+ dropTarget = desktopWindowDropTarget
)
}
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DropTargetManager.kt b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DropTargetManager.kt
index 29ce8d90e66f..2dc183f3f707 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DropTargetManager.kt
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DropTargetManager.kt
@@ -16,22 +16,54 @@
package com.android.wm.shell.shared.bubbles
+import android.content.Context
+import android.graphics.Rect
+import android.view.View
+import android.widget.FrameLayout
+import androidx.core.animation.Animator
+import androidx.core.animation.AnimatorListenerAdapter
+import androidx.core.animation.ValueAnimator
+
/**
* Manages animating drop targets in response to dragging bubble icons or bubble expanded views
* across different drag zones.
*/
class DropTargetManager(
+ context: Context,
+ private val container: FrameLayout,
private val isLayoutRtl: Boolean,
- private val dragZoneChangedListener: DragZoneChangedListener
+ private val dragZoneChangedListener: DragZoneChangedListener,
) {
private var state: DragState? = null
+ private val dropTargetView = View(context)
+ private var animator: ValueAnimator? = null
+
+ private companion object {
+ const val ANIMATION_DURATION_MS = 250L
+ }
/** Must be called when a drag gesture is starting. */
fun onDragStarted(draggedObject: DraggedObject, dragZones: List<DragZone>) {
val state = DragState(dragZones, draggedObject)
dragZoneChangedListener.onInitialDragZoneSet(state.initialDragZone)
this.state = state
+ animator?.cancel()
+ setupDropTarget()
+ }
+
+ private fun setupDropTarget() {
+ if (dropTargetView.parent != null) container.removeView(dropTargetView)
+ container.addView(dropTargetView, 0)
+ // TODO b/393173014: set elevation and background
+ dropTargetView.alpha = 0f
+ dropTargetView.scaleX = 1f
+ dropTargetView.scaleY = 1f
+ dropTargetView.translationX = 0f
+ dropTargetView.translationY = 0f
+ // the drop target is added with a width and height of 1 pixel. when it gets resized, we use
+ // set its scale to the width and height of the bounds it should have to avoid layout passes
+ dropTargetView.layoutParams = FrameLayout.LayoutParams(/* width= */ 1, /* height= */ 1)
}
/** Called when the user drags to a new location. */
@@ -42,14 +74,67 @@ class DropTargetManager(
state.currentDragZone = newDragZone
if (oldDragZone != newDragZone) {
dragZoneChangedListener.onDragZoneChanged(from = oldDragZone, to = newDragZone)
+ updateDropTarget()
}
}
/** Called when the drag ended. */
fun onDragEnded() {
+ startFadeAnimation(from = dropTargetView.alpha, to = 0f) {
+ container.removeView(dropTargetView)
+ }
state = null
}
+ private fun updateDropTarget() {
+ val currentDragZone = state?.currentDragZone ?: return
+ val dropTargetBounds = currentDragZone.dropTarget
+ when {
+ dropTargetBounds == null -> startFadeAnimation(from = dropTargetView.alpha, to = 0f)
+ dropTargetView.alpha == 0f -> {
+ dropTargetView.translationX = dropTargetBounds.exactCenterX()
+ dropTargetView.translationY = dropTargetBounds.exactCenterY()
+ dropTargetView.scaleX = dropTargetBounds.width().toFloat()
+ dropTargetView.scaleY = dropTargetBounds.height().toFloat()
+ startFadeAnimation(from = 0f, to = 1f)
+ }
+ else -> startMorphAnimation(dropTargetBounds)
+ }
+ }
+
+ private fun startFadeAnimation(from: Float, to: Float, onEnd: (() -> Unit)? = null) {
+ animator?.cancel()
+ val animator = ValueAnimator.ofFloat(from, to).setDuration(ANIMATION_DURATION_MS)
+ animator.addUpdateListener { _ -> dropTargetView.alpha = animator.animatedValue as Float }
+ if (onEnd != null) {
+ animator.doOnEnd(onEnd)
+ }
+ this.animator = animator
+ animator.start()
+ }
+
+ private fun startMorphAnimation(bounds: Rect) {
+ animator?.cancel()
+ val startAlpha = dropTargetView.alpha
+ val startTx = dropTargetView.translationX
+ val startTy = dropTargetView.translationY
+ val startScaleX = dropTargetView.scaleX
+ val startScaleY = dropTargetView.scaleY
+ val animator = ValueAnimator.ofFloat(0f, 1f).setDuration(ANIMATION_DURATION_MS)
+ animator.addUpdateListener { _ ->
+ val fraction = animator.animatedValue as Float
+ dropTargetView.alpha = startAlpha + (1 - startAlpha) * fraction
+ dropTargetView.translationX = startTx + (bounds.exactCenterX() - startTx) * fraction
+ dropTargetView.translationY = startTy + (bounds.exactCenterY() - startTy) * fraction
+ dropTargetView.scaleX =
+ startScaleX + (bounds.width().toFloat() - startScaleX) * fraction
+ dropTargetView.scaleY =
+ startScaleY + (bounds.height().toFloat() - startScaleY) * fraction
+ }
+ this.animator = animator
+ animator.start()
+ }
+
/** Stores the current drag state. */
private inner class DragState(
private val dragZones: List<DragZone>,
@@ -72,7 +157,18 @@ class DropTargetManager(
interface DragZoneChangedListener {
/** An initial drag zone was set. Called when a drag starts. */
fun onInitialDragZoneSet(dragZone: DragZone)
+
/** Called when the object was dragged to a different drag zone. */
fun onDragZoneChanged(from: DragZone, to: DragZone)
}
+
+ private fun Animator.doOnEnd(onEnd: () -> Unit) {
+ addListener(
+ object : AnimatorListenerAdapter() {
+ override fun onAnimationEnd(animation: Animator) {
+ onEnd()
+ }
+ }
+ )
+ }
}
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeCompatPolicy.kt b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeCompatPolicy.kt
index 126ab3d74689..14338a49ee2f 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeCompatPolicy.kt
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeCompatPolicy.kt
@@ -24,7 +24,6 @@ import android.content.pm.ActivityInfo.OVERRIDE_ENABLE_INSETS_DECOUPLED_CONFIGUR
import android.content.pm.ActivityInfo.OVERRIDE_EXCLUDE_CAPTION_INSETS_FROM_APP_BOUNDS
import android.window.DesktopModeFlags
import com.android.internal.R
-import com.android.window.flags.Flags
/**
* Class to decide whether to apply app compat policies in desktop mode.
@@ -64,7 +63,7 @@ class DesktopModeCompatPolicy(private val context: Context) {
* is enabled.
*/
fun shouldExcludeCaptionFromAppBounds(taskInfo: TaskInfo): Boolean =
- Flags.excludeCaptionFromAppBounds()
+ DesktopModeFlags.EXCLUDE_CAPTION_FROM_APP_BOUNDS.isTrue
&& isAnyForceConsumptionFlagsEnabled()
&& taskInfo.topActivityInfo?.let {
isInsetsCoupledWithConfiguration(it) && (!taskInfo.isResizeable || it.isChangeEnabled(
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 643c1506e4c2..00c446c3da60 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
@@ -27,6 +27,7 @@ import android.hardware.display.DisplayManager;
import android.os.SystemProperties;
import android.view.Display;
import android.view.WindowManager;
+import android.window.DesktopExperienceFlags;
import android.window.DesktopModeFlags;
import com.android.internal.R;
@@ -226,6 +227,7 @@ public class DesktopModeStatus {
return context.getResources().getBoolean(R.bool.config_canInternalDisplayHostDesktops);
}
+
/**
* Return {@code true} if desktop mode dev option should be shown on current device
*/
@@ -239,23 +241,22 @@ public class DesktopModeStatus {
*/
public static boolean canShowDesktopExperienceDevOption(@NonNull Context context) {
return Flags.showDesktopExperienceDevOption()
- && isInternalDisplayEligibleToHostDesktops(context);
+ && isDeviceEligibleForDesktopMode(context);
}
/** Returns if desktop mode dev option should be enabled if there is no user override. */
public static boolean shouldDevOptionBeEnabledByDefault(Context context) {
- return isInternalDisplayEligibleToHostDesktops(context)
- && Flags.enableDesktopWindowingMode();
+ return isDeviceEligibleForDesktopMode(context)
+ && Flags.enableDesktopWindowingMode();
}
/**
* Return {@code true} if desktop mode is enabled and can be entered on the current device.
*/
public static boolean canEnterDesktopMode(@NonNull Context context) {
- return (isInternalDisplayEligibleToHostDesktops(context)
- && DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_MODE.isTrue()
- && (isDesktopModeSupported(context) || !enforceDeviceRestrictions())
- || isDesktopModeEnabledByDevOption(context));
+ return (isDeviceEligibleForDesktopMode(context)
+ && DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_MODE.isTrue())
+ || isDesktopModeEnabledByDevOption(context);
}
/**
@@ -271,7 +272,7 @@ public class DesktopModeStatus {
* frontend implementations).
*/
public static boolean enableMultipleDesktops(@NonNull Context context) {
- return Flags.enableMultipleDesktopsBackend()
+ return DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue()
&& Flags.enableMultipleDesktopsFrontend()
&& canEnterDesktopMode(context);
}
@@ -323,25 +324,34 @@ public class DesktopModeStatus {
}
/**
- * Return {@code true} if desktop sessions is unrestricted and can be host for the device's
- * internal display.
+ * Return {@code true} if desktop mode is unrestricted and is supported on the device.
*/
- public static boolean isInternalDisplayEligibleToHostDesktops(@NonNull Context context) {
- return !enforceDeviceRestrictions() || canInternalDisplayHostDesktops(context) || (
- Flags.enableDesktopModeThroughDevOption() && isDesktopModeDevOptionSupported(
- context));
+ public static boolean isDeviceEligibleForDesktopMode(@NonNull Context context) {
+ if (!enforceDeviceRestrictions()) {
+ return true;
+ }
+ final boolean desktopModeSupported = isDesktopModeSupported(context)
+ && canInternalDisplayHostDesktops(context);
+ final boolean desktopModeSupportedByDevOptions =
+ Flags.enableDesktopModeThroughDevOption()
+ && isDesktopModeDevOptionSupported(context);
+ return desktopModeSupported || desktopModeSupportedByDevOptions;
}
/**
* Return {@code true} if the developer option for desktop mode is unrestricted and is supported
* in the device.
*
- * Note that, if {@link #isInternalDisplayEligibleToHostDesktops(Context)} is true, then
+ * Note that, if {@link #isDeviceEligibleForDesktopMode(Context)} is true, then
* {@link #isDeviceEligibleForDesktopModeDevOption(Context)} is also true.
*/
private static boolean isDeviceEligibleForDesktopModeDevOption(@NonNull Context context) {
- return !enforceDeviceRestrictions() || isDesktopModeSupported(context)
- || isDesktopModeDevOptionSupported(context);
+ if (!enforceDeviceRestrictions()) {
+ return true;
+ }
+ final boolean desktopModeSupported = isDesktopModeSupported(context)
+ && canInternalDisplayHostDesktops(context);
+ return desktopModeSupported || isDesktopModeDevOptionSupported(context);
}
/**
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/split/SplitScreenConstants.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/split/SplitScreenConstants.java
index b48296f5f76a..759e711100c3 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/split/SplitScreenConstants.java
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/split/SplitScreenConstants.java
@@ -262,6 +262,7 @@ public class SplitScreenConstants {
/** Flag applied to a transition change to identify it as a divider bar for animation. */
public static final int FLAG_IS_DIVIDER_BAR = TransitionUtil.FLAG_IS_DIVIDER_BAR;
+ public static final int FLAG_IS_DIM_LAYER = TransitionUtil.FLAG_IS_DIM_LAYER;
public static final String splitPositionToString(@SplitPosition int pos) {
switch (pos) {
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 f51023fcaaf5..58b46d202599 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
@@ -845,6 +845,10 @@ public class BubbleController implements ConfigurationChangeListener,
mLogger.log(onLeft ? BubbleLogger.Event.BUBBLE_BAR_MOVED_LEFT_APP_ICON_DROP
: BubbleLogger.Event.BUBBLE_BAR_MOVED_RIGHT_APP_ICON_DROP);
break;
+ case BubbleBarLocation.UpdateSource.DRAG_TASK:
+ mLogger.log(onLeft ? BubbleLogger.Event.BUBBLE_BAR_MOVED_LEFT_DRAG_TASK
+ : BubbleLogger.Event.BUBBLE_BAR_MOVED_RIGHT_DRAG_TASK);
+ break;
}
}
@@ -1291,6 +1295,11 @@ public class BubbleController implements ConfigurationChangeListener,
mContext.getResources().getDimensionPixelSize(
com.android.internal.R.dimen.importance_ring_stroke_width));
mStackView.onDisplaySizeChanged();
+ // TODO b/392893178: Merge the unfold and the task view transition so that we don't
+ // have to post a delayed runnable to the looper to update the bounds
+ if (mStackView.isExpanded()) {
+ mStackView.postDelayed(() -> mStackView.updateExpandedView(), 500);
+ }
}
if (newConfig.fontScale != mFontScale) {
mFontScale = newConfig.fontScale;
@@ -1598,13 +1607,21 @@ public class BubbleController implements ConfigurationChangeListener,
if (!BubbleAnythingFlagHelper.enableBubbleToFullscreen()) return;
Bubble b = mBubbleData.getOrCreateBubble(taskInfo); // Removes from overflow
ProtoLog.v(WM_SHELL_BUBBLES, "expandStackAndSelectBubble - intent=%s", taskInfo.taskId);
+ BubbleBarLocation location = null;
+ if (dragData != null) {
+ location =
+ dragData.isReleasedOnLeft() ? BubbleBarLocation.LEFT : BubbleBarLocation.RIGHT;
+ }
if (b.isInflated()) {
- mBubbleData.setSelectedBubbleAndExpandStack(b);
+ mBubbleData.setSelectedBubbleAndExpandStack(b, location);
if (dragData != null && dragData.getPendingWct() != null) {
mTransitions.startTransition(TRANSIT_CHANGE,
dragData.getPendingWct(), /* handler= */ null);
}
} else {
+ if (location != null) {
+ setBubbleBarLocation(location, BubbleBarLocation.UpdateSource.DRAG_TASK);
+ }
b.enable(Notification.BubbleMetadata.FLAG_AUTO_EXPAND_BUBBLE);
// Lazy init stack view when a bubble is created
ensureBubbleViewsAndWindowCreated();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleLogger.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleLogger.java
index 831f2271d500..a0c473173bf1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleLogger.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleLogger.java
@@ -156,6 +156,12 @@ public class BubbleLogger {
@UiEvent(doc = "while bubble bar is expanded, switch to another/existing bubble")
BUBBLE_BAR_BUBBLE_SWITCHED(1977),
+ @UiEvent(doc = "bubble bar moved to the left edge of the screen by dragging a task")
+ BUBBLE_BAR_MOVED_LEFT_DRAG_TASK(2146),
+
+ @UiEvent(doc = "bubble bar moved to the right edge of the screen by dragging a task")
+ BUBBLE_BAR_MOVED_RIGHT_DRAG_TASK(2147),
+
// endregion
;
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 dad627f85d95..92724178cf84 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
@@ -3548,7 +3548,7 @@ public class BubbleStackView extends FrameLayout
}
}
- private void updateExpandedView() {
+ void updateExpandedView() {
boolean isOverflowExpanded = mExpandedBubble != null
&& BubbleOverflow.KEY.equals(mExpandedBubble.getKey());
int[] paddings = mPositioner.getExpandedViewContainerPadding(
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTransitions.java
index 6be3c1f18b39..a676f41baafe 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTransitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTransitions.java
@@ -156,14 +156,18 @@ public class BubbleTransitions {
public static class DragData {
private final Rect mBounds;
private final WindowContainerTransaction mPendingWct;
+ private final boolean mReleasedOnLeft;
/**
* @param bounds bounds of the dragged task when the drag was released
* @param wct pending operations to be applied when finishing the drag
+ * @param releasedOnLeft true if the bubble was released in the left drop target
*/
- public DragData(@Nullable Rect bounds, @Nullable WindowContainerTransaction wct) {
+ public DragData(@Nullable Rect bounds, @Nullable WindowContainerTransaction wct,
+ boolean releasedOnLeft) {
mBounds = bounds;
mPendingWct = wct;
+ mReleasedOnLeft = releasedOnLeft;
}
/**
@@ -181,6 +185,13 @@ public class BubbleTransitions {
public WindowContainerTransaction getPendingWct() {
return mPendingWct;
}
+
+ /**
+ * @return true if the bubble was released in the left drop target
+ */
+ public boolean isReleasedOnLeft() {
+ return mReleasedOnLeft;
+ }
}
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java
index 4c3bde9b2b3a..97184c859d4d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java
@@ -67,6 +67,7 @@ public class DisplayController {
private final SparseArray<DisplayRecord> mDisplays = new SparseArray<>();
private final ArrayList<OnDisplaysChangedListener> mDisplayChangedListeners = new ArrayList<>();
private final Map<Integer, RectF> mUnpopulatedDisplayBounds = new HashMap<>();
+ private DisplayTopology mDisplayTopology;
public DisplayController(Context context, IWindowManager wmService, ShellInit shellInit,
ShellExecutor mainExecutor, DisplayManager displayManager) {
@@ -157,6 +158,7 @@ public class DisplayController {
for (int i = 0; i < mDisplays.size(); ++i) {
listener.onDisplayAdded(mDisplays.keyAt(i));
}
+ listener.onTopologyChanged(mDisplayTopology);
}
}
@@ -245,6 +247,7 @@ public class DisplayController {
if (topology == null) {
return;
}
+ mDisplayTopology = topology;
SparseArray<RectF> absoluteBounds = topology.getAbsoluteBounds();
mUnpopulatedDisplayBounds.clear();
for (int i = 0; i < absoluteBounds.size(); ++i) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/WindowContainerTransactionSupplier.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/WindowContainerTransactionSupplier.kt
new file mode 100644
index 000000000000..a1d700af5569
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/WindowContainerTransactionSupplier.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.common
+
+import android.window.WindowContainerTransaction
+import com.android.wm.shell.dagger.WMSingleton
+import java.util.function.Supplier
+import javax.inject.Inject
+
+/**
+ * An Injectable [Supplier<WindowContainerTransaction>]. This can be used in place of kotlin default
+ * parameters values [builder = ::WindowContainerTransaction] which requires the
+ * [@JvmOverloads] annotation to make this available in Java.
+ * This can be used every time a component needs the dependency to the default [Supplier] for
+ * [WindowContainerTransaction]s.
+ */
+@WMSingleton
+class WindowContainerTransactionSupplier @Inject constructor(
+) : Supplier<WindowContainerTransaction> {
+ override fun get(): WindowContainerTransaction = WindowContainerTransaction()
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/CenterParallaxSpec.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/CenterParallaxSpec.java
new file mode 100644
index 000000000000..fb2a324375b6
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/CenterParallaxSpec.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.common.split;
+
+import android.graphics.Point;
+import android.graphics.Rect;
+
+/**
+ * Calculation class, used when
+ * {@link com.android.wm.shell.common.split.SplitLayout#PARALLAX_ALIGN_CENTER} is the desired
+ * parallax effect.
+ */
+public class CenterParallaxSpec implements ParallaxSpec {
+ @Override
+ public void getParallax(Point retreatingOut, Point advancingOut, int position,
+ DividerSnapAlgorithm snapAlgorithm, boolean isLeftRightSplit, Rect displayBounds,
+ Rect retreatingSurface, Rect retreatingContent, Rect advancingSurface,
+ Rect advancingContent, int dimmingSide, boolean topLeftShrink) {
+ if (isLeftRightSplit) {
+ retreatingOut.x = (retreatingSurface.width() - retreatingContent.width()) / 2;
+ } else {
+ retreatingOut.y = (retreatingSurface.height() - retreatingContent.height()) / 2;
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DismissingParallaxSpec.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DismissingParallaxSpec.java
new file mode 100644
index 000000000000..39ecbb379d7d
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DismissingParallaxSpec.java
@@ -0,0 +1,75 @@
+/*
+ * 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.split;
+
+import static android.view.WindowManager.DOCKED_INVALID;
+
+import static com.android.wm.shell.shared.animation.Interpolators.SLOWDOWN_INTERPOLATOR;
+
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.view.WindowManager;
+
+/**
+ * Calculation class, used when
+ * {@link com.android.wm.shell.common.split.SplitLayout#PARALLAX_DISMISSING} is the desired parallax
+ * effect.
+ */
+public class DismissingParallaxSpec implements ParallaxSpec {
+ @Override
+ public void getParallax(Point retreatingOut, Point advancingOut, int position,
+ DividerSnapAlgorithm snapAlgorithm, boolean isLeftRightSplit, Rect displayBounds,
+ Rect retreatingSurface, Rect retreatingContent, Rect advancingSurface,
+ Rect advancingContent, int dimmingSide, boolean topLeftShrink) {
+ if (dimmingSide == DOCKED_INVALID) {
+ return;
+ }
+
+ float progressTowardScreenEdge =
+ Math.max(0, Math.min(snapAlgorithm.calculateDismissingFraction(position), 1f));
+ int totalDismissingDistance = 0;
+ if (position < snapAlgorithm.getFirstSplitTarget().getPosition()) {
+ totalDismissingDistance = snapAlgorithm.getDismissStartTarget().getPosition()
+ - snapAlgorithm.getFirstSplitTarget().getPosition();
+ } else if (position > snapAlgorithm.getLastSplitTarget().getPosition()) {
+ totalDismissingDistance = snapAlgorithm.getLastSplitTarget().getPosition()
+ - snapAlgorithm.getDismissEndTarget().getPosition();
+ }
+
+ float parallaxFraction =
+ calculateParallaxDismissingFraction(progressTowardScreenEdge, dimmingSide);
+ if (isLeftRightSplit) {
+ retreatingOut.x = (int) (parallaxFraction * totalDismissingDistance);
+ } else {
+ retreatingOut.y = (int) (parallaxFraction * totalDismissingDistance);
+ }
+ }
+
+ /**
+ * @return for a specified {@code fraction}, this returns an adjusted value that simulates a
+ * slowing down parallax effect
+ */
+ private float calculateParallaxDismissingFraction(float fraction, int dockSide) {
+ float result = SLOWDOWN_INTERPOLATOR.getInterpolation(fraction) / 3.5f;
+
+ // Less parallax at the top, just because.
+ if (dockSide == WindowManager.DOCKED_TOP) {
+ result /= 2f;
+ }
+ return result;
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerSnapAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerSnapAlgorithm.java
index 2f5afcaa907b..5b2dd97a338f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerSnapAlgorithm.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerSnapAlgorithm.java
@@ -465,5 +465,9 @@ public class DividerSnapAlgorithm {
this.snapPosition = snapPosition;
this.distanceMultiplier = distanceMultiplier;
}
+
+ public int getPosition() {
+ return position;
+ }
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/FlexParallaxSpec.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/FlexParallaxSpec.java
new file mode 100644
index 000000000000..9fa162164e0e
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/FlexParallaxSpec.java
@@ -0,0 +1,172 @@
+/*
+ * 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.split;
+
+import static android.view.WindowManager.DOCKED_BOTTOM;
+import static android.view.WindowManager.DOCKED_INVALID;
+import static android.view.WindowManager.DOCKED_LEFT;
+import static android.view.WindowManager.DOCKED_RIGHT;
+import static android.view.WindowManager.DOCKED_TOP;
+
+import static com.android.wm.shell.common.split.ResizingEffectPolicy.DEFAULT_OFFSCREEN_DIM;
+import static com.android.wm.shell.shared.animation.Interpolators.DIM_INTERPOLATOR;
+import static com.android.wm.shell.shared.animation.Interpolators.FAST_DIM_INTERPOLATOR;
+
+import android.graphics.Point;
+import android.graphics.Rect;
+
+/**
+ * Calculation class, used when {@link com.android.wm.shell.common.split.SplitLayout#PARALLAX_FLEX}
+ * is the desired parallax effect.
+ */
+public class FlexParallaxSpec implements ParallaxSpec {
+ final Rect mTempRect = new Rect();
+
+ @Override
+ public int getDimmingSide(int position, DividerSnapAlgorithm snapAlgorithm,
+ boolean isLeftRightSplit) {
+ if (position < snapAlgorithm.getMiddleTarget().getPosition()) {
+ return isLeftRightSplit ? DOCKED_LEFT : DOCKED_TOP;
+ } else if (position > snapAlgorithm.getMiddleTarget().getPosition()) {
+ return isLeftRightSplit ? DOCKED_RIGHT : DOCKED_BOTTOM;
+ }
+ return DOCKED_INVALID;
+ }
+
+ /**
+ * Calculates the amount of dim to apply to a task surface moving offscreen in flexible split.
+ * In flexible split, there are two dimming "behaviors".
+ * 1) "slow dim": when moving the divider from the middle of the screen to a target at 10% or
+ * 90%, we dim the app slightly as it moves partially offscreen.
+ * 2) "fast dim": when moving the divider from a side snap target further toward the screen
+ * edge, we dim the app rapidly as it approaches the dismiss point.
+ * @return 0f = no dim applied. 1f = full black.
+ */
+ public float getDimValue(int position, DividerSnapAlgorithm snapAlgorithm) {
+ int startDismissPos = snapAlgorithm.getDismissStartTarget().getPosition();
+ int firstTargetPos = snapAlgorithm.getFirstSplitTarget().getPosition();
+ int middleTargetPos = snapAlgorithm.getMiddleTarget().getPosition();
+ int lastTargetPos = snapAlgorithm.getLastSplitTarget().getPosition();
+ int endDismissPos = snapAlgorithm.getDismissEndTarget().getPosition();
+ float progress;
+
+ if (startDismissPos <= position && position < firstTargetPos) {
+ // Divider is on the left/top (between 0% and 10% of screen), "fast dim" as it moves
+ // toward the screen edge
+ progress = (float) (firstTargetPos - position) / (firstTargetPos - startDismissPos);
+ return fastDim(progress);
+ } else if (firstTargetPos <= position && position < middleTargetPos) {
+ // Divider is between 10% and 50%, "slow dim" as it moves toward the left/top target
+ progress = (float) (middleTargetPos - position) / (middleTargetPos - firstTargetPos);
+ return slowDim(progress);
+ } else if (middleTargetPos <= position && position < lastTargetPos) {
+ // Divider is between 50% and 90%, "slow dim" as it moves toward the right/bottom target
+ progress = (float) (position - middleTargetPos) / (lastTargetPos - middleTargetPos);
+ return slowDim(progress);
+ } else if (lastTargetPos <= position && position <= endDismissPos) {
+ // Divider is on the right/bottom (between 90% and 100% of screen), "fast dim" as it
+ // moves toward screen edge
+ progress = (float) (position - lastTargetPos) / (endDismissPos - lastTargetPos);
+ return fastDim(progress);
+ }
+ return 0f;
+ }
+
+ /**
+ * Used by {@link #getDimValue} to determine the amount to dim an app. Starts at zero and ramps
+ * up to the default amount of dimming for an offscreen app,
+ * {@link ResizingEffectPolicy#DEFAULT_OFFSCREEN_DIM}.
+ */
+ private float slowDim(float progress) {
+ return DIM_INTERPOLATOR.getInterpolation(progress) * DEFAULT_OFFSCREEN_DIM;
+ }
+
+ /**
+ * Used by {@link #getDimValue} to determine the amount to dim an app. Starts at
+ * {@link ResizingEffectPolicy#DEFAULT_OFFSCREEN_DIM} and ramps up to 100% dim (full black).
+ */
+ private float fastDim(float progress) {
+ return DEFAULT_OFFSCREEN_DIM + (FAST_DIM_INTERPOLATOR.getInterpolation(progress)
+ * (1 - DEFAULT_OFFSCREEN_DIM));
+ }
+
+ @Override
+ public void getParallax(Point retreatingOut, Point advancingOut, int position,
+ DividerSnapAlgorithm snapAlgorithm, boolean isLeftRightSplit, Rect displayBounds,
+ Rect retreatingSurface, Rect retreatingContent, Rect advancingSurface,
+ Rect advancingContent, int dimmingSide, boolean topLeftShrink) {
+ // Whether an app is getting pushed offscreen by the divider.
+ boolean isRetreatingOffscreen = !displayBounds.contains(retreatingSurface);
+ // Whether an app was getting pulled onscreen at the beginning of the drag.
+ boolean advancingSideStartedOffscreen = !displayBounds.contains(advancingContent);
+
+ // The simpler case when an app gets pushed offscreen (e.g. 50:50 -> 90:10)
+ if (isRetreatingOffscreen && !advancingSideStartedOffscreen) {
+ // On the left side, we use parallax to simulate the contents sticking to the
+ // divider. This is because surfaces naturally expand to the bottom and right,
+ // so when a surface's area expands, the contents stick to the left. This is
+ // correct behavior on the right-side surface, but not the left.
+ if (topLeftShrink) {
+ if (isLeftRightSplit) {
+ retreatingOut.x = retreatingSurface.width() - retreatingContent.width();
+ } else {
+ retreatingOut.y = retreatingSurface.height() - retreatingContent.height();
+ }
+ }
+ // All other cases (e.g. 10:90 -> 50:50, 10:90 -> 90:10, 10:90 -> dismiss)
+ } else {
+ mTempRect.set(retreatingSurface);
+ Point rootOffset = new Point();
+ // 10:90 -> 50:50, 10:90, or dismiss right
+ if (advancingSideStartedOffscreen) {
+ // We have to handle a complicated case here to keep the parallax smooth.
+ // When the divider crosses the 50% mark, the retreating-side app surface
+ // will start expanding offscreen. This is expected and unavoidable, but
+ // makes the parallax look disjointed. In order to preserve the illusion,
+ // we add another offset (rootOffset) to simulate the surface staying
+ // onscreen.
+ if (mTempRect.intersect(displayBounds)) {
+ if (retreatingSurface.left < displayBounds.left) {
+ rootOffset.x = displayBounds.left - retreatingSurface.left;
+ }
+ if (retreatingSurface.top < displayBounds.top) {
+ rootOffset.y = displayBounds.top - retreatingSurface.top;
+ }
+ }
+
+ // On the left side, we again have to simulate the contents sticking to the
+ // divider.
+ if (!topLeftShrink) {
+ if (isLeftRightSplit) {
+ advancingOut.x = advancingSurface.width() - advancingContent.width();
+ } else {
+ advancingOut.y = advancingSurface.height() - advancingContent.height();
+ }
+ }
+ }
+
+ // In all these cases, the shrinking app also receives a center parallax.
+ if (isLeftRightSplit) {
+ retreatingOut.x = rootOffset.x
+ + ((mTempRect.width() - retreatingContent.width()) / 2);
+ } else {
+ retreatingOut.y = rootOffset.y
+ + ((mTempRect.height() - retreatingContent.height()) / 2);
+ }
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/NoParallaxSpec.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/NoParallaxSpec.java
new file mode 100644
index 000000000000..043b2880f28b
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/NoParallaxSpec.java
@@ -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.wm.shell.common.split;
+
+import android.graphics.Point;
+import android.graphics.Rect;
+
+/**
+ * Calculation class, used when {@link com.android.wm.shell.common.split.SplitLayout#PARALLAX_NONE}
+ * is the desired parallax effect.
+ */
+public class NoParallaxSpec implements ParallaxSpec {
+ @Override
+ public void getParallax(Point retreatingOut, Point advancingOut, int position,
+ DividerSnapAlgorithm snapAlgorithm, boolean isLeftRightSplit, Rect displayBounds,
+ Rect retreatingSurface, Rect retreatingContent, Rect advancingSurface,
+ Rect advancingContent, int dimmingSide, boolean topLeftShrink) {
+ // no-op
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/ParallaxSpec.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/ParallaxSpec.java
new file mode 100644
index 000000000000..84d849b3c1f9
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/ParallaxSpec.java
@@ -0,0 +1,62 @@
+/*
+ * 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.common.split;
+
+import static android.view.WindowManager.DOCKED_BOTTOM;
+import static android.view.WindowManager.DOCKED_INVALID;
+import static android.view.WindowManager.DOCKED_LEFT;
+import static android.view.WindowManager.DOCKED_RIGHT;
+import static android.view.WindowManager.DOCKED_TOP;
+
+import static com.android.wm.shell.shared.animation.Interpolators.DIM_INTERPOLATOR;
+
+import android.graphics.Point;
+import android.graphics.Rect;
+
+/**
+ * Default interface for a set of calculation classes, used for calculating various parallax and
+ * dimming effects in split screen.
+ */
+public interface ParallaxSpec {
+ /** Returns an int indicating which side of the screen is being dimmed (if any). */
+ default int getDimmingSide(int position, DividerSnapAlgorithm snapAlgorithm,
+ boolean isLeftRightSplit) {
+ if (position < snapAlgorithm.getFirstSplitTarget().getPosition()) {
+ return isLeftRightSplit ? DOCKED_LEFT : DOCKED_TOP;
+ } else if (position > snapAlgorithm.getLastSplitTarget().getPosition()) {
+ return isLeftRightSplit ? DOCKED_RIGHT : DOCKED_BOTTOM;
+ }
+ return DOCKED_INVALID;
+ }
+
+ /** Returns the dim amount that we'll apply to the app surface. 0f = no dim, 1f = full black */
+ default float getDimValue(int position, DividerSnapAlgorithm snapAlgorithm) {
+ float progressTowardScreenEdge =
+ Math.max(0, Math.min(snapAlgorithm.calculateDismissingFraction(position), 1f));
+ return DIM_INTERPOLATOR.getInterpolation(progressTowardScreenEdge);
+ }
+
+ /**
+ * Calculates the amount to offset app surfaces to create nice parallax effects. Writes to
+ * {@link ResizingEffectPolicy#mRetreatingSideParallax} and
+ * {@link ResizingEffectPolicy#mAdvancingSideParallax}.
+ */
+ void getParallax(Point retreatingOut, Point advancingOut, int position,
+ DividerSnapAlgorithm snapAlgorithm, boolean isLeftRightSplit, Rect displayBounds,
+ Rect retreatingSurface, Rect retreatingContent, Rect advancingSurface,
+ Rect advancingContent, int dimmingSide, boolean topLeftShrink);
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/ResizingEffectPolicy.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/ResizingEffectPolicy.java
index 3f76fd0220ff..e2e1f9698a90 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/ResizingEffectPolicy.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/ResizingEffectPolicy.java
@@ -26,27 +26,32 @@ import static com.android.wm.shell.common.split.SplitLayout.PARALLAX_ALIGN_CENTE
import static com.android.wm.shell.common.split.SplitLayout.PARALLAX_DISMISSING;
import static com.android.wm.shell.common.split.SplitLayout.PARALLAX_FLEX;
import static com.android.wm.shell.common.split.SplitLayout.PARALLAX_NONE;
-import static com.android.wm.shell.shared.animation.Interpolators.DIM_INTERPOLATOR;
-import static com.android.wm.shell.shared.animation.Interpolators.SLOWDOWN_INTERPOLATOR;
import android.graphics.Point;
import android.graphics.Rect;
import android.view.SurfaceControl;
-import android.view.WindowManager;
/**
* This class governs how and when parallax and dimming effects are applied to task surfaces,
* usually when the divider is being moved around by the user (or during an animation).
*/
class ResizingEffectPolicy {
+ /** The default amount to dim an app that is partially offscreen. */
+ public static float DEFAULT_OFFSCREEN_DIM = 0.32f;
+
private final SplitLayout mSplitLayout;
/** The parallax algorithm we are currently using. */
private final int mParallaxType;
+ /**
+ * A convenience class, corresponding to {@link #mParallaxType}, that performs all the
+ * calculations for parallax and dimming values.
+ */
+ private final ParallaxSpec mParallaxSpec;
int mShrinkSide = DOCKED_INVALID;
// The current dismissing side.
- int mDismissingSide = DOCKED_INVALID;
+ int mDimmingSide = DOCKED_INVALID;
/**
* A {@link Point} that stores a single x and y value, representing the parallax translation
@@ -62,7 +67,7 @@ class ResizingEffectPolicy {
final Point mAdvancingSideParallax = new Point();
// The dimming value to hint the dismissing side and progress.
- float mDismissingDimValue = 0.0f;
+ float mDimValue = 0.0f;
/**
* Content bounds for the app that the divider is moving toward. This is the content that is
@@ -95,35 +100,38 @@ class ResizingEffectPolicy {
ResizingEffectPolicy(int parallaxType, SplitLayout splitLayout) {
mParallaxType = parallaxType;
mSplitLayout = splitLayout;
+ switch (mParallaxType) {
+ case PARALLAX_DISMISSING:
+ mParallaxSpec = new DismissingParallaxSpec();
+ break;
+ case PARALLAX_ALIGN_CENTER:
+ mParallaxSpec = new CenterParallaxSpec();
+ break;
+ case PARALLAX_FLEX:
+ mParallaxSpec = new FlexParallaxSpec();
+ break;
+ case PARALLAX_NONE:
+ default:
+ mParallaxSpec = new NoParallaxSpec();
+ break;
+ }
}
/**
- * Calculates the desired parallax values and stores them in {@link #mRetreatingSideParallax}
- * and {@link #mAdvancingSideParallax}. These values will be then be applied in
- * {@link #adjustRootSurface}.
- *
- * @param position The divider's position on the screen (x-coordinate in left-right split,
- * y-coordinate in top-bottom split).
+ * Calculates the desired parallax and dimming values for a task surface and stores them in
+ * {@link #mRetreatingSideParallax}, {@link #mAdvancingSideParallax}, and
+ * {@link #mDimValue} These values will be then be applied in
+ * {@link #adjustRootSurface} and {@link #adjustDimSurface} respectively.
*/
void applyDividerPosition(
int position, boolean isLeftRightSplit, DividerSnapAlgorithm snapAlgorithm) {
- mDismissingSide = DOCKED_INVALID;
+ mDimmingSide = DOCKED_INVALID;
mRetreatingSideParallax.set(0, 0);
mAdvancingSideParallax.set(0, 0);
- mDismissingDimValue = 0;
+ mDimValue = 0;
Rect displayBounds = mSplitLayout.getRootBounds();
- int totalDismissingDistance = 0;
- if (position < snapAlgorithm.getFirstSplitTarget().position) {
- mDismissingSide = isLeftRightSplit ? DOCKED_LEFT : DOCKED_TOP;
- totalDismissingDistance = snapAlgorithm.getDismissStartTarget().position
- - snapAlgorithm.getFirstSplitTarget().position;
- } else if (position > snapAlgorithm.getLastSplitTarget().position) {
- mDismissingSide = isLeftRightSplit ? DOCKED_RIGHT : DOCKED_BOTTOM;
- totalDismissingDistance = snapAlgorithm.getLastSplitTarget().position
- - snapAlgorithm.getDismissEndTarget().position;
- }
-
+ // Figure out which side is shrinking, and assign retreating/advancing bounds
final boolean topLeftShrink = isLeftRightSplit
? position < mSplitLayout.getTopLeftContentBounds().right
: position < mSplitLayout.getTopLeftContentBounds().bottom;
@@ -141,106 +149,20 @@ class ResizingEffectPolicy {
mAdvancingSurface.set(mSplitLayout.getTopLeftBounds());
}
- if (mDismissingSide != DOCKED_INVALID) {
- float fraction =
- Math.max(0, Math.min(snapAlgorithm.calculateDismissingFraction(position), 1f));
- mDismissingDimValue = DIM_INTERPOLATOR.getInterpolation(fraction);
- if (mParallaxType == PARALLAX_DISMISSING) {
- fraction = calculateParallaxDismissingFraction(fraction, mDismissingSide);
- if (isLeftRightSplit) {
- mRetreatingSideParallax.x = (int) (fraction * totalDismissingDistance);
- } else {
- mRetreatingSideParallax.y = (int) (fraction * totalDismissingDistance);
- }
- }
- }
-
- if (mParallaxType == PARALLAX_ALIGN_CENTER) {
- if (isLeftRightSplit) {
- mRetreatingSideParallax.x =
- (mRetreatingSurface.width() - mRetreatingContent.width()) / 2;
- } else {
- mRetreatingSideParallax.y =
- (mRetreatingSurface.height() - mRetreatingContent.height()) / 2;
- }
- } else if (mParallaxType == PARALLAX_FLEX) {
- // Whether an app is getting pushed offscreen by the divider.
- boolean isRetreatingOffscreen = !displayBounds.contains(mRetreatingSurface);
- // Whether an app was getting pulled onscreen at the beginning of the drag.
- boolean advancingSideStartedOffscreen = !displayBounds.contains(mAdvancingContent);
+ // Figure out if we should be dimming one side
+ mDimmingSide = mParallaxSpec.getDimmingSide(position, snapAlgorithm, isLeftRightSplit);
- // The simpler case when an app gets pushed offscreen (e.g. 50:50 -> 90:10)
- if (isRetreatingOffscreen && !advancingSideStartedOffscreen) {
- // On the left side, we use parallax to simulate the contents sticking to the
- // divider. This is because surfaces naturally expand to the bottom and right,
- // so when a surface's area expands, the contents stick to the left. This is
- // correct behavior on the right-side surface, but not the left.
- if (topLeftShrink) {
- if (isLeftRightSplit) {
- mRetreatingSideParallax.x =
- mRetreatingSurface.width() - mRetreatingContent.width();
- } else {
- mRetreatingSideParallax.y =
- mRetreatingSurface.height() - mRetreatingContent.height();
- }
- }
- // All other cases (e.g. 10:90 -> 50:50, 10:90 -> 90:10, 10:90 -> dismiss)
- } else {
- mTempRect.set(mRetreatingSurface);
- Point rootOffset = new Point();
- // 10:90 -> 50:50, 10:90, or dismiss right
- if (advancingSideStartedOffscreen) {
- // We have to handle a complicated case here to keep the parallax smooth.
- // When the divider crosses the 50% mark, the retreating-side app surface
- // will start expanding offscreen. This is expected and unavoidable, but
- // makes the parallax look disjointed. In order to preserve the illusion,
- // we add another offset (rootOffset) to simulate the surface staying
- // onscreen.
- mTempRect.intersect(displayBounds);
- if (mRetreatingSurface.left < displayBounds.left) {
- rootOffset.x = displayBounds.left - mRetreatingSurface.left;
- }
- if (mRetreatingSurface.top < displayBounds.top) {
- rootOffset.y = displayBounds.top - mRetreatingSurface.top;
- }
-
- // On the left side, we again have to simulate the contents sticking to the
- // divider.
- if (!topLeftShrink) {
- if (isLeftRightSplit) {
- mAdvancingSideParallax.x =
- mAdvancingSurface.width() - mAdvancingContent.width();
- } else {
- mAdvancingSideParallax.y =
- mAdvancingSurface.height() - mAdvancingContent.height();
- }
- }
- }
-
- // In all these cases, the shrinking app also receives a center parallax.
- if (isLeftRightSplit) {
- mRetreatingSideParallax.x = rootOffset.x
- + ((mTempRect.width() - mRetreatingContent.width()) / 2);
- } else {
- mRetreatingSideParallax.y = rootOffset.y
- + ((mTempRect.height() - mRetreatingContent.height()) / 2);
- }
- }
+ // If so, calculate dimming
+ if (mDimmingSide != DOCKED_INVALID) {
+ mDimValue = mParallaxSpec.getDimValue(position, snapAlgorithm);
}
- }
- /**
- * @return for a specified {@code fraction}, this returns an adjusted value that simulates a
- * slowing down parallax effect
- */
- private float calculateParallaxDismissingFraction(float fraction, int dockSide) {
- float result = SLOWDOWN_INTERPOLATOR.getInterpolation(fraction) / 3.5f;
-
- // Less parallax at the top, just because.
- if (dockSide == WindowManager.DOCKED_TOP) {
- result /= 2f;
- }
- return result;
+ // Calculate parallax and modify mRetreatingSideParallax and mAdvancingSideParallax, for use
+ // in adjustRootSurface().
+ mParallaxSpec.getParallax(mRetreatingSideParallax, mAdvancingSideParallax, position,
+ snapAlgorithm, isLeftRightSplit, displayBounds, mRetreatingSurface,
+ mRetreatingContent, mAdvancingSurface, mAdvancingContent, mDimmingSide,
+ topLeftShrink);
}
/** Applies the calculated parallax and dimming values to task surfaces. */
@@ -250,7 +172,7 @@ class ResizingEffectPolicy {
SurfaceControl advancingLeash = null;
if (mParallaxType == PARALLAX_DISMISSING) {
- switch (mDismissingSide) {
+ switch (mDimmingSide) {
case DOCKED_TOP:
case DOCKED_LEFT:
retreatingLeash = leash1;
@@ -303,14 +225,17 @@ class ResizingEffectPolicy {
void adjustDimSurface(SurfaceControl.Transaction t,
SurfaceControl dimLayer1, SurfaceControl dimLayer2) {
SurfaceControl targetDimLayer;
- switch (mDismissingSide) {
+ SurfaceControl oppositeDimLayer;
+ switch (mDimmingSide) {
case DOCKED_TOP:
case DOCKED_LEFT:
targetDimLayer = dimLayer1;
+ oppositeDimLayer = dimLayer2;
break;
case DOCKED_BOTTOM:
case DOCKED_RIGHT:
targetDimLayer = dimLayer2;
+ oppositeDimLayer = dimLayer1;
break;
case DOCKED_INVALID:
default:
@@ -318,7 +243,9 @@ class ResizingEffectPolicy {
t.setAlpha(dimLayer2, 0).hide(dimLayer2);
return;
}
- t.setAlpha(targetDimLayer, mDismissingDimValue)
- .setVisibility(targetDimLayer, mDismissingDimValue > 0.001f);
+ t.setAlpha(targetDimLayer, mDimValue)
+ .setVisibility(targetDimLayer, mDimValue > 0.001f);
+ t.setAlpha(oppositeDimLayer, 0f)
+ .setVisibility(oppositeDimLayer, false);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
index bd89f5cf45f6..708e26cc5546 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
@@ -128,6 +128,8 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
// The touch layer is on a stage root, and is sibling with things like the app activity itself
// and the app veil. We want it to be above all those.
public static final int RESTING_TOUCH_LAYER = Integer.MAX_VALUE;
+ // The dim layer is also on the stage root, and stays under the touch layer.
+ public static final int RESTING_DIM_LAYER = RESTING_TOUCH_LAYER - 1;
// Animation specs for the swap animation
private static final int SWAP_ANIMATION_TOTAL_DURATION = 500;
@@ -1201,6 +1203,12 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
// Resets layer of divider bar to make sure it is always on top.
t.setLayer(dividerLeash, RESTING_DIVIDER_LAYER);
}
+ if (dimLayer1 != null) {
+ t.setLayer(dimLayer1, RESTING_DIM_LAYER);
+ }
+ if (dimLayer2 != null) {
+ t.setLayer(dimLayer2, RESTING_DIM_LAYER);
+ }
copyTopLeftRefBounds(mTempRect);
t.setPosition(leash1, mTempRect.left, mTempRect.top)
.setWindowCrop(leash1, mTempRect.width(), mTempRect.height());
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitState.java
index d1d133d16ae4..ad0e7fc187e9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitState.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitState.java
@@ -57,4 +57,9 @@ public class SplitState {
public List<RectF> getLayout(@SplitScreenState int state) {
return mSplitSpec.getSpec(state);
}
+
+ /** Returns the layout associated with the current split state. */
+ public List<RectF> getCurrentLayout() {
+ return getLayout(mState);
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/events/ReachabilityGestureListener.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/events/ReachabilityGestureListener.kt
new file mode 100644
index 000000000000..bdffcf51e7d4
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/events/ReachabilityGestureListener.kt
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.compatui.letterbox.events
+
+import android.graphics.Rect
+import android.view.GestureDetector
+import android.view.MotionEvent
+import android.window.WindowContainerToken
+import com.android.wm.shell.common.WindowContainerTransactionSupplier
+import com.android.wm.shell.transition.Transitions
+import com.android.wm.shell.transition.Transitions.TRANSIT_MOVE_LETTERBOX_REACHABILITY
+
+/**
+ * [GestureDetector.SimpleOnGestureListener] implementation which receives events from the
+ * Letterbox Input surface, understands the type of event and filter them based on the current
+ * letterbox position.
+ */
+class ReachabilityGestureListener(
+ private val taskId: Int,
+ private val token: WindowContainerToken?,
+ private val transitions: Transitions,
+ private val animationHandler: Transitions.TransitionHandler,
+ private val wctSupplier: WindowContainerTransactionSupplier
+) : GestureDetector.SimpleOnGestureListener() {
+
+ // The current letterbox bounds. Double tap events are ignored when happening in these bounds.
+ private val activityBounds = Rect()
+
+ override fun onDoubleTap(e: MotionEvent): Boolean {
+ val x = e.rawX.toInt()
+ val y = e.rawY.toInt()
+ if (!activityBounds.contains(x, y)) {
+ val wct = wctSupplier.get().apply {
+ setReachabilityOffset(token!!, taskId, x, y)
+ }
+ transitions.startTransition(
+ TRANSIT_MOVE_LETTERBOX_REACHABILITY,
+ wct,
+ animationHandler
+ )
+ return true
+ }
+ return false
+ }
+
+ /**
+ * Updates the bounds for the letterboxed activity.
+ */
+ fun updateActivityBounds(newActivityBounds: Rect) {
+ activityBounds.set(newActivityBounds)
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/events/ReachabilityGestureListenerFactory.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/events/ReachabilityGestureListenerFactory.kt
new file mode 100644
index 000000000000..5e9fe09bc840
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/events/ReachabilityGestureListenerFactory.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.compatui.letterbox.events
+
+import android.window.WindowContainerToken
+import com.android.wm.shell.common.WindowContainerTransactionSupplier
+import com.android.wm.shell.dagger.WMSingleton
+import com.android.wm.shell.transition.Transitions
+import javax.inject.Inject
+
+/**
+ * A Factory for [ReachabilityGestureListener].
+ */
+@WMSingleton
+class ReachabilityGestureListenerFactory @Inject constructor(
+ private val transitions: Transitions,
+ private val animationHandler: Transitions.TransitionHandler,
+ private val wctSupplier: WindowContainerTransactionSupplier
+) {
+ /**
+ * @return a [ReachabilityGestureListener] implementation to listen to double tap events and
+ * creating the related [WindowContainerTransaction] to handle the transition.
+ */
+ fun createReachabilityGestureListener(
+ taskId: Int,
+ token: WindowContainerToken?
+ ): ReachabilityGestureListener =
+ ReachabilityGestureListener(taskId, token, transitions, animationHandler, wctSupplier)
+}
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 35475c7ee4ce..2fd8c27d5970 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
@@ -759,7 +759,6 @@ public abstract class WMShellModule {
FocusTransitionObserver focusTransitionObserver,
DesktopModeEventLogger desktopModeEventLogger,
DesktopModeUiEventLogger desktopModeUiEventLogger,
- DesktopTilingDecorViewModel desktopTilingDecorViewModel,
DesktopWallpaperActivityTokenProvider desktopWallpaperActivityTokenProvider,
Optional<BubbleController> bubbleController,
OverviewToDesktopTransitionObserver overviewToDesktopTransitionObserver,
@@ -798,7 +797,6 @@ public abstract class WMShellModule {
mainHandler,
desktopModeEventLogger,
desktopModeUiEventLogger,
- desktopTilingDecorViewModel,
desktopWallpaperActivityTokenProvider,
bubbleController,
overviewToDesktopTransitionObserver,
@@ -990,7 +988,8 @@ public abstract class WMShellModule {
DesktopModeUiEventLogger desktopModeUiEventLogger,
WindowDecorTaskResourceLoader taskResourceLoader,
RecentsTransitionHandler recentsTransitionHandler,
- DesktopModeCompatPolicy desktopModeCompatPolicy
+ DesktopModeCompatPolicy desktopModeCompatPolicy,
+ DesktopTilingDecorViewModel desktopTilingDecorViewModel
) {
if (!DesktopModeStatus.canEnterDesktopModeOrShowAppHandle(context)) {
return Optional.empty();
@@ -1006,7 +1005,8 @@ public abstract class WMShellModule {
desktopTasksLimiter, appHandleEducationController, appToWebEducationController,
windowDecorCaptionHandleRepository, activityOrientationChangeHandler,
focusTransitionObserver, desktopModeEventLogger, desktopModeUiEventLogger,
- taskResourceLoader, recentsTransitionHandler, desktopModeCompatPolicy));
+ taskResourceLoader, recentsTransitionHandler, desktopModeCompatPolicy,
+ desktopTilingDecorViewModel));
}
@WMSingleton
@@ -1278,10 +1278,10 @@ public abstract class WMShellModule {
@WMSingleton
@Provides
static DesktopWindowingEducationTooltipController
- provideDesktopWindowingEducationTooltipController(
- Context context,
- AdditionalSystemViewContainer.Factory additionalSystemViewContainerFactory,
- DisplayController displayController) {
+ provideDesktopWindowingEducationTooltipController(
+ Context context,
+ AdditionalSystemViewContainer.Factory additionalSystemViewContainerFactory,
+ DisplayController displayController) {
return new DesktopWindowingEducationTooltipController(
context, additionalSystemViewContainerFactory, displayController);
}
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 7d80ee5f3bb6..f8b18f29c797 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
@@ -53,6 +53,7 @@ import com.android.wm.shell.pip2.phone.PipTransition;
import com.android.wm.shell.pip2.phone.PipTransitionState;
import com.android.wm.shell.pip2.phone.PipUiStateChangeController;
import com.android.wm.shell.shared.annotations.ShellMainThread;
+import com.android.wm.shell.splitscreen.SplitScreenController;
import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
@@ -85,11 +86,13 @@ public abstract class Pip2Module {
@NonNull PipDisplayLayoutState pipDisplayLayoutState,
@NonNull PipUiStateChangeController pipUiStateChangeController,
DisplayController displayController,
+ Optional<SplitScreenController> splitScreenControllerOptional,
PipDesktopState pipDesktopState) {
return new PipTransition(context, shellInit, shellTaskOrganizer, transitions,
pipBoundsState, null, pipBoundsAlgorithm, pipTaskListener,
pipScheduler, pipStackListenerController, pipDisplayLayoutState,
- pipUiStateChangeController, displayController, pipDesktopState);
+ pipUiStateChangeController, displayController, splitScreenControllerOptional,
+ pipDesktopState);
}
@WMSingleton
@@ -140,9 +143,10 @@ public abstract class Pip2Module {
PipBoundsState pipBoundsState,
@ShellMainThread ShellExecutor mainExecutor,
PipTransitionState pipTransitionState,
+ Optional<SplitScreenController> splitScreenControllerOptional,
PipDesktopState pipDesktopState) {
return new PipScheduler(context, pipBoundsState, mainExecutor, pipTransitionState,
- pipDesktopState);
+ splitScreenControllerOptional, pipDesktopState);
}
@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 6f455df6cfec..c38558d7bde9 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
@@ -26,6 +26,7 @@ import android.provider.Settings.Global.DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERN
import android.view.Display.DEFAULT_DISPLAY
import android.view.IWindowManager
import android.view.WindowManager.TRANSIT_CHANGE
+import android.window.DesktopExperienceFlags
import android.window.WindowContainerTransaction
import com.android.internal.protolog.ProtoLog
import com.android.window.flags.Flags
@@ -62,7 +63,7 @@ class DesktopDisplayEventHandler(
private fun onInit() {
displayController.addDisplayWindowListener(this)
- if (Flags.enableMultipleDesktopsBackend()) {
+ if (DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue()) {
desktopTasksController.onDeskRemovedListener = this
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandler.kt
index c9b3ec0d3a11..1f7edb413908 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandler.kt
@@ -71,9 +71,31 @@ class DesktopMixedTransitionHandler(
wct: WindowContainerTransaction?,
) = freeformTaskTransitionHandler.startWindowingModeTransition(targetWindowingMode, wct)
- /** Delegates starting minimized mode transition to [FreeformTaskTransitionHandler]. */
- override fun startMinimizedModeTransition(wct: WindowContainerTransaction?): IBinder =
- freeformTaskTransitionHandler.startMinimizedModeTransition(wct)
+ /**
+ * Starts a minimize transition for [taskId], with [isLastTask] which is true if the task going
+ * to be minimized is the last visible task.
+ */
+ override fun startMinimizedModeTransition(
+ wct: WindowContainerTransaction?,
+ taskId: Int,
+ isLastTask: Boolean,
+ ): IBinder {
+ if (!DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_EXIT_BY_MINIMIZE_TRANSITION_BUGFIX.isTrue) {
+ return freeformTaskTransitionHandler.startMinimizedModeTransition(
+ wct,
+ taskId,
+ isLastTask,
+ )
+ }
+ requireNotNull(wct)
+ return transitions
+ .startTransition(Transitions.TRANSIT_MINIMIZE, wct, /* handler= */ this)
+ .also { transition ->
+ pendingMixedTransitions.add(
+ PendingMixedTransition.Minimize(transition, taskId, isLastTask)
+ )
+ }
+ }
/** Delegates starting PiP transition to [FreeformTaskTransitionHandler]. */
override fun startPipTransition(wct: WindowContainerTransaction?): IBinder =
@@ -298,7 +320,15 @@ class DesktopMixedTransitionHandler(
finishTransaction: SurfaceControl.Transaction,
finishCallback: TransitionFinishCallback,
): Boolean {
- if (!DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION.isTrue) return false
+ val shouldAnimate =
+ if (info.type == Transitions.TRANSIT_MINIMIZE) {
+ DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_EXIT_BY_MINIMIZE_TRANSITION_BUGFIX.isTrue
+ } else {
+ DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION.isTrue
+ }
+ if (!shouldAnimate) {
+ return false
+ }
val minimizeChange = findTaskChange(info, pending.minimizingTask)
if (minimizeChange == null) {
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 03bc42f08d59..0cc8a6a5c1a3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeShellCommandHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeShellCommandHandler.kt
@@ -16,7 +16,7 @@
package com.android.wm.shell.desktopmode
-import com.android.window.flags.Flags
+import android.window.DesktopExperienceFlags
import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource.UNKNOWN
import com.android.wm.shell.sysui.ShellCommandHandler
import java.io.PrintWriter
@@ -56,7 +56,7 @@ class DesktopModeShellCommandHandler(private val controller: DesktopTasksControl
pw.println("Error: task id should be an integer")
return false
}
- if (!Flags.enableMultipleDesktopsBackend()) {
+ if (!DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) {
return controller.moveTaskToDefaultDeskAndActivate(taskId, transitionSource = UNKNOWN)
}
if (args.size < 3) {
@@ -95,7 +95,7 @@ class DesktopModeShellCommandHandler(private val controller: DesktopTasksControl
}
private fun runCreateDesk(args: Array<String>, pw: PrintWriter): Boolean {
- if (!Flags.enableMultipleDesktopsBackend()) {
+ if (!DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) {
pw.println("Not supported.")
return false
}
@@ -116,7 +116,7 @@ class DesktopModeShellCommandHandler(private val controller: DesktopTasksControl
}
private fun runActivateDesk(args: Array<String>, pw: PrintWriter): Boolean {
- if (!Flags.enableMultipleDesktopsBackend()) {
+ if (!DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) {
pw.println("Not supported.")
return false
}
@@ -137,7 +137,7 @@ class DesktopModeShellCommandHandler(private val controller: DesktopTasksControl
}
private fun runRemoveDesk(args: Array<String>, pw: PrintWriter): Boolean {
- if (!Flags.enableMultipleDesktopsBackend()) {
+ if (!DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) {
pw.println("Not supported.")
return false
}
@@ -158,7 +158,7 @@ class DesktopModeShellCommandHandler(private val controller: DesktopTasksControl
}
private fun runRemoveAllDesks(args: Array<String>, pw: PrintWriter): Boolean {
- if (!Flags.enableMultipleDesktopsBackend()) {
+ if (!DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) {
pw.println("Not supported.")
return false
}
@@ -167,7 +167,7 @@ class DesktopModeShellCommandHandler(private val controller: DesktopTasksControl
}
private fun runMoveTaskToFront(args: Array<String>, pw: PrintWriter): Boolean {
- if (!Flags.enableMultipleDesktopsBackend()) {
+ if (!DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) {
pw.println("Not supported.")
return false
}
@@ -188,7 +188,7 @@ class DesktopModeShellCommandHandler(private val controller: DesktopTasksControl
}
private fun runMoveTaskOutOfDesk(args: Array<String>, pw: PrintWriter): Boolean {
- if (!Flags.enableMultipleDesktopsBackend()) {
+ if (!DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) {
pw.println("Not supported.")
return false
}
@@ -204,12 +204,12 @@ class DesktopModeShellCommandHandler(private val controller: DesktopTasksControl
pw.println("Error: task id should be an integer")
return false
}
- pw.println("Not implemented.")
- return false
+ controller.moveToFullscreen(taskId, transitionSource = UNKNOWN)
+ return true
}
private fun runCanCreateDesk(args: Array<String>, pw: PrintWriter): Boolean {
- if (!Flags.enableMultipleDesktopsBackend()) {
+ if (!DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) {
pw.println("Not supported.")
return false
}
@@ -225,7 +225,7 @@ class DesktopModeShellCommandHandler(private val controller: DesktopTasksControl
}
private fun runGetActiveDeskId(args: Array<String>, pw: PrintWriter): Boolean {
- if (!Flags.enableMultipleDesktopsBackend()) {
+ if (!DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) {
pw.println("Not supported.")
return false
}
@@ -246,7 +246,7 @@ class DesktopModeShellCommandHandler(private val controller: DesktopTasksControl
}
override fun printShellCommandHelp(pw: PrintWriter, prefix: String) {
- if (!Flags.enableMultipleDesktopsBackend()) {
+ if (!DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) {
pw.println("$prefix moveTaskToDesk <taskId> ")
pw.println("$prefix Move a task with given id to desktop mode.")
pw.println("$prefix moveToNextDisplay <taskId> ")
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt
index 9b850de6fede..c5ee3137e5ba 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt
@@ -33,6 +33,7 @@ import android.util.Size
import com.android.wm.shell.R
import com.android.wm.shell.common.DisplayController
import com.android.wm.shell.common.DisplayLayout
+import kotlin.math.ceil
val DESKTOP_MODE_INITIAL_BOUNDS_SCALE: Float =
SystemProperties.getInt("persist.wm.debug.desktop_mode_initial_bounds_scale", 75) / 100f
@@ -190,22 +191,22 @@ fun maximizeSizeGivenAspectRatio(
val finalWidth: Int
// Get orientation either through top activity or task's orientation
if (taskInfo.hasPortraitTopActivity()) {
- val tempWidth = (targetHeight / aspectRatio).toInt()
+ val tempWidth = ceil(targetHeight / aspectRatio).toInt()
if (tempWidth <= targetWidth) {
finalHeight = targetHeight
finalWidth = tempWidth
} else {
finalWidth = targetWidth
- finalHeight = (finalWidth * aspectRatio).toInt()
+ finalHeight = ceil(finalWidth * aspectRatio).toInt()
}
} else {
- val tempWidth = (targetHeight * aspectRatio).toInt()
+ val tempWidth = ceil(targetHeight * aspectRatio).toInt()
if (tempWidth <= targetWidth) {
finalHeight = targetHeight
finalWidth = tempWidth
} else {
finalWidth = targetWidth
- finalHeight = (finalWidth / aspectRatio).toInt()
+ finalHeight = ceil(finalWidth / aspectRatio).toInt()
}
}
return Size(finalWidth, finalHeight + captionInsets)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java
index aecbf1a23cb2..99f052832a51 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java
@@ -214,6 +214,13 @@ public class DesktopModeVisualIndicator {
return result;
}
+ /**
+ * Returns the [DragStartState] of the visual indicator.
+ */
+ DragStartState getDragStartState() {
+ return mDragStartState;
+ }
+
@VisibleForTesting
Region calculateFullscreenRegion(DisplayLayout layout, int captionHeight) {
final Region region = new Region();
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 4777e7f93bc9..eba1be517147 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
@@ -22,6 +22,7 @@ import android.util.ArrayMap
import android.util.ArraySet
import android.util.SparseArray
import android.view.Display.INVALID_DISPLAY
+import android.window.DesktopExperienceFlags
import android.window.DesktopModeFlags
import androidx.core.util.forEach
import androidx.core.util.valueIterator
@@ -137,7 +138,7 @@ class DesktopRepository(
private var desktopGestureExclusionExecutor: Executor? = null
private val desktopData: DesktopData =
- if (Flags.enableMultipleDesktopsBackend()) {
+ if (DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) {
MultiDesktopData()
} else {
SingleDesktopData()
@@ -226,10 +227,19 @@ class DesktopRepository(
desktopData.setActiveDesk(displayId = displayId, deskId = deskId)
}
+ /** Sets the given desk as inactive if it was active. */
+ fun setDeskInactive(deskId: Int) {
+ desktopData.setDeskInactive(deskId)
+ }
+
/** Returns the id of the active desk in the given display, if any. */
@VisibleForTesting
fun getActiveDeskId(displayId: Int): Int? = desktopData.getActiveDesk(displayId)?.deskId
+ /** Returns the id of the desk to which this task belongs. */
+ fun getDeskIdForTask(taskId: Int): Int? =
+ desktopData.desksSequence().find { desk -> desk.activeTasks.contains(taskId) }?.deskId
+
/**
* Adds task with [taskId] to the list of freeform tasks on [displayId]'s active desk.
*
@@ -270,20 +280,40 @@ class DesktopRepository(
@VisibleForTesting
fun removeActiveTask(taskId: Int, excludedDeskId: Int? = null) {
val affectedDisplays = mutableSetOf<Int>()
- desktopData.forAllDesks { displayId, desk ->
- if (desk.deskId != excludedDeskId && desk.activeTasks.remove(taskId)) {
- logD(
- "Removed active task=%d displayId=%d deskId=%d",
- taskId,
- displayId,
- desk.deskId,
- )
- affectedDisplays.add(displayId)
+ desktopData
+ .desksSequence()
+ .filter { desk -> desk.displayId != excludedDeskId }
+ .forEach { desk ->
+ val removed = removeActiveTaskFromDesk(desk.deskId, taskId, notifyListeners = false)
+ if (removed) {
+ logD(
+ "Removed active task=%d displayId=%d deskId=%d",
+ taskId,
+ desk.displayId,
+ desk.deskId,
+ )
+ affectedDisplays.add(desk.displayId)
+ }
}
- }
affectedDisplays.forEach { displayId -> updateActiveTasksListeners(displayId) }
}
+ private fun removeActiveTaskFromDesk(
+ deskId: Int,
+ taskId: Int,
+ notifyListeners: Boolean = true,
+ ): Boolean {
+ val desk = desktopData.getDesk(deskId) ?: return false
+ if (desk.activeTasks.remove(taskId)) {
+ logD("Removed active task=%d from deskId=%d", taskId, desk.deskId)
+ if (notifyListeners) {
+ updateActiveTasksListeners(desk.displayId)
+ }
+ return true
+ }
+ return false
+ }
+
/**
* Adds given task to the closing task list for [displayId]'s active desk.
*
@@ -322,10 +352,22 @@ class DesktopRepository(
fun isActiveTask(taskId: Int) = desksSequence().any { taskId in it.activeTasks }
+ @VisibleForTesting
+ fun isActiveTaskInDesk(taskId: Int, deskId: Int): Boolean {
+ val desk = desktopData.getDesk(deskId) ?: return false
+ return taskId in desk.activeTasks
+ }
+
fun isClosingTask(taskId: Int) = desksSequence().any { taskId in it.closingTasks }
fun isVisibleTask(taskId: Int) = desksSequence().any { taskId in it.visibleTasks }
+ @VisibleForTesting
+ fun isVisibleTaskInDesk(taskId: Int, deskId: Int): Boolean {
+ val desk = desktopData.getDesk(deskId) ?: return false
+ return taskId in desk.visibleTasks
+ }
+
fun isMinimizedTask(taskId: Int) = desksSequence().any { taskId in it.minimizedTasks }
/**
@@ -415,12 +457,19 @@ class DesktopRepository(
/** Removes task from visible tasks of all desks except [excludedDeskId]. */
private fun removeVisibleTask(taskId: Int, excludedDeskId: Int? = null) {
desktopData.forAllDesks { displayId, desk ->
- if (desk.deskId != excludedDeskId && desk.visibleTasks.remove(taskId)) {
- notifyVisibleTaskListeners(displayId, desk.visibleTasks.size)
+ if (desk.deskId != excludedDeskId) {
+ removeVisibleTaskFromDesk(deskId = desk.deskId, taskId = taskId)
}
}
}
+ private fun removeVisibleTaskFromDesk(deskId: Int, taskId: Int) {
+ val desk = desktopData.getDesk(deskId) ?: return
+ if (desk.visibleTasks.remove(taskId)) {
+ notifyVisibleTaskListeners(desk.displayId, desk.visibleTasks.size)
+ }
+ }
+
/**
* Updates visibility of a freeform task with [taskId] on [displayId] and notifies listeners.
*
@@ -576,15 +625,26 @@ class DesktopRepository(
/**
* Set whether the given task is the full-immersive task in this display's active desk.
*
- * TODO: b/389960283 - add explicit [deskId] argument.
+ * TODO: b/389960283 - consider forcing callers to use [setTaskInFullImmersiveStateInDesk] with
+ * an explicit desk id instead of using this function and defaulting to the active one.
*/
fun setTaskInFullImmersiveState(displayId: Int, taskId: Int, immersive: Boolean) {
- val desktopData = desktopData.getActiveDesk(displayId) ?: return
+ val activeDesk = desktopData.getActiveDesk(displayId) ?: return
+ setTaskInFullImmersiveStateInDesk(
+ deskId = activeDesk.deskId,
+ taskId = taskId,
+ immersive = immersive,
+ )
+ }
+
+ /** Sets whether the given task is the full-immersive task in the given desk. */
+ fun setTaskInFullImmersiveStateInDesk(deskId: Int, taskId: Int, immersive: Boolean) {
+ val desk = desktopData.getDesk(deskId) ?: return
if (immersive) {
- desktopData.fullImmersiveTaskId = taskId
+ desk.fullImmersiveTaskId = taskId
} else {
- if (desktopData.fullImmersiveTaskId == taskId) {
- desktopData.fullImmersiveTaskId = null
+ if (desk.fullImmersiveTaskId == taskId) {
+ desk.fullImmersiveTaskId = null
}
}
}
@@ -674,7 +734,8 @@ class DesktopRepository(
/**
* Minimizes the task for [taskId] and [displayId]'s active display.
*
- * TODO: b/389960283 - add explicit [deskId] argument.
+ * TODO: b/389960283 - consider forcing callers to use [minimizeTaskInDesk] with an explicit
+ * desk id instead of using this function and defaulting to the active one.
*/
fun minimizeTask(displayId: Int, taskId: Int) {
if (displayId == INVALID_DISPLAY) {
@@ -683,32 +744,41 @@ class DesktopRepository(
getDisplayIdForTask(taskId)?.let { minimizeTask(it, taskId) }
?: logW("Minimize task: No display id found for task: taskId=%d", taskId)
return
- } else {
- logD("Minimize Task: display=%d, task=%d", displayId, taskId)
- desktopData.getActiveDesk(displayId)?.minimizedTasks?.add(taskId)
- ?: logD("Minimize task: No active desk found for task: taskId=%d", taskId)
}
- updateTask(displayId, taskId, isVisible = false)
+ val deskId = desktopData.getActiveDesk(displayId)?.deskId
+ if (deskId == null) {
+ logD("Minimize task: No active desk found for task: taskId=%d", taskId)
+ return
+ }
+ minimizeTaskInDesk(displayId, deskId, taskId)
+ }
+
+ /** Minimizes the task in its desk. */
+ @VisibleForTesting
+ fun minimizeTaskInDesk(displayId: Int, deskId: Int, taskId: Int) {
+ logD("Minimize Task: displayId=%d deskId=%d, task=%d", displayId, deskId, taskId)
+ desktopData.getDesk(deskId)?.minimizedTasks?.add(taskId)
+ ?: logD("Minimize task: No active desk found for task: taskId=%d", taskId)
+ updateTaskInDesk(displayId, deskId, taskId, isVisible = false)
if (DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_PERSISTENCE.isTrue()) {
- updatePersistentRepository(displayId)
+ updatePersistentRepositoryForDesk(deskId)
}
}
/**
* Unminimizes the task for [taskId] and [displayId].
*
- * TODO: b/389960283 - consider adding an explicit [deskId] argument.
+ * TODO: b/389960283 - consider using [unminimizeTaskFromDesk] instead.
*/
fun unminimizeTask(displayId: Int, taskId: Int) {
logD("Unminimize Task: display=%d, task=%d", displayId, taskId)
- var removed = false
- desktopData.forAllDesks(displayId) { desk ->
- if (desk.minimizedTasks.remove(taskId)) {
- removed = true
- }
- }
- if (!removed) {
- logW("Unminimize Task: display=%d, task=%d, no task data", displayId, taskId)
+ desktopData.forAllDesks(displayId) { desk -> unminimizeTaskFromDesk(desk.deskId, taskId) }
+ }
+
+ private fun unminimizeTaskFromDesk(deskId: Int, taskId: Int) {
+ logD("Unminimize Task: deskId=%d, taskId=%d", deskId, taskId)
+ if (desktopData.getDesk(deskId)?.minimizedTasks?.remove(taskId) != true) {
+ logW("Unminimize Task: deskId=%d, taskId=%d, no task data", deskId, taskId)
}
}
@@ -729,7 +799,7 @@ class DesktopRepository(
* Removes [taskId] from the respective display. If [INVALID_DISPLAY], the original display id
* will be looked up from the task id.
*
- * TODO: b/389960283 - consider adding an explicit [deskId] argument.
+ * TODO: b/389960283 - consider using [removeTaskFromDesk] instead.
*/
fun removeTask(displayId: Int, taskId: Int) {
logD("Removes freeform task: taskId=%d", taskId)
@@ -745,24 +815,33 @@ class DesktopRepository(
private fun removeTaskFromDisplay(displayId: Int, taskId: Int) {
logD("Removes freeform task: taskId=%d, displayId=%d", taskId, displayId)
desktopData.forAllDesks(displayId) { desk ->
- if (desk.freeformTasksInZOrder.remove(taskId)) {
- logD(
- "Remaining freeform tasks in desk: %d, tasks: %s",
- desk.deskId,
- desk.freeformTasksInZOrder.toDumpString(),
- )
- }
+ removeTaskFromDesk(deskId = desk.deskId, taskId = taskId)
}
+ }
+
+ /** Removes the given task from the given desk. */
+ fun removeTaskFromDesk(deskId: Int, taskId: Int) {
+ logD("removeTaskFromDesk: deskId=%d, taskId=%d", deskId, taskId)
+ // TODO: b/362720497 - consider not clearing bounds on any removal, such as when moving
+ // it between desks. It might be better to allow restoring to the previous bounds as long
+ // as they're valid (probably valid if in the same display).
boundsBeforeMaximizeByTaskId.remove(taskId)
boundsBeforeFullImmersiveByTaskId.remove(taskId)
- // Remove task from unminimized task if it is minimized.
- unminimizeTask(displayId, taskId)
+ val desk = desktopData.getDesk(deskId) ?: return
+ if (desk.freeformTasksInZOrder.remove(taskId)) {
+ logD(
+ "Remaining freeform tasks in desk: %d, tasks: %s",
+ desk.deskId,
+ desk.freeformTasksInZOrder.toDumpString(),
+ )
+ }
+ unminimizeTaskFromDesk(deskId, taskId)
// Mark task as not in immersive if it was immersive.
- setTaskInFullImmersiveState(displayId = displayId, taskId = taskId, immersive = false)
- removeActiveTask(taskId)
- removeVisibleTask(taskId)
- if (DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_PERSISTENCE.isTrue()) {
- updatePersistentRepository(displayId)
+ setTaskInFullImmersiveStateInDesk(deskId = deskId, taskId = taskId, immersive = false)
+ removeActiveTaskFromDesk(deskId = deskId, taskId = taskId)
+ removeVisibleTaskFromDesk(deskId = deskId, taskId = taskId)
+ if (DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_PERSISTENCE.isTrue) {
+ updatePersistentRepositoryForDesk(desk.deskId)
}
}
@@ -832,24 +911,29 @@ class DesktopRepository(
private fun updatePersistentRepository(displayId: Int) {
val desks = desktopData.desksSequence(displayId).map { desk -> desk.deepCopy() }.toList()
mainCoroutineScope.launch {
- desks.forEach { desk ->
- try {
- persistentRepository.addOrUpdateDesktop(
- // Use display id as desk id for now since only once desk per display
- // is supported.
- userId = userId,
- desktopId = desk.deskId,
- visibleTasks = desk.visibleTasks,
- minimizedTasks = desk.minimizedTasks,
- freeformTasksInZOrder = desk.freeformTasksInZOrder,
- )
- } catch (exception: Exception) {
- logE(
- "An exception occurred while updating the persistent repository \n%s",
- exception.stackTrace,
- )
- }
- }
+ desks.forEach { desk -> updatePersistentRepositoryForDesk(desk) }
+ }
+ }
+
+ private fun updatePersistentRepositoryForDesk(deskId: Int) {
+ val desk = desktopData.getDesk(deskId)?.deepCopy() ?: return
+ mainCoroutineScope.launch { updatePersistentRepositoryForDesk(desk) }
+ }
+
+ private suspend fun updatePersistentRepositoryForDesk(desk: Desk) {
+ try {
+ persistentRepository.addOrUpdateDesktop(
+ userId = userId,
+ desktopId = desk.deskId,
+ visibleTasks = desk.visibleTasks,
+ minimizedTasks = desk.minimizedTasks,
+ freeformTasksInZOrder = desk.freeformTasksInZOrder,
+ )
+ } catch (exception: Exception) {
+ logE(
+ "An exception occurred while updating the persistent repository \n%s",
+ exception.stackTrace,
+ )
}
}
@@ -866,21 +950,27 @@ class DesktopRepository(
desktopData
.desksSequence()
.groupBy { it.displayId }
- .forEach { (displayId, desks) ->
+ .map { (displayId, desks) ->
+ Triple(displayId, desktopData.getActiveDesk(displayId)?.deskId, desks)
+ }
+ .forEach { (displayId, activeDeskId, desks) ->
pw.println("${prefix}Display #$displayId:")
+ pw.println("${innerPrefix}activeDesk=$activeDeskId")
+ pw.println("${innerPrefix}desks:")
+ val desksPrefix = "$innerPrefix "
desks.forEach { desk ->
- pw.println("${innerPrefix}Desk #${desk.deskId}:")
- pw.print("$innerPrefix activeTasks=")
+ pw.println("${desksPrefix}Desk #${desk.deskId}:")
+ pw.print("$desksPrefix activeTasks=")
pw.println(desk.activeTasks.toDumpString())
- pw.print("$innerPrefix visibleTasks=")
+ pw.print("$desksPrefix visibleTasks=")
pw.println(desk.visibleTasks.toDumpString())
- pw.print("$innerPrefix freeformTasksInZOrder=")
+ pw.print("$desksPrefix freeformTasksInZOrder=")
pw.println(desk.freeformTasksInZOrder.toDumpString())
- pw.print("$innerPrefix minimizedTasks=")
+ pw.print("$desksPrefix minimizedTasks=")
pw.println(desk.minimizedTasks.toDumpString())
- pw.print("$innerPrefix fullImmersiveTaskId=")
+ pw.print("$desksPrefix fullImmersiveTaskId=")
pw.println(desk.fullImmersiveTaskId)
- pw.print("$innerPrefix topTransparentFullscreenTaskId=")
+ pw.print("$desksPrefix topTransparentFullscreenTaskId=")
pw.println(desk.topTransparentFullscreenTaskId)
}
}
@@ -910,6 +1000,9 @@ class DesktopRepository(
/** Sets the given desk as the active desk in the given display. */
fun setActiveDesk(displayId: Int, deskId: Int)
+ /** Sets the desk as inactive if it was active. */
+ fun setDeskInactive(deskId: Int)
+
/**
* Returns the default desk in the given display. Useful when the system wants to activate a
* desk but doesn't care about which one it activates (e.g. when putting a window into a
@@ -990,6 +1083,11 @@ class DesktopRepository(
// existence of visible desktop windows, among other factors.
}
+ override fun setDeskInactive(deskId: Int) {
+ // No-op, in single-desk setups, which desktop is "active" is determined by the
+ // existence of visible desktop windows, among other factors.
+ }
+
override fun getDefaultDesk(displayId: Int): Desk = getDesk(deskId = displayId)
override fun getAllActiveDesks(): Set<Desk> =
@@ -1058,6 +1156,14 @@ class DesktopRepository(
display.activeDeskId = desk.deskId
}
+ override fun setDeskInactive(deskId: Int) {
+ desktopDisplays.forEach { id, display ->
+ if (display.activeDeskId == deskId) {
+ display.activeDeskId = null
+ }
+ }
+ }
+
override fun getDefaultDesk(displayId: Int): Desk? {
val display = desktopDisplays[displayId] ?: return null
return display.orderedDesks.find { it.deskId == display.activeDeskId }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTaskChangeListener.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTaskChangeListener.kt
index 4d87b2189115..e831d5eecdc2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTaskChangeListener.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTaskChangeListener.kt
@@ -42,6 +42,12 @@ class DesktopTaskChangeListener(private val desktopUserRepositories: DesktopUser
desktopUserRepositories.getProfile(taskInfo.userId)
if (!desktopRepository.isActiveTask(taskInfo.taskId)) return
+ // TODO: b/394281403 - with multiple desks, it's possible to have a non-freeform task
+ // inside a desk, so this should be decoupled from windowing mode.
+ // Also, changes in/out of desks are handled by the [DesksTransitionObserver], which has
+ // more specific information about the desk involved in the transition, which might be
+ // more accurate than assuming it's always the default/active desk in the display, as this
+ // method does.
// Case 1: Freeform task is changed in Desktop Mode.
if (isFreeformTask(taskInfo)) {
if (taskInfo.isVisible) {
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 f17b680f6fae..f7fe694be8e2 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
@@ -41,6 +41,7 @@ import android.os.Handler
import android.os.IBinder
import android.os.SystemProperties
import android.os.UserHandle
+import android.util.Slog
import android.view.Display.DEFAULT_DISPLAY
import android.view.DragEvent
import android.view.MotionEvent
@@ -53,6 +54,7 @@ import android.view.WindowManager.TRANSIT_OPEN
import android.view.WindowManager.TRANSIT_PIP
import android.view.WindowManager.TRANSIT_TO_FRONT
import android.widget.Toast
+import android.window.DesktopExperienceFlags
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
@@ -136,7 +138,6 @@ import com.android.wm.shell.sysui.UserChangeListener
import com.android.wm.shell.transition.OneShotRemoteHandler
import com.android.wm.shell.transition.Transitions
import com.android.wm.shell.transition.Transitions.TransitionFinishCallback
-import com.android.wm.shell.windowdecor.DesktopModeWindowDecoration
import com.android.wm.shell.windowdecor.DragPositioningCallbackUtility
import com.android.wm.shell.windowdecor.MoveToDesktopAnimator
import com.android.wm.shell.windowdecor.OnTaskRepositionAnimationListener
@@ -144,7 +145,7 @@ import com.android.wm.shell.windowdecor.OnTaskResizeAnimationListener
import com.android.wm.shell.windowdecor.extension.isFullscreen
import com.android.wm.shell.windowdecor.extension.isMultiWindow
import com.android.wm.shell.windowdecor.extension.requestingImmersive
-import com.android.wm.shell.windowdecor.tiling.DesktopTilingDecorViewModel
+import com.android.wm.shell.windowdecor.tiling.SnapEventHandler
import java.io.PrintWriter
import java.util.Optional
import java.util.concurrent.Executor
@@ -152,6 +153,16 @@ import java.util.concurrent.TimeUnit
import java.util.function.Consumer
import kotlin.jvm.optionals.getOrNull
+/**
+ * A callback to be invoked when a transition is started via |Transitions.startTransition| with the
+ * transition binder token that it produces.
+ *
+ * Useful when multiple components are appending WCT operations to a single transition that is
+ * started outside of their control, and each of them wants to track the transition lifecycle
+ * independently by cross-referencing the transition token with future ready-transitions.
+ */
+typealias RunOnTransitStart = (IBinder) -> Unit
+
/** Handles moving tasks in and out of desktop */
class DesktopTasksController(
private val context: Context,
@@ -184,7 +195,6 @@ class DesktopTasksController(
@ShellMainThread private val handler: Handler,
private val desktopModeEventLogger: DesktopModeEventLogger,
private val desktopModeUiEventLogger: DesktopModeUiEventLogger,
- private val desktopTilingDecorViewModel: DesktopTilingDecorViewModel,
private val desktopWallpaperActivityTokenProvider: DesktopWallpaperActivityTokenProvider,
private val bubbleController: Optional<BubbleController>,
private val overviewToDesktopTransitionObserver: OverviewToDesktopTransitionObserver,
@@ -204,7 +214,9 @@ class DesktopTasksController(
private var userId: Int
private val desktopModeShellCommandHandler: DesktopModeShellCommandHandler =
DesktopModeShellCommandHandler(this)
+
private val mOnAnimationFinishedCallback = { releaseVisualIndicator() }
+ private lateinit var snapEventHandler: SnapEventHandler
private val dragToDesktopStateListener =
object : DragToDesktopStateListener {
override fun onCommitToDesktopAnimationStart() {
@@ -269,7 +281,7 @@ class DesktopTasksController(
RecentsTransitionStateListener.stateToString(state),
)
recentsTransitionState = state
- desktopTilingDecorViewModel.onOverviewAnimationStateChange(
+ snapEventHandler.onOverviewAnimationStateChange(
RecentsTransitionStateListener.isAnimating(state)
)
}
@@ -300,6 +312,11 @@ class DesktopTasksController(
dragToDesktopTransitionHandler.setSplitScreenController(controller)
}
+ /** Setter to handle snap events */
+ fun setSnapEventHandler(handler: SnapEventHandler) {
+ snapEventHandler = handler
+ }
+
/** Returns the transition type for the given remote transition. */
private fun transitionType(remoteTransition: RemoteTransition?): Int {
if (remoteTransition == null) {
@@ -423,7 +440,7 @@ class DesktopTasksController(
/** Creates a new desk in the given display. */
fun createDesk(displayId: Int) {
- if (Flags.enableMultipleDesktopsBackend()) {
+ if (DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) {
desksOrganizer.createDesk(displayId) { deskId ->
taskRepository.addDesk(displayId = displayId, deskId = deskId)
}
@@ -450,10 +467,7 @@ class DesktopTasksController(
}
// TODO(342378842): Instead of using default display, support multiple displays
val displayId = runningTask?.displayId ?: DEFAULT_DISPLAY
- val deskId =
- checkNotNull(taskRepository.getDefaultDeskId(displayId)) {
- "Expected a default desk to exist"
- }
+ val deskId = getDefaultDeskId(displayId)
return moveTaskToDesk(
taskId = taskId,
deskId = deskId,
@@ -474,7 +488,7 @@ class DesktopTasksController(
): Boolean {
val runningTask = shellTaskOrganizer.getRunningTaskInfo(taskId)
if (runningTask != null) {
- moveRunningTaskToDesk(
+ return moveRunningTaskToDesk(
task = runningTask,
deskId = deskId,
wct = wct,
@@ -556,10 +570,10 @@ class DesktopTasksController(
transitionSource: DesktopModeTransitionSource,
remoteTransition: RemoteTransition? = null,
callback: IMoveToDesktopCallback? = null,
- ) {
+ ): Boolean {
if (desktopModeCompatPolicy.isTopActivityExemptFromDesktopWindowing(task)) {
logW("Cannot enter desktop for taskId %d, ineligible top activity found", task.taskId)
- return
+ return false
}
val displayId = taskRepository.getDisplayForDesk(deskId)
logV(
@@ -602,7 +616,7 @@ class DesktopTasksController(
addPendingMinimizeTransition(transition, it, MinimizeReason.TASK_LIMIT)
}
exitResult.asExit()?.runOnTransitionStart?.invoke(transition)
- if (Flags.enableMultipleDesktopsBackend()) {
+ if (DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) {
desksTransitionObserver.addPendingTransition(
DeskTransition.ActiveDeskWithTask(
token = transition,
@@ -614,6 +628,7 @@ class DesktopTasksController(
} else {
taskRepository.setActiveDesk(displayId = displayId, deskId = deskId)
}
+ return true
}
/**
@@ -630,7 +645,7 @@ class DesktopTasksController(
task: RunningTaskInfo,
): Int? {
val taskIdToMinimize =
- if (Flags.enableMultipleDesktopsBackend()) {
+ if (DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) {
// Activate the desk first.
prepareForDeskActivation(displayId, wct)
desksOrganizer.activateDesk(wct, deskId)
@@ -650,7 +665,7 @@ class DesktopTasksController(
// Bring other apps to front first.
bringDesktopAppsToFrontBeforeShowingNewTask(displayId, wct, task.taskId)
}
- if (Flags.enableMultipleDesktopsBackend()) {
+ if (DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) {
prepareMoveTaskToDesk(wct, task, deskId)
} else {
addMoveToDesktopChanges(wct, task)
@@ -697,10 +712,7 @@ class DesktopTasksController(
* [startDragToDesktop].
*/
private fun finalizeDragToDesktop(taskInfo: RunningTaskInfo) {
- val deskId =
- checkNotNull(taskRepository.getDefaultDeskId(taskInfo.displayId)) {
- "Expected a default desk to exist"
- }
+ val deskId = getDefaultDeskId(taskInfo.displayId)
ProtoLog.v(
WM_SHELL_DESKTOP_MODE,
"DesktopTasksController: finalizeDragToDesktop taskId=%d deskId=%d",
@@ -709,7 +721,7 @@ class DesktopTasksController(
)
val wct = WindowContainerTransaction()
exitSplitIfApplicable(wct, taskInfo)
- if (!Flags.enableMultipleDesktopsBackend()) {
+ if (!DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) {
// |moveHomeTask| is also called in |bringDesktopAppsToFrontBeforeShowingNewTask|, so
// this shouldn't be necessary at all.
if (Flags.enablePerDisplayDesktopWallpaperActivity()) {
@@ -741,7 +753,7 @@ class DesktopTasksController(
addPendingMinimizeTransition(it, taskId, MinimizeReason.TASK_LIMIT)
}
exitResult.asExit()?.runOnTransitionStart?.invoke(transition)
- if (Flags.enableMultipleDesktopsBackend()) {
+ if (DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) {
desksTransitionObserver.addPendingTransition(
DeskTransition.ActiveDeskWithTask(
token = transition,
@@ -782,22 +794,44 @@ class DesktopTasksController(
wct: WindowContainerTransaction,
displayId: Int,
taskInfo: RunningTaskInfo,
- ): ((IBinder) -> Unit)? {
+ ): ((IBinder) -> Unit) {
val taskId = taskInfo.taskId
- desktopTilingDecorViewModel.removeTaskIfTiled(displayId, taskId)
- performDesktopExitCleanupIfNeeded(taskId, displayId, wct, forceToFullscreen = false)
+ val deskId = taskRepository.getDeskIdForTask(taskInfo.taskId)
+ snapEventHandler.removeTaskIfTiled(displayId, taskId)
+ val shouldExitDesktop =
+ willExitDesktop(
+ triggerTaskId = taskInfo.taskId,
+ displayId = displayId,
+ forceToFullscreen = false,
+ )
+ taskRepository.setPipShouldKeepDesktopActive(displayId, keepActive = true)
+ val desktopExitRunnable =
+ performDesktopExitCleanUp(
+ wct = wct,
+ deskId = deskId,
+ displayId = displayId,
+ willExitDesktop = shouldExitDesktop,
+ shouldEndUpAtHome = true,
+ )
+
taskRepository.addClosingTask(displayId, taskId)
taskbarDesktopTaskListener?.onTaskbarCornerRoundingUpdate(
doesAnyTaskRequireTaskbarRounding(displayId, taskId)
)
- return desktopImmersiveController
- .exitImmersiveIfApplicable(
- wct = wct,
- taskInfo = taskInfo,
- reason = DesktopImmersiveController.ExitReason.CLOSED,
- )
- .asExit()
- ?.runOnTransitionStart
+
+ val immersiveRunnable =
+ desktopImmersiveController
+ .exitImmersiveIfApplicable(
+ wct = wct,
+ taskInfo = taskInfo,
+ reason = DesktopImmersiveController.ExitReason.CLOSED,
+ )
+ .asExit()
+ ?.runOnTransitionStart
+ return { transitionToken ->
+ immersiveRunnable?.invoke(transitionToken)
+ desktopExitRunnable?.invoke(transitionToken)
+ }
}
fun minimizeTask(taskInfo: RunningTaskInfo, minimizeReason: MinimizeReason) {
@@ -831,10 +865,20 @@ class DesktopTasksController(
private fun minimizeTaskInner(taskInfo: RunningTaskInfo, minimizeReason: MinimizeReason) {
val taskId = taskInfo.taskId
+ val deskId = taskRepository.getDeskIdForTask(taskInfo.taskId)
val displayId = taskInfo.displayId
val wct = WindowContainerTransaction()
- desktopTilingDecorViewModel.removeTaskIfTiled(displayId, taskId)
- performDesktopExitCleanupIfNeeded(taskId, displayId, wct, forceToFullscreen = false)
+
+ snapEventHandler.removeTaskIfTiled(displayId, taskId)
+ taskRepository.setPipShouldKeepDesktopActive(displayId, keepActive = true)
+ val willExitDesktop = willExitDesktop(taskId, displayId, forceToFullscreen = false)
+ val desktopExitRunnable =
+ performDesktopExitCleanUp(
+ wct = wct,
+ deskId = deskId,
+ displayId = displayId,
+ willExitDesktop = willExitDesktop,
+ )
// Notify immersive handler as it might need to exit immersive state.
val exitResult =
desktopImmersiveController.exitImmersiveIfApplicable(
@@ -844,7 +888,9 @@ class DesktopTasksController(
)
wct.reorder(taskInfo.token, false)
- val transition = freeformTaskTransitionStarter.startMinimizedModeTransition(wct)
+ val isLastTask = taskRepository.isOnlyVisibleNonClosingTask(taskId, displayId)
+ val transition: IBinder =
+ freeformTaskTransitionStarter.startMinimizedModeTransition(wct, taskId, isLastTask)
desktopTasksLimiter.ifPresent {
it.addPendingMinimizeChange(
transition = transition,
@@ -854,12 +900,13 @@ class DesktopTasksController(
)
}
exitResult.asExit()?.runOnTransitionStart?.invoke(transition)
+ desktopExitRunnable?.invoke(transition)
}
/** Move a task with given `taskId` to fullscreen */
fun moveToFullscreen(taskId: Int, transitionSource: DesktopModeTransitionSource) {
shellTaskOrganizer.getRunningTaskInfo(taskId)?.let { task ->
- desktopTilingDecorViewModel.removeTaskIfTiled(task.displayId, taskId)
+ snapEventHandler.removeTaskIfTiled(task.displayId, taskId)
moveToFullscreenWithAnimation(task, task.positionInParent, transitionSource)
}
}
@@ -867,7 +914,7 @@ class DesktopTasksController(
/** Enter fullscreen by moving the focused freeform task in given `displayId` to fullscreen. */
fun enterFullscreen(displayId: Int, transitionSource: DesktopModeTransitionSource) {
getFocusedFreeformTask(displayId)?.let {
- desktopTilingDecorViewModel.removeTaskIfTiled(displayId, it.taskId)
+ snapEventHandler.removeTaskIfTiled(displayId, it.taskId)
moveToFullscreenWithAnimation(it, it.positionInParent, transitionSource)
}
}
@@ -901,7 +948,8 @@ class DesktopTasksController(
) {
logV("moveToFullscreenWithAnimation taskId=%d", task.taskId)
val wct = WindowContainerTransaction()
- addMoveToFullscreenChanges(wct, task)
+ val willExitDesktop = willExitDesktop(task.taskId, task.displayId, forceToFullscreen = true)
+ val deactivationRunnable = addMoveToFullscreenChanges(wct, task, willExitDesktop)
// We are moving a freeform task to fullscreen, put the home task under the fullscreen task.
if (!forceEnterDesktop(task.displayId)) {
@@ -909,12 +957,14 @@ class DesktopTasksController(
wct.reorder(task.token, /* onTop= */ true)
}
- exitDesktopTaskTransitionHandler.startTransition(
- transitionSource,
- wct,
- position,
- mOnAnimationFinishedCallback,
- )
+ val transition =
+ exitDesktopTaskTransitionHandler.startTransition(
+ transitionSource,
+ wct,
+ position,
+ mOnAnimationFinishedCallback,
+ )
+ deactivationRunnable?.invoke(transition)
// handles case where we are moving to full screen without closing all DW tasks.
if (!taskRepository.isOnlyVisibleNonClosingTask(task.taskId)) {
@@ -986,7 +1036,7 @@ class DesktopTasksController(
logV("moveTaskToFront taskId=%s", taskInfo.taskId)
// If a task is tiled, another task should be brought to foreground with it so let
// tiling controller handle the request.
- if (desktopTilingDecorViewModel.moveTaskToFrontIfTiled(taskInfo)) {
+ if (snapEventHandler.moveTaskToFrontIfTiled(taskInfo)) {
return
}
val wct = WindowContainerTransaction()
@@ -1173,6 +1223,8 @@ class DesktopTasksController(
wct.reorder(task.token, /* onTop= */ true, /* includingParents= */ true)
}
+ // TODO: b/394268248 - desk needs to be deactivated when moving the last task and going
+ // home.
if (Flags.enablePerDisplayDesktopWallpaperActivity()) {
performDesktopExitCleanupIfNeeded(
task.taskId,
@@ -1228,7 +1280,7 @@ class DesktopTasksController(
} else {
// Save current bounds so that task can be restored back to original bounds if necessary
// and toggle to the stable bounds.
- desktopTilingDecorViewModel.removeTaskIfTiled(taskInfo.displayId, taskInfo.taskId)
+ snapEventHandler.removeTaskIfTiled(taskInfo.displayId, taskInfo.taskId)
taskRepository.saveBoundsBeforeMaximize(taskInfo.taskId, currentTaskBounds)
destinationBounds.set(calculateMaximizeBounds(displayLayout, taskInfo))
}
@@ -1354,7 +1406,6 @@ class DesktopTasksController(
position: SnapPosition,
resizeTrigger: ResizeTrigger,
inputMethod: InputMethod,
- desktopWindowDecoration: DesktopModeWindowDecoration,
) {
desktopModeEventLogger.logTaskResizingStarted(
resizeTrigger,
@@ -1376,13 +1427,7 @@ class DesktopTasksController(
)
if (DesktopModeFlags.ENABLE_TILE_RESIZING.isTrue()) {
- val isTiled =
- desktopTilingDecorViewModel.snapToHalfScreen(
- taskInfo,
- desktopWindowDecoration,
- position,
- currentDragBounds,
- )
+ val isTiled = snapEventHandler.snapToHalfScreen(taskInfo, currentDragBounds, position)
if (isTiled) {
taskbarDesktopTaskListener?.onTaskbarCornerRoundingUpdate(true)
}
@@ -1419,7 +1464,6 @@ class DesktopTasksController(
position: SnapPosition,
resizeTrigger: ResizeTrigger,
inputMethod: InputMethod,
- desktopModeWindowDecoration: DesktopModeWindowDecoration,
) {
if (!isSnapResizingAllowed(taskInfo)) {
Toast.makeText(
@@ -1438,7 +1482,6 @@ class DesktopTasksController(
position,
resizeTrigger,
inputMethod,
- desktopModeWindowDecoration,
)
}
@@ -1450,7 +1493,6 @@ class DesktopTasksController(
currentDragBounds: Rect,
dragStartBounds: Rect,
motionEvent: MotionEvent,
- desktopModeWindowDecoration: DesktopModeWindowDecoration,
) {
releaseVisualIndicator()
if (!isSnapResizingAllowed(taskInfo)) {
@@ -1498,7 +1540,6 @@ class DesktopTasksController(
position,
resizeTrigger,
DesktopModeEventLogger.getInputMethodFromMotionEvent(motionEvent),
- desktopModeWindowDecoration,
)
}
}
@@ -1547,7 +1588,7 @@ class DesktopTasksController(
private fun prepareForDeskActivation(displayId: Int, wct: WindowContainerTransaction) {
// Move home to front, ensures that we go back home when all desktop windows are closed
val useParamDisplayId =
- Flags.enableMultipleDesktopsBackend() ||
+ DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue ||
Flags.enablePerDisplayDesktopWallpaperActivity()
moveHomeTask(displayId = if (useParamDisplayId) displayId else context.displayId, wct = wct)
// Currently, we only handle the desktop on the default display really.
@@ -1730,33 +1771,59 @@ class DesktopTasksController(
}
}
- /**
- * Remove wallpaper activity if task provided is last task and wallpaper activity token is not
- * null
- */
- private fun performDesktopExitCleanupIfNeeded(
- taskId: Int,
+ private fun willExitDesktop(
+ triggerTaskId: Int,
displayId: Int,
- wct: WindowContainerTransaction,
forceToFullscreen: Boolean,
- shouldEndUpAtHome: Boolean = true,
- ) {
- taskRepository.setPipShouldKeepDesktopActive(displayId, !forceToFullscreen)
+ ): Boolean {
if (Flags.enablePerDisplayDesktopWallpaperActivity()) {
- if (!taskRepository.isOnlyVisibleNonClosingTask(taskId, displayId)) {
- return
+ if (!taskRepository.isOnlyVisibleNonClosingTask(triggerTaskId, displayId)) {
+ return false
}
} else if (
Flags.enableDesktopWindowingPip() &&
taskRepository.isMinimizedPipPresentInDisplay(displayId) &&
!forceToFullscreen
) {
- return
+ return false
} else {
- if (!taskRepository.isOnlyVisibleNonClosingTask(taskId)) {
- return
+ if (!taskRepository.isOnlyVisibleNonClosingTask(triggerTaskId)) {
+ return false
}
}
+ return true
+ }
+
+ private fun performDesktopExitCleanupIfNeeded(
+ taskId: Int,
+ displayId: Int,
+ wct: WindowContainerTransaction,
+ forceToFullscreen: Boolean,
+ shouldEndUpAtHome: Boolean = true,
+ ): RunOnTransitStart? {
+ taskRepository.setPipShouldKeepDesktopActive(displayId, keepActive = !forceToFullscreen)
+ if (!willExitDesktop(taskId, displayId, forceToFullscreen)) {
+ return null
+ }
+ // TODO: b/394268248 - update remaining callers to pass in a |deskId| and apply the
+ // |RunOnTransitStart| when the transition is started.
+ return performDesktopExitCleanUp(
+ wct = wct,
+ deskId = null,
+ displayId = displayId,
+ willExitDesktop = true,
+ shouldEndUpAtHome = shouldEndUpAtHome,
+ )
+ }
+
+ private fun performDesktopExitCleanUp(
+ wct: WindowContainerTransaction,
+ deskId: Int?,
+ displayId: Int,
+ willExitDesktop: Boolean,
+ shouldEndUpAtHome: Boolean = true,
+ ): RunOnTransitStart? {
+ if (!willExitDesktop) return null
desktopModeEnterExitTransitionListener?.onExitDesktopModeTransitionStarted(
FULLSCREEN_ANIMATION_DURATION
)
@@ -1766,6 +1833,7 @@ class DesktopTasksController(
// intent.
addLaunchHomePendingIntent(wct, displayId)
}
+ return prepareDeskDeactivationIfNeeded(wct, deskId)
}
fun releaseVisualIndicator() {
@@ -1976,8 +2044,10 @@ class DesktopTasksController(
unminimizeReason = UnminimizeReason.APP_HANDLE_MENU_BUTTON,
)
} else {
- moveBackgroundTaskToDesktop(
+ val deskId = getDefaultDeskId(callingTask.displayId)
+ moveTaskToDesk(
requestedTaskId,
+ deskId,
WindowContainerTransaction(),
DesktopModeTransitionSource.APP_HANDLE_MENU_BUTTON,
)
@@ -2095,7 +2165,16 @@ class DesktopTasksController(
): WindowContainerTransaction? {
logV("DesktopTasksController: handleMidRecentsFreeformTaskLaunch")
val wct = WindowContainerTransaction()
- addMoveToFullscreenChanges(wct, task)
+ addMoveToFullscreenChanges(
+ wct = wct,
+ taskInfo = task,
+ willExitDesktop =
+ willExitDesktop(
+ triggerTaskId = task.taskId,
+ displayId = task.displayId,
+ forceToFullscreen = true,
+ ),
+ )
wct.reorder(task.token, true)
return wct
}
@@ -2119,7 +2198,16 @@ class DesktopTasksController(
// launched. We should make this task go to fullscreen instead of freeform. Note
// that this means any re-launch of a freeform window outside of desktop will be in
// fullscreen as long as default-desktop flag is disabled.
- addMoveToFullscreenChanges(wct, task)
+ addMoveToFullscreenChanges(
+ wct = wct,
+ taskInfo = task,
+ willExitDesktop =
+ willExitDesktop(
+ triggerTaskId = task.taskId,
+ displayId = task.displayId,
+ forceToFullscreen = true,
+ ),
+ )
return wct
}
bringDesktopAppsToFrontBeforeShowingNewTask(task.displayId, wct, task.taskId)
@@ -2169,7 +2257,7 @@ class DesktopTasksController(
return wct
}
if (!wct.isEmpty) {
- desktopTilingDecorViewModel.removeTaskIfTiled(task.displayId, task.taskId)
+ snapEventHandler.removeTaskIfTiled(task.displayId, task.taskId)
return wct
}
return null
@@ -2215,7 +2303,16 @@ class DesktopTasksController(
// changes we do for similar transitions. The task not having WINDOWING_MODE_UNDEFINED
// set when needed can interfere with future split / multi-instance transitions.
return WindowContainerTransaction().also { wct ->
- addMoveToFullscreenChanges(wct, task)
+ addMoveToFullscreenChanges(
+ wct = wct,
+ taskInfo = task,
+ willExitDesktop =
+ willExitDesktop(
+ triggerTaskId = task.taskId,
+ displayId = task.displayId,
+ forceToFullscreen = true,
+ ),
+ )
}
}
return null
@@ -2243,10 +2340,25 @@ class DesktopTasksController(
}
// Already fullscreen, no-op.
if (task.isFullscreen) return null
- return WindowContainerTransaction().also { wct -> addMoveToFullscreenChanges(wct, task) }
+ return WindowContainerTransaction().also { wct ->
+ addMoveToFullscreenChanges(
+ wct = wct,
+ taskInfo = task,
+ willExitDesktop =
+ willExitDesktop(
+ triggerTaskId = task.taskId,
+ displayId = task.displayId,
+ forceToFullscreen = true,
+ ),
+ )
+ }
}
- /** Handle task closing by removing wallpaper activity if it's the last active task */
+ /**
+ * Handle task closing by removing wallpaper activity if it's the last active task.
+ *
+ * TODO: b/394268248 - desk needs to be deactivated.
+ */
private fun handleTaskClosing(
task: RunningTaskInfo,
transition: IBinder,
@@ -2265,7 +2377,7 @@ class DesktopTasksController(
if (!DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION.isTrue()) {
taskRepository.addClosingTask(task.displayId, task.taskId)
- desktopTilingDecorViewModel.removeTaskIfTiled(task.displayId, task.taskId)
+ snapEventHandler.removeTaskIfTiled(task.displayId, task.taskId)
}
taskbarDesktopTaskListener?.onTaskbarCornerRoundingUpdate(
@@ -2313,7 +2425,7 @@ class DesktopTasksController(
taskInfo: RunningTaskInfo,
deskId: Int,
) {
- if (!Flags.enableMultipleDesktopsBackend()) return
+ if (!DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) return
val displayId = taskRepository.getDisplayForDesk(deskId)
val displayLayout = displayController.getDisplayLayout(displayId) ?: return
val initialBounds = getInitialBounds(displayLayout, taskInfo, displayId)
@@ -2397,10 +2509,15 @@ class DesktopTasksController(
return bounds
}
+ /**
+ * Applies the changes needed to enter fullscreen and returns the id of the desk that needs to
+ * be deactivated.
+ */
private fun addMoveToFullscreenChanges(
wct: WindowContainerTransaction,
taskInfo: RunningTaskInfo,
- ) {
+ willExitDesktop: Boolean,
+ ): RunOnTransitStart? {
val tdaInfo = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(taskInfo.displayId)!!
val tdaWindowingMode = tdaInfo.configuration.windowConfiguration.windowingMode
val targetWindowingMode =
@@ -2415,12 +2532,16 @@ class DesktopTasksController(
if (useDesktopOverrideDensity()) {
wct.setDensityDpi(taskInfo.token, getDefaultDensityDpi())
}
-
- performDesktopExitCleanupIfNeeded(
- taskInfo.taskId,
- taskInfo.displayId,
- wct,
- forceToFullscreen = true,
+ if (DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) {
+ wct.reparent(taskInfo.token, tdaInfo.token, /* onTop= */ true)
+ }
+ taskRepository.setPipShouldKeepDesktopActive(taskInfo.displayId, keepActive = false)
+ val deskId = taskRepository.getDeskIdForTask(taskInfo.taskId)
+ return performDesktopExitCleanUp(
+ wct = wct,
+ deskId = deskId,
+ displayId = taskInfo.displayId,
+ willExitDesktop = willExitDesktop,
shouldEndUpAtHome = false,
)
}
@@ -2445,6 +2566,8 @@ class DesktopTasksController(
/**
* Adds split screen changes to a transaction. Note that bounds are not reset here due to
* animation; see {@link onDesktopSplitSelectAnimComplete}
+ *
+ * TODO: b/394268248 - desk needs to be deactivated.
*/
private fun addMoveToSplitChanges(wct: WindowContainerTransaction, taskInfo: RunningTaskInfo) {
// This windowing mode is to get the transition animation started; once we complete
@@ -2534,10 +2657,7 @@ class DesktopTasksController(
displayId: Int,
remoteTransition: RemoteTransition? = null,
) {
- val deskId =
- checkNotNull(taskRepository.getDefaultDeskId(displayId)) {
- "Expected a default desk to exist"
- }
+ val deskId = getDefaultDeskId(displayId)
activateDesk(deskId, remoteTransition)
}
@@ -2545,7 +2665,7 @@ class DesktopTasksController(
fun activateDesk(deskId: Int, remoteTransition: RemoteTransition? = null) {
val displayId = taskRepository.getDisplayForDesk(deskId)
val wct = WindowContainerTransaction()
- if (Flags.enableMultipleDesktopsBackend()) {
+ if (DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) {
prepareForDeskActivation(displayId, wct)
desksOrganizer.activateDesk(wct, deskId)
if (DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_PERSISTENCE.isTrue()) {
@@ -2566,7 +2686,7 @@ class DesktopTasksController(
val transition = transitions.startTransition(transitionType, wct, handler)
handler?.setTransition(transition)
- if (Flags.enableMultipleDesktopsBackend()) {
+ if (DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) {
desksTransitionObserver.addPendingTransition(
DeskTransition.ActivateDesk(
token = transition,
@@ -2581,16 +2701,35 @@ class DesktopTasksController(
)
}
+ /**
+ * TODO: b/393978539 - Deactivation should not happen in desktop-first devices when going home.
+ */
+ private fun prepareDeskDeactivationIfNeeded(
+ wct: WindowContainerTransaction,
+ deskId: Int?,
+ ): RunOnTransitStart? {
+ if (!DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) return null
+ if (deskId == null) return null
+ desksOrganizer.deactivateDesk(wct, deskId)
+ return { transition ->
+ desksTransitionObserver.addPendingTransition(
+ DeskTransition.DeactivateDesk(token = transition, deskId = deskId)
+ )
+ }
+ }
+
/** Removes the default desk in the given display. */
@Deprecated("Deprecated with multi-desks.", ReplaceWith("removeDesk()"))
fun removeDefaultDeskInDisplay(displayId: Int) {
- val deskId =
- checkNotNull(taskRepository.getDefaultDeskId(displayId)) {
- "Expected a default desk to exist"
- }
+ val deskId = getDefaultDeskId(displayId)
removeDesk(displayId = displayId, deskId = deskId)
}
+ private fun getDefaultDeskId(displayId: Int) =
+ checkNotNull(taskRepository.getDefaultDeskId(displayId)) {
+ "Expected a default desk to exist in display: $displayId"
+ }
+
/** Removes the given desk. */
fun removeDesk(deskId: Int) {
val displayId = taskRepository.getDisplayForDesk(deskId)
@@ -2602,7 +2741,7 @@ class DesktopTasksController(
logV("removeDesk deskId=%d from displayId=%d", deskId, displayId)
val tasksToRemove =
- if (Flags.enableMultipleDesktopsBackend()) {
+ if (DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) {
taskRepository.getActiveTaskIdsInDesk(deskId)
} else {
// TODO: 362720497 - make sure minimized windows are also removed in WM
@@ -2611,7 +2750,7 @@ class DesktopTasksController(
}
val wct = WindowContainerTransaction()
- if (!Flags.enableMultipleDesktopsBackend()) {
+ if (!DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) {
tasksToRemove.forEach {
val task = shellTaskOrganizer.getRunningTaskInfo(it)
if (task != null) {
@@ -2624,9 +2763,9 @@ class DesktopTasksController(
// TODO: 362720497 - double check background tasks are also removed.
desksOrganizer.removeDesk(wct, deskId)
}
- if (!Flags.enableMultipleDesktopsBackend() && wct.isEmpty) return
+ if (!DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue && wct.isEmpty) return
val transition = transitions.startTransition(TRANSIT_CLOSE, wct, /* handler= */ null)
- if (Flags.enableMultipleDesktopsBackend()) {
+ if (DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) {
desksTransitionObserver.addPendingTransition(
DeskTransition.RemoveDesk(
token = transition,
@@ -2730,7 +2869,7 @@ class DesktopTasksController(
taskBounds: Rect,
) {
if (taskInfo.windowingMode != WINDOWING_MODE_FREEFORM) return
- desktopTilingDecorViewModel.removeTaskIfTiled(taskInfo.displayId, taskInfo.taskId)
+ snapEventHandler.removeTaskIfTiled(taskInfo.displayId, taskInfo.taskId)
updateVisualIndicator(
taskInfo,
taskSurface,
@@ -2747,6 +2886,12 @@ class DesktopTasksController(
taskTop: Float,
dragStartState: DragStartState,
): DesktopModeVisualIndicator.IndicatorType {
+ // If the visual indicator has the wrong start state, it was never cleared from a previous
+ // drag event and needs to be cleared
+ if (visualIndicator != null && visualIndicator?.dragStartState != dragStartState) {
+ Slog.e(TAG, "Visual indicator from previous motion event was never released")
+ releaseVisualIndicator()
+ }
// If the visual indicator does not exist, create it.
val indicator =
visualIndicator
@@ -2790,7 +2935,6 @@ class DesktopTasksController(
validDragArea: Rect,
dragStartBounds: Rect,
motionEvent: MotionEvent,
- desktopModeWindowDecoration: DesktopModeWindowDecoration,
) {
if (taskInfo.configuration.windowConfiguration.windowingMode != WINDOWING_MODE_FREEFORM) {
return
@@ -2829,7 +2973,6 @@ class DesktopTasksController(
currentDragBounds,
dragStartBounds,
motionEvent,
- desktopModeWindowDecoration,
)
}
IndicatorType.TO_SPLIT_RIGHT_INDICATOR -> {
@@ -2844,7 +2987,6 @@ class DesktopTasksController(
currentDragBounds,
dragStartBounds,
motionEvent,
- desktopModeWindowDecoration,
)
}
IndicatorType.NO_INDICATOR,
@@ -3130,7 +3272,7 @@ class DesktopTasksController(
logV("onUserChanged previousUserId=%d, newUserId=%d", userId, newUserId)
userId = newUserId
taskRepository = userRepositories.getProfile(userId)
- desktopTilingDecorViewModel.onUserChange()
+ snapEventHandler.onUserChange()
}
/** Called when a task's info changes. */
@@ -3300,11 +3442,15 @@ class DesktopTasksController(
}
override fun createDesk(displayId: Int) {
- // TODO: b/362720497 - Implement this API.
+ executeRemoteCallWithTaskPermission(controller, "createDesk") { c ->
+ c.createDesk(displayId)
+ }
}
override fun activateDesk(deskId: Int, remoteTransition: RemoteTransition?) {
- // TODO: b/362720497 - Implement this API.
+ executeRemoteCallWithTaskPermission(controller, "activateDesk") { c ->
+ c.activateDesk(deskId, remoteTransition)
+ }
}
override fun showDesktopApps(displayId: Int, remoteTransition: RemoteTransition?) {
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 3ada988ba2a3..9a97ae8d61a0 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
@@ -186,14 +186,16 @@ class DesktopTasksTransitionObserver(
for (change in info.changes) {
val taskInfo = change.taskInfo
if (taskInfo == null || taskInfo.taskId == -1) continue
- if (change.mode != TRANSIT_CLOSE) continue
- if (minimizingTask == null) {
- minimizingTask = getMinimizingTaskForClosingTransition(taskInfo)
+ if (
+ TransitionUtil.isClosingMode(change.mode) &&
+ DesktopWallpaperActivity.isWallpaperTask(taskInfo)
+ ) {
+ hasWallpaperClosing = true
}
- if (DesktopWallpaperActivity.isWallpaperTask(taskInfo)) {
- hasWallpaperClosing = true
+ if (change.mode == TRANSIT_CLOSE && minimizingTask == null) {
+ minimizingTask = getMinimizingTaskForClosingTransition(taskInfo)
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt
index fc29498291da..0929ae15e668 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt
@@ -251,14 +251,15 @@ sealed class DragToDesktopTransitionHandler(
(cancelState == CancelState.CANCEL_BUBBLE_LEFT ||
cancelState == CancelState.CANCEL_BUBBLE_RIGHT)
) {
- if (!bubbleController.isPresent) {
+ if (bubbleController.isEmpty || state !is TransitionState.FromFullscreen) {
+ // TODO(b/388853233): add support for dragging split task to bubble
startCancelAnimation()
} else {
// Animation is handled by BubbleController
val wct = WindowContainerTransaction()
restoreWindowOrder(wct, state)
- // TODO(b/388851898): pass along information about left or right side
- requestBubbleFromScaledTask(wct)
+ val onLeft = cancelState == CancelState.CANCEL_BUBBLE_LEFT
+ requestBubbleFromScaledTask(wct, onLeft)
}
} else {
// There's no dragged task, this can happen when the "cancel" happened too quickly
@@ -318,23 +319,27 @@ sealed class DragToDesktopTransitionHandler(
splitScreenController.requestEnterSplitSelect(taskInfo, wct, splitPosition, taskBounds)
}
- private fun requestBubbleFromScaledTask(wct: WindowContainerTransaction) {
+ private fun requestBubbleFromScaledTask(wct: WindowContainerTransaction, onLeft: Boolean) {
// TODO(b/391928049): update density once we can drag from desktop to bubble
val state = requireTransitionState()
val taskInfo = state.draggedTaskChange?.taskInfo ?: error("Expected non-null taskInfo")
val taskBounds = getAnimatedTaskBounds()
state.dragAnimator.cancelAnimator()
- requestBubble(wct, taskInfo, taskBounds)
+ requestBubble(wct, taskInfo, onLeft, taskBounds)
}
private fun requestBubble(
wct: WindowContainerTransaction,
taskInfo: RunningTaskInfo,
+ onLeft: Boolean,
taskBounds: Rect = Rect(taskInfo.configuration.windowConfiguration.bounds),
) {
val controller =
bubbleController.orElseThrow { IllegalStateException("BubbleController not set") }
- controller.expandStackAndSelectBubble(taskInfo, BubbleTransitions.DragData(taskBounds, wct))
+ controller.expandStackAndSelectBubble(
+ taskInfo,
+ BubbleTransitions.DragData(taskBounds, wct, onLeft),
+ )
}
override fun startAnimation(
@@ -493,12 +498,17 @@ sealed class DragToDesktopTransitionHandler(
state.cancelState == CancelState.CANCEL_BUBBLE_LEFT ||
state.cancelState == CancelState.CANCEL_BUBBLE_RIGHT
) {
+ if (bubbleController.isEmpty || state !is TransitionState.FromFullscreen) {
+ // TODO(b/388853233): add support for dragging split task to bubble
+ startCancelDragToDesktopTransition()
+ return true
+ }
val taskInfo =
state.draggedTaskChange?.taskInfo ?: error("Expected non-null task info.")
val wct = WindowContainerTransaction()
restoreWindowOrder(wct)
- // TODO(b/388851898): pass along information about left or right side
- requestBubble(wct, taskInfo)
+ val onLeft = state.cancelState == CancelState.CANCEL_BUBBLE_LEFT
+ requestBubble(wct, taskInfo, onLeft)
}
return true
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandler.java
index 5ae1fca73d4e..95cc1e68ac11 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandler.java
@@ -106,7 +106,7 @@ public class ExitDesktopTaskTransitionHandler implements Transitions.TransitionH
* @param position Position of the task when transition is started
* @param onAnimationEndCallback to be called after animation
*/
- public void startTransition(@NonNull DesktopModeTransitionSource transitionSource,
+ public IBinder startTransition(@NonNull DesktopModeTransitionSource transitionSource,
@NonNull WindowContainerTransaction wct, Point position,
Function0<Unit> onAnimationEndCallback) {
mPosition = position;
@@ -114,6 +114,7 @@ public class ExitDesktopTaskTransitionHandler implements Transitions.TransitionH
final IBinder token = mTransitions.startTransition(getExitTransitionType(transitionSource),
wct, this);
mPendingTransitionTokens.add(token);
+ return token;
}
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/desktopwallpaperactivity/DesktopWallpaperActivityTokenProvider.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/desktopwallpaperactivity/DesktopWallpaperActivityTokenProvider.kt
index 2a8a3475c2a5..b5490cb4b595 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/desktopwallpaperactivity/DesktopWallpaperActivityTokenProvider.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/desktopwallpaperactivity/DesktopWallpaperActivityTokenProvider.kt
@@ -20,7 +20,7 @@ import android.util.SparseArray
import android.util.SparseBooleanArray
import android.view.Display.DEFAULT_DISPLAY
import android.window.WindowContainerToken
-import androidx.core.util.forEach
+import androidx.core.util.keyIterator
import com.android.internal.protolog.ProtoLog
import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
@@ -45,11 +45,13 @@ class DesktopWallpaperActivityTokenProvider {
}
fun removeToken(token: WindowContainerToken) {
- wallpaperActivityTokenByDisplayId.forEach { displayId, value ->
- if (value == token) {
- logV("Remove desktop wallpaper activity token for display %s", displayId)
- wallpaperActivityTokenByDisplayId.delete(displayId)
+ val displayId =
+ wallpaperActivityTokenByDisplayId.keyIterator().asSequence().find {
+ wallpaperActivityTokenByDisplayId[it] == token
}
+ if (displayId != null) {
+ logV("Remove desktop wallpaper activity token for display %s", displayId)
+ wallpaperActivityTokenByDisplayId.delete(displayId)
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/DeskTransition.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/DeskTransition.kt
index 8c4fd9db050f..9dec96933ee5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/DeskTransition.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/DeskTransition.kt
@@ -42,4 +42,7 @@ sealed class DeskTransition {
val deskId: Int,
val enterTaskId: Int,
) : DeskTransition()
+
+ /** A transition to deactivate a desk. */
+ data class DeactivateDesk(override val token: IBinder, val deskId: Int) : DeskTransition()
}
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
index 547890a6200a..0f2f3711a9a3 100644
--- 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
@@ -27,6 +27,9 @@ interface DesksOrganizer {
/** Activates the given desk, making it visible in its display. */
fun activateDesk(wct: WindowContainerTransaction, deskId: Int)
+ /** Deactivates the given desk, removing it as the default launch container for new tasks. */
+ fun deactivateDesk(wct: WindowContainerTransaction, deskId: Int)
+
/** Removes the given desk and its desktop windows. */
fun removeDesk(wct: WindowContainerTransaction, deskId: Int)
@@ -37,6 +40,9 @@ interface DesksOrganizer {
task: ActivityManager.RunningTaskInfo,
)
+ /** Whether the change is for the given desk id. */
+ fun isDeskChange(change: TransitionInfo.Change, deskId: Int): Boolean
+
/**
* Returns the desk id in which the task in the given change is located at the end of a
* transition, if any.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/DesksTransitionObserver.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/DesksTransitionObserver.kt
index 6d88c3310a63..e57b56378fb3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/DesksTransitionObserver.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/DesksTransitionObserver.kt
@@ -17,9 +17,11 @@ package com.android.wm.shell.desktopmode.multidesks
import android.os.IBinder
import android.view.WindowManager.TRANSIT_CLOSE
+import android.window.DesktopExperienceFlags
import android.window.TransitionInfo
-import com.android.window.flags.Flags
+import com.android.internal.protolog.ProtoLog
import com.android.wm.shell.desktopmode.DesktopUserRepositories
+import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
/**
* Observer of desk-related transitions, such as adding, removing or activating a whole desk. It
@@ -33,7 +35,7 @@ class DesksTransitionObserver(
/** Adds a pending desk transition to be tracked. */
fun addPendingTransition(transition: DeskTransition) {
- if (!Flags.enableMultipleDesktopsBackend()) return
+ if (!DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) return
deskTransitions[transition.token] = transition
}
@@ -42,8 +44,9 @@ class DesksTransitionObserver(
* observer.
*/
fun onTransitionReady(transition: IBinder, info: TransitionInfo) {
- if (!Flags.enableMultipleDesktopsBackend()) return
+ if (!DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) return
val deskTransition = deskTransitions.remove(transition) ?: return
+ logD("Desk transition ready: %s", deskTransition)
val desktopRepository = desktopUserRepositories.current
when (deskTransition) {
is DeskTransition.RemoveDesk -> {
@@ -88,6 +91,42 @@ class DesksTransitionObserver(
)
}
}
+ is DeskTransition.DeactivateDesk -> {
+ var visibleDeactivation = false
+ for (change in info.changes) {
+ val isDeskChange = desksOrganizer.isDeskChange(change, deskTransition.deskId)
+ if (isDeskChange) {
+ visibleDeactivation = true
+ continue
+ }
+ val taskId = change.taskInfo?.taskId ?: continue
+ val removedFromDesk =
+ desktopRepository.getDeskIdForTask(taskId) == deskTransition.deskId &&
+ desksOrganizer.getDeskAtEnd(change) == null
+ if (removedFromDesk) {
+ desktopRepository.removeTaskFromDesk(
+ deskId = deskTransition.deskId,
+ taskId = taskId,
+ )
+ }
+ }
+ // Always deactivate even if there's no change that confirms the desk was
+ // deactivated. Some interactions, such as the desk deactivating because it's
+ // occluded by a fullscreen task result in a transition change, but others, such
+ // as transitioning from an empty desk to home may not.
+ if (!visibleDeactivation) {
+ logD("Deactivating desk without transition change")
+ }
+ desktopRepository.setDeskInactive(deskId = deskTransition.deskId)
+ }
}
}
+
+ private fun logD(msg: String, vararg arguments: Any?) {
+ ProtoLog.d(WM_SHELL_DESKTOP_MODE, "%s: $msg", TAG, *arguments)
+ }
+
+ private companion object {
+ private const val TAG = "DesksTransitionObserver"
+ }
}
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
index 5cda76e2f3e0..339932cabd2c 100644
--- 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
@@ -23,12 +23,12 @@ import android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED
import android.util.SparseArray
import android.view.SurfaceControl
import android.view.WindowManager.TRANSIT_TO_FRONT
+import android.window.DesktopExperienceFlags
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
@@ -47,7 +47,7 @@ class RootTaskDesksOrganizer(
@VisibleForTesting val roots = SparseArray<DeskRoot>()
init {
- if (Flags.enableMultipleDesktopsBackend()) {
+ if (DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) {
shellInit.addInitCallback(
{ shellCommandHandler.addDumpCallback(this::dump, this) },
this,
@@ -83,6 +83,16 @@ class RootTaskDesksOrganizer(
)
}
+ override fun deactivateDesk(wct: WindowContainerTransaction, deskId: Int) {
+ logV("deactivateDesk %d", deskId)
+ val root = checkNotNull(roots[deskId]) { "Root not found for desk: $deskId" }
+ wct.setLaunchRoot(
+ /* container= */ root.taskInfo.token,
+ /* windowingModes= */ null,
+ /* activityTypes= */ null,
+ )
+ }
+
override fun moveTaskToDesk(
wct: WindowContainerTransaction,
deskId: Int,
@@ -93,6 +103,9 @@ class RootTaskDesksOrganizer(
wct.reparent(task.token, root.taskInfo.token, /* onTop= */ true)
}
+ override fun isDeskChange(change: TransitionInfo.Change, deskId: Int): Boolean =
+ roots.contains(deskId) && change.taskInfo?.taskId == deskId
+
override fun getDeskAtEnd(change: TransitionInfo.Change): Int? =
change.taskInfo?.parentTaskId?.takeIf { it in roots }
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 5a89451ffdbc..0507e59c06e1 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
@@ -17,8 +17,8 @@
package com.android.wm.shell.desktopmode.persistence
import android.content.Context
+import android.window.DesktopExperienceFlags
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
@@ -58,7 +58,7 @@ class DesktopRepositoryInitializerImpl(
repository.addDesk(
displayId = persistentDesktop.displayId,
deskId =
- if (Flags.enableMultipleDesktopsBackend()) {
+ if (DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) {
persistentDesktop.desktopId
} else {
// When disabled, desk ids are always the display id.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java
index 31715f0444a9..b60fb5e7bfdd 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java
@@ -93,7 +93,8 @@ public class FreeformTaskTransitionHandler
}
@Override
- public IBinder startMinimizedModeTransition(WindowContainerTransaction wct) {
+ public IBinder startMinimizedModeTransition(
+ WindowContainerTransaction wct, int taskId, boolean isLastTask) {
final int type = Transitions.TRANSIT_MINIMIZE;
final IBinder token = mTransitions.startTransition(type, wct, this);
mPendingTransitionTokens.add(token);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionStarter.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionStarter.java
index a874a5be426d..822934c1e646 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionStarter.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionStarter.java
@@ -38,10 +38,13 @@ public interface FreeformTaskTransitionStarter {
* Starts window minimization transition
*
* @param wct the {@link WindowContainerTransaction} that changes the windowing mode
+ * @param taskId the task id of the task being minimized
+ * @param isLastTask true if the task being minimized is the last visible task
*
* @return the started transition
*/
- IBinder startMinimizedModeTransition(WindowContainerTransaction wct);
+ IBinder startMinimizedModeTransition(
+ WindowContainerTransaction wct, int taskId, boolean isLastTask);
/**
* Starts close window transition
@@ -60,4 +63,4 @@ public interface FreeformTaskTransitionStarter {
* @return the started transition
*/
IBinder startPipTransition(WindowContainerTransaction wct);
-} \ No newline at end of file
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java
index c0a0f469add4..d666126b91ba 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java
@@ -22,7 +22,6 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.service.dreams.Flags.dismissDreamOnKeyguardDismiss;
import static android.view.WindowManager.KEYGUARD_VISIBILITY_TRANSIT_FLAGS;
-import static android.view.WindowManager.TRANSIT_FLAG_AOD_APPEARING;
import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_APPEARING;
import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY;
import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_LOCKED;
@@ -201,8 +200,7 @@ public class KeyguardTransitionHandler
transition, info, startTransaction, finishTransaction, finishCallback);
}
- if ((info.getFlags() & TRANSIT_FLAG_KEYGUARD_APPEARING) != 0
- || (info.getFlags() & TRANSIT_FLAG_AOD_APPEARING) != 0) {
+ if ((info.getFlags() & TRANSIT_FLAG_KEYGUARD_APPEARING) != 0) {
return startAnimation(mAppearTransition, "appearing",
transition, info, startTransaction, finishTransaction, finishCallback);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
index da3181096d98..cef18f55b86d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
@@ -145,7 +145,7 @@ public abstract class PipTransitionController implements Transitions.TransitionH
/**
* Called when the Shell wants to start an exit-via-expand from Pip transition/animation.
*/
- public void startExpandTransition(WindowContainerTransaction out) {
+ public void startExpandTransition(WindowContainerTransaction out, boolean toSplit) {
// Default implementation does nothing.
}
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 71697596afd3..a837e7d308eb 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
@@ -296,6 +296,7 @@ public class PipDismissTargetHandler implements ViewTreeObserver.OnPreDrawListen
return;
}
+ mMagneticTarget.updateLocationOnScreen();
createOrUpdateDismissTarget();
if (mTargetViewContainer.getVisibility() != View.VISIBLE) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java
index e17587ff18bc..df7a25af8376 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java
@@ -35,6 +35,10 @@ 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.protolog.ShellProtoLogGroup;
+import com.android.wm.shell.shared.split.SplitScreenConstants;
+import com.android.wm.shell.splitscreen.SplitScreenController;
+
+import java.util.Optional;
/**
* Scheduler for Shell initiated PiP transitions and animations.
@@ -47,6 +51,7 @@ public class PipScheduler {
private final ShellExecutor mMainExecutor;
private final PipTransitionState mPipTransitionState;
private final PipDesktopState mPipDesktopState;
+ private final Optional<SplitScreenController> mSplitScreenControllerOptional;
private PipTransitionController mPipTransitionController;
private PipSurfaceTransactionHelper.SurfaceControlTransactionFactory
mSurfaceControlTransactionFactory;
@@ -59,12 +64,14 @@ public class PipScheduler {
PipBoundsState pipBoundsState,
ShellExecutor mainExecutor,
PipTransitionState pipTransitionState,
+ Optional<SplitScreenController> splitScreenControllerOptional,
PipDesktopState pipDesktopState) {
mContext = context;
mPipBoundsState = pipBoundsState;
mMainExecutor = mainExecutor;
mPipTransitionState = pipTransitionState;
mPipDesktopState = pipDesktopState;
+ mSplitScreenControllerOptional = splitScreenControllerOptional;
mSurfaceControlTransactionFactory =
new PipSurfaceTransactionHelper.VsyncSurfaceControlTransactionFactory();
@@ -96,10 +103,23 @@ public class PipScheduler {
public void scheduleExitPipViaExpand() {
mMainExecutor.execute(() -> {
if (!mPipTransitionState.isInPip()) return;
- WindowContainerTransaction wct = getExitPipViaExpandTransaction();
- if (wct != null) {
- mPipTransitionController.startExpandTransition(wct);
- }
+
+ final WindowContainerTransaction expandWct = getExitPipViaExpandTransaction();
+ if (expandWct == null) return;
+
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ mSplitScreenControllerOptional.ifPresent(splitScreenController -> {
+ int lastParentTaskId = mPipTransitionState.getPipTaskInfo()
+ .lastParentTaskIdBeforePip;
+ if (splitScreenController.isTaskInSplitScreen(lastParentTaskId)) {
+ splitScreenController.prepareEnterSplitScreen(wct,
+ null /* taskInfo */, SplitScreenConstants.SPLIT_POSITION_UNDEFINED);
+ }
+ });
+
+ boolean toSplit = !wct.isEmpty();
+ wct.merge(expandWct, true /* transfer */);
+ mPipTransitionController.startExpandTransition(wct, toSplit);
});
}
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 9adaa3614a0f..035c93db7ee4 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
@@ -16,7 +16,6 @@
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.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.view.Surface.ROTATION_0;
@@ -29,7 +28,13 @@ import static android.view.WindowManager.TRANSIT_PIP;
import static android.view.WindowManager.TRANSIT_TO_BACK;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
+import static com.android.wm.shell.pip2.phone.transition.PipTransitionUtils.getChangeByToken;
+import static com.android.wm.shell.pip2.phone.transition.PipTransitionUtils.getFixedRotationDelta;
+import static com.android.wm.shell.pip2.phone.transition.PipTransitionUtils.getLeash;
+import static com.android.wm.shell.pip2.phone.transition.PipTransitionUtils.getPipChange;
+import static com.android.wm.shell.pip2.phone.transition.PipTransitionUtils.getPipParams;
import static com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_PIP;
+import static com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_PIP_TO_SPLIT;
import static com.android.wm.shell.transition.Transitions.TRANSIT_REMOVE_PIP;
import static com.android.wm.shell.transition.Transitions.TRANSIT_RESIZE_PIP;
import static com.android.wm.shell.transition.Transitions.transitTypeToString;
@@ -45,7 +50,6 @@ import android.graphics.PointF;
import android.graphics.Rect;
import android.os.Bundle;
import android.os.IBinder;
-import android.view.Surface;
import android.view.SurfaceControl;
import android.view.WindowManager;
import android.window.TransitionInfo;
@@ -70,11 +74,14 @@ 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;
+import com.android.wm.shell.pip2.phone.transition.PipExpandHandler;
import com.android.wm.shell.shared.TransitionUtil;
+import com.android.wm.shell.splitscreen.SplitScreenController;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.Transitions;
+import java.util.Optional;
+
/**
* Implementation of transitions for PiP on phone.
*/
@@ -130,6 +137,7 @@ public class PipTransition extends PipTransitionController implements
//
// Internal state and relevant cached info
//
+ private final PipExpandHandler mExpandHandler;
private Transitions.TransitionFinishCallback mFinishCallback;
@@ -151,6 +159,7 @@ public class PipTransition extends PipTransitionController implements
PipDisplayLayoutState pipDisplayLayoutState,
PipUiStateChangeController pipUiStateChangeController,
DisplayController displayController,
+ Optional<SplitScreenController> splitScreenControllerOptional,
PipDesktopState pipDesktopState) {
super(shellInit, shellTaskOrganizer, transitions, pipBoundsState, pipMenuController,
pipBoundsAlgorithm);
@@ -165,6 +174,9 @@ public class PipTransition extends PipTransitionController implements
mDisplayController = displayController;
mPipSurfaceTransactionHelper = new PipSurfaceTransactionHelper(mContext);
mPipDesktopState = pipDesktopState;
+
+ mExpandHandler = new PipExpandHandler(mContext, pipBoundsState, pipBoundsAlgorithm,
+ pipTransitionState, pipDisplayLayoutState, splitScreenControllerOptional);
}
@Override
@@ -184,10 +196,11 @@ public class PipTransition extends PipTransitionController implements
//
@Override
- public void startExpandTransition(WindowContainerTransaction out) {
+ public void startExpandTransition(WindowContainerTransaction out, boolean toSplit) {
if (out == null) return;
mPipTransitionState.setState(PipTransitionState.EXITING_PIP);
- mExitViaExpandTransition = mTransitions.startTransition(TRANSIT_EXIT_PIP, out, this);
+ mExitViaExpandTransition = mTransitions.startTransition(toSplit ? TRANSIT_EXIT_PIP_TO_SPLIT
+ : TRANSIT_EXIT_PIP, out, this);
}
@Override
@@ -239,10 +252,11 @@ public class PipTransition extends PipTransitionController implements
@NonNull SurfaceControl.Transaction finishT,
@NonNull IBinder mergeTarget,
@NonNull Transitions.TransitionFinishCallback finishCallback) {
- // Just jump-cut the current animation if any, but do not merge.
if (info.getType() == TRANSIT_EXIT_PIP) {
end();
}
+ mExpandHandler.mergeAnimation(transition, info, startT, finishT, mergeTarget,
+ finishCallback);
}
@Override
@@ -290,7 +304,8 @@ public class PipTransition extends PipTransitionController implements
finishCallback);
} else if (transition == mExitViaExpandTransition) {
mExitViaExpandTransition = null;
- return startExpandAnimation(info, startTransaction, finishTransaction, finishCallback);
+ return mExpandHandler.startAnimation(transition, info, startTransaction,
+ finishTransaction, finishCallback);
} else if (transition == mResizeTransition) {
mResizeTransition = null;
return startResizeAnimation(info, startTransaction, finishTransaction, finishCallback);
@@ -436,7 +451,7 @@ public class PipTransition extends PipTransitionController implements
(destinationBounds.height() - overlaySize) / 2f);
}
- final int delta = getFixedRotationDelta(info, pipChange);
+ final int delta = getFixedRotationDelta(info, pipChange, mPipDisplayLayoutState);
if (delta != ROTATION_0) {
// Update transition target changes in place to prepare for fixed rotation.
handleBoundsEnterFixedRotation(info, pipChange, pipActivityChange);
@@ -496,7 +511,7 @@ public class PipTransition extends PipTransitionController implements
final Rect adjustedSourceRectHint = getAdjustedSourceRectHint(info, pipChange,
pipActivityChange);
- final int delta = getFixedRotationDelta(info, pipChange);
+ final int delta = getFixedRotationDelta(info, pipChange, mPipDisplayLayoutState);
if (delta != ROTATION_0) {
// Update transition target changes in place to prepare for fixed rotation.
handleBoundsEnterFixedRotation(info, pipChange, pipActivityChange);
@@ -585,27 +600,6 @@ public class PipTransition extends PipTransitionController implements
endBounds.top + activityEndOffset.y);
}
- private void handleExpandFixedRotation(TransitionInfo.Change outPipTaskChange, int delta) {
- final Rect endBounds = outPipTaskChange.getEndAbsBounds();
- final int width = endBounds.width();
- final int height = endBounds.height();
- final int left = endBounds.left;
- final int top = endBounds.top;
- int newTop, newLeft;
-
- if (delta == Surface.ROTATION_90) {
- newLeft = top;
- newTop = -(left + width);
- } else {
- newLeft = -(height + top);
- newTop = left;
- }
- // Modify the endBounds, rotating and placing them potentially off-screen, so that
- // as we translate and rotate around the origin, we place them right into the target.
- endBounds.set(newLeft, newTop, newLeft + height, newTop + width);
- }
-
-
private boolean startAlphaTypeEnterAnimation(@NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction startTransaction,
@NonNull SurfaceControl.Transaction finishTransaction,
@@ -633,83 +627,6 @@ public class PipTransition extends PipTransitionController implements
return true;
}
- private boolean startExpandAnimation(@NonNull TransitionInfo info,
- @NonNull SurfaceControl.Transaction startTransaction,
- @NonNull SurfaceControl.Transaction finishTransaction,
- @NonNull Transitions.TransitionFinishCallback finishCallback) {
- WindowContainerToken pipToken = mPipTransitionState.getPipTaskToken();
-
- TransitionInfo.Change pipChange = getChangeByToken(info, pipToken);
- if (pipChange == null) {
- // pipChange is null, check to see if we've reparented the PIP activity for
- // the multi activity case. If so we should use the activity leash instead
- for (TransitionInfo.Change change : info.getChanges()) {
- if (change.getTaskInfo() == null
- && change.getLastParent() != null
- && change.getLastParent().equals(pipToken)) {
- pipChange = change;
- break;
- }
- }
-
- // failsafe
- if (pipChange == null) {
- return false;
- }
- }
- mFinishCallback = finishCallback;
-
- // The parent change if we were in a multi-activity PiP; null if single activity PiP.
- final TransitionInfo.Change parentBeforePip = pipChange.getTaskInfo() == null
- ? getChangeByToken(info, pipChange.getParent()) : null;
- if (parentBeforePip != null) {
- // For multi activity, we need to manually set the leash layer
- startTransaction.setLayer(parentBeforePip.getLeash(), Integer.MAX_VALUE - 1);
- }
-
- final Rect startBounds = pipChange.getStartAbsBounds();
- final Rect endBounds = pipChange.getEndAbsBounds();
- final SurfaceControl pipLeash = getLeash(pipChange);
-
- PictureInPictureParams params = null;
- if (pipChange.getTaskInfo() != null) {
- // single activity
- params = getPipParams(pipChange);
- } else if (parentBeforePip != null && parentBeforePip.getTaskInfo() != null) {
- // multi activity
- params = getPipParams(parentBeforePip);
- }
- final Rect sourceRectHint = PipBoundsAlgorithm.getValidSourceHintRect(params, endBounds,
- startBounds);
-
- // We define delta = startRotation - endRotation, so we need to flip the sign.
- final int delta = -getFixedRotationDelta(info, pipChange);
- if (delta != ROTATION_0) {
- // Update PiP target change in place to prepare for fixed rotation;
- handleExpandFixedRotation(pipChange, delta);
- }
-
- PipExpandAnimator animator = new PipExpandAnimator(mContext, pipLeash,
- startTransaction, finishTransaction, endBounds, startBounds, endBounds,
- sourceRectHint, delta);
- animator.setAnimationEndCallback(() -> {
- if (parentBeforePip != null) {
- // TODO b/377362511: Animate local leash instead to also handle letterbox case.
- // For multi-activity, set the crop to be null
- finishTransaction.setCrop(pipLeash, null);
- }
- finishTransition();
- });
- cacheAndStartTransitionAnimator(animator);
-
- // Save the PiP bounds in case, we re-enter the PiP with the same component.
- float snapFraction = mPipBoundsAlgorithm.getSnapFraction(
- mPipBoundsState.getBounds());
- mPipBoundsState.saveReentryState(snapFraction);
-
- return true;
- }
-
private boolean startRemoveAnimation(@NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction startTransaction,
@NonNull SurfaceControl.Transaction finishTransaction,
@@ -743,29 +660,6 @@ public class PipTransition extends PipTransitionController implements
// Various helpers to resolve transition requests and infos
//
- @Nullable
- private TransitionInfo.Change getPipChange(TransitionInfo info) {
- for (TransitionInfo.Change change : info.getChanges()) {
- if (change.getTaskInfo() != null
- && change.getTaskInfo().getWindowingMode() == WINDOWING_MODE_PINNED) {
- return change;
- }
- }
- return null;
- }
-
- @Nullable
- private TransitionInfo.Change getChangeByToken(TransitionInfo info,
- WindowContainerToken token) {
- for (TransitionInfo.Change change : info.getChanges()) {
- if (change.getTaskInfo() != null
- && change.getTaskInfo().getToken().equals(token)) {
- return change;
- }
- }
- return null;
- }
-
@NonNull
private Rect getAdjustedSourceRectHint(@NonNull TransitionInfo info,
@NonNull TransitionInfo.Change pipTaskChange,
@@ -789,8 +683,8 @@ public class PipTransition extends PipTransitionController implements
Rect cutoutInsets = parentBeforePip != null
? parentBeforePip.getTaskInfo().displayCutoutInsets
: pipTaskChange.getTaskInfo().displayCutoutInsets;
- if (cutoutInsets != null
- && getFixedRotationDelta(info, pipTaskChange) == ROTATION_90) {
+ if (cutoutInsets != null && getFixedRotationDelta(info, pipTaskChange,
+ mPipDisplayLayoutState) == ROTATION_90) {
adjustedSourceRectHint.offset(cutoutInsets.left, cutoutInsets.top);
}
if (mPipDesktopState.isDesktopWindowingPipEnabled()) {
@@ -807,25 +701,6 @@ public class PipTransition extends PipTransitionController implements
return adjustedSourceRectHint;
}
- @Surface.Rotation
- private int getFixedRotationDelta(@NonNull TransitionInfo info,
- @NonNull TransitionInfo.Change pipChange) {
- TransitionInfo.Change fixedRotationChange = findFixedRotationChange(info);
- int startRotation = pipChange.getStartRotation();
- if (pipChange.getEndRotation() != ROTATION_UNDEFINED
- && startRotation != pipChange.getEndRotation()) {
- // If PiP change was collected along with the display change and the orientation change
- // happened in sync with the PiP change, then do not treat this as fixed-rotation case.
- return ROTATION_0;
- }
-
- int endRotation = fixedRotationChange != null
- ? fixedRotationChange.getEndFixedRotation() : mPipDisplayLayoutState.getRotation();
- int delta = endRotation == ROTATION_UNDEFINED ? ROTATION_0
- : startRotation - endRotation;
- return delta;
- }
-
private void prepareOtherTargetTransforms(TransitionInfo info,
SurfaceControl.Transaction startTransaction,
SurfaceControl.Transaction finishTransaction) {
@@ -853,7 +728,8 @@ public class PipTransition extends PipTransitionController implements
// If PiP is enabled on Connected Displays, update PipDisplayLayoutState to have the correct
// display info that PiP is entering in.
- if (mPipDesktopState.isConnectedDisplaysPipEnabled()) {
+ if (mPipDesktopState.isConnectedDisplaysPipEnabled()
+ && pipTask.displayId != mPipDisplayLayoutState.getDisplayId()) {
final DisplayLayout displayLayout = mDisplayController.getDisplayLayout(
pipTask.displayId);
if (displayLayout != null) {
@@ -1012,20 +888,6 @@ public class PipTransition extends PipTransitionController implements
mTransitionAnimator.start();
}
- @NonNull
- private static PictureInPictureParams getPipParams(@NonNull TransitionInfo.Change pipChange) {
- return pipChange.getTaskInfo().pictureInPictureParams != null
- ? pipChange.getTaskInfo().pictureInPictureParams
- : new PictureInPictureParams.Builder().build();
- }
-
- @NonNull
- private static SurfaceControl getLeash(TransitionInfo.Change change) {
- SurfaceControl leash = change.getLeash();
- Preconditions.checkNotNull(leash, "Leash is null for change=" + change);
- return leash;
- }
-
//
// Miscellaneous callbacks and listeners
//
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransitionState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransitionState.java
index 8805cbb0dfbd..18c9a705dcf7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransitionState.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransitionState.java
@@ -314,7 +314,8 @@ public class PipTransitionState {
mSwipePipToHomeAppBounds.setEmpty();
}
- @Nullable WindowContainerToken getPipTaskToken() {
+ @Nullable
+ public WindowContainerToken getPipTaskToken() {
return mPipTaskInfo != null ? mPipTaskInfo.getToken() : null;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/transition/PipExpandHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/transition/PipExpandHandler.java
new file mode 100644
index 000000000000..db4942b2fb95
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/transition/PipExpandHandler.java
@@ -0,0 +1,331 @@
+/*
+ * 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.pip2.phone.transition;
+
+import static android.view.Surface.ROTATION_0;
+
+import static com.android.wm.shell.pip2.phone.transition.PipTransitionUtils.getChangeByToken;
+import static com.android.wm.shell.pip2.phone.transition.PipTransitionUtils.getFixedRotationDelta;
+import static com.android.wm.shell.pip2.phone.transition.PipTransitionUtils.getLeash;
+import static com.android.wm.shell.pip2.phone.transition.PipTransitionUtils.getPipParams;
+import static com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_PIP;
+import static com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_PIP_TO_SPLIT;
+
+import android.animation.ValueAnimator;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.PictureInPictureParams;
+import android.content.Context;
+import android.graphics.Rect;
+import android.os.IBinder;
+import android.view.Surface;
+import android.view.SurfaceControl;
+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.internal.protolog.ProtoLog;
+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.pip2.animation.PipExpandAnimator;
+import com.android.wm.shell.pip2.phone.PipTransitionState;
+import com.android.wm.shell.protolog.ShellProtoLogGroup;
+import com.android.wm.shell.splitscreen.SplitScreenController;
+import com.android.wm.shell.transition.Transitions;
+
+import java.util.Optional;
+
+public class PipExpandHandler implements Transitions.TransitionHandler {
+ private final Context mContext;
+ private final PipBoundsState mPipBoundsState;
+ private final PipBoundsAlgorithm mPipBoundsAlgorithm;
+ private final PipTransitionState mPipTransitionState;
+ private final PipDisplayLayoutState mPipDisplayLayoutState;
+ private final Optional<SplitScreenController> mSplitScreenControllerOptional;
+
+ @Nullable
+ private Transitions.TransitionFinishCallback mFinishCallback;
+ @Nullable
+ private ValueAnimator mTransitionAnimator;
+
+ private PipExpandAnimatorSupplier mPipExpandAnimatorSupplier;
+
+ public PipExpandHandler(Context context,
+ PipBoundsState pipBoundsState,
+ PipBoundsAlgorithm pipBoundsAlgorithm,
+ PipTransitionState pipTransitionState,
+ PipDisplayLayoutState pipDisplayLayoutState,
+ Optional<SplitScreenController> splitScreenControllerOptional) {
+ mContext = context;
+ mPipBoundsState = pipBoundsState;
+ mPipBoundsAlgorithm = pipBoundsAlgorithm;
+ mPipTransitionState = pipTransitionState;
+ mPipDisplayLayoutState = pipDisplayLayoutState;
+ mSplitScreenControllerOptional = splitScreenControllerOptional;
+
+ mPipExpandAnimatorSupplier = PipExpandAnimator::new;
+ }
+
+ @Override
+ public WindowContainerTransaction handleRequest(@NonNull IBinder transition,
+ @NonNull TransitionRequestInfo request) {
+ // All Exit-via-Expand from PiP transitions are Shell initiated.
+ return null;
+ }
+
+ @Override
+ public boolean startAnimation(@NonNull IBinder transition,
+ @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction,
+ @NonNull Transitions.TransitionFinishCallback finishCallback) {
+ switch (info.getType()) {
+ case TRANSIT_EXIT_PIP:
+ return startExpandAnimation(info, startTransaction, finishTransaction,
+ finishCallback);
+ case TRANSIT_EXIT_PIP_TO_SPLIT:
+ return startExpandToSplitAnimation(info, startTransaction, finishTransaction,
+ finishCallback);
+ }
+ return false;
+ }
+
+ @Override
+ public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
+ @NonNull Transitions.TransitionFinishCallback finishCallback) {
+ end();
+ }
+
+ /**
+ * Ends the animation if such is running in the context of expanding out of PiP.
+ */
+ public void end() {
+ if (mTransitionAnimator != null && mTransitionAnimator.isRunning()) {
+ mTransitionAnimator.end();
+ mTransitionAnimator = null;
+ }
+ }
+
+ private boolean startExpandAnimation(@NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction,
+ @NonNull Transitions.TransitionFinishCallback finishCallback) {
+ WindowContainerToken pipToken = mPipTransitionState.getPipTaskToken();
+
+ TransitionInfo.Change pipChange = getChangeByToken(info, pipToken);
+ if (pipChange == null) {
+ // pipChange is null, check to see if we've reparented the PIP activity for
+ // the multi activity case. If so we should use the activity leash instead
+ for (TransitionInfo.Change change : info.getChanges()) {
+ if (change.getTaskInfo() == null
+ && change.getLastParent() != null
+ && change.getLastParent().equals(pipToken)) {
+ pipChange = change;
+ break;
+ }
+ }
+
+ // failsafe
+ if (pipChange == null) {
+ return false;
+ }
+ }
+ mFinishCallback = finishCallback;
+
+ // The parent change if we were in a multi-activity PiP; null if single activity PiP.
+ final TransitionInfo.Change parentBeforePip = pipChange.getTaskInfo() == null
+ ? getChangeByToken(info, pipChange.getParent()) : null;
+ if (parentBeforePip != null) {
+ // For multi activity, we need to manually set the leash layer
+ startTransaction.setLayer(parentBeforePip.getLeash(), Integer.MAX_VALUE - 1);
+ }
+
+ final Rect startBounds = pipChange.getStartAbsBounds();
+ final Rect endBounds = pipChange.getEndAbsBounds();
+ final SurfaceControl pipLeash = getLeash(pipChange);
+
+ PictureInPictureParams params = null;
+ if (pipChange.getTaskInfo() != null) {
+ // single activity
+ params = getPipParams(pipChange);
+ } else if (parentBeforePip != null && parentBeforePip.getTaskInfo() != null) {
+ // multi activity
+ params = getPipParams(parentBeforePip);
+ }
+ final Rect sourceRectHint = PipBoundsAlgorithm.getValidSourceHintRect(params, endBounds,
+ startBounds);
+
+ // We define delta = startRotation - endRotation, so we need to flip the sign.
+ final int delta = -getFixedRotationDelta(info, pipChange, mPipDisplayLayoutState);
+ if (delta != ROTATION_0) {
+ // Update PiP target change in place to prepare for fixed rotation;
+ handleExpandFixedRotation(pipChange, delta);
+ }
+
+ PipExpandAnimator animator = mPipExpandAnimatorSupplier.get(mContext, pipLeash,
+ startTransaction, finishTransaction, endBounds, startBounds, endBounds,
+ sourceRectHint, delta);
+ animator.setAnimationEndCallback(() -> {
+ if (parentBeforePip != null) {
+ // TODO b/377362511: Animate local leash instead to also handle letterbox case.
+ // For multi-activity, set the crop to be null
+ finishTransaction.setCrop(pipLeash, null);
+ }
+ finishTransition();
+ });
+ cacheAndStartTransitionAnimator(animator);
+ saveReentryState();
+ return true;
+ }
+
+ private boolean startExpandToSplitAnimation(@NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction,
+ @NonNull Transitions.TransitionFinishCallback finishCallback) {
+ WindowContainerToken pipToken = mPipTransitionState.getPipTaskToken();
+
+ // Expanding PiP to Split-screen makes sense only if we are dealing with multi-activity PiP
+ // and the lastParentBeforePip is still in one of the split-stages.
+ //
+ // This means we should be animating the PiP activity leash, since we do the reparenting
+ // of the PiP activity back to its original task in startWCT.
+ TransitionInfo.Change pipChange = null;
+ for (TransitionInfo.Change change : info.getChanges()) {
+ if (change.getTaskInfo() == null
+ && change.getLastParent() != null
+ && change.getLastParent().equals(pipToken)) {
+ pipChange = change;
+ break;
+ }
+ }
+ // failsafe
+ if (pipChange == null || pipChange.getLeash() == null) {
+ return false;
+ }
+ mFinishCallback = finishCallback;
+
+ // Get the original parent before PiP. If original task hosting the PiP activity was
+ // already visible, then it's not participating in this transition; in that case,
+ // parentBeforePip would be null.
+ final TransitionInfo.Change parentBeforePip = getChangeByToken(info, pipChange.getParent());
+
+ final Rect startBounds = pipChange.getStartAbsBounds();
+ final Rect endBounds = pipChange.getEndAbsBounds();
+ if (parentBeforePip != null) {
+ // Since we have the parent task amongst the targets, all PiP activity
+ // leash translations will be relative to the original task, NOT the root leash.
+ startBounds.offset(-parentBeforePip.getStartAbsBounds().left,
+ -parentBeforePip.getStartAbsBounds().top);
+ endBounds.offset(-parentBeforePip.getEndAbsBounds().left,
+ -parentBeforePip.getEndAbsBounds().top);
+ }
+
+ final SurfaceControl pipLeash = pipChange.getLeash();
+ PipExpandAnimator animator = mPipExpandAnimatorSupplier.get(mContext, pipLeash,
+ startTransaction, finishTransaction, endBounds, startBounds, endBounds,
+ null /* srcRectHint */, ROTATION_0 /* delta */);
+
+
+ mSplitScreenControllerOptional.ifPresent(splitController -> {
+ splitController.finishEnterSplitScreen(finishTransaction);
+ });
+
+ animator.setAnimationEndCallback(() -> {
+ if (parentBeforePip == null) {
+ // After PipExpandAnimator is done modifying finishTransaction, we need to make
+ // sure PiP activity leash is offset at origin relative to its task as we reparent
+ // targets back from the transition root leash.
+ finishTransaction.setPosition(pipLeash, 0, 0);
+ }
+ finishTransition();
+ });
+ cacheAndStartTransitionAnimator(animator);
+ saveReentryState();
+ return true;
+ }
+
+ private void finishTransition() {
+ final int currentState = mPipTransitionState.getState();
+ if (currentState != PipTransitionState.EXITING_PIP) {
+ ProtoLog.e(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "Unexpected state %s as we are finishing an exit-via-expand transition",
+ mPipTransitionState);
+ }
+ mPipTransitionState.setState(PipTransitionState.EXITED_PIP);
+
+ if (mFinishCallback != null) {
+ // Need to unset mFinishCallback first because onTransitionFinished can re-enter this
+ // handler if there is a pending PiP animation.
+ final Transitions.TransitionFinishCallback finishCallback = mFinishCallback;
+ mFinishCallback = null;
+ finishCallback.onTransitionFinished(null /* finishWct */);
+ }
+ }
+
+ private void handleExpandFixedRotation(TransitionInfo.Change outPipTaskChange, int delta) {
+ final Rect endBounds = outPipTaskChange.getEndAbsBounds();
+ final int width = endBounds.width();
+ final int height = endBounds.height();
+ final int left = endBounds.left;
+ final int top = endBounds.top;
+ int newTop, newLeft;
+
+ if (delta == Surface.ROTATION_90) {
+ newLeft = top;
+ newTop = -(left + width);
+ } else {
+ newLeft = -(height + top);
+ newTop = left;
+ }
+ // Modify the endBounds, rotating and placing them potentially off-screen, so that
+ // as we translate and rotate around the origin, we place them right into the target.
+ endBounds.set(newLeft, newTop, newLeft + height, newTop + width);
+ }
+
+ private void saveReentryState() {
+ float snapFraction = mPipBoundsAlgorithm.getSnapFraction(
+ mPipBoundsState.getBounds());
+ mPipBoundsState.saveReentryState(snapFraction);
+ }
+
+ private void cacheAndStartTransitionAnimator(@NonNull ValueAnimator animator) {
+ mTransitionAnimator = animator;
+ mTransitionAnimator.start();
+ }
+
+ @VisibleForTesting
+ interface PipExpandAnimatorSupplier {
+ PipExpandAnimator get(Context context,
+ @NonNull SurfaceControl leash,
+ SurfaceControl.Transaction startTransaction,
+ SurfaceControl.Transaction finishTransaction,
+ @NonNull Rect baseBounds,
+ @NonNull Rect startBounds,
+ @NonNull Rect endBounds,
+ @Nullable Rect sourceRectHint,
+ @Surface.Rotation int rotation);
+ }
+
+ @VisibleForTesting
+ void setPipExpandAnimatorSupplier(@NonNull PipExpandAnimatorSupplier supplier) {
+ mPipExpandAnimatorSupplier = supplier;
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/transition/PipTransitionUtils.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/transition/PipTransitionUtils.java
new file mode 100644
index 000000000000..01cda6c91108
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/transition/PipTransitionUtils.java
@@ -0,0 +1,133 @@
+/*
+ * 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.pip2.phone.transition;
+
+import static android.app.WindowConfiguration.ROTATION_UNDEFINED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+import static android.view.Surface.ROTATION_0;
+
+import android.annotation.NonNull;
+import android.app.PictureInPictureParams;
+import android.view.Surface;
+import android.view.SurfaceControl;
+import android.window.TransitionInfo;
+import android.window.WindowContainerToken;
+
+import androidx.annotation.Nullable;
+
+import com.android.internal.util.Preconditions;
+import com.android.wm.shell.common.pip.PipDisplayLayoutState;
+
+/**
+ * A set of utility methods to help resolve PiP transitions.
+ */
+public class PipTransitionUtils {
+
+ /**
+ * @return change for a pinned mode task; null if no such task is in the list of changes.
+ */
+ @Nullable
+ public static TransitionInfo.Change getPipChange(TransitionInfo info) {
+ for (TransitionInfo.Change change : info.getChanges()) {
+ if (change.getTaskInfo() != null
+ && change.getTaskInfo().getWindowingMode() == WINDOWING_MODE_PINNED) {
+ return change;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * @return change for a task with the provided token; null if no task with such token found.
+ */
+ @Nullable
+ public static TransitionInfo.Change getChangeByToken(TransitionInfo info,
+ WindowContainerToken token) {
+ for (TransitionInfo.Change change : info.getChanges()) {
+ if (change.getTaskInfo() != null
+ && change.getTaskInfo().getToken().equals(token)) {
+ return change;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * @return the leash to interact with the container this change represents.
+ * @throws NullPointerException if the leash is null.
+ */
+ @NonNull
+ public static SurfaceControl getLeash(TransitionInfo.Change change) {
+ SurfaceControl leash = change.getLeash();
+ Preconditions.checkNotNull(leash, "Leash is null for change=" + change);
+ return leash;
+ }
+
+ /**
+ * Get the rotation delta in a potential fixed rotation transition.
+ *
+ * Whenever PiP participates in fixed rotation, its actual orientation isn't updated
+ * in the initial transition as per the async rotation convention.
+ *
+ * @param pipChange PiP change to verify that PiP task's rotation wasn't updated already.
+ * @param pipDisplayLayoutState display layout state that PiP component keeps track of.
+ */
+ @Surface.Rotation
+ public static int getFixedRotationDelta(@NonNull TransitionInfo info,
+ @NonNull TransitionInfo.Change pipChange,
+ @NonNull PipDisplayLayoutState pipDisplayLayoutState) {
+ TransitionInfo.Change fixedRotationChange = findFixedRotationChange(info);
+ int startRotation = pipChange.getStartRotation();
+ if (pipChange.getEndRotation() != ROTATION_UNDEFINED
+ && startRotation != pipChange.getEndRotation()) {
+ // If PiP change was collected along with the display change and the orientation change
+ // happened in sync with the PiP change, then do not treat this as fixed-rotation case.
+ return ROTATION_0;
+ }
+
+ int endRotation = fixedRotationChange != null
+ ? fixedRotationChange.getEndFixedRotation() : pipDisplayLayoutState.getRotation();
+ int delta = endRotation == ROTATION_UNDEFINED ? ROTATION_0
+ : startRotation - endRotation;
+ return delta;
+ }
+
+ /**
+ * Gets a change amongst the transition targets that is in a different final orientation than
+ * the display, signalling a potential fixed rotation transition.
+ */
+ @Nullable
+ public static TransitionInfo.Change findFixedRotationChange(@NonNull TransitionInfo info) {
+ for (int i = info.getChanges().size() - 1; i >= 0; --i) {
+ final TransitionInfo.Change change = info.getChanges().get(i);
+ if (change.getEndFixedRotation() != ROTATION_UNDEFINED) {
+ return change;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * @return {@link PictureInPictureParams} provided by the client from the PiP change.
+ */
+ @NonNull
+ public static PictureInPictureParams getPipParams(@NonNull TransitionInfo.Change pipChange) {
+ return pipChange.getTaskInfo().pictureInPictureParams != null
+ ? pipChange.getTaskInfo().pictureInPictureParams
+ : new PictureInPictureParams.Builder().build();
+ }
+}
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 4f2e028a1df0..2fa09664b73f 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
@@ -381,7 +381,8 @@ public class RecentTasksController implements TaskStackListenerCallback,
private void notifyRunningTaskAppeared(RunningTaskInfo taskInfo) {
if (mListener == null
|| !shouldEnableRunningTasksForDesktopMode()
- || taskInfo.realActivity == null) {
+ || taskInfo.realActivity == null
+ || excludeTaskFromGeneratedList(taskInfo)) {
return;
}
try {
@@ -397,7 +398,8 @@ public class RecentTasksController implements TaskStackListenerCallback,
private void notifyRunningTaskChanged(RunningTaskInfo taskInfo) {
if (mListener == null
|| !shouldEnableRunningTasksForDesktopMode()
- || taskInfo.realActivity == null) {
+ || taskInfo.realActivity == null
+ || excludeTaskFromGeneratedList(taskInfo)) {
return;
}
try {
@@ -413,7 +415,8 @@ public class RecentTasksController implements TaskStackListenerCallback,
private void notifyRunningTaskVanished(RunningTaskInfo taskInfo) {
if (mListener == null
|| !shouldEnableRunningTasksForDesktopMode()
- || taskInfo.realActivity == null) {
+ || taskInfo.realActivity == null
+ || excludeTaskFromGeneratedList(taskInfo)) {
return;
}
try {
@@ -430,7 +433,8 @@ public class RecentTasksController implements TaskStackListenerCallback,
if (mListener == null
|| !DesktopModeFlags.ENABLE_TASK_STACK_OBSERVER_IN_SHELL.isTrue()
|| taskInfo.realActivity == null
- || enableShellTopTaskTracking()) {
+ || enableShellTopTaskTracking()
+ || excludeTaskFromGeneratedList(taskInfo)) {
return;
}
try {
@@ -447,7 +451,8 @@ public class RecentTasksController implements TaskStackListenerCallback,
if (mListener == null
|| !DesktopModeFlags.ENABLE_TASK_STACK_OBSERVER_IN_SHELL.isTrue()
|| taskInfo.realActivity == null
- || enableShellTopTaskTracking()) {
+ || enableShellTopTaskTracking()
+ || excludeTaskFromGeneratedList(taskInfo)) {
return;
}
try {
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 a969845fb8e8..847a0383e7d0 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
@@ -796,7 +796,8 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler,
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
" unhandled root taskId=%d", taskInfo.taskId);
}
- } else if (TransitionUtil.isDividerBar(change)) {
+ } else if (TransitionUtil.isDividerBar(change)
+ || TransitionUtil.isDimLayer(change)) {
final RemoteAnimationTarget target = TransitionUtil.newTarget(change,
belowLayers - i, info, t, mLeashMap);
// Add this as a app and we will separate them on launcher side by window type.
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 a799b7f2580e..73b42d6f007c 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
@@ -40,11 +40,13 @@ import static com.android.wm.shell.Flags.enableFlexibleSplit;
import static com.android.wm.shell.Flags.enableFlexibleTwoAppSplit;
import static com.android.wm.shell.common.split.SplitLayout.PARALLAX_ALIGN_CENTER;
import static com.android.wm.shell.common.split.SplitLayout.PARALLAX_FLEX;
+import static com.android.wm.shell.common.split.SplitLayout.RESTING_DIM_LAYER;
import static com.android.wm.shell.common.split.SplitScreenUtils.reverseSplitPosition;
import static com.android.wm.shell.common.split.SplitScreenUtils.splitFailureMessage;
import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN;
import static com.android.wm.shell.shared.TransitionUtil.isClosingType;
import static com.android.wm.shell.shared.TransitionUtil.isOpeningType;
+import static com.android.wm.shell.shared.split.SplitScreenConstants.FLAG_IS_DIM_LAYER;
import static com.android.wm.shell.shared.split.SplitScreenConstants.FLAG_IS_DIVIDER_BAR;
import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_10_90;
import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_50_50;
@@ -1824,6 +1826,14 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
// Ensure divider surface are re-parented back into the hierarchy at the end of the
// transition. See Transition#buildFinishTransaction for more detail.
finishT.reparent(mSplitLayout.getDividerLeash(), mRootTaskLeash);
+ if (Flags.enableFlexibleSplit()) {
+ mStageOrderOperator.getActiveStages().forEach(stage -> {
+ finishT.reparent(stage.mDimLayer, stage.mRootLeash);
+ });
+ } else if (Flags.enableFlexibleTwoAppSplit()) {
+ finishT.reparent(mMainStage.mDimLayer, mMainStage.mRootLeash);
+ finishT.reparent(mSideStage.mDimLayer, mSideStage.mRootLeash);
+ }
updateSurfaceBounds(mSplitLayout, finishT, false /* applyResizingOffset */);
finishT.show(mRootTaskLeash);
@@ -3540,6 +3550,9 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
finishEnterSplitScreen(finishT);
addDividerBarToTransition(info, true /* show */);
+ if (Flags.enableFlexibleTwoAppSplit()) {
+ addAllDimLayersToTransition(info, true /* show */);
+ }
return true;
}
@@ -3790,6 +3803,9 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
}
addDividerBarToTransition(info, false /* show */);
+ if (Flags.enableFlexibleTwoAppSplit()) {
+ addAllDimLayersToTransition(info, false /* show */);
+ }
}
/** Call this when the recents animation canceled during split-screen. */
@@ -3836,6 +3852,19 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
returnToApp);
mPausingTasks.clear();
if (returnToApp) {
+ // Reparent auxiliary surfaces (divider bar and dim layers) back onto their
+ // original roots.
+ if (Flags.enableFlexibleSplit()) {
+ mStageOrderOperator.getActiveStages().forEach(stage -> {
+ finishT.reparent(stage.mDimLayer, stage.mRootLeash);
+ finishT.setLayer(stage.mDimLayer, RESTING_DIM_LAYER);
+ });
+ } else if (Flags.enableFlexibleTwoAppSplit()) {
+ finishT.reparent(mMainStage.mDimLayer, mMainStage.mRootLeash);
+ finishT.reparent(mSideStage.mDimLayer, mSideStage.mRootLeash);
+ finishT.setLayer(mMainStage.mDimLayer, RESTING_DIM_LAYER);
+ finishT.setLayer(mSideStage.mDimLayer, RESTING_DIM_LAYER);
+ }
updateSurfaceBounds(mSplitLayout, finishT,
false /* applyResizingOffset */);
finishT.reparent(mSplitLayout.getDividerLeash(), mRootTaskLeash);
@@ -3902,6 +3931,39 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
info.addChange(barChange);
}
+ /** Add dim layers to the transition, so that they can be hidden/shown when animation starts. */
+ private void addAllDimLayersToTransition(@NonNull TransitionInfo info, boolean show) {
+ if (Flags.enableFlexibleSplit()) {
+ List<StageTaskListener> stages = mStageOrderOperator.getActiveStages();
+ for (int i = 0; i < stages.size(); i++) {
+ final StageTaskListener stage = stages.get(i);
+ mSplitState.getCurrentLayout().get(i).roundOut(mTempRect1);
+ addDimLayerToTransition(info, show, stage, mTempRect1);
+ }
+ } else {
+ addDimLayerToTransition(info, show, mMainStage, getMainStageBounds());
+ addDimLayerToTransition(info, show, mSideStage, getSideStageBounds());
+ }
+ }
+
+ /** Adds a single dim layer to the given TransitionInfo. */
+ private void addDimLayerToTransition(@NonNull TransitionInfo info, boolean show,
+ StageTaskListener stage, Rect bounds) {
+ final SurfaceControl dimLayer = stage.mDimLayer;
+ if (dimLayer == null || !dimLayer.isValid()) {
+ Slog.w(TAG, "addDimLayerToTransition but leash was released or not created");
+ } else {
+ final TransitionInfo.Change change =
+ new TransitionInfo.Change(null /* token */, dimLayer);
+ change.setParent(mRootTaskInfo.token);
+ change.setStartAbsBounds(bounds);
+ change.setEndAbsBounds(bounds);
+ change.setMode(show ? TRANSIT_TO_FRONT : TRANSIT_TO_BACK);
+ change.setFlags(FLAG_IS_DIM_LAYER);
+ info.addChange(change);
+ }
+ }
+
@NeverCompile
@Override
public void dump(@NonNull PrintWriter pw, String prefix) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
index c5994f83429a..e132c5ee7c6b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
@@ -681,7 +681,8 @@ public class SplashscreenContentDrawer {
// C. The background of the adaptive icon is grayscale, and the foreground of the
// adaptive icon forms a certain contrast with the theme color.
// D. Didn't specify icon background color.
- if (!iconColor.mIsBgComplex && mTmpAttrs.mIconBgColor == Color.TRANSPARENT
+ if (iconForeground != null
+ && !iconColor.mIsBgComplex && mTmpAttrs.mIconBgColor == Color.TRANSPARENT
&& (isRgbSimilarInHsv(mThemeColor, iconColor.mBgColor)
|| (iconColor.mIsBgGrayscale
&& !isRgbSimilarInHsv(mThemeColor, iconColor.mFgColor)))) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenWindowCreator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenWindowCreator.java
index cc962acf1182..caed194c5fd8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenWindowCreator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenWindowCreator.java
@@ -368,8 +368,12 @@ class SplashscreenWindowCreator extends AbsSplashWindowCreator {
mStartingWindowRecordManager.addRecord(taskId, tView);
}
- private void removeWindowInner(@NonNull View decorView, boolean hideView) {
+ private void removeWindowInner(@NonNull View decorView, StartingWindowRemovalInfo info,
+ boolean hideView) {
requestTopUi(false);
+ if (info.windowAnimationLeash != null && info.windowAnimationLeash.isValid()) {
+ info.windowAnimationLeash.release();
+ }
if (decorView.getParent() == null) {
Slog.w(TAG, "This root view has no parent, never been added to a ViewRootImpl?");
return;
@@ -452,22 +456,22 @@ class SplashscreenWindowCreator extends AbsSplashWindowCreator {
if (mSplashView == null) {
// shouldn't happen, the app window may be drawn earlier than starting window?
Slog.e(TAG, "Found empty splash screen, remove!");
- removeWindowInner(mRootView, false);
+ removeWindowInner(mRootView, info, false);
return true;
}
if (immediately
|| mSuggestType == STARTING_WINDOW_TYPE_LEGACY_SPLASH_SCREEN) {
- removeWindowInner(mRootView, false);
+ removeWindowInner(mRootView, info, false);
} else {
if (info.playRevealAnimation) {
mSplashscreenContentDrawer.applyExitAnimation(mSplashView,
info.windowAnimationLeash, info.mainFrame,
- () -> removeWindowInner(mRootView, true),
+ () -> removeWindowInner(mRootView, info, true),
mCreateTime, info.roundedCornerRadius);
} else {
// the SplashScreenView has been copied to client, hide the view to skip
// default exit animation
- removeWindowInner(mRootView, true);
+ removeWindowInner(mRootView, info, true);
}
}
return true;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
index 7aa00370ff58..7871179a50de 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
@@ -339,6 +339,7 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel, FocusT
taskInfo,
taskSurface,
mMainHandler,
+ mMainExecutor,
mBgExecutor,
mMainChoreographer,
mSyncQueue,
@@ -389,7 +390,9 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel, FocusT
} else if (id == R.id.back_button) {
mTaskOperations.injectBackKey(mDisplayId);
} else if (id == R.id.minimize_window) {
- mTaskOperations.minimizeTask(mTaskToken);
+ // This minimize button uses the same effect for any minimization. The last argument
+ // doesn't matter.
+ mTaskOperations.minimizeTask(mTaskToken, mTaskId, /* isLastTask= */ false);
} else if (id == R.id.maximize_window) {
RunningTaskInfo taskInfo = mTaskOrganizer.getRunningTaskInfo(mTaskId);
final DisplayAreaInfo rootDisplayAreaInfo =
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
index 23bb2aa616f9..49510c8060fc 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
@@ -48,6 +48,8 @@ import android.view.View;
import android.view.ViewConfiguration;
import android.view.WindowInsets;
import android.view.WindowManager;
+import android.view.WindowManagerGlobal;
+import android.window.DesktopModeFlags;
import android.window.WindowContainerTransaction;
import com.android.internal.annotations.VisibleForTesting;
@@ -58,6 +60,7 @@ 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.shared.annotations.ShellBackgroundThread;
+import com.android.wm.shell.shared.annotations.ShellMainThread;
import com.android.wm.shell.windowdecor.common.viewhost.WindowDecorViewHost;
import com.android.wm.shell.windowdecor.common.viewhost.WindowDecorViewHostSupplier;
import com.android.wm.shell.windowdecor.extension.TaskInfoKt;
@@ -69,6 +72,7 @@ import com.android.wm.shell.windowdecor.extension.TaskInfoKt;
*/
public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearLayout> {
private final Handler mHandler;
+ private final @ShellMainThread ShellExecutor mMainExecutor;
private final @ShellBackgroundThread ShellExecutor mBgExecutor;
private final Choreographer mChoreographer;
private final SyncTransactionQueue mSyncQueue;
@@ -90,6 +94,7 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL
RunningTaskInfo taskInfo,
SurfaceControl taskSurface,
Handler handler,
+ @ShellMainThread ShellExecutor mainExecutor,
@ShellBackgroundThread ShellExecutor bgExecutor,
Choreographer choreographer,
SyncTransactionQueue syncQueue,
@@ -97,6 +102,7 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL
super(context, userContext, displayController, taskOrganizer, taskInfo,
taskSurface, windowDecorViewHostSupplier);
mHandler = handler;
+ mMainExecutor = mainExecutor;
mBgExecutor = bgExecutor;
mChoreographer = choreographer;
mSyncQueue = syncQueue;
@@ -287,8 +293,14 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL
if (oldDecorationSurface != mDecorationContainerSurface || mDragResizeListener == null) {
closeDragResizeListener();
+ final ShellExecutor bgExecutor =
+ DesktopModeFlags.ENABLE_DRAG_RESIZE_SET_UP_IN_BG_THREAD.isTrue()
+ ? mBgExecutor : mMainExecutor;
mDragResizeListener = new DragResizeInputListener(
mContext,
+ WindowManagerGlobal.getWindowSession(),
+ mMainExecutor,
+ bgExecutor,
mTaskInfo,
mHandler,
mChoreographer,
@@ -299,17 +311,19 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL
mSurfaceControlTransactionSupplier,
mDisplayController);
}
-
+ final DragResizeInputListener newListener = mDragResizeListener;
final int touchSlop = ViewConfiguration.get(mResult.mRootView.getContext())
.getScaledTouchSlop();
-
final Resources res = mResult.mRootView.getResources();
- mDragResizeListener.setGeometry(new DragResizeWindowGeometry(0 /* taskCornerRadius */,
- new Size(mResult.mWidth, mResult.mHeight),
- getResizeEdgeHandleSize(res),
- getResizeHandleEdgeInset(res), getFineResizeCornerSize(res),
- getLargeResizeCornerSize(res), DragResizeWindowGeometry.DisabledEdge.NONE),
- touchSlop);
+ final DragResizeWindowGeometry newGeometry = new DragResizeWindowGeometry(
+ 0 /* taskCornerRadius */,
+ new Size(mResult.mWidth, mResult.mHeight),
+ getResizeEdgeHandleSize(res),
+ getResizeHandleEdgeInset(res), getFineResizeCornerSize(res),
+ getLargeResizeCornerSize(res), DragResizeWindowGeometry.DisabledEdge.NONE);
+ newListener.addInitializedCallback(() -> {
+ mDragResizeListener.setGeometry(newGeometry, touchSlop);
+ });
}
/**
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 1cc04b421132..5a6ea214e561 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
@@ -149,6 +149,8 @@ import com.android.wm.shell.windowdecor.common.viewhost.WindowDecorViewHost;
import com.android.wm.shell.windowdecor.common.viewhost.WindowDecorViewHostSupplier;
import com.android.wm.shell.windowdecor.extension.InsetsStateKt;
import com.android.wm.shell.windowdecor.extension.TaskInfoKt;
+import com.android.wm.shell.windowdecor.tiling.DesktopTilingDecorViewModel;
+import com.android.wm.shell.windowdecor.tiling.SnapEventHandler;
import com.android.wm.shell.windowdecor.viewholder.AppHeaderViewHolder;
import kotlin.Pair;
@@ -173,7 +175,7 @@ import java.util.function.Supplier;
*/
public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
- FocusTransitionListener {
+ FocusTransitionListener, SnapEventHandler {
private static final String TAG = "DesktopModeWindowDecorViewModel";
private final DesktopModeWindowDecoration.Factory mDesktopModeWindowDecorFactory;
@@ -255,6 +257,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
private final WindowDecorTaskResourceLoader mTaskResourceLoader;
private final RecentsTransitionHandler mRecentsTransitionHandler;
private final DesktopModeCompatPolicy mDesktopModeCompatPolicy;
+ private final DesktopTilingDecorViewModel mDesktopTilingDecorViewModel;
public DesktopModeWindowDecorViewModel(
Context context,
@@ -292,7 +295,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
DesktopModeUiEventLogger desktopModeUiEventLogger,
WindowDecorTaskResourceLoader taskResourceLoader,
RecentsTransitionHandler recentsTransitionHandler,
- DesktopModeCompatPolicy desktopModeCompatPolicy) {
+ DesktopModeCompatPolicy desktopModeCompatPolicy,
+ DesktopTilingDecorViewModel desktopTilingDecorViewModel) {
this(
context,
shellExecutor,
@@ -335,7 +339,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
desktopModeUiEventLogger,
taskResourceLoader,
recentsTransitionHandler,
- desktopModeCompatPolicy);
+ desktopModeCompatPolicy,
+ desktopTilingDecorViewModel);
}
@VisibleForTesting
@@ -381,7 +386,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
DesktopModeUiEventLogger desktopModeUiEventLogger,
WindowDecorTaskResourceLoader taskResourceLoader,
RecentsTransitionHandler recentsTransitionHandler,
- DesktopModeCompatPolicy desktopModeCompatPolicy) {
+ DesktopModeCompatPolicy desktopModeCompatPolicy,
+ DesktopTilingDecorViewModel desktopTilingDecorViewModel) {
mContext = context;
mMainExecutor = shellExecutor;
mMainHandler = mainHandler;
@@ -452,7 +458,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
mTaskResourceLoader = taskResourceLoader;
mRecentsTransitionHandler = recentsTransitionHandler;
mDesktopModeCompatPolicy = desktopModeCompatPolicy;
-
+ mDesktopTilingDecorViewModel = desktopTilingDecorViewModel;
+ mDesktopTasksController.setSnapEventHandler(this);
shellInit.addInitCallback(this::onInit, this);
}
@@ -723,8 +730,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
decoration.mTaskInfo,
left ? SnapPosition.LEFT : SnapPosition.RIGHT,
left ? ResizeTrigger.SNAP_LEFT_MENU : ResizeTrigger.SNAP_RIGHT_MENU,
- inputMethod,
- decoration);
+ inputMethod);
decoration.closeHandleMenu();
decoration.closeMaximizeMenu();
@@ -885,6 +891,33 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
return snapshotList;
}
+ @Override
+ public boolean snapToHalfScreen(@NonNull RunningTaskInfo taskInfo,
+ @NonNull Rect currentDragBounds, @NonNull SnapPosition position) {
+ return mDesktopTilingDecorViewModel.snapToHalfScreen(taskInfo,
+ mWindowDecorByTaskId.get(taskInfo.taskId), position, currentDragBounds);
+ }
+
+ @Override
+ public void removeTaskIfTiled(int displayId, int taskId) {
+ mDesktopTilingDecorViewModel.removeTaskIfTiled(displayId, taskId);
+ }
+
+ @Override
+ public void onUserChange() {
+ mDesktopTilingDecorViewModel.onUserChange();
+ }
+
+ @Override
+ public void onOverviewAnimationStateChange(boolean running) {
+ mDesktopTilingDecorViewModel.onOverviewAnimationStateChange(running);
+ }
+
+ @Override
+ public boolean moveTaskToFrontIfTiled(@NonNull RunningTaskInfo taskInfo) {
+ return mDesktopTilingDecorViewModel.moveTaskToFrontIfTiled(taskInfo);
+ }
+
private class DesktopModeTouchEventListener extends GestureDetector.SimpleOnGestureListener
implements View.OnClickListener, View.OnTouchListener, View.OnLongClickListener,
View.OnGenericMotionListener, DragDetector.MotionEventHandler {
@@ -951,7 +984,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
mDesktopTasksController.onDesktopWindowClose(
wct, mDisplayId, decoration.mTaskInfo);
final IBinder transition = mTaskOperations.closeTask(mTaskToken, wct);
- if (transition != null && runOnTransitionStart != null) {
+ if (transition != null) {
runOnTransitionStart.invoke(transition);
}
}
@@ -1238,8 +1271,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
taskInfo, decoration.mTaskSurface,
new PointF(e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx)),
newTaskBounds, decoration.calculateValidDragArea(),
- new Rect(mOnDragStartInitialBounds), e,
- mWindowDecorByTaskId.get(taskInfo.taskId));
+ new Rect(mOnDragStartInitialBounds), e);
if (touchingButton) {
// We need the input event to not be consumed here to end the ripple
// effect on the touched button. We will reset drag state in the ensuing
@@ -1444,16 +1476,13 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
relevantDecor.mTaskInfo.configuration.windowConfiguration.getBounds());
boolean dragFromStatusBarAllowed = false;
final int windowingMode = relevantDecor.mTaskInfo.getWindowingMode();
- if (DesktopModeStatus.canEnterDesktopMode(mContext)) {
+ if (DesktopModeStatus.canEnterDesktopMode(mContext)
+ || BubbleAnythingFlagHelper.enableBubbleToFullscreen()) {
// In proto2 any full screen or multi-window task can be dragged to
// freeform.
dragFromStatusBarAllowed = windowingMode == WINDOWING_MODE_FULLSCREEN
|| windowingMode == WINDOWING_MODE_MULTI_WINDOW;
}
- if (BubbleAnythingFlagHelper.enableBubbleToFullscreen()) {
- // TODO(b/388851898): add support for split screen (multi-window wm mode)
- dragFromStatusBarAllowed = windowingMode == WINDOWING_MODE_FULLSCREEN;
- }
final boolean shouldStartTransitionDrag =
relevantDecor.checkTouchEventInFocusedCaptionHandle(ev)
|| DesktopModeFlags.ENABLE_HANDLE_INPUT_FIX.isTrue();
@@ -1502,7 +1531,11 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
// Do not create an indicator at all if we're not past transition height.
DisplayLayout layout = mDisplayController
.getDisplayLayout(relevantDecor.mTaskInfo.displayId);
- if (ev.getRawY() < 2 * layout.stableInsets().top
+ // It's possible task is not at the top of the screen (e.g. bottom of vertical
+ // Splitscreen)
+ final int taskTop = relevantDecor.mTaskInfo.configuration.windowConfiguration
+ .getBounds().top;
+ if (ev.getRawY() < 2 * layout.stableInsets().top + taskTop
&& mMoveToDesktopAnimator == null) {
return;
}
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 3fb94630eab3..dca376f7df0e 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
@@ -68,6 +68,7 @@ import android.view.View;
import android.view.ViewConfiguration;
import android.view.WindowInsets;
import android.view.WindowManager;
+import android.view.WindowManagerGlobal;
import android.widget.ImageButton;
import android.window.DesktopModeFlags;
import android.window.TaskSnapshot;
@@ -479,7 +480,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
if (shouldDelayUpdate) {
return;
}
- updateDragResizeListener(mDecorationContainerSurface, inFullImmersive);
+ updateDragResizeListenerIfNeeded(mDecorationContainerSurface, inFullImmersive);
}
@@ -587,7 +588,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
closeMaximizeMenu();
notifyNoCaptionHandle();
}
- updateDragResizeListener(oldDecorationSurface, inFullImmersive);
+ updateDragResizeListenerIfNeeded(oldDecorationSurface, inFullImmersive);
updateMaximizeMenu(startT, inFullImmersive);
Trace.endSection(); // DesktopModeWindowDecoration#relayout
}
@@ -665,22 +666,42 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
return mUserContext.getUser();
}
- private void updateDragResizeListener(SurfaceControl oldDecorationSurface,
+ private void updateDragResizeListenerIfNeeded(@Nullable SurfaceControl containerSurface,
boolean inFullImmersive) {
+ final boolean taskPositionChanged = !mTaskInfo.positionInParent.equals(mPositionInParent);
if (!isDragResizable(mTaskInfo, inFullImmersive)) {
- if (!mTaskInfo.positionInParent.equals(mPositionInParent)) {
+ if (taskPositionChanged) {
// We still want to track caption bar's exclusion region on a non-resizeable task.
updateExclusionRegion(inFullImmersive);
}
closeDragResizeListener();
return;
}
+ updateDragResizeListener(containerSurface,
+ (geometryChanged) -> {
+ if (geometryChanged || taskPositionChanged) {
+ updateExclusionRegion(inFullImmersive);
+ }
+ });
+ }
- if (oldDecorationSurface != mDecorationContainerSurface || mDragResizeListener == null) {
+ private void updateDragResizeListener(@Nullable SurfaceControl containerSurface,
+ Consumer<Boolean> onUpdateFinished) {
+ final boolean containerSurfaceChanged = containerSurface != mDecorationContainerSurface;
+ final boolean isFirstDragResizeListener = mDragResizeListener == null;
+ final boolean shouldCreateListener = containerSurfaceChanged || isFirstDragResizeListener;
+ if (containerSurfaceChanged) {
closeDragResizeListener();
- Trace.beginSection("DesktopModeWindowDecoration#relayout-DragResizeInputListener");
+ }
+ if (shouldCreateListener) {
+ final ShellExecutor bgExecutor =
+ DesktopModeFlags.ENABLE_DRAG_RESIZE_SET_UP_IN_BG_THREAD.isTrue()
+ ? mBgExecutor : mMainExecutor;
mDragResizeListener = new DragResizeInputListener(
mContext,
+ WindowManagerGlobal.getWindowSession(),
+ mMainExecutor,
+ bgExecutor,
mTaskInfo,
mHandler,
mChoreographer,
@@ -691,24 +712,20 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
mSurfaceControlTransactionSupplier,
mDisplayController,
mDesktopModeEventLogger);
- Trace.endSection();
}
-
+ final DragResizeInputListener newListener = mDragResizeListener;
final int touchSlop = ViewConfiguration.get(mResult.mRootView.getContext())
.getScaledTouchSlop();
-
- // If either task geometry or position have changed, update this task's
- // exclusion region listener
final Resources res = mResult.mRootView.getResources();
- if (mDragResizeListener.setGeometry(
- new DragResizeWindowGeometry(mRelayoutParams.mCornerRadius,
- new Size(mResult.mWidth, mResult.mHeight),
- getResizeEdgeHandleSize(res), getResizeHandleEdgeInset(res),
- getFineResizeCornerSize(res), getLargeResizeCornerSize(res),
- mDisabledResizingEdge), touchSlop)
- || !mTaskInfo.positionInParent.equals(mPositionInParent)) {
- updateExclusionRegion(inFullImmersive);
- }
+ final DragResizeWindowGeometry newGeometry = new DragResizeWindowGeometry(
+ mRelayoutParams.mCornerRadius,
+ new Size(mResult.mWidth, mResult.mHeight),
+ getResizeEdgeHandleSize(res), getResizeHandleEdgeInset(res),
+ getFineResizeCornerSize(res), getLargeResizeCornerSize(res),
+ mDisabledResizingEdge);
+ newListener.addInitializedCallback(() -> {
+ onUpdateFinished.accept(newListener.setGeometry(newGeometry, touchSlop));
+ });
}
private static boolean isDragResizable(ActivityManager.RunningTaskInfo taskInfo,
@@ -803,8 +820,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
if (!mTaskInfo.isVisible()) {
closeMaximizeMenu();
} else {
- final int menuWidth = calculateMaximizeMenuWidth();
- mMaximizeMenu.positionMenu(calculateMaximizeMenuPosition(menuWidth), startT);
+ mMaximizeMenu.positionMenu(startT);
}
}
@@ -1069,27 +1085,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
return Resources.ID_NULL;
}
- private int calculateMaximizeMenuWidth() {
- final boolean showImmersive = DesktopModeFlags.ENABLE_FULLY_IMMERSIVE_IN_DESKTOP.isTrue()
- && TaskInfoKt.getRequestingImmersive(mTaskInfo);
- final boolean showMaximize = true;
- final boolean showSnaps = mTaskInfo.isResizeable;
- int showCount = 0;
- if (showImmersive) showCount++;
- if (showMaximize) showCount++;
- if (showSnaps) showCount++;
- return switch (showCount) {
- case 1 -> loadDimensionPixelSize(mContext.getResources(),
- R.dimen.desktop_mode_maximize_menu_width_one_options);
- case 2 -> loadDimensionPixelSize(mContext.getResources(),
- R.dimen.desktop_mode_maximize_menu_width_two_options);
- case 3 -> loadDimensionPixelSize(mContext.getResources(),
- R.dimen.desktop_mode_maximize_menu_width_three_options);
- default -> throw new IllegalArgumentException("");
- };
- }
-
- private PointF calculateMaximizeMenuPosition(int menuWidth) {
+ private PointF calculateMaximizeMenuPosition(int menuWidth, int menuHeight) {
final PointF position = new PointF();
final Resources resources = mContext.getResources();
final DisplayLayout displayLayout =
@@ -1105,9 +1101,6 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
final int[] maximizeButtonLocation = new int[2];
maximizeWindowButton.getLocationInWindow(maximizeButtonLocation);
- final int menuHeight = loadDimensionPixelSize(
- resources, R.dimen.desktop_mode_maximize_menu_height);
-
float menuLeft = (mPositionInParent.x + maximizeButtonLocation[0] - ((float) (menuWidth
- maximizeWindowButton.getWidth()) / 2));
float menuTop = (mPositionInParent.y + captionHeight);
@@ -1294,17 +1287,16 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
* Create and display maximize menu window
*/
void createMaximizeMenu() {
- final int menuWidth = calculateMaximizeMenuWidth();
mMaximizeMenu = mMaximizeMenuFactory.create(mSyncQueue, mRootTaskDisplayAreaOrganizer,
mDisplayController, mTaskInfo, mContext,
- calculateMaximizeMenuPosition(menuWidth), mSurfaceControlTransactionSupplier);
+ (width, height) -> calculateMaximizeMenuPosition(width, height),
+ mSurfaceControlTransactionSupplier);
mMaximizeMenu.show(
/* isTaskInImmersiveMode= */
DesktopModeFlags.ENABLE_FULLY_IMMERSIVE_IN_DESKTOP.isTrue()
&& mDesktopUserRepositories.getProfile(mTaskInfo.userId)
.isTaskInFullImmersiveState(mTaskInfo.taskId),
- /* menuWidth= */ menuWidth,
/* showImmersiveOption= */
DesktopModeFlags.ENABLE_FULLY_IMMERSIVE_IN_DESKTOP.isTrue()
&& TaskInfoKt.getRequestingImmersive(mTaskInfo),
@@ -1736,7 +1728,8 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
*/
private Region getGlobalExclusionRegion(boolean inFullImmersive) {
Region exclusionRegion;
- if (mDragResizeListener != null && isDragResizable(mTaskInfo, inFullImmersive)) {
+ if (mDragResizeListener != null
+ && isDragResizable(mTaskInfo, inFullImmersive)) {
exclusionRegion = mDragResizeListener.getCornersRegion();
} else {
exclusionRegion = new Region();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
index b531079f18c1..7a4a834e9dc2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
@@ -43,6 +43,7 @@ import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
+import android.os.Trace;
import android.util.Size;
import android.view.Choreographer;
import android.view.IWindowSession;
@@ -54,14 +55,19 @@ import android.view.PointerIcon;
import android.view.SurfaceControl;
import android.view.View;
import android.view.ViewConfiguration;
-import android.view.WindowManagerGlobal;
import android.window.InputTransferToken;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.ProtoLog;
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.desktopmode.DesktopModeEventLogger;
+import com.android.wm.shell.shared.annotations.ShellBackgroundThread;
+import com.android.wm.shell.shared.annotations.ShellMainThread;
+import java.util.ArrayList;
+import java.util.List;
import java.util.function.Consumer;
import java.util.function.Supplier;
@@ -73,28 +79,45 @@ import java.util.function.Supplier;
*/
class DragResizeInputListener implements AutoCloseable {
private static final String TAG = "DragResizeInputListener";
- private final IWindowSession mWindowSession = WindowManagerGlobal.getWindowSession();
+ private final IWindowSession mWindowSession;
+ private final TaskResizeInputEventReceiverFactory mEventReceiverFactory;
+ private final Supplier<SurfaceControl.Builder> mSurfaceControlBuilderSupplier;
private final Supplier<SurfaceControl.Transaction> mSurfaceControlTransactionSupplier;
private final int mDisplayId;
- private final IBinder mClientToken;
+ @VisibleForTesting
+ final IBinder mClientToken;
private final SurfaceControl mDecorationSurface;
- private final InputChannel mInputChannel;
- private final TaskResizeInputEventReceiver mInputEventReceiver;
+ private InputChannel mInputChannel;
+ private TaskResizeInputEventReceiver mInputEventReceiver;
private final Context mContext;
+ private final @ShellBackgroundThread ShellExecutor mBgExecutor;
private final RunningTaskInfo mTaskInfo;
- private final SurfaceControl mInputSinkSurface;
- private final IBinder mSinkClientToken;
- private final InputChannel mSinkInputChannel;
+ private final Handler mHandler;
+ private final Choreographer mChoreographer;
+ private SurfaceControl mInputSinkSurface;
+ @VisibleForTesting
+ final IBinder mSinkClientToken;
+ private InputChannel mSinkInputChannel;
private final DisplayController mDisplayController;
+ /** TODO: b/396490344 - this desktop-specific class should be abstracted out of here. */
private final DesktopModeEventLogger mDesktopModeEventLogger;
+ private final DragPositioningCallback mDragPositioningCallback;
private final Region mTouchRegion = new Region();
+ private final List<Runnable> mOnInitializedCallbacks = new ArrayList<>();
+
+ private final Runnable mInitInputChannels;
+ private boolean mClosed = false;
DragResizeInputListener(
Context context,
+ IWindowSession windowSession,
+ @ShellMainThread ShellExecutor mainExecutor,
+ @ShellBackgroundThread ShellExecutor bgExecutor,
+ TaskResizeInputEventReceiverFactory eventReceiverFactory,
RunningTaskInfo taskInfo,
Handler handler,
Choreographer choreographer,
@@ -106,20 +129,138 @@ class DragResizeInputListener implements AutoCloseable {
DisplayController displayController,
DesktopModeEventLogger desktopModeEventLogger) {
mContext = context;
+ mWindowSession = windowSession;
+ mBgExecutor = bgExecutor;
+ mEventReceiverFactory = eventReceiverFactory;
mTaskInfo = taskInfo;
- mSurfaceControlTransactionSupplier = surfaceControlTransactionSupplier;
+ mHandler = handler;
+ mChoreographer = choreographer;
mDisplayId = displayId;
mDecorationSurface = decorationSurface;
+ mDragPositioningCallback = callback;
+ mSurfaceControlBuilderSupplier = surfaceControlBuilderSupplier;
+ mSurfaceControlTransactionSupplier = surfaceControlTransactionSupplier;
mDisplayController = displayController;
mDesktopModeEventLogger = desktopModeEventLogger;
mClientToken = new Binder();
+ mSinkClientToken = new Binder();
+
+ // Setting up input channels for both the resize listener and the input sink requires
+ // multiple blocking binder calls, so it's moved to a bg thread to keep the shell.main
+ // thread free.
+ // The input event receiver must be created back in the shell.main thread though because
+ // its geometry and util methods are updated/queried from the shell.main thread.
+ mInitInputChannels = () -> {
+ final InputSetUpResult result = setUpInputChannels(mDisplayId, mWindowSession,
+ mDecorationSurface, mClientToken, mSinkClientToken,
+ mSurfaceControlBuilderSupplier,
+ mSurfaceControlTransactionSupplier);
+ mainExecutor.execute(() -> {
+ if (mClosed) {
+ return;
+ }
+ mInputSinkSurface = result.mInputSinkSurface;
+ mInputChannel = result.mInputChannel;
+ mSinkInputChannel = result.mSinkInputChannel;
+ Trace.beginSection("DragResizeInputListener#ctor-initReceiver");
+ mInputEventReceiver = mEventReceiverFactory.create(
+ mContext,
+ mTaskInfo,
+ mInputChannel,
+ mDragPositioningCallback,
+ mHandler,
+ mChoreographer,
+ () -> {
+ final DisplayLayout layout =
+ mDisplayController.getDisplayLayout(mDisplayId);
+ return new Size(layout.width(), layout.height());
+ },
+ this::updateSinkInputChannel,
+ mDesktopModeEventLogger);
+ mInputEventReceiver.setTouchSlop(
+ ViewConfiguration.get(mContext).getScaledTouchSlop());
+ for (Runnable initCallback : mOnInitializedCallbacks) {
+ initCallback.run();
+ }
+ mOnInitializedCallbacks.clear();
+ Trace.endSection();
+ });
+ };
+ bgExecutor.execute(mInitInputChannels);
+ }
+
+ DragResizeInputListener(
+ Context context,
+ IWindowSession windowSession,
+ @ShellMainThread ShellExecutor mainExecutor,
+ @ShellBackgroundThread ShellExecutor bgExecutor,
+ RunningTaskInfo taskInfo,
+ Handler handler,
+ Choreographer choreographer,
+ int displayId,
+ SurfaceControl decorationSurface,
+ DragPositioningCallback callback,
+ Supplier<SurfaceControl.Builder> surfaceControlBuilderSupplier,
+ Supplier<SurfaceControl.Transaction> surfaceControlTransactionSupplier,
+ DisplayController displayController,
+ DesktopModeEventLogger desktopModeEventLogger) {
+ this(context, windowSession, mainExecutor, bgExecutor,
+ new DefaultTaskResizeInputEventReceiverFactory(), taskInfo,
+ handler, choreographer, displayId, decorationSurface, callback,
+ surfaceControlBuilderSupplier, surfaceControlTransactionSupplier,
+ displayController, desktopModeEventLogger);
+ }
+
+ DragResizeInputListener(
+ Context context,
+ IWindowSession windowSession,
+ @ShellMainThread ShellExecutor mainExecutor,
+ @ShellBackgroundThread ShellExecutor bgExecutor,
+ RunningTaskInfo taskInfo,
+ Handler handler,
+ Choreographer choreographer,
+ int displayId,
+ SurfaceControl decorationSurface,
+ DragPositioningCallback callback,
+ Supplier<SurfaceControl.Builder> surfaceControlBuilderSupplier,
+ Supplier<SurfaceControl.Transaction> surfaceControlTransactionSupplier,
+ DisplayController displayController) {
+ this(context, windowSession, mainExecutor, bgExecutor, taskInfo,
+ handler, choreographer, displayId, decorationSurface, callback,
+ surfaceControlBuilderSupplier, surfaceControlTransactionSupplier,
+ displayController, new DesktopModeEventLogger());
+ }
+
+ /**
+ * Registers a callback to be invoked when the input listener has finished initializing. If
+ * already finished, the callback will be invoked immediately.
+ */
+ void addInitializedCallback(Runnable onReady) {
+ if (mInputEventReceiver != null) {
+ onReady.run();
+ return;
+ }
+ mOnInitializedCallbacks.add(onReady);
+ }
+
+ @ShellBackgroundThread
+ private static InputSetUpResult setUpInputChannels(
+ int displayId,
+ @NonNull IWindowSession windowSession,
+ @NonNull SurfaceControl decorationSurface,
+ @NonNull IBinder clientToken,
+ @NonNull IBinder sinkClientToken,
+ @NonNull Supplier<SurfaceControl.Builder> surfaceControlBuilderSupplier,
+ @NonNull Supplier<SurfaceControl.Transaction> surfaceControlTransactionSupplier) {
+ Trace.beginSection("DragResizeInputListener#setUpInputChannels");
final InputTransferToken inputTransferToken = new InputTransferToken();
- mInputChannel = new InputChannel();
+ final InputChannel inputChannel = new InputChannel();
+ final InputChannel sinkInputChannel = new InputChannel();
try {
- mWindowSession.grantInputChannel(
- mDisplayId,
- mDecorationSurface,
- mClientToken,
+ windowSession.grantInputChannel(
+ displayId,
+ decorationSurface,
+ clientToken,
null /* hostInputToken */,
FLAG_NOT_FOCUSABLE,
PRIVATE_FLAG_TRUSTED_OVERLAY,
@@ -127,37 +268,27 @@ class DragResizeInputListener implements AutoCloseable {
TYPE_APPLICATION,
null /* windowToken */,
inputTransferToken,
- TAG + " of " + decorationSurface.toString(),
- mInputChannel);
+ TAG + " of " + decorationSurface,
+ inputChannel);
} catch (RemoteException e) {
e.rethrowFromSystemServer();
}
- mInputEventReceiver = new TaskResizeInputEventReceiver(context, mTaskInfo, mInputChannel,
- callback,
- handler, choreographer, () -> {
- final DisplayLayout layout = mDisplayController.getDisplayLayout(mDisplayId);
- return new Size(layout.width(), layout.height());
- }, this::updateSinkInputChannel, mDesktopModeEventLogger);
- mInputEventReceiver.setTouchSlop(ViewConfiguration.get(context).getScaledTouchSlop());
-
- mInputSinkSurface = surfaceControlBuilderSupplier.get()
+ final SurfaceControl inputSinkSurface = surfaceControlBuilderSupplier.get()
.setName("TaskInputSink of " + decorationSurface)
.setContainerLayer()
- .setParent(mDecorationSurface)
- .setCallsite("DragResizeInputListener.constructor")
+ .setParent(decorationSurface)
+ .setCallsite("DragResizeInputListener.setUpInputChannels")
.build();
- mSurfaceControlTransactionSupplier.get()
- .setLayer(mInputSinkSurface, WindowDecoration.INPUT_SINK_Z_ORDER)
- .show(mInputSinkSurface)
+ surfaceControlTransactionSupplier.get()
+ .setLayer(inputSinkSurface, WindowDecoration.INPUT_SINK_Z_ORDER)
+ .show(inputSinkSurface)
.apply();
- mSinkClientToken = new Binder();
- mSinkInputChannel = new InputChannel();
try {
- mWindowSession.grantInputChannel(
- mDisplayId,
- mInputSinkSurface,
- mSinkClientToken,
+ windowSession.grantInputChannel(
+ displayId,
+ inputSinkSurface,
+ sinkClientToken,
null /* hostInputToken */,
FLAG_NOT_FOCUSABLE,
0 /* privateFlags */,
@@ -166,26 +297,12 @@ class DragResizeInputListener implements AutoCloseable {
null /* windowToken */,
inputTransferToken,
"TaskInputSink of " + decorationSurface,
- mSinkInputChannel);
+ sinkInputChannel);
} catch (RemoteException e) {
e.rethrowFromSystemServer();
}
- }
-
- DragResizeInputListener(
- Context context,
- RunningTaskInfo taskInfo,
- Handler handler,
- Choreographer choreographer,
- int displayId,
- SurfaceControl decorationSurface,
- DragPositioningCallback callback,
- Supplier<SurfaceControl.Builder> surfaceControlBuilderSupplier,
- Supplier<SurfaceControl.Transaction> surfaceControlTransactionSupplier,
- DisplayController displayController) {
- this(context, taskInfo, handler, choreographer, displayId, decorationSurface, callback,
- surfaceControlBuilderSupplier, surfaceControlTransactionSupplier, displayController,
- new DesktopModeEventLogger());
+ Trace.endSection();
+ return new InputSetUpResult(inputSinkSurface, inputChannel, sinkInputChannel);
}
/**
@@ -268,35 +385,101 @@ class DragResizeInputListener implements AutoCloseable {
}
boolean shouldHandleEvent(@NonNull MotionEvent e, @NonNull Point offset) {
- return mInputEventReceiver.shouldHandleEvent(e, offset);
+ return mInputEventReceiver != null && mInputEventReceiver.shouldHandleEvent(e, offset);
}
boolean isHandlingDragResize() {
- return mInputEventReceiver.isHandlingEvents();
+ return mInputEventReceiver != null && mInputEventReceiver.isHandlingEvents();
}
@Override
public void close() {
- mInputEventReceiver.dispose();
- mInputChannel.dispose();
- try {
- mWindowSession.remove(mClientToken);
- } catch (RemoteException e) {
- e.rethrowFromSystemServer();
+ mClosed = true;
+ if (mInitInputChannels != null) {
+ mBgExecutor.removeCallbacks(mInitInputChannels);
+ }
+ if (mInputEventReceiver != null) {
+ mInputEventReceiver.dispose();
+ }
+ if (mInputChannel != null) {
+ mInputChannel.dispose();
+ }
+ if (mSinkInputChannel != null) {
+ mSinkInputChannel.dispose();
}
- mSinkInputChannel.dispose();
- try {
- mWindowSession.remove(mSinkClientToken);
- } catch (RemoteException e) {
- e.rethrowFromSystemServer();
+ if (mInputSinkSurface != null) {
+ mSurfaceControlTransactionSupplier.get()
+ .remove(mInputSinkSurface)
+ .apply();
}
- mSurfaceControlTransactionSupplier.get()
- .remove(mInputSinkSurface)
- .apply();
+
+ mBgExecutor.execute(() -> {
+ try {
+ mWindowSession.remove(mClientToken);
+ mWindowSession.remove(mSinkClientToken);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ });
}
- private static class TaskResizeInputEventReceiver extends InputEventReceiver implements
+ private static class InputSetUpResult {
+ final @NonNull SurfaceControl mInputSinkSurface;
+ final @NonNull InputChannel mInputChannel;
+ final @NonNull InputChannel mSinkInputChannel;
+
+ InputSetUpResult(@NonNull SurfaceControl inputSinkSurface,
+ @NonNull InputChannel inputChannel,
+ @NonNull InputChannel sinkInputChannel) {
+ mInputSinkSurface = inputSinkSurface;
+ mInputChannel = inputChannel;
+ mSinkInputChannel = sinkInputChannel;
+ }
+ }
+
+ /** A factory that creates {@link TaskResizeInputEventReceiver}s. */
+ interface TaskResizeInputEventReceiverFactory {
+ @NonNull
+ TaskResizeInputEventReceiver create(
+ @NonNull Context context,
+ @NonNull RunningTaskInfo taskInfo,
+ @NonNull InputChannel inputChannel,
+ @NonNull DragPositioningCallback callback,
+ @NonNull Handler handler,
+ @NonNull Choreographer choreographer,
+ @NonNull Supplier<Size> displayLayoutSizeSupplier,
+ @NonNull Consumer<Region> touchRegionConsumer,
+ @NonNull DesktopModeEventLogger desktopModeEventLogger
+ );
+ }
+
+ /** A default implementation of {@link TaskResizeInputEventReceiverFactory}. */
+ static class DefaultTaskResizeInputEventReceiverFactory
+ implements TaskResizeInputEventReceiverFactory {
+ @Override
+ @NonNull
+ public TaskResizeInputEventReceiver create(
+ @NonNull Context context,
+ @NonNull RunningTaskInfo taskInfo,
+ @NonNull InputChannel inputChannel,
+ @NonNull DragPositioningCallback callback,
+ @NonNull Handler handler,
+ @NonNull Choreographer choreographer,
+ @NonNull Supplier<Size> displayLayoutSizeSupplier,
+ @NonNull Consumer<Region> touchRegionConsumer,
+ @NonNull DesktopModeEventLogger desktopModeEventLogger) {
+ return new TaskResizeInputEventReceiver(context, taskInfo, inputChannel, callback,
+ handler, choreographer, displayLayoutSizeSupplier, touchRegionConsumer,
+ desktopModeEventLogger);
+ }
+ }
+
+ /**
+ * An input event receiver to handle motion events on the task's corners and edges for
+ * drag-resizing, as well as keeping the input sink updated.
+ */
+ static class TaskResizeInputEventReceiver extends InputEventReceiver implements
DragDetector.MotionEventHandler {
@NonNull private final Context mContext;
@NonNull private final RunningTaskInfo mTaskInfo;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt
index 38accce82999..ad3525af3f94 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt
@@ -90,7 +90,7 @@ class MaximizeMenu(
private val displayController: DisplayController,
private val taskInfo: RunningTaskInfo,
private val decorWindowContext: Context,
- private val menuPosition: PointF,
+ private val positionSupplier: (Int, Int) -> PointF,
private val transactionSupplier: Supplier<Transaction> = Supplier { Transaction() }
) {
private var maximizeMenu: AdditionalViewHostViewContainer? = null
@@ -100,19 +100,19 @@ class MaximizeMenu(
private val cornerRadius = loadDimensionPixelSize(
R.dimen.desktop_mode_maximize_menu_corner_radius
).toFloat()
- private val menuHeight = loadDimensionPixelSize(R.dimen.desktop_mode_maximize_menu_height)
+ private lateinit var menuPosition: PointF
private val menuPadding = loadDimensionPixelSize(R.dimen.desktop_mode_menu_padding)
/** Position the menu relative to the caption's position. */
- fun positionMenu(position: PointF, t: Transaction) {
- menuPosition.set(position)
+ fun positionMenu(t: Transaction) {
+ menuPosition = positionSupplier(maximizeMenuView?.measureWidth() ?: 0,
+ maximizeMenuView?.measureHeight() ?: 0)
t.setPosition(leash, menuPosition.x, menuPosition.y)
}
/** Creates and shows the maximize window. */
fun show(
isTaskInImmersiveMode: Boolean,
- menuWidth: Int,
showImmersiveOption: Boolean,
showSnapOptions: Boolean,
onMaximizeOrRestoreClickListener: () -> Unit,
@@ -125,7 +125,6 @@ class MaximizeMenu(
if (maximizeMenu != null) return
createMaximizeMenu(
isTaskInImmersiveMode = isTaskInImmersiveMode,
- menuWidth = menuWidth,
showImmersiveOption = showImmersiveOption,
showSnapOptions = showSnapOptions,
onMaximizeClickListener = onMaximizeOrRestoreClickListener,
@@ -161,7 +160,6 @@ class MaximizeMenu(
/** Create a maximize menu that is attached to the display area. */
private fun createMaximizeMenu(
isTaskInImmersiveMode: Boolean,
- menuWidth: Int,
showImmersiveOption: Boolean,
showSnapOptions: Boolean,
onMaximizeClickListener: () -> Unit,
@@ -178,16 +176,6 @@ class MaximizeMenu(
.setName("Maximize Menu")
.setContainerLayer()
.build()
- val lp = WindowManager.LayoutParams(
- menuWidth,
- menuHeight,
- WindowManager.LayoutParams.TYPE_APPLICATION,
- WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
- or WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH,
- PixelFormat.TRANSPARENT
- )
- lp.title = "Maximize Menu for Task=" + taskInfo.taskId
- lp.setTrustedOverlay()
val windowManager = WindowlessWindowManager(
taskInfo.configuration,
leash,
@@ -207,7 +195,6 @@ class MaximizeMenu(
MaximizeMenuView.ImmersiveConfig.Hidden
},
showSnapOptions = showSnapOptions,
- menuHeight = menuHeight,
menuPadding = menuPadding,
).also { menuView ->
menuView.bind(taskInfo)
@@ -217,6 +204,19 @@ class MaximizeMenu(
menuView.onRightSnapClickListener = onRightSnapClickListener
menuView.onMenuHoverListener = onHoverListener
menuView.onOutsideTouchListener = onOutsideTouchListener
+ val menuWidth = menuView.measureWidth()
+ val menuHeight = menuView.measureHeight()
+ menuPosition = positionSupplier(menuWidth, menuHeight)
+ val lp = WindowManager.LayoutParams(
+ menuWidth.toInt(),
+ menuHeight.toInt(),
+ WindowManager.LayoutParams.TYPE_APPLICATION,
+ WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+ or WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH,
+ PixelFormat.TRANSPARENT
+ )
+ lp.title = "Maximize Menu for Task=" + taskInfo.taskId
+ lp.setTrustedOverlay()
viewHost.setView(menuView.rootView, lp)
}
@@ -268,7 +268,6 @@ class MaximizeMenu(
private val sizeToggleDirection: SizeToggleDirection,
immersiveConfig: ImmersiveConfig,
showSnapOptions: Boolean,
- private val menuHeight: Int,
private val menuPadding: Int
) {
val rootView = LayoutInflater.from(context)
@@ -583,7 +582,7 @@ class MaximizeMenu(
// the menu.
val value = animatedValue as Float
val topPadding = menuPadding -
- ((1 - value) * menuHeight).toInt()
+ ((1 - value) * measureHeight()).toInt()
container.setPadding(menuPadding, topPadding,
menuPadding, menuPadding)
}
@@ -604,7 +603,7 @@ class MaximizeMenu(
}
},
ObjectAnimator.ofFloat(rootView, TRANSLATION_Y,
- (STARTING_MENU_HEIGHT_SCALE - 1) * menuHeight, 0f).apply {
+ (STARTING_MENU_HEIGHT_SCALE - 1) * measureHeight(), 0f).apply {
duration = OPEN_MENU_HEIGHT_ANIMATION_DURATION_MS
interpolator = EMPHASIZED_DECELERATE
},
@@ -667,7 +666,7 @@ class MaximizeMenu(
// the menu.
val value = animatedValue as Float
val topPadding = menuPadding -
- ((1 - value) * menuHeight).toInt()
+ ((1 - value) * measureHeight()).toInt()
container.setPadding(menuPadding, topPadding,
menuPadding, menuPadding)
}
@@ -688,7 +687,7 @@ class MaximizeMenu(
}
},
ObjectAnimator.ofFloat(rootView, TRANSLATION_Y,
- 0f, (STARTING_MENU_HEIGHT_SCALE - 1) * menuHeight).apply {
+ 0f, (STARTING_MENU_HEIGHT_SCALE - 1) * measureHeight()).apply {
duration = CLOSE_MENU_HEIGHT_ANIMATION_DURATION_MS
interpolator = FAST_OUT_LINEAR_IN
},
@@ -792,6 +791,18 @@ class MaximizeMenu(
)
}
+ /** Measure width of the root view of this menu. */
+ fun measureWidth() : Int {
+ rootView.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
+ return rootView.getMeasuredWidth()
+ }
+
+ /** Measure height of the root view of this menu. */
+ fun measureHeight() : Int {
+ rootView.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
+ return rootView.getMeasuredHeight()
+ }
+
private fun deactivateSnapOptions() {
// TODO(b/346440693): the background/colorStateList set on these buttons is overridden
// to a static resource & color on manually tracked hover events, which defeats the
@@ -1036,7 +1047,7 @@ interface MaximizeMenuFactory {
displayController: DisplayController,
taskInfo: RunningTaskInfo,
decorWindowContext: Context,
- menuPosition: PointF,
+ positionSupplier: (Int, Int) -> PointF,
transactionSupplier: Supplier<Transaction>
): MaximizeMenu
}
@@ -1049,7 +1060,7 @@ object DefaultMaximizeMenuFactory : MaximizeMenuFactory {
displayController: DisplayController,
taskInfo: RunningTaskInfo,
decorWindowContext: Context,
- menuPosition: PointF,
+ positionSupplier: (Int, Int) -> PointF,
transactionSupplier: Supplier<Transaction>
): MaximizeMenu {
return MaximizeMenu(
@@ -1058,7 +1069,7 @@ object DefaultMaximizeMenuFactory : MaximizeMenuFactory {
displayController,
taskInfo,
decorWindowContext,
- menuPosition,
+ positionSupplier,
transactionSupplier
)
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskOperations.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskOperations.java
index bc85d2b40748..45ba4413814c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskOperations.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskOperations.java
@@ -86,14 +86,18 @@ class TaskOperations {
return null;
}
- IBinder minimizeTask(WindowContainerToken taskToken) {
- return minimizeTask(taskToken, new WindowContainerTransaction());
+ IBinder minimizeTask(WindowContainerToken taskToken, int taskId, boolean isLastTask) {
+ return minimizeTask(taskToken, taskId, isLastTask, new WindowContainerTransaction());
}
- IBinder minimizeTask(WindowContainerToken taskToken, WindowContainerTransaction wct) {
+ IBinder minimizeTask(
+ WindowContainerToken taskToken,
+ int taskId,
+ boolean isLastTask,
+ WindowContainerTransaction wct) {
wct.reorder(taskToken, false);
if (Transitions.ENABLE_SHELL_TRANSITIONS) {
- return mTransitionStarter.startMinimizedModeTransition(wct);
+ return mTransitionStarter.startMinimizedModeTransition(wct, taskId, isLastTask);
} else {
mSyncQueue.queue(wct);
return null;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
index 25dadfde274d..4002dc572897 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
@@ -361,6 +361,8 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
}
outResult.mRootView = rootView;
+ final boolean fontScaleChanged = mWindowDecorConfig != null
+ && mWindowDecorConfig.fontScale != mTaskInfo.configuration.fontScale;
final int oldDensityDpi = mWindowDecorConfig != null
? mWindowDecorConfig.densityDpi : DENSITY_DPI_UNDEFINED;
final int oldNightMode = mWindowDecorConfig != null
@@ -375,7 +377,8 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
|| mDisplay.getDisplayId() != mTaskInfo.displayId
|| oldLayoutResId != mLayoutResId
|| oldNightMode != newNightMode
- || mDecorWindowContext == null) {
+ || mDecorWindowContext == null
+ || fontScaleChanged) {
releaseViews(wct);
if (!obtainDisplayOrRegisterListener()) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/ReusableWindowDecorViewHost.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/ReusableWindowDecorViewHost.kt
index 4a09614029dc..a5592f81a39e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/ReusableWindowDecorViewHost.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/ReusableWindowDecorViewHost.kt
@@ -50,9 +50,8 @@ class ReusableWindowDecorViewHost(
@VisibleForTesting
val viewHostAdapter: SurfaceControlViewHostAdapter =
SurfaceControlViewHostAdapter(context, display),
+ private val rootView: FrameLayout = FrameLayout(context)
) : WindowDecorViewHost, Warmable {
- @VisibleForTesting val rootView = FrameLayout(context)
-
private var currentUpdateJob: Job? = null
override val surfaceControl: SurfaceControl
@@ -131,8 +130,10 @@ class ReusableWindowDecorViewHost(
Trace.beginSection("ReusableWindowDecorViewHost#updateViewHost")
viewHostAdapter.prepareViewHost(configuration, touchableRegion)
onDrawTransaction?.let { viewHostAdapter.applyTransactionOnDraw(it) }
- rootView.removeAllViews()
- rootView.addView(view)
+ if (view.parent != rootView) {
+ rootView.removeAllViews()
+ rootView.addView(view)
+ }
viewHostAdapter.updateView(rootView, attrs)
Trace.endSection()
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/SnapEventHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/SnapEventHandler.kt
new file mode 100644
index 000000000000..52e24d6fe0d0
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/SnapEventHandler.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.windowdecor.tiling
+
+import android.app.ActivityManager.RunningTaskInfo
+import android.graphics.Rect
+import com.android.wm.shell.desktopmode.DesktopTasksController.SnapPosition
+
+/** Interface for handling snap to half screen events. */
+interface SnapEventHandler {
+ /** Snaps an app to half the screen for tiling. */
+ fun snapToHalfScreen(
+ taskInfo: RunningTaskInfo,
+ currentDragBounds: Rect,
+ position: SnapPosition,
+ ): Boolean
+
+ /** Removes a task from tiling if it's tiled, for example on task exiting. */
+ fun removeTaskIfTiled(displayId: Int, taskId: Int)
+
+ /** Notifies the tiling handler of user switch. */
+ fun onUserChange()
+
+ /** Notifies the tiling handler of overview animation state change. */
+ fun onOverviewAnimationStateChange(running: Boolean)
+
+ /** If a task is tiled, delegate moving to front to tiling infrastructure. */
+ fun moveTaskToFrontIfTiled(taskInfo: RunningTaskInfo): Boolean
+}
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/EnterDesktopWithDrag.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/EnterDesktopWithDrag.kt
index 2115f70faad0..af2840e9c34a 100644
--- a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/EnterDesktopWithDrag.kt
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/EnterDesktopWithDrag.kt
@@ -45,6 +45,7 @@ constructor(
tapl.setExpectedRotation(rotation.value)
ChangeDisplayOrientationRule.setRotation(rotation)
tapl.enableTransientTaskbar(false)
+ testApp.exit(wmHelper)
}
@Test
diff --git a/libs/WindowManager/Shell/tests/unittest/Android.bp b/libs/WindowManager/Shell/tests/unittest/Android.bp
index bf5e374c7607..bff12d026b93 100644
--- a/libs/WindowManager/Shell/tests/unittest/Android.bp
+++ b/libs/WindowManager/Shell/tests/unittest/Android.bp
@@ -45,6 +45,7 @@ android_test {
"androidx.test.rules",
"androidx.test.ext.junit",
"androidx.datastore_datastore",
+ "androidx.core_core-animation-testing",
"kotlinx_coroutines_test",
"androidx.dynamicanimation_dynamicanimation",
"dagger2",
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleTransitionsTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleTransitionsTest.java
index 87ee4f58bfdd..42310caba1c6 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleTransitionsTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleTransitionsTest.java
@@ -215,7 +215,7 @@ public class BubbleTransitionsTest extends ShellTestCase {
pendingWct.reorder(pendingDragOpToken, /* onTop= */ false);
BubbleTransitions.DragData dragData = new BubbleTransitions.DragData(
- draggedTaskBounds, pendingWct
+ draggedTaskBounds, pendingWct, /* releasedOnLeft= */ false
);
final BubbleTransitions.BubbleTransition bt = mBubbleTransitions.startConvertToBubble(
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayControllerTests.java
index d3de0f7c09b4..3d5e9495e29d 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayControllerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayControllerTests.java
@@ -16,26 +16,52 @@
package com.android.wm.shell.common;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
-import android.content.Context;
+import android.content.res.Configuration;
+import android.graphics.RectF;
import android.hardware.display.DisplayManager;
+import android.hardware.display.DisplayTopology;
+import android.os.RemoteException;
+import android.platform.test.annotations.EnableFlags;
+import android.testing.TestableContext;
+import android.util.SparseArray;
+import android.view.Display;
+import android.view.DisplayAdjustments;
+import android.view.IDisplayWindowListener;
import android.view.IWindowManager;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
+import com.android.dx.mockito.inline.extended.ExtendedMockito;
+import com.android.dx.mockito.inline.extended.StaticMockitoSession;
+import com.android.window.flags.Flags;
import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.TestSyncExecutor;
+import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
import com.android.wm.shell.sysui.ShellInit;
+import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
+import org.mockito.quality.Strictness;
+
+import java.util.function.Consumer;
/**
* Tests for the display controller.
@@ -46,23 +72,163 @@ import org.mockito.MockitoAnnotations;
@SmallTest
@RunWith(AndroidJUnit4.class)
public class DisplayControllerTests extends ShellTestCase {
-
- private @Mock Context mContext;
- private @Mock IWindowManager mWM;
- private @Mock ShellInit mShellInit;
- private @Mock ShellExecutor mMainExecutor;
- private @Mock DisplayManager mDisplayManager;
+ @Mock private IWindowManager mWM;
+ @Mock private ShellInit mShellInit;
+ @Mock private DisplayManager mDisplayManager;
+ @Mock private DisplayTopology mMockTopology;
+ @Mock private DisplayController.OnDisplaysChangedListener mListener;
+ private StaticMockitoSession mMockitoSession;
+ private TestSyncExecutor mMainExecutor;
+ private IDisplayWindowListener mDisplayContainerListener;
+ private Consumer<DisplayTopology> mCapturedTopologyListener;
+ private Display mMockDisplay;
private DisplayController mController;
+ private static final int DISPLAY_ID_0 = 0;
+ private static final int DISPLAY_ID_1 = 1;
+ private static final RectF DISPLAY_ABS_BOUNDS_0 = new RectF(10, 10, 20, 20);
+ private static final RectF DISPLAY_ABS_BOUNDS_1 = new RectF(11, 11, 22, 22);
@Before
- public void setUp() {
- MockitoAnnotations.initMocks(this);
+ public void setUp() throws RemoteException {
+ mMockitoSession =
+ ExtendedMockito.mockitoSession()
+ .initMocks(this)
+ .mockStatic(DesktopModeStatus.class)
+ .strictness(Strictness.LENIENT)
+ .startMocking();
+
+ mContext = spy(new TestableContext(
+ androidx.test.platform.app.InstrumentationRegistry.getInstrumentation()
+ .getContext(), null));
+
+ mMainExecutor = new TestSyncExecutor();
mController = new DisplayController(
mContext, mWM, mShellInit, mMainExecutor, mDisplayManager);
+
+ mMockDisplay = mock(Display.class);
+ when(mMockDisplay.getDisplayAdjustments()).thenReturn(
+ new DisplayAdjustments(new Configuration()));
+ when(mDisplayManager.getDisplay(anyInt())).thenReturn(mMockDisplay);
+ when(mDisplayManager.getDisplayTopology()).thenReturn(mMockTopology);
+ doAnswer(invocation -> {
+ mDisplayContainerListener = invocation.getArgument(0);
+ return new int[]{DISPLAY_ID_0};
+ }).when(mWM).registerDisplayWindowListener(any());
+ doAnswer(invocation -> {
+ mCapturedTopologyListener = invocation.getArgument(1);
+ return null;
+ }).when(mDisplayManager).registerTopologyListener(any(), any());
+ SparseArray<RectF> absoluteBounds = new SparseArray<>();
+ absoluteBounds.put(DISPLAY_ID_0, DISPLAY_ABS_BOUNDS_0);
+ absoluteBounds.put(DISPLAY_ID_1, DISPLAY_ABS_BOUNDS_1);
+ when(mMockTopology.getAbsoluteBounds()).thenReturn(absoluteBounds);
+ }
+
+ @After
+ public void tearDown() {
+ if (mMockitoSession != null) {
+ mMockitoSession.finishMocking();
+ }
}
@Test
public void instantiateController_addInitCallback() {
verify(mShellInit, times(1)).addInitCallback(any(), eq(mController));
}
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_CONNECTED_DISPLAYS_WINDOW_DRAG)
+ public void onInit_canEnterDesktopMode_registerListeners() throws RemoteException {
+ ExtendedMockito.doReturn(true)
+ .when(() -> DesktopModeStatus.canEnterDesktopMode(any()));
+
+ mController.onInit();
+
+ assertNotNull(mController.getDisplayContext(DISPLAY_ID_0));
+ verify(mWM).registerDisplayWindowListener(any());
+ verify(mDisplayManager).registerTopologyListener(eq(mMainExecutor), any());
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_CONNECTED_DISPLAYS_WINDOW_DRAG)
+ public void onInit_canNotEnterDesktopMode_onlyRegisterDisplayWindowListener()
+ throws RemoteException {
+ ExtendedMockito.doReturn(false)
+ .when(() -> DesktopModeStatus.canEnterDesktopMode(any()));
+
+ mController.onInit();
+
+ assertNotNull(mController.getDisplayContext(DISPLAY_ID_0));
+ verify(mWM).registerDisplayWindowListener(any());
+ verify(mDisplayManager, never()).registerTopologyListener(any(), any());
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_CONNECTED_DISPLAYS_WINDOW_DRAG)
+ public void addDisplayWindowListener_notifiesExistingDisplaysAndTopology() {
+ ExtendedMockito.doReturn(true)
+ .when(() -> DesktopModeStatus.canEnterDesktopMode(any()));
+
+ mController.onInit();
+ mController.addDisplayWindowListener(mListener);
+
+ verify(mListener).onDisplayAdded(eq(DISPLAY_ID_0));
+ verify(mListener).onTopologyChanged(eq(mMockTopology));
+ }
+
+ @Test
+ public void onDisplayAddedAndRemoved_updatesDisplayContexts() throws RemoteException {
+ mController.onInit();
+ mController.addDisplayWindowListener(mListener);
+
+ mDisplayContainerListener.onDisplayAdded(DISPLAY_ID_1);
+
+ verify(mListener).onDisplayAdded(eq(DISPLAY_ID_0));
+ verify(mListener).onDisplayAdded(eq(DISPLAY_ID_1));
+ assertNotNull(mController.getDisplayContext(DISPLAY_ID_1));
+ verify(mContext).createDisplayContext(eq(mMockDisplay));
+
+ mDisplayContainerListener.onDisplayRemoved(DISPLAY_ID_1);
+
+ assertNull(mController.getDisplayContext(DISPLAY_ID_1));
+ verify(mListener).onDisplayRemoved(eq(DISPLAY_ID_1));
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_CONNECTED_DISPLAYS_WINDOW_DRAG)
+ public void onDisplayTopologyChanged_updateDisplayLayout() throws RemoteException {
+ ExtendedMockito.doReturn(true)
+ .when(() -> DesktopModeStatus.canEnterDesktopMode(any()));
+ mController.onInit();
+ mController.addDisplayWindowListener(mListener);
+ mDisplayContainerListener.onDisplayAdded(DISPLAY_ID_1);
+
+ mCapturedTopologyListener.accept(mMockTopology);
+
+ assertEquals(DISPLAY_ABS_BOUNDS_0, mController.getDisplayLayout(DISPLAY_ID_0)
+ .globalBoundsDp());
+ assertEquals(DISPLAY_ABS_BOUNDS_1, mController.getDisplayLayout(DISPLAY_ID_1)
+ .globalBoundsDp());
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_CONNECTED_DISPLAYS_WINDOW_DRAG)
+ public void onDisplayTopologyChanged_topologyBeforeDisplayAdded_appliesBoundsOnAdd()
+ throws RemoteException {
+ ExtendedMockito.doReturn(true)
+ .when(() -> DesktopModeStatus.canEnterDesktopMode(any()));
+ mController.onInit();
+ mController.addDisplayWindowListener(mListener);
+
+ mCapturedTopologyListener.accept(mMockTopology);
+
+ assertNull(mController.getDisplayLayout(DISPLAY_ID_1));
+
+ mDisplayContainerListener.onDisplayAdded(DISPLAY_ID_1);
+
+ assertEquals(DISPLAY_ABS_BOUNDS_0,
+ mController.getDisplayLayout(DISPLAY_ID_0).globalBoundsDp());
+ assertEquals(DISPLAY_ABS_BOUNDS_1,
+ mController.getDisplayLayout(DISPLAY_ID_1).globalBoundsDp());
+ }
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/WindowContainerTransactionSupplierTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/WindowContainerTransactionSupplierTest.kt
new file mode 100644
index 000000000000..c91ef5e6b868
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/WindowContainerTransactionSupplierTest.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.common
+
+import android.testing.AndroidTestingRunner
+import android.window.WindowContainerTransaction
+import androidx.test.filters.SmallTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Tests for [WindowContainerTransactionSupplier].
+ *
+ * Build/Install/Run:
+ * atest WMShellUnitTests:WindowContainerTransactionSupplierTest
+ */
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class WindowContainerTransactionSupplierTest {
+
+ @Test
+ fun `WindowContainerTransactionSupplier supplies a WindowContainerTransaction`() {
+ val supplier = WindowContainerTransactionSupplier()
+ SuppliersUtilsTest.assertSupplierProvidesValue(supplier) {
+ it is WindowContainerTransaction
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PhonePipKeepClearAlgorithmTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/pip/PhonePipKeepClearAlgorithmTest.java
index e3798e92c092..a6c35f1bd93c 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PhonePipKeepClearAlgorithmTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/pip/PhonePipKeepClearAlgorithmTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 The Android Open Source Project
+ * Copyright (C) 2025 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.wm.shell.pip.phone;
+package com.android.wm.shell.common.pip;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -29,9 +29,6 @@ import android.testing.TestableLooper;
import androidx.test.filters.SmallTest;
import com.android.wm.shell.ShellTestCase;
-import com.android.wm.shell.common.pip.PhonePipKeepClearAlgorithm;
-import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
-import com.android.wm.shell.common.pip.PipBoundsState;
import org.junit.Before;
import org.junit.Test;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PhoneSizeSpecSourceTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/pip/PhoneSizeSpecSourceTest.java
index 85f1da5322ea..737735c9efcd 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PhoneSizeSpecSourceTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/pip/PhoneSizeSpecSourceTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2025 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.wm.shell.pip.phone;
+package com.android.wm.shell.common.pip;
import static org.mockito.Mockito.when;
@@ -27,9 +27,6 @@ import android.view.DisplayInfo;
import com.android.wm.shell.R;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.common.DisplayLayout;
-import com.android.wm.shell.common.pip.PhoneSizeSpecSource;
-import com.android.wm.shell.common.pip.PipDisplayLayoutState;
-import com.android.wm.shell.common.pip.SizeSpecSource;
import org.junit.Assert;
import org.junit.Before;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsAlgorithmTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/pip/PipBoundsAlgorithmTest.java
index 080b0ae006ea..6bda2259b44c 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsAlgorithmTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/pip/PipBoundsAlgorithmTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2020 The Android Open Source Project
+ * Copyright (C) 2025 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.wm.shell.pip;
+package com.android.wm.shell.common.pip;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
@@ -32,13 +32,6 @@ import androidx.test.filters.SmallTest;
import com.android.wm.shell.R;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.common.DisplayLayout;
-import com.android.wm.shell.common.pip.PhoneSizeSpecSource;
-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.PipKeepClearAlgorithmInterface;
-import com.android.wm.shell.common.pip.PipSnapAlgorithm;
-import com.android.wm.shell.common.pip.SizeSpecSource;
import org.junit.Before;
import org.junit.Test;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsStateTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/pip/PipBoundsStateTest.java
index 304da75f870c..ad664acfdc37 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsStateTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/pip/PipBoundsStateTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2020 The Android Open Source Project
+ * Copyright (C) 2025 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.wm.shell.pip;
+package com.android.wm.shell.common.pip;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
@@ -36,10 +36,6 @@ import androidx.test.filters.SmallTest;
import com.android.internal.util.function.TriConsumer;
import com.android.wm.shell.R;
import com.android.wm.shell.ShellTestCase;
-import com.android.wm.shell.common.pip.PhoneSizeSpecSource;
-import com.android.wm.shell.common.pip.PipBoundsState;
-import com.android.wm.shell.common.pip.PipDisplayLayoutState;
-import com.android.wm.shell.common.pip.SizeSpecSource;
import org.junit.Before;
import org.junit.Test;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipDoubleTapHelperTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/pip/PipDoubleTapHelperTest.java
index b583acda1c9a..1756aad8fc9b 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipDoubleTapHelperTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/pip/PipDoubleTapHelperTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 The Android Open Source Project
+ * Copyright (C) 2025 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.wm.shell.pip.phone;
+package com.android.wm.shell.common.pip;
import static com.android.wm.shell.common.pip.PipDoubleTapHelper.SIZE_SPEC_CUSTOM;
import static com.android.wm.shell.common.pip.PipDoubleTapHelper.SIZE_SPEC_DEFAULT;
@@ -29,8 +29,6 @@ import android.graphics.Rect;
import android.testing.AndroidTestingRunner;
import com.android.wm.shell.ShellTestCase;
-import com.android.wm.shell.common.pip.PipBoundsState;
-import com.android.wm.shell.common.pip.PipDoubleTapHelper;
import org.junit.Assert;
import org.junit.Before;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipSnapAlgorithmTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/pip/PipSnapAlgorithmTest.java
index ac13d7ffcd61..3e71ab3e1ad4 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipSnapAlgorithmTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/pip/PipSnapAlgorithmTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2020 The Android Open Source Project
+ * Copyright (C) 2025 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.wm.shell.pip;
+package com.android.wm.shell.common.pip;
import static org.junit.Assert.assertEquals;
@@ -25,8 +25,6 @@ import android.testing.TestableLooper;
import androidx.test.filters.SmallTest;
import com.android.wm.shell.ShellTestCase;
-import com.android.wm.shell.common.pip.PipBoundsState;
-import com.android.wm.shell.common.pip.PipSnapAlgorithm;
import org.junit.Before;
import org.junit.Test;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/FlexParallaxSpecTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/FlexParallaxSpecTests.java
new file mode 100644
index 000000000000..22a85fc49a4b
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/FlexParallaxSpecTests.java
@@ -0,0 +1,401 @@
+/*
+ * 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.split;
+
+import static android.view.WindowManager.DOCKED_INVALID;
+import static android.view.WindowManager.DOCKED_LEFT;
+import static android.view.WindowManager.DOCKED_RIGHT;
+
+import static com.android.wm.shell.common.split.ResizingEffectPolicy.DEFAULT_OFFSCREEN_DIM;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.when;
+
+import android.graphics.Point;
+import android.graphics.Rect;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.wm.shell.common.split.DividerSnapAlgorithm.SnapTarget;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(AndroidJUnit4.class)
+public class FlexParallaxSpecTests {
+ ParallaxSpec mFlexSpec = new FlexParallaxSpec();
+
+ Rect mDisplayBounds = new Rect(0, 0, 1000, 1000);
+ Rect mRetreatingSurface = new Rect(0, 0, 1000, 1000);
+ Rect mRetreatingContent = new Rect(0, 0, 1000, 1000);
+ Rect mAdvancingSurface = new Rect(0, 0, 1000, 1000);
+ Rect mAdvancingContent = new Rect(0, 0, 1000, 1000);
+ boolean mIsLeftRightSplit;
+ boolean mTopLeftShrink;
+
+ int mDimmingSide;
+ float mDimValue;
+ Point mRetreatingParallax = new Point(0, 0);
+ Point mAdvancingParallax = new Point(0, 0);
+
+ @Mock DividerSnapAlgorithm mockSnapAlgorithm;
+ @Mock SnapTarget mockStartEdge;
+ @Mock SnapTarget mockFirstTarget;
+ @Mock SnapTarget mockMiddleTarget;
+ @Mock SnapTarget mockLastTarget;
+ @Mock SnapTarget mockEndEdge;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ when(mockSnapAlgorithm.getDismissStartTarget()).thenReturn(mockStartEdge);
+ when(mockSnapAlgorithm.getFirstSplitTarget()).thenReturn(mockFirstTarget);
+ when(mockSnapAlgorithm.getMiddleTarget()).thenReturn(mockMiddleTarget);
+ when(mockSnapAlgorithm.getLastSplitTarget()).thenReturn(mockLastTarget);
+ when(mockSnapAlgorithm.getDismissEndTarget()).thenReturn(mockEndEdge);
+
+ when(mockStartEdge.getPosition()).thenReturn(0);
+ when(mockFirstTarget.getPosition()).thenReturn(250);
+ when(mockMiddleTarget.getPosition()).thenReturn(500);
+ when(mockLastTarget.getPosition()).thenReturn(750);
+ when(mockEndEdge.getPosition()).thenReturn(1000);
+ }
+
+ @Test
+ public void testHorizontalDragFromCenter() {
+ mIsLeftRightSplit = true;
+ simulateDragFromCenterToLeft(125);
+ assertThat(mDimmingSide).isEqualTo(DOCKED_LEFT);
+ assertThat(mDimValue).isGreaterThan(DEFAULT_OFFSCREEN_DIM);
+ assertThat(mDimValue).isLessThan(1f);
+ assertThat(mRetreatingParallax.x).isGreaterThan(0);
+ assertThat(mRetreatingParallax.y).isEqualTo(0);
+ assertThat(mAdvancingParallax.x).isEqualTo(0);
+ assertThat(mAdvancingParallax.y).isEqualTo(0);
+
+ simulateDragFromCenterToLeft(250);
+ assertThat(mDimmingSide).isEqualTo(DOCKED_LEFT);
+ assertThat(mDimValue).isEqualTo(DEFAULT_OFFSCREEN_DIM);
+ assertThat(mRetreatingParallax.x).isGreaterThan(0);
+ assertThat(mRetreatingParallax.y).isEqualTo(0);
+ assertThat(mAdvancingParallax.x).isEqualTo(0);
+ assertThat(mAdvancingParallax.y).isEqualTo(0);
+
+ simulateDragFromCenterToLeft(375);
+ assertThat(mDimmingSide).isEqualTo(DOCKED_LEFT);
+ assertThat(mDimValue).isGreaterThan(0f);
+ assertThat(mDimValue).isLessThan(DEFAULT_OFFSCREEN_DIM);
+ assertThat(mRetreatingParallax.x).isGreaterThan(0);
+ assertThat(mRetreatingParallax.y).isEqualTo(0);
+ assertThat(mAdvancingParallax.x).isEqualTo(0);
+ assertThat(mAdvancingParallax.y).isEqualTo(0);
+
+ simulateDragFromCenterToRight(500);
+ assertThat(mDimmingSide).isEqualTo(DOCKED_INVALID);
+ assertThat(mDimValue).isEqualTo(0f);
+ assertThat(mRetreatingParallax.x).isEqualTo(0);
+ assertThat(mRetreatingParallax.y).isEqualTo(0);
+ assertThat(mAdvancingParallax.x).isEqualTo(0);
+ assertThat(mAdvancingParallax.y).isEqualTo(0);
+
+ simulateDragFromCenterToRight(625);
+ assertThat(mDimmingSide).isEqualTo(DOCKED_RIGHT);
+ assertThat(mDimValue).isGreaterThan(0f);
+ assertThat(mDimValue).isLessThan(DEFAULT_OFFSCREEN_DIM);
+ assertThat(mRetreatingParallax.x).isEqualTo(0);
+ assertThat(mRetreatingParallax.y).isEqualTo(0);
+ assertThat(mAdvancingParallax.x).isEqualTo(0);
+ assertThat(mAdvancingParallax.y).isEqualTo(0);
+
+ simulateDragFromCenterToRight(750);
+ assertThat(mDimmingSide).isEqualTo(DOCKED_RIGHT);
+ assertThat(mDimValue).isEqualTo(DEFAULT_OFFSCREEN_DIM);
+ assertThat(mRetreatingParallax.x).isEqualTo(0);
+ assertThat(mRetreatingParallax.y).isEqualTo(0);
+ assertThat(mAdvancingParallax.x).isEqualTo(0);
+ assertThat(mAdvancingParallax.y).isEqualTo(0);
+
+ simulateDragFromCenterToRight(875);
+ assertThat(mDimmingSide).isEqualTo(DOCKED_RIGHT);
+ assertThat(mDimValue).isGreaterThan(DEFAULT_OFFSCREEN_DIM);
+ assertThat(mDimValue).isLessThan(1f);
+ assertThat(mRetreatingParallax.x).isEqualTo(0);
+ assertThat(mRetreatingParallax.y).isEqualTo(0);
+ assertThat(mAdvancingParallax.x).isEqualTo(0);
+ assertThat(mAdvancingParallax.y).isEqualTo(0);
+ }
+
+ @Test
+ public void testHorizontalDragFromLeft() {
+ mIsLeftRightSplit = true;
+ simulateDragFromLeftToLeft(125);
+ assertThat(mDimmingSide).isEqualTo(DOCKED_LEFT);
+ assertThat(mDimValue).isGreaterThan(DEFAULT_OFFSCREEN_DIM);
+ assertThat(mDimValue).isLessThan(1f);
+ assertThat(mRetreatingParallax.x).isGreaterThan(0);
+ assertThat(mRetreatingParallax.y).isEqualTo(0);
+ assertThat(mAdvancingParallax.x).isEqualTo(0);
+ assertThat(mAdvancingParallax.y).isEqualTo(0);
+
+ simulateDragFromLeftToLeft(250);
+ assertThat(mDimmingSide).isEqualTo(DOCKED_LEFT);
+ assertThat(mDimValue).isEqualTo(DEFAULT_OFFSCREEN_DIM);
+ assertThat(mRetreatingParallax.x).isEqualTo(0);
+ assertThat(mRetreatingParallax.y).isEqualTo(0);
+ assertThat(mAdvancingParallax.x).isEqualTo(0);
+ assertThat(mAdvancingParallax.y).isEqualTo(0);
+
+ simulateDragFromLeftToCenter(375);
+ assertThat(mDimmingSide).isEqualTo(DOCKED_LEFT);
+ assertThat(mDimValue).isGreaterThan(0f);
+ assertThat(mDimValue).isLessThan(DEFAULT_OFFSCREEN_DIM);
+ assertThat(mRetreatingParallax.x).isLessThan(0);
+ assertThat(mRetreatingParallax.y).isEqualTo(0);
+ assertThat(mAdvancingParallax.x).isEqualTo(0);
+ assertThat(mAdvancingParallax.y).isEqualTo(0);
+
+ simulateDragFromLeftToCenter(500);
+ assertThat(mDimmingSide).isEqualTo(DOCKED_INVALID);
+ assertThat(mDimValue).isEqualTo(0f);
+ assertThat(mRetreatingParallax.x).isLessThan(0);
+ assertThat(mRetreatingParallax.y).isEqualTo(0);
+ assertThat(mAdvancingParallax.x).isEqualTo(0);
+ assertThat(mAdvancingParallax.y).isEqualTo(0);
+
+ simulateDragFromLeftToRight(625);
+ assertThat(mDimmingSide).isEqualTo(DOCKED_RIGHT);
+ assertThat(mDimValue).isGreaterThan(0f);
+ assertThat(mDimValue).isLessThan(DEFAULT_OFFSCREEN_DIM);
+ assertThat(mRetreatingParallax.x).isLessThan(0);
+ assertThat(mRetreatingParallax.y).isEqualTo(0);
+ assertThat(mAdvancingParallax.x).isEqualTo(0);
+ assertThat(mAdvancingParallax.y).isEqualTo(0);
+
+ simulateDragFromLeftToRight(750);
+ assertThat(mDimmingSide).isEqualTo(DOCKED_RIGHT);
+ assertThat(mDimValue).isEqualTo(DEFAULT_OFFSCREEN_DIM);
+ assertThat(mRetreatingParallax.x).isLessThan(0);
+ assertThat(mRetreatingParallax.y).isEqualTo(0);
+ assertThat(mAdvancingParallax.x).isEqualTo(0);
+ assertThat(mAdvancingParallax.y).isEqualTo(0);
+
+ simulateDragFromLeftToRight(875);
+ assertThat(mDimmingSide).isEqualTo(DOCKED_RIGHT);
+ assertThat(mDimValue).isGreaterThan(DEFAULT_OFFSCREEN_DIM);
+ assertThat(mDimValue).isLessThan(1f);
+ assertThat(mRetreatingParallax.x).isLessThan(0);
+ assertThat(mRetreatingParallax.y).isEqualTo(0);
+ assertThat(mAdvancingParallax.x).isGreaterThan(0);
+ assertThat(mAdvancingParallax.y).isEqualTo(0);
+ }
+
+ @Test
+ public void testHorizontalDragFromRight() {
+ mIsLeftRightSplit = true;
+
+ simulateDragFromRightToLeft(125);
+ assertThat(mDimmingSide).isEqualTo(DOCKED_LEFT);
+ assertThat(mDimValue).isGreaterThan(DEFAULT_OFFSCREEN_DIM);
+ assertThat(mDimValue).isLessThan(1f);
+ assertThat(mRetreatingParallax.x).isGreaterThan(0);
+ assertThat(mRetreatingParallax.y).isEqualTo(0);
+ assertThat(mAdvancingParallax.x).isEqualTo(0);
+ assertThat(mAdvancingParallax.y).isEqualTo(0);
+
+ simulateDragFromRightToLeft(250);
+ assertThat(mDimmingSide).isEqualTo(DOCKED_LEFT);
+ assertThat(mDimValue).isEqualTo(DEFAULT_OFFSCREEN_DIM);
+ assertThat(mRetreatingParallax.x).isGreaterThan(0);
+ assertThat(mRetreatingParallax.y).isEqualTo(0);
+ assertThat(mAdvancingParallax.x).isEqualTo(0);
+ assertThat(mAdvancingParallax.y).isEqualTo(0);
+
+ simulateDragFromRightToLeft(375);
+ assertThat(mDimmingSide).isEqualTo(DOCKED_LEFT);
+ assertThat(mDimValue).isGreaterThan(0f);
+ assertThat(mDimValue).isLessThan(DEFAULT_OFFSCREEN_DIM);
+ assertThat(mRetreatingParallax.x).isGreaterThan(0);
+ assertThat(mRetreatingParallax.y).isEqualTo(0);
+ assertThat(mAdvancingParallax.x).isEqualTo(0);
+ assertThat(mAdvancingParallax.y).isEqualTo(0);
+
+ simulateDragFromRightToCenter(500);
+ assertThat(mDimmingSide).isEqualTo(DOCKED_INVALID);
+ assertThat(mDimValue).isEqualTo(0f);
+ assertThat(mRetreatingParallax.x).isLessThan(0);
+ assertThat(mRetreatingParallax.y).isEqualTo(0);
+ assertThat(mAdvancingParallax.x).isEqualTo(0);
+ assertThat(mAdvancingParallax.y).isEqualTo(0);
+
+ simulateDragFromRightToCenter(625);
+ assertThat(mDimmingSide).isEqualTo(DOCKED_RIGHT);
+ assertThat(mDimValue).isGreaterThan(0f);
+ assertThat(mDimValue).isLessThan(DEFAULT_OFFSCREEN_DIM);
+ assertThat(mRetreatingParallax.x).isLessThan(0);
+ assertThat(mRetreatingParallax.y).isEqualTo(0);
+ assertThat(mAdvancingParallax.x).isEqualTo(0);
+ assertThat(mAdvancingParallax.y).isEqualTo(0);
+
+ simulateDragFromRightToRight(750);
+ assertThat(mDimmingSide).isEqualTo(DOCKED_RIGHT);
+ assertThat(mDimValue).isEqualTo(DEFAULT_OFFSCREEN_DIM);
+ assertThat(mRetreatingParallax.x).isEqualTo(0);
+ assertThat(mRetreatingParallax.y).isEqualTo(0);
+ assertThat(mAdvancingParallax.x).isEqualTo(0);
+ assertThat(mAdvancingParallax.y).isEqualTo(0);
+
+ simulateDragFromRightToRight(875);
+ assertThat(mDimmingSide).isEqualTo(DOCKED_RIGHT);
+ assertThat(mDimValue).isGreaterThan(DEFAULT_OFFSCREEN_DIM);
+ assertThat(mDimValue).isLessThan(1f);
+ assertThat(mRetreatingParallax.x).isEqualTo(0);
+ assertThat(mRetreatingParallax.y).isEqualTo(0);
+ assertThat(mAdvancingParallax.x).isEqualTo(0);
+ assertThat(mAdvancingParallax.y).isEqualTo(0);
+ }
+
+ private void simulateDragFromCenterToLeft(int to) {
+ int from = 500;
+
+ mRetreatingSurface = flexOffscreenAppLeft(to);
+ mRetreatingContent = onscreenAppLeft(from);
+ mAdvancingSurface = onscreenAppRight(to);
+ mAdvancingContent = onscreenAppRight(from);
+
+ calculateDimAndParallax(from, to);
+ }
+
+ private void simulateDragFromCenterToRight(int to) {
+ int from = 500;
+
+ mRetreatingSurface = flexOffscreenAppRight(to);
+ mRetreatingContent = onscreenAppRight(from);
+ mAdvancingSurface = onscreenAppLeft(to);
+ mAdvancingContent = onscreenAppLeft(from);
+
+ calculateDimAndParallax(from, to);
+ }
+
+ private void simulateDragFromLeftToLeft(int to) {
+ int from = 250;
+
+ mRetreatingSurface = flexOffscreenAppLeft(to);
+ mRetreatingContent = fullOffscreenAppLeft(from);
+ mAdvancingSurface = onscreenAppRight(to);
+ mAdvancingContent = onscreenAppRight(from);
+
+ calculateDimAndParallax(from, to);
+ }
+
+ private void simulateDragFromLeftToCenter(int to) {
+ int from = 250;
+
+ mRetreatingSurface = onscreenAppRight(to);
+ mRetreatingContent = onscreenAppRight(from);
+ mAdvancingSurface = fullOffscreenAppLeft(to);
+ mAdvancingContent = fullOffscreenAppLeft(from);
+
+ calculateDimAndParallax(from, to);
+ }
+
+ private void simulateDragFromLeftToRight(int to) {
+ int from = 250;
+
+ mRetreatingSurface = flexOffscreenAppRight(to);
+ mRetreatingContent = onscreenAppRight(from);
+ mAdvancingSurface = fullOffscreenAppLeft(to);
+ mAdvancingContent = fullOffscreenAppLeft(from);
+
+ calculateDimAndParallax(from, to);
+ }
+
+ private void simulateDragFromRightToLeft(int to) {
+ int from = 750;
+
+ mRetreatingSurface = flexOffscreenAppLeft(to);
+ mRetreatingContent = onscreenAppLeft(from);
+ mAdvancingSurface = fullOffscreenAppRight(to);
+ mAdvancingContent = fullOffscreenAppRight(from);
+
+ calculateDimAndParallax(from, to);
+ }
+
+ private void simulateDragFromRightToCenter(int to) {
+ int from = 750;
+
+ mRetreatingSurface = onscreenAppLeft(to);
+ mRetreatingContent = onscreenAppLeft(from);
+ mAdvancingSurface = fullOffscreenAppRight(to);
+ mAdvancingContent = fullOffscreenAppRight(from);
+
+ calculateDimAndParallax(from, to);
+ }
+
+ private void simulateDragFromRightToRight(int to) {
+ int from = 750;
+
+ mRetreatingSurface = flexOffscreenAppRight(to);
+ mRetreatingContent = fullOffscreenAppRight(from);
+ mAdvancingSurface = onscreenAppLeft(to);
+ mAdvancingContent = onscreenAppLeft(from);
+
+ calculateDimAndParallax(from, to);
+ }
+
+ private Rect flexOffscreenAppLeft(int pos) {
+ return new Rect(pos - (1000 - pos), 0, pos, 1000);
+ }
+
+ private Rect onscreenAppLeft(int pos) {
+ return new Rect(0, 0, pos, 1000);
+ }
+
+ private Rect fullOffscreenAppLeft(int pos) {
+ return new Rect(Math.min(0, pos - 750), 0, pos, 1000);
+ }
+
+ private Rect flexOffscreenAppRight(int pos) {
+ return new Rect(pos, 0, pos * 2, 1000);
+ }
+
+ private Rect onscreenAppRight(int pos) {
+ return new Rect(pos, 0, 1000, 1000);
+ }
+
+ private Rect fullOffscreenAppRight(int pos) {
+ return new Rect(pos, 0, Math.max(pos + 750, 1000), 1000);
+ }
+
+ private void calculateDimAndParallax(int from, int to) {
+ resetParallax();
+ mTopLeftShrink = to < from;
+ mDimmingSide = mFlexSpec.getDimmingSide(to, mockSnapAlgorithm, mIsLeftRightSplit);
+ mDimValue = mFlexSpec.getDimValue(to, mockSnapAlgorithm);
+ mFlexSpec.getParallax(mRetreatingParallax, mAdvancingParallax, to, mockSnapAlgorithm,
+ mIsLeftRightSplit, mDisplayBounds, mRetreatingSurface, mRetreatingContent,
+ mAdvancingSurface, mAdvancingContent, mDimmingSide, mTopLeftShrink);
+ }
+
+ private void resetParallax() {
+ mRetreatingParallax.set(0, 0);
+ mAdvancingParallax.set(0, 0);
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/events/ReachabilityGestureListenerFactoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/events/ReachabilityGestureListenerFactoryTest.kt
new file mode 100644
index 000000000000..a5f6ced20dc0
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/events/ReachabilityGestureListenerFactoryTest.kt
@@ -0,0 +1,131 @@
+/*
+ * Copyright 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.compatui.letterbox.events
+
+import android.graphics.Rect
+import android.testing.AndroidTestingRunner
+import android.window.WindowContainerToken
+import android.window.WindowContainerTransaction
+import androidx.test.filters.SmallTest
+import com.android.wm.shell.ShellTestCase
+import com.android.wm.shell.common.WindowContainerTransactionSupplier
+import com.android.wm.shell.compatui.letterbox.LetterboxEvents.motionEventAt
+import com.android.wm.shell.transition.Transitions
+import com.android.wm.shell.transition.Transitions.TRANSIT_MOVE_LETTERBOX_REACHABILITY
+import java.util.function.Consumer
+import kotlin.test.assertEquals
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.verify
+
+/**
+ * Tests for [ReachabilityGestureListenerFactory].
+ *
+ * Build/Install/Run:
+ * atest WMShellUnitTests:ReachabilityGestureListenerFactoryTest
+ */
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class ReachabilityGestureListenerFactoryTest : ShellTestCase() {
+
+ @Test
+ fun `When invoked a ReachabilityGestureListenerFactory is created`() {
+ runTestScenario { r ->
+ r.invokeCreate()
+
+ r.checkReachabilityGestureListenerCreated()
+ }
+ }
+
+ @Test
+ fun `Right parameters are used for creation`() {
+ runTestScenario { r ->
+ r.invokeCreate()
+
+ r.checkRightParamsAreUsed()
+ }
+ }
+
+ /**
+ * Runs a test scenario providing a Robot.
+ */
+ fun runTestScenario(consumer: Consumer<ReachabilityGestureListenerFactoryRobotTest>) {
+ val robot = ReachabilityGestureListenerFactoryRobotTest()
+ consumer.accept(robot)
+ }
+
+ class ReachabilityGestureListenerFactoryRobotTest {
+
+ companion object {
+ @JvmStatic
+ private val TASK_ID = 1
+
+ @JvmStatic
+ private val TOKEN = mock<WindowContainerToken>()
+ }
+
+ private val transitions: Transitions
+ private val animationHandler: Transitions.TransitionHandler
+ private val factory: ReachabilityGestureListenerFactory
+ private val wctSupplier: WindowContainerTransactionSupplier
+ private val wct: WindowContainerTransaction
+ private lateinit var obtainedResult: Any
+
+ init {
+ transitions = mock<Transitions>()
+ animationHandler = mock<Transitions.TransitionHandler>()
+ wctSupplier = mock<WindowContainerTransactionSupplier>()
+ wct = mock<WindowContainerTransaction>()
+ doReturn(wct).`when`(wctSupplier).get()
+ factory = ReachabilityGestureListenerFactory(transitions, animationHandler, wctSupplier)
+ }
+
+ fun invokeCreate(taskId: Int = TASK_ID, token: WindowContainerToken? = TOKEN) {
+ obtainedResult = factory.createReachabilityGestureListener(taskId, token)
+ }
+
+ fun checkReachabilityGestureListenerCreated(expected: Boolean = true) {
+ assertEquals(expected, obtainedResult is ReachabilityGestureListener)
+ }
+
+ fun checkRightParamsAreUsed(taskId: Int = TASK_ID, token: WindowContainerToken? = TOKEN) {
+ with(obtainedResult as ReachabilityGestureListener) {
+ // Click outside the bounds
+ updateActivityBounds(Rect(0, 0, 10, 20))
+ onDoubleTap(motionEventAt(50f, 100f))
+ // WindowContainerTransactionSupplier is invoked to create a
+ // WindowContainerTransaction
+ verify(wctSupplier).get()
+ // Verify the right params are passed to startAppCompatReachability()
+ verify(wct).setReachabilityOffset(
+ token!!,
+ taskId,
+ 50,
+ 100
+ )
+ // startTransition() is invoked on Transitions with the right parameters
+ verify(transitions).startTransition(
+ TRANSIT_MOVE_LETTERBOX_REACHABILITY,
+ wct,
+ animationHandler
+ )
+ }
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/events/ReachabilityGestureListenerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/events/ReachabilityGestureListenerTest.kt
new file mode 100644
index 000000000000..bc10ea578ffb
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/events/ReachabilityGestureListenerTest.kt
@@ -0,0 +1,146 @@
+/*
+ * Copyright 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.compatui.letterbox.events
+
+import android.graphics.Rect
+import android.testing.AndroidTestingRunner
+import android.window.WindowContainerToken
+import android.window.WindowContainerTransaction
+import androidx.test.filters.SmallTest
+import com.android.wm.shell.ShellTestCase
+import com.android.wm.shell.common.WindowContainerTransactionSupplier
+import com.android.wm.shell.compatui.letterbox.LetterboxEvents.motionEventAt
+import com.android.wm.shell.compatui.letterbox.asMode
+import com.android.wm.shell.transition.Transitions
+import com.android.wm.shell.transition.Transitions.TRANSIT_MOVE_LETTERBOX_REACHABILITY
+import java.util.function.Consumer
+import kotlin.test.assertEquals
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.verify
+
+/**
+ * Tests for [ReachabilityGestureListener].
+ *
+ * Build/Install/Run:
+ * atest WMShellUnitTests:ReachabilityGestureListenerTest
+ */
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class ReachabilityGestureListenerTest : ShellTestCase() {
+
+ @Test
+ fun `Only events outside the bounds are handled`() {
+ runTestScenario { r ->
+ r.updateActivityBounds(Rect(0, 0, 100, 200))
+ r.sendMotionEvent(50, 100)
+
+ r.verifyReachabilityTransitionCreated(expected = false, 50, 100)
+ r.verifyReachabilityTransitionStarted(expected = false)
+ r.verifyEventIsHandled(expected = false)
+
+ r.updateActivityBounds(Rect(0, 0, 10, 50))
+ r.sendMotionEvent(50, 100)
+
+ r.verifyReachabilityTransitionCreated(expected = true, 50, 100)
+ r.verifyReachabilityTransitionStarted(expected = true)
+ r.verifyEventIsHandled(expected = true)
+ }
+ }
+
+ /**
+ * Runs a test scenario providing a Robot.
+ */
+ fun runTestScenario(consumer: Consumer<ReachabilityGestureListenerRobotTest>) {
+ val robot = ReachabilityGestureListenerRobotTest()
+ consumer.accept(robot)
+ }
+
+ class ReachabilityGestureListenerRobotTest(
+ taskId: Int = TASK_ID,
+ token: WindowContainerToken? = TOKEN
+ ) {
+
+ companion object {
+ @JvmStatic
+ private val TASK_ID = 1
+
+ @JvmStatic
+ private val TOKEN = mock<WindowContainerToken>()
+ }
+
+ private val reachabilityListener: ReachabilityGestureListener
+ private val transitions: Transitions
+ private val animationHandler: Transitions.TransitionHandler
+ private val wctSupplier: WindowContainerTransactionSupplier
+ private val wct: WindowContainerTransaction
+ private var eventHandled = false
+
+ init {
+ transitions = mock<Transitions>()
+ animationHandler = mock<Transitions.TransitionHandler>()
+ wctSupplier = mock<WindowContainerTransactionSupplier>()
+ wct = mock<WindowContainerTransaction>()
+ doReturn(wct).`when`(wctSupplier).get()
+ reachabilityListener =
+ ReachabilityGestureListener(
+ taskId,
+ token,
+ transitions,
+ animationHandler,
+ wctSupplier
+ )
+ }
+
+ fun updateActivityBounds(activityBounds: Rect) {
+ reachabilityListener.updateActivityBounds(activityBounds)
+ }
+
+ fun sendMotionEvent(x: Int, y: Int) {
+ eventHandled = reachabilityListener.onDoubleTap(motionEventAt(x.toFloat(), y.toFloat()))
+ }
+
+ fun verifyReachabilityTransitionCreated(
+ expected: Boolean,
+ x: Int,
+ y: Int,
+ taskId: Int = TASK_ID,
+ token: WindowContainerToken? = TOKEN
+ ) {
+ verify(wct, expected.asMode()).setReachabilityOffset(
+ token!!,
+ taskId,
+ x,
+ y
+ )
+ }
+
+ fun verifyReachabilityTransitionStarted(expected: Boolean = true) {
+ verify(transitions, expected.asMode()).startTransition(
+ TRANSIT_MOVE_LETTERBOX_REACHABILITY,
+ wct,
+ animationHandler
+ )
+ }
+
+ fun verifyEventIsHandled(expected: Boolean) {
+ assertEquals(expected, eventHandled)
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandlerTest.kt
index 0b41952a89d7..e9f92cfd7c56 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandlerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandlerTest.kt
@@ -58,6 +58,7 @@ import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyBoolean
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Mock
import org.mockito.Mockito
@@ -126,17 +127,6 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() {
}
@Test
- fun startMinimizedModeTransition_callsFreeformTaskTransitionHandler() {
- val wct = WindowContainerTransaction()
- whenever(freeformTaskTransitionHandler.startMinimizedModeTransition(any()))
- .thenReturn(mock())
-
- mixedHandler.startMinimizedModeTransition(wct)
-
- verify(freeformTaskTransitionHandler).startMinimizedModeTransition(wct)
- }
-
- @Test
@DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS_BUGFIX)
fun startRemoveTransition_callsFreeformTaskTransitionHandler() {
val wct = WindowContainerTransaction()
@@ -531,6 +521,131 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() {
}
@Test
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_EXIT_BY_MINIMIZE_TRANSITION_BUGFIX)
+ fun startMinimizedModeTransition_exitByMinimizeTransitionFlagsDisabled_doesNotUseMixedHandler() {
+ val wct = WindowContainerTransaction()
+ val task = createTask(WINDOWING_MODE_FREEFORM)
+ whenever(
+ freeformTaskTransitionHandler.startMinimizedModeTransition(
+ any(),
+ anyInt(),
+ anyBoolean(),
+ )
+ )
+ .thenReturn(mock())
+
+ mixedHandler.startMinimizedModeTransition(
+ wct = wct,
+ taskId = task.taskId,
+ isLastTask = true,
+ )
+
+ verify(freeformTaskTransitionHandler)
+ .startMinimizedModeTransition(eq(wct), eq(task.taskId), eq(true))
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_EXIT_BY_MINIMIZE_TRANSITION_BUGFIX)
+ fun startMinimizedModeTransition_exitByMinimizeTransitionFlagsEnabled_notLastTask_callsMinimizationHandler() {
+ val wct = WindowContainerTransaction()
+ val minimizingTask = createTask(WINDOWING_MODE_FREEFORM)
+ val minimizingTaskChange = createChange(minimizingTask)
+ val transition = Binder()
+ whenever(
+ transitions.startTransition(eq(Transitions.TRANSIT_MINIMIZE), eq(wct), anyOrNull())
+ )
+ .thenReturn(transition)
+ whenever(
+ desktopMinimizationTransitionHandler.startAnimation(
+ any(),
+ any(),
+ any(),
+ any(),
+ any(),
+ )
+ )
+ .thenReturn(true)
+
+ mixedHandler.startMinimizedModeTransition(
+ wct = wct,
+ taskId = minimizingTask.taskId,
+ isLastTask = false,
+ )
+ val started =
+ mixedHandler.startAnimation(
+ transition = transition,
+ info =
+ createCloseTransitionInfo(
+ Transitions.TRANSIT_MINIMIZE,
+ listOf(minimizingTaskChange),
+ ),
+ startTransaction = mock(),
+ finishTransaction = mock(),
+ finishCallback = {},
+ )
+
+ assertTrue("Should delegate animation to minimization transition handler", started)
+ verify(desktopMinimizationTransitionHandler)
+ .startAnimation(
+ eq(transition),
+ argThat { info -> info.changes.contains(minimizingTaskChange) },
+ any(),
+ any(),
+ any(),
+ )
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_EXIT_BY_MINIMIZE_TRANSITION_BUGFIX)
+ fun startMinimizedModeTransition_exitByMinimizeTransitionFlagsEnabled_withMinimizingLastTask_dispatchesTransition() {
+ val wct = WindowContainerTransaction()
+ val minimizingTask = createTask(WINDOWING_MODE_FREEFORM)
+ val minimizingTaskChange = createChange(minimizingTask)
+ val transition = Binder()
+ whenever(
+ transitions.startTransition(eq(Transitions.TRANSIT_MINIMIZE), eq(wct), anyOrNull())
+ )
+ .thenReturn(transition)
+ whenever(
+ desktopMinimizationTransitionHandler.startAnimation(
+ any(),
+ any(),
+ any(),
+ any(),
+ any(),
+ )
+ )
+ .thenReturn(true)
+
+ mixedHandler.startMinimizedModeTransition(
+ wct = wct,
+ taskId = minimizingTask.taskId,
+ isLastTask = true,
+ )
+ mixedHandler.startAnimation(
+ transition = transition,
+ info =
+ createCloseTransitionInfo(
+ Transitions.TRANSIT_MINIMIZE,
+ listOf(minimizingTaskChange),
+ ),
+ startTransaction = mock(),
+ finishTransaction = mock(),
+ finishCallback = {},
+ )
+
+ verify(transitions)
+ .dispatchTransition(
+ eq(transition),
+ argThat { info -> info.changes.contains(minimizingTaskChange) },
+ any(),
+ any(),
+ any(),
+ eq(mixedHandler),
+ )
+ }
+
+ @Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS_BUGFIX)
fun addPendingAndAnimateLaunchTransition_noMinimizeChange_doesNotReparentMinimizeChange() {
val wct = WindowContainerTransaction()
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 8510441c0557..ed9b97d264f7 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
@@ -55,6 +55,8 @@ import org.mockito.Mock
import org.mockito.Mockito.inOrder
import org.mockito.Mockito.spy
import org.mockito.kotlin.any
+import org.mockito.kotlin.clearInvocations
+import org.mockito.kotlin.eq
import org.mockito.kotlin.never
import org.mockito.kotlin.times
import org.mockito.kotlin.verify
@@ -894,12 +896,12 @@ class DesktopRepositoryTest(flags: FlagsParameterization) : ShellTestCase() {
val taskId = 1
val listener = TestListener()
repo.addActiveTaskListener(listener)
- repo.addTask(DEFAULT_DISPLAY, taskId, isVisible = true)
+ repo.addTask(THIRD_DISPLAY, taskId, isVisible = true)
repo.removeTask(THIRD_DISPLAY, taskId)
assertThat(repo.isActiveTask(taskId)).isFalse()
- assertThat(listener.activeChangesOnDefaultDisplay).isEqualTo(2)
+ assertThat(listener.activeChangesOnThirdDisplay).isEqualTo(2)
}
@Test
@@ -917,7 +919,7 @@ class DesktopRepositoryTest(flags: FlagsParameterization) : ShellTestCase() {
fun removeTask_updatesTaskVisibility() {
repo.addDesk(displayId = THIRD_DISPLAY, deskId = THIRD_DISPLAY)
val taskId = 1
- repo.addTask(DEFAULT_DISPLAY, taskId, isVisible = true)
+ repo.addTask(THIRD_DISPLAY, taskId, isVisible = true)
repo.removeTask(THIRD_DISPLAY, taskId)
@@ -1106,6 +1108,30 @@ class DesktopRepositoryTest(flags: FlagsParameterization) : ShellTestCase() {
}
@Test
+ @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun setTaskInFullImmersiveState_inDesk_savedAsInImmersiveState() {
+ repo.addDesk(DEFAULT_DISPLAY, deskId = 6)
+ repo.addTaskToDesk(DEFAULT_DISPLAY, deskId = 6, taskId = 10, isVisible = true)
+ assertThat(repo.isTaskInFullImmersiveState(6)).isFalse()
+
+ repo.setTaskInFullImmersiveStateInDesk(deskId = 6, taskId = 10, immersive = true)
+
+ assertThat(repo.isTaskInFullImmersiveState(taskId = 10)).isTrue()
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun removeTaskInFullImmersiveState_inDesk_removedAsInImmersiveState() {
+ repo.addDesk(DEFAULT_DISPLAY, deskId = 6)
+ repo.addTaskToDesk(DEFAULT_DISPLAY, deskId = 6, taskId = 10, isVisible = true)
+ repo.setTaskInFullImmersiveStateInDesk(deskId = 6, taskId = 10, immersive = true)
+
+ repo.setTaskInFullImmersiveStateInDesk(deskId = 6, taskId = 10, immersive = false)
+
+ assertThat(repo.isTaskInFullImmersiveState(taskId = 10)).isFalse()
+ }
+
+ @Test
fun removeTaskInFullImmersiveState_otherWasImmersive_otherRemainsImmersive() {
repo.setTaskInFullImmersiveState(DEFAULT_DISPLAY, taskId = 1, immersive = true)
@@ -1274,14 +1300,146 @@ class DesktopRepositoryTest(flags: FlagsParameterization) : ShellTestCase() {
assertEquals(SECOND_DISPLAY, repo.getDisplayForDesk(deskId = 8))
}
+ @Test
+ @EnableFlags(FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun setDeskActive() {
+ repo.addDesk(DEFAULT_DISPLAY, deskId = 6)
+
+ repo.setActiveDesk(DEFAULT_DISPLAY, deskId = 6)
+
+ assertThat(repo.getActiveDeskId(DEFAULT_DISPLAY)).isEqualTo(6)
+ }
+
+ @Test
+ @EnableFlags(FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun setDeskInactive() {
+ repo.addDesk(DEFAULT_DISPLAY, deskId = 6)
+ repo.setActiveDesk(DEFAULT_DISPLAY, deskId = 6)
+
+ repo.setDeskInactive(deskId = 6)
+
+ assertThat(repo.getActiveDeskId(DEFAULT_DISPLAY)).isNull()
+ }
+
+ @Test
+ @EnableFlags(FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun getDeskIdForTask() {
+ repo.addDesk(DEFAULT_DISPLAY, deskId = 6)
+ repo.addTaskToDesk(DEFAULT_DISPLAY, deskId = 6, taskId = 10, isVisible = true)
+
+ assertThat(repo.getDeskIdForTask(10)).isEqualTo(6)
+ }
+
+ @Test
+ @EnableFlags(FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun removeTaskFromDesk_clearsBoundsBeforeMaximize() {
+ repo.addDesk(DEFAULT_DISPLAY, deskId = 6)
+ repo.addTaskToDesk(DEFAULT_DISPLAY, deskId = 6, taskId = 10, isVisible = true)
+ repo.saveBoundsBeforeMaximize(taskId = 10, bounds = Rect(10, 10, 100, 100))
+
+ repo.removeTaskFromDesk(deskId = 6, taskId = 10)
+
+ assertThat(repo.removeBoundsBeforeMaximize(taskId = 10)).isNull()
+ }
+
+ @Test
+ @EnableFlags(FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun removeTaskFromDesk_clearsBoundsBeforeImmersive() {
+ repo.addDesk(DEFAULT_DISPLAY, deskId = 6)
+ repo.addTaskToDesk(DEFAULT_DISPLAY, deskId = 6, taskId = 10, isVisible = true)
+ repo.saveBoundsBeforeFullImmersive(taskId = 10, bounds = Rect(10, 10, 100, 100))
+
+ repo.removeTaskFromDesk(deskId = 6, taskId = 10)
+
+ assertThat(repo.removeBoundsBeforeFullImmersive(taskId = 10)).isNull()
+ }
+
+ @Test
+ @EnableFlags(FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun removeTaskFromDesk_removesFromZOrderList() {
+ repo.addDesk(DEFAULT_DISPLAY, deskId = 6)
+ repo.addTaskToDesk(DEFAULT_DISPLAY, deskId = 6, taskId = 10, isVisible = true)
+
+ repo.removeTaskFromDesk(deskId = 6, taskId = 10)
+
+ assertThat(repo.getFreeformTasksIdsInDeskInZOrder(deskId = 6)).doesNotContain(10)
+ }
+
+ @Test
+ @EnableFlags(FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun removeTaskFromDesk_removesFromMinimized() {
+ repo.addDesk(DEFAULT_DISPLAY, deskId = 6)
+ repo.addTaskToDesk(DEFAULT_DISPLAY, deskId = 6, taskId = 10, isVisible = true)
+ repo.minimizeTaskInDesk(DEFAULT_DISPLAY, deskId = 6, taskId = 10)
+
+ repo.removeTaskFromDesk(deskId = 6, taskId = 10)
+
+ assertThat(repo.getMinimizedTaskIdsInDesk(deskId = 6)).doesNotContain(10)
+ }
+
+ @Test
+ @EnableFlags(FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun removeTaskFromDesk_removesFromImmersive() {
+ repo.addDesk(DEFAULT_DISPLAY, deskId = 6)
+ repo.addTaskToDesk(DEFAULT_DISPLAY, deskId = 6, taskId = 10, isVisible = true)
+ repo.setTaskInFullImmersiveStateInDesk(deskId = 6, taskId = 10, immersive = true)
+
+ repo.removeTaskFromDesk(deskId = 6, taskId = 10)
+
+ assertThat(repo.isTaskInFullImmersiveState(taskId = 10)).isFalse()
+ }
+
+ @Test
+ @EnableFlags(FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun removeTaskFromDesk_removesFromActiveTasks() {
+ repo.addDesk(DEFAULT_DISPLAY, deskId = 6)
+ repo.addTaskToDesk(DEFAULT_DISPLAY, deskId = 6, taskId = 10, isVisible = true)
+
+ repo.removeTaskFromDesk(deskId = 6, taskId = 10)
+
+ assertThat(repo.isActiveTaskInDesk(taskId = 10, deskId = 6)).isFalse()
+ }
+
+ @Test
+ @EnableFlags(FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun removeTaskFromDesk_removesFromVisibleTasks() {
+ repo.addDesk(DEFAULT_DISPLAY, deskId = 6)
+ repo.addTaskToDesk(DEFAULT_DISPLAY, deskId = 6, taskId = 10, isVisible = true)
+
+ repo.removeTaskFromDesk(deskId = 6, taskId = 10)
+
+ assertThat(repo.isVisibleTaskInDesk(taskId = 10, deskId = 6)).isFalse()
+ }
+
+ @Test
+ @EnableFlags(FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND, FLAG_ENABLE_DESKTOP_WINDOWING_PERSISTENCE)
+ fun removeTaskFromDesk_updatesPersistence() = runTest {
+ repo.addDesk(DEFAULT_DISPLAY, deskId = 6)
+ repo.addTaskToDesk(DEFAULT_DISPLAY, deskId = 6, taskId = 10, isVisible = true)
+ clearInvocations(persistentRepository)
+
+ repo.removeTaskFromDesk(deskId = 6, taskId = 10)
+
+ verify(persistentRepository)
+ .addOrUpdateDesktop(
+ userId = eq(DEFAULT_USER_ID),
+ desktopId = eq(6),
+ visibleTasks = any(),
+ minimizedTasks = any(),
+ freeformTasksInZOrder = any(),
+ )
+ }
+
class TestListener : DesktopRepository.ActiveTasksListener {
var activeChangesOnDefaultDisplay = 0
var activeChangesOnSecondaryDisplay = 0
+ var activeChangesOnThirdDisplay = 0
override fun onActiveTasksChanged(displayId: Int) {
when (displayId) {
DEFAULT_DISPLAY -> activeChangesOnDefaultDisplay++
SECOND_DISPLAY -> activeChangesOnSecondaryDisplay++
+ THIRD_DISPLAY -> activeChangesOnThirdDisplay++
else -> fail("Active task listener received unexpected display id: $displayId")
}
}
@@ -1290,9 +1448,11 @@ class DesktopRepositoryTest(flags: FlagsParameterization) : ShellTestCase() {
class TestVisibilityListener : DesktopRepository.VisibleTasksListener {
var visibleTasksCountOnDefaultDisplay = 0
var visibleTasksCountOnSecondaryDisplay = 0
+ var visibleTasksCountOnThirdDisplay = 0
var visibleChangesOnDefaultDisplay = 0
var visibleChangesOnSecondaryDisplay = 0
+ var visibleChangesOnThirdDisplay = 0
override fun onTasksVisibilityChanged(displayId: Int, visibleTasksCount: Int) {
when (displayId) {
@@ -1304,6 +1464,10 @@ class DesktopRepositoryTest(flags: FlagsParameterization) : ShellTestCase() {
visibleTasksCountOnSecondaryDisplay = visibleTasksCount
visibleChangesOnSecondaryDisplay++
}
+ THIRD_DISPLAY -> {
+ visibleTasksCountOnThirdDisplay = visibleTasksCount
+ visibleChangesOnThirdDisplay++
+ }
else -> fail("Visible task listener received unexpected display id: $displayId")
}
}
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 3ee9501dd8dd..fcd92ac2678a 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
@@ -151,8 +151,7 @@ import com.android.wm.shell.transition.Transitions
import com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS
import com.android.wm.shell.transition.Transitions.TransitionHandler
import com.android.wm.shell.windowdecor.DesktopModeWindowDecorViewModelTestsBase.Companion.HOME_LAUNCHER_PACKAGE_NAME
-import com.android.wm.shell.windowdecor.DesktopModeWindowDecoration
-import com.android.wm.shell.windowdecor.tiling.DesktopTilingDecorViewModel
+import com.android.wm.shell.windowdecor.tiling.SnapEventHandler
import com.google.common.truth.Truth.assertThat
import com.google.common.truth.Truth.assertWithMessage
import java.util.Optional
@@ -179,6 +178,7 @@ import org.mockito.ArgumentMatchers.isA
import org.mockito.ArgumentMatchers.isNull
import org.mockito.Mock
import org.mockito.Mockito
+import org.mockito.Mockito.anyBoolean
import org.mockito.Mockito.anyInt
import org.mockito.Mockito.clearInvocations
import org.mockito.Mockito.mock
@@ -233,6 +233,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
@Mock lateinit var multiInstanceHelper: MultiInstanceHelper
@Mock lateinit var desktopModeVisualIndicator: DesktopModeVisualIndicator
@Mock lateinit var recentTasksController: RecentTasksController
+ @Mock lateinit var snapEventHandler: SnapEventHandler
@Mock private lateinit var mockInteractionJankMonitor: InteractionJankMonitor
@Mock private lateinit var mockSurface: SurfaceControl
@Mock private lateinit var taskbarDesktopTaskListener: TaskbarDesktopTaskListener
@@ -245,9 +246,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
@Mock lateinit var repositoryInitializer: DesktopRepositoryInitializer
@Mock private lateinit var mockToast: Toast
private lateinit var mockitoSession: StaticMockitoSession
- @Mock private lateinit var desktopTilingDecorViewModel: DesktopTilingDecorViewModel
@Mock private lateinit var bubbleController: BubbleController
- @Mock private lateinit var desktopWindowDecoration: DesktopModeWindowDecoration
@Mock private lateinit var resources: Resources
@Mock
lateinit var desktopModeEnterExitTransitionListener: DesktopModeEntryExitTransitionListener
@@ -286,7 +285,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
private val DEFAULT_PORTRAIT_BOUNDS = Rect(200, 165, 1400, 2085)
private val RESIZABLE_LANDSCAPE_BOUNDS = Rect(25, 435, 1575, 1635)
private val RESIZABLE_PORTRAIT_BOUNDS = Rect(680, 75, 1880, 1275)
- private val UNRESIZABLE_LANDSCAPE_BOUNDS = Rect(25, 449, 1575, 1611)
+ private val UNRESIZABLE_LANDSCAPE_BOUNDS = Rect(25, 448, 1575, 1611)
private val UNRESIZABLE_PORTRAIT_BOUNDS = Rect(830, 75, 1730, 1275)
private val wallpaperToken = MockToken().token()
private val homeComponentName = ComponentName(HOME_LAUNCHER_PACKAGE_NAME, /* class */ "")
@@ -328,8 +327,10 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
desktopModeCompatPolicy = spy(DesktopModeCompatPolicy(spyContext))
whenever(shellTaskOrganizer.getRunningTasks(anyInt())).thenAnswer { runningTasks }
- whenever(transitions.startTransition(anyInt(), any(), isNull())).thenAnswer { Binder() }
+ whenever(transitions.startTransition(anyInt(), any(), anyOrNull())).thenAnswer { Binder() }
whenever(enterDesktopTransitionHandler.moveToDesktop(any(), any())).thenAnswer { Binder() }
+ whenever(exitDesktopTransitionHandler.startTransition(any(), any(), any(), any()))
+ .thenReturn(Binder())
whenever(displayController.getDisplayLayout(anyInt())).thenReturn(displayLayout)
whenever(displayController.getDisplayContext(anyInt())).thenReturn(mockDisplayContext)
whenever(displayController.getDisplay(anyInt())).thenReturn(display)
@@ -379,6 +380,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
recentsTransitionStateListener = captor.firstValue
controller.taskbarDesktopTaskListener = taskbarDesktopTaskListener
+ controller.setSnapEventHandler(snapEventHandler)
assumeTrue(ENABLE_SHELL_TRANSITIONS)
@@ -422,7 +424,6 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
mockHandler,
desktopModeEventLogger,
desktopModeUiEventLogger,
- desktopTilingDecorViewModel,
desktopWallpaperActivityTokenProvider,
Optional.of(bubbleController),
overviewToDesktopTransitionObserver,
@@ -918,7 +919,10 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
}
@Test
- @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ @DisableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
+ )
fun showDesktopApps_twoDisplays_bringsToFrontOnlyOneDisplay_desktopWallpaperDisabled() {
taskRepository.addDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY)
val homeTaskDefaultDisplay = setUpHomeTask(DEFAULT_DISPLAY)
@@ -2040,6 +2044,30 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
}
@Test
+ @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun moveToFullscreen_fromDesk_reparentsToTaskDisplayArea() {
+ val task = setUpFreeformTask()
+ val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!!
+
+ controller.moveToFullscreen(task.taskId, transitionSource = UNKNOWN)
+
+ val wct = getLatestExitDesktopWct()
+ wct.assertHop(ReparentPredicate(token = task.token, parentToken = tda.token, toTop = true))
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun moveToFullscreen_fromDesk_deactivatesDesk() {
+ val task = setUpFreeformTask()
+ val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!!
+
+ controller.moveToFullscreen(task.taskId, transitionSource = UNKNOWN)
+
+ val wct = getLatestExitDesktopWct()
+ verify(desksOrganizer).deactivateDesk(wct, deskId = 0)
+ }
+
+ @Test
fun moveToFullscreen_tdaFullscreen_windowingModeSetToUndefined() {
val task = setUpFreeformTask()
val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!!
@@ -2054,6 +2082,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WALLPAPER_ACTIVITY_FOR_SYSTEM_USER)
+ @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
fun moveToFullscreen_tdaFullscreen_windowingModeUndefined_removesWallpaperActivity() {
whenever(DesktopModeStatus.enterDesktopByDefaultOnFreeformDisplay(context)).thenReturn(true)
val homeTask = setUpHomeTask()
@@ -2079,6 +2108,60 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
}
@Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WALLPAPER_ACTIVITY_FOR_SYSTEM_USER,
+ Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
+ )
+ fun moveToFullscreen_tdaFullscreen_windowingModeUndefined_removesWallpaperActivity_multiDesksEnabled() {
+ whenever(DesktopModeStatus.enterDesktopByDefaultOnFreeformDisplay(context)).thenReturn(true)
+ setUpHomeTask()
+ val task = setUpFreeformTask()
+ assertNotNull(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY))
+ .configuration
+ .windowConfiguration
+ .windowingMode = WINDOWING_MODE_FULLSCREEN
+
+ controller.moveToFullscreen(task.taskId, transitionSource = UNKNOWN)
+
+ val wct = getLatestExitDesktopWct()
+ val taskChange = assertNotNull(wct.changes[task.token.asBinder()])
+ verify(desktopModeEnterExitTransitionListener)
+ .onExitDesktopModeTransitionStarted(FULLSCREEN_ANIMATION_DURATION)
+ assertThat(taskChange.windowingMode).isEqualTo(WINDOWING_MODE_UNDEFINED)
+ // Removes wallpaper activity when leaving desktop
+ wct.assertReorder(wallpaperToken, toTop = false)
+ }
+
+ @Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WALLPAPER_ACTIVITY_FOR_SYSTEM_USER,
+ Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
+ )
+ fun moveToFullscreen_tdaFullscreen_windowingModeUndefined_homeBehindFullscreen_multiDesksEnabled() {
+ whenever(DesktopModeStatus.enterDesktopByDefaultOnFreeformDisplay(context)).thenReturn(true)
+ val homeTask = setUpHomeTask()
+ val task = setUpFreeformTask()
+ assertNotNull(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY))
+ .configuration
+ .windowConfiguration
+ .windowingMode = WINDOWING_MODE_FULLSCREEN
+
+ controller.moveToFullscreen(task.taskId, transitionSource = UNKNOWN)
+
+ val wct = getLatestExitDesktopWct()
+ val taskChange = assertNotNull(wct.changes[task.token.asBinder()])
+ verify(desktopModeEnterExitTransitionListener)
+ .onExitDesktopModeTransitionStarted(FULLSCREEN_ANIMATION_DURATION)
+ assertThat(taskChange.windowingMode).isEqualTo(WINDOWING_MODE_UNDEFINED)
+ // Moves home task behind the fullscreen task
+ val homeReorderIndex = wct.indexOfReorder(homeTask, toTop = true)
+ val fullscreenReorderIndex = wct.indexOfReorder(task, toTop = true)
+ assertThat(homeReorderIndex).isNotEqualTo(-1)
+ assertThat(fullscreenReorderIndex).isNotEqualTo(-1)
+ assertThat(fullscreenReorderIndex).isGreaterThan(homeReorderIndex)
+ }
+
+ @Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WALLPAPER_ACTIVITY_FOR_SYSTEM_USER)
fun moveToFullscreen_tdaFreeform_enforcedDesktop_doesNotReorderHome() {
whenever(DesktopModeStatus.enterDesktopByDefaultOnFreeformDisplay(context)).thenReturn(true)
@@ -2094,9 +2177,9 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
val wct = getLatestExitDesktopWct()
verify(desktopModeEnterExitTransitionListener)
.onExitDesktopModeTransitionStarted(FULLSCREEN_ANIMATION_DURATION)
- assertThat(wct.hierarchyOps).hasSize(1)
// Removes wallpaper activity when leaving desktop but doesn't reorder home or the task
- wct.assertReorderAt(index = 0, wallpaperToken, toTop = false)
+ wct.assertReorder(wallpaperToken, toTop = false)
+ wct.assertWithoutHop(ReorderPredicate(homeTask.token, toTop = null))
}
@Test
@@ -2114,6 +2197,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WALLPAPER_ACTIVITY_FOR_SYSTEM_USER)
+ @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
fun moveToFullscreen_tdaFreeform_windowingModeFullscreen_removesWallpaperActivity() {
val homeTask = setUpHomeTask()
val task = setUpFreeformTask()
@@ -2139,6 +2223,61 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
}
@Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WALLPAPER_ACTIVITY_FOR_SYSTEM_USER,
+ Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
+ )
+ fun moveToFullscreen_tdaFreeform_windowingModeFullscreen_removesWallpaperActivity_multiDesksEnabled() {
+ val homeTask = setUpHomeTask()
+ val task = setUpFreeformTask()
+
+ assertNotNull(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY))
+ .configuration
+ .windowConfiguration
+ .windowingMode = WINDOWING_MODE_FREEFORM
+
+ controller.moveToFullscreen(task.taskId, transitionSource = UNKNOWN)
+
+ val wct = getLatestExitDesktopWct()
+ val taskChange = assertNotNull(wct.changes[task.token.asBinder()])
+ assertThat(taskChange.windowingMode).isEqualTo(WINDOWING_MODE_FULLSCREEN)
+ verify(desktopModeEnterExitTransitionListener)
+ .onExitDesktopModeTransitionStarted(FULLSCREEN_ANIMATION_DURATION)
+ // Removes wallpaper activity when leaving desktop
+ wct.assertReorder(wallpaperToken, toTop = false)
+ }
+
+ @Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WALLPAPER_ACTIVITY_FOR_SYSTEM_USER,
+ Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
+ )
+ fun moveToFullscreen_tdaFreeform_windowingModeFullscreen_homeBehindFullscreen_multiDesksEnabled() {
+ val homeTask = setUpHomeTask()
+ val task = setUpFreeformTask()
+
+ assertNotNull(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY))
+ .configuration
+ .windowConfiguration
+ .windowingMode = WINDOWING_MODE_FREEFORM
+
+ controller.moveToFullscreen(task.taskId, transitionSource = UNKNOWN)
+
+ val wct = getLatestExitDesktopWct()
+ val taskChange = assertNotNull(wct.changes[task.token.asBinder()])
+ assertThat(taskChange.windowingMode).isEqualTo(WINDOWING_MODE_FULLSCREEN)
+ verify(desktopModeEnterExitTransitionListener)
+ .onExitDesktopModeTransitionStarted(FULLSCREEN_ANIMATION_DURATION)
+ // Moves home task behind the fullscreen task
+ val homeReorderIndex = wct.indexOfReorder(homeTask, toTop = true)
+ val fullscreenReorderIndex = wct.indexOfReorder(task, toTop = true)
+ assertThat(homeReorderIndex).isNotEqualTo(-1)
+ assertThat(fullscreenReorderIndex).isNotEqualTo(-1)
+ assertThat(fullscreenReorderIndex).isGreaterThan(homeReorderIndex)
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
fun moveToFullscreen_multipleVisibleNonMinimizedTasks_doesNotRemoveWallpaperActivity() {
val homeTask = setUpHomeTask()
val task1 = setUpFreeformTask()
@@ -2165,6 +2304,29 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
}
@Test
+ @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun moveToFullscreen_multipleVisibleNonMinimizedTasks_doesNotRemoveWallpaperActivity_multiDesksEnabled() {
+ val homeTask = setUpHomeTask()
+ val task1 = setUpFreeformTask()
+ // Setup task2
+ setUpFreeformTask()
+
+ val tdaInfo = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)
+ assertNotNull(tdaInfo).configuration.windowConfiguration.windowingMode =
+ WINDOWING_MODE_FULLSCREEN
+
+ controller.moveToFullscreen(task1.taskId, transitionSource = UNKNOWN)
+
+ val wct = getLatestExitDesktopWct()
+ val task1Change = assertNotNull(wct.changes[task1.token.asBinder()])
+ assertThat(task1Change.windowingMode).isEqualTo(WINDOWING_MODE_UNDEFINED)
+ verify(desktopModeEnterExitTransitionListener)
+ .onExitDesktopModeTransitionStarted(FULLSCREEN_ANIMATION_DURATION)
+ // Does not remove wallpaper activity, as desktop still has a visible desktop task
+ wct.assertWithoutHop(ReorderPredicate(wallpaperToken, toTop = false))
+ }
+
+ @Test
fun moveToFullscreen_nonExistentTask_doesNothing() {
controller.moveToFullscreen(999, transitionSource = UNKNOWN)
verifyExitDesktopWCTNotExecuted()
@@ -2738,7 +2900,10 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
}
@Test
- @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_PIP)
+ @EnableFlags(
+ FLAG_ENABLE_DESKTOP_WINDOWING_PIP,
+ Flags.FLAG_ENABLE_DESKTOP_WALLPAPER_ACTIVITY_FOR_SYSTEM_USER,
+ )
fun onDesktopWindowClose_minimizedPipNotPresent_exitDesktop() {
val freeformTask = setUpFreeformTask()
val pipTask = setUpPipTask(autoEnterEnabled = true)
@@ -2753,29 +2918,102 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
val wct = WindowContainerTransaction()
controller.onDesktopWindowClose(wct, displayId = DEFAULT_DISPLAY, freeformTask)
- // Remove wallpaper operation
- wct.hierarchyOps.any { hop ->
- hop.type == HIERARCHY_OP_TYPE_REMOVE_TASK && hop.container == wallpaperToken.asBinder()
- }
+ // Moves wallpaper activity to back when leaving desktop
+ wct.assertReorder(wallpaperToken, toTop = false)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun onDesktopWindowClose_lastWindow_deactivatesDesk() {
+ val task = setUpFreeformTask()
+ val wct = WindowContainerTransaction()
+
+ controller.onDesktopWindowClose(wct, displayId = DEFAULT_DISPLAY, task)
+
+ verify(desksOrganizer).deactivateDesk(wct, deskId = 0)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun onDesktopWindowClose_lastWindow_addsPendingDeactivateTransition() {
+ val task = setUpFreeformTask()
+ val wct = WindowContainerTransaction()
+
+ val transition = Binder()
+ val runOnTransitStart =
+ controller.onDesktopWindowClose(wct, displayId = DEFAULT_DISPLAY, task)
+ runOnTransitStart(transition)
+
+ verify(desksTransitionsObserver)
+ .addPendingTransition(DeskTransition.DeactivateDesk(transition, deskId = 0))
}
@Test
fun onDesktopWindowMinimize_noActiveTask_doesntRemoveWallpaper() {
val task = setUpFreeformTask(active = false)
val transition = Binder()
- whenever(freeformTaskTransitionStarter.startMinimizedModeTransition(any()))
+ whenever(
+ freeformTaskTransitionStarter.startMinimizedModeTransition(
+ any(),
+ anyInt(),
+ anyBoolean(),
+ )
+ )
.thenReturn(transition)
controller.minimizeTask(task, MinimizeReason.MINIMIZE_BUTTON)
val captor = argumentCaptor<WindowContainerTransaction>()
- verify(freeformTaskTransitionStarter).startMinimizedModeTransition(captor.capture())
+ verify(freeformTaskTransitionStarter)
+ .startMinimizedModeTransition(captor.capture(), eq(task.taskId), eq(false))
captor.firstValue.hierarchyOps.none { hop ->
hop.type == HIERARCHY_OP_TYPE_REMOVE_TASK && hop.container == wallpaperToken.asBinder()
}
}
@Test
+ @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun onDesktopWindowMinimize_lastWindow_deactivatesDesk() {
+ val task = setUpFreeformTask()
+ val transition = Binder()
+ whenever(
+ freeformTaskTransitionStarter.startMinimizedModeTransition(
+ any(),
+ anyInt(),
+ anyBoolean(),
+ )
+ )
+ .thenReturn(transition)
+
+ controller.minimizeTask(task, MinimizeReason.MINIMIZE_BUTTON)
+
+ val captor = argumentCaptor<WindowContainerTransaction>()
+ verify(freeformTaskTransitionStarter)
+ .startMinimizedModeTransition(captor.capture(), eq(task.taskId), eq(true))
+ verify(desksOrganizer).deactivateDesk(captor.firstValue, deskId = 0)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun onDesktopWindowMinimize_lastWindow_addsPendingDeactivateTransition() {
+ val task = setUpFreeformTask()
+ val transition = Binder()
+ whenever(
+ freeformTaskTransitionStarter.startMinimizedModeTransition(
+ any(),
+ anyInt(),
+ anyBoolean(),
+ )
+ )
+ .thenReturn(transition)
+
+ controller.minimizeTask(task, MinimizeReason.MINIMIZE_BUTTON)
+
+ verify(desksTransitionsObserver)
+ .addPendingTransition(DeskTransition.DeactivateDesk(token = transition, deskId = 0))
+ }
+
+ @Test
fun onPipTaskMinimize_autoEnterEnabled_startPipTransition() {
val task = setUpPipTask(autoEnterEnabled = true)
val handler = mock(TransitionHandler::class.java)
@@ -2785,18 +3023,26 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
controller.minimizeTask(task, MinimizeReason.MINIMIZE_BUTTON)
verify(freeformTaskTransitionStarter).startPipTransition(any())
- verify(freeformTaskTransitionStarter, never()).startMinimizedModeTransition(any())
+ verify(freeformTaskTransitionStarter, never())
+ .startMinimizedModeTransition(any(), anyInt(), anyBoolean())
}
@Test
fun onPipTaskMinimize_autoEnterDisabled_startMinimizeTransition() {
val task = setUpPipTask(autoEnterEnabled = false)
- whenever(freeformTaskTransitionStarter.startMinimizedModeTransition(any()))
+ whenever(
+ freeformTaskTransitionStarter.startMinimizedModeTransition(
+ any(),
+ anyInt(),
+ anyBoolean(),
+ )
+ )
.thenReturn(Binder())
controller.minimizeTask(task, MinimizeReason.MINIMIZE_BUTTON)
- verify(freeformTaskTransitionStarter).startMinimizedModeTransition(any())
+ verify(freeformTaskTransitionStarter)
+ .startMinimizedModeTransition(any(), eq(task.taskId), anyBoolean())
verify(freeformTaskTransitionStarter, never()).startPipTransition(any())
}
@@ -2820,13 +3066,20 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
fun onDesktopWindowMinimize_singleActiveTask_noWallpaperActivityToken_doesntRemoveWallpaper() {
val task = setUpFreeformTask(active = true)
val transition = Binder()
- whenever(freeformTaskTransitionStarter.startMinimizedModeTransition(any()))
+ whenever(
+ freeformTaskTransitionStarter.startMinimizedModeTransition(
+ any(),
+ anyInt(),
+ anyBoolean(),
+ )
+ )
.thenReturn(transition)
controller.minimizeTask(task, MinimizeReason.MINIMIZE_BUTTON)
val captor = argumentCaptor<WindowContainerTransaction>()
- verify(freeformTaskTransitionStarter).startMinimizedModeTransition(captor.capture())
+ verify(freeformTaskTransitionStarter)
+ .startMinimizedModeTransition(captor.capture(), eq(task.taskId), eq(true))
captor.firstValue.hierarchyOps.none { hop -> hop.type == HIERARCHY_OP_TYPE_REMOVE_TASK }
}
@@ -2835,14 +3088,21 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
fun onTaskMinimize_singleActiveTask_hasWallpaperActivityToken_removesWallpaper() {
val task = setUpFreeformTask()
val transition = Binder()
- whenever(freeformTaskTransitionStarter.startMinimizedModeTransition(any()))
+ whenever(
+ freeformTaskTransitionStarter.startMinimizedModeTransition(
+ any(),
+ anyInt(),
+ anyBoolean(),
+ )
+ )
.thenReturn(transition)
// The only active task is being minimized.
controller.minimizeTask(task, MinimizeReason.MINIMIZE_BUTTON)
val captor = argumentCaptor<WindowContainerTransaction>()
- verify(freeformTaskTransitionStarter).startMinimizedModeTransition(captor.capture())
+ verify(freeformTaskTransitionStarter)
+ .startMinimizedModeTransition(captor.capture(), eq(task.taskId), eq(true))
// Adds remove wallpaper operation
captor.firstValue.assertReorderAt(index = 0, wallpaperToken, toTop = false)
}
@@ -2851,7 +3111,13 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
fun onDesktopWindowMinimize_singleActiveTask_alreadyMinimized_doesntRemoveWallpaper() {
val task = setUpFreeformTask()
val transition = Binder()
- whenever(freeformTaskTransitionStarter.startMinimizedModeTransition(any()))
+ whenever(
+ freeformTaskTransitionStarter.startMinimizedModeTransition(
+ any(),
+ anyInt(),
+ anyBoolean(),
+ )
+ )
.thenReturn(transition)
taskRepository.minimizeTask(DEFAULT_DISPLAY, task.taskId)
@@ -2859,7 +3125,8 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
controller.minimizeTask(task, MinimizeReason.MINIMIZE_BUTTON)
val captor = argumentCaptor<WindowContainerTransaction>()
- verify(freeformTaskTransitionStarter).startMinimizedModeTransition(captor.capture())
+ verify(freeformTaskTransitionStarter)
+ .startMinimizedModeTransition(captor.capture(), eq(task.taskId), eq(false))
captor.firstValue.hierarchyOps.none { hop ->
hop.type == HIERARCHY_OP_TYPE_REMOVE_TASK && hop.container == wallpaperToken.asBinder()
}
@@ -2870,13 +3137,20 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
val task1 = setUpFreeformTask(active = true)
setUpFreeformTask(active = true)
val transition = Binder()
- whenever(freeformTaskTransitionStarter.startMinimizedModeTransition(any()))
+ whenever(
+ freeformTaskTransitionStarter.startMinimizedModeTransition(
+ any(),
+ anyInt(),
+ anyBoolean(),
+ )
+ )
.thenReturn(transition)
controller.minimizeTask(task1, MinimizeReason.MINIMIZE_BUTTON)
val captor = argumentCaptor<WindowContainerTransaction>()
- verify(freeformTaskTransitionStarter).startMinimizedModeTransition(captor.capture())
+ verify(freeformTaskTransitionStarter)
+ .startMinimizedModeTransition(captor.capture(), eq(task1.taskId), eq(false))
captor.firstValue.hierarchyOps.none { hop ->
hop.type == HIERARCHY_OP_TYPE_REMOVE_TASK && hop.container == wallpaperToken.asBinder()
}
@@ -2888,7 +3162,13 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
val task1 = setUpFreeformTask(active = true)
val task2 = setUpFreeformTask(active = true)
val transition = Binder()
- whenever(freeformTaskTransitionStarter.startMinimizedModeTransition(any()))
+ whenever(
+ freeformTaskTransitionStarter.startMinimizedModeTransition(
+ any(),
+ anyInt(),
+ anyBoolean(),
+ )
+ )
.thenReturn(transition)
taskRepository.minimizeTask(DEFAULT_DISPLAY, task2.taskId)
@@ -2896,7 +3176,8 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
controller.minimizeTask(task1, MinimizeReason.MINIMIZE_BUTTON)
// Adds remove wallpaper operation
val captor = argumentCaptor<WindowContainerTransaction>()
- verify(freeformTaskTransitionStarter).startMinimizedModeTransition(captor.capture())
+ verify(freeformTaskTransitionStarter)
+ .startMinimizedModeTransition(captor.capture(), eq(task1.taskId), eq(true))
// Adds remove wallpaper operation
captor.firstValue.assertReorderAt(index = 0, wallpaperToken, toTop = false)
}
@@ -2905,7 +3186,13 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
fun onDesktopWindowMinimize_triesToExitImmersive() {
val task = setUpFreeformTask()
val transition = Binder()
- whenever(freeformTaskTransitionStarter.startMinimizedModeTransition(any()))
+ whenever(
+ freeformTaskTransitionStarter.startMinimizedModeTransition(
+ any(),
+ anyInt(),
+ anyBoolean(),
+ )
+ )
.thenReturn(transition)
controller.minimizeTask(task, MinimizeReason.MINIMIZE_BUTTON)
@@ -2918,7 +3205,13 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
val task = setUpFreeformTask()
val transition = Binder()
val runOnTransit = RunOnStartTransitionCallback()
- whenever(freeformTaskTransitionStarter.startMinimizedModeTransition(any()))
+ whenever(
+ freeformTaskTransitionStarter.startMinimizedModeTransition(
+ any(),
+ anyInt(),
+ anyBoolean(),
+ )
+ )
.thenReturn(transition)
whenever(mMockDesktopImmersiveController.exitImmersiveIfApplicable(any(), eq(task), any()))
.thenReturn(
@@ -2932,6 +3225,24 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
}
@Test
+ fun onDesktopWindowMinimize_triesToStopTiling() {
+ val task = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
+ val transition = Binder()
+ whenever(
+ freeformTaskTransitionStarter.startMinimizedModeTransition(
+ any(),
+ anyInt(),
+ anyBoolean(),
+ )
+ )
+ .thenReturn(transition)
+
+ controller.minimizeTask(task, MinimizeReason.MINIMIZE_BUTTON)
+
+ verify(snapEventHandler).removeTaskIfTiled(eq(DEFAULT_DISPLAY), eq(task.taskId))
+ }
+
+ @Test
fun handleRequest_fullscreenTask_freeformVisible_returnSwitchToFreeformWCT() {
val homeTask = setUpHomeTask()
val freeformTask = setUpFreeformTask()
@@ -3972,10 +4283,11 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
val taskChange = assertNotNull(wct.changes[task2.token.asBinder()])
assertThat(taskChange.windowingMode)
.isEqualTo(WINDOWING_MODE_UNDEFINED) // inherited FULLSCREEN
- wct.assertReorderAt(index = 0, wallpaperToken, toTop = false)
+ wct.assertReorder(wallpaperToken, toTop = false)
}
@Test
+ @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
fun moveFocusedTaskToFullscreen_multipleVisibleTasks_doesNotRemoveWallpaperActivity() {
val homeTask = setUpHomeTask()
val task1 = setUpFreeformTask()
@@ -3999,7 +4311,56 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
}
@Test
- @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_PIP)
+ @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun moveFocusedTaskToFullscreen_multipleVisibleTasks_doesNotRemoveWallpaperActivity_multiDesksEnabled() {
+ val homeTask = setUpHomeTask()
+ val task1 = setUpFreeformTask()
+ val task2 = setUpFreeformTask()
+ val task3 = setUpFreeformTask()
+
+ task1.isFocused = false
+ task2.isFocused = true
+ task3.isFocused = false
+ controller.enterFullscreen(DEFAULT_DISPLAY, transitionSource = UNKNOWN)
+
+ val wct = getLatestExitDesktopWct()
+ val taskChange = assertNotNull(wct.changes[task2.token.asBinder()])
+ assertThat(taskChange.windowingMode)
+ .isEqualTo(WINDOWING_MODE_UNDEFINED) // inherited FULLSCREEN
+ // Does not remove wallpaper activity
+ wct.assertWithoutHop(ReorderPredicate(wallpaperToken, toTop = null))
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun moveFocusedTaskToFullscreen_multipleVisibleTasks_fullscreenOverHome_multiDesksEnabled() {
+ val homeTask = setUpHomeTask()
+ val task1 = setUpFreeformTask()
+ val task2 = setUpFreeformTask()
+ val task3 = setUpFreeformTask()
+
+ task1.isFocused = false
+ task2.isFocused = true
+ task3.isFocused = false
+ controller.enterFullscreen(DEFAULT_DISPLAY, transitionSource = UNKNOWN)
+
+ val wct = getLatestExitDesktopWct()
+ val taskChange = assertNotNull(wct.changes[task2.token.asBinder()])
+ assertThat(taskChange.windowingMode)
+ .isEqualTo(WINDOWING_MODE_UNDEFINED) // inherited FULLSCREEN
+ // Moves home task behind the fullscreen task
+ val homeReorderIndex = wct.indexOfReorder(homeTask, toTop = true)
+ val fullscreenReorderIndex = wct.indexOfReorder(task2, toTop = true)
+ assertThat(homeReorderIndex).isNotEqualTo(-1)
+ assertThat(fullscreenReorderIndex).isNotEqualTo(-1)
+ assertThat(fullscreenReorderIndex).isGreaterThan(homeReorderIndex)
+ }
+
+ @Test
+ @EnableFlags(
+ FLAG_ENABLE_DESKTOP_WINDOWING_PIP,
+ Flags.FLAG_ENABLE_DESKTOP_WALLPAPER_ACTIVITY_FOR_SYSTEM_USER,
+ )
fun moveFocusedTaskToFullscreen_minimizedPipPresent_removeWallpaperActivity() {
val freeformTask = setUpFreeformTask()
val pipTask = setUpPipTask(autoEnterEnabled = true)
@@ -4017,10 +4378,8 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
val taskChange = assertNotNull(wct.changes[freeformTask.token.asBinder()])
assertThat(taskChange.windowingMode)
.isEqualTo(WINDOWING_MODE_UNDEFINED) // inherited FULLSCREEN
- // Remove wallpaper operation
- wct.hierarchyOps.any { hop ->
- hop.type == HIERARCHY_OP_TYPE_REMOVE_TASK && hop.container == wallpaperToken.asBinder()
- }
+ // Moves wallpaper activity to back when leaving desktop
+ wct.assertReorder(wallpaperToken, toTop = false)
}
@Test
@@ -4420,7 +4779,6 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
validDragArea = Rect(0, 50, 2000, 2000),
dragStartBounds = Rect(),
motionEvent,
- desktopWindowDecoration,
)
val rectAfterEnd = Rect(100, 50, 500, 1150)
verify(transitions)
@@ -4458,7 +4816,6 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
validDragArea = Rect(0, 50, 2000, 2000),
dragStartBounds = Rect(),
motionEvent,
- desktopWindowDecoration,
)
verify(transitions)
@@ -4498,7 +4855,6 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
validDragArea = Rect(0, 50, 2000, 2000),
dragStartBounds = Rect(),
motionEvent,
- desktopWindowDecoration,
)
verify(transitions)
@@ -4539,7 +4895,6 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
validDragArea = Rect(0, 50, 2000, 2000),
dragStartBounds = Rect(),
motionEvent,
- desktopWindowDecoration,
)
// Assert the task exits desktop mode
@@ -4577,7 +4932,6 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
validDragArea = Rect(0, 50, 2000, 2000),
dragStartBounds = Rect(),
motionEvent,
- desktopWindowDecoration,
)
// Assert bounds set to stable bounds
@@ -4633,7 +4987,6 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
validDragArea = Rect(0, 50, 2000, 2000),
dragStartBounds = Rect(),
motionEvent,
- desktopWindowDecoration,
)
// Assert that task is NOT updated via WCT
@@ -5053,7 +5406,6 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
SnapPosition.LEFT,
ResizeTrigger.SNAP_LEFT_MENU,
InputMethod.TOUCH,
- desktopWindowDecoration,
)
// Assert bounds set to stable bounds
val wct = getLatestToggleResizeDesktopTaskWct(currentDragBounds)
@@ -5099,7 +5451,6 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
SnapPosition.LEFT,
ResizeTrigger.SNAP_LEFT_MENU,
InputMethod.TOUCH,
- desktopWindowDecoration,
)
// Assert that task is NOT updated via WCT
verify(toggleResizeDesktopTaskTransitionHandler, never()).startTransition(any(), any())
@@ -5143,7 +5494,6 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
currentDragBounds,
preDragBounds,
motionEvent,
- desktopWindowDecoration,
)
val wct = getLatestToggleResizeDesktopTaskWct(currentDragBounds)
assertThat(findBoundsChange(wct, task)).isEqualTo(expectedBounds)
@@ -5173,7 +5523,6 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
currentDragBounds,
preDragBounds,
motionEvent,
- desktopWindowDecoration,
)
verify(mReturnToDragStartAnimator)
.start(
@@ -5198,7 +5547,6 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
SnapPosition.LEFT,
ResizeTrigger.SNAP_LEFT_MENU,
InputMethod.MOUSE,
- desktopWindowDecoration,
)
// Assert that task is NOT updated via WCT
@@ -5225,7 +5573,6 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
SnapPosition.LEFT,
ResizeTrigger.SNAP_LEFT_MENU,
InputMethod.MOUSE,
- desktopWindowDecoration,
)
// Assert bounds set to half of the stable bounds
@@ -6278,15 +6625,46 @@ private fun WindowContainerTransaction.assertWithoutHop(
assertThat(hierarchyOps.none(predicate)).isTrue()
}
-private fun WindowContainerTransaction.assertReorder(
+private fun WindowContainerTransaction.indexOfReorder(
task: RunningTaskInfo,
toTop: Boolean? = null,
-) {
- assertHop { hop ->
+): Int {
+ val hop = hierarchyOps.singleOrNull(ReorderPredicate(task.token, toTop)) ?: return -1
+ return hierarchyOps.indexOf(hop)
+}
+
+private class ReorderPredicate(val token: WindowContainerToken, val toTop: Boolean? = null) :
+ ((WindowContainerTransaction.HierarchyOp) -> Boolean) {
+ override fun invoke(hop: WindowContainerTransaction.HierarchyOp): Boolean =
hop.type == HIERARCHY_OP_TYPE_REORDER &&
(toTop == null || hop.toTop == toTop) &&
- hop.container == task.token.asBinder()
- }
+ hop.container == token.asBinder()
+}
+
+private class ReparentPredicate(
+ val token: WindowContainerToken,
+ val parentToken: WindowContainerToken,
+ val toTop: Boolean? = null,
+) : ((WindowContainerTransaction.HierarchyOp) -> Boolean) {
+ override fun invoke(hop: WindowContainerTransaction.HierarchyOp): Boolean =
+ hop.isReparent &&
+ (toTop == null || hop.toTop == toTop) &&
+ hop.container == token.asBinder() &&
+ hop.newParent == parentToken.asBinder()
+}
+
+private fun WindowContainerTransaction.assertReorder(
+ task: RunningTaskInfo,
+ toTop: Boolean? = null,
+) {
+ assertReorder(task.token, toTop)
+}
+
+private fun WindowContainerTransaction.assertReorder(
+ token: WindowContainerToken,
+ toTop: Boolean? = null,
+) {
+ assertHop(ReorderPredicate(token, toTop))
}
private fun WindowContainerTransaction.assertReorderAt(
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt
index c29edece5537..dd577f402952 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt
@@ -24,6 +24,7 @@ import android.content.Context
import android.content.Intent
import android.os.Binder
import android.os.IBinder
+import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
import android.view.Display.DEFAULT_DISPLAY
import android.view.WindowManager
@@ -168,7 +169,8 @@ class DesktopTasksTransitionObserverTest {
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION)
- fun backNavigation_withCloseTransitionLastTask_taskMinimized() {
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WALLPAPER_ACTIVITY_FOR_SYSTEM_USER)
+ fun backNavigation_withCloseTransitionLastTask_wallpaperActivityClosed_taskMinimized() {
val task = createTaskInfo(1)
val transition = mock<IBinder>()
whenever(taskRepository.getVisibleTaskCount(any())).thenReturn(1)
@@ -193,6 +195,35 @@ class DesktopTasksTransitionObserverTest {
}
@Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION,
+ Flags.FLAG_ENABLE_DESKTOP_WALLPAPER_ACTIVITY_FOR_SYSTEM_USER,
+ )
+ fun backNavigation_withCloseTransitionLastTask_wallpaperActivityReordered_taskMinimized() {
+ val task = createTaskInfo(1)
+ val transition = mock<IBinder>()
+ whenever(taskRepository.getVisibleTaskCount(any())).thenReturn(1)
+ whenever(taskRepository.isClosingTask(task.taskId)).thenReturn(false)
+ whenever(backAnimationController.latestTriggerBackTask).thenReturn(task.taskId)
+
+ transitionObserver.onTransitionReady(
+ transition = transition,
+ info = createBackNavigationTransition(task, TRANSIT_CLOSE, true, TRANSIT_TO_BACK),
+ startTransaction = mock(),
+ finishTransaction = mock(),
+ )
+
+ verify(taskRepository).minimizeTask(task.displayId, task.taskId)
+ val pendingTransition =
+ DesktopMixedTransitionHandler.PendingMixedTransition.Minimize(
+ transition,
+ task.taskId,
+ isLastTask = true,
+ )
+ verify(mixedHandler).addPendingMixedTransition(pendingTransition)
+ }
+
+ @Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION)
fun backNavigation_nullTaskInfo_taskNotMinimized() {
val task = createTaskInfo(1)
@@ -434,6 +465,7 @@ class DesktopTasksTransitionObserverTest {
task: RunningTaskInfo?,
type: Int = TRANSIT_TO_BACK,
withWallpaper: Boolean = false,
+ wallpaperChangeMode: Int = TRANSIT_CLOSE,
): TransitionInfo {
return TransitionInfo(type, /* flags= */ 0).apply {
addChange(
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt
index ba26d1df94f6..85f6cd36992d 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt
@@ -51,6 +51,7 @@ import org.mockito.ArgumentMatchers.anyInt
import org.mockito.ArgumentMatchers.eq
import org.mockito.Mock
import org.mockito.MockitoSession
+import org.mockito.kotlin.argThat
import org.mockito.kotlin.mock
import org.mockito.kotlin.never
import org.mockito.kotlin.times
@@ -180,10 +181,11 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() {
defaultHandler,
DragToDesktopTransitionHandler.CancelState.CANCEL_BUBBLE_LEFT,
)
- verify(bubbleController).expandStackAndSelectBubble(
- any<RunningTaskInfo>(),
- any<BubbleTransitions.DragData>()
- )
+ verify(bubbleController)
+ .expandStackAndSelectBubble(
+ any<RunningTaskInfo>(),
+ argThat<BubbleTransitions.DragData> { isReleasedOnLeft },
+ )
}
@Test
@@ -192,10 +194,11 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() {
defaultHandler,
DragToDesktopTransitionHandler.CancelState.CANCEL_BUBBLE_RIGHT,
)
- verify(bubbleController).expandStackAndSelectBubble(
- any<RunningTaskInfo>(),
- any<BubbleTransitions.DragData>()
- )
+ verify(bubbleController)
+ .expandStackAndSelectBubble(
+ any<RunningTaskInfo>(),
+ argThat<BubbleTransitions.DragData> { !isReleasedOnLeft },
+ )
}
@Test
@@ -382,10 +385,11 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() {
)
// Verify the request went through bubble controller.
- verify(bubbleController).expandStackAndSelectBubble(
- any<RunningTaskInfo>(),
- any<BubbleTransitions.DragData>()
- )
+ verify(bubbleController)
+ .expandStackAndSelectBubble(
+ any<RunningTaskInfo>(),
+ argThat<BubbleTransitions.DragData> { isReleasedOnLeft },
+ )
}
@Test
@@ -398,10 +402,11 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() {
)
// Verify the request went through bubble controller.
- verify(bubbleController).expandStackAndSelectBubble(
- any<RunningTaskInfo>(),
- any<BubbleTransitions.DragData>()
- )
+ verify(bubbleController)
+ .expandStackAndSelectBubble(
+ any<RunningTaskInfo>(),
+ argThat<BubbleTransitions.DragData> { !isReleasedOnLeft },
+ )
}
@Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/desktopwallpaperactivity/DesktopWallpaperActivityTokenProviderTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/desktopwallpaperactivity/DesktopWallpaperActivityTokenProviderTest.kt
new file mode 100644
index 000000000000..aa4e9aaf248e
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/desktopwallpaperactivity/DesktopWallpaperActivityTokenProviderTest.kt
@@ -0,0 +1,128 @@
+/*
+ * 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.desktopwallpaperactivity
+
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper.RunWithLooper
+import android.view.Display.DEFAULT_DISPLAY
+import androidx.test.filters.SmallTest
+import com.android.wm.shell.MockToken
+import com.android.wm.shell.ShellTestCase
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Test class for [DesktopWallpaperActivityTokenProvider]
+ *
+ * Usage: atest WMShellUnitTests:DesktopWallpaperActivityTokenProviderTest
+ */
+@SmallTest
+@RunWithLooper
+@RunWith(AndroidTestingRunner::class)
+class DesktopWallpaperActivityTokenProviderTest : ShellTestCase() {
+
+ private lateinit var provider: DesktopWallpaperActivityTokenProvider
+ private val DEFAULT_DISPLAY = 0
+ private val SECONDARY_DISPLAY = 1
+
+ @Before
+ fun setUp() {
+ provider = DesktopWallpaperActivityTokenProvider()
+ }
+
+ @Test
+ fun setToken_setsTokenForDisplay() {
+ val token = MockToken().token()
+
+ provider.setToken(token, DEFAULT_DISPLAY)
+
+ assertThat(provider.getToken(DEFAULT_DISPLAY)).isEqualTo(token)
+ }
+
+ @Test
+ fun setToken_overwritesExistingTokenForDisplay() {
+ val token1 = MockToken().token()
+ val token2 = MockToken().token()
+
+ provider.setToken(token1, DEFAULT_DISPLAY)
+ provider.setToken(token2, DEFAULT_DISPLAY)
+
+ assertThat(provider.getToken(DEFAULT_DISPLAY)).isEqualTo(token2)
+ }
+
+ @Test
+ fun getToken_returnsNullForNonExistentDisplay() {
+ assertThat(provider.getToken(SECONDARY_DISPLAY)).isNull()
+ }
+
+ @Test
+ fun removeToken_removesTokenForDisplay() {
+ val token = MockToken().token()
+
+ provider.setToken(token, DEFAULT_DISPLAY)
+ provider.removeToken(DEFAULT_DISPLAY)
+
+ assertThat(provider.getToken(DEFAULT_DISPLAY)).isNull()
+ }
+
+ @Test
+ fun removeToken_withToken_removesTokenForDisplay() {
+ val token = MockToken().token()
+
+ provider.setToken(token, DEFAULT_DISPLAY)
+ provider.removeToken(token)
+
+ assertThat(provider.getToken(DEFAULT_DISPLAY)).isNull()
+ }
+
+ @Test
+ fun removeToken_doesNothingForNonExistentDisplay() {
+ provider.removeToken(SECONDARY_DISPLAY)
+
+ assertThat(provider.getToken(SECONDARY_DISPLAY)).isNull()
+ }
+
+ @Test
+ fun removeToken_withNonExistentToken_doesNothing() {
+ val token1 = MockToken().token()
+ val token2 = MockToken().token()
+
+ provider.setToken(token1, DEFAULT_DISPLAY)
+ provider.removeToken(token2)
+
+ assertThat(provider.getToken(DEFAULT_DISPLAY)).isEqualTo(token1)
+ }
+
+ @Test
+ fun multipleDisplays_tokensAreIndependent() {
+ val token1 = MockToken().token()
+ val token2 = MockToken().token()
+
+ provider.setToken(token1, DEFAULT_DISPLAY)
+ provider.setToken(token2, SECONDARY_DISPLAY)
+
+ assertThat(provider.getToken(DEFAULT_DISPLAY)).isEqualTo(token1)
+ assertThat(provider.getToken(SECONDARY_DISPLAY)).isEqualTo(token2)
+
+ provider.removeToken(DEFAULT_DISPLAY)
+
+ assertThat(provider.getToken(DEFAULT_DISPLAY)).isNull()
+ assertThat(provider.getToken(SECONDARY_DISPLAY)).isEqualTo(token2)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/multidesks/DesksTransitionObserverTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/multidesks/DesksTransitionObserverTest.kt
index 9f09e3f57927..4dcf669f4d25 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/multidesks/DesksTransitionObserverTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/multidesks/DesksTransitionObserverTest.kt
@@ -20,6 +20,7 @@ import android.platform.test.annotations.EnableFlags
import android.platform.test.flag.junit.SetFlagsRule
import android.testing.AndroidTestingRunner
import android.view.Display.DEFAULT_DISPLAY
+import android.view.WindowManager.TRANSIT_CHANGE
import android.view.WindowManager.TRANSIT_CLOSE
import android.view.WindowManager.TRANSIT_TO_FRONT
import android.window.TransitionInfo
@@ -177,4 +178,70 @@ class DesksTransitionObserverTest : ShellTestCase() {
assertThat(repository.getActiveDeskId(DEFAULT_DISPLAY)).isEqualTo(deskId)
assertThat(repository.getActiveTaskIdsInDesk(deskId)).contains(task.taskId)
}
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun onTransitionReady_deactivateDesk_updatesRepository() {
+ val transition = Binder()
+ val deskChange = Change(mock(), mock())
+ whenever(mockDesksOrganizer.isDeskChange(deskChange, deskId = 5)).thenReturn(true)
+ val deactivateTransition = DeskTransition.DeactivateDesk(transition, deskId = 5)
+ repository.addDesk(DEFAULT_DISPLAY, deskId = 5)
+ repository.setActiveDesk(DEFAULT_DISPLAY, deskId = 5)
+
+ observer.addPendingTransition(deactivateTransition)
+ observer.onTransitionReady(
+ transition = transition,
+ info = TransitionInfo(TRANSIT_CHANGE, /* flags= */ 0).apply { addChange(deskChange) },
+ )
+
+ assertThat(repository.getActiveDeskId(DEFAULT_DISPLAY)).isNull()
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun onTransitionReady_deactivateDeskWithExitingTask_updatesRepository() {
+ val transition = Binder()
+ val exitingTask = createFreeformTask(DEFAULT_DISPLAY)
+ val exitingTaskChange = Change(mock(), mock()).apply { taskInfo = exitingTask }
+ whenever(mockDesksOrganizer.getDeskAtEnd(exitingTaskChange)).thenReturn(null)
+ val deactivateTransition = DeskTransition.DeactivateDesk(transition, deskId = 5)
+ repository.addDesk(DEFAULT_DISPLAY, deskId = 5)
+ repository.setActiveDesk(DEFAULT_DISPLAY, deskId = 5)
+ repository.addTaskToDesk(
+ displayId = DEFAULT_DISPLAY,
+ deskId = 5,
+ taskId = exitingTask.taskId,
+ isVisible = true,
+ )
+ assertThat(repository.isActiveTaskInDesk(deskId = 5, taskId = exitingTask.taskId)).isTrue()
+
+ observer.addPendingTransition(deactivateTransition)
+ observer.onTransitionReady(
+ transition = transition,
+ info =
+ TransitionInfo(TRANSIT_CHANGE, /* flags= */ 0).apply {
+ addChange(exitingTaskChange)
+ },
+ )
+
+ assertThat(repository.isActiveTaskInDesk(deskId = 5, taskId = exitingTask.taskId)).isFalse()
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun onTransitionReady_deactivateDeskWithoutVisibleChange_updatesRepository() {
+ val transition = Binder()
+ val deactivateTransition = DeskTransition.DeactivateDesk(transition, deskId = 5)
+ repository.addDesk(DEFAULT_DISPLAY, deskId = 5)
+ repository.setActiveDesk(DEFAULT_DISPLAY, deskId = 5)
+
+ observer.addPendingTransition(deactivateTransition)
+ observer.onTransitionReady(
+ transition = transition,
+ info = TransitionInfo(TRANSIT_CHANGE, /* flags= */ 0),
+ )
+
+ assertThat(repository.getActiveDeskId(DEFAULT_DISPLAY)).isNull()
+ }
}
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
index 4d4b15389eca..8b10ca1a2a70 100644
--- 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
@@ -23,11 +23,13 @@ import android.view.WindowManager.TRANSIT_TO_FRONT
import android.window.TransitionInfo
import android.window.WindowContainerTransaction
import android.window.WindowContainerTransaction.HierarchyOp
+import android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_LAUNCH_ROOT
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.desktopmode.multidesks.RootTaskDesksOrganizer.DeskRoot
import com.android.wm.shell.sysui.ShellCommandHandler
import com.android.wm.shell.sysui.ShellInit
import com.google.common.truth.Truth.assertThat
@@ -104,54 +106,45 @@ class RootTaskDesksOrganizerTest : ShellTestCase() {
@Test
fun testOnTaskVanished_removesRoot() {
- val callback = FakeOnCreateCallback()
- organizer.createDesk(Display.DEFAULT_DISPLAY, callback)
- val freeformRoot = createFreeformTask().apply { parentTaskId = -1 }
- organizer.onTaskAppeared(freeformRoot, SurfaceControl())
+ val desk = createDesk()
- organizer.onTaskVanished(freeformRoot)
+ organizer.onTaskVanished(desk.taskInfo)
- assertThat(organizer.roots.contains(freeformRoot.taskId)).isFalse()
+ assertThat(organizer.roots.contains(desk.deskId)).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 }
+ val desk = createDesk()
+ val child = createFreeformTask().apply { parentTaskId = desk.deskId }
organizer.onTaskAppeared(child, SurfaceControl())
- assertThat(organizer.roots[freeformRoot.taskId].children).contains(child.taskId)
+ assertThat(desk.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 }
+ val desk = createDesk()
+ val child = createFreeformTask().apply { parentTaskId = desk.deskId }
organizer.onTaskAppeared(child, SurfaceControl())
organizer.onTaskVanished(child)
- assertThat(organizer.roots[freeformRoot.taskId].children).doesNotContain(child.taskId)
+ assertThat(desk.children).doesNotContain(child.taskId)
}
@Test
fun testRemoveDesk() {
- organizer.createDesk(Display.DEFAULT_DISPLAY, FakeOnCreateCallback())
- val freeformRoot = createFreeformTask().apply { parentTaskId = -1 }
- organizer.onTaskAppeared(freeformRoot, SurfaceControl())
+ val desk = createDesk()
val wct = WindowContainerTransaction()
- organizer.removeDesk(wct, freeformRoot.taskId)
+ organizer.removeDesk(wct, desk.deskId)
assertThat(
wct.hierarchyOps.any { hop ->
hop.type == HierarchyOp.HIERARCHY_OP_TYPE_REMOVE_ROOT_TASK &&
- hop.container == freeformRoot.token.asBinder()
+ hop.container == desk.taskInfo.token.asBinder()
}
)
.isTrue()
@@ -167,25 +160,23 @@ class RootTaskDesksOrganizerTest : ShellTestCase() {
@Test
fun testActivateDesk() {
- organizer.createDesk(Display.DEFAULT_DISPLAY, FakeOnCreateCallback())
- val freeformRoot = createFreeformTask().apply { parentTaskId = -1 }
- organizer.onTaskAppeared(freeformRoot, SurfaceControl())
+ val desk = createDesk()
val wct = WindowContainerTransaction()
- organizer.activateDesk(wct, freeformRoot.taskId)
+ organizer.activateDesk(wct, desk.deskId)
assertThat(
wct.hierarchyOps.any { hop ->
hop.type == HierarchyOp.HIERARCHY_OP_TYPE_REORDER &&
hop.toTop &&
- hop.container == freeformRoot.token.asBinder()
+ hop.container == desk.taskInfo.token.asBinder()
}
)
.isTrue()
assertThat(
wct.hierarchyOps.any { hop ->
hop.type == HierarchyOp.HIERARCHY_OP_TYPE_SET_LAUNCH_ROOT &&
- hop.container == freeformRoot.token.asBinder()
+ hop.container == desk.taskInfo.token.asBinder()
}
)
.isTrue()
@@ -201,20 +192,18 @@ class RootTaskDesksOrganizerTest : ShellTestCase() {
@Test
fun testMoveTaskToDesk() {
- organizer.createDesk(Display.DEFAULT_DISPLAY, FakeOnCreateCallback())
- val freeformRoot = createFreeformTask().apply { parentTaskId = -1 }
- organizer.onTaskAppeared(freeformRoot, SurfaceControl())
+ val desk = createDesk()
val desktopTask = createFreeformTask().apply { parentTaskId = -1 }
val wct = WindowContainerTransaction()
- organizer.moveTaskToDesk(wct, freeformRoot.taskId, desktopTask)
+ organizer.moveTaskToDesk(wct, desk.deskId, desktopTask)
assertThat(
wct.hierarchyOps.any { hop ->
hop.isReparent &&
hop.toTop &&
hop.container == desktopTask.token.asBinder() &&
- hop.newParent == freeformRoot.token.asBinder()
+ hop.newParent == desk.taskInfo.token.asBinder()
}
)
.isTrue()
@@ -240,17 +229,15 @@ class RootTaskDesksOrganizerTest : ShellTestCase() {
@Test
fun testGetDeskAtEnd() {
- organizer.createDesk(Display.DEFAULT_DISPLAY, FakeOnCreateCallback())
- val freeformRoot = createFreeformTask().apply { parentTaskId = -1 }
- organizer.onTaskAppeared(freeformRoot, SurfaceControl())
+ val desk = createDesk()
- val task = createFreeformTask().apply { parentTaskId = freeformRoot.taskId }
+ val task = createFreeformTask().apply { parentTaskId = desk.deskId }
val endDesk =
organizer.getDeskAtEnd(
TransitionInfo.Change(task.token, SurfaceControl()).apply { taskInfo = task }
)
- assertThat(endDesk).isEqualTo(freeformRoot.taskId)
+ assertThat(endDesk).isEqualTo(desk.deskId)
}
@Test
@@ -273,6 +260,47 @@ class RootTaskDesksOrganizerTest : ShellTestCase() {
assertThat(isActive).isTrue()
}
+ @Test
+ fun deactivateDesk_clearsLaunchRoot() {
+ val wct = WindowContainerTransaction()
+ val desk = createDesk()
+ organizer.activateDesk(wct, desk.deskId)
+
+ organizer.deactivateDesk(wct, desk.deskId)
+
+ assertThat(
+ wct.hierarchyOps.any { hop ->
+ hop.type == HIERARCHY_OP_TYPE_SET_LAUNCH_ROOT &&
+ hop.container == desk.taskInfo.token.asBinder() &&
+ hop.windowingModes == null &&
+ hop.activityTypes == null
+ }
+ )
+ .isTrue()
+ }
+
+ @Test
+ fun isDeskChange() {
+ val desk = createDesk()
+
+ assertThat(
+ organizer.isDeskChange(
+ TransitionInfo.Change(desk.taskInfo.token, desk.leash).apply {
+ taskInfo = desk.taskInfo
+ },
+ desk.deskId,
+ )
+ )
+ .isTrue()
+ }
+
+ private fun createDesk(): DeskRoot {
+ organizer.createDesk(Display.DEFAULT_DISPLAY, FakeOnCreateCallback())
+ val freeformRoot = createFreeformTask().apply { parentTaskId = -1 }
+ organizer.onTaskAppeared(freeformRoot, SurfaceControl())
+ return organizer.roots[freeformRoot.taskId]
+ }
+
private class FakeOnCreateCallback : DesksOrganizer.OnCreateCallback {
var deskId: Int? = null
val created: Boolean
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipSchedulerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipSchedulerTest.java
index 8e0381e4f933..0c1952910d1a 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipSchedulerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipSchedulerTest.java
@@ -19,6 +19,7 @@ package com.android.wm.shell.pip2.phone;
import static org.junit.Assert.assertNotNull;
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.Mockito.never;
import static org.mockito.Mockito.when;
@@ -26,6 +27,7 @@ import static org.mockito.kotlin.MatchersKt.eq;
import static org.mockito.kotlin.VerificationKt.times;
import static org.mockito.kotlin.VerificationKt.verify;
+import android.app.ActivityManager;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Matrix;
@@ -44,15 +46,19 @@ import com.android.wm.shell.common.pip.PipDesktopState;
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.splitscreen.SplitScreenController;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
+import org.mockito.ArgumentMatchers;
import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.util.Optional;
+
/**
* Unit test against {@link PipScheduler}
*/
@@ -77,6 +83,8 @@ public class PipSchedulerTest {
@Mock private PipSurfaceTransactionHelper.SurfaceControlTransactionFactory mMockFactory;
@Mock private SurfaceControl.Transaction mMockTransaction;
@Mock private PipAlphaAnimator mMockAlphaAnimator;
+ @Mock private SplitScreenController mMockSplitScreenController;
+
@Captor private ArgumentCaptor<Runnable> mRunnableArgumentCaptor;
@Captor private ArgumentCaptor<WindowContainerTransaction> mWctArgumentCaptor;
@@ -93,7 +101,8 @@ public class PipSchedulerTest {
.thenReturn(mMockTransaction);
mPipScheduler = new PipScheduler(mMockContext, mMockPipBoundsState, mMockMainExecutor,
- mMockPipTransitionState, mMockPipDesktopState);
+ mMockPipTransitionState, Optional.of(mMockSplitScreenController),
+ mMockPipDesktopState);
mPipScheduler.setPipTransitionController(mMockPipTransitionController);
mPipScheduler.setSurfaceControlTransactionFactory(mMockFactory);
mPipScheduler.setPipAlphaAnimatorSupplier((context, leash, startTx, finishTx, direction) ->
@@ -119,12 +128,18 @@ public class PipSchedulerTest {
assertNotNull(mRunnableArgumentCaptor.getValue());
mRunnableArgumentCaptor.getValue().run();
- verify(mMockPipTransitionController, never()).startExpandTransition(any());
+ verify(mMockPipTransitionController, never()).startExpandTransition(any(), anyBoolean());
}
@Test
- public void scheduleExitPipViaExpand_exitTransitionCalled() {
+ public void scheduleExitPipViaExpand_noSplit_expandTransitionCalled() {
setMockPipTaskToken();
+ ActivityManager.RunningTaskInfo pipTaskInfo = getTaskInfoWithLastParentBeforePip(1);
+ when(mMockPipTransitionState.getPipTaskInfo()).thenReturn(pipTaskInfo);
+
+ // Make sure task with the id = 1 isn't in split-screen.
+ when(mMockSplitScreenController.isTaskInSplitScreen(
+ ArgumentMatchers.eq(1))).thenReturn(false);
mPipScheduler.scheduleExitPipViaExpand();
@@ -132,7 +147,29 @@ public class PipSchedulerTest {
assertNotNull(mRunnableArgumentCaptor.getValue());
mRunnableArgumentCaptor.getValue().run();
- verify(mMockPipTransitionController, times(1)).startExpandTransition(any());
+ verify(mMockPipTransitionController, times(1)).startExpandTransition(any(), anyBoolean());
+ }
+
+ @Test
+ public void scheduleExitPipViaExpand_lastParentInSplit_prepareSplitAndExpand() {
+ setMockPipTaskToken();
+ ActivityManager.RunningTaskInfo pipTaskInfo = getTaskInfoWithLastParentBeforePip(1);
+ when(mMockPipTransitionState.getPipTaskInfo()).thenReturn(pipTaskInfo);
+
+ // Make sure task with the id = 1 is in split-screen.
+ when(mMockSplitScreenController.isTaskInSplitScreen(
+ ArgumentMatchers.eq(1))).thenReturn(true);
+
+ mPipScheduler.scheduleExitPipViaExpand();
+
+ verify(mMockMainExecutor, times(1)).execute(mRunnableArgumentCaptor.capture());
+ assertNotNull(mRunnableArgumentCaptor.getValue());
+ mRunnableArgumentCaptor.getValue().run();
+
+ // We need to both prepare the split screen with the last parent and start expanding.
+ verify(mMockSplitScreenController,
+ times(1)).prepareEnterSplitScreen(any(), any(), anyInt());
+ verify(mMockPipTransitionController, times(1)).startExpandTransition(any(), anyBoolean());
}
@Test
@@ -259,4 +296,10 @@ public class PipSchedulerTest {
private void setMockPipTaskToken() {
when(mMockPipTransitionState.getPipTaskToken()).thenReturn(mMockPipTaskToken);
}
+
+ private ActivityManager.RunningTaskInfo getTaskInfoWithLastParentBeforePip(int lastParentId) {
+ final ActivityManager.RunningTaskInfo taskInfo = new ActivityManager.RunningTaskInfo();
+ taskInfo.lastParentTaskIdBeforePip = lastParentId;
+ return taskInfo;
+ }
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipTouchStateTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipTouchStateTest.java
new file mode 100644
index 000000000000..2e389b7dd151
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipTouchStateTest.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.pip2.phone;
+
+import static android.view.MotionEvent.ACTION_BUTTON_PRESS;
+import static android.view.MotionEvent.ACTION_DOWN;
+import static android.view.MotionEvent.ACTION_MOVE;
+import static android.view.MotionEvent.ACTION_UP;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.os.SystemClock;
+import android.testing.AndroidTestingRunner;
+import android.view.MotionEvent;
+import android.view.ViewConfiguration;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.TestShellExecutor;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.CountDownLatch;
+
+@RunWith(AndroidTestingRunner.class)
+@SmallTest
+public class PipTouchStateTest extends ShellTestCase {
+
+ private PipTouchState mTouchState;
+ private CountDownLatch mDoubleTapCallbackTriggeredLatch;
+ private CountDownLatch mHoverExitCallbackTriggeredLatch;
+ private TestShellExecutor mMainExecutor;
+
+ @Before
+ public void setUp() throws Exception {
+ mMainExecutor = new TestShellExecutor();
+ mDoubleTapCallbackTriggeredLatch = new CountDownLatch(1);
+ mHoverExitCallbackTriggeredLatch = new CountDownLatch(1);
+ mTouchState = new PipTouchState(ViewConfiguration.get(getContext()),
+ mDoubleTapCallbackTriggeredLatch::countDown,
+ mHoverExitCallbackTriggeredLatch::countDown,
+ mMainExecutor);
+ assertFalse(mTouchState.isDoubleTap());
+ assertFalse(mTouchState.isWaitingForDoubleTap());
+ }
+
+ @Test
+ public void testDoubleTapLongSingleTap_notDoubleTapAndNotWaiting() {
+ final long currentTime = SystemClock.uptimeMillis();
+
+ mTouchState.onTouchEvent(createMotionEvent(ACTION_DOWN, currentTime, 0, 0));
+ mTouchState.onTouchEvent(createMotionEvent(ACTION_UP,
+ currentTime + PipTouchState.DOUBLE_TAP_TIMEOUT + 10, 0, 0));
+ assertFalse(mTouchState.isDoubleTap());
+ assertFalse(mTouchState.isWaitingForDoubleTap());
+ assertTrue(mTouchState.getDoubleTapTimeoutCallbackDelay() == -1);
+ }
+
+ @Test
+ public void testDoubleTapTimeout_timeoutCallbackCalled() throws Exception {
+ final long currentTime = SystemClock.uptimeMillis();
+
+ mTouchState.onTouchEvent(createMotionEvent(ACTION_DOWN, currentTime, 0, 0));
+ mTouchState.onTouchEvent(createMotionEvent(ACTION_UP,
+ currentTime + PipTouchState.DOUBLE_TAP_TIMEOUT - 10, 0, 0));
+ assertFalse(mTouchState.isDoubleTap());
+ assertTrue(mTouchState.isWaitingForDoubleTap());
+
+ assertTrue(mTouchState.getDoubleTapTimeoutCallbackDelay() == 10);
+ mTouchState.scheduleDoubleTapTimeoutCallback();
+
+ mMainExecutor.flushAll();
+ assertTrue(mDoubleTapCallbackTriggeredLatch.getCount() == 0);
+ }
+
+ @Test
+ public void testDoubleTapDrag_doubleTapCanceled() {
+ final long currentTime = SystemClock.uptimeMillis();
+
+ mTouchState.onTouchEvent(createMotionEvent(ACTION_DOWN, currentTime, 0, 0));
+ mTouchState.onTouchEvent(createMotionEvent(ACTION_MOVE, currentTime + 10, 500, 500));
+ mTouchState.onTouchEvent(createMotionEvent(ACTION_UP, currentTime + 20, 500, 500));
+ assertTrue(mTouchState.isDragging());
+ assertFalse(mTouchState.isDoubleTap());
+ assertFalse(mTouchState.isWaitingForDoubleTap());
+ assertTrue(mTouchState.getDoubleTapTimeoutCallbackDelay() == -1);
+ }
+
+ @Test
+ public void testDoubleTap_doubleTapRegistered() {
+ final long currentTime = SystemClock.uptimeMillis();
+
+ mTouchState.onTouchEvent(createMotionEvent(ACTION_DOWN, currentTime, 0, 0));
+ mTouchState.onTouchEvent(createMotionEvent(ACTION_UP, currentTime + 10, 0, 0));
+ mTouchState.onTouchEvent(createMotionEvent(ACTION_DOWN,
+ currentTime + PipTouchState.DOUBLE_TAP_TIMEOUT - 20, 0, 0));
+ mTouchState.onTouchEvent(createMotionEvent(ACTION_UP,
+ currentTime + PipTouchState.DOUBLE_TAP_TIMEOUT - 10, 0, 0));
+ assertTrue(mTouchState.isDoubleTap());
+ assertFalse(mTouchState.isWaitingForDoubleTap());
+ assertTrue(mTouchState.getDoubleTapTimeoutCallbackDelay() == -1);
+ }
+
+ @Test
+ public void testHoverExitTimeout_timeoutCallbackCalled() throws Exception {
+ mTouchState.scheduleHoverExitTimeoutCallback();
+ mMainExecutor.flushAll();
+ assertTrue(mHoverExitCallbackTriggeredLatch.getCount() == 0);
+ }
+
+ @Test
+ public void testHoverExitTimeout_timeoutCallbackNotCalled() throws Exception {
+ mTouchState.scheduleHoverExitTimeoutCallback();
+ assertTrue(mHoverExitCallbackTriggeredLatch.getCount() == 1);
+ }
+
+ @Test
+ public void testHoverExitTimeout_timeoutCallbackNotCalled_ifButtonPress() throws Exception {
+ mTouchState.scheduleHoverExitTimeoutCallback();
+ mTouchState.onTouchEvent(createMotionEvent(ACTION_BUTTON_PRESS, SystemClock.uptimeMillis(),
+ 0, 0));
+ mMainExecutor.flushAll();
+ assertTrue(mHoverExitCallbackTriggeredLatch.getCount() == 1);
+ }
+
+ private MotionEvent createMotionEvent(int action, long eventTime, float x, float y) {
+ return MotionEvent.obtain(0, eventTime, action, x, y, 0);
+ }
+
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/transition/PipExpandHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/transition/PipExpandHandlerTest.java
new file mode 100644
index 000000000000..2a22842eda1a
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/transition/PipExpandHandlerTest.java
@@ -0,0 +1,188 @@
+/*
+ * 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.pip2.phone.transition;
+
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.view.WindowManager.TRANSIT_CHANGE;
+
+import static com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_PIP;
+import static com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_PIP_TO_SPLIT;
+
+import static org.junit.Assert.assertNull;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+import static org.mockito.kotlin.VerificationKt.times;
+import static org.mockito.kotlin.VerificationKt.verify;
+
+import android.annotation.Nullable;
+import android.app.ActivityManager;
+import android.app.PictureInPictureParams;
+import android.app.WindowConfiguration;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Rect;
+import android.os.IBinder;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.view.Surface;
+import android.view.SurfaceControl;
+import android.view.WindowManager;
+import android.window.TransitionInfo;
+import android.window.TransitionRequestInfo;
+import android.window.WindowContainerToken;
+import android.window.WindowContainerTransaction;
+
+import androidx.test.filters.SmallTest;
+
+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.pip2.animation.PipExpandAnimator;
+import com.android.wm.shell.pip2.phone.PipTransitionState;
+import com.android.wm.shell.splitscreen.SplitScreenController;
+import com.android.wm.shell.transition.TransitionInfoBuilder;
+import com.android.wm.shell.util.StubTransaction;
+
+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;
+
+/**
+ * Unit test against {@link PipExpandHandler}
+ */
+
+@SmallTest
+@TestableLooper.RunWithLooper
+@RunWith(AndroidTestingRunner.class)
+public class PipExpandHandlerTest {
+ @Mock private Context mMockContext;
+ @Mock private PipBoundsState mMockPipBoundsState;
+ @Mock private PipBoundsAlgorithm mMockPipBoundsAlgorithm;
+ @Mock private PipTransitionState mMockPipTransitionState;
+ @Mock private PipDisplayLayoutState mMockPipDisplayLayoutState;
+ @Mock private SplitScreenController mMockSplitScreenController;
+
+ @Mock private IBinder mMockTransitionToken;
+ @Mock private TransitionRequestInfo mMockRequestInfo;
+ @Mock private StubTransaction mStartT;
+ @Mock private StubTransaction mFinishT;
+ @Mock private SurfaceControl mPipLeash;
+
+ @Mock private PipExpandAnimator mMockPipExpandAnimator;
+
+ @Surface.Rotation
+ private static final int DISPLAY_ROTATION = Surface.ROTATION_0;
+
+ private static final float SNAP_FRACTION = 1.5f;
+ private static final Rect PIP_BOUNDS = new Rect(0, 0, 100, 100);
+ private static final Rect DISPLAY_BOUNDS = new Rect(0, 0, 1000, 1000);
+ private static final Rect RIGHT_HALF_DISPLAY_BOUNDS = new Rect(500, 0, 1000, 1000);
+
+ private PipExpandHandler mPipExpandHandler;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ when(mMockPipBoundsState.getBounds()).thenReturn(PIP_BOUNDS);
+ when(mMockPipBoundsAlgorithm.getSnapFraction(eq(PIP_BOUNDS))).thenReturn(SNAP_FRACTION);
+ when(mMockPipDisplayLayoutState.getRotation()).thenReturn(DISPLAY_ROTATION);
+
+ mPipExpandHandler = new PipExpandHandler(mMockContext, mMockPipBoundsState,
+ mMockPipBoundsAlgorithm, mMockPipTransitionState, mMockPipDisplayLayoutState,
+ Optional.of(mMockSplitScreenController));
+ mPipExpandHandler.setPipExpandAnimatorSupplier((context, leash, startTransaction,
+ finishTransaction, baseBounds, startBounds, endBounds,
+ sourceRectHint, rotation) -> mMockPipExpandAnimator);
+ }
+
+ @Test
+ public void handleRequest_returnNull() {
+ // All expand from PiP transitions are started in Shell, so handleRequest shouldn't be
+ // returning any non-null WCT
+ WindowContainerTransaction wct = mPipExpandHandler.handleRequest(
+ mMockTransitionToken, mMockRequestInfo);
+ assertNull(wct);
+ }
+
+ @Test
+ public void startAnimation_transitExit_startExpandAnimator() {
+ final ActivityManager.RunningTaskInfo pipTaskInfo = createPipTaskInfo(
+ 1, WINDOWING_MODE_FULLSCREEN, new PictureInPictureParams.Builder().build());
+
+ final TransitionInfo info = getExpandFromPipTransitionInfo(
+ TRANSIT_EXIT_PIP, pipTaskInfo, null /* lastParent */, false /* toSplit */);
+ final WindowContainerToken pipToken = pipTaskInfo.getToken();
+ when(mMockPipTransitionState.getPipTaskToken()).thenReturn(pipToken);
+
+ mPipExpandHandler.startAnimation(mMockTransitionToken, info, mStartT, mFinishT,
+ (wct) -> {});
+
+ verify(mMockPipExpandAnimator, times(1)).start();
+ verify(mMockPipBoundsState, times(1)).saveReentryState(SNAP_FRACTION);
+ }
+
+ @Test
+ public void startAnimation_transitExitToSplit_startExpandAnimator() {
+ // The task info of the task that was pinned while we were in PiP.
+ final WindowContainerToken pipToken = createPipTaskInfo(1, WINDOWING_MODE_FULLSCREEN,
+ new PictureInPictureParams.Builder().build()).getToken();
+ when(mMockPipTransitionState.getPipTaskToken()).thenReturn(pipToken);
+
+ // Change representing the ActivityRecord we are animating in the multi-activity PiP case;
+ // make sure change's taskInfo=null as this is an activity, but let lastParent be PiP token.
+ final TransitionInfo info = getExpandFromPipTransitionInfo(
+ TRANSIT_EXIT_PIP_TO_SPLIT, null /* taskInfo */, pipToken, true /* toSplit */);
+
+ mPipExpandHandler.startAnimation(mMockTransitionToken, info, mStartT, mFinishT,
+ (wct) -> {});
+
+ verify(mMockSplitScreenController, times(1)).finishEnterSplitScreen(eq(mFinishT));
+ verify(mMockPipExpandAnimator, times(1)).start();
+ verify(mMockPipBoundsState, times(1)).saveReentryState(SNAP_FRACTION);
+ }
+
+ private TransitionInfo getExpandFromPipTransitionInfo(@WindowManager.TransitionType int type,
+ @Nullable ActivityManager.RunningTaskInfo pipTaskInfo,
+ @Nullable WindowContainerToken lastParent, boolean toSplit) {
+ final TransitionInfo info = new TransitionInfoBuilder(type)
+ .addChange(TRANSIT_CHANGE, pipTaskInfo).build();
+ final TransitionInfo.Change pipChange = info.getChanges().getFirst();
+ pipChange.setRotation(DISPLAY_ROTATION,
+ WindowConfiguration.ROTATION_UNDEFINED);
+ pipChange.setStartAbsBounds(PIP_BOUNDS);
+ pipChange.setEndAbsBounds(toSplit ? RIGHT_HALF_DISPLAY_BOUNDS : DISPLAY_BOUNDS);
+ pipChange.setLeash(mPipLeash);
+ pipChange.setLastParent(lastParent);
+ return info;
+ }
+
+ private static ActivityManager.RunningTaskInfo createPipTaskInfo(int taskId,
+ int windowingMode, PictureInPictureParams params) {
+ ActivityManager.RunningTaskInfo taskInfo = new ActivityManager.RunningTaskInfo();
+ taskInfo.taskId = taskId;
+ taskInfo.configuration.windowConfiguration.setWindowingMode(windowingMode);
+ taskInfo.token = mock(WindowContainerToken.class);
+ taskInfo.baseIntent = mock(Intent.class);
+ taskInfo.pictureInPictureParams = params;
+ return taskInfo;
+ }
+}
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 5028479b6ace..a546b3ea7d8f 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
@@ -107,6 +107,7 @@ import java.util.function.Consumer;
@RunWith(AndroidJUnit4.class)
@SmallTest
public class RecentTasksControllerTest extends ShellTestCase {
+ private static final String SYSTEM_UI_PACKAGE_NAME = "com.android.systemui";
@Mock
private Context mContext;
@@ -582,6 +583,19 @@ public class RecentTasksControllerTest extends ShellTestCase {
@Test
@EnableFlags({Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE,
Flags.FLAG_ENABLE_DESKTOP_WINDOWING_TASKBAR_RUNNING_APPS})
+ public void onTaskAdded_orDesktopWallpaperActivity_doesNotTriggerOnRunningTaskAppeared()
+ throws Exception {
+ RunningTaskInfo taskInfo = makeDesktopWallpaperActivityTaskInfo(/* taskId= */10);
+ mRecentTasksControllerReal.registerRecentTasksListener(mRecentTasksListener);
+
+ mRecentTasksControllerReal.onTaskAdded(taskInfo);
+
+ verify(mRecentTasksListener, never()).onRunningTaskAppeared(any());
+ }
+
+ @Test
+ @EnableFlags({Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE,
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_TASKBAR_RUNNING_APPS})
public void taskWindowingModeChanged_desktopRunningAppsEnabled_triggersOnRunningTaskChanged()
throws Exception {
mRecentTasksControllerReal.registerRecentTasksListener(mRecentTasksListener);
@@ -593,6 +607,19 @@ public class RecentTasksControllerTest extends ShellTestCase {
}
@Test
+ @EnableFlags({Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE,
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_TASKBAR_RUNNING_APPS})
+ public void taskInfoChanged_forDesktopWallpaperActivity_doesNotTriggerOnRunningTaskChanged()
+ throws Exception {
+ RunningTaskInfo taskInfo = makeDesktopWallpaperActivityTaskInfo(/* taskId= */10);
+ mRecentTasksControllerReal.registerRecentTasksListener(mRecentTasksListener);
+
+ mRecentTasksControllerReal.onTaskRunningInfoChanged(taskInfo);
+
+ verify(mRecentTasksListener, never()).onRunningTaskChanged(any());
+ }
+
+ @Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
@DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_TASKBAR_RUNNING_APPS)
public void
@@ -619,6 +646,20 @@ public class RecentTasksControllerTest extends ShellTestCase {
verify(mRecentTasksListener).onRunningTaskVanished(taskInfo);
}
+
+ @Test
+ @EnableFlags({Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE,
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_TASKBAR_RUNNING_APPS})
+ public void onTaskRemoved_forDesktopWallpaperActivity_doesNotTriggerOnRunningTaskVanished()
+ throws Exception {
+ RunningTaskInfo taskInfo = makeDesktopWallpaperActivityTaskInfo(/* taskId= */10);
+ mRecentTasksControllerReal.registerRecentTasksListener(mRecentTasksListener);
+
+ mRecentTasksControllerReal.onTaskRemoved(taskInfo);
+
+ verify(mRecentTasksListener, never()).onRunningTaskVanished(any());
+ }
+
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
@DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_TASKBAR_RUNNING_APPS)
@@ -659,6 +700,18 @@ public class RecentTasksControllerTest extends ShellTestCase {
}
@Test
+ @EnableFlags(Flags.FLAG_ENABLE_TASK_STACK_OBSERVER_IN_SHELL)
+ public void onDesktopWallpaperActivityMovedToFront_doesNotTriggerOnTaskMovedToFront()
+ throws Exception {
+ RunningTaskInfo taskInfo = makeDesktopWallpaperActivityTaskInfo(/* taskId= */10);
+ mRecentTasksControllerReal.registerRecentTasksListener(mRecentTasksListener);
+
+ mRecentTasksControllerReal.onTaskMovedToFrontThroughTransition(taskInfo);
+
+ verify(mRecentTasksListener, never()).onTaskMovedToFront(any());
+ }
+
+ @Test
public void getNullSplitBoundsNonSplitTask() {
SplitBounds sb = mRecentTasksController.getSplitBoundsForTaskId(3);
assertNull("splitBounds should be null for non-split task", sb);
@@ -829,16 +882,25 @@ public class RecentTasksControllerTest extends ShellTestCase {
* Helper to create a running task with a given task id.
*/
private RunningTaskInfo makeRunningTaskInfo(int taskId) {
+ return makeRunningTaskInfo(taskId, new ComponentName("com." + taskId, "Activity" + taskId));
+ }
+
+ private RunningTaskInfo makeRunningTaskInfo(int taskId, ComponentName intentComponent) {
RunningTaskInfo info = new RunningTaskInfo();
info.taskId = taskId;
info.realActivity = new ComponentName("testPackage", "testClass");
Intent intent = new Intent();
- intent.setComponent(new ComponentName("com." + taskId, "Activity" + taskId));
+ intent.setComponent(intentComponent);
info.baseIntent = intent;
info.lastNonFullscreenBounds = new Rect();
return info;
}
+ private RunningTaskInfo makeDesktopWallpaperActivityTaskInfo(int taskId) {
+ return makeRunningTaskInfo(taskId, new ComponentName(SYSTEM_UI_PACKAGE_NAME,
+ DesktopWallpaperActivity.class.getName()));
+ }
+
/**
* Helper to set the raw task list on the controller.
*/
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/bubbles/DropTargetManagerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/bubbles/DropTargetManagerTest.kt
index efb91c5fbfda..180a6915b45f 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/bubbles/DropTargetManagerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/bubbles/DropTargetManagerTest.kt
@@ -16,23 +16,33 @@
package com.android.wm.shell.shared.bubbles
+import android.content.Context
import android.graphics.Rect
+import android.view.View
+import android.widget.FrameLayout
+import androidx.core.animation.AnimatorTestRule
+import androidx.test.core.app.ApplicationProvider.getApplicationContext
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import androidx.test.platform.app.InstrumentationRegistry
import com.google.common.truth.Truth.assertThat
+import kotlin.test.assertFails
import org.junit.Before
+import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
-import kotlin.test.assertFails
/** Unit tests for [DropTargetManager]. */
@SmallTest
@RunWith(AndroidJUnit4::class)
class DropTargetManagerTest {
+ @get:Rule val animatorTestRule = AnimatorTestRule()
+
+ private val context = getApplicationContext<Context>()
private lateinit var dropTargetManager: DropTargetManager
private lateinit var dragZoneChangedListener: FakeDragZoneChangedListener
- private val dropTarget = Rect(0, 0, 0, 0)
+ private lateinit var container: FrameLayout
// create 3 drop zones that are horizontally next to each other
// -------------------------------------------------
@@ -43,15 +53,20 @@ class DropTargetManagerTest {
// | | | |
// -------------------------------------------------
private val bubbleLeftDragZone =
- DragZone.Bubble.Left(bounds = Rect(0, 0, 100, 100), dropTarget = dropTarget)
+ DragZone.Bubble.Left(bounds = Rect(0, 0, 100, 100), dropTarget = Rect(0, 0, 50, 200))
private val dismissDragZone = DragZone.Dismiss(bounds = Rect(100, 0, 200, 100))
private val bubbleRightDragZone =
- DragZone.Bubble.Right(bounds = Rect(200, 0, 300, 100), dropTarget = dropTarget)
+ DragZone.Bubble.Right(bounds = Rect(200, 0, 300, 100), dropTarget = Rect(200, 0, 280, 150))
+
+ private val dropTargetView: View
+ get() = container.getChildAt(0)
@Before
fun setUp() {
+ container = FrameLayout(context)
dragZoneChangedListener = FakeDragZoneChangedListener()
- dropTargetManager = DropTargetManager(isLayoutRtl = false, dragZoneChangedListener)
+ dropTargetManager =
+ DropTargetManager(context, container, isLayoutRtl = false, dragZoneChangedListener)
}
@Test
@@ -79,17 +94,21 @@ class DropTargetManagerTest {
DraggedObject.Bubble(BubbleBarLocation.LEFT),
listOf(bubbleLeftDragZone, bubbleRightDragZone, dismissDragZone)
)
- dropTargetManager.onDragUpdated(
- bubbleRightDragZone.bounds.centerX(),
- bubbleRightDragZone.bounds.centerY()
- )
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {
+ dropTargetManager.onDragUpdated(
+ bubbleRightDragZone.bounds.centerX(),
+ bubbleRightDragZone.bounds.centerY()
+ )
+ }
assertThat(dragZoneChangedListener.fromDragZone).isEqualTo(bubbleLeftDragZone)
assertThat(dragZoneChangedListener.toDragZone).isEqualTo(bubbleRightDragZone)
- dropTargetManager.onDragUpdated(
- dismissDragZone.bounds.centerX(),
- dismissDragZone.bounds.centerY()
- )
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {
+ dropTargetManager.onDragUpdated(
+ dismissDragZone.bounds.centerX(),
+ dismissDragZone.bounds.centerY()
+ )
+ }
assertThat(dragZoneChangedListener.fromDragZone).isEqualTo(bubbleRightDragZone)
assertThat(dragZoneChangedListener.toDragZone).isEqualTo(dismissDragZone)
}
@@ -100,10 +119,12 @@ class DropTargetManagerTest {
DraggedObject.Bubble(BubbleBarLocation.LEFT),
listOf(bubbleLeftDragZone, bubbleRightDragZone, dismissDragZone)
)
- dropTargetManager.onDragUpdated(
- bubbleLeftDragZone.bounds.centerX(),
- bubbleLeftDragZone.bounds.centerY()
- )
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {
+ dropTargetManager.onDragUpdated(
+ bubbleLeftDragZone.bounds.centerX(),
+ bubbleLeftDragZone.bounds.centerY()
+ )
+ }
assertThat(dragZoneChangedListener.fromDragZone).isNull()
assertThat(dragZoneChangedListener.toDragZone).isNull()
}
@@ -118,7 +139,9 @@ class DropTargetManagerTest {
val pointY = 200
assertThat(bubbleLeftDragZone.contains(pointX, pointY)).isFalse()
assertThat(bubbleRightDragZone.contains(pointX, pointY)).isFalse()
- dropTargetManager.onDragUpdated(pointX, pointY)
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {
+ dropTargetManager.onDragUpdated(pointX, pointY)
+ }
assertThat(dragZoneChangedListener.fromDragZone).isNull()
assertThat(dragZoneChangedListener.toDragZone).isNull()
}
@@ -135,27 +158,30 @@ class DropTargetManagerTest {
// drag to a point that is within both the bubble right zone and split zone
val (pointX, pointY) =
- Pair(
- bubbleRightDragZone.bounds.centerX(),
- bubbleRightDragZone.bounds.centerY()
- )
+ Pair(bubbleRightDragZone.bounds.centerX(), bubbleRightDragZone.bounds.centerY())
assertThat(splitDragZone.contains(pointX, pointY)).isTrue()
- dropTargetManager.onDragUpdated(pointX, pointY)
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {
+ dropTargetManager.onDragUpdated(pointX, pointY)
+ }
// verify we dragged to the bubble right zone because that has higher priority than split
assertThat(dragZoneChangedListener.fromDragZone).isEqualTo(bubbleLeftDragZone)
assertThat(dragZoneChangedListener.toDragZone).isEqualTo(bubbleRightDragZone)
- dropTargetManager.onDragUpdated(
- bubbleRightDragZone.bounds.centerX(),
- 150 // below the bubble and dismiss drag zones but within split
- )
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {
+ dropTargetManager.onDragUpdated(
+ bubbleRightDragZone.bounds.centerX(),
+ 150 // below the bubble and dismiss drag zones but within split
+ )
+ }
assertThat(dragZoneChangedListener.fromDragZone).isEqualTo(bubbleRightDragZone)
assertThat(dragZoneChangedListener.toDragZone).isEqualTo(splitDragZone)
val (dismissPointX, dismissPointY) =
Pair(dismissDragZone.bounds.centerX(), dismissDragZone.bounds.centerY())
assertThat(splitDragZone.contains(dismissPointX, dismissPointY)).isTrue()
- dropTargetManager.onDragUpdated(dismissPointX, dismissPointY)
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {
+ dropTargetManager.onDragUpdated(dismissPointX, dismissPointY)
+ }
assertThat(dragZoneChangedListener.fromDragZone).isEqualTo(splitDragZone)
assertThat(dragZoneChangedListener.toDragZone).isEqualTo(dismissDragZone)
}
@@ -166,7 +192,9 @@ class DropTargetManagerTest {
DraggedObject.Bubble(BubbleBarLocation.LEFT),
listOf(bubbleLeftDragZone, bubbleRightDragZone, dismissDragZone)
)
- dropTargetManager.onDragEnded()
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {
+ dropTargetManager.onDragEnded()
+ }
dropTargetManager.onDragUpdated(
bubbleRightDragZone.bounds.centerX(),
bubbleRightDragZone.bounds.centerY()
@@ -175,6 +203,129 @@ class DropTargetManagerTest {
assertThat(dragZoneChangedListener.toDragZone).isNull()
}
+ @Test
+ fun onDragStarted_dropTargetAddedToContainer() {
+ dropTargetManager.onDragStarted(
+ DraggedObject.Bubble(BubbleBarLocation.LEFT),
+ listOf(bubbleLeftDragZone, bubbleRightDragZone)
+ )
+ assertThat(container.childCount).isEqualTo(1)
+ assertThat(dropTargetView.alpha).isEqualTo(0)
+ }
+
+ @Test
+ fun onDragEnded_dropTargetRemovedFromContainer() {
+ dropTargetManager.onDragStarted(
+ DraggedObject.Bubble(BubbleBarLocation.LEFT),
+ listOf(bubbleLeftDragZone, bubbleRightDragZone)
+ )
+ assertThat(container.childCount).isEqualTo(1)
+
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {
+ dropTargetManager.onDragEnded()
+ animatorTestRule.advanceTimeBy(250)
+ }
+ assertThat(container.childCount).isEqualTo(0)
+ }
+
+ @Test
+ fun startNewDrag_beforeDropTargetRemoved() {
+ dropTargetManager.onDragStarted(
+ DraggedObject.Bubble(BubbleBarLocation.LEFT),
+ listOf(bubbleLeftDragZone, bubbleRightDragZone)
+ )
+ assertThat(container.childCount).isEqualTo(1)
+
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {
+ dropTargetManager.onDragEnded()
+ // advance the timer by 100ms so the animation doesn't complete
+ animatorTestRule.advanceTimeBy(100)
+ }
+ assertThat(container.childCount).isEqualTo(1)
+
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {
+ dropTargetManager.onDragStarted(
+ DraggedObject.Bubble(BubbleBarLocation.LEFT),
+ listOf(bubbleLeftDragZone, bubbleRightDragZone)
+ )
+ }
+ assertThat(container.childCount).isEqualTo(1)
+ }
+
+ @Test
+ fun updateDragZone_withDropTarget_dropTargetUpdated() {
+ dropTargetManager.onDragStarted(
+ DraggedObject.Bubble(BubbleBarLocation.LEFT),
+ listOf(dismissDragZone, bubbleLeftDragZone, bubbleRightDragZone)
+ )
+
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {
+ dropTargetManager.onDragUpdated(
+ bubbleRightDragZone.bounds.centerX(),
+ bubbleRightDragZone.bounds.centerY()
+ )
+ animatorTestRule.advanceTimeBy(250)
+ }
+
+ assertThat(dropTargetView.alpha).isEqualTo(1)
+ verifyDropTargetPosition(bubbleRightDragZone.dropTarget)
+ }
+
+ @Test
+ fun updateDragZone_withoutDropTarget_dropTargetHidden() {
+ dropTargetManager.onDragStarted(
+ DraggedObject.Bubble(BubbleBarLocation.LEFT),
+ listOf(dismissDragZone, bubbleLeftDragZone, bubbleRightDragZone)
+ )
+
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {
+ dropTargetManager.onDragUpdated(
+ dismissDragZone.bounds.centerX(),
+ dismissDragZone.bounds.centerY()
+ )
+ animatorTestRule.advanceTimeBy(250)
+ }
+
+ assertThat(dropTargetView.alpha).isEqualTo(0)
+ }
+
+ @Test
+ fun updateDragZone_betweenZonesWithDropTarget_dropTargetUpdated() {
+ dropTargetManager.onDragStarted(
+ DraggedObject.Bubble(BubbleBarLocation.LEFT),
+ listOf(dismissDragZone, bubbleLeftDragZone, bubbleRightDragZone)
+ )
+
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {
+ dropTargetManager.onDragUpdated(
+ bubbleRightDragZone.bounds.centerX(),
+ bubbleRightDragZone.bounds.centerY()
+ )
+ animatorTestRule.advanceTimeBy(250)
+ }
+
+ assertThat(dropTargetView.alpha).isEqualTo(1)
+ verifyDropTargetPosition(bubbleRightDragZone.dropTarget)
+
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {
+ dropTargetManager.onDragUpdated(
+ bubbleLeftDragZone.bounds.centerX(),
+ bubbleLeftDragZone.bounds.centerY()
+ )
+ animatorTestRule.advanceTimeBy(250)
+ }
+
+ assertThat(dropTargetView.alpha).isEqualTo(1)
+ verifyDropTargetPosition(bubbleLeftDragZone.dropTarget)
+ }
+
+ private fun verifyDropTargetPosition(rect: Rect) {
+ assertThat(dropTargetView.scaleX).isEqualTo(rect.width())
+ assertThat(dropTargetView.scaleY).isEqualTo(rect.height())
+ assertThat(dropTargetView.translationX).isEqualTo(rect.exactCenterX())
+ assertThat(dropTargetView.translationY).isEqualTo(rect.exactCenterY())
+ }
+
private class FakeDragZoneChangedListener : DropTargetManager.DragZoneChangedListener {
var initialDragZone: DragZone? = null
var fromDragZone: DragZone? = null
@@ -183,6 +334,7 @@ class DropTargetManagerTest {
override fun onInitialDragZoneSet(dragZone: DragZone) {
initialDragZone = dragZone
}
+
override fun onDragZoneChanged(from: DragZone, to: DragZone) {
fromDragZone = from
toDragZone = to
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatusTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatusTest.kt
index 391d46287498..4082ffd4ac0a 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatusTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatusTest.kt
@@ -22,8 +22,6 @@ 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.provider.Settings.Global.DEVELOPMENT_OVERRIDE_DESKTOP_MODE_FEATURES
import android.window.DesktopModeFlags
import androidx.test.filters.SmallTest
import com.android.internal.R
@@ -63,14 +61,12 @@ class DesktopModeStatusTest : ShellTestCase() {
doReturn(context.contentResolver).whenever(mockContext).contentResolver
resetDesktopModeFlagsCache()
resetEnforceDeviceRestriction()
- resetFlagOverride()
}
@After
fun tearDown() {
resetDesktopModeFlagsCache()
resetEnforceDeviceRestriction()
- resetFlagOverride()
}
@DisableFlags(
@@ -157,23 +153,40 @@ class DesktopModeStatusTest : ShellTestCase() {
}
@Test
- fun isInternalDisplayEligibleToHostDesktops_configDEModeOn_returnsTrue() {
+ fun isDeviceEligibleForDesktopMode_configDEModeOnAndIntDispHostsDesktop_returnsTrue() {
+ doReturn(true).whenever(mockResources).getBoolean(eq(R.bool.config_isDesktopModeSupported))
doReturn(true).whenever(mockResources).getBoolean(eq(R.bool.config_canInternalDisplayHostDesktops))
- assertThat(DesktopModeStatus.isInternalDisplayEligibleToHostDesktops(mockContext)).isTrue()
+ assertThat(DesktopModeStatus.isDeviceEligibleForDesktopMode(mockContext)).isTrue()
+ }
+
+ @Test
+ fun isDeviceEligibleForDesktopMode_configDEModeOffAndIntDispHostsDesktop_returnsFalse() {
+ doReturn(false).whenever(mockResources).getBoolean(eq(R.bool.config_isDesktopModeSupported))
+ doReturn(true).whenever(mockResources).getBoolean(eq(R.bool.config_canInternalDisplayHostDesktops))
+
+ assertThat(DesktopModeStatus.isDeviceEligibleForDesktopMode(mockContext)).isFalse()
+ }
+
+ @Test
+ fun isDeviceEligibleForDesktopMode_configDEModeOnAndIntDispHostsDesktopOff_returnsFalse() {
+ doReturn(true).whenever(mockResources).getBoolean(eq(R.bool.config_isDesktopModeSupported))
+ doReturn(false).whenever(mockResources).getBoolean(eq(R.bool.config_canInternalDisplayHostDesktops))
+
+ assertThat(DesktopModeStatus.isDeviceEligibleForDesktopMode(mockContext)).isFalse()
}
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
@DisableFlags(Flags.FLAG_ENABLE_DESKTOP_MODE_THROUGH_DEV_OPTION)
@Test
fun isInternalDisplayEligibleToHostDesktops_supportFlagOff_returnsFalse() {
- assertThat(DesktopModeStatus.isInternalDisplayEligibleToHostDesktops(mockContext)).isFalse()
+ assertThat(DesktopModeStatus.isDeviceEligibleForDesktopMode(mockContext)).isFalse()
}
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_MODE_THROUGH_DEV_OPTION)
@Test
fun isInternalDisplayEligibleToHostDesktops_supportFlagOn_returnsFalse() {
- assertThat(DesktopModeStatus.isInternalDisplayEligibleToHostDesktops(mockContext)).isFalse()
+ assertThat(DesktopModeStatus.isDeviceEligibleForDesktopMode(mockContext)).isFalse()
}
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_MODE_THROUGH_DEV_OPTION)
@@ -183,7 +196,7 @@ class DesktopModeStatusTest : ShellTestCase() {
eq(R.bool.config_isDesktopModeDevOptionSupported)
)
- assertThat(DesktopModeStatus.isInternalDisplayEligibleToHostDesktops(mockContext)).isTrue()
+ assertThat(DesktopModeStatus.isDeviceEligibleForDesktopMode(mockContext)).isTrue()
}
@DisableFlags(Flags.FLAG_SHOW_DESKTOP_EXPERIENCE_DEV_OPTION)
@@ -229,18 +242,11 @@ class DesktopModeStatusTest : ShellTestCase() {
cachedToggleOverride.set(null, null)
}
- private fun resetFlagOverride() {
- Settings.Global.putString(
- context.contentResolver,
- DEVELOPMENT_OVERRIDE_DESKTOP_MODE_FEATURES, null
- )
- }
-
private fun setFlagOverride(override: DesktopModeFlags.ToggleOverride) {
- Settings.Global.putInt(
- context.contentResolver,
- DEVELOPMENT_OVERRIDE_DESKTOP_MODE_FEATURES, override.setting
- )
+ val cachedToggleOverride =
+ DesktopModeFlags::class.java.getDeclaredField("sCachedToggleOverride")
+ cachedToggleOverride.isAccessible = true
+ cachedToggleOverride.set(null, override)
}
private fun setDeviceEligibleForDesktopMode(eligible: Boolean) {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
index da41a23f066c..d8d45c02b364 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
@@ -484,7 +484,6 @@ class DesktopModeWindowDecorViewModelTests : DesktopModeWindowDecorViewModelTest
eq(SnapPosition.LEFT),
eq(ResizeTrigger.SNAP_LEFT_MENU),
eq(InputMethod.UNKNOWN_INPUT_METHOD),
- eq(decor)
)
}
@@ -520,7 +519,6 @@ class DesktopModeWindowDecorViewModelTests : DesktopModeWindowDecorViewModelTest
eq(SnapPosition.LEFT),
eq(ResizeTrigger.SNAP_LEFT_MENU),
eq(InputMethod.UNKNOWN_INPUT_METHOD),
- eq(decor),
)
}
@@ -542,7 +540,6 @@ class DesktopModeWindowDecorViewModelTests : DesktopModeWindowDecorViewModelTest
eq(decor.mTaskInfo), any(), eq(currentBounds), eq(SnapPosition.LEFT),
eq(ResizeTrigger.MAXIMIZE_BUTTON),
eq(InputMethod.UNKNOWN_INPUT_METHOD),
- eq(decor),
)
}
@@ -562,7 +559,6 @@ class DesktopModeWindowDecorViewModelTests : DesktopModeWindowDecorViewModelTest
eq(SnapPosition.RIGHT),
eq(ResizeTrigger.SNAP_RIGHT_MENU),
eq(InputMethod.UNKNOWN_INPUT_METHOD),
- eq(decor),
)
}
@@ -598,7 +594,6 @@ class DesktopModeWindowDecorViewModelTests : DesktopModeWindowDecorViewModelTest
eq(SnapPosition.RIGHT),
eq(ResizeTrigger.SNAP_RIGHT_MENU),
eq(InputMethod.UNKNOWN_INPUT_METHOD),
- eq(decor),
)
}
@@ -620,7 +615,6 @@ class DesktopModeWindowDecorViewModelTests : DesktopModeWindowDecorViewModelTest
eq(decor.mTaskInfo), any(), eq(currentBounds), eq(SnapPosition.RIGHT),
eq(ResizeTrigger.MAXIMIZE_BUTTON),
eq(InputMethod.UNKNOWN_INPUT_METHOD),
- eq(decor),
)
}
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 e40034b09f39..8cccdb2b6120 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
@@ -81,6 +81,7 @@ import com.android.wm.shell.windowdecor.DesktopModeWindowDecorViewModel.DesktopM
import com.android.wm.shell.windowdecor.common.WindowDecorTaskResourceLoader
import com.android.wm.shell.windowdecor.common.viewhost.WindowDecorViewHost
import com.android.wm.shell.windowdecor.common.viewhost.WindowDecorViewHostSupplier
+import com.android.wm.shell.windowdecor.tiling.DesktopTilingDecorViewModel
import com.android.wm.shell.windowdecor.viewholder.AppHeaderViewHolder
import org.junit.After
import org.mockito.Mockito
@@ -147,6 +148,7 @@ open class DesktopModeWindowDecorViewModelTestsBase : ShellTestCase() {
protected val mockCaptionHandleRepository = mock<WindowDecorCaptionHandleRepository>()
protected val mockDesktopRepository: DesktopRepository = mock<DesktopRepository>()
protected val mockRecentsTransitionHandler = mock<RecentsTransitionHandler>()
+ protected val mockTilingWindowDecoration = mock<DesktopTilingDecorViewModel>()
protected val motionEvent = mock<MotionEvent>()
private val displayLayout = mock<DisplayLayout>()
private val display = mock<Display>()
@@ -226,6 +228,7 @@ open class DesktopModeWindowDecorViewModelTestsBase : ShellTestCase() {
mock<WindowDecorTaskResourceLoader>(),
mockRecentsTransitionHandler,
desktopModeCompatPolicy,
+ mockTilingWindowDecoration,
)
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 71c821dd9b71..c4f70ac2297f 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
@@ -120,6 +120,7 @@ import com.android.wm.shell.windowdecor.viewholder.AppHeaderViewHolder;
import kotlin.Unit;
import kotlin.jvm.functions.Function0;
import kotlin.jvm.functions.Function1;
+import kotlin.jvm.functions.Function2;
import kotlinx.coroutines.CoroutineScope;
import kotlinx.coroutines.MainCoroutineDispatcher;
@@ -998,8 +999,8 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
createMaximizeMenu(decoration);
- verify(menu).show(anyBoolean(), anyInt(), anyBoolean(), anyBoolean(), any(), any(), any(),
- any(), mOnMaxMenuHoverChangeListener.capture(), any());
+ verify(menu).show(anyBoolean(), anyBoolean(), anyBoolean(), any(), any(), any(), any(),
+ mOnMaxMenuHoverChangeListener.capture(), any());
assertTrue(decoration.isMaximizeMenuActive());
}
@@ -1011,8 +1012,8 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
new FakeMaximizeMenuFactory(menu));
decoration.setAppHeaderMaximizeButtonHovered(false);
createMaximizeMenu(decoration);
- verify(menu).show(anyBoolean(), anyInt(), anyBoolean(), anyBoolean(), any(), any(), any(),
- any(), mOnMaxMenuHoverChangeListener.capture(), any());
+ verify(menu).show(anyBoolean(), anyBoolean(), anyBoolean(), any(), any(), any(), any(),
+ mOnMaxMenuHoverChangeListener.capture(), any());
mOnMaxMenuHoverChangeListener.getValue().invoke(false);
@@ -1050,8 +1051,8 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
final DesktopModeWindowDecoration decoration = createWindowDecoration(taskInfo,
new FakeMaximizeMenuFactory(menu));
createMaximizeMenu(decoration);
- verify(menu).show(anyBoolean(), anyInt(), anyBoolean(), anyBoolean(), any(), any(), any(),
- any(), mOnMaxMenuHoverChangeListener.capture(), any());
+ verify(menu).show(anyBoolean(), anyBoolean(), anyBoolean(), any(), any(), any(), any(),
+ mOnMaxMenuHoverChangeListener.capture(), any());
mOnMaxMenuHoverChangeListener.getValue().invoke(true);
@@ -1065,8 +1066,8 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
final DesktopModeWindowDecoration decoration = createWindowDecoration(taskInfo,
new FakeMaximizeMenuFactory(menu));
createMaximizeMenu(decoration);
- verify(menu).show(anyBoolean(), anyInt(), anyBoolean(), anyBoolean(), any(), any(), any(),
- any(), mOnMaxMenuHoverChangeListener.capture(), any());
+ verify(menu).show(anyBoolean(), anyBoolean(), anyBoolean(), any(), any(), any(), any(),
+ mOnMaxMenuHoverChangeListener.capture(), any());
decoration.setAppHeaderMaximizeButtonHovered(true);
@@ -1086,7 +1087,6 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
verify(menu).show(
anyBoolean(),
- anyInt(),
/* showImmersiveOption= */ eq(true),
anyBoolean(),
any(),
@@ -1111,7 +1111,6 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
verify(menu).show(
anyBoolean(),
- anyInt(),
/* showImmersiveOption= */ eq(false),
anyBoolean(),
any(),
@@ -1136,7 +1135,6 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
verify(menu).show(
anyBoolean(),
- anyInt(),
anyBoolean(),
/* showSnapOptions= */ eq(true),
any(),
@@ -1161,7 +1159,6 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
verify(menu).show(
anyBoolean(),
- anyInt(),
anyBoolean(),
/* showSnapOptions= */ eq(false),
any(),
@@ -1766,7 +1763,9 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
@NonNull RootTaskDisplayAreaOrganizer rootTdaOrganizer,
@NonNull DisplayController displayController,
@NonNull ActivityManager.RunningTaskInfo taskInfo,
- @NonNull Context decorWindowContext, @NonNull PointF menuPosition,
+ @NonNull Context decorWindowContext,
+ @NonNull Function2<? super Integer,? super Integer,? extends PointF>
+ positionSupplier,
@NonNull Supplier<SurfaceControl.Transaction> transactionSupplier) {
return mMaximizeMenu;
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragResizeInputListenerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragResizeInputListenerTest.kt
new file mode 100644
index 000000000000..7341e098add5
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragResizeInputListenerTest.kt
@@ -0,0 +1,207 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.wm.shell.windowdecor
+
+import android.app.ActivityManager
+import android.content.Context
+import android.graphics.Region
+import android.os.Handler
+import android.os.Looper
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.util.Size
+import android.view.Choreographer
+import android.view.Display
+import android.view.IWindowSession
+import android.view.InputChannel
+import android.view.SurfaceControl
+import androidx.test.filters.SmallTest
+import com.android.wm.shell.ShellTestCase
+import com.android.wm.shell.TestHandler
+import com.android.wm.shell.TestRunningTaskInfoBuilder
+import com.android.wm.shell.TestShellExecutor
+import com.android.wm.shell.common.DisplayController
+import com.android.wm.shell.desktopmode.DesktopModeEventLogger
+import com.android.wm.shell.util.StubTransaction
+import com.android.wm.shell.windowdecor.DragResizeInputListener.TaskResizeInputEventReceiver
+import com.google.common.truth.Truth.assertThat
+import java.util.function.Consumer
+import java.util.function.Supplier
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.any
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.kotlin.anyOrNull
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.never
+import org.mockito.kotlin.verify
+
+/**
+ * Tests for [DragResizeInputListener].
+ *
+ * Build/Install/Run:
+ * atest WMShellUnitTests:DragResizeInputListenerTest
+ */
+@SmallTest
+@TestableLooper.RunWithLooper
+@RunWith(AndroidTestingRunner::class)
+class DragResizeInputListenerTest : ShellTestCase() {
+ private val testMainExecutor = TestShellExecutor()
+ private val testBgExecutor = TestShellExecutor()
+ private val mockWindowSession = mock<IWindowSession>()
+ private val mockInputEventReceiver = mock<TaskResizeInputEventReceiver>()
+
+ @Test
+ fun testGrantInputChannelOffMainThread() {
+ create()
+ testMainExecutor.flushAll()
+
+ verifyNoInputChannelGrantRequests()
+ }
+
+ @Test
+ fun testInitializationCallback_waitsForBgSetup() {
+ val inputListener = create()
+
+ val callback = TestInitializationCallback()
+ inputListener.addInitializedCallback(callback)
+ assertThat(callback.initialized).isFalse()
+
+ testBgExecutor.flushAll()
+ testMainExecutor.flushAll()
+
+ assertThat(callback.initialized).isTrue()
+ }
+
+ @Test
+ fun testInitializationCallback_alreadyInitialized_callsBackImmediately() {
+ val inputListener = create()
+ testBgExecutor.flushAll()
+ testMainExecutor.flushAll()
+
+ val callback = TestInitializationCallback()
+ inputListener.addInitializedCallback(callback)
+
+ assertThat(callback.initialized).isTrue()
+ }
+
+ @Test
+ fun testClose_beforeBgSetup_cancelsBgSetup() {
+ val inputListener = create()
+
+ inputListener.close()
+ testBgExecutor.flushAll()
+
+ verifyNoInputChannelGrantRequests()
+ }
+
+ @Test
+ fun testClose_beforeBgSetupResultSet_cancelsInit() {
+ val inputListener = create()
+ val callback = TestInitializationCallback()
+ inputListener.addInitializedCallback(callback)
+
+ testBgExecutor.flushAll()
+ inputListener.close()
+ testMainExecutor.flushAll()
+
+ assertThat(callback.initialized).isFalse()
+ }
+
+ @Test
+ fun testClose_afterInit_disposesOfReceiver() {
+ val inputListener = create()
+
+ testBgExecutor.flushAll()
+ testMainExecutor.flushAll()
+ inputListener.close()
+
+ verify(mockInputEventReceiver).dispose()
+ }
+
+ @Test
+ fun testClose_afterInit_removesTokens() {
+ val inputListener = create()
+
+ inputListener.close()
+ testBgExecutor.flushAll()
+
+ verify(mockWindowSession).remove(inputListener.mClientToken)
+ verify(mockWindowSession).remove(inputListener.mSinkClientToken)
+ }
+
+ private fun verifyNoInputChannelGrantRequests() {
+ verify(mockWindowSession, never())
+ .grantInputChannel(
+ anyInt(),
+ any(),
+ any(),
+ anyOrNull(),
+ anyInt(),
+ anyInt(),
+ anyInt(),
+ anyInt(),
+ anyOrNull(),
+ any(),
+ any(),
+ any(),
+ )
+ }
+
+ private fun create(): DragResizeInputListener =
+ DragResizeInputListener(
+ context,
+ mockWindowSession,
+ testMainExecutor,
+ testBgExecutor,
+ TestTaskResizeInputEventReceiverFactory(mockInputEventReceiver),
+ TestRunningTaskInfoBuilder().build(),
+ TestHandler(Looper.getMainLooper()),
+ mock<Choreographer>(),
+ Display.DEFAULT_DISPLAY,
+ mock<SurfaceControl>(),
+ mock<DragPositioningCallback>(),
+ { SurfaceControl.Builder() },
+ { StubTransaction() },
+ mock<DisplayController>(),
+ mock<DesktopModeEventLogger>(),
+ )
+
+ private class TestInitializationCallback : Runnable {
+ var initialized: Boolean = false
+ private set
+
+ override fun run() {
+ initialized = true
+ }
+ }
+
+ private class TestTaskResizeInputEventReceiverFactory(
+ private val mockInputEventReceiver: TaskResizeInputEventReceiver
+ ) : DragResizeInputListener.TaskResizeInputEventReceiverFactory {
+ override fun create(
+ context: Context,
+ taskInfo: ActivityManager.RunningTaskInfo,
+ inputChannel: InputChannel,
+ callback: DragPositioningCallback,
+ handler: Handler,
+ choreographer: Choreographer,
+ displayLayoutSizeSupplier: Supplier<Size?>,
+ touchRegionConsumer: Consumer<Region?>,
+ desktopModeEventLogger: DesktopModeEventLogger,
+ ): TaskResizeInputEventReceiver = mockInputEventReceiver
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
index aa1f82e3e4d8..af0162334440 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
@@ -40,6 +40,7 @@ import static org.mockito.ArgumentMatchers.anyFloat;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.argThat;
+import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.inOrder;
@@ -386,6 +387,49 @@ public class WindowDecorationTests extends ShellTestCase {
verify(mMockWindowDecorViewHost).updateView(same(mMockView), any(), any(), any(), any());
}
+
+ @Test
+ public void testReinflateViewsOnFontScaleChange() {
+ final Display defaultDisplay = mock(Display.class);
+ doReturn(defaultDisplay).when(mMockDisplayController)
+ .getDisplay(Display.DEFAULT_DISPLAY);
+
+ final ActivityManager.RunningTaskInfo taskInfo = new TestRunningTaskInfoBuilder()
+ .setVisible(true)
+ .setDisplayId(Display.DEFAULT_DISPLAY)
+ .build();
+ final TestWindowDecoration windowDecor = spy(createWindowDecoration(taskInfo));
+ windowDecor.relayout(taskInfo, true /* hasGlobalFocus */, Region.obtain());
+ clearInvocations(windowDecor);
+ final ActivityManager.RunningTaskInfo taskInfo2 = new TestRunningTaskInfoBuilder()
+ .setVisible(true)
+ .setDisplayId(Display.DEFAULT_DISPLAY)
+ .build();
+ taskInfo2.configuration.fontScale = taskInfo.configuration.fontScale + 1;
+ windowDecor.relayout(taskInfo2, true /* hasGlobalFocus */, Region.obtain());
+ // WindowDecoration#releaseViews should be called since the font scale has changed.
+ verify(windowDecor).releaseViews(any());
+ }
+
+ @Test
+ public void testViewNotReinflatedWhenFontScaleNotChanged() {
+ final Display defaultDisplay = mock(Display.class);
+ doReturn(defaultDisplay).when(mMockDisplayController)
+ .getDisplay(Display.DEFAULT_DISPLAY);
+
+ final ActivityManager.RunningTaskInfo taskInfo = new TestRunningTaskInfoBuilder()
+ .setVisible(true)
+ .setDisplayId(Display.DEFAULT_DISPLAY)
+ .build();
+ final TestWindowDecoration windowDecor = spy(createWindowDecoration(taskInfo));
+ windowDecor.relayout(taskInfo, true /* hasGlobalFocus */, Region.obtain());
+ clearInvocations(windowDecor);
+ windowDecor.relayout(taskInfo, true /* hasGlobalFocus */, Region.obtain());
+ // WindowDecoration#releaseViews should be called since task info (and therefore the
+ // fontScale) has not changed.
+ verify(windowDecor, never()).releaseViews(any());
+ }
+
@Test
public void testAddViewHostViewContainer() {
final Display defaultDisplay = mock(Display.class);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/common/viewhost/ReusableWindowDecorViewHostTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/common/viewhost/ReusableWindowDecorViewHostTest.kt
index d99a4825e580..c86730ed1dc7 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/common/viewhost/ReusableWindowDecorViewHostTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/common/viewhost/ReusableWindowDecorViewHostTest.kt
@@ -20,6 +20,7 @@ import android.testing.TestableLooper
import android.view.SurfaceControl
import android.view.View
import android.view.WindowManager
+import android.widget.FrameLayout
import androidx.test.filters.SmallTest
import com.android.wm.shell.ShellTestCase
import com.google.common.truth.Truth.assertThat
@@ -30,6 +31,9 @@ import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito.mock
+import org.mockito.Mockito.times
+import org.mockito.kotlin.clearInvocations
+import org.mockito.kotlin.never
import org.mockito.kotlin.spy
import org.mockito.kotlin.verify
@@ -47,24 +51,46 @@ class ReusableWindowDecorViewHostTest : ShellTestCase() {
fun update_differentView_replacesView() = runTest {
val view = View(context)
val lp = WindowManager.LayoutParams()
- val reusableVH = createReusableViewHost()
- reusableVH.updateView(view, lp, context.resources.configuration, null)
+ val rootView = FrameLayout(context)
+ val reusableVH = createReusableViewHost(rootView)
+ reusableVH.updateView(view, lp, context.resources.configuration)
- assertThat(reusableVH.rootView.childCount).isEqualTo(1)
- assertThat(reusableVH.rootView.getChildAt(0)).isEqualTo(view)
+ assertThat(rootView.childCount).isEqualTo(1)
+ assertThat(rootView.getChildAt(0)).isEqualTo(view)
val newView = View(context)
val newLp = WindowManager.LayoutParams()
- reusableVH.updateView(newView, newLp, context.resources.configuration, null)
+ reusableVH.updateView(newView, newLp, context.resources.configuration)
- assertThat(reusableVH.rootView.childCount).isEqualTo(1)
- assertThat(reusableVH.rootView.getChildAt(0)).isEqualTo(newView)
+ assertThat(rootView.childCount).isEqualTo(1)
+ assertThat(rootView.getChildAt(0)).isEqualTo(newView)
+ }
+
+ @Test
+ fun update_sameView_doesNotReplaceView() = runTest {
+ val view = View(context)
+ val lp = WindowManager.LayoutParams()
+ val spyRootView = spy(FrameLayout(context))
+ val reusableVH = createReusableViewHost(spyRootView)
+ reusableVH.updateView(view, lp, context.resources.configuration)
+
+ verify(spyRootView, times(1)).removeAllViews()
+ assertThat(spyRootView.childCount).isEqualTo(1)
+ assertThat(spyRootView.getChildAt(0)).isEqualTo(view)
+
+ reusableVH.updateView(view, lp, context.resources.configuration)
+
+ clearInvocations(spyRootView)
+ verify(spyRootView, never()).removeAllViews()
+ assertThat(spyRootView.childCount).isEqualTo(1)
+ assertThat(spyRootView.getChildAt(0)).isEqualTo(view)
}
@OptIn(ExperimentalCoroutinesApi::class)
@Test
fun updateView_clearsPendingAsyncJob() = runTest {
- val reusableVH = createReusableViewHost()
+ val rootView = FrameLayout(context)
+ val reusableVH = createReusableViewHost(rootView)
val asyncView = View(context)
val syncView = View(context)
val asyncAttrs = WindowManager.LayoutParams(100, 100)
@@ -83,7 +109,6 @@ class ReusableWindowDecorViewHostTest : ShellTestCase() {
view = syncView,
attrs = syncAttrs,
configuration = context.resources.configuration,
- onDrawTransaction = null,
)
// Would run coroutine if it hadn't been cancelled.
@@ -91,7 +116,7 @@ class ReusableWindowDecorViewHostTest : ShellTestCase() {
assertThat(reusableVH.viewHostAdapter.isInitialized()).isTrue()
// View host view/attrs should match the ones from the sync call.
- assertThat(reusableVH.rootView.getChildAt(0)).isEqualTo(syncView)
+ assertThat(rootView.getChildAt(0)).isEqualTo(syncView)
assertThat(reusableVH.view()!!.layoutParams.width).isEqualTo(syncAttrs.width)
}
@@ -118,7 +143,8 @@ class ReusableWindowDecorViewHostTest : ShellTestCase() {
@OptIn(ExperimentalCoroutinesApi::class)
@Test
fun updateViewAsync_clearsPendingAsyncJob() = runTest {
- val reusableVH = createReusableViewHost()
+ val rootView = FrameLayout(context)
+ val reusableVH = createReusableViewHost(rootView)
val view = View(context)
reusableVH.updateViewAsync(
@@ -136,7 +162,7 @@ class ReusableWindowDecorViewHostTest : ShellTestCase() {
advanceUntilIdle()
assertThat(reusableVH.viewHostAdapter.isInitialized()).isTrue()
- assertThat(reusableVH.rootView.getChildAt(0)).isEqualTo(otherView)
+ assertThat(rootView.getChildAt(0)).isEqualTo(otherView)
}
@Test
@@ -148,7 +174,6 @@ class ReusableWindowDecorViewHostTest : ShellTestCase() {
view = view,
attrs = WindowManager.LayoutParams(100, 100),
configuration = context.resources.configuration,
- onDrawTransaction = null,
)
val t = mock(SurfaceControl.Transaction::class.java)
@@ -159,19 +184,23 @@ class ReusableWindowDecorViewHostTest : ShellTestCase() {
@Test
fun warmUp_addsRootView() = runTest {
- val reusableVH = createReusableViewHost().apply { warmUp() }
+ val rootView = FrameLayout(context)
+ val reusableVH = createReusableViewHost(rootView).apply { warmUp() }
assertThat(reusableVH.viewHostAdapter.isInitialized()).isTrue()
- assertThat(reusableVH.view()).isEqualTo(reusableVH.rootView)
+ assertThat(reusableVH.view()).isEqualTo(rootView)
}
- private fun CoroutineScope.createReusableViewHost() =
+ private fun CoroutineScope.createReusableViewHost(
+ rootView: FrameLayout = FrameLayout(context)
+ ) =
ReusableWindowDecorViewHost(
context = context,
mainScope = this,
display = context.display,
id = 1,
viewHostAdapter = spy(SurfaceControlViewHostAdapter(context, context.display)),
+ rootView
)
private fun ReusableWindowDecorViewHost.view(): View? = viewHostAdapter.viewHost?.view
diff --git a/libs/androidfw/ApkAssets.cpp b/libs/androidfw/ApkAssets.cpp
index dbb891455ddd..e693fcfd3918 100644
--- a/libs/androidfw/ApkAssets.cpp
+++ b/libs/androidfw/ApkAssets.cpp
@@ -162,10 +162,13 @@ const std::string& ApkAssets::GetDebugName() const {
return assets_provider_->GetDebugName();
}
-bool ApkAssets::IsUpToDate() const {
+UpToDate ApkAssets::IsUpToDate() const {
// Loaders are invalidated by the app, not the system, so assume they are up to date.
- return IsLoader() || ((!loaded_idmap_ || loaded_idmap_->IsUpToDate())
- && assets_provider_->IsUpToDate());
+ if (IsLoader()) {
+ return UpToDate::Always;
+ }
+ const auto idmap_res = loaded_idmap_ ? loaded_idmap_->IsUpToDate() : UpToDate::Always;
+ return combine(idmap_res, [this] { return assets_provider_->IsUpToDate(); });
}
} // namespace android
diff --git a/libs/androidfw/AssetManager2.cpp b/libs/androidfw/AssetManager2.cpp
index 0fa31c7a832e..f5e10d94452f 100644
--- a/libs/androidfw/AssetManager2.cpp
+++ b/libs/androidfw/AssetManager2.cpp
@@ -1467,8 +1467,6 @@ base::expected<uint32_t, NullOrIOError> AssetManager2::GetResourceId(
}
const StringPiece16 kAttr16 = u"attr";
- const static std::u16string kAttrPrivate16 = u"^attr-private";
-
for (const PackageGroup& package_group : package_groups_) {
for (const ConfiguredPackage& package_impl : package_group.packages_) {
const LoadedPackage* package = package_impl.loaded_package_;
@@ -1480,12 +1478,13 @@ base::expected<uint32_t, NullOrIOError> AssetManager2::GetResourceId(
base::expected<uint32_t, NullOrIOError> resid = package->FindEntryByName(type16, entry16);
if (UNLIKELY(IsIOError(resid))) {
return base::unexpected(resid.error());
- }
+ }
if (!resid.has_value() && kAttr16 == type16) {
// Private attributes in libraries (such as the framework) are sometimes encoded
// under the type '^attr-private' in order to leave the ID space of public 'attr'
// free for future additions. Check '^attr-private' for the same name.
+ const static std::u16string kAttrPrivate16 = u"^attr-private";
resid = package->FindEntryByName(kAttrPrivate16, entry16);
}
diff --git a/libs/androidfw/AssetsProvider.cpp b/libs/androidfw/AssetsProvider.cpp
index 2d3c06506a1f..808509120462 100644
--- a/libs/androidfw/AssetsProvider.cpp
+++ b/libs/androidfw/AssetsProvider.cpp
@@ -24,9 +24,27 @@
#include <ziparchive/zip_archive.h>
namespace android {
-namespace {
-constexpr const char* kEmptyDebugString = "<empty>";
-} // namespace
+
+static constexpr std::string_view kEmptyDebugString = "<empty>";
+
+std::unique_ptr<AssetsProvider> AssetsProvider::CreateWithOverride(
+ std::unique_ptr<AssetsProvider> provider, std::unique_ptr<AssetsProvider> override) {
+ if (provider == nullptr) {
+ return {};
+ }
+ if (override == nullptr) {
+ return provider;
+ }
+ return MultiAssetsProvider::Create(std::move(override), std::move(provider));
+}
+
+std::unique_ptr<AssetsProvider> AssetsProvider::CreateFromNullable(
+ std::unique_ptr<AssetsProvider> nullable) {
+ if (nullable) {
+ return nullable;
+ }
+ return EmptyAssetsProvider::Create();
+}
std::unique_ptr<Asset> AssetsProvider::Open(const std::string& path, Asset::AccessMode mode,
bool* file_exists) const {
@@ -86,11 +104,9 @@ void ZipAssetsProvider::ZipCloser::operator()(ZipArchive* a) const {
}
ZipAssetsProvider::ZipAssetsProvider(ZipArchiveHandle handle, PathOrDebugName&& path,
- package_property_t flags, time_t last_mod_time)
- : zip_handle_(handle),
- name_(std::move(path)),
- flags_(flags),
- last_mod_time_(last_mod_time) {}
+ package_property_t flags, ModDate last_mod_time)
+ : zip_handle_(handle), name_(std::move(path)), flags_(flags), last_mod_time_(last_mod_time) {
+}
std::unique_ptr<ZipAssetsProvider> ZipAssetsProvider::Create(std::string path,
package_property_t flags,
@@ -104,10 +120,10 @@ std::unique_ptr<ZipAssetsProvider> ZipAssetsProvider::Create(std::string path,
return {};
}
- struct stat sb{.st_mtime = -1};
+ ModDate mod_date = kInvalidModDate;
// Skip all up-to-date checks if the file won't ever change.
- if (!isReadonlyFilesystem(path.c_str())) {
- if ((released_fd < 0 ? stat(path.c_str(), &sb) : fstat(released_fd, &sb)) < 0) {
+ if (isKnownWritablePath(path.c_str()) || !isReadonlyFilesystem(GetFileDescriptor(handle))) {
+ if (mod_date = getFileModDate(GetFileDescriptor(handle)); mod_date == kInvalidModDate) {
// Stat requires execute permissions on all directories path to the file. If the process does
// not have execute permissions on this file, allow the zip to be opened but IsUpToDate() will
// always have to return true.
@@ -116,7 +132,7 @@ std::unique_ptr<ZipAssetsProvider> ZipAssetsProvider::Create(std::string path,
}
return std::unique_ptr<ZipAssetsProvider>(
- new ZipAssetsProvider(handle, PathOrDebugName::Path(std::move(path)), flags, sb.st_mtime));
+ new ZipAssetsProvider(handle, PathOrDebugName::Path(std::move(path)), flags, mod_date));
}
std::unique_ptr<ZipAssetsProvider> ZipAssetsProvider::Create(base::unique_fd fd,
@@ -137,10 +153,10 @@ std::unique_ptr<ZipAssetsProvider> ZipAssetsProvider::Create(base::unique_fd fd,
return {};
}
- struct stat sb{.st_mtime = -1};
+ ModDate mod_date = kInvalidModDate;
// Skip all up-to-date checks if the file won't ever change.
if (!isReadonlyFilesystem(released_fd)) {
- if (fstat(released_fd, &sb) < 0) {
+ if (mod_date = getFileModDate(released_fd); mod_date == kInvalidModDate) {
// Stat requires execute permissions on all directories path to the file. If the process does
// not have execute permissions on this file, allow the zip to be opened but IsUpToDate() will
// always have to return true.
@@ -150,7 +166,7 @@ std::unique_ptr<ZipAssetsProvider> ZipAssetsProvider::Create(base::unique_fd fd,
}
return std::unique_ptr<ZipAssetsProvider>(new ZipAssetsProvider(
- handle, PathOrDebugName::DebugName(std::move(friendly_name)), flags, sb.st_mtime));
+ handle, PathOrDebugName::DebugName(std::move(friendly_name)), flags, mod_date));
}
std::unique_ptr<Asset> ZipAssetsProvider::OpenInternal(const std::string& path,
@@ -282,21 +298,16 @@ const std::string& ZipAssetsProvider::GetDebugName() const {
return name_.GetDebugName();
}
-bool ZipAssetsProvider::IsUpToDate() const {
- if (last_mod_time_ == -1) {
- return true;
+UpToDate ZipAssetsProvider::IsUpToDate() const {
+ if (last_mod_time_ == kInvalidModDate) {
+ return UpToDate::Always;
}
- struct stat sb{};
- if (fstat(GetFileDescriptor(zip_handle_.get()), &sb) < 0) {
- // If fstat fails on the zip archive, return true so the zip archive the resource system does
- // attempt to refresh the ApkAsset.
- return true;
- }
- return last_mod_time_ == sb.st_mtime;
+ return fromBool(last_mod_time_ == getFileModDate(GetFileDescriptor(zip_handle_.get())));
}
-DirectoryAssetsProvider::DirectoryAssetsProvider(std::string&& path, time_t last_mod_time)
- : dir_(std::move(path)), last_mod_time_(last_mod_time) {}
+DirectoryAssetsProvider::DirectoryAssetsProvider(std::string&& path, ModDate last_mod_time)
+ : dir_(std::move(path)), last_mod_time_(last_mod_time) {
+}
std::unique_ptr<DirectoryAssetsProvider> DirectoryAssetsProvider::Create(std::string path) {
struct stat sb;
@@ -317,7 +328,7 @@ std::unique_ptr<DirectoryAssetsProvider> DirectoryAssetsProvider::Create(std::st
const bool isReadonly = isReadonlyFilesystem(path.c_str());
return std::unique_ptr<DirectoryAssetsProvider>(
- new DirectoryAssetsProvider(std::move(path), isReadonly ? -1 : sb.st_mtime));
+ new DirectoryAssetsProvider(std::move(path), isReadonly ? kInvalidModDate : getModDate(sb)));
}
std::unique_ptr<Asset> DirectoryAssetsProvider::OpenInternal(const std::string& path,
@@ -346,17 +357,11 @@ const std::string& DirectoryAssetsProvider::GetDebugName() const {
return dir_;
}
-bool DirectoryAssetsProvider::IsUpToDate() const {
- if (last_mod_time_ == -1) {
- return true;
+UpToDate DirectoryAssetsProvider::IsUpToDate() const {
+ if (last_mod_time_ == kInvalidModDate) {
+ return UpToDate::Always;
}
- struct stat sb;
- if (stat(dir_.c_str(), &sb) < 0) {
- // If stat fails on the zip archive, return true so the zip archive the resource system does
- // attempt to refresh the ApkAsset.
- return true;
- }
- return last_mod_time_ == sb.st_mtime;
+ return fromBool(last_mod_time_ == getFileModDate(dir_.c_str()));
}
MultiAssetsProvider::MultiAssetsProvider(std::unique_ptr<AssetsProvider>&& primary,
@@ -397,8 +402,8 @@ const std::string& MultiAssetsProvider::GetDebugName() const {
return debug_name_;
}
-bool MultiAssetsProvider::IsUpToDate() const {
- return primary_->IsUpToDate() && secondary_->IsUpToDate();
+UpToDate MultiAssetsProvider::IsUpToDate() const {
+ return combine(primary_->IsUpToDate(), [this] { return secondary_->IsUpToDate(); });
}
EmptyAssetsProvider::EmptyAssetsProvider(std::optional<std::string>&& path) :
@@ -438,12 +443,12 @@ const std::string& EmptyAssetsProvider::GetDebugName() const {
if (path_.has_value()) {
return *path_;
}
- const static std::string kEmpty = kEmptyDebugString;
+ constexpr static std::string kEmpty{kEmptyDebugString};
return kEmpty;
}
-bool EmptyAssetsProvider::IsUpToDate() const {
- return true;
+UpToDate EmptyAssetsProvider::IsUpToDate() const {
+ return UpToDate::Always;
}
} // namespace android
diff --git a/libs/androidfw/Idmap.cpp b/libs/androidfw/Idmap.cpp
index 095be57a5dc8..f0ef97e5bdcc 100644
--- a/libs/androidfw/Idmap.cpp
+++ b/libs/androidfw/Idmap.cpp
@@ -22,9 +22,10 @@
#include "android-base/logging.h"
#include "android-base/stringprintf.h"
#include "android-base/utf8.h"
-#include "androidfw/misc.h"
+#include "androidfw/AssetManager.h"
#include "androidfw/ResourceTypes.h"
#include "androidfw/Util.h"
+#include "androidfw/misc.h"
#include "utils/ByteOrder.h"
#include "utils/Trace.h"
@@ -280,11 +281,16 @@ LoadedIdmap::LoadedIdmap(const std::string& idmap_path, const Idmap_header* head
configurations_(configs),
overlay_entries_(overlay_entries),
string_pool_(std::move(string_pool)),
- idmap_fd_(
- android::base::utf8::open(idmap_path.c_str(), O_RDONLY | O_CLOEXEC | O_BINARY | O_PATH)),
overlay_apk_path_(overlay_apk_path),
target_apk_path_(target_apk_path),
- idmap_last_mod_time_(getFileModDate(idmap_fd_.get())) {
+ idmap_last_mod_time_(kInvalidModDate) {
+ if (!isReadonlyFilesystem(std::string(overlay_apk_path_).c_str()) ||
+ !(target_apk_path_ == AssetManager::TARGET_APK_PATH ||
+ isReadonlyFilesystem(std::string(target_apk_path_).c_str()))) {
+ idmap_fd_.reset(
+ android::base::utf8::open(idmap_path.c_str(), O_RDONLY | O_CLOEXEC | O_BINARY | O_PATH));
+ idmap_last_mod_time_ = getFileModDate(idmap_fd_);
+ }
}
std::unique_ptr<LoadedIdmap> LoadedIdmap::Load(StringPiece idmap_path, StringPiece idmap_data) {
@@ -405,8 +411,11 @@ std::unique_ptr<LoadedIdmap> LoadedIdmap::Load(StringPiece idmap_path, StringPie
std::move(idmap_string_pool),*overlay_path, *target_path));
}
-bool LoadedIdmap::IsUpToDate() const {
- return idmap_last_mod_time_ == getFileModDate(idmap_fd_.get());
+UpToDate LoadedIdmap::IsUpToDate() const {
+ if (idmap_last_mod_time_ == kInvalidModDate) {
+ return UpToDate::Always;
+ }
+ return fromBool(idmap_last_mod_time_ == getFileModDate(idmap_fd_.get()));
}
} // namespace android
diff --git a/libs/androidfw/LocaleDataLookup.cpp b/libs/androidfw/LocaleDataLookup.cpp
index ea9e9a2d4280..9aacdcb9ca92 100644
--- a/libs/androidfw/LocaleDataLookup.cpp
+++ b/libs/androidfw/LocaleDataLookup.cpp
@@ -14871,12 +14871,22 @@ static uint32_t findLatnParent(uint32_t packed_lang_region) {
case 0x656E4154u: // en-AT -> en-150
case 0x656E4245u: // en-BE -> en-150
case 0x656E4348u: // en-CH -> en-150
+ case 0x656E435Au: // en-CZ -> en-150
case 0x656E4445u: // en-DE -> en-150
case 0x656E444Bu: // en-DK -> en-150
+ case 0x656E4553u: // en-ES -> en-150
case 0x656E4649u: // en-FI -> en-150
+ case 0x656E4652u: // en-FR -> en-150
+ case 0x656E4855u: // en-HU -> en-150
+ case 0x656E4954u: // en-IT -> en-150
case 0x656E4E4Cu: // en-NL -> en-150
+ case 0x656E4E4Fu: // en-NO -> en-150
+ case 0x656E504Cu: // en-PL -> en-150
+ case 0x656E5054u: // en-PT -> en-150
+ case 0x656E524Fu: // en-RO -> en-150
case 0x656E5345u: // en-SE -> en-150
case 0x656E5349u: // en-SI -> en-150
+ case 0x656E534Bu: // en-SK -> en-150
return 0x656E80A1u;
case 0x65734152u: // es-AR -> es-419
case 0x6573424Fu: // es-BO -> es-419
diff --git a/libs/androidfw/ResourceTypes.cpp b/libs/androidfw/ResourceTypes.cpp
index 978bc768cd3d..a18c5f5f92f6 100644
--- a/libs/androidfw/ResourceTypes.cpp
+++ b/libs/androidfw/ResourceTypes.cpp
@@ -152,12 +152,11 @@ static void fill9patchOffsets(Res_png_9patch* patch) {
patch->colorsOffset = patch->yDivsOffset + (patch->numYDivs * sizeof(int32_t));
}
-void Res_value::copyFrom_dtoh(const Res_value& src)
-{
- size = dtohs(src.size);
- res0 = src.res0;
- dataType = src.dataType;
- data = dtohl(src.data);
+void Res_value::copyFrom_dtoh_slow(const Res_value& src) {
+ size = dtohs(src.size);
+ res0 = src.res0;
+ dataType = src.dataType;
+ data = dtohl(src.data);
}
void Res_png_9patch::deviceToFile()
@@ -2035,16 +2034,6 @@ status_t ResXMLTree::validateNode(const ResXMLTree_node* node) const
// --------------------------------------------------------------------
// --------------------------------------------------------------------
-void ResTable_config::copyFromDeviceNoSwap(const ResTable_config& o) {
- const size_t size = dtohl(o.size);
- if (size >= sizeof(ResTable_config)) {
- *this = o;
- } else {
- memcpy(this, &o, size);
- memset(((uint8_t*)this)+size, 0, sizeof(ResTable_config)-size);
- }
-}
-
/* static */ size_t unpackLanguageOrRegion(const char in[2], const char base,
char out[4]) {
if (in[0] & 0x80) {
@@ -2109,34 +2098,33 @@ size_t ResTable_config::unpackRegion(char region[4]) const {
return unpackLanguageOrRegion(this->country, '0', region);
}
-
-void ResTable_config::copyFromDtoH(const ResTable_config& o) {
- copyFromDeviceNoSwap(o);
- size = sizeof(ResTable_config);
- mcc = dtohs(mcc);
- mnc = dtohs(mnc);
- density = dtohs(density);
- screenWidth = dtohs(screenWidth);
- screenHeight = dtohs(screenHeight);
- sdkVersion = dtohs(sdkVersion);
- minorVersion = dtohs(minorVersion);
- smallestScreenWidthDp = dtohs(smallestScreenWidthDp);
- screenWidthDp = dtohs(screenWidthDp);
- screenHeightDp = dtohs(screenHeightDp);
-}
-
-void ResTable_config::swapHtoD() {
- size = htodl(size);
- mcc = htods(mcc);
- mnc = htods(mnc);
- density = htods(density);
- screenWidth = htods(screenWidth);
- screenHeight = htods(screenHeight);
- sdkVersion = htods(sdkVersion);
- minorVersion = htods(minorVersion);
- smallestScreenWidthDp = htods(smallestScreenWidthDp);
- screenWidthDp = htods(screenWidthDp);
- screenHeightDp = htods(screenHeightDp);
+void ResTable_config::copyFromDtoH_slow(const ResTable_config& o) {
+ copyFromDeviceNoSwap(o);
+ size = sizeof(ResTable_config);
+ mcc = dtohs(mcc);
+ mnc = dtohs(mnc);
+ density = dtohs(density);
+ screenWidth = dtohs(screenWidth);
+ screenHeight = dtohs(screenHeight);
+ sdkVersion = dtohs(sdkVersion);
+ minorVersion = dtohs(minorVersion);
+ smallestScreenWidthDp = dtohs(smallestScreenWidthDp);
+ screenWidthDp = dtohs(screenWidthDp);
+ screenHeightDp = dtohs(screenHeightDp);
+}
+
+void ResTable_config::swapHtoD_slow() {
+ size = htodl(size);
+ mcc = htods(mcc);
+ mnc = htods(mnc);
+ density = htods(density);
+ screenWidth = htods(screenWidth);
+ screenHeight = htods(screenHeight);
+ sdkVersion = htods(sdkVersion);
+ minorVersion = htods(minorVersion);
+ smallestScreenWidthDp = htods(smallestScreenWidthDp);
+ screenWidthDp = htods(screenWidthDp);
+ screenHeightDp = htods(screenHeightDp);
}
/* static */ inline int compareLocales(const ResTable_config &l, const ResTable_config &r) {
@@ -2149,7 +2137,7 @@ void ResTable_config::swapHtoD() {
// systems should happen very infrequently (if at all.)
// The comparison code relies on memcmp low-level optimizations that make it
// more efficient than strncmp.
- const char emptyScript[sizeof(l.localeScript)] = {'\0', '\0', '\0', '\0'};
+ static constexpr char emptyScript[sizeof(l.localeScript)] = {'\0', '\0', '\0', '\0'};
const char *lScript = l.localeScriptWasComputed ? emptyScript : l.localeScript;
const char *rScript = r.localeScriptWasComputed ? emptyScript : r.localeScript;
diff --git a/libs/androidfw/Util.cpp b/libs/androidfw/Util.cpp
index be55fe8b4bb6..86c459fb4647 100644
--- a/libs/androidfw/Util.cpp
+++ b/libs/androidfw/Util.cpp
@@ -32,13 +32,18 @@ namespace android {
namespace util {
void ReadUtf16StringFromDevice(const uint16_t* src, size_t len, std::string* out) {
- char buf[5];
- while (*src && len != 0) {
- char16_t c = static_cast<char16_t>(dtohs(*src));
- utf16_to_utf8(&c, 1, buf, sizeof(buf));
- out->append(buf, strlen(buf));
- ++src;
- --len;
+ static constexpr bool kDeviceEndiannessSame = dtohs(0x1001) == 0x1001;
+ if constexpr (kDeviceEndiannessSame) {
+ *out = Utf16ToUtf8({(const char16_t*)src, strnlen16((const char16_t*)src, len)});
+ } else {
+ char buf[5];
+ while (*src && len != 0) {
+ char16_t c = static_cast<char16_t>(dtohs(*src));
+ utf16_to_utf8(&c, 1, buf, sizeof(buf));
+ out->append(buf, strlen(buf));
+ ++src;
+ --len;
+ }
}
}
@@ -63,8 +68,10 @@ std::string Utf16ToUtf8(StringPiece16 utf16) {
}
std::string utf8;
- utf8.resize(utf8_length);
- utf16_to_utf8(utf16.data(), utf16.length(), &*utf8.begin(), utf8_length + 1);
+ utf8.resize_and_overwrite(utf8_length, [&utf16](char* data, size_t size) {
+ utf16_to_utf8(utf16.data(), utf16.length(), data, size + 1);
+ return size;
+ });
return utf8;
}
diff --git a/libs/androidfw/include/androidfw/ApkAssets.h b/libs/androidfw/include/androidfw/ApkAssets.h
index 231808beb718..3f6f4661f2f7 100644
--- a/libs/androidfw/include/androidfw/ApkAssets.h
+++ b/libs/androidfw/include/androidfw/ApkAssets.h
@@ -116,7 +116,7 @@ class ApkAssets : public RefBase {
return resources_asset_ != nullptr && resources_asset_->isAllocated();
}
- bool IsUpToDate() const;
+ UpToDate IsUpToDate() const;
// DANGER!
// This is a destructive method that rips the assets provider out of ApkAssets object.
diff --git a/libs/androidfw/include/androidfw/AssetsProvider.h b/libs/androidfw/include/androidfw/AssetsProvider.h
index d33c325ff369..037f684f5b78 100644
--- a/libs/androidfw/include/androidfw/AssetsProvider.h
+++ b/libs/androidfw/include/androidfw/AssetsProvider.h
@@ -14,8 +14,7 @@
* limitations under the License.
*/
-#ifndef ANDROIDFW_ASSETSPROVIDER_H
-#define ANDROIDFW_ASSETSPROVIDER_H
+#pragma once
#include <memory>
#include <string>
@@ -37,6 +36,12 @@ namespace android {
struct AssetsProvider {
static constexpr off64_t kUnknownLength = -1;
+ static std::unique_ptr<AssetsProvider> CreateWithOverride(
+ std::unique_ptr<AssetsProvider> provider, std::unique_ptr<AssetsProvider> override);
+
+ static std::unique_ptr<AssetsProvider> CreateFromNullable(
+ std::unique_ptr<AssetsProvider> nullable);
+
// Opens a file for reading. If `file_exists` is not null, it will be set to `true` if the file
// exists. This is useful for determining if the file exists but was unable to be opened due to
// an I/O error.
@@ -58,7 +63,7 @@ struct AssetsProvider {
WARN_UNUSED virtual const std::string& GetDebugName() const = 0;
// Returns whether the interface provides the most recent version of its files.
- WARN_UNUSED virtual bool IsUpToDate() const = 0;
+ WARN_UNUSED virtual UpToDate IsUpToDate() const = 0;
// Creates an Asset from a file on disk.
static std::unique_ptr<Asset> CreateAssetFromFile(const std::string& path);
@@ -95,7 +100,7 @@ struct ZipAssetsProvider : public AssetsProvider {
WARN_UNUSED std::optional<std::string_view> GetPath() const override;
WARN_UNUSED const std::string& GetDebugName() const override;
- WARN_UNUSED bool IsUpToDate() const override;
+ WARN_UNUSED UpToDate IsUpToDate() const override;
WARN_UNUSED std::optional<uint32_t> GetCrc(std::string_view path) const;
~ZipAssetsProvider() override = default;
@@ -106,7 +111,7 @@ struct ZipAssetsProvider : public AssetsProvider {
private:
struct PathOrDebugName;
ZipAssetsProvider(ZipArchive* handle, PathOrDebugName&& path, package_property_t flags,
- time_t last_mod_time);
+ ModDate last_mod_time);
struct PathOrDebugName {
static PathOrDebugName Path(std::string value) {
@@ -135,7 +140,7 @@ struct ZipAssetsProvider : public AssetsProvider {
std::unique_ptr<ZipArchive, ZipCloser> zip_handle_;
PathOrDebugName name_;
package_property_t flags_;
- time_t last_mod_time_;
+ ModDate last_mod_time_;
};
// Supplies assets from a root directory.
@@ -147,7 +152,7 @@ struct DirectoryAssetsProvider : public AssetsProvider {
WARN_UNUSED std::optional<std::string_view> GetPath() const override;
WARN_UNUSED const std::string& GetDebugName() const override;
- WARN_UNUSED bool IsUpToDate() const override;
+ WARN_UNUSED UpToDate IsUpToDate() const override;
~DirectoryAssetsProvider() override = default;
protected:
@@ -156,9 +161,9 @@ struct DirectoryAssetsProvider : public AssetsProvider {
bool* file_exists) const override;
private:
- explicit DirectoryAssetsProvider(std::string&& path, time_t last_mod_time);
+ explicit DirectoryAssetsProvider(std::string&& path, ModDate last_mod_time);
std::string dir_;
- time_t last_mod_time_;
+ ModDate last_mod_time_;
};
// Supplies assets from a `primary` asset provider and falls back to supplying assets from the
@@ -172,7 +177,7 @@ struct MultiAssetsProvider : public AssetsProvider {
WARN_UNUSED std::optional<std::string_view> GetPath() const override;
WARN_UNUSED const std::string& GetDebugName() const override;
- WARN_UNUSED bool IsUpToDate() const override;
+ WARN_UNUSED UpToDate IsUpToDate() const override;
~MultiAssetsProvider() override = default;
protected:
@@ -199,7 +204,7 @@ struct EmptyAssetsProvider : public AssetsProvider {
WARN_UNUSED std::optional<std::string_view> GetPath() const override;
WARN_UNUSED const std::string& GetDebugName() const override;
- WARN_UNUSED bool IsUpToDate() const override;
+ WARN_UNUSED UpToDate IsUpToDate() const override;
~EmptyAssetsProvider() override = default;
protected:
@@ -212,5 +217,3 @@ struct EmptyAssetsProvider : public AssetsProvider {
};
} // namespace android
-
-#endif /* ANDROIDFW_ASSETSPROVIDER_H */
diff --git a/libs/androidfw/include/androidfw/Idmap.h b/libs/androidfw/include/androidfw/Idmap.h
index d1db13f53069..0c0856315d8f 100644
--- a/libs/androidfw/include/androidfw/Idmap.h
+++ b/libs/androidfw/include/androidfw/Idmap.h
@@ -14,8 +14,7 @@
* limitations under the License.
*/
-#ifndef IDMAP_H_
-#define IDMAP_H_
+#pragma once
#include <memory>
#include <string>
@@ -32,6 +31,31 @@
namespace android {
+// An enum that tracks more states than just 'up to date' or 'not' for a resources container:
+// there are several cases where we know for sure that the object can't change and won't get
+// out of date. Reporting those states to the managed layer allows it to stop checking here
+// completely, speeding up the cache lookups by dozens of milliseconds.
+enum class UpToDate : int { False, True, Always };
+
+// Combines two UpToDate values, and only accesses the second one if it matters to the result.
+template <class Getter>
+UpToDate combine(UpToDate first, Getter secondGetter) {
+ switch (first) {
+ case UpToDate::False:
+ return UpToDate::False;
+ case UpToDate::True: {
+ const auto second = secondGetter();
+ return second == UpToDate::False ? UpToDate::False : UpToDate::True;
+ }
+ case UpToDate::Always:
+ return secondGetter();
+ }
+}
+
+inline UpToDate fromBool(bool value) {
+ return value ? UpToDate::True : UpToDate::False;
+}
+
class LoadedIdmap;
class IdmapResMap;
struct Idmap_header;
@@ -197,7 +221,7 @@ class LoadedIdmap {
// Returns whether the idmap file on disk has not been modified since the construction of this
// LoadedIdmap.
- bool IsUpToDate() const;
+ UpToDate IsUpToDate() const;
protected:
// Exposed as protected so that tests can subclass and mock this class out.
@@ -237,5 +261,3 @@ class LoadedIdmap {
};
} // namespace android
-
-#endif // IDMAP_H_
diff --git a/libs/androidfw/include/androidfw/ResourceTypes.h b/libs/androidfw/include/androidfw/ResourceTypes.h
index 8b2871c21a1e..30594dcfa939 100644
--- a/libs/androidfw/include/androidfw/ResourceTypes.h
+++ b/libs/androidfw/include/androidfw/ResourceTypes.h
@@ -47,6 +47,8 @@
namespace android {
+constexpr const bool kDeviceEndiannessSame = dtohs(0x1001) == 0x1001;
+
constexpr const uint32_t kIdmapMagic = 0x504D4449u;
constexpr const uint32_t kIdmapCurrentVersion = 0x0000000Bu;
@@ -408,7 +410,16 @@ struct Res_value
typedef uint32_t data_type;
data_type data;
- void copyFrom_dtoh(const Res_value& src);
+ void copyFrom_dtoh(const Res_value& src) {
+ if constexpr (kDeviceEndiannessSame) {
+ *this = src;
+ } else {
+ copyFrom_dtoh_slow(src);
+ }
+ }
+
+ private:
+ void copyFrom_dtoh_slow(const Res_value& src);
};
/**
@@ -1254,11 +1265,32 @@ struct ResTable_config
// Varies in length from 3 to 8 chars. Zero-filled value.
char localeNumberingSystem[8];
- void copyFromDeviceNoSwap(const ResTable_config& o);
-
- void copyFromDtoH(const ResTable_config& o);
-
- void swapHtoD();
+ void copyFromDeviceNoSwap(const ResTable_config& o) {
+ const auto o_size = dtohl(o.size);
+ if (o_size >= sizeof(ResTable_config)) [[likely]] {
+ *this = o;
+ } else {
+ memcpy(this, &o, o_size);
+ memset(((uint8_t*)this) + o_size, 0, sizeof(ResTable_config) - o_size);
+ }
+ this->size = sizeof(*this);
+ }
+
+ void copyFromDtoH(const ResTable_config& o) {
+ if constexpr (kDeviceEndiannessSame) {
+ copyFromDeviceNoSwap(o);
+ } else {
+ copyFromDtoH_slow(o);
+ }
+ }
+
+ void swapHtoD() {
+ if constexpr (kDeviceEndiannessSame) {
+ ; // noop
+ } else {
+ swapHtoD_slow();
+ }
+ }
int compare(const ResTable_config& o) const;
int compareLogical(const ResTable_config& o) const;
@@ -1384,6 +1416,10 @@ struct ResTable_config
bool isBetterThanBeforeLocale(const ResTable_config& o, const ResTable_config* requested) const;
String8 toString() const;
+
+ private:
+ void copyFromDtoH_slow(const ResTable_config& o);
+ void swapHtoD_slow();
};
/**
diff --git a/libs/androidfw/include/androidfw/misc.h b/libs/androidfw/include/androidfw/misc.h
index c9ba8a01a5e9..d8ca64a174a2 100644
--- a/libs/androidfw/include/androidfw/misc.h
+++ b/libs/androidfw/include/androidfw/misc.h
@@ -15,6 +15,7 @@
*/
#pragma once
+#include <sys/stat.h>
#include <time.h>
//
@@ -64,10 +65,15 @@ ModDate getFileModDate(const char* fileName);
/* same, but also returns -1 if the file has already been deleted */
ModDate getFileModDate(int fd);
+// Extract the modification date from the stat structure.
+ModDate getModDate(const struct ::stat& st);
+
// Check if |path| or |fd| resides on a readonly filesystem.
bool isReadonlyFilesystem(const char* path);
bool isReadonlyFilesystem(int fd);
+bool isKnownWritablePath(const char* path);
+
} // namespace android
// Whoever uses getFileModDate() will need this as well
diff --git a/libs/androidfw/misc.cpp b/libs/androidfw/misc.cpp
index 32f3624a3aee..26eb320805c9 100644
--- a/libs/androidfw/misc.cpp
+++ b/libs/androidfw/misc.cpp
@@ -16,10 +16,10 @@
#define LOG_TAG "misc"
-//
-// Miscellaneous utility functions.
-//
-#include <androidfw/misc.h>
+#include "androidfw/misc.h"
+
+#include <errno.h>
+#include <sys/stat.h>
#include "android-base/logging.h"
@@ -28,9 +28,7 @@
#include <sys/vfs.h>
#endif // __linux__
-#include <errno.h>
-#include <sys/stat.h>
-
+#include <array>
#include <cstdio>
#include <cstring>
#include <tuple>
@@ -40,28 +38,26 @@ namespace android {
/*
* Get a file's type.
*/
-FileType getFileType(const char* fileName)
-{
- struct stat sb;
-
- if (stat(fileName, &sb) < 0) {
- if (errno == ENOENT || errno == ENOTDIR)
- return kFileTypeNonexistent;
- else {
- PLOG(ERROR) << "getFileType(): stat(" << fileName << ") failed";
- return kFileTypeUnknown;
- }
- } else {
- if (S_ISREG(sb.st_mode))
- return kFileTypeRegular;
- else if (S_ISDIR(sb.st_mode))
- return kFileTypeDirectory;
- else if (S_ISCHR(sb.st_mode))
- return kFileTypeCharDev;
- else if (S_ISBLK(sb.st_mode))
- return kFileTypeBlockDev;
- else if (S_ISFIFO(sb.st_mode))
- return kFileTypeFifo;
+FileType getFileType(const char* fileName) {
+ struct stat sb;
+ if (stat(fileName, &sb) < 0) {
+ if (errno == ENOENT || errno == ENOTDIR)
+ return kFileTypeNonexistent;
+ else {
+ PLOG(ERROR) << "getFileType(): stat(" << fileName << ") failed";
+ return kFileTypeUnknown;
+ }
+ } else {
+ if (S_ISREG(sb.st_mode))
+ return kFileTypeRegular;
+ else if (S_ISDIR(sb.st_mode))
+ return kFileTypeDirectory;
+ else if (S_ISCHR(sb.st_mode))
+ return kFileTypeCharDev;
+ else if (S_ISBLK(sb.st_mode))
+ return kFileTypeBlockDev;
+ else if (S_ISFIFO(sb.st_mode))
+ return kFileTypeFifo;
#if defined(S_ISLNK)
else if (S_ISLNK(sb.st_mode))
return kFileTypeSymlink;
@@ -75,7 +71,7 @@ FileType getFileType(const char* fileName)
}
}
-static ModDate getModDate(const struct stat& st) {
+ModDate getModDate(const struct stat& st) {
#ifdef _WIN32
return st.st_mtime;
#elif defined(__APPLE__)
@@ -113,8 +109,14 @@ bool isReadonlyFilesystem(const char*) {
bool isReadonlyFilesystem(int) {
return false;
}
+bool isKnownWritablePath(const char*) {
+ return false;
+}
#else // __linux__
bool isReadonlyFilesystem(const char* path) {
+ if (isKnownWritablePath(path)) {
+ return false;
+ }
struct statfs sfs;
if (::statfs(path, &sfs)) {
PLOG(ERROR) << "isReadonlyFilesystem(): statfs(" << path << ") failed";
@@ -131,6 +133,13 @@ bool isReadonlyFilesystem(int fd) {
}
return (sfs.f_flags & ST_RDONLY) != 0;
}
+
+bool isKnownWritablePath(const char* path) {
+ // We know that all paths in /data/ are writable.
+ static constexpr char kRwPrefix[] = "/data/";
+ return strncmp(kRwPrefix, path, std::size(kRwPrefix) - 1) == 0;
+}
+
#endif // __linux__
} // namespace android
diff --git a/libs/androidfw/tests/Idmap_test.cpp b/libs/androidfw/tests/Idmap_test.cpp
index cb2e56f5f5e4..22b9e69500d9 100644
--- a/libs/androidfw/tests/Idmap_test.cpp
+++ b/libs/androidfw/tests/Idmap_test.cpp
@@ -218,10 +218,11 @@ TEST_F(IdmapTest, OverlayAssetsIsUpToDate) {
auto apk_assets = ApkAssets::LoadOverlay(temp_file.path);
ASSERT_NE(nullptr, apk_assets);
- ASSERT_TRUE(apk_assets->IsUpToDate());
+ ASSERT_TRUE(apk_assets->IsOverlay());
+ ASSERT_EQ(UpToDate::True, apk_assets->IsUpToDate());
unlink(temp_file.path);
- ASSERT_FALSE(apk_assets->IsUpToDate());
+ ASSERT_EQ(UpToDate::False, apk_assets->IsUpToDate());
const auto sleep_duration =
std::chrono::nanoseconds(std::max(kModDateResolutionNs, 1'000'000ull));
@@ -230,7 +231,27 @@ TEST_F(IdmapTest, OverlayAssetsIsUpToDate) {
base::WriteStringToFile("hello", temp_file.path);
std::this_thread::sleep_for(sleep_duration);
- ASSERT_FALSE(apk_assets->IsUpToDate());
+ ASSERT_EQ(UpToDate::False, apk_assets->IsUpToDate());
+}
+
+TEST(IdmapTestUpToDate, Combine) {
+ ASSERT_EQ(UpToDate::False, combine(UpToDate::False, [] {
+ ADD_FAILURE(); // Shouldn't get called at all.
+ return UpToDate::False;
+ }));
+
+ ASSERT_EQ(UpToDate::False, combine(UpToDate::True, [] { return UpToDate::False; }));
+
+ ASSERT_EQ(UpToDate::True, combine(UpToDate::True, [] { return UpToDate::True; }));
+ ASSERT_EQ(UpToDate::True, combine(UpToDate::True, [] { return UpToDate::Always; }));
+ ASSERT_EQ(UpToDate::True, combine(UpToDate::Always, [] { return UpToDate::True; }));
+
+ ASSERT_EQ(UpToDate::Always, combine(UpToDate::Always, [] { return UpToDate::Always; }));
+}
+
+TEST(IdmapTestUpToDate, FromBool) {
+ ASSERT_EQ(UpToDate::False, fromBool(false));
+ ASSERT_EQ(UpToDate::True, fromBool(true));
}
} // namespace
diff --git a/libs/hwui/aconfig/hwui_flags.aconfig b/libs/hwui/aconfig/hwui_flags.aconfig
index 62fd7d358123..d3fc91b65829 100644
--- a/libs/hwui/aconfig/hwui_flags.aconfig
+++ b/libs/hwui/aconfig/hwui_flags.aconfig
@@ -137,6 +137,14 @@ flag {
}
flag {
+ name: "shader_color_space"
+ is_exported: true
+ namespace: "core_graphics"
+ description: "API to set the working colorspace of a Shader or ColorFilter"
+ bug: "299670828"
+}
+
+flag {
name: "query_global_priority"
namespace: "core_graphics"
description: "Attempt to query whether the vulkan driver supports the requested global priority before queue creation."
@@ -174,7 +182,7 @@ flag {
flag {
name: "early_preload_gl_context"
namespace: "core_graphics"
- description: "Initialize GL context and GraphicBufferAllocater init on renderThread preload. This improves app startup time for apps using GL."
+ description: "Preload GL context on renderThread preload. This improves app startup time for apps using GL."
bug: "383612849"
}
@@ -187,4 +195,12 @@ flag {
metadata {
purpose: PURPOSE_BUGFIX
}
+}
+
+flag {
+ name: "early_preinit_buffer_allocator"
+ namespace: "core_graphics"
+ description: "Initialize GraphicBufferAllocater on ViewRootImpl init, to avoid blocking on init during buffer allocation, improving app launch latency."
+ bug: "389908734"
+ is_fixed_read_only: true
} \ No newline at end of file
diff --git a/libs/hwui/jni/Shader.cpp b/libs/hwui/jni/Shader.cpp
index eadb9dea566f..45f0fe0288a4 100644
--- a/libs/hwui/jni/Shader.cpp
+++ b/libs/hwui/jni/Shader.cpp
@@ -266,11 +266,17 @@ static jlong RuntimeShader_getNativeFinalizer(JNIEnv*, jobject) {
return static_cast<jlong>(reinterpret_cast<uintptr_t>(&SkRuntimeShaderBuilder_delete));
}
-static jlong RuntimeShader_create(JNIEnv* env, jobject, jlong shaderBuilder, jlong matrixPtr) {
+static jlong RuntimeShader_create(JNIEnv* env, jobject, jlong shaderBuilder, jlong matrixPtr,
+ jlong colorSpacePtr) {
SkRuntimeShaderBuilder* builder = reinterpret_cast<SkRuntimeShaderBuilder*>(shaderBuilder);
const SkMatrix* matrix = reinterpret_cast<const SkMatrix*>(matrixPtr);
+ auto colorSpace = GraphicsJNI::getNativeColorSpace(colorSpacePtr);
sk_sp<SkShader> shader = builder->makeShader(matrix);
ThrowIAE_IfNull(env, shader);
+ if (colorSpace) {
+ shader = shader->makeWithWorkingColorSpace(colorSpace);
+ ThrowIAE_IfNull(env, shader);
+ }
return reinterpret_cast<jlong>(shader.release());
}
@@ -350,6 +356,10 @@ static void RuntimeShader_updateChild(JNIEnv* env, jobject, jlong shaderBuilder,
UpdateChild(env, builder, name.c_str(), childEffect);
}
+static void RuntimeShader_no(JNIEnv* env) {
+ jniThrowRuntimeException(env, "Not supported");
+}
+
///////////////////////////////////////////////////////////////////////////////////////////////
static const JNINativeMethod gShaderMethods[] = {
@@ -379,7 +389,8 @@ static const JNINativeMethod gComposeShaderMethods[] = {
static const JNINativeMethod gRuntimeShaderMethods[] = {
{"nativeGetFinalizer", "()J", (void*)RuntimeShader_getNativeFinalizer},
- {"nativeCreateShader", "(JJ)J", (void*)RuntimeShader_create},
+ {"nativeCreateShader", "(JJ)J", (void*)RuntimeShader_no},
+ {"nativeCreateShader", "(JJJ)J", (void*)RuntimeShader_create},
{"nativeCreateBuilder", "(Ljava/lang/String;)J", (void*)RuntimeShader_createShaderBuilder},
{"nativeUpdateUniforms", "(JLjava/lang/String;[FZ)V",
(void*)RuntimeShader_updateFloatArrayUniforms},
diff --git a/libs/hwui/jni/android_graphics_HardwareRenderer.cpp b/libs/hwui/jni/android_graphics_HardwareRenderer.cpp
index df9f83036709..99e7740d66d2 100644
--- a/libs/hwui/jni/android_graphics_HardwareRenderer.cpp
+++ b/libs/hwui/jni/android_graphics_HardwareRenderer.cpp
@@ -52,6 +52,9 @@
#include <renderthread/RenderThread.h>
#include <src/image/SkImage_Base.h>
#include <thread/CommonPool.h>
+#ifdef __ANDROID__
+#include <ui/GraphicBufferAllocator.h>
+#endif
#include <utils/Color.h>
#include <utils/RefBase.h>
#include <utils/StrongPointer.h>
@@ -849,6 +852,17 @@ static void android_view_ThreadedRenderer_preload(JNIEnv*, jclass) {
RenderProxy::preload();
}
+static void android_view_ThreadedRenderer_preInitBufferAllocator(JNIEnv*, jclass) {
+#ifdef __ANDROID__
+ CommonPool::async([] {
+ ATRACE_NAME("preInitBufferAllocator:GraphicBufferAllocator");
+ // This involves several binder calls which we do not want blocking
+ // critical path of the activity that is launching.
+ GraphicBufferAllocator::getInstance();
+ });
+#endif
+}
+
static void android_view_ThreadedRenderer_setRtAnimationsEnabled(JNIEnv* env, jobject clazz,
jboolean enabled) {
RenderProxy::setRtAnimationsEnabled(enabled);
@@ -1040,6 +1054,8 @@ static const JNINativeMethod gMethods[] = {
(void*)android_view_ThreadedRenderer_setDisplayDensityDpi},
{"nInitDisplayInfo", "(IIFIJJZZZ)V", (void*)android_view_ThreadedRenderer_initDisplayInfo},
{"preload", "()V", (void*)android_view_ThreadedRenderer_preload},
+ {"preInitBufferAllocator", "()V",
+ (void*)android_view_ThreadedRenderer_preInitBufferAllocator},
{"isWebViewOverlaysEnabled", "()Z",
(void*)android_view_ThreadedRenderer_isWebViewOverlaysEnabled},
{"nSetDrawingEnabled", "(Z)V", (void*)android_view_ThreadedRenderer_setDrawingEnabled},
diff --git a/location/Android.bp b/location/Android.bp
index bc02d1f852de..80556a2376bf 100644
--- a/location/Android.bp
+++ b/location/Android.bp
@@ -42,6 +42,7 @@ java_sdk_library {
"FlaggedApi",
],
},
+ jarjar_prefix: "com.android.internal.hidden_from_bootclasspath",
}
platform_compat_config {
diff --git a/media/java/android/media/AudioDeviceVolumeManager.java b/media/java/android/media/AudioDeviceVolumeManager.java
index e1fbfea19235..892a8612d74a 100644
--- a/media/java/android/media/AudioDeviceVolumeManager.java
+++ b/media/java/android/media/AudioDeviceVolumeManager.java
@@ -86,10 +86,10 @@ public class AudioDeviceVolumeManager {
/**
* @hide
* Interface to receive volume changes on a device that behaves in absolute volume mode.
- * @see #setDeviceAbsoluteMultiVolumeBehavior(AudioDeviceAttributes, List, Executor,
- * OnAudioDeviceVolumeChangeListener)
- * @see #setDeviceAbsoluteVolumeBehavior(AudioDeviceAttributes, VolumeInfo, Executor,
- * OnAudioDeviceVolumeChangeListener)
+ * @see #setDeviceAbsoluteMultiVolumeBehavior(AudioDeviceAttributes, List, boolean, Executor,
+ * OnAudioDeviceVolumeChangedListener)
+ * @see #setDeviceAbsoluteVolumeBehavior(AudioDeviceAttributes, VolumeInfo, boolean, Executor,
+ * OnAudioDeviceVolumeChangedListener)
*/
public interface OnAudioDeviceVolumeChangedListener {
/**
@@ -203,6 +203,9 @@ public class AudioDeviceVolumeManager {
* volume updates to apply on that device
* @param device the audio device set to absolute volume mode
* @param volume the type of volume this device responds to
+ * @param handlesVolumeAdjustment whether the controller handles volume adjustments separately
+ * from volume changes. If true, adjustments from {@link AudioManager#adjustStreamVolume}
+ * will be sent via {@link OnAudioDeviceVolumeChangedListener#onAudioDeviceVolumeAdjusted}.
* @param executor the Executor used for receiving volume updates through the listener
* @param vclistener the callback for volume updates
*/
@@ -211,13 +214,13 @@ public class AudioDeviceVolumeManager {
public void setDeviceAbsoluteVolumeBehavior(
@NonNull AudioDeviceAttributes device,
@NonNull VolumeInfo volume,
+ boolean handlesVolumeAdjustment,
@NonNull @CallbackExecutor Executor executor,
- @NonNull OnAudioDeviceVolumeChangedListener vclistener,
- boolean handlesVolumeAdjustment) {
+ @NonNull OnAudioDeviceVolumeChangedListener vclistener) {
final ArrayList<VolumeInfo> volumes = new ArrayList<>(1);
volumes.add(volume);
- setDeviceAbsoluteMultiVolumeBehavior(device, volumes, executor, vclistener,
- handlesVolumeAdjustment);
+ setDeviceAbsoluteMultiVolumeBehavior(device, volumes, handlesVolumeAdjustment, executor,
+ vclistener);
}
/**
@@ -226,20 +229,20 @@ public class AudioDeviceVolumeManager {
* registers a listener for receiving volume updates to apply on that device
* @param device the audio device set to absolute multi-volume mode
* @param volumes the list of volumes the given device responds to
+ * @param handlesVolumeAdjustment whether the controller handles volume adjustments separately
+ * from volume changes. If true, adjustments from {@link AudioManager#adjustStreamVolume}
+ * will be sent via {@link OnAudioDeviceVolumeChangedListener#onAudioDeviceVolumeAdjusted}.
* @param executor the Executor used for receiving volume updates through the listener
* @param vclistener the callback for volume updates
- * @param handlesVolumeAdjustment whether the controller handles volume adjustments separately
- * from volume changes. If true, adjustments from {@link AudioManager#adjustStreamVolume}
- * will be sent via {@link OnAudioDeviceVolumeChangedListener#onAudioDeviceVolumeAdjusted}.
*/
@RequiresPermission(anyOf = { android.Manifest.permission.MODIFY_AUDIO_ROUTING,
android.Manifest.permission.BLUETOOTH_PRIVILEGED })
public void setDeviceAbsoluteMultiVolumeBehavior(
@NonNull AudioDeviceAttributes device,
@NonNull List<VolumeInfo> volumes,
+ boolean handlesVolumeAdjustment,
@NonNull @CallbackExecutor Executor executor,
- @NonNull OnAudioDeviceVolumeChangedListener vclistener,
- boolean handlesVolumeAdjustment) {
+ @NonNull OnAudioDeviceVolumeChangedListener vclistener) {
baseSetDeviceAbsoluteMultiVolumeBehavior(device, volumes, executor, vclistener,
handlesVolumeAdjustment, AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE);
}
@@ -249,11 +252,14 @@ public class AudioDeviceVolumeManager {
* Configures a device to use absolute volume model, and registers a listener for receiving
* volume updates to apply on that device.
*
- * Should be used instead of {@link #setDeviceAbsoluteVolumeBehavior} when there is no reliable
- * way to set the device's volume to a percentage.
+ * <p>Should be used instead of {@link #setDeviceAbsoluteVolumeBehavior} when there is no
+ * reliable way to set the device's volume to a percentage.
*
* @param device the audio device set to absolute volume mode
* @param volume the type of volume this device responds to
+ * @param handlesVolumeAdjustment whether the controller handles volume adjustments separately
+ * from volume changes. If true, adjustments from {@link AudioManager#adjustStreamVolume}
+ * will be sent via {@link OnAudioDeviceVolumeChangedListener#onAudioDeviceVolumeAdjusted}.
* @param executor the Executor used for receiving volume updates through the listener
* @param vclistener the callback for volume updates
*/
@@ -262,13 +268,13 @@ public class AudioDeviceVolumeManager {
public void setDeviceAbsoluteVolumeAdjustOnlyBehavior(
@NonNull AudioDeviceAttributes device,
@NonNull VolumeInfo volume,
+ boolean handlesVolumeAdjustment,
@NonNull @CallbackExecutor Executor executor,
- @NonNull OnAudioDeviceVolumeChangedListener vclistener,
- boolean handlesVolumeAdjustment) {
+ @NonNull OnAudioDeviceVolumeChangedListener vclistener) {
final ArrayList<VolumeInfo> volumes = new ArrayList<>(1);
volumes.add(volume);
- setDeviceAbsoluteMultiVolumeAdjustOnlyBehavior(device, volumes, executor, vclistener,
- handlesVolumeAdjustment);
+ setDeviceAbsoluteMultiVolumeAdjustOnlyBehavior(device, volumes, handlesVolumeAdjustment,
+ executor, vclistener);
}
/**
@@ -276,11 +282,14 @@ public class AudioDeviceVolumeManager {
* Configures a device to use absolute volume model applied to different volume types, and
* registers a listener for receiving volume updates to apply on that device.
*
- * Should be used instead of {@link #setDeviceAbsoluteMultiVolumeBehavior} when there is
+ * <p>Should be used instead of {@link #setDeviceAbsoluteMultiVolumeBehavior} when there is
* no reliable way to set the device's volume to a percentage.
*
* @param device the audio device set to absolute multi-volume mode
* @param volumes the list of volumes the given device responds to
+ * @param handlesVolumeAdjustment whether the controller handles volume adjustments separately
+ * from volume changes. If true, adjustments from {@link AudioManager#adjustStreamVolume}
+ * will be sent via {@link OnAudioDeviceVolumeChangedListener#onAudioDeviceVolumeAdjusted}.
* @param executor the Executor used for receiving volume updates through the listener
* @param vclistener the callback for volume updates
*/
@@ -289,16 +298,16 @@ public class AudioDeviceVolumeManager {
public void setDeviceAbsoluteMultiVolumeAdjustOnlyBehavior(
@NonNull AudioDeviceAttributes device,
@NonNull List<VolumeInfo> volumes,
+ boolean handlesVolumeAdjustment,
@NonNull @CallbackExecutor Executor executor,
- @NonNull OnAudioDeviceVolumeChangedListener vclistener,
- boolean handlesVolumeAdjustment) {
+ @NonNull OnAudioDeviceVolumeChangedListener vclistener) {
baseSetDeviceAbsoluteMultiVolumeBehavior(device, volumes, executor, vclistener,
handlesVolumeAdjustment, AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_ADJUST_ONLY);
}
/**
* Base method for configuring a device to use absolute volume behavior, or one of its variants.
- * See {@link AudioManager#AbsoluteDeviceVolumeBehavior} for a list of allowed behaviors.
+ * See {@link AudioManager.AbsoluteDeviceVolumeBehavior} for a list of allowed behaviors.
*
* @param behavior the variant of absolute device volume behavior to adopt
*/
diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java
index 12d7f33a0d51..e01cb928e369 100644
--- a/media/java/android/media/AudioSystem.java
+++ b/media/java/android/media/AudioSystem.java
@@ -1754,13 +1754,21 @@ public class AudioSystem
@UnsupportedAppUsage
public static int setDeviceConnectionState(AudioDeviceAttributes attributes, int state,
int codecFormat) {
+ return setDeviceConnectionState(attributes, state, codecFormat, false /*deviceSwitch*/);
+ }
+
+ /**
+ * @hide
+ */
+ public static int setDeviceConnectionState(AudioDeviceAttributes attributes, int state,
+ int codecFormat, boolean deviceSwitch) {
android.media.audio.common.AudioPort port =
AidlConversion.api2aidl_AudioDeviceAttributes_AudioPort(attributes);
Parcel parcel = Parcel.obtain();
port.writeToParcel(parcel, 0);
parcel.setDataPosition(0);
try {
- return setDeviceConnectionState(state, parcel, codecFormat);
+ return setDeviceConnectionState(state, parcel, codecFormat, deviceSwitch);
} finally {
parcel.recycle();
}
@@ -1769,7 +1777,10 @@ public class AudioSystem
* @hide
*/
@UnsupportedAppUsage
- public static native int setDeviceConnectionState(int state, Parcel parcel, int codecFormat);
+ public static native int setDeviceConnectionState(int state, Parcel parcel, int codecFormat,
+ boolean deviceSwitch);
+
+
/** @hide */
@UnsupportedAppUsage
public static native int getDeviceConnectionState(int device, String device_address);
diff --git a/media/java/android/media/ExifInterface.java b/media/java/android/media/ExifInterface.java
index 23f87abaffed..b8259ef45de4 100644
--- a/media/java/android/media/ExifInterface.java
+++ b/media/java/android/media/ExifInterface.java
@@ -2037,9 +2037,10 @@ public class ExifInterface {
// Ignore exceptions in order to keep the compatibility with the old versions of
// ExifInterface.
mIsSupportedFile = false;
- Log.w(TAG, "Invalid image: ExifInterface got an unsupported image format file"
- + "(ExifInterface supports JPEG and some RAW image formats only) "
- + "or a corrupted JPEG file to ExifInterface.", e);
+ Log.d(
+ TAG,
+ "Invalid image: ExifInterface got an unsupported or corrupted image file",
+ e);
} finally {
addDefaultValuesForCompatibility();
diff --git a/media/java/android/media/MediaCodec.java b/media/java/android/media/MediaCodec.java
index fb1b5b57cce6..15c832392a22 100644
--- a/media/java/android/media/MediaCodec.java
+++ b/media/java/android/media/MediaCodec.java
@@ -4060,6 +4060,7 @@ final public class MediaCodec {
* Finish building a queue request and queue the buffers with tunings.
*/
public void queue() {
+ Trace.traceBegin(Trace.TRACE_TAG_VIDEO, "MediaCodec::queueRequest-queue#java");
if (!isAccessible()) {
throw new IllegalStateException("The request is stale");
}
@@ -4088,6 +4089,7 @@ final public class MediaCodec {
mTuningKeys, mTuningValues);
}
clear();
+ Trace.traceEnd(Trace.TRACE_TAG_VIDEO);
}
@NonNull QueueRequest clear() {
diff --git a/media/java/android/media/RingtoneManager.java b/media/java/android/media/RingtoneManager.java
index 0f24654879cd..021348153bb8 100644
--- a/media/java/android/media/RingtoneManager.java
+++ b/media/java/android/media/RingtoneManager.java
@@ -60,6 +60,7 @@ import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
+import java.util.Objects;
/**
* RingtoneManager provides access to ringtones, notification, and other types
@@ -810,9 +811,7 @@ public class RingtoneManager {
// Don't set the stream type
Ringtone ringtone = getRingtone(context, ringtoneUri, -1 /* streamType */,
volumeShaperConfig, false);
- if (Flags.enableRingtoneHapticsCustomization()
- && Utils.isRingtoneVibrationSettingsSupported(context)
- && Utils.hasVibration(ringtoneUri) && hasHapticChannels(ringtoneUri)) {
+ if (muteHapticChannelForVibration(context, ringtoneUri)) {
audioAttributes = new AudioAttributes.Builder(
audioAttributes).setHapticChannelsMuted(true).build();
}
@@ -1305,4 +1304,19 @@ public class RingtoneManager {
default: throw new IllegalArgumentException();
}
}
+
+ private static boolean muteHapticChannelForVibration(Context context, Uri ringtoneUri) {
+ final Uri vibrationUri = Utils.getVibrationUri(ringtoneUri);
+ // No vibration is specified
+ if (vibrationUri == null) {
+ return false;
+ }
+ // The user specified the synchronized pattern
+ if (Objects.equals(vibrationUri.toString(), Utils.SYNCHRONIZED_VIBRATION)) {
+ return false;
+ }
+ return Flags.enableRingtoneHapticsCustomization()
+ && Utils.isRingtoneVibrationSettingsSupported(context)
+ && hasHapticChannels(ringtoneUri);
+ }
}
diff --git a/media/java/android/media/Utils.java b/media/java/android/media/Utils.java
index 11bd221ec696..d6e27b0ffa75 100644
--- a/media/java/android/media/Utils.java
+++ b/media/java/android/media/Utils.java
@@ -66,6 +66,8 @@ public class Utils {
public static final String VIBRATION_URI_PARAM = "vibration_uri";
+ public static final String SYNCHRONIZED_VIBRATION = "synchronized";
+
/**
* Sorts distinct (non-intersecting) range array in ascending order.
* @throws java.lang.IllegalArgumentException if ranges are not distinct
@@ -757,8 +759,8 @@ public class Utils {
return null;
}
String filePath = vibrationUri.getPath();
- if (filePath == null) {
- Log.w(TAG, "The file path is null.");
+ if (filePath == null || filePath.equals(Utils.SYNCHRONIZED_VIBRATION)) {
+ Log.w(TAG, "Ignore the vibration parsing for file:" + filePath);
return null;
}
File vibrationFile = new File(filePath);
diff --git a/packages/LocalTransport/src/com/android/localtransport/LocalTransport.java b/packages/LocalTransport/src/com/android/localtransport/LocalTransport.java
index a3b06e8c71fc..5590ca725327 100644
--- a/packages/LocalTransport/src/com/android/localtransport/LocalTransport.java
+++ b/packages/LocalTransport/src/com/android/localtransport/LocalTransport.java
@@ -23,6 +23,7 @@ import android.app.backup.BackupAnnotations;
import android.app.backup.BackupDataInput;
import android.app.backup.BackupDataOutput;
import android.app.backup.BackupManagerMonitor;
+import android.app.backup.BackupRestoreEventLogger;
import android.app.backup.BackupRestoreEventLogger.DataTypeResult;
import android.app.backup.BackupTransport;
import android.app.backup.RestoreDescription;
@@ -38,6 +39,7 @@ import android.system.Os;
import android.system.StructStat;
import android.util.ArrayMap;
import android.util.Base64;
+import android.util.Dumpable;
import android.util.Log;
import com.android.tools.r8.keepanno.annotations.KeepTarget;
@@ -51,6 +53,8 @@ import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@@ -59,11 +63,14 @@ import java.util.List;
/**
* Backup transport for stashing stuff into a known location on disk, and
* later restoring from there. For testing only.
+ *
+ * <p>Note: the quickest way to build and sync this class is:
+ * {@code m LocalTransport && adb install $OUT/system/priv-app/LocalTransport/LocalTransport.apk}
*/
public class LocalTransport extends BackupTransport {
- private static final String TAG = "LocalTransport";
- private static final boolean DEBUG = false;
+ static final String TAG = "LocalTransport";
+ static final boolean DEBUG = Log.isLoggable(TAG, Log.VERBOSE);
private static final String TRANSPORT_DIR_NAME
= "com.android.localtransport.LocalTransport";
@@ -124,6 +131,8 @@ public class LocalTransport extends BackupTransport {
}
public LocalTransport(Context context, LocalTransportParameters parameters) {
+ Log.i(TAG, "Creating LocalTransport for user " + context.getUserId()
+ + " (DEBUG=" + DEBUG + ")");
mContext = context;
mParameters = parameters;
makeDataDirs();
@@ -910,36 +919,69 @@ public class LocalTransport extends BackupTransport {
return mMonitor;
}
- private class TestBackupManagerMonitor extends BackupManagerMonitor {
+ private class TestBackupManagerMonitor extends BackupManagerMonitor implements Dumpable {
+
+ private final List<DataTypeResult> mReceivedResults = new ArrayList<>();
+ private int mNumberReceivedEvents;
+
@Override
public void onEvent(Bundle event) {
- if (event == null || !mParameters.logAgentResults()) {
+ if (event == null) {
+ if (DEBUG) {
+ Log.w(TAG, "onEvent() called with null");
+ }
return;
}
-
- if (event.getInt(BackupManagerMonitor.EXTRA_LOG_EVENT_ID)
- == BackupManagerMonitor.LOG_EVENT_ID_AGENT_LOGGING_RESULTS) {
+ int eventId = event.getInt(BackupManagerMonitor.EXTRA_LOG_EVENT_ID);
+ if (eventId != BackupManagerMonitor.LOG_EVENT_ID_AGENT_LOGGING_RESULTS) {
+ if (DEBUG) Log.v(TAG, "ignoring event with id " + eventId);
+ return;
+ }
+ mNumberReceivedEvents++;
+ boolean logResults = mParameters.logAgentResults();
+ ArrayList<DataTypeResult> results = event.getParcelableArrayList(
+ BackupManagerMonitor.EXTRA_LOG_AGENT_LOGGING_RESULTS,
+ DataTypeResult.class);
+ int size = results.size();
+ if (size == 0) {
+ if (logResults) {
+ Log.i(TAG, "no agent_logging_results on event #" + mNumberReceivedEvents);
+ }
+ return;
+ }
+ if (logResults) {
Log.i(TAG, "agent_logging_results {");
- ArrayList<DataTypeResult> results = event.getParcelableArrayList(
- BackupManagerMonitor.EXTRA_LOG_AGENT_LOGGING_RESULTS,
- DataTypeResult.class);
- for (DataTypeResult result : results) {
- Log.i(TAG, "\tdataType: " + result.getDataType());
- Log.i(TAG, "\tsuccessCount: " + result.getSuccessCount());
- Log.i(TAG, "\tfailCount: " + result.getFailCount());
- Log.i(TAG, "\tmetadataHash: " + Arrays.toString(result.getMetadataHash()));
-
- if (!result.getErrors().isEmpty()) {
- Log.i(TAG, "\terrors {");
- for (String error : result.getErrors().keySet()) {
- Log.i(TAG, "\t\t" + error + ": " + result.getErrors().get(error));
- }
- Log.i(TAG, "\t}");
- }
-
- Log.i(TAG, "}");
+ }
+ for (int i = 0; i < size; i++) {
+ var result = results.get(i);
+ mReceivedResults.add(result);
+ if (logResults) {
+ Log.i(TAG, "\t" + BackupRestoreEventLogger.toString(result));
}
}
+ if (logResults) {
+ Log.i(TAG, "}");
+ }
+ }
+
+ @Override
+ public void dump(PrintWriter pw, String[] args) {
+ pw.println("TestBackupManagerMonitor");
+ pw.printf("%d events received", mNumberReceivedEvents);
+ if (mNumberReceivedEvents == 0) {
+ pw.println();
+ return;
+ }
+ int size = mReceivedResults.size();
+ if (size == 0) {
+ pw.println("; no results on them");
+ return;
+ }
+ pw.printf("; %d results on them:\n", size);
+ for (int i = 0; i < size; i++) {
+ DataTypeResult result = mReceivedResults.get(i);
+ pw.printf(" #%d: %s\n", i, BackupRestoreEventLogger.toString(result));
+ }
}
}
@@ -953,4 +995,60 @@ public class LocalTransport extends BackupTransport {
}
return mParameters.noRestrictedModePackages();
}
+
+ @Override
+ public String toString() {
+ try {
+ try (StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw)) {
+ dump(pw, /* args= */ null);
+ pw.flush();
+ return sw.toString();
+ }
+ } catch (IOException e) {
+ // Shouldn't happen...
+ Log.e(TAG, "toString(): failed to dump", e);
+ return super.toString();
+ }
+ }
+
+ void dump(PrintWriter pw, String[] args) {
+ pw.printf("mDataDir: %s\n", mDataDir);
+ pw.printf("mCurrentSetDir: %s\n", mCurrentSetDir);
+ pw.printf("mCurrentSetIncrementalDir: %s\n", mCurrentSetIncrementalDir);
+ pw.printf("mCurrentSetFullDir: %s\n", mCurrentSetFullDir);
+
+ pw.printf("mRestorePackages: %s\n", Arrays.toString(mRestorePackages));
+ pw.printf("mRestorePackage: %d\n", mRestorePackage);
+ pw.printf("mRestoreType: %d\n", mRestoreType);
+ pw.printf("mRestoreSetDir: %s\n", mRestoreSetDir);
+ pw.printf("mRestoreSetIncrementalDir: %s\n", mRestoreSetIncrementalDir);
+ pw.printf("mRestoreSetFullDir: %s\n", mRestoreSetFullDir);
+
+ pw.printf("mFullTargetPackage: %s\n", mFullTargetPackage);
+ pw.printf("mSocket: %s\n", mSocket);
+ pw.printf("mSocketInputStream: %s\n", mSocketInputStream);
+ pw.printf("mFullBackupOutputStream: %s\n", mFullBackupOutputStream);
+ dumpByteArray(pw, "mFullBackupBuffer", mFullBackupBuffer);
+ pw.printf("mFullBackupSize: %d\n", mFullBackupSize);
+
+ pw.printf("mCurFullRestoreStream: %s\n", mCurFullRestoreStream);
+ dumpByteArray(pw, "mFullRestoreBuffer", mFullRestoreBuffer);
+ if (mParameters == null) {
+ pw.println("No LocalTransportParameters");
+ } else {
+ pw.println(mParameters);
+ }
+ if (mMonitor instanceof Dumpable) {
+ ((Dumpable) mMonitor).dump(pw, args);
+ }
+
+ pw.printf("currentDestinationString(): %s\n", currentDestinationString());
+ pw.printf("dataManagementIntent(): %s\n", dataManagementIntent());
+ pw.printf("dataManagementIntentLabel(): %s\n", dataManagementIntentLabel());
+ pw.printf("transportDirName(): %s\n", transportDirName());
+ }
+
+ private void dumpByteArray(PrintWriter pw, String name, byte[] array) {
+ pw.printf("%s size: %d\n", name, (array == null ? 0 : array.length));
+ }
}
diff --git a/packages/LocalTransport/src/com/android/localtransport/LocalTransportParameters.java b/packages/LocalTransport/src/com/android/localtransport/LocalTransportParameters.java
index c980913f80c6..c7cfc5b4d304 100644
--- a/packages/LocalTransport/src/com/android/localtransport/LocalTransportParameters.java
+++ b/packages/LocalTransport/src/com/android/localtransport/LocalTransportParameters.java
@@ -16,11 +16,15 @@
package com.android.localtransport;
+import static com.android.localtransport.LocalTransport.DEBUG;
+import static com.android.localtransport.LocalTransport.TAG;
+
import android.content.ContentResolver;
import android.os.Handler;
import android.provider.Settings;
import android.util.KeyValueListParser;
import android.util.KeyValueSettingObserver;
+import android.util.Log;
import java.util.Arrays;
import java.util.List;
@@ -76,15 +80,30 @@ public class LocalTransportParameters extends KeyValueSettingObserver {
}
public String getSettingValue(ContentResolver resolver) {
- return Settings.Secure.getString(resolver, SETTING);
+ String value = Settings.Secure.getString(resolver, SETTING);
+ if (DEBUG) {
+ Log.d(TAG, "LocalTransportParameters.getSettingValue(): returning " + value);
+ }
+ return value;
}
public void update(KeyValueListParser parser) {
- mFakeEncryptionFlag = parser.getBoolean(KEY_FAKE_ENCRYPTION_FLAG, false);
- mIsNonIncrementalOnly = parser.getBoolean(KEY_NON_INCREMENTAL_ONLY, false);
- mIsDeviceTransfer = parser.getBoolean(KEY_IS_DEVICE_TRANSFER, false);
- mIsEncrypted = parser.getBoolean(KEY_IS_ENCRYPTED, false);
- mLogAgentResults = parser.getBoolean(KEY_LOG_AGENT_RESULTS, /* def */ false);
+ mFakeEncryptionFlag = parser.getBoolean(KEY_FAKE_ENCRYPTION_FLAG, /* def= */ false);
+ mIsNonIncrementalOnly = parser.getBoolean(KEY_NON_INCREMENTAL_ONLY, /* def= */ false);
+ mIsDeviceTransfer = parser.getBoolean(KEY_IS_DEVICE_TRANSFER, /* def= */ false);
+ mIsEncrypted = parser.getBoolean(KEY_IS_ENCRYPTED, /* def= */ false);
+ mLogAgentResults = parser.getBoolean(KEY_LOG_AGENT_RESULTS, /* def= */ false);
mNoRestrictedModePackages = parser.getString(KEY_NO_RESTRICTED_MODE_PACKAGES, /* def */ "");
}
+
+ @Override
+ public String toString() {
+ return "LocalTransportParameters[mFakeEncryptionFlag=" + mFakeEncryptionFlag
+ + ", mIsNonIncrementalOnly=" + mIsNonIncrementalOnly
+ + ", mIsDeviceTransfer=" + mIsDeviceTransfer
+ + ", mIsEncrypted=" + mIsEncrypted
+ + ", mLogAgentResults=" + mLogAgentResults
+ + ", noRestrictedModePackages()=" + noRestrictedModePackages()
+ + "]";
+ }
}
diff --git a/packages/LocalTransport/src/com/android/localtransport/LocalTransportService.java b/packages/LocalTransport/src/com/android/localtransport/LocalTransportService.java
index ac4f418b68f6..2f6c57a7b207 100644
--- a/packages/LocalTransport/src/com/android/localtransport/LocalTransportService.java
+++ b/packages/LocalTransport/src/com/android/localtransport/LocalTransportService.java
@@ -16,15 +16,28 @@
package com.android.localtransport;
+import static com.android.localtransport.LocalTransport.DEBUG;
+import static com.android.localtransport.LocalTransport.TAG;
+
+import android.annotation.Nullable;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
+import android.util.Log;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
public class LocalTransportService extends Service {
- private static LocalTransport sTransport = null;
+
+ @Nullable
+ private static LocalTransport sTransport;
@Override
public void onCreate() {
+ if (DEBUG) {
+ Log.d(TAG, "LocalTransportService.onCreate()");
+ }
if (sTransport == null) {
LocalTransportParameters parameters =
new LocalTransportParameters(getMainThreadHandler(), getContentResolver());
@@ -35,11 +48,27 @@ public class LocalTransportService extends Service {
@Override
public void onDestroy() {
+ if (DEBUG) {
+ Log.d(TAG, "LocalTransportService.onDestroy()");
+ }
sTransport.getParameters().stop();
}
@Override
public IBinder onBind(Intent intent) {
+ if (DEBUG) {
+ Log.d(TAG, "LocalTransportService.onBind(" + intent + "): parameters="
+ + sTransport.getParameters());
+ }
return sTransport.getBinder();
}
+
+ @Override
+ protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ if (sTransport == null) {
+ pw.println("No sTransport");
+ return;
+ }
+ sTransport.dump(pw, args);
+ }
}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/InstallLaunch.kt b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/InstallLaunch.kt
index c61a2ac9f0dd..481023ed5677 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/InstallLaunch.kt
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/InstallLaunch.kt
@@ -16,9 +16,6 @@
package com.android.packageinstaller.v2.ui
-import android.app.Activity.RESULT_CANCELED
-import android.app.Activity.RESULT_FIRST_USER
-import android.app.Activity.RESULT_OK
import android.app.AppOpsManager
import android.content.ActivityNotFoundException
import android.content.Intent
@@ -67,6 +64,7 @@ class InstallLaunch : FragmentActivity(), InstallActionListener {
InstallLaunch::class.java.packageName + ".callingPkgName"
private val LOG_TAG = InstallLaunch::class.java.simpleName
private const val TAG_DIALOG = "dialog"
+ private const val ARGS_SAVED_INTENT = "saved_intent"
}
/**
@@ -96,7 +94,15 @@ class InstallLaunch : FragmentActivity(), InstallActionListener {
intent.getStringExtra(EXTRA_CALLING_PKG_NAME),
intent.getIntExtra(EXTRA_CALLING_PKG_UID, Process.INVALID_UID)
)
- installViewModel!!.preprocessIntent(intent, info)
+
+ var savedIntent: Intent? = null
+ if (savedInstanceState != null) {
+ savedIntent = savedInstanceState.getParcelable(ARGS_SAVED_INTENT, Intent::class.java)
+ }
+ if (!intent.filterEquals(savedIntent)) {
+ installViewModel!!.preprocessIntent(intent, info)
+ }
+
installViewModel!!.currentInstallStage.observe(this) { installStage: InstallStage ->
onInstallStageChange(installStage)
}
@@ -344,6 +350,11 @@ class InstallLaunch : FragmentActivity(), InstallActionListener {
appOpsManager!!.stopWatchingMode(listener)
}
+ override fun onSaveInstanceState(outState: Bundle) {
+ outState.putParcelable(ARGS_SAVED_INTENT, intent)
+ super.onSaveInstanceState(outState)
+ }
+
override fun onDestroy() {
super.onDestroy()
while (activeUnknownSourcesListeners.isNotEmpty()) {
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/UninstallLaunch.kt b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/UninstallLaunch.kt
index c4ca27247575..0a02845e0dd3 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/UninstallLaunch.kt
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/UninstallLaunch.kt
@@ -16,7 +16,6 @@
package com.android.packageinstaller.v2.ui
-import android.app.Activity
import android.app.NotificationManager
import android.content.Intent
import android.os.Bundle
@@ -51,6 +50,7 @@ class UninstallLaunch : FragmentActivity(), UninstallActionListener {
UninstallLaunch::class.java.packageName + ".callingActivityName"
val LOG_TAG = UninstallLaunch::class.java.simpleName
private const val TAG_DIALOG = "dialog"
+ private const val ARGS_SAVED_INTENT = "saved_intent"
}
private var uninstallViewModel: UninstallViewModel? = null
@@ -76,7 +76,15 @@ class UninstallLaunch : FragmentActivity(), UninstallActionListener {
intent.getStringExtra(EXTRA_CALLING_ACTIVITY_NAME),
intent.getIntExtra(EXTRA_CALLING_PKG_UID, Process.INVALID_UID)
)
- uninstallViewModel!!.preprocessIntent(intent, callerInfo)
+
+ var savedIntent: Intent? = null
+ if (savedInstanceState != null) {
+ savedIntent = savedInstanceState.getParcelable(ARGS_SAVED_INTENT, Intent::class.java)
+ }
+ if (!intent.filterEquals(savedIntent)) {
+ uninstallViewModel!!.preprocessIntent(intent, callerInfo)
+ }
+
uninstallViewModel!!.currentUninstallStage.observe(this) { uninstallStage: UninstallStage ->
onUninstallStageChange(uninstallStage)
}
@@ -171,6 +179,11 @@ class UninstallLaunch : FragmentActivity(), UninstallActionListener {
Log.d(LOG_TAG, "Cancelling uninstall")
}
uninstallViewModel!!.cancelUninstall()
- setResult(Activity.RESULT_FIRST_USER, null, true)
+ setResult(RESULT_FIRST_USER, null, true)
+ }
+
+ override fun onSaveInstanceState(outState: Bundle) {
+ outState.putParcelable(ARGS_SAVED_INTENT, intent)
+ super.onSaveInstanceState(outState)
}
}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/viewmodel/InstallViewModel.kt b/packages/PackageInstaller/src/com/android/packageinstaller/v2/viewmodel/InstallViewModel.kt
index 388e03f023a1..5a2b122f0bec 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/viewmodel/InstallViewModel.kt
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/viewmodel/InstallViewModel.kt
@@ -49,6 +49,20 @@ class InstallViewModel(application: Application, val repository: InstallReposito
_currentInstallStage.value = installStage
}
}
+
+ // Since staging is an async operation, we will get the staging result later in time.
+ // Result of the file staging will be set in InstallRepository#mStagingResult.
+ // As such, mCurrentInstallStage will need to add another MutableLiveData
+ // as a data source
+ _currentInstallStage.addSource(
+ repository.stagingResult.distinctUntilChanged()
+ ) { installStage: InstallStage ->
+ if (installStage.stageCode != InstallStage.STAGE_READY) {
+ _currentInstallStage.value = installStage
+ } else {
+ checkIfAllowedAndInitiateInstall()
+ }
+ }
}
fun preprocessIntent(intent: Intent, callerInfo: InstallRepository.CallerInfo) {
@@ -56,18 +70,7 @@ class InstallViewModel(application: Application, val repository: InstallReposito
if (stage.stageCode == InstallStage.STAGE_ABORTED) {
_currentInstallStage.value = stage
} else {
- // Since staging is an async operation, we will get the staging result later in time.
- // Result of the file staging will be set in InstallRepository#mStagingResult.
- // As such, mCurrentInstallStage will need to add another MutableLiveData
- // as a data source
repository.stageForInstall()
- _currentInstallStage.addSource(repository.stagingResult) { installStage: InstallStage ->
- if (installStage.stageCode != InstallStage.STAGE_READY) {
- _currentInstallStage.value = installStage
- } else {
- checkIfAllowedAndInitiateInstall()
- }
- }
}
}
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/Android.bp b/packages/SettingsLib/CollapsingToolbarBaseActivity/Android.bp
index b56b944955d7..af8e856a5ad6 100644
--- a/packages/SettingsLib/CollapsingToolbarBaseActivity/Android.bp
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/Android.bp
@@ -35,5 +35,6 @@ android_library {
"com.android.extservices",
"com.android.permission",
"com.android.healthfitness",
+ "com.android.mediaprovider",
],
}
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 dbac17d4e8b8..44c93c77e33b 100644
--- a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBindings.kt
+++ b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBindings.kt
@@ -74,8 +74,14 @@ interface BooleanValuePreferenceBinding : PreferenceBinding {
override fun bind(preference: Preference, metadata: PreferenceMetadata) {
super.bind(preference, metadata)
(preference as TwoStatePreference).apply {
+ // MUST suppress persistent when initializing the checked state:
+ // 1. default value is written to datastore if not set (b/396260949)
+ // 2. avoid redundant read to the datastore
+ val suppressPersistent = isPersistent
+ if (suppressPersistent) isPersistent = false
// "false" is kind of placeholder, metadata datastore should provide the default value
isChecked = preferenceDataStore!!.getBoolean(key, false)
+ if (suppressPersistent) isPersistent = true
}
}
}
diff --git a/packages/SettingsLib/SettingsTheme/src/com/android/settingslib/widget/SettingsBasePreferenceFragment.kt b/packages/SettingsLib/SettingsTheme/src/com/android/settingslib/widget/SettingsBasePreferenceFragment.kt
index bfaeb42d5a31..8d12f01e24ed 100644
--- a/packages/SettingsLib/SettingsTheme/src/com/android/settingslib/widget/SettingsBasePreferenceFragment.kt
+++ b/packages/SettingsLib/SettingsTheme/src/com/android/settingslib/widget/SettingsBasePreferenceFragment.kt
@@ -17,7 +17,9 @@
package com.android.settingslib.widget
import android.os.Bundle
+import android.view.LayoutInflater;
import android.view.View
+import android.view.ViewGroup;
import androidx.annotation.CallSuper
import androidx.preference.PreferenceFragmentCompat
import androidx.preference.PreferenceScreen
@@ -27,6 +29,15 @@ import androidx.recyclerview.widget.RecyclerView
abstract class SettingsBasePreferenceFragment : PreferenceFragmentCompat() {
@CallSuper
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View {
+ return super.onCreateView(inflater, container, savedInstanceState)
+ }
+
+ @CallSuper
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
if (SettingsThemeHelper.isExpressiveTheme(requireContext())) {
diff --git a/packages/SettingsLib/SettingsTransition/Android.bp b/packages/SettingsLib/SettingsTransition/Android.bp
index e04af6c1ab11..6b9cbfa8ece7 100644
--- a/packages/SettingsLib/SettingsTransition/Android.bp
+++ b/packages/SettingsLib/SettingsTransition/Android.bp
@@ -30,5 +30,6 @@ android_library {
"com.android.extservices",
"com.android.permission",
"com.android.healthfitness",
+ "com.android.mediaprovider",
],
}
diff --git a/packages/SettingsLib/Spa/spa/Android.bp b/packages/SettingsLib/Spa/spa/Android.bp
index ac44a1be4cff..5aeea9c09f21 100644
--- a/packages/SettingsLib/Spa/spa/Android.bp
+++ b/packages/SettingsLib/Spa/spa/Android.bp
@@ -24,7 +24,9 @@ android_library {
srcs: ["src/**/*.kt"],
use_resource_processor: true,
static_libs: [
+ "MPAndroidChart",
"SettingsLibColor",
+ "aconfig_settingstheme_exported_flags_java_lib",
"androidx.compose.animation_animation",
"androidx.compose.material3_material3",
"androidx.compose.material_material-icons-extended",
@@ -36,7 +38,6 @@ android_library {
"androidx.navigation_navigation-compose",
"com.google.android.material_material",
"lottie_compose",
- "MPAndroidChart",
],
kotlincflags: [
"-Xjvm-default=all",
diff --git a/packages/SettingsLib/Spa/spa/build.gradle.kts b/packages/SettingsLib/Spa/spa/build.gradle.kts
index 13966299b923..de1fa4ed20ed 100644
--- a/packages/SettingsLib/Spa/spa/build.gradle.kts
+++ b/packages/SettingsLib/Spa/spa/build.gradle.kts
@@ -51,6 +51,7 @@ android {
dependencies {
api(project(":SettingsLibColor"))
+ api(project(":SettingsLib:SettingsTheme"))
api("androidx.appcompat:appcompat:1.7.0")
api("androidx.compose.material3:material3:1.4.0-alpha05")
api("androidx.compose.material:material-icons-extended")
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsTheme.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsTheme.kt
index f948d5163177..badf7aeb97c5 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsTheme.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsTheme.kt
@@ -22,6 +22,7 @@ import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import com.android.settingslib.spa.framework.util.SystemProperties
+import com.android.settingslib.widget.theme.flags.Flags
/**
* The Material 3 Theme for Settings.
@@ -42,5 +43,7 @@ fun SettingsTheme(content: @Composable () -> Unit) {
}
}
-val isSpaExpressiveEnabled
- by lazy { SystemProperties.getBoolean("is_expressive_design_enabled", false) }
+val isSpaExpressiveEnabled by lazy {
+ SystemProperties.getBoolean("is_expressive_design_enabled", false) ||
+ Flags.isExpressiveDesignEnabled()
+}
diff --git a/packages/SettingsLib/Spa/tests/Android.bp b/packages/SettingsLib/Spa/tests/Android.bp
index 871449e9d803..315b4009110e 100644
--- a/packages/SettingsLib/Spa/tests/Android.bp
+++ b/packages/SettingsLib/Spa/tests/Android.bp
@@ -30,10 +30,14 @@ android_test {
static_libs: [
"SpaLib",
"SpaLibTestUtils",
+ "aconfig_settingstheme_exported_flags_java_lib",
"androidx.compose.runtime_runtime",
"androidx.test.ext.junit",
"androidx.test.runner",
+ "flag-junit",
+ "flag-junit-base",
"mockito-target-minus-junit4",
+ "platform-test-annotations",
],
kotlincflags: ["-Xjvm-default=all"],
sdk_version: "current",
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/button/ActionButtonsTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/button/ActionButtonsTest.kt
index 8d9bac64b078..bab02b0753a9 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/button/ActionButtonsTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/button/ActionButtonsTest.kt
@@ -16,6 +16,8 @@
package com.android.settingslib.spa.widget.button
+import android.platform.test.annotations.RequiresFlagsDisabled
+import android.platform.test.flag.junit.DeviceFlagsValueProvider
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.outlined.Launch
import androidx.compose.material.icons.outlined.Close
@@ -28,6 +30,7 @@ import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.widget.theme.flags.Flags.FLAG_IS_EXPRESSIVE_DESIGN_ENABLED
import com.google.common.truth.Truth.assertThat
import org.junit.Rule
import org.junit.Test
@@ -37,6 +40,8 @@ import org.junit.runner.RunWith
class ActionButtonsTest {
@get:Rule
val composeTestRule = createComposeRule()
+ @get:Rule
+ val mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule()
@Test
fun button_displayed() {
@@ -54,6 +59,7 @@ class ActionButtonsTest {
composeTestRule.onNodeWithText("Open").assertIsDisplayed()
}
+ @RequiresFlagsDisabled(FLAG_IS_EXPRESSIVE_DESIGN_ENABLED)
@Test
fun button_clickable() {
var clicked by mutableStateOf(false)
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBarTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBarTest.kt
index 0a4f0d937600..89206baed6f7 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBarTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBarTest.kt
@@ -16,6 +16,8 @@
package com.android.settingslib.spa.widget.scaffold
+import android.platform.test.annotations.RequiresFlagsDisabled
+import android.platform.test.flag.junit.DeviceFlagsValueProvider
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.lazy.LazyColumn
@@ -52,6 +54,7 @@ import androidx.compose.ui.text.TextStyle
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.settingslib.spa.testutils.rootWidth
import com.android.settingslib.spa.testutils.setContentForSizeAssertions
+import com.android.settingslib.widget.theme.flags.Flags.FLAG_IS_EXPRESSIVE_DESIGN_ENABLED
import com.google.common.truth.Truth.assertThat
import org.junit.Rule
import org.junit.Test
@@ -62,6 +65,8 @@ import org.junit.runner.RunWith
class CustomizedAppBarTest {
@get:Rule val rule = createComposeRule()
+ @get:Rule
+ val mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule()
@Test
fun smallTopAppBar_expandsToScreen() {
@@ -97,6 +102,7 @@ class CustomizedAppBarTest {
assertThat(textStyle).isEqualTo(expectedTextStyle)
}
+ @RequiresFlagsDisabled(FLAG_IS_EXPRESSIVE_DESIGN_ENABLED)
@Test
fun smallTopAppBar_contentColor() {
var titleColor: Color = Color.Unspecified
diff --git a/packages/SettingsLib/TopIntroPreference/Android.bp b/packages/SettingsLib/TopIntroPreference/Android.bp
index 76e36dc5ff7d..bf26264a4f0e 100644
--- a/packages/SettingsLib/TopIntroPreference/Android.bp
+++ b/packages/SettingsLib/TopIntroPreference/Android.bp
@@ -32,5 +32,6 @@ android_library {
"com.android.cellbroadcast",
"com.android.devicelock",
"com.android.healthfitness",
+ "com.android.mediaprovider",
],
}
diff --git a/packages/SettingsLib/res/drawable/ic_1x_mobiledata_updated.xml b/packages/SettingsLib/res/drawable/ic_1x_mobiledata_updated.xml
new file mode 100644
index 000000000000..bd2fad2fedc0
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_1x_mobiledata_updated.xml
@@ -0,0 +1,24 @@
+<!--
+ 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="17dp"
+ android:height="12.88dp"
+ android:viewportWidth="13.53"
+ android:viewportHeight="10.25">
+ <path
+ android:pathData="M3.738,10.252C3.468,10.252 3.234,10.154 3.038,9.958C2.847,9.757 2.751,9.522 2.751,9.251L2.751,2.587L1.372,3.497C1.167,3.628 0.943,3.672 0.7,3.63C0.462,3.588 0.276,3.467 0.14,3.266C0.01,3.065 -0.032,2.844 0.014,2.601C0.066,2.358 0.192,2.172 0.392,2.041L3.185,0.2C3.265,0.149 3.344,0.104 3.423,0.067C3.507,0.025 3.619,0.004 3.759,0.004C4.03,0.004 4.259,0.102 4.445,0.298C4.637,0.494 4.732,0.734 4.732,1.019L4.732,9.251C4.732,9.522 4.634,9.757 4.438,9.958C4.242,10.154 4.009,10.252 3.738,10.252ZM12.582,10.245C12.391,10.245 12.218,10.194 12.064,10.091C11.915,9.984 11.796,9.848 11.707,9.685L9.803,6.038L9.593,5.961L7.332,1.411C7.136,1.084 7.127,0.769 7.304,0.466C7.482,0.163 7.755,0.011 8.123,0.011C8.301,0.011 8.466,0.065 8.62,0.172C8.779,0.279 8.903,0.415 8.991,0.578L10.783,4.015L11.014,4.05L13.373,8.796C13.569,9.132 13.581,9.459 13.408,9.776C13.236,10.089 12.96,10.245 12.582,10.245ZM7.92,10.238C7.57,10.238 7.309,10.089 7.136,9.79C6.968,9.491 6.978,9.186 7.164,8.873L9.621,4.008L9.817,4.008L11.63,0.557C11.719,0.398 11.836,0.27 11.98,0.172C12.125,0.069 12.288,0.018 12.47,0.018C12.816,0.018 13.075,0.163 13.247,0.452C13.42,0.741 13.411,1.047 13.219,1.369L10.909,5.982L10.762,5.989L8.781,9.713C8.697,9.867 8.578,9.993 8.424,10.091C8.27,10.189 8.102,10.238 7.92,10.238Z"
+ android:fillColor="#000"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_3g_mobiledata_updated.xml b/packages/SettingsLib/res/drawable/ic_3g_mobiledata_updated.xml
new file mode 100644
index 000000000000..8e632e69a56e
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_3g_mobiledata_updated.xml
@@ -0,0 +1,24 @@
+<!--
+ 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="17dp"
+ android:height="12.21dp"
+ android:viewportWidth="14.51"
+ android:viewportHeight="10.42">
+ <path
+ android:pathData="M2.827,10.416C2.225,10.416 1.726,10.311 1.329,10.101C0.937,9.891 0.629,9.606 0.405,9.247C0.181,8.888 0.051,8.58 0.013,8.323C-0.019,8.066 0.023,7.849 0.139,7.672C0.256,7.49 0.415,7.371 0.615,7.315C0.816,7.254 1.003,7.259 1.175,7.329C1.353,7.394 1.481,7.495 1.56,7.63C1.644,7.765 1.733,7.922 1.826,8.099C1.92,8.272 2.041,8.416 2.19,8.533C2.34,8.65 2.533,8.708 2.771,8.708C3.089,8.708 3.343,8.615 3.534,8.428C3.726,8.237 3.821,7.959 3.821,7.595L3.821,6.888C3.821,6.529 3.726,6.256 3.534,6.069C3.348,5.882 3.077,5.789 2.722,5.789L2.456,5.789C2.251,5.789 2.071,5.714 1.917,5.565C1.768,5.416 1.693,5.236 1.693,5.026C1.693,4.816 1.768,4.636 1.917,4.487C2.067,4.338 2.244,4.263 2.449,4.263L2.666,4.263C2.984,4.263 3.222,4.177 3.38,4.004C3.544,3.831 3.625,3.558 3.625,3.185L3.625,2.618C3.625,2.315 3.544,2.084 3.38,1.925C3.217,1.766 2.998,1.687 2.722,1.687C2.531,1.687 2.37,1.729 2.239,1.813C2.109,1.892 1.999,2.004 1.91,2.149C1.822,2.294 1.738,2.427 1.658,2.548C1.579,2.665 1.453,2.756 1.28,2.821C1.108,2.882 0.926,2.882 0.734,2.821C0.543,2.756 0.391,2.632 0.279,2.45C0.172,2.268 0.142,2.049 0.188,1.792C0.24,1.531 0.384,1.248 0.622,0.945C0.86,0.642 1.159,0.408 1.518,0.245C1.882,0.082 2.326,0 2.848,0C3.67,0 4.323,0.215 4.808,0.644C5.294,1.069 5.536,1.636 5.536,2.345L5.536,2.8C5.536,3.332 5.417,3.768 5.179,4.109C4.941,4.445 4.598,4.69 4.15,4.844L4.15,4.9C4.678,5.017 5.086,5.259 5.375,5.628C5.669,5.992 5.816,6.484 5.816,7.105L5.816,7.679C5.816,8.514 5.55,9.179 5.018,9.674C4.491,10.169 3.761,10.416 2.827,10.416ZM10.943,10.416C9.819,10.411 8.92,10.031 8.248,9.275C7.581,8.514 7.247,7.406 7.247,5.95L7.247,4.417C7.247,2.975 7.588,1.878 8.269,1.127C8.951,0.371 9.863,-0.005 11.006,0C11.52,0 11.954,0.075 12.308,0.224C12.668,0.369 12.973,0.576 13.225,0.847C13.482,1.113 13.659,1.374 13.757,1.631C13.855,1.883 13.862,2.121 13.778,2.345C13.694,2.569 13.55,2.732 13.344,2.835C13.144,2.938 12.948,2.968 12.756,2.926C12.565,2.884 12.416,2.802 12.308,2.681C12.201,2.555 12.091,2.422 11.979,2.282C11.867,2.142 11.727,2.032 11.559,1.953C11.391,1.869 11.191,1.827 10.957,1.827C10.439,1.822 10.024,2.011 9.711,2.394C9.403,2.777 9.249,3.358 9.249,4.137L9.249,6.293C9.249,7.063 9.408,7.644 9.725,8.036C10.047,8.428 10.467,8.624 10.985,8.624C11.489,8.624 11.884,8.477 12.168,8.183C12.453,7.889 12.607,7.474 12.63,6.937L12.63,6.265L11.657,6.265C11.452,6.265 11.275,6.188 11.125,6.034C10.976,5.875 10.901,5.689 10.901,5.474C10.901,5.255 10.978,5.068 11.132,4.914C11.291,4.755 11.48,4.676 11.699,4.676L13.666,4.676C13.9,4.676 14.098,4.755 14.261,4.914C14.425,5.073 14.509,5.269 14.513,5.502L14.513,6.538C14.513,7.77 14.191,8.724 13.547,9.401C12.908,10.078 12.04,10.416 10.943,10.416Z"
+ android:fillColor="#000"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_4g_lte_mobiledata_updated.xml b/packages/SettingsLib/res/drawable/ic_4g_lte_mobiledata_updated.xml
new file mode 100644
index 000000000000..bba359e8b238
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_4g_lte_mobiledata_updated.xml
@@ -0,0 +1,24 @@
+<!--
+ 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="34dp"
+ android:height="14.07dp"
+ android:viewportWidth="27.29"
+ android:viewportHeight="11.29">
+ <path
+ android:pathData="M4.552,11.195C4.286,11.195 4.058,11.099 3.866,10.908C3.68,10.717 3.586,10.488 3.586,10.222L3.586,8.171L3.74,7.891L3.74,2.823L4.72,3.53L3.712,3.53L1.808,7.366L4.37,7.366L4.797,7.275L5.777,7.275C6.015,7.275 6.218,7.359 6.386,7.527C6.554,7.695 6.638,7.898 6.638,8.136C6.638,8.369 6.554,8.57 6.386,8.738C6.218,8.906 6.015,8.99 5.777,8.99L1.192,8.99C0.856,8.99 0.572,8.88 0.338,8.661C0.11,8.442 -0.005,8.169 -0.005,7.842C-0.005,7.683 0.016,7.569 0.058,7.499C0.1,7.424 0.142,7.35 0.184,7.275L3.124,1.633C3.222,1.446 3.374,1.283 3.579,1.143C3.789,1.003 4.013,0.933 4.251,0.933C4.606,0.933 4.905,1.061 5.147,1.318C5.39,1.57 5.511,1.876 5.511,2.235L5.511,10.222C5.511,10.488 5.416,10.717 5.224,10.908C5.038,11.099 4.814,11.195 4.552,11.195ZM11.303,11.286C10.179,11.281 9.28,10.901 8.608,10.145C7.941,9.384 7.607,8.276 7.607,6.82L7.607,5.287C7.607,3.845 7.948,2.748 8.629,1.997C9.311,1.241 10.223,0.865 11.366,0.87C11.88,0.87 12.314,0.945 12.668,1.094C13.028,1.239 13.333,1.446 13.585,1.717C13.842,1.983 14.019,2.244 14.117,2.501C14.215,2.753 14.222,2.991 14.138,3.215C14.054,3.439 13.91,3.602 13.704,3.705C13.504,3.808 13.308,3.838 13.116,3.796C12.925,3.754 12.776,3.672 12.668,3.551C12.561,3.425 12.451,3.292 12.339,3.152C12.227,3.012 12.087,2.902 11.919,2.823C11.751,2.739 11.551,2.697 11.317,2.697C10.799,2.692 10.384,2.881 10.071,3.264C9.763,3.647 9.609,4.228 9.609,5.007L9.609,7.163C9.609,7.933 9.768,8.514 10.085,8.906C10.407,9.298 10.827,9.494 11.345,9.494C11.849,9.494 12.244,9.347 12.528,9.053C12.813,8.759 12.967,8.344 12.99,7.807L12.99,7.135L12.017,7.135C11.812,7.135 11.635,7.058 11.485,6.904C11.336,6.745 11.261,6.559 11.261,6.344C11.261,6.125 11.338,5.938 11.492,5.784C11.651,5.625 11.84,5.546 12.059,5.546L14.026,5.546C14.26,5.546 14.458,5.625 14.621,5.784C14.785,5.943 14.869,6.139 14.873,6.372L14.873,7.408C14.873,8.64 14.551,9.594 13.907,10.271C13.268,10.948 12.4,11.286 11.303,11.286ZM16.641,6.09C16.434,6.09 16.256,6.016 16.108,5.867C15.962,5.719 15.89,5.543 15.89,5.338L15.89,0.689C15.89,0.501 15.957,0.34 16.091,0.206C16.226,0.069 16.385,0 16.57,0C16.758,0 16.919,0.069 17.053,0.206C17.187,0.34 17.255,0.501 17.255,0.689L17.255,4.83L18.54,4.83C18.713,4.83 18.863,4.892 18.989,5.015C19.115,5.138 19.178,5.286 19.178,5.46C19.178,5.631 19.115,5.779 18.989,5.905C18.863,6.028 18.713,6.09 18.54,6.09L16.641,6.09ZM20.979,6.166C20.792,6.166 20.63,6.098 20.496,5.964C20.365,5.827 20.299,5.664 20.299,5.477L20.299,0.731L21.664,0.731L21.664,5.477C21.664,5.664 21.597,5.827 21.462,5.964C21.328,6.098 21.167,6.166 20.979,6.166ZM19.648,1.331C19.474,1.331 19.324,1.27 19.198,1.147C19.075,1.023 19.014,0.876 19.014,0.706C19.014,0.532 19.075,0.384 19.198,0.26C19.324,0.137 19.474,0.076 19.648,0.076L22.306,0.076C22.48,0.076 22.628,0.137 22.751,0.26C22.877,0.384 22.941,0.532 22.941,0.706C22.941,0.876 22.877,1.023 22.751,1.147C22.628,1.27 22.48,1.331 22.306,1.331L19.648,1.331ZM24.553,6.09C24.346,6.09 24.168,6.016 24.019,5.867C23.874,5.719 23.801,5.543 23.801,5.338L23.801,0.823C23.801,0.619 23.874,0.444 24.019,0.298C24.168,0.15 24.346,0.076 24.553,0.076L26.59,0.076C26.763,0.076 26.912,0.137 27.035,0.26C27.161,0.384 27.224,0.531 27.224,0.701C27.224,0.872 27.161,1.019 27.035,1.142C26.912,1.266 26.763,1.327 26.59,1.327L25.158,1.327L25.158,4.838L26.657,4.838C26.831,4.838 26.979,4.9 27.102,5.023C27.225,5.146 27.287,5.293 27.287,5.464C27.287,5.635 27.225,5.782 27.102,5.905C26.979,6.028 26.831,6.09 26.657,6.09L24.553,6.09ZM24.637,3.595L24.637,2.444L26.3,2.444C26.46,2.444 26.595,2.5 26.707,2.612C26.822,2.724 26.88,2.86 26.88,3.02C26.88,3.177 26.822,3.312 26.707,3.427C26.595,3.539 26.46,3.595 26.3,3.595L24.637,3.595Z"
+ android:fillColor="#000"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_4g_lte_plus_mobiledata_updated.xml b/packages/SettingsLib/res/drawable/ic_4g_lte_plus_mobiledata_updated.xml
new file mode 100644
index 000000000000..cb6fd50a07ea
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_4g_lte_plus_mobiledata_updated.xml
@@ -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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="34dp"
+ android:height="11.93dp"
+ android:viewportWidth="32.18"
+ android:viewportHeight="11.29">
+ <path
+ android:pathData="M4.552,11.195C4.286,11.195 4.058,11.099 3.866,10.908C3.68,10.717 3.586,10.488 3.586,10.222L3.586,8.171L3.74,7.891L3.74,2.823L4.72,3.53L3.712,3.53L1.808,7.366L4.37,7.366L4.797,7.275L5.777,7.275C6.015,7.275 6.218,7.359 6.386,7.527C6.554,7.695 6.638,7.898 6.638,8.136C6.638,8.369 6.554,8.57 6.386,8.738C6.218,8.906 6.015,8.99 5.777,8.99L1.192,8.99C0.856,8.99 0.572,8.88 0.338,8.661C0.11,8.442 -0.005,8.169 -0.005,7.842C-0.005,7.683 0.016,7.569 0.058,7.499C0.1,7.424 0.142,7.35 0.184,7.275L3.124,1.633C3.222,1.446 3.374,1.283 3.579,1.143C3.789,1.003 4.013,0.933 4.251,0.933C4.606,0.933 4.905,1.061 5.147,1.318C5.39,1.57 5.511,1.876 5.511,2.235L5.511,10.222C5.511,10.488 5.416,10.717 5.224,10.908C5.038,11.099 4.814,11.195 4.552,11.195ZM11.303,11.286C10.179,11.281 9.28,10.901 8.608,10.145C7.941,9.384 7.607,8.276 7.607,6.82L7.607,5.287C7.607,3.845 7.948,2.748 8.629,1.997C9.311,1.241 10.223,0.865 11.366,0.87C11.88,0.87 12.314,0.945 12.668,1.094C13.028,1.239 13.333,1.446 13.585,1.717C13.842,1.983 14.019,2.244 14.117,2.501C14.215,2.753 14.222,2.991 14.138,3.215C14.054,3.439 13.91,3.602 13.704,3.705C13.504,3.808 13.308,3.838 13.116,3.796C12.925,3.754 12.776,3.672 12.668,3.551C12.561,3.425 12.451,3.292 12.339,3.152C12.227,3.012 12.087,2.902 11.919,2.823C11.751,2.739 11.551,2.697 11.317,2.697C10.799,2.692 10.384,2.881 10.071,3.264C9.763,3.647 9.609,4.228 9.609,5.007L9.609,7.163C9.609,7.933 9.768,8.514 10.085,8.906C10.407,9.298 10.827,9.494 11.345,9.494C11.849,9.494 12.244,9.347 12.528,9.053C12.813,8.759 12.967,8.344 12.99,7.807L12.99,7.135L12.017,7.135C11.812,7.135 11.635,7.058 11.485,6.904C11.336,6.745 11.261,6.559 11.261,6.344C11.261,6.125 11.338,5.938 11.492,5.784C11.651,5.625 11.84,5.546 12.059,5.546L14.026,5.546C14.26,5.546 14.458,5.625 14.621,5.784C14.785,5.943 14.869,6.139 14.873,6.372L14.873,7.408C14.873,8.64 14.551,9.594 13.907,10.271C13.268,10.948 12.4,11.286 11.303,11.286ZM16.641,6.09C16.434,6.09 16.256,6.016 16.108,5.867C15.962,5.719 15.89,5.543 15.89,5.338L15.89,0.689C15.89,0.501 15.957,0.34 16.091,0.206C16.226,0.069 16.385,0 16.57,0C16.758,0 16.919,0.069 17.053,0.206C17.187,0.34 17.255,0.501 17.255,0.689L17.255,4.83L18.54,4.83C18.713,4.83 18.863,4.892 18.989,5.015C19.115,5.138 19.178,5.286 19.178,5.46C19.178,5.631 19.115,5.779 18.989,5.905C18.863,6.028 18.713,6.09 18.54,6.09L16.641,6.09ZM20.979,6.166C20.792,6.166 20.63,6.098 20.496,5.964C20.365,5.827 20.299,5.664 20.299,5.477L20.299,0.731L21.664,0.731L21.664,5.477C21.664,5.664 21.597,5.827 21.462,5.964C21.328,6.098 21.167,6.166 20.979,6.166ZM19.648,1.331C19.474,1.331 19.324,1.27 19.198,1.147C19.075,1.023 19.014,0.876 19.014,0.706C19.014,0.532 19.075,0.384 19.198,0.26C19.324,0.137 19.474,0.076 19.648,0.076L22.306,0.076C22.48,0.076 22.628,0.137 22.751,0.26C22.877,0.384 22.941,0.532 22.941,0.706C22.941,0.876 22.877,1.023 22.751,1.147C22.628,1.27 22.48,1.331 22.306,1.331L19.648,1.331ZM24.553,6.09C24.346,6.09 24.168,6.016 24.019,5.867C23.874,5.719 23.801,5.543 23.801,5.338L23.801,0.823C23.801,0.619 23.874,0.444 24.019,0.298C24.168,0.15 24.346,0.076 24.553,0.076L26.59,0.076C26.763,0.076 26.912,0.137 27.035,0.26C27.161,0.384 27.224,0.531 27.224,0.701C27.224,0.872 27.161,1.019 27.035,1.142C26.912,1.266 26.763,1.327 26.59,1.327L25.158,1.327L25.158,4.838L26.657,4.838C26.831,4.838 26.979,4.9 27.102,5.023C27.225,5.146 27.287,5.293 27.287,5.464C27.287,5.635 27.225,5.782 27.102,5.905C26.979,6.028 26.831,6.09 26.657,6.09L24.553,6.09ZM24.637,3.595L24.637,2.444L26.3,2.444C26.46,2.444 26.595,2.5 26.707,2.612C26.822,2.724 26.88,2.86 26.88,3.02C26.88,3.177 26.822,3.312 26.707,3.427C26.595,3.539 26.46,3.595 26.3,3.595L24.637,3.595Z"
+ android:fillColor="#000"/>
+ <path
+ android:pathData="M30.101,5.346C29.923,5.346 29.768,5.282 29.637,5.154C29.509,5.023 29.445,4.867 29.445,4.686L29.445,1.742C29.445,1.561 29.509,1.406 29.637,1.278C29.768,1.147 29.923,1.082 30.101,1.082C30.28,1.082 30.433,1.147 30.561,1.278C30.689,1.406 30.753,1.561 30.753,1.742L30.753,4.686C30.753,4.867 30.689,5.023 30.561,5.154C30.433,5.282 30.28,5.346 30.101,5.346ZM28.677,3.858C28.499,3.858 28.345,3.795 28.217,3.67C28.089,3.542 28.025,3.39 28.025,3.214C28.025,3.038 28.089,2.887 28.217,2.762C28.345,2.634 28.499,2.57 28.677,2.57L31.525,2.57C31.704,2.57 31.857,2.634 31.985,2.762C32.113,2.887 32.177,3.038 32.177,3.214C32.177,3.39 32.113,3.542 31.985,3.67C31.857,3.795 31.704,3.858 31.525,3.858L28.677,3.858Z"
+ android:fillColor="#000"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_4g_mobiledata_updated.xml b/packages/SettingsLib/res/drawable/ic_4g_mobiledata_updated.xml
new file mode 100644
index 000000000000..562bcaf2fdba
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_4g_mobiledata_updated.xml
@@ -0,0 +1,25 @@
+<!--
+ 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.
+-->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="19dp"
+ android:height="13.31dp"
+ android:viewportWidth="14.88"
+ android:viewportHeight="10.42">
+ <path
+ android:pathData="M4.552,10.325C4.286,10.325 4.058,10.229 3.866,10.038C3.68,9.847 3.586,9.618 3.586,9.352L3.586,7.301L3.74,7.021L3.74,1.953L4.72,2.66L3.712,2.66L1.808,6.496L4.37,6.496L4.797,6.405L5.777,6.405C6.015,6.405 6.218,6.489 6.386,6.657C6.554,6.825 6.638,7.028 6.638,7.266C6.638,7.499 6.554,7.7 6.386,7.868C6.218,8.036 6.015,8.12 5.777,8.12L1.192,8.12C0.856,8.12 0.572,8.01 0.338,7.791C0.11,7.572 -0.005,7.299 -0.005,6.972C-0.005,6.813 0.016,6.699 0.058,6.629C0.1,6.554 0.142,6.48 0.184,6.405L3.124,0.763C3.222,0.576 3.374,0.413 3.579,0.273C3.789,0.133 4.013,0.063 4.251,0.063C4.606,0.063 4.905,0.191 5.147,0.448C5.39,0.7 5.511,1.006 5.511,1.365L5.511,9.352C5.511,9.618 5.416,9.847 5.224,10.038C5.038,10.229 4.814,10.325 4.552,10.325ZM11.303,10.416C10.179,10.411 9.28,10.031 8.608,9.275C7.941,8.514 7.607,7.406 7.607,5.95L7.607,4.417C7.607,2.975 7.948,1.878 8.629,1.127C9.311,0.371 10.223,-0.005 11.366,0C11.88,0 12.314,0.075 12.668,0.224C13.028,0.369 13.333,0.576 13.585,0.847C13.842,1.113 14.019,1.374 14.117,1.631C14.215,1.883 14.222,2.121 14.138,2.345C14.054,2.569 13.91,2.732 13.704,2.835C13.504,2.938 13.308,2.968 13.116,2.926C12.925,2.884 12.776,2.802 12.668,2.681C12.561,2.555 12.451,2.422 12.339,2.282C12.227,2.142 12.087,2.032 11.919,1.953C11.751,1.869 11.551,1.827 11.317,1.827C10.799,1.822 10.384,2.011 10.071,2.394C9.763,2.777 9.609,3.358 9.609,4.137L9.609,6.293C9.609,7.063 9.768,7.644 10.085,8.036C10.407,8.428 10.827,8.624 11.345,8.624C11.849,8.624 12.244,8.477 12.528,8.183C12.813,7.889 12.967,7.474 12.99,6.937L12.99,6.265L12.017,6.265C11.812,6.265 11.635,6.188 11.485,6.034C11.336,5.875 11.261,5.689 11.261,5.474C11.261,5.255 11.338,5.068 11.492,4.914C11.651,4.755 11.84,4.676 12.059,4.676L14.026,4.676C14.26,4.676 14.458,4.755 14.621,4.914C14.785,5.073 14.869,5.269 14.873,5.502L14.873,6.538C14.873,7.77 14.551,8.724 13.907,9.401C13.268,10.078 12.4,10.416 11.303,10.416Z"
+ android:fillColor="#000"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_4g_plus_mobiledata_updated.xml b/packages/SettingsLib/res/drawable/ic_4g_plus_mobiledata_updated.xml
new file mode 100644
index 000000000000..73e8994681c2
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_4g_plus_mobiledata_updated.xml
@@ -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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="23dp"
+ android:height="12.5dp"
+ android:viewportWidth="19.17"
+ android:viewportHeight="10.42">
+ <path
+ android:pathData="M4.552,10.325C4.286,10.325 4.058,10.229 3.866,10.038C3.68,9.847 3.586,9.618 3.586,9.352L3.586,7.301L3.74,7.021L3.74,1.953L4.72,2.66L3.712,2.66L1.808,6.496L4.37,6.496L4.797,6.405L5.777,6.405C6.015,6.405 6.218,6.489 6.386,6.657C6.554,6.825 6.638,7.028 6.638,7.266C6.638,7.499 6.554,7.7 6.386,7.868C6.218,8.036 6.015,8.12 5.777,8.12L1.192,8.12C0.856,8.12 0.572,8.01 0.338,7.791C0.11,7.572 -0.005,7.299 -0.005,6.972C-0.005,6.813 0.016,6.699 0.058,6.629C0.1,6.554 0.142,6.48 0.184,6.405L3.124,0.763C3.222,0.576 3.374,0.413 3.579,0.273C3.789,0.133 4.013,0.063 4.251,0.063C4.606,0.063 4.905,0.191 5.147,0.448C5.39,0.7 5.511,1.006 5.511,1.365L5.511,9.352C5.511,9.618 5.416,9.847 5.224,10.038C5.038,10.229 4.814,10.325 4.552,10.325ZM11.303,10.416C10.179,10.411 9.28,10.031 8.608,9.275C7.941,8.514 7.607,7.406 7.607,5.95L7.607,4.417C7.607,2.975 7.948,1.878 8.629,1.127C9.311,0.371 10.223,-0.005 11.366,0C11.88,0 12.314,0.075 12.668,0.224C13.028,0.369 13.333,0.576 13.585,0.847C13.842,1.113 14.019,1.374 14.117,1.631C14.215,1.883 14.222,2.121 14.138,2.345C14.054,2.569 13.91,2.732 13.704,2.835C13.504,2.938 13.308,2.968 13.116,2.926C12.925,2.884 12.776,2.802 12.668,2.681C12.561,2.555 12.451,2.422 12.339,2.282C12.227,2.142 12.087,2.032 11.919,1.953C11.751,1.869 11.551,1.827 11.317,1.827C10.799,1.822 10.384,2.011 10.071,2.394C9.763,2.777 9.609,3.358 9.609,4.137L9.609,6.293C9.609,7.063 9.768,7.644 10.085,8.036C10.407,8.428 10.827,8.624 11.345,8.624C11.849,8.624 12.244,8.477 12.528,8.183C12.813,7.889 12.967,7.474 12.99,6.937L12.99,6.265L12.017,6.265C11.812,6.265 11.635,6.188 11.485,6.034C11.336,5.875 11.261,5.689 11.261,5.474C11.261,5.255 11.338,5.068 11.492,4.914C11.651,4.755 11.84,4.676 12.059,4.676L14.026,4.676C14.26,4.676 14.458,4.755 14.621,4.914C14.785,5.073 14.869,5.269 14.873,5.502L14.873,6.538C14.873,7.77 14.551,8.724 13.907,9.401C13.268,10.078 12.4,10.416 11.303,10.416Z"
+ android:fillColor="#000"/>
+ <path
+ android:pathData="M17.086,4.476C16.907,4.476 16.752,4.412 16.622,4.284C16.494,4.153 16.43,3.997 16.43,3.816L16.43,0.872C16.43,0.691 16.494,0.536 16.622,0.408C16.752,0.277 16.907,0.212 17.086,0.212C17.264,0.212 17.418,0.277 17.546,0.408C17.674,0.536 17.738,0.691 17.738,0.872L17.738,3.816C17.738,3.997 17.674,4.153 17.546,4.284C17.418,4.412 17.264,4.476 17.086,4.476ZM15.662,2.988C15.483,2.988 15.33,2.925 15.202,2.8C15.074,2.672 15.01,2.52 15.01,2.344C15.01,2.168 15.074,2.017 15.202,1.892C15.33,1.764 15.483,1.7 15.662,1.7L18.51,1.7C18.688,1.7 18.842,1.764 18.97,1.892C19.098,2.017 19.162,2.168 19.162,2.344C19.162,2.52 19.098,2.672 18.97,2.8C18.842,2.925 18.688,2.988 18.51,2.988L15.662,2.988Z"
+ android:fillColor="#000"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_5g_e_mobiledata_updated.xml b/packages/SettingsLib/res/drawable/ic_5g_e_mobiledata_updated.xml
new file mode 100644
index 000000000000..c46da66a9183
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_5g_e_mobiledata_updated.xml
@@ -0,0 +1,25 @@
+<!--
+ 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="22.0dp"
+ android:height="12.57dp"
+ android:viewportHeight="11.21"
+ android:viewportWidth="19.62">
+
+ <path
+ android:fillColor="#000"
+ android:pathData="M2.573,11.206C1.99,11.206 1.502,11.094 1.11,10.87C0.718,10.641 0.436,10.364 0.263,10.037C0.091,9.706 0.002,9.4 -0.003,9.12C-0.007,8.84 0.058,8.616 0.193,8.448C0.333,8.275 0.499,8.168 0.69,8.126C0.882,8.079 1.057,8.089 1.215,8.154C1.379,8.219 1.502,8.315 1.586,8.441C1.675,8.567 1.75,8.716 1.81,8.889C1.871,9.062 1.971,9.202 2.111,9.309C2.251,9.412 2.429,9.463 2.643,9.463C2.947,9.463 3.203,9.363 3.413,9.162C3.623,8.961 3.763,8.663 3.833,8.266L4.008,7.272C4.074,6.899 4.029,6.609 3.875,6.404C3.726,6.199 3.5,6.096 3.196,6.096C3,6.096 2.825,6.143 2.671,6.236C2.522,6.325 2.401,6.43 2.307,6.551C2.181,6.672 2.03,6.752 1.852,6.789C1.675,6.826 1.488,6.81 1.292,6.74C1.045,6.651 0.865,6.497 0.753,6.278C0.641,6.054 0.62,5.804 0.69,5.529L1.593,1.98C1.677,1.672 1.831,1.429 2.055,1.252C2.284,1.075 2.557,0.986 2.874,0.986L5.674,0.986C5.964,0.986 6.195,1.089 6.367,1.294C6.54,1.495 6.601,1.733 6.549,2.008C6.517,2.213 6.412,2.388 6.234,2.533C6.062,2.673 5.875,2.743 5.674,2.743L3.056,2.743L2.433,5.13L2.475,5.137C2.676,4.955 2.905,4.815 3.161,4.717C3.418,4.614 3.7,4.563 4.008,4.563C4.727,4.563 5.268,4.836 5.632,5.382C6.001,5.923 6.111,6.628 5.961,7.496L5.814,8.336C5.656,9.274 5.299,9.988 4.743,10.478C4.188,10.963 3.465,11.206 2.573,11.206ZM10.577,11.206C9.392,11.206 8.515,10.795 7.945,9.974C7.381,9.153 7.229,8.009 7.49,6.544L7.749,5.039C8.006,3.606 8.498,2.54 9.226,1.84C9.959,1.14 10.883,0.79 11.998,0.79C12.535,0.79 12.995,0.879 13.377,1.056C13.76,1.229 14.059,1.46 14.273,1.749C14.488,2.038 14.612,2.314 14.644,2.575C14.677,2.832 14.644,3.053 14.546,3.24C14.453,3.427 14.313,3.567 14.126,3.66C13.94,3.749 13.753,3.774 13.566,3.737C13.384,3.695 13.244,3.606 13.146,3.471C13.048,3.336 12.941,3.2 12.824,3.065C12.708,2.925 12.57,2.815 12.411,2.736C12.257,2.657 12.068,2.617 11.844,2.617C11.35,2.617 10.918,2.808 10.549,3.191C10.185,3.569 9.936,4.141 9.8,4.906L9.422,7.048C9.287,7.813 9.348,8.399 9.604,8.805C9.861,9.211 10.248,9.414 10.766,9.414C11.256,9.414 11.665,9.267 11.991,8.973C12.318,8.679 12.54,8.264 12.656,7.727L12.775,7.055L11.893,7.055C11.665,7.055 11.48,6.964 11.34,6.782C11.205,6.6 11.158,6.385 11.2,6.138C11.233,5.951 11.326,5.795 11.48,5.669C11.639,5.538 11.809,5.473 11.991,5.473L13.979,5.473C14.25,5.473 14.462,5.566 14.616,5.753C14.775,5.94 14.831,6.168 14.784,6.439L14.595,7.489C14.376,8.702 13.916,9.626 13.216,10.261C12.521,10.891 11.641,11.206 10.577,11.206ZM16.001,6.01C15.799,6.01 15.637,5.937 15.514,5.792C15.39,5.643 15.346,5.47 15.379,5.271L16.181,0.735C16.218,0.53 16.321,0.357 16.492,0.214C16.666,0.068 16.856,-0.004 17.063,-0.004L18.983,-0.004C19.187,-0.004 19.351,0.068 19.474,0.214C19.597,0.36 19.642,0.529 19.609,0.722C19.581,0.868 19.505,0.992 19.382,1.096C19.259,1.197 19.126,1.247 18.983,1.247L17.462,1.247L16.841,4.758L18.21,4.758C18.414,4.758 18.577,4.831 18.697,4.977C18.82,5.122 18.865,5.292 18.832,5.485C18.806,5.631 18.732,5.755 18.609,5.859C18.486,5.96 18.353,6.01 18.21,6.01L16.001,6.01ZM16.526,3.515L16.732,2.364L18.281,2.364C18.472,2.364 18.623,2.432 18.735,2.566C18.847,2.698 18.888,2.853 18.857,3.032C18.832,3.167 18.762,3.281 18.647,3.377C18.535,3.469 18.413,3.515 18.281,3.515L16.526,3.515Z" />
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_5g_mobiledata_updated.xml b/packages/SettingsLib/res/drawable/ic_5g_mobiledata_updated.xml
new file mode 100644
index 000000000000..66787b0a7594
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_5g_mobiledata_updated.xml
@@ -0,0 +1,24 @@
+<!--
+ 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="17dp"
+ android:height="12.28dp"
+ android:viewportWidth="14.42"
+ android:viewportHeight="10.42">
+ <path
+ android:pathData="M2.807,10.416C2.247,10.416 1.774,10.325 1.386,10.143C0.999,9.956 0.684,9.704 0.441,9.387C0.199,9.065 0.056,8.745 0.014,8.428C-0.028,8.111 0.014,7.859 0.14,7.672C0.271,7.485 0.439,7.366 0.644,7.315C0.85,7.259 1.036,7.266 1.204,7.336C1.372,7.406 1.494,7.506 1.568,7.637C1.643,7.763 1.727,7.912 1.82,8.085C1.914,8.253 2.03,8.393 2.17,8.505C2.315,8.617 2.504,8.673 2.737,8.673C3.055,8.673 3.309,8.57 3.5,8.365C3.696,8.16 3.794,7.849 3.794,7.434L3.794,6.461C3.794,6.083 3.701,5.796 3.514,5.6C3.332,5.399 3.092,5.299 2.793,5.299C2.593,5.299 2.427,5.341 2.296,5.425C2.166,5.509 2.054,5.605 1.96,5.712C1.858,5.819 1.715,5.901 1.533,5.957C1.351,6.008 1.148,6.001 0.924,5.936C0.682,5.861 0.49,5.714 0.35,5.495C0.215,5.271 0.159,5.035 0.182,4.788L0.455,1.281C0.483,0.987 0.614,0.733 0.847,0.518C1.081,0.303 1.347,0.196 1.645,0.196L4.515,0.196C4.758,0.196 4.966,0.282 5.138,0.455C5.316,0.628 5.404,0.833 5.404,1.071C5.404,1.314 5.316,1.521 5.138,1.694C4.966,1.867 4.758,1.953 4.515,1.953L2.044,1.953L1.841,4.34L1.897,4.354C2.07,4.172 2.285,4.03 2.541,3.927C2.803,3.824 3.094,3.773 3.416,3.773C4.126,3.773 4.697,4.02 5.131,4.515C5.565,5.005 5.782,5.677 5.782,6.531L5.782,7.378C5.782,8.33 5.516,9.074 4.984,9.611C4.452,10.148 3.727,10.416 2.807,10.416ZM10.853,10.416C9.729,10.411 8.83,10.031 8.158,9.275C7.491,8.514 7.157,7.406 7.157,5.95L7.157,4.417C7.157,2.975 7.498,1.878 8.179,1.127C8.861,0.371 9.773,-0.005 10.916,0C11.43,0 11.864,0.075 12.218,0.224C12.578,0.369 12.883,0.576 13.135,0.847C13.392,1.113 13.569,1.374 13.667,1.631C13.765,1.883 13.772,2.121 13.688,2.345C13.604,2.569 13.46,2.732 13.254,2.835C13.054,2.938 12.858,2.968 12.666,2.926C12.475,2.884 12.326,2.802 12.218,2.681C12.111,2.555 12.001,2.422 11.889,2.282C11.777,2.142 11.637,2.032 11.469,1.953C11.301,1.869 11.101,1.827 10.867,1.827C10.349,1.822 9.934,2.011 9.621,2.394C9.313,2.777 9.159,3.358 9.159,4.137L9.159,6.293C9.159,7.063 9.318,7.644 9.635,8.036C9.957,8.428 10.377,8.624 10.895,8.624C11.399,8.624 11.794,8.477 12.078,8.183C12.363,7.889 12.517,7.474 12.54,6.937L12.54,6.265L11.567,6.265C11.362,6.265 11.185,6.188 11.035,6.034C10.886,5.875 10.811,5.689 10.811,5.474C10.811,5.255 10.888,5.068 11.042,4.914C11.201,4.755 11.39,4.676 11.609,4.676L13.576,4.676C13.81,4.676 14.008,4.755 14.171,4.914C14.335,5.073 14.419,5.269 14.423,5.502L14.423,6.538C14.423,7.77 14.101,8.724 13.457,9.401C12.818,10.078 11.95,10.416 10.853,10.416Z"
+ android:fillColor="#000"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_5g_plus_mobiledata_default_updated.xml b/packages/SettingsLib/res/drawable/ic_5g_plus_mobiledata_default_updated.xml
new file mode 100644
index 000000000000..5ba3c98028a2
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_5g_plus_mobiledata_default_updated.xml
@@ -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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="22dp"
+ android:height="11.63dp"
+ android:viewportWidth="19.71"
+ android:viewportHeight="10.42">
+ <path
+ android:pathData="M2.807,10.416C2.247,10.416 1.774,10.325 1.386,10.143C0.999,9.956 0.684,9.704 0.441,9.387C0.199,9.065 0.056,8.745 0.014,8.428C-0.028,8.111 0.014,7.859 0.14,7.672C0.271,7.485 0.439,7.366 0.644,7.315C0.85,7.259 1.036,7.266 1.204,7.336C1.372,7.406 1.494,7.506 1.568,7.637C1.643,7.763 1.727,7.912 1.82,8.085C1.914,8.253 2.03,8.393 2.17,8.505C2.315,8.617 2.504,8.673 2.737,8.673C3.055,8.673 3.309,8.57 3.5,8.365C3.696,8.16 3.794,7.849 3.794,7.434L3.794,6.461C3.794,6.083 3.701,5.796 3.514,5.6C3.332,5.399 3.092,5.299 2.793,5.299C2.593,5.299 2.427,5.341 2.296,5.425C2.166,5.509 2.054,5.605 1.96,5.712C1.858,5.819 1.715,5.901 1.533,5.957C1.351,6.008 1.148,6.001 0.924,5.936C0.682,5.861 0.49,5.714 0.35,5.495C0.215,5.271 0.159,5.035 0.182,4.788L0.455,1.281C0.483,0.987 0.614,0.733 0.847,0.518C1.081,0.303 1.347,0.196 1.645,0.196L4.515,0.196C4.758,0.196 4.966,0.282 5.138,0.455C5.316,0.628 5.404,0.833 5.404,1.071C5.404,1.314 5.316,1.521 5.138,1.694C4.966,1.867 4.758,1.953 4.515,1.953L2.044,1.953L1.841,4.34L1.897,4.354C2.07,4.172 2.285,4.03 2.541,3.927C2.803,3.824 3.094,3.773 3.416,3.773C4.126,3.773 4.697,4.02 5.131,4.515C5.565,5.005 5.782,5.677 5.782,6.531L5.782,7.378C5.782,8.33 5.516,9.074 4.984,9.611C4.452,10.148 3.727,10.416 2.807,10.416ZM10.853,10.416C9.729,10.411 8.83,10.031 8.158,9.275C7.491,8.514 7.157,7.406 7.157,5.95L7.157,4.417C7.157,2.975 7.498,1.878 8.179,1.127C8.861,0.371 9.773,-0.005 10.916,0C11.43,0 11.864,0.075 12.218,0.224C12.578,0.369 12.883,0.576 13.135,0.847C13.392,1.113 13.569,1.374 13.667,1.631C13.765,1.883 13.772,2.121 13.688,2.345C13.604,2.569 13.46,2.732 13.254,2.835C13.054,2.938 12.858,2.968 12.666,2.926C12.475,2.884 12.326,2.802 12.218,2.681C12.111,2.555 12.001,2.422 11.889,2.282C11.777,2.142 11.637,2.032 11.469,1.953C11.301,1.869 11.101,1.827 10.867,1.827C10.349,1.822 9.934,2.011 9.621,2.394C9.313,2.777 9.159,3.358 9.159,4.137L9.159,6.293C9.159,7.063 9.318,7.644 9.635,8.036C9.957,8.428 10.377,8.624 10.895,8.624C11.399,8.624 11.794,8.477 12.078,8.183C12.363,7.889 12.517,7.474 12.54,6.937L12.54,6.265L11.567,6.265C11.362,6.265 11.185,6.188 11.035,6.034C10.886,5.875 10.811,5.689 10.811,5.474C10.811,5.255 10.888,5.068 11.042,4.914C11.201,4.755 11.39,4.676 11.609,4.676L13.576,4.676C13.81,4.676 14.008,4.755 14.171,4.914C14.335,5.073 14.419,5.269 14.423,5.502L14.423,6.538C14.423,7.77 14.101,8.724 13.457,9.401C12.818,10.078 11.95,10.416 10.853,10.416Z"
+ android:fillColor="#000"/>
+ <path
+ android:pathData="M17.636,4.476C17.457,4.476 17.302,4.412 17.172,4.284C17.044,4.153 16.98,3.997 16.98,3.816L16.98,0.872C16.98,0.691 17.044,0.536 17.172,0.408C17.302,0.277 17.457,0.212 17.636,0.212C17.814,0.212 17.968,0.277 18.096,0.408C18.224,0.536 18.288,0.691 18.288,0.872L18.288,3.816C18.288,3.997 18.224,4.153 18.096,4.284C17.968,4.412 17.814,4.476 17.636,4.476ZM16.212,2.988C16.033,2.988 15.88,2.925 15.752,2.8C15.624,2.672 15.56,2.52 15.56,2.344C15.56,2.168 15.624,2.017 15.752,1.892C15.88,1.764 16.033,1.7 16.212,1.7L19.06,1.7C19.238,1.7 19.392,1.764 19.52,1.892C19.648,2.017 19.712,2.168 19.712,2.344C19.712,2.52 19.648,2.672 19.52,2.8C19.392,2.925 19.238,2.988 19.06,2.988L16.212,2.988Z"
+ android:fillColor="#000"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_5g_plus_mobiledata_updated.xml b/packages/SettingsLib/res/drawable/ic_5g_plus_mobiledata_updated.xml
new file mode 100644
index 000000000000..5ba3c98028a2
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_5g_plus_mobiledata_updated.xml
@@ -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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="22dp"
+ android:height="11.63dp"
+ android:viewportWidth="19.71"
+ android:viewportHeight="10.42">
+ <path
+ android:pathData="M2.807,10.416C2.247,10.416 1.774,10.325 1.386,10.143C0.999,9.956 0.684,9.704 0.441,9.387C0.199,9.065 0.056,8.745 0.014,8.428C-0.028,8.111 0.014,7.859 0.14,7.672C0.271,7.485 0.439,7.366 0.644,7.315C0.85,7.259 1.036,7.266 1.204,7.336C1.372,7.406 1.494,7.506 1.568,7.637C1.643,7.763 1.727,7.912 1.82,8.085C1.914,8.253 2.03,8.393 2.17,8.505C2.315,8.617 2.504,8.673 2.737,8.673C3.055,8.673 3.309,8.57 3.5,8.365C3.696,8.16 3.794,7.849 3.794,7.434L3.794,6.461C3.794,6.083 3.701,5.796 3.514,5.6C3.332,5.399 3.092,5.299 2.793,5.299C2.593,5.299 2.427,5.341 2.296,5.425C2.166,5.509 2.054,5.605 1.96,5.712C1.858,5.819 1.715,5.901 1.533,5.957C1.351,6.008 1.148,6.001 0.924,5.936C0.682,5.861 0.49,5.714 0.35,5.495C0.215,5.271 0.159,5.035 0.182,4.788L0.455,1.281C0.483,0.987 0.614,0.733 0.847,0.518C1.081,0.303 1.347,0.196 1.645,0.196L4.515,0.196C4.758,0.196 4.966,0.282 5.138,0.455C5.316,0.628 5.404,0.833 5.404,1.071C5.404,1.314 5.316,1.521 5.138,1.694C4.966,1.867 4.758,1.953 4.515,1.953L2.044,1.953L1.841,4.34L1.897,4.354C2.07,4.172 2.285,4.03 2.541,3.927C2.803,3.824 3.094,3.773 3.416,3.773C4.126,3.773 4.697,4.02 5.131,4.515C5.565,5.005 5.782,5.677 5.782,6.531L5.782,7.378C5.782,8.33 5.516,9.074 4.984,9.611C4.452,10.148 3.727,10.416 2.807,10.416ZM10.853,10.416C9.729,10.411 8.83,10.031 8.158,9.275C7.491,8.514 7.157,7.406 7.157,5.95L7.157,4.417C7.157,2.975 7.498,1.878 8.179,1.127C8.861,0.371 9.773,-0.005 10.916,0C11.43,0 11.864,0.075 12.218,0.224C12.578,0.369 12.883,0.576 13.135,0.847C13.392,1.113 13.569,1.374 13.667,1.631C13.765,1.883 13.772,2.121 13.688,2.345C13.604,2.569 13.46,2.732 13.254,2.835C13.054,2.938 12.858,2.968 12.666,2.926C12.475,2.884 12.326,2.802 12.218,2.681C12.111,2.555 12.001,2.422 11.889,2.282C11.777,2.142 11.637,2.032 11.469,1.953C11.301,1.869 11.101,1.827 10.867,1.827C10.349,1.822 9.934,2.011 9.621,2.394C9.313,2.777 9.159,3.358 9.159,4.137L9.159,6.293C9.159,7.063 9.318,7.644 9.635,8.036C9.957,8.428 10.377,8.624 10.895,8.624C11.399,8.624 11.794,8.477 12.078,8.183C12.363,7.889 12.517,7.474 12.54,6.937L12.54,6.265L11.567,6.265C11.362,6.265 11.185,6.188 11.035,6.034C10.886,5.875 10.811,5.689 10.811,5.474C10.811,5.255 10.888,5.068 11.042,4.914C11.201,4.755 11.39,4.676 11.609,4.676L13.576,4.676C13.81,4.676 14.008,4.755 14.171,4.914C14.335,5.073 14.419,5.269 14.423,5.502L14.423,6.538C14.423,7.77 14.101,8.724 13.457,9.401C12.818,10.078 11.95,10.416 10.853,10.416Z"
+ android:fillColor="#000"/>
+ <path
+ android:pathData="M17.636,4.476C17.457,4.476 17.302,4.412 17.172,4.284C17.044,4.153 16.98,3.997 16.98,3.816L16.98,0.872C16.98,0.691 17.044,0.536 17.172,0.408C17.302,0.277 17.457,0.212 17.636,0.212C17.814,0.212 17.968,0.277 18.096,0.408C18.224,0.536 18.288,0.691 18.288,0.872L18.288,3.816C18.288,3.997 18.224,4.153 18.096,4.284C17.968,4.412 17.814,4.476 17.636,4.476ZM16.212,2.988C16.033,2.988 15.88,2.925 15.752,2.8C15.624,2.672 15.56,2.52 15.56,2.344C15.56,2.168 15.624,2.017 15.752,1.892C15.88,1.764 16.033,1.7 16.212,1.7L19.06,1.7C19.238,1.7 19.392,1.764 19.52,1.892C19.648,2.017 19.712,2.168 19.712,2.344C19.712,2.52 19.648,2.672 19.52,2.8C19.392,2.925 19.238,2.988 19.06,2.988L16.212,2.988Z"
+ android:fillColor="#000"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_carrier_wifi_updated.xml b/packages/SettingsLib/res/drawable/ic_carrier_wifi_updated.xml
new file mode 100644
index 000000000000..14db82614ad1
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_carrier_wifi_updated.xml
@@ -0,0 +1,30 @@
+<?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.
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="19.0dp"
+ android:height="12.38dp"
+ android:viewportHeight="10.26"
+ android:viewportWidth="15.75">
+
+ <path
+ android:fillColor="#000"
+ android:pathData="M2.864,10.259C2.598,10.259 2.358,10.168 2.143,9.986C1.929,9.804 1.793,9.589 1.737,9.342L0.036,1.208C-0.043,0.877 0.013,0.594 0.204,0.361C0.4,0.128 0.657,0.011 0.974,0.011C1.208,0.011 1.418,0.093 1.604,0.256C1.791,0.419 1.908,0.611 1.954,0.83L2.885,5.828L3.039,6.92L3.088,6.92L3.249,5.828L4.278,0.851C4.325,0.622 4.446,0.424 4.642,0.256C4.843,0.088 5.065,0.004 5.307,0.004C5.55,0.004 5.769,0.088 5.965,0.256C6.166,0.424 6.292,0.625 6.343,0.858L7.358,5.814L7.526,6.913L7.575,6.913L7.729,5.814L8.653,0.781C8.695,0.571 8.805,0.391 8.982,0.242C9.16,0.093 9.356,0.018 9.57,0.018C9.874,0.018 10.114,0.128 10.291,0.347C10.473,0.566 10.529,0.828 10.459,1.131L8.765,9.342C8.709,9.594 8.574,9.811 8.359,9.993C8.145,10.17 7.902,10.259 7.631,10.259C7.37,10.259 7.132,10.17 6.917,9.993C6.703,9.811 6.57,9.594 6.518,9.342L5.44,4.19L5.279,3.133L5.23,3.133L5.069,4.183L3.977,9.342C3.926,9.589 3.793,9.804 3.578,9.986C3.364,10.168 3.126,10.259 2.864,10.259Z" />
+
+ <path
+ android:fillColor="#000"
+ android:pathData="M13.676,4.396C13.497,4.396 13.342,4.332 13.212,4.204C13.084,4.073 13.02,3.917 13.02,3.736L13.02,0.792C13.02,0.611 13.084,0.456 13.212,0.328C13.342,0.197 13.497,0.132 13.676,0.132C13.854,0.132 14.008,0.197 14.136,0.328C14.264,0.456 14.328,0.611 14.328,0.792L14.328,3.736C14.328,3.917 14.264,4.073 14.136,4.204C14.008,4.332 13.854,4.396 13.676,4.396ZM12.252,2.908C12.073,2.908 11.92,2.845 11.792,2.72C11.664,2.592 11.6,2.44 11.6,2.264C11.6,2.088 11.664,1.937 11.792,1.812C11.92,1.684 12.073,1.62 12.252,1.62L15.1,1.62C15.278,1.62 15.432,1.684 15.56,1.812C15.688,1.937 15.752,2.088 15.752,2.264C15.752,2.44 15.688,2.592 15.56,2.72C15.432,2.845 15.278,2.908 15.1,2.908L12.252,2.908Z" />
+
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_e_mobiledata_updated.xml b/packages/SettingsLib/res/drawable/ic_e_mobiledata_updated.xml
new file mode 100644
index 000000000000..febcd87efbc3
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_e_mobiledata_updated.xml
@@ -0,0 +1,24 @@
+<!--
+ 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="17dp"
+ android:height="31.08dp"
+ android:viewportWidth="5.48"
+ android:viewportHeight="10.02">
+ <path
+ android:pathData="M1.081,10.02C0.787,10.02 0.533,9.913 0.318,9.698C0.104,9.483 -0.004,9.229 -0.004,8.935L-0.004,1.081C-0.004,0.787 0.104,0.533 0.318,0.318C0.533,0.103 0.787,-0.004 1.081,-0.004L4.455,-0.004C4.707,-0.004 4.922,0.087 5.099,0.269C5.281,0.446 5.372,0.659 5.372,0.906C5.372,1.153 5.281,1.368 5.099,1.55C4.922,1.727 4.707,1.816 4.455,1.816L1.963,1.816L1.963,8.2L4.56,8.2C4.812,8.2 5.027,8.291 5.204,8.473C5.386,8.65 5.477,8.863 5.477,9.11C5.477,9.357 5.386,9.572 5.204,9.754C5.027,9.931 4.812,10.02 4.56,10.02L1.081,10.02ZM1.186,5.736L1.186,4.042L3.951,4.042C4.185,4.042 4.385,4.126 4.553,4.294C4.721,4.457 4.805,4.656 4.805,4.889C4.805,5.118 4.721,5.316 4.553,5.484C4.385,5.652 4.185,5.736 3.951,5.736L1.186,5.736Z"
+ android:fillColor="#000"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_g_mobiledata_updated.xml b/packages/SettingsLib/res/drawable/ic_g_mobiledata_updated.xml
new file mode 100644
index 000000000000..d719f7a05aa0
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_g_mobiledata_updated.xml
@@ -0,0 +1,24 @@
+<!--
+ 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="17dp"
+ android:height="24.37dp"
+ android:viewportWidth="7.27"
+ android:viewportHeight="10.42">
+ <path
+ android:pathData="M3.696,10.416C2.571,10.411 1.673,10.031 1.001,9.275C0.333,8.514 -0,7.406 -0,5.95L-0,4.417C-0,2.975 0.34,1.878 1.022,1.127C1.703,0.371 2.615,-0.005 3.759,0C4.272,0 4.706,0.075 5.061,0.224C5.42,0.369 5.726,0.576 5.978,0.847C6.235,1.113 6.412,1.374 6.51,1.631C6.608,1.883 6.615,2.121 6.531,2.345C6.447,2.569 6.302,2.732 6.097,2.835C5.896,2.938 5.7,2.968 5.509,2.926C5.317,2.884 5.168,2.802 5.061,2.681C4.953,2.555 4.844,2.422 4.732,2.282C4.62,2.142 4.48,2.032 4.312,1.953C4.144,1.869 3.943,1.827 3.71,1.827C3.192,1.822 2.776,2.011 2.464,2.394C2.156,2.777 2.002,3.358 2.002,4.137L2.002,6.293C2.002,7.063 2.16,7.644 2.478,8.036C2.8,8.428 3.22,8.624 3.738,8.624C4.242,8.624 4.636,8.477 4.921,8.183C5.205,7.889 5.359,7.474 5.383,6.937L5.383,6.265L4.41,6.265C4.204,6.265 4.027,6.188 3.878,6.034C3.728,5.875 3.654,5.689 3.654,5.474C3.654,5.255 3.731,5.068 3.885,4.914C4.043,4.755 4.232,4.676 4.452,4.676L6.419,4.676C6.652,4.676 6.85,4.755 7.014,4.914C7.177,5.073 7.261,5.269 7.266,5.502L7.266,6.538C7.266,7.77 6.944,8.724 6.3,9.401C5.661,10.078 4.792,10.416 3.696,10.416Z"
+ android:fillColor="#000"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_h_mobiledata_updated.xml b/packages/SettingsLib/res/drawable/ic_h_mobiledata_updated.xml
new file mode 100644
index 000000000000..e60ff8cde396
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_h_mobiledata_updated.xml
@@ -0,0 +1,24 @@
+<!--
+ 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="17dp"
+ android:height="26.68dp"
+ android:viewportWidth="6.53"
+ android:viewportHeight="10.25">
+ <path
+ android:pathData="M5.547,10.252C5.277,10.252 5.043,10.154 4.847,9.958C4.656,9.757 4.56,9.522 4.56,9.251L4.56,1.005C4.56,0.73 4.656,0.494 4.847,0.298C5.043,0.102 5.277,0.004 5.547,0.004C5.818,0.004 6.049,0.102 6.24,0.298C6.436,0.494 6.534,0.73 6.534,1.005L6.534,9.251C6.534,9.522 6.436,9.757 6.24,9.958C6.049,10.154 5.818,10.252 5.547,10.252ZM0.99,10.252C0.72,10.252 0.486,10.154 0.29,9.958C0.099,9.757 0.003,9.522 0.003,9.251L0.003,1.005C0.003,0.73 0.099,0.494 0.29,0.298C0.486,0.102 0.72,0.004 0.99,0.004C1.261,0.004 1.492,0.102 1.683,0.298C1.879,0.494 1.977,0.73 1.977,1.005L1.977,9.251C1.977,9.522 1.879,9.757 1.683,9.958C1.492,10.154 1.261,10.252 0.99,10.252ZM1.151,5.933L1.151,4.106L5.484,4.106L5.484,5.933L1.151,5.933Z"
+ android:fillColor="#000"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_h_plus_mobiledata_updated.xml b/packages/SettingsLib/res/drawable/ic_h_plus_mobiledata_updated.xml
new file mode 100644
index 000000000000..e02df563dfdd
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_h_plus_mobiledata_updated.xml
@@ -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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="16dp"
+ android:height="13.52dp"
+ android:viewportWidth="12.13"
+ android:viewportHeight="10.25">
+ <path
+ android:pathData="M5.541,10.252C5.271,10.252 5.037,10.154 4.841,9.958C4.65,9.757 4.554,9.522 4.554,9.251L4.554,1.005C4.554,0.73 4.65,0.494 4.841,0.298C5.037,0.102 5.271,0.004 5.541,0.004C5.812,0.004 6.043,0.102 6.234,0.298C6.43,0.494 6.528,0.73 6.528,1.005L6.528,9.251C6.528,9.522 6.43,9.757 6.234,9.958C6.043,10.154 5.812,10.252 5.541,10.252ZM0.984,10.252C0.714,10.252 0.48,10.154 0.284,9.958C0.093,9.757 -0.003,9.522 -0.003,9.251L-0.003,1.005C-0.003,0.73 0.093,0.494 0.284,0.298C0.48,0.102 0.714,0.004 0.984,0.004C1.255,0.004 1.486,0.102 1.677,0.298C1.873,0.494 1.971,0.73 1.971,1.005L1.971,9.251C1.971,9.522 1.873,9.757 1.677,9.958C1.486,10.154 1.255,10.252 0.984,10.252ZM1.145,5.933L1.145,4.106L5.478,4.106L5.478,5.933L1.145,5.933Z"
+ android:fillColor="#000"/>
+ <path
+ android:pathData="M10.056,4.396C9.877,4.396 9.722,4.332 9.592,4.204C9.464,4.073 9.4,3.917 9.4,3.736L9.4,0.792C9.4,0.611 9.464,0.456 9.592,0.328C9.722,0.197 9.877,0.132 10.056,0.132C10.234,0.132 10.388,0.197 10.516,0.328C10.644,0.456 10.708,0.611 10.708,0.792L10.708,3.736C10.708,3.917 10.644,4.073 10.516,4.204C10.388,4.332 10.234,4.396 10.056,4.396ZM8.632,2.908C8.453,2.908 8.3,2.845 8.172,2.72C8.044,2.592 7.98,2.44 7.98,2.264C7.98,2.088 8.044,1.937 8.172,1.812C8.3,1.684 8.453,1.62 8.632,1.62L11.48,1.62C11.658,1.62 11.812,1.684 11.94,1.812C12.068,1.937 12.132,2.088 12.132,2.264C12.132,2.44 12.068,2.592 11.94,2.72C11.812,2.845 11.658,2.908 11.48,2.908L8.632,2.908Z"
+ android:fillColor="#000"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_lte_mobiledata_updated.xml b/packages/SettingsLib/res/drawable/ic_lte_mobiledata_updated.xml
new file mode 100644
index 000000000000..9d64439cf30b
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_lte_mobiledata_updated.xml
@@ -0,0 +1,24 @@
+<!--
+ 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="20dp"
+ android:height="11.92dp"
+ android:viewportWidth="17.2"
+ android:viewportHeight="10.25">
+ <path
+ android:pathData="M1.082,10.14C0.788,10.14 0.534,10.033 0.319,9.818C0.105,9.603 -0.003,9.349 -0.003,9.055L-0.003,1.005C-0.003,0.73 0.093,0.494 0.284,0.298C0.48,0.102 0.714,0.004 0.984,0.004C1.255,0.004 1.486,0.102 1.677,0.298C1.873,0.494 1.971,0.73 1.971,1.005L1.971,8.313L4.19,8.313C4.442,8.313 4.659,8.404 4.841,8.586C5.023,8.763 5.114,8.976 5.114,9.223C5.114,9.475 5.023,9.692 4.841,9.874C4.659,10.051 4.442,10.14 4.19,10.14L1.082,10.14ZM7.59,10.252C7.32,10.252 7.086,10.154 6.89,9.958C6.694,9.757 6.596,9.522 6.596,9.251L6.596,1.068L8.577,1.068L8.577,9.251C8.577,9.522 8.479,9.757 8.283,9.958C8.087,10.154 7.856,10.252 7.59,10.252ZM5.413,1.936C5.161,1.936 4.944,1.847 4.762,1.67C4.585,1.488 4.496,1.273 4.496,1.026C4.496,0.779 4.585,0.566 4.762,0.389C4.944,0.207 5.161,0.116 5.413,0.116L9.753,0.116C10.005,0.116 10.222,0.207 10.404,0.389C10.586,0.566 10.677,0.779 10.677,1.026C10.677,1.273 10.586,1.488 10.404,1.67C10.222,1.847 10.005,1.936 9.753,1.936L5.413,1.936ZM12.799,10.14C12.505,10.14 12.251,10.033 12.036,9.818C11.821,9.603 11.714,9.349 11.714,9.055L11.714,1.201C11.714,0.907 11.821,0.653 12.036,0.438C12.251,0.223 12.505,0.116 12.799,0.116L16.173,0.116C16.425,0.116 16.64,0.207 16.817,0.389C16.999,0.566 17.09,0.779 17.09,1.026C17.09,1.273 16.999,1.488 16.817,1.67C16.64,1.847 16.425,1.936 16.173,1.936L13.681,1.936L13.681,8.32L16.278,8.32C16.53,8.32 16.745,8.411 16.922,8.593C17.104,8.77 17.195,8.983 17.195,9.23C17.195,9.477 17.104,9.692 16.922,9.874C16.745,10.051 16.53,10.14 16.278,10.14L12.799,10.14ZM12.904,5.856L12.904,4.162L15.669,4.162C15.902,4.162 16.103,4.246 16.271,4.414C16.439,4.577 16.523,4.776 16.523,5.009C16.523,5.238 16.439,5.436 16.271,5.604C16.103,5.772 15.902,5.856 15.669,5.856L12.904,5.856Z"
+ android:fillColor="#000"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_lte_plus_mobiledata_updated.xml b/packages/SettingsLib/res/drawable/ic_lte_plus_mobiledata_updated.xml
new file mode 100644
index 000000000000..7075516e6dd3
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_lte_plus_mobiledata_updated.xml
@@ -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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="25dp"
+ android:height="11.85dp"
+ android:viewportWidth="21.63"
+ android:viewportHeight="10.25">
+ <path
+ android:pathData="M1.082,10.14C0.788,10.14 0.534,10.033 0.319,9.818C0.105,9.603 -0.003,9.349 -0.003,9.055L-0.003,1.005C-0.003,0.73 0.093,0.494 0.284,0.298C0.48,0.102 0.714,0.004 0.984,0.004C1.255,0.004 1.486,0.102 1.677,0.298C1.873,0.494 1.971,0.73 1.971,1.005L1.971,8.313L4.19,8.313C4.442,8.313 4.659,8.404 4.841,8.586C5.023,8.763 5.114,8.976 5.114,9.223C5.114,9.475 5.023,9.692 4.841,9.874C4.659,10.051 4.442,10.14 4.19,10.14L1.082,10.14ZM7.59,10.252C7.32,10.252 7.086,10.154 6.89,9.958C6.694,9.757 6.596,9.522 6.596,9.251L6.596,1.068L8.577,1.068L8.577,9.251C8.577,9.522 8.479,9.757 8.283,9.958C8.087,10.154 7.856,10.252 7.59,10.252ZM5.413,1.936C5.161,1.936 4.944,1.847 4.762,1.67C4.585,1.488 4.496,1.273 4.496,1.026C4.496,0.779 4.585,0.566 4.762,0.389C4.944,0.207 5.161,0.116 5.413,0.116L9.753,0.116C10.005,0.116 10.222,0.207 10.404,0.389C10.586,0.566 10.677,0.779 10.677,1.026C10.677,1.273 10.586,1.488 10.404,1.67C10.222,1.847 10.005,1.936 9.753,1.936L5.413,1.936ZM12.799,10.14C12.505,10.14 12.251,10.033 12.036,9.818C11.821,9.603 11.714,9.349 11.714,9.055L11.714,1.201C11.714,0.907 11.821,0.653 12.036,0.438C12.251,0.223 12.505,0.116 12.799,0.116L16.173,0.116C16.425,0.116 16.64,0.207 16.817,0.389C16.999,0.566 17.09,0.779 17.09,1.026C17.09,1.273 16.999,1.488 16.817,1.67C16.64,1.847 16.425,1.936 16.173,1.936L13.681,1.936L13.681,8.32L16.278,8.32C16.53,8.32 16.745,8.411 16.922,8.593C17.104,8.77 17.195,8.983 17.195,9.23C17.195,9.477 17.104,9.692 16.922,9.874C16.745,10.051 16.53,10.14 16.278,10.14L12.799,10.14ZM12.904,5.856L12.904,4.162L15.669,4.162C15.902,4.162 16.103,4.246 16.271,4.414C16.439,4.577 16.523,4.776 16.523,5.009C16.523,5.238 16.439,5.436 16.271,5.604C16.103,5.772 15.902,5.856 15.669,5.856L12.904,5.856Z"
+ android:fillColor="#000"/>
+ <path
+ android:pathData="M19.556,4.396C19.377,4.396 19.222,4.332 19.092,4.204C18.964,4.073 18.9,3.917 18.9,3.736L18.9,0.792C18.9,0.611 18.964,0.456 19.092,0.328C19.222,0.197 19.377,0.132 19.556,0.132C19.734,0.132 19.888,0.197 20.016,0.328C20.144,0.456 20.208,0.611 20.208,0.792L20.208,3.736C20.208,3.917 20.144,4.073 20.016,4.204C19.888,4.332 19.734,4.396 19.556,4.396ZM18.132,2.908C17.953,2.908 17.8,2.845 17.672,2.72C17.544,2.592 17.48,2.44 17.48,2.264C17.48,2.088 17.544,1.937 17.672,1.812C17.8,1.684 17.953,1.62 18.132,1.62L20.98,1.62C21.158,1.62 21.312,1.684 21.44,1.812C21.568,1.937 21.632,2.088 21.632,2.264C21.632,2.44 21.568,2.592 21.44,2.72C21.312,2.845 21.158,2.908 20.98,2.908L18.132,2.908Z"
+ android:fillColor="#000"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_mobile_0_4_bar.xml b/packages/SettingsLib/res/drawable/ic_mobile_0_4_bar.xml
index d9a417f1ea99..54c878810d10 100644
--- a/packages/SettingsLib/res/drawable/ic_mobile_0_4_bar.xml
+++ b/packages/SettingsLib/res/drawable/ic_mobile_0_4_bar.xml
@@ -1,5 +1,5 @@
<!--
- Copyright (C) 2023 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,24 +14,24 @@
limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="14dp"
- android:height="14dp"
- android:viewportWidth="14.0"
- android:viewportHeight="14.0">
+ android:width="17dp"
+ android:height="12dp"
+ android:viewportWidth="16.0"
+ android:viewportHeight="12.0">
<path
- android:pathData="M8.25,3L9.25,3A0.5,0.5 0,0 1,9.75 3.5L9.75,13.5A0.5,0.5 0,0 1,9.25 14L8.25,14A0.5,0.5 0,0 1,7.75 13.5L7.75,3.5A0.5,0.5 0,0 1,8.25 3z"
- android:fillAlpha="0.24"
+ android:pathData="M1.249,7C1.939,7 2.499,7.56 2.499,8.25L2.499,10.75C2.499,11.44 1.939,12 1.249,12C0.559,12 -0.001,11.44 -0.001,10.75L-0.001,8.25C-0.001,7.56 0.559,7 1.249,7Z"
+ android:fillAlpha="0.45"
android:fillColor="#000"/>
<path
- android:pathData="M11.75,0L12.75,0A0.5,0.5 0,0 1,13.25 0.5L13.25,13.5A0.5,0.5 0,0 1,12.75 14L11.75,14A0.5,0.5 0,0 1,11.25 13.5L11.25,0.5A0.5,0.5 0,0 1,11.75 0z"
- android:fillAlpha="0.24"
+ android:pathData="M5.749,4.5C6.439,4.5 6.999,5.06 6.999,5.75L6.999,10.75C6.999,11.44 6.439,12 5.749,12C5.059,12 4.499,11.44 4.499,10.75L4.499,5.75C4.499,5.06 5.059,4.5 5.749,4.5Z"
+ android:fillAlpha="0.45"
android:fillColor="#000"/>
<path
- android:pathData="M1.25,10L2.25,10A0.5,0.5 0,0 1,2.75 10.5L2.75,13.5A0.5,0.5 0,0 1,2.25 14L1.25,14A0.5,0.5 0,0 1,0.75 13.5L0.75,10.5A0.5,0.5 0,0 1,1.25 10z"
- android:fillAlpha="0.24"
+ android:pathData="M10.249,2C10.939,2 11.499,2.56 11.499,3.25L11.499,10.75C11.499,11.44 10.939,12 10.249,12C9.559,12 8.999,11.44 8.999,10.75L8.999,3.25C8.999,2.56 9.559,2 10.249,2Z"
+ android:fillAlpha="0.45"
android:fillColor="#000"/>
<path
- android:pathData="M4.75,6.5L5.75,6.5A0.5,0.5 0,0 1,6.25 7L6.25,13.5A0.5,0.5 0,0 1,5.75 14L4.75,14A0.5,0.5 0,0 1,4.25 13.5L4.25,7A0.5,0.5 0,0 1,4.75 6.5z"
- android:fillAlpha="0.24"
+ android:pathData="M14.749,0C15.439,0 15.999,0.56 15.999,1.25L15.999,10.75C15.999,11.44 15.439,12 14.749,12C14.059,12 13.499,11.44 13.499,10.75L13.499,1.25C13.499,0.56 14.059,0 14.749,0Z"
+ android:fillAlpha="0.45"
android:fillColor="#000"/>
</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_mobile_0_4_bar_error.xml b/packages/SettingsLib/res/drawable/ic_mobile_0_4_bar_error.xml
index facc285a45ca..6015be8c894f 100644
--- a/packages/SettingsLib/res/drawable/ic_mobile_0_4_bar_error.xml
+++ b/packages/SettingsLib/res/drawable/ic_mobile_0_4_bar_error.xml
@@ -1,28 +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.
+-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="15dp"
- android:height="14dp"
- android:viewportWidth="15.0"
- android:viewportHeight="14.0">
+ android:width="17dp"
+ android:height="12dp"
+ android:viewportWidth="16.0"
+ android:viewportHeight="12.0">
+ <group>
+ <!-- clip-out the circle which will contain the exclamation point (below this group) -->
+ <clip-path
+ android:pathData="
+ M0,0
+ V13.5,0
+ H13.5,20
+ V0,20
+ H0,0
+ M14.999,13.5C17.761,13.5 19.999,11.261 19.999,8.5C19.999,5.739 17.761,3.5 14.999,3.5C12.238,3.5 9.999,5.739 9.999,8.5C9.999,11.261 12.238,13.5 14.999,13.5Z" />
+ <path
+ android:pathData="M1.249,7C1.939,7 2.499,7.56 2.499,8.25L2.499,10.75C2.499,11.44 1.939,12 1.249,12C0.559,12 -0.001,11.44 -0.001,10.75L-0.001,8.25C-0.001,7.56 0.559,7 1.249,7Z"
+ android:fillAlpha="0.45"
+ android:fillColor="#000"/>
+ <path
+ android:pathData="M5.749,4.5C6.439,4.5 6.999,5.06 6.999,5.75L6.999,10.75C6.999,11.44 6.439,12 5.749,12C5.059,12 4.499,11.44 4.499,10.75L4.499,5.75C4.499,5.06 5.059,4.5 5.749,4.5Z"
+ android:fillAlpha="0.45"
+ android:fillColor="#000"/>
+ <path
+ android:pathData="M10.249,2C10.939,2 11.499,2.56 11.499,3.25L11.499,10.75C11.499,11.44 10.939,12 10.249,12C9.559,12 8.999,11.44 8.999,10.75L8.999,3.25C8.999,2.56 9.559,2 10.249,2Z"
+ android:fillAlpha="0.45"
+ android:fillColor="#000"/>
+ <path
+ android:pathData="M14.749,0C15.439,0 15.999,0.56 15.999,1.25L15.999,10.75C15.999,11.44 15.439,12 14.749,12C14.059,12 13.499,11.44 13.499,10.75L13.499,1.25C13.499,0.56 14.059,0 14.749,0Z"
+ android:fillAlpha="0.45"
+ android:fillColor="#000"/>
+ </group>
<path
- android:pathData="M7,3.5C7,3.224 7.224,3 7.5,3H8.5C8.776,3 9,3.224 9,3.5V13.5C9,13.776 8.776,14 8.5,14H7.5C7.224,14 7,13.776 7,13.5V3.5Z"
- android:fillAlpha="0.3"
- android:fillColor="#000"/>
- <path
- android:pathData="M0,10.5C0,10.224 0.224,10 0.5,10H1.5C1.776,10 2,10.224 2,10.5V13.5C2,13.776 1.776,14 1.5,14H0.5C0.224,14 0,13.776 0,13.5V10.5Z"
- android:fillAlpha="0.3"
- android:fillColor="#000"/>
- <path
- android:pathData="M3.5,7C3.5,6.724 3.724,6.5 4,6.5H5C5.276,6.5 5.5,6.724 5.5,7V13.5C5.5,13.776 5.276,14 5,14H4C3.724,14 3.5,13.776 3.5,13.5V7Z"
- android:fillAlpha="0.3"
- android:fillColor="#000"/>
- <path
- android:pathData="M11,0C10.724,0 10.5,0.224 10.5,0.5V3H12.5V0.5C12.5,0.224 12.276,0 12,0H11Z"
- android:fillAlpha="0.3"
- android:fillColor="#000"/>
- <path
- android:pathData="M12.25,13C12.25,12.448 12.698,12 13.25,12C13.802,12 14.25,12.448 14.25,13C14.25,13.552 13.802,14 13.25,14C12.698,14 12.25,13.552 12.25,13Z"
- android:fillColor="#000"/>
- <path
- android:pathData="M12.25,5C12.25,4.724 12.474,4.5 12.75,4.5H13.75C14.026,4.5 14.25,4.724 14.25,5V10C14.25,10.276 14.026,10.5 13.75,10.5H12.75C12.474,10.5 12.25,10.276 12.25,10V5Z"
+ android:pathData="M14.999,5C14.589,5 14.249,5.34 14.249,5.75L14.249,8.75C14.249,9.16 14.589,9.5 14.999,9.5C15.409,9.5 15.749,9.16 15.749,8.75L15.749,5.75C15.749,5.34 15.409,5 14.999,5ZM14.999,12C15.409,12 15.749,11.66 15.749,11.25C15.749,10.84 15.409,10.5 14.999,10.5C14.589,10.5 14.249,10.84 14.249,11.25C14.249,11.66 14.589,12 14.999,12Z"
android:fillColor="#000"/>
</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_mobile_0_5_bar.xml b/packages/SettingsLib/res/drawable/ic_mobile_0_5_bar.xml
index 2c05a938c2cf..7c85b9e44383 100644
--- a/packages/SettingsLib/res/drawable/ic_mobile_0_5_bar.xml
+++ b/packages/SettingsLib/res/drawable/ic_mobile_0_5_bar.xml
@@ -14,28 +14,28 @@
limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="16dp"
- android:height="14dp"
- android:viewportWidth="16.0"
- android:viewportHeight="14.0">
+ android:width="21dp"
+ android:height="12dp"
+ android:viewportWidth="20.5"
+ android:viewportHeight="12.0">
<path
- android:pathData="M7.5,5L8.5,5A0.5,0.5 0,0 1,9 5.5L9,13.5A0.5,0.5 0,0 1,8.5 14L7.5,14A0.5,0.5 0,0 1,7 13.5L7,5.5A0.5,0.5 0,0 1,7.5 5z"
- android:fillAlpha="0.24"
+ android:pathData="M1.249,7C1.939,7 2.499,7.56 2.499,8.25L2.499,10.75C2.499,11.44 1.939,12 1.249,12C0.559,12 -0.001,11.44 -0.001,10.75L-0.001,8.25C-0.001,7.56 0.559,7 1.249,7Z"
+ android:fillAlpha="0.45"
android:fillColor="#000"/>
<path
- android:pathData="M11,2L12,2A0.5,0.5 0,0 1,12.5 2.5L12.5,13.5A0.5,0.5 0,0 1,12 14L11,14A0.5,0.5 0,0 1,10.5 13.5L10.5,2.5A0.5,0.5 0,0 1,11 2z"
- android:fillAlpha="0.24"
+ android:pathData="M5.749,5C6.439,5 6.999,5.56 6.999,6.25L6.999,10.75C6.999,11.44 6.439,12 5.749,12C5.059,12 4.499,11.44 4.499,10.75L4.499,6.25C4.499,5.56 5.059,5 5.749,5Z"
+ android:fillAlpha="0.45"
android:fillColor="#000"/>
<path
- android:pathData="M0.5,11L1.5,11A0.5,0.5 0,0 1,2 11.5L2,13.5A0.5,0.5 0,0 1,1.5 14L0.5,14A0.5,0.5 0,0 1,0 13.5L0,11.5A0.5,0.5 0,0 1,0.5 11z"
- android:fillAlpha="0.24"
+ android:pathData="M10.249,3C10.939,3 11.499,3.56 11.499,4.25L11.499,10.75C11.499,11.44 10.939,12 10.249,12C9.559,12 8.999,11.44 8.999,10.75L8.999,4.25C8.999,3.56 9.559,3 10.249,3Z"
+ android:fillAlpha="0.45"
android:fillColor="#000"/>
<path
- android:pathData="M14.5,0L15.5,0A0.5,0.5 0,0 1,16 0.5L16,13.5A0.5,0.5 0,0 1,15.5 14L14.5,14A0.5,0.5 0,0 1,14 13.5L14,0.5A0.5,0.5 0,0 1,14.5 0z"
- android:fillAlpha="0.24"
+ android:pathData="M14.749,1.5C15.439,1.5 15.999,2.06 15.999,2.75L15.999,10.75C15.999,11.44 15.439,12 14.749,12C14.059,12 13.499,11.44 13.499,10.75L13.499,2.75C13.499,2.06 14.059,1.5 14.749,1.5Z"
+ android:fillAlpha="0.45"
android:fillColor="#000"/>
<path
- android:pathData="M4,8L5,8A0.5,0.5 0,0 1,5.5 8.5L5.5,13.5A0.5,0.5 0,0 1,5 14L4,14A0.5,0.5 0,0 1,3.5 13.5L3.5,8.5A0.5,0.5 0,0 1,4 8z"
- android:fillAlpha="0.24"
+ android:pathData="M19.249,0C19.939,0 20.499,0.56 20.499,1.25L20.499,10.75C20.499,11.44 19.939,12 19.249,12C18.559,12 17.999,11.44 17.999,10.75L17.999,1.25C17.999,0.56 18.559,0 19.249,0Z"
+ android:fillAlpha="0.45"
android:fillColor="#000"/>
</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_mobile_0_5_bar_error.xml b/packages/SettingsLib/res/drawable/ic_mobile_0_5_bar_error.xml
index 328e45ec7e19..f75ec57f2a5e 100644
--- a/packages/SettingsLib/res/drawable/ic_mobile_0_5_bar_error.xml
+++ b/packages/SettingsLib/res/drawable/ic_mobile_0_5_bar_error.xml
@@ -1,32 +1,54 @@
+<!--
+ 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.
+-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="18dp"
- android:height="14dp"
- android:viewportWidth="18.0"
- android:viewportHeight="14.0">
+ android:width="21dp"
+ android:height="12dp"
+ android:viewportHeight="12.0"
+ android:viewportWidth="20.5">
+ <!-- clip-out the circle which will contain the exclamation point (below this group) -->
+ <group>
+ <clip-path android:pathData="
+ M0,0
+ H20.5
+ V12.0
+ H0
+ Z
+ M19.499,13.5C22.261,13.5 24.499,11.261 24.499,8.5C24.499,5.739 22.261,3.5 19.499,3.5C16.738,3.5 14.499,5.739 14.499,8.5C14.499,11.261 16.738,13.5 19.499,13.5Z" />
+ <path
+ android:fillAlpha="0.45"
+ android:fillColor="#000"
+ android:pathData="M1.249,7C1.939,7 2.499,7.56 2.499,8.25L2.499,10.75C2.499,11.44 1.939,12 1.249,12C0.559,12 -0.001,11.44 -0.001,10.75L-0.001,8.25C-0.001,7.56 0.559,7 1.249,7Z" />
+ <path
+ android:fillAlpha="0.45"
+ android:fillColor="#000"
+ android:pathData="M5.749,5C6.439,5 6.999,5.56 6.999,6.25L6.999,10.75C6.999,11.44 6.439,12 5.749,12C5.059,12 4.499,11.44 4.499,10.75L4.499,6.25C4.499,5.56 5.059,5 5.749,5Z" />
+ <path
+ android:fillAlpha="0.45"
+ android:fillColor="#000"
+ android:pathData="M10.249,3C10.939,3 11.499,3.56 11.499,4.25L11.499,10.75C11.499,11.44 10.939,12 10.249,12C9.559,12 8.999,11.44 8.999,10.75L8.999,4.25C8.999,3.56 9.559,3 10.249,3Z" />
+ <path
+ android:fillAlpha="0.45"
+ android:fillColor="#000"
+ android:pathData="M14.749,1.5C15.439,1.5 15.999,2.06 15.999,2.75L15.999,10.75C15.999,11.44 15.439,12 14.749,12C14.059,12 13.499,11.44 13.499,10.75L13.499,2.75C13.499,2.06 14.059,1.5 14.749,1.5Z" />
+ <path
+ android:fillAlpha="0.45"
+ android:fillColor="#000"
+ android:pathData="M19.249,0C19.939,0 20.499,0.56 20.499,1.25L20.499,10.75C20.499,11.44 19.939,12 19.249,12C18.559,12 17.999,11.44 17.999,10.75L17.999,1.25C17.999,0.56 18.559,0 19.249,0Z" />
+ </group>
<path
- android:pathData="M14,0.5C14,0.224 14.224,0 14.5,0H15.5C15.776,0 16,0.224 16,0.5V3H14V0.5Z"
- android:fillAlpha="0.3"
- android:fillColor="#000"/>
- <path
- android:pathData="M10.5,2.5C10.5,2.224 10.724,2 11,2H12C12.276,2 12.5,2.224 12.5,2.5V13.5C12.5,13.776 12.276,14 12,14H11C10.724,14 10.5,13.776 10.5,13.5V2.5Z"
- android:fillAlpha="0.3"
- android:fillColor="#000"/>
- <path
- android:pathData="M7,5.5C7,5.224 7.224,5 7.5,5H8.5C8.776,5 9,5.224 9,5.5V13.5C9,13.776 8.776,14 8.5,14H7.5C7.224,14 7,13.776 7,13.5V5.5Z"
- android:fillAlpha="0.3"
- android:fillColor="#000"/>
- <path
- android:pathData="M0.5,11C0.224,11 0,11.224 0,11.5V13.5C0,13.776 0.224,14 0.5,14H1.5C1.776,14 2,13.776 2,13.5V11.5C2,11.224 1.776,11 1.5,11H0.5Z"
- android:fillAlpha="0.3"
- android:fillColor="#000"/>
- <path
- android:pathData="M4,8C3.724,8 3.5,8.224 3.5,8.5V13.5C3.5,13.776 3.724,14 4,14H5C5.276,14 5.5,13.776 5.5,13.5V8.5C5.5,8.224 5.276,8 5,8H4Z"
- android:fillAlpha="0.3"
- android:fillColor="#000"/>
- <path
- android:pathData="M16,13C16,12.448 16.448,12 17,12C17.552,12 18,12.448 18,13C18,13.552 17.552,14 17,14C16.448,14 16,13.552 16,13Z"
- android:fillColor="#000"/>
- <path
- android:pathData="M16,5C16,4.724 16.224,4.5 16.5,4.5H17.5C17.776,4.5 18,4.724 18,5V10C18,10.276 17.776,10.5 17.5,10.5H16.5C16.224,10.5 16,10.276 16,10V5Z"
- android:fillColor="#000"/>
+ android:fillColor="#000"
+ android:pathData="M19.499,5C19.089,5 18.749,5.34 18.749,5.75L18.749,8.75C18.749,9.16 19.089,9.5 19.499,9.5C19.909,9.5 20.249,9.16 20.249,8.75L20.249,5.75C20.249,5.34 19.909,5 19.499,5ZM19.499,12C19.909,12 20.249,11.66 20.249,11.25C20.249,10.84 19.909,10.5 19.499,10.5C19.089,10.5 18.749,10.84 18.749,11.25C18.749,11.66 19.089,12 19.499,12Z" />
</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_mobile_1_4_bar.xml b/packages/SettingsLib/res/drawable/ic_mobile_1_4_bar.xml
index b9054ba7a4e3..df89aef3b63d 100644
--- a/packages/SettingsLib/res/drawable/ic_mobile_1_4_bar.xml
+++ b/packages/SettingsLib/res/drawable/ic_mobile_1_4_bar.xml
@@ -14,23 +14,23 @@
limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="14dp"
- android:height="14dp"
- android:viewportWidth="14.0"
- android:viewportHeight="14.0">
+ android:width="17dp"
+ android:height="12dp"
+ android:viewportWidth="16.0"
+ android:viewportHeight="12.0">
<path
- android:pathData="M8.25,3L9.25,3A0.5,0.5 0,0 1,9.75 3.5L9.75,13.5A0.5,0.5 0,0 1,9.25 14L8.25,14A0.5,0.5 0,0 1,7.75 13.5L7.75,3.5A0.5,0.5 0,0 1,8.25 3z"
- android:fillAlpha="0.24"
+ android:pathData="M1.249,7C1.939,7 2.499,7.56 2.499,8.25L2.499,10.75C2.499,11.44 1.939,12 1.249,12C0.559,12 -0.001,11.44 -0.001,10.75L-0.001,8.25C-0.001,7.56 0.559,7 1.249,7Z"
android:fillColor="#000"/>
<path
- android:pathData="M11.75,0L12.75,0A0.5,0.5 0,0 1,13.25 0.5L13.25,13.5A0.5,0.5 0,0 1,12.75 14L11.75,14A0.5,0.5 0,0 1,11.25 13.5L11.25,0.5A0.5,0.5 0,0 1,11.75 0z"
- android:fillAlpha="0.24"
+ android:pathData="M5.749,4.5C6.439,4.5 6.999,5.06 6.999,5.75L6.999,10.75C6.999,11.44 6.439,12 5.749,12C5.059,12 4.499,11.44 4.499,10.75L4.499,5.75C4.499,5.06 5.059,4.5 5.749,4.5Z"
+ android:fillAlpha="0.45"
android:fillColor="#000"/>
<path
- android:pathData="M1.25,10L2.25,10A0.5,0.5 0,0 1,2.75 10.5L2.75,13.5A0.5,0.5 0,0 1,2.25 14L1.25,14A0.5,0.5 0,0 1,0.75 13.5L0.75,10.5A0.5,0.5 0,0 1,1.25 10z"
+ android:pathData="M10.249,2C10.939,2 11.499,2.56 11.499,3.25L11.499,10.75C11.499,11.44 10.939,12 10.249,12C9.559,12 8.999,11.44 8.999,10.75L8.999,3.25C8.999,2.56 9.559,2 10.249,2Z"
+ android:fillAlpha="0.45"
android:fillColor="#000"/>
<path
- android:pathData="M4.75,6.5L5.75,6.5A0.5,0.5 0,0 1,6.25 7L6.25,13.5A0.5,0.5 0,0 1,5.75 14L4.75,14A0.5,0.5 0,0 1,4.25 13.5L4.25,7A0.5,0.5 0,0 1,4.75 6.5z"
- android:fillAlpha="0.24"
+ android:pathData="M14.749,0C15.439,0 15.999,0.56 15.999,1.25L15.999,10.75C15.999,11.44 15.439,12 14.749,12C14.059,12 13.499,11.44 13.499,10.75L13.499,1.25C13.499,0.56 14.059,0 14.749,0Z"
+ android:fillAlpha="0.45"
android:fillColor="#000"/>
</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_mobile_1_4_bar_error.xml b/packages/SettingsLib/res/drawable/ic_mobile_1_4_bar_error.xml
index 03a93491491c..fb73b6b253e1 100644
--- a/packages/SettingsLib/res/drawable/ic_mobile_1_4_bar_error.xml
+++ b/packages/SettingsLib/res/drawable/ic_mobile_1_4_bar_error.xml
@@ -1,27 +1,35 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="15dp"
- android:height="14dp"
- android:viewportWidth="15.0"
- android:viewportHeight="14.0">
+ android:width="17dp"
+ android:height="12dp"
+ android:viewportWidth="16.0"
+ android:viewportHeight="12.0">
+ <group>
+ <!-- clip-out the circle which will contain the exclamation point (below this group) -->
+ <clip-path
+ android:pathData="
+ M0,0
+ V13.5,0
+ H13.5,20
+ V0,20
+ H0,0
+ M14.999,13.5C17.761,13.5 19.999,11.261 19.999,8.5C19.999,5.739 17.761,3.5 14.999,3.5C12.238,3.5 9.999,5.739 9.999,8.5C9.999,11.261 12.238,13.5 14.999,13.5Z" />
+ <path
+ android:pathData="M1.249,7C1.939,7 2.499,7.56 2.499,8.25L2.499,10.75C2.499,11.44 1.939,12 1.249,12C0.559,12 -0.001,11.44 -0.001,10.75L-0.001,8.25C-0.001,7.56 0.559,7 1.249,7Z"
+ android:fillColor="#000"/>
+ <path
+ android:pathData="M5.749,4.5C6.439,4.5 6.999,5.06 6.999,5.75L6.999,10.75C6.999,11.44 6.439,12 5.749,12C5.059,12 4.499,11.44 4.499,10.75L4.499,5.75C4.499,5.06 5.059,4.5 5.749,4.5Z"
+ android:fillAlpha="0.45"
+ android:fillColor="#000"/>
+ <path
+ android:pathData="M10.249,2C10.939,2 11.499,2.56 11.499,3.25L11.499,10.75C11.499,11.44 10.939,12 10.249,12C9.559,12 8.999,11.44 8.999,10.75L8.999,3.25C8.999,2.56 9.559,2 10.249,2Z"
+ android:fillAlpha="0.45"
+ android:fillColor="#000"/>
+ <path
+ android:pathData="M14.749,0C15.439,0 15.999,0.56 15.999,1.25L15.999,10.75C15.999,11.44 15.439,12 14.749,12C14.059,12 13.499,11.44 13.499,10.75L13.499,1.25C13.499,0.56 14.059,0 14.749,0Z"
+ android:fillAlpha="0.45"
+ android:fillColor="#000"/>
+ </group>
<path
- android:pathData="M7,3.5C7,3.224 7.224,3 7.5,3H8.5C8.776,3 9,3.224 9,3.5V13.5C9,13.776 8.776,14 8.5,14H7.5C7.224,14 7,13.776 7,13.5V3.5Z"
- android:fillAlpha="0.3"
- android:fillColor="#000"/>
- <path
- android:pathData="M0,10.5C0,10.224 0.224,10 0.5,10H1.5C1.776,10 2,10.224 2,10.5V13.5C2,13.776 1.776,14 1.5,14H0.5C0.224,14 0,13.776 0,13.5V10.5Z"
- android:fillColor="#000"/>
- <path
- android:pathData="M3.5,7C3.5,6.724 3.724,6.5 4,6.5H5C5.276,6.5 5.5,6.724 5.5,7V13.5C5.5,13.776 5.276,14 5,14H4C3.724,14 3.5,13.776 3.5,13.5V7Z"
- android:fillAlpha="0.3"
- android:fillColor="#000"/>
- <path
- android:pathData="M11,0C10.724,0 10.5,0.224 10.5,0.5V3H12.5V0.5C12.5,0.224 12.276,0 12,0H11Z"
- android:fillAlpha="0.3"
- android:fillColor="#000"/>
- <path
- android:pathData="M12.25,13C12.25,12.448 12.698,12 13.25,12C13.802,12 14.25,12.448 14.25,13C14.25,13.552 13.802,14 13.25,14C12.698,14 12.25,13.552 12.25,13Z"
- android:fillColor="#000"/>
- <path
- android:pathData="M12.25,5C12.25,4.724 12.474,4.5 12.75,4.5H13.75C14.026,4.5 14.25,4.724 14.25,5V10C14.25,10.276 14.026,10.5 13.75,10.5H12.75C12.474,10.5 12.25,10.276 12.25,10V5Z"
+ android:pathData="M14.999,5C14.589,5 14.249,5.34 14.249,5.75L14.249,8.75C14.249,9.16 14.589,9.5 14.999,9.5C15.409,9.5 15.749,9.16 15.749,8.75L15.749,5.75C15.749,5.34 15.409,5 14.999,5ZM14.999,12C15.409,12 15.749,11.66 15.749,11.25C15.749,10.84 15.409,10.5 14.999,10.5C14.589,10.5 14.249,10.84 14.249,11.25C14.249,11.66 14.589,12 14.999,12Z"
android:fillColor="#000"/>
</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_mobile_1_5_bar.xml b/packages/SettingsLib/res/drawable/ic_mobile_1_5_bar.xml
index 774e91794df3..5b7d8daa74f2 100644
--- a/packages/SettingsLib/res/drawable/ic_mobile_1_5_bar.xml
+++ b/packages/SettingsLib/res/drawable/ic_mobile_1_5_bar.xml
@@ -14,27 +14,27 @@
limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="16dp"
- android:height="14dp"
- android:viewportWidth="16.0"
- android:viewportHeight="14.0">
+ android:width="21dp"
+ android:height="12dp"
+ android:viewportWidth="20.5"
+ android:viewportHeight="12.0">
<path
- android:pathData="M7.5,5L8.5,5A0.5,0.5 0,0 1,9 5.5L9,13.5A0.5,0.5 0,0 1,8.5 14L7.5,14A0.5,0.5 0,0 1,7 13.5L7,5.5A0.5,0.5 0,0 1,7.5 5z"
- android:fillAlpha="0.24"
+ android:pathData="M1.249,7C1.939,7 2.499,7.56 2.499,8.25L2.499,10.75C2.499,11.44 1.939,12 1.249,12C0.559,12 -0.001,11.44 -0.001,10.75L-0.001,8.25C-0.001,7.56 0.559,7 1.249,7Z"
android:fillColor="#000"/>
<path
- android:pathData="M11,2L12,2A0.5,0.5 0,0 1,12.5 2.5L12.5,13.5A0.5,0.5 0,0 1,12 14L11,14A0.5,0.5 0,0 1,10.5 13.5L10.5,2.5A0.5,0.5 0,0 1,11 2z"
- android:fillAlpha="0.24"
+ android:pathData="M5.749,5C6.439,5 6.999,5.56 6.999,6.25L6.999,10.75C6.999,11.44 6.439,12 5.749,12C5.059,12 4.499,11.44 4.499,10.75L4.499,6.25C4.499,5.56 5.059,5 5.749,5Z"
+ android:fillAlpha="0.45"
android:fillColor="#000"/>
<path
- android:pathData="M0.5,11L1.5,11A0.5,0.5 0,0 1,2 11.5L2,13.5A0.5,0.5 0,0 1,1.5 14L0.5,14A0.5,0.5 0,0 1,0 13.5L0,11.5A0.5,0.5 0,0 1,0.5 11z"
+ android:pathData="M10.249,3C10.939,3 11.499,3.56 11.499,4.25L11.499,10.75C11.499,11.44 10.939,12 10.249,12C9.559,12 8.999,11.44 8.999,10.75L8.999,4.25C8.999,3.56 9.559,3 10.249,3Z"
+ android:fillAlpha="0.45"
android:fillColor="#000"/>
<path
- android:pathData="M14.5,0L15.5,0A0.5,0.5 0,0 1,16 0.5L16,13.5A0.5,0.5 0,0 1,15.5 14L14.5,14A0.5,0.5 0,0 1,14 13.5L14,0.5A0.5,0.5 0,0 1,14.5 0z"
- android:fillAlpha="0.24"
+ android:pathData="M14.749,1.5C15.439,1.5 15.999,2.06 15.999,2.75L15.999,10.75C15.999,11.44 15.439,12 14.749,12C14.059,12 13.499,11.44 13.499,10.75L13.499,2.75C13.499,2.06 14.059,1.5 14.749,1.5Z"
+ android:fillAlpha="0.45"
android:fillColor="#000"/>
<path
- android:pathData="M4,8L5,8A0.5,0.5 0,0 1,5.5 8.5L5.5,13.5A0.5,0.5 0,0 1,5 14L4,14A0.5,0.5 0,0 1,3.5 13.5L3.5,8.5A0.5,0.5 0,0 1,4 8z"
- android:fillAlpha="0.24"
+ android:pathData="M19.249,0C19.939,0 20.499,0.56 20.499,1.25L20.499,10.75C20.499,11.44 19.939,12 19.249,12C18.559,12 17.999,11.44 17.999,10.75L17.999,1.25C17.999,0.56 18.559,0 19.249,0Z"
+ android:fillAlpha="0.45"
android:fillColor="#000"/>
</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_mobile_1_5_bar_error.xml b/packages/SettingsLib/res/drawable/ic_mobile_1_5_bar_error.xml
index 343ec1b2e50f..27e233d244c7 100644
--- a/packages/SettingsLib/res/drawable/ic_mobile_1_5_bar_error.xml
+++ b/packages/SettingsLib/res/drawable/ic_mobile_1_5_bar_error.xml
@@ -1,31 +1,53 @@
+<!--
+ 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.
+-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="18dp"
- android:height="14dp"
- android:viewportWidth="18.0"
- android:viewportHeight="14.0">
+ android:width="21dp"
+ android:height="12dp"
+ android:viewportHeight="12.0"
+ android:viewportWidth="20.5">
+ <!-- clip-out the circle which will contain the exclamation point (below this group) -->
+ <group>
+ <clip-path android:pathData="
+ M0,0
+ H20.5
+ V12.0
+ H0
+ Z
+ M19.499,13.5C22.261,13.5 24.499,11.261 24.499,8.5C24.499,5.739 22.261,3.5 19.499,3.5C16.738,3.5 14.499,5.739 14.499,8.5C14.499,11.261 16.738,13.5 19.499,13.5Z" />
+ <path
+ android:fillColor="#000"
+ android:pathData="M1.249,7C1.939,7 2.499,7.56 2.499,8.25L2.499,10.75C2.499,11.44 1.939,12 1.249,12C0.559,12 -0.001,11.44 -0.001,10.75L-0.001,8.25C-0.001,7.56 0.559,7 1.249,7Z" />
+ <path
+ android:fillAlpha="0.45"
+ android:fillColor="#000"
+ android:pathData="M5.749,5C6.439,5 6.999,5.56 6.999,6.25L6.999,10.75C6.999,11.44 6.439,12 5.749,12C5.059,12 4.499,11.44 4.499,10.75L4.499,6.25C4.499,5.56 5.059,5 5.749,5Z" />
+ <path
+ android:fillAlpha="0.45"
+ android:fillColor="#000"
+ android:pathData="M10.249,3C10.939,3 11.499,3.56 11.499,4.25L11.499,10.75C11.499,11.44 10.939,12 10.249,12C9.559,12 8.999,11.44 8.999,10.75L8.999,4.25C8.999,3.56 9.559,3 10.249,3Z" />
+ <path
+ android:fillAlpha="0.45"
+ android:fillColor="#000"
+ android:pathData="M14.749,1.5C15.439,1.5 15.999,2.06 15.999,2.75L15.999,10.75C15.999,11.44 15.439,12 14.749,12C14.059,12 13.499,11.44 13.499,10.75L13.499,2.75C13.499,2.06 14.059,1.5 14.749,1.5Z" />
+ <path
+ android:fillAlpha="0.45"
+ android:fillColor="#000"
+ android:pathData="M19.249,0C19.939,0 20.499,0.56 20.499,1.25L20.499,10.75C20.499,11.44 19.939,12 19.249,12C18.559,12 17.999,11.44 17.999,10.75L17.999,1.25C17.999,0.56 18.559,0 19.249,0Z" />
+ </group>
<path
- android:pathData="M7,5.5C7,5.224 7.224,5 7.5,5H8.5C8.776,5 9,5.224 9,5.5V13.5C9,13.776 8.776,14 8.5,14H7.5C7.224,14 7,13.776 7,13.5V5.5Z"
- android:fillAlpha="0.3"
- android:fillColor="#000"/>
- <path
- android:pathData="M10.5,2.5C10.5,2.224 10.724,2 11,2H12C12.276,2 12.5,2.224 12.5,2.5V13.5C12.5,13.776 12.276,14 12,14H11C10.724,14 10.5,13.776 10.5,13.5V2.5Z"
- android:fillAlpha="0.3"
- android:fillColor="#000"/>
- <path
- android:pathData="M0,11.5C0,11.224 0.224,11 0.5,11H1.5C1.776,11 2,11.224 2,11.5V13.5C2,13.776 1.776,14 1.5,14H0.5C0.224,14 0,13.776 0,13.5V11.5Z"
- android:fillColor="#000"/>
- <path
- android:pathData="M3.5,8.5C3.5,8.224 3.724,8 4,8H5C5.276,8 5.5,8.224 5.5,8.5V13.5C5.5,13.776 5.276,14 5,14H4C3.724,14 3.5,13.776 3.5,13.5V8.5Z"
- android:fillAlpha="0.3"
- android:fillColor="#000"/>
- <path
- android:pathData="M14.5,0C14.224,0 14,0.224 14,0.5V3H16V0.5C16,0.224 15.776,0 15.5,0H14.5Z"
- android:fillAlpha="0.3"
- android:fillColor="#000"/>
- <path
- android:pathData="M16,13C16,12.448 16.448,12 17,12C17.552,12 18,12.448 18,13C18,13.552 17.552,14 17,14C16.448,14 16,13.552 16,13Z"
- android:fillColor="#000"/>
- <path
- android:pathData="M16,5C16,4.724 16.224,4.5 16.5,4.5H17.5C17.776,4.5 18,4.724 18,5V10C18,10.276 17.776,10.5 17.5,10.5H16.5C16.224,10.5 16,10.276 16,10V5Z"
- android:fillColor="#000"/>
+ android:fillColor="#000"
+ android:pathData="M19.499,5C19.089,5 18.749,5.34 18.749,5.75L18.749,8.75C18.749,9.16 19.089,9.5 19.499,9.5C19.909,9.5 20.249,9.16 20.249,8.75L20.249,5.75C20.249,5.34 19.909,5 19.499,5ZM19.499,12C19.909,12 20.249,11.66 20.249,11.25C20.249,10.84 19.909,10.5 19.499,10.5C19.089,10.5 18.749,10.84 18.749,11.25C18.749,11.66 19.089,12 19.499,12Z" />
</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_mobile_2_4_bar.xml b/packages/SettingsLib/res/drawable/ic_mobile_2_4_bar.xml
index b699203dd652..e7ebf6f6883a 100644
--- a/packages/SettingsLib/res/drawable/ic_mobile_2_4_bar.xml
+++ b/packages/SettingsLib/res/drawable/ic_mobile_2_4_bar.xml
@@ -1,5 +1,5 @@
<!--
- Copyright (C) 2023 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,22 +14,22 @@
limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="14dp"
- android:height="14dp"
- android:viewportWidth="14.0"
- android:viewportHeight="14.0">
+ android:width="17dp"
+ android:height="12dp"
+ android:viewportWidth="16.0"
+ android:viewportHeight="12.0">
<path
- android:pathData="M8.25,3L9.25,3A0.5,0.5 0,0 1,9.75 3.5L9.75,13.5A0.5,0.5 0,0 1,9.25 14L8.25,14A0.5,0.5 0,0 1,7.75 13.5L7.75,3.5A0.5,0.5 0,0 1,8.25 3z"
- android:fillAlpha="0.24"
+ android:pathData="M1.249,7C1.939,7 2.499,7.56 2.499,8.25L2.499,10.75C2.499,11.44 1.939,12 1.249,12C0.559,12 -0.001,11.44 -0.001,10.75L-0.001,8.25C-0.001,7.56 0.559,7 1.249,7Z"
android:fillColor="#000"/>
<path
- android:pathData="M11.75,0L12.75,0A0.5,0.5 0,0 1,13.25 0.5L13.25,13.5A0.5,0.5 0,0 1,12.75 14L11.75,14A0.5,0.5 0,0 1,11.25 13.5L11.25,0.5A0.5,0.5 0,0 1,11.75 0z"
- android:fillAlpha="0.24"
+ android:pathData="M5.749,4.5C6.439,4.5 6.999,5.06 6.999,5.75L6.999,10.75C6.999,11.44 6.439,12 5.749,12C5.059,12 4.499,11.44 4.499,10.75L4.499,5.75C4.499,5.06 5.059,4.5 5.749,4.5Z"
android:fillColor="#000"/>
<path
- android:pathData="M1.25,10L2.25,10A0.5,0.5 0,0 1,2.75 10.5L2.75,13.5A0.5,0.5 0,0 1,2.25 14L1.25,14A0.5,0.5 0,0 1,0.75 13.5L0.75,10.5A0.5,0.5 0,0 1,1.25 10z"
+ android:pathData="M10.249,2C10.939,2 11.499,2.56 11.499,3.25L11.499,10.75C11.499,11.44 10.939,12 10.249,12C9.559,12 8.999,11.44 8.999,10.75L8.999,3.25C8.999,2.56 9.559,2 10.249,2Z"
+ android:fillAlpha="0.45"
android:fillColor="#000"/>
<path
- android:pathData="M4.75,6.5L5.75,6.5A0.5,0.5 0,0 1,6.25 7L6.25,13.5A0.5,0.5 0,0 1,5.75 14L4.75,14A0.5,0.5 0,0 1,4.25 13.5L4.25,7A0.5,0.5 0,0 1,4.75 6.5z"
+ android:pathData="M14.749,0C15.439,0 15.999,0.56 15.999,1.25L15.999,10.75C15.999,11.44 15.439,12 14.749,12C14.059,12 13.499,11.44 13.499,10.75L13.499,1.25C13.499,0.56 14.059,0 14.749,0Z"
+ android:fillAlpha="0.45"
android:fillColor="#000"/>
</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_mobile_2_4_bar_error.xml b/packages/SettingsLib/res/drawable/ic_mobile_2_4_bar_error.xml
index ba8649b23b38..49ae9e4ef17f 100644
--- a/packages/SettingsLib/res/drawable/ic_mobile_2_4_bar_error.xml
+++ b/packages/SettingsLib/res/drawable/ic_mobile_2_4_bar_error.xml
@@ -1,26 +1,49 @@
+<!--
+ 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.
+-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="15dp"
- android:height="14dp"
- android:viewportWidth="15.0"
- android:viewportHeight="14.0">
+ android:width="17dp"
+ android:height="12dp"
+ android:viewportWidth="16.0"
+ android:viewportHeight="12.0">
+ <group>
+ <!-- clip-out the circle which will contain the exclamation point (below this group) -->
+ <clip-path
+ android:pathData="
+ M0,0
+ V13.5,0
+ H13.5,20
+ V0,20
+ H0,0
+ M14.999,13.5C17.761,13.5 19.999,11.261 19.999,8.5C19.999,5.739 17.761,3.5 14.999,3.5C12.238,3.5 9.999,5.739 9.999,8.5C9.999,11.261 12.238,13.5 14.999,13.5Z" />
+ <path
+ android:pathData="M1.249,7C1.939,7 2.499,7.56 2.499,8.25L2.499,10.75C2.499,11.44 1.939,12 1.249,12C0.559,12 -0.001,11.44 -0.001,10.75L-0.001,8.25C-0.001,7.56 0.559,7 1.249,7Z"
+ android:fillColor="#000"/>
+ <path
+ android:pathData="M5.749,4.5C6.439,4.5 6.999,5.06 6.999,5.75L6.999,10.75C6.999,11.44 6.439,12 5.749,12C5.059,12 4.499,11.44 4.499,10.75L4.499,5.75C4.499,5.06 5.059,4.5 5.749,4.5Z"
+ android:fillColor="#000"/>
+ <path
+ android:pathData="M10.249,2C10.939,2 11.499,2.56 11.499,3.25L11.499,10.75C11.499,11.44 10.939,12 10.249,12C9.559,12 8.999,11.44 8.999,10.75L8.999,3.25C8.999,2.56 9.559,2 10.249,2Z"
+ android:fillAlpha="0.45"
+ android:fillColor="#000"/>
+ <path
+ android:pathData="M14.749,0C15.439,0 15.999,0.56 15.999,1.25L15.999,10.75C15.999,11.44 15.439,12 14.749,12C14.059,12 13.499,11.44 13.499,10.75L13.499,1.25C13.499,0.56 14.059,0 14.749,0Z"
+ android:fillAlpha="0.45"
+ android:fillColor="#000"/>
+ </group>
<path
- android:pathData="M7,3.5C7,3.224 7.224,3 7.5,3H8.5C8.776,3 9,3.224 9,3.5V13.5C9,13.776 8.776,14 8.5,14H7.5C7.224,14 7,13.776 7,13.5V3.5Z"
- android:fillAlpha="0.3"
- android:fillColor="#000"/>
- <path
- android:pathData="M0,10.5C0,10.224 0.224,10 0.5,10H1.5C1.776,10 2,10.224 2,10.5V13.5C2,13.776 1.776,14 1.5,14H0.5C0.224,14 0,13.776 0,13.5V10.5Z"
- android:fillColor="#000"/>
- <path
- android:pathData="M3.5,7C3.5,6.724 3.724,6.5 4,6.5H5C5.276,6.5 5.5,6.724 5.5,7V13.5C5.5,13.776 5.276,14 5,14H4C3.724,14 3.5,13.776 3.5,13.5V7Z"
- android:fillColor="#000"/>
- <path
- android:pathData="M11,0C10.724,0 10.5,0.224 10.5,0.5V3H12.5V0.5C12.5,0.224 12.276,0 12,0H11Z"
- android:fillAlpha="0.3"
- android:fillColor="#000"/>
- <path
- android:pathData="M12.25,13C12.25,12.448 12.698,12 13.25,12C13.802,12 14.25,12.448 14.25,13C14.25,13.552 13.802,14 13.25,14C12.698,14 12.25,13.552 12.25,13Z"
- android:fillColor="#000"/>
- <path
- android:pathData="M12.25,5C12.25,4.724 12.474,4.5 12.75,4.5H13.75C14.026,4.5 14.25,4.724 14.25,5V10C14.25,10.276 14.026,10.5 13.75,10.5H12.75C12.474,10.5 12.25,10.276 12.25,10V5Z"
+ android:pathData="M14.999,5C14.589,5 14.249,5.34 14.249,5.75L14.249,8.75C14.249,9.16 14.589,9.5 14.999,9.5C15.409,9.5 15.749,9.16 15.749,8.75L15.749,5.75C15.749,5.34 15.409,5 14.999,5ZM14.999,12C15.409,12 15.749,11.66 15.749,11.25C15.749,10.84 15.409,10.5 14.999,10.5C14.589,10.5 14.249,10.84 14.249,11.25C14.249,11.66 14.589,12 14.999,12Z"
android:fillColor="#000"/>
</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_mobile_2_5_bar.xml b/packages/SettingsLib/res/drawable/ic_mobile_2_5_bar.xml
index 43fa734c0c8d..19387bc6c75c 100644
--- a/packages/SettingsLib/res/drawable/ic_mobile_2_5_bar.xml
+++ b/packages/SettingsLib/res/drawable/ic_mobile_2_5_bar.xml
@@ -1,5 +1,5 @@
<!--
- Copyright (C) 2023 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,26 +14,26 @@
limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="16dp"
- android:height="14dp"
- android:viewportWidth="16.0"
- android:viewportHeight="14.0">
+ android:width="21dp"
+ android:height="12dp"
+ android:viewportWidth="20.5"
+ android:viewportHeight="12.0">
<path
- android:pathData="M7.5,5L8.5,5A0.5,0.5 0,0 1,9 5.5L9,13.5A0.5,0.5 0,0 1,8.5 14L7.5,14A0.5,0.5 0,0 1,7 13.5L7,5.5A0.5,0.5 0,0 1,7.5 5z"
- android:fillAlpha="0.24"
+ android:pathData="M1.249,7C1.939,7 2.499,7.56 2.499,8.25L2.499,10.75C2.499,11.44 1.939,12 1.249,12C0.559,12 -0.001,11.44 -0.001,10.75L-0.001,8.25C-0.001,7.56 0.559,7 1.249,7Z"
android:fillColor="#000"/>
<path
- android:pathData="M11,2L12,2A0.5,0.5 0,0 1,12.5 2.5L12.5,13.5A0.5,0.5 0,0 1,12 14L11,14A0.5,0.5 0,0 1,10.5 13.5L10.5,2.5A0.5,0.5 0,0 1,11 2z"
- android:fillAlpha="0.24"
+ android:pathData="M5.749,5C6.439,5 6.999,5.56 6.999,6.25L6.999,10.75C6.999,11.44 6.439,12 5.749,12C5.059,12 4.499,11.44 4.499,10.75L4.499,6.25C4.499,5.56 5.059,5 5.749,5Z"
android:fillColor="#000"/>
<path
- android:pathData="M0.5,11L1.5,11A0.5,0.5 0,0 1,2 11.5L2,13.5A0.5,0.5 0,0 1,1.5 14L0.5,14A0.5,0.5 0,0 1,0 13.5L0,11.5A0.5,0.5 0,0 1,0.5 11z"
+ android:pathData="M10.249,3C10.939,3 11.499,3.56 11.499,4.25L11.499,10.75C11.499,11.44 10.939,12 10.249,12C9.559,12 8.999,11.44 8.999,10.75L8.999,4.25C8.999,3.56 9.559,3 10.249,3Z"
+ android:fillAlpha="0.45"
android:fillColor="#000"/>
<path
- android:pathData="M14.5,0L15.5,0A0.5,0.5 0,0 1,16 0.5L16,13.5A0.5,0.5 0,0 1,15.5 14L14.5,14A0.5,0.5 0,0 1,14 13.5L14,0.5A0.5,0.5 0,0 1,14.5 0z"
- android:fillAlpha="0.24"
+ android:pathData="M14.749,1.5C15.439,1.5 15.999,2.06 15.999,2.75L15.999,10.75C15.999,11.44 15.439,12 14.749,12C14.059,12 13.499,11.44 13.499,10.75L13.499,2.75C13.499,2.06 14.059,1.5 14.749,1.5Z"
+ android:fillAlpha="0.45"
android:fillColor="#000"/>
<path
- android:pathData="M4,8L5,8A0.5,0.5 0,0 1,5.5 8.5L5.5,13.5A0.5,0.5 0,0 1,5 14L4,14A0.5,0.5 0,0 1,3.5 13.5L3.5,8.5A0.5,0.5 0,0 1,4 8z"
+ android:pathData="M19.249,0C19.939,0 20.499,0.56 20.499,1.25L20.499,10.75C20.499,11.44 19.939,12 19.249,12C18.559,12 17.999,11.44 17.999,10.75L17.999,1.25C17.999,0.56 18.559,0 19.249,0Z"
+ android:fillAlpha="0.45"
android:fillColor="#000"/>
</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_mobile_2_5_bar_error.xml b/packages/SettingsLib/res/drawable/ic_mobile_2_5_bar_error.xml
index 6309e1772d4a..322ede67de5b 100644
--- a/packages/SettingsLib/res/drawable/ic_mobile_2_5_bar_error.xml
+++ b/packages/SettingsLib/res/drawable/ic_mobile_2_5_bar_error.xml
@@ -1,30 +1,52 @@
+<!--
+ 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.
+-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="18dp"
- android:height="14dp"
- android:viewportWidth="18.0"
- android:viewportHeight="14.0">
+ android:width="21dp"
+ android:height="12dp"
+ android:viewportHeight="12.0"
+ android:viewportWidth="20.5">
+ <!-- clip-out the circle which will contain the exclamation point (below this group) -->
+ <group>
+ <clip-path android:pathData="
+ M0,0
+ H20.5
+ V12.0
+ H0
+ Z
+ M19.499,13.5C22.261,13.5 24.499,11.261 24.499,8.5C24.499,5.739 22.261,3.5 19.499,3.5C16.738,3.5 14.499,5.739 14.499,8.5C14.499,11.261 16.738,13.5 19.499,13.5Z" />
+ <path
+ android:fillColor="#000"
+ android:pathData="M1.249,7C1.939,7 2.499,7.56 2.499,8.25L2.499,10.75C2.499,11.44 1.939,12 1.249,12C0.559,12 -0.001,11.44 -0.001,10.75L-0.001,8.25C-0.001,7.56 0.559,7 1.249,7Z" />
+ <path
+ android:fillColor="#000"
+ android:pathData="M5.749,5C6.439,5 6.999,5.56 6.999,6.25L6.999,10.75C6.999,11.44 6.439,12 5.749,12C5.059,12 4.499,11.44 4.499,10.75L4.499,6.25C4.499,5.56 5.059,5 5.749,5Z" />
+ <path
+ android:fillAlpha="0.45"
+ android:fillColor="#000"
+ android:pathData="M10.249,3C10.939,3 11.499,3.56 11.499,4.25L11.499,10.75C11.499,11.44 10.939,12 10.249,12C9.559,12 8.999,11.44 8.999,10.75L8.999,4.25C8.999,3.56 9.559,3 10.249,3Z" />
+ <path
+ android:fillAlpha="0.45"
+ android:fillColor="#000"
+ android:pathData="M14.749,1.5C15.439,1.5 15.999,2.06 15.999,2.75L15.999,10.75C15.999,11.44 15.439,12 14.749,12C14.059,12 13.499,11.44 13.499,10.75L13.499,2.75C13.499,2.06 14.059,1.5 14.749,1.5Z" />
+ <path
+ android:fillAlpha="0.45"
+ android:fillColor="#000"
+ android:pathData="M19.249,0C19.939,0 20.499,0.56 20.499,1.25L20.499,10.75C20.499,11.44 19.939,12 19.249,12C18.559,12 17.999,11.44 17.999,10.75L17.999,1.25C17.999,0.56 18.559,0 19.249,0Z" />
+ </group>
<path
- android:pathData="M7,5.5C7,5.224 7.224,5 7.5,5H8.5C8.776,5 9,5.224 9,5.5V13.5C9,13.776 8.776,14 8.5,14H7.5C7.224,14 7,13.776 7,13.5V5.5Z"
- android:fillAlpha="0.3"
- android:fillColor="#000"/>
- <path
- android:pathData="M10.5,2.5C10.5,2.224 10.724,2 11,2H12C12.276,2 12.5,2.224 12.5,2.5V13.5C12.5,13.776 12.276,14 12,14H11C10.724,14 10.5,13.776 10.5,13.5V2.5Z"
- android:fillAlpha="0.3"
- android:fillColor="#000"/>
- <path
- android:pathData="M0,11.5C0,11.224 0.224,11 0.5,11H1.5C1.776,11 2,11.224 2,11.5V13.5C2,13.776 1.776,14 1.5,14H0.5C0.224,14 0,13.776 0,13.5V11.5Z"
- android:fillColor="#000"/>
- <path
- android:pathData="M3.5,8.5C3.5,8.224 3.724,8 4,8H5C5.276,8 5.5,8.224 5.5,8.5V13.5C5.5,13.776 5.276,14 5,14H4C3.724,14 3.5,13.776 3.5,13.5V8.5Z"
- android:fillColor="#000"/>
- <path
- android:pathData="M14.5,0C14.224,0 14,0.224 14,0.5V3H16V0.5C16,0.224 15.776,0 15.5,0H14.5Z"
- android:fillAlpha="0.3"
- android:fillColor="#000"/>
- <path
- android:pathData="M16,13C16,12.448 16.448,12 17,12C17.552,12 18,12.448 18,13C18,13.552 17.552,14 17,14C16.448,14 16,13.552 16,13Z"
- android:fillColor="#000"/>
- <path
- android:pathData="M16,5C16,4.724 16.224,4.5 16.5,4.5H17.5C17.776,4.5 18,4.724 18,5V10C18,10.276 17.776,10.5 17.5,10.5H16.5C16.224,10.5 16,10.276 16,10V5Z"
- android:fillColor="#000"/>
+ android:fillColor="#000"
+ android:pathData="M19.499,5C19.089,5 18.749,5.34 18.749,5.75L18.749,8.75C18.749,9.16 19.089,9.5 19.499,9.5C19.909,9.5 20.249,9.16 20.249,8.75L20.249,5.75C20.249,5.34 19.909,5 19.499,5ZM19.499,12C19.909,12 20.249,11.66 20.249,11.25C20.249,10.84 19.909,10.5 19.499,10.5C19.089,10.5 18.749,10.84 18.749,11.25C18.749,11.66 19.089,12 19.499,12Z" />
</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_mobile_3_4_bar.xml b/packages/SettingsLib/res/drawable/ic_mobile_3_4_bar.xml
index 6a218b310b3a..b84b6583e8aa 100644
--- a/packages/SettingsLib/res/drawable/ic_mobile_3_4_bar.xml
+++ b/packages/SettingsLib/res/drawable/ic_mobile_3_4_bar.xml
@@ -1,5 +1,5 @@
<!--
- Copyright (C) 2023 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,21 +14,21 @@
limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="14dp"
- android:height="14dp"
- android:viewportWidth="14.0"
- android:viewportHeight="14.0">
+ android:width="17dp"
+ android:height="12dp"
+ android:viewportWidth="16.0"
+ android:viewportHeight="12.0">
<path
- android:pathData="M8.25,3L9.25,3A0.5,0.5 0,0 1,9.75 3.5L9.75,13.5A0.5,0.5 0,0 1,9.25 14L8.25,14A0.5,0.5 0,0 1,7.75 13.5L7.75,3.5A0.5,0.5 0,0 1,8.25 3z"
+ android:pathData="M1.249,7C1.939,7 2.499,7.56 2.499,8.25L2.499,10.75C2.499,11.44 1.939,12 1.249,12C0.559,12 -0.001,11.44 -0.001,10.75L-0.001,8.25C-0.001,7.56 0.559,7 1.249,7Z"
android:fillColor="#000"/>
<path
- android:pathData="M11.75,0L12.75,0A0.5,0.5 0,0 1,13.25 0.5L13.25,13.5A0.5,0.5 0,0 1,12.75 14L11.75,14A0.5,0.5 0,0 1,11.25 13.5L11.25,0.5A0.5,0.5 0,0 1,11.75 0z"
- android:fillAlpha="0.24"
+ android:pathData="M5.749,4.5C6.439,4.5 6.999,5.06 6.999,5.75L6.999,10.75C6.999,11.44 6.439,12 5.749,12C5.059,12 4.499,11.44 4.499,10.75L4.499,5.75C4.499,5.06 5.059,4.5 5.749,4.5Z"
android:fillColor="#000"/>
<path
- android:pathData="M1.25,10L2.25,10A0.5,0.5 0,0 1,2.75 10.5L2.75,13.5A0.5,0.5 0,0 1,2.25 14L1.25,14A0.5,0.5 0,0 1,0.75 13.5L0.75,10.5A0.5,0.5 0,0 1,1.25 10z"
+ android:pathData="M10.249,2C10.939,2 11.499,2.56 11.499,3.25L11.499,10.75C11.499,11.44 10.939,12 10.249,12C9.559,12 8.999,11.44 8.999,10.75L8.999,3.25C8.999,2.56 9.559,2 10.249,2Z"
android:fillColor="#000"/>
<path
- android:pathData="M4.75,6.5L5.75,6.5A0.5,0.5 0,0 1,6.25 7L6.25,13.5A0.5,0.5 0,0 1,5.75 14L4.75,14A0.5,0.5 0,0 1,4.25 13.5L4.25,7A0.5,0.5 0,0 1,4.75 6.5z"
+ android:pathData="M14.749,0C15.439,0 15.999,0.56 15.999,1.25L15.999,10.75C15.999,11.44 15.439,12 14.749,12C14.059,12 13.499,11.44 13.499,10.75L13.499,1.25C13.499,0.56 14.059,0 14.749,0Z"
+ android:fillAlpha="0.45"
android:fillColor="#000"/>
</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_mobile_3_4_bar_error.xml b/packages/SettingsLib/res/drawable/ic_mobile_3_4_bar_error.xml
index 27433c79e8bb..7c4c1c6b1126 100644
--- a/packages/SettingsLib/res/drawable/ic_mobile_3_4_bar_error.xml
+++ b/packages/SettingsLib/res/drawable/ic_mobile_3_4_bar_error.xml
@@ -1,25 +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.
+-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="15dp"
- android:height="14dp"
- android:viewportWidth="15.0"
- android:viewportHeight="14.0">
+ android:width="17dp"
+ android:height="12dp"
+ android:viewportWidth="16.0"
+ android:viewportHeight="12.0">
+ <group>
+ <!-- clip-out the circle which will contain the exclamation point (below this group) -->
+ <clip-path
+ android:pathData="
+ M0,0
+ V13.5,0
+ H13.5,20
+ V0,20
+ H0,0
+ M14.999,13.5C17.761,13.5 19.999,11.261 19.999,8.5C19.999,5.739 17.761,3.5 14.999,3.5C12.238,3.5 9.999,5.739 9.999,8.5C9.999,11.261 12.238,13.5 14.999,13.5Z" />
+ <path
+ android:pathData="M1.249,7C1.939,7 2.499,7.56 2.499,8.25L2.499,10.75C2.499,11.44 1.939,12 1.249,12C0.559,12 -0.001,11.44 -0.001,10.75L-0.001,8.25C-0.001,7.56 0.559,7 1.249,7Z"
+ android:fillColor="#000"/>
+ <path
+ android:pathData="M5.749,4.5C6.439,4.5 6.999,5.06 6.999,5.75L6.999,10.75C6.999,11.44 6.439,12 5.749,12C5.059,12 4.499,11.44 4.499,10.75L4.499,5.75C4.499,5.06 5.059,4.5 5.749,4.5Z"
+ android:fillColor="#000"/>
+ <path
+ android:pathData="M10.249,2C10.939,2 11.499,2.56 11.499,3.25L11.499,10.75C11.499,11.44 10.939,12 10.249,12C9.559,12 8.999,11.44 8.999,10.75L8.999,3.25C8.999,2.56 9.559,2 10.249,2Z"
+ android:fillColor="#000"/>
+ <path
+ android:pathData="M14.749,0C15.439,0 15.999,0.56 15.999,1.25L15.999,10.75C15.999,11.44 15.439,12 14.749,12C14.059,12 13.499,11.44 13.499,10.75L13.499,1.25C13.499,0.56 14.059,0 14.749,0Z"
+ android:fillAlpha="0.45"
+ android:fillColor="#000"/>
+ </group>
<path
- android:pathData="M7,3.5C7,3.224 7.224,3 7.5,3H8.5C8.776,3 9,3.224 9,3.5V13.5C9,13.776 8.776,14 8.5,14H7.5C7.224,14 7,13.776 7,13.5V3.5Z"
- android:fillColor="#000"/>
- <path
- android:pathData="M0,10.5C0,10.224 0.224,10 0.5,10H1.5C1.776,10 2,10.224 2,10.5V13.5C2,13.776 1.776,14 1.5,14H0.5C0.224,14 0,13.776 0,13.5V10.5Z"
- android:fillColor="#000"/>
- <path
- android:pathData="M3.5,7C3.5,6.724 3.724,6.5 4,6.5H5C5.276,6.5 5.5,6.724 5.5,7V13.5C5.5,13.776 5.276,14 5,14H4C3.724,14 3.5,13.776 3.5,13.5V7Z"
- android:fillColor="#000"/>
- <path
- android:pathData="M11,0C10.724,0 10.5,0.224 10.5,0.5V3H12.5V0.5C12.5,0.224 12.276,0 12,0H11Z"
- android:fillAlpha="0.3"
- android:fillColor="#000"/>
- <path
- android:pathData="M12.25,13C12.25,12.448 12.698,12 13.25,12C13.802,12 14.25,12.448 14.25,13C14.25,13.552 13.802,14 13.25,14C12.698,14 12.25,13.552 12.25,13Z"
- android:fillColor="#000"/>
- <path
- android:pathData="M12.25,5C12.25,4.724 12.474,4.5 12.75,4.5H13.75C14.026,4.5 14.25,4.724 14.25,5V10C14.25,10.276 14.026,10.5 13.75,10.5H12.75C12.474,10.5 12.25,10.276 12.25,10V5Z"
+ android:pathData="M14.999,5C14.589,5 14.249,5.34 14.249,5.75L14.249,8.75C14.249,9.16 14.589,9.5 14.999,9.5C15.409,9.5 15.749,9.16 15.749,8.75L15.749,5.75C15.749,5.34 15.409,5 14.999,5ZM14.999,12C15.409,12 15.749,11.66 15.749,11.25C15.749,10.84 15.409,10.5 14.999,10.5C14.589,10.5 14.249,10.84 14.249,11.25C14.249,11.66 14.589,12 14.999,12Z"
android:fillColor="#000"/>
</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_mobile_3_5_bar.xml b/packages/SettingsLib/res/drawable/ic_mobile_3_5_bar.xml
index 158ae016ffb5..973032f3c237 100644
--- a/packages/SettingsLib/res/drawable/ic_mobile_3_5_bar.xml
+++ b/packages/SettingsLib/res/drawable/ic_mobile_3_5_bar.xml
@@ -1,5 +1,5 @@
<!--
- Copyright (C) 2023 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,25 +14,25 @@
limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="16dp"
- android:height="14dp"
- android:viewportWidth="16.0"
- android:viewportHeight="14.0">
+ android:width="21dp"
+ android:height="12dp"
+ android:viewportWidth="20.5"
+ android:viewportHeight="12.0">
<path
- android:pathData="M7.5,5L8.5,5A0.5,0.5 0,0 1,9 5.5L9,13.5A0.5,0.5 0,0 1,8.5 14L7.5,14A0.5,0.5 0,0 1,7 13.5L7,5.5A0.5,0.5 0,0 1,7.5 5z"
+ android:pathData="M1.249,7C1.939,7 2.499,7.56 2.499,8.25L2.499,10.75C2.499,11.44 1.939,12 1.249,12C0.559,12 -0.001,11.44 -0.001,10.75L-0.001,8.25C-0.001,7.56 0.559,7 1.249,7Z"
android:fillColor="#000"/>
<path
- android:pathData="M11,2L12,2A0.5,0.5 0,0 1,12.5 2.5L12.5,13.5A0.5,0.5 0,0 1,12 14L11,14A0.5,0.5 0,0 1,10.5 13.5L10.5,2.5A0.5,0.5 0,0 1,11 2z"
- android:fillAlpha="0.24"
+ android:pathData="M5.749,5C6.439,5 6.999,5.56 6.999,6.25L6.999,10.75C6.999,11.44 6.439,12 5.749,12C5.059,12 4.499,11.44 4.499,10.75L4.499,6.25C4.499,5.56 5.059,5 5.749,5Z"
android:fillColor="#000"/>
<path
- android:pathData="M0.5,11L1.5,11A0.5,0.5 0,0 1,2 11.5L2,13.5A0.5,0.5 0,0 1,1.5 14L0.5,14A0.5,0.5 0,0 1,0 13.5L0,11.5A0.5,0.5 0,0 1,0.5 11z"
+ android:pathData="M10.249,3C10.939,3 11.499,3.56 11.499,4.25L11.499,10.75C11.499,11.44 10.939,12 10.249,12C9.559,12 8.999,11.44 8.999,10.75L8.999,4.25C8.999,3.56 9.559,3 10.249,3Z"
android:fillColor="#000"/>
<path
- android:pathData="M14.5,0L15.5,0A0.5,0.5 0,0 1,16 0.5L16,13.5A0.5,0.5 0,0 1,15.5 14L14.5,14A0.5,0.5 0,0 1,14 13.5L14,0.5A0.5,0.5 0,0 1,14.5 0z"
- android:fillAlpha="0.24"
+ android:pathData="M14.749,1.5C15.439,1.5 15.999,2.06 15.999,2.75L15.999,10.75C15.999,11.44 15.439,12 14.749,12C14.059,12 13.499,11.44 13.499,10.75L13.499,2.75C13.499,2.06 14.059,1.5 14.749,1.5Z"
+ android:fillAlpha="0.45"
android:fillColor="#000"/>
<path
- android:pathData="M4,8L5,8A0.5,0.5 0,0 1,5.5 8.5L5.5,13.5A0.5,0.5 0,0 1,5 14L4,14A0.5,0.5 0,0 1,3.5 13.5L3.5,8.5A0.5,0.5 0,0 1,4 8z"
+ android:pathData="M19.249,0C19.939,0 20.499,0.56 20.499,1.25L20.499,10.75C20.499,11.44 19.939,12 19.249,12C18.559,12 17.999,11.44 17.999,10.75L17.999,1.25C17.999,0.56 18.559,0 19.249,0Z"
+ android:fillAlpha="0.45"
android:fillColor="#000"/>
</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_mobile_3_5_bar_error.xml b/packages/SettingsLib/res/drawable/ic_mobile_3_5_bar_error.xml
index e0517cfdfeee..25c9520ad011 100644
--- a/packages/SettingsLib/res/drawable/ic_mobile_3_5_bar_error.xml
+++ b/packages/SettingsLib/res/drawable/ic_mobile_3_5_bar_error.xml
@@ -1,29 +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.
+-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="18dp"
- android:height="14dp"
- android:viewportWidth="18.0"
- android:viewportHeight="14.0">
+ android:width="21dp"
+ android:height="12dp"
+ android:viewportHeight="12.0"
+ android:viewportWidth="20.5">
+ <!-- clip-out the circle which will contain the exclamation point (below this group) -->
+ <group>
+ <clip-path android:pathData="
+ M0,0
+ H20.5
+ V12.0
+ H0
+ Z
+ M19.499,13.5C22.261,13.5 24.499,11.261 24.499,8.5C24.499,5.739 22.261,3.5 19.499,3.5C16.738,3.5 14.499,5.739 14.499,8.5C14.499,11.261 16.738,13.5 19.499,13.5Z" />
+ <path
+ android:fillColor="#000"
+ android:pathData="M1.249,7C1.939,7 2.499,7.56 2.499,8.25L2.499,10.75C2.499,11.44 1.939,12 1.249,12C0.559,12 -0.001,11.44 -0.001,10.75L-0.001,8.25C-0.001,7.56 0.559,7 1.249,7Z" />
+ <path
+ android:fillColor="#000"
+ android:pathData="M5.749,5C6.439,5 6.999,5.56 6.999,6.25L6.999,10.75C6.999,11.44 6.439,12 5.749,12C5.059,12 4.499,11.44 4.499,10.75L4.499,6.25C4.499,5.56 5.059,5 5.749,5Z" />
+ <path
+ android:fillColor="#000"
+ android:pathData="M10.249,3C10.939,3 11.499,3.56 11.499,4.25L11.499,10.75C11.499,11.44 10.939,12 10.249,12C9.559,12 8.999,11.44 8.999,10.75L8.999,4.25C8.999,3.56 9.559,3 10.249,3Z" />
+ <path
+ android:fillAlpha="0.45"
+ android:fillColor="#000"
+ android:pathData="M14.749,1.5C15.439,1.5 15.999,2.06 15.999,2.75L15.999,10.75C15.999,11.44 15.439,12 14.749,12C14.059,12 13.499,11.44 13.499,10.75L13.499,2.75C13.499,2.06 14.059,1.5 14.749,1.5Z" />
+ <path
+ android:fillAlpha="0.45"
+ android:fillColor="#000"
+ android:pathData="M19.249,0C19.939,0 20.499,0.56 20.499,1.25L20.499,10.75C20.499,11.44 19.939,12 19.249,12C18.559,12 17.999,11.44 17.999,10.75L17.999,1.25C17.999,0.56 18.559,0 19.249,0Z" />
+ </group>
<path
- android:pathData="M7,5.5C7,5.224 7.224,5 7.5,5H8.5C8.776,5 9,5.224 9,5.5V13.5C9,13.776 8.776,14 8.5,14H7.5C7.224,14 7,13.776 7,13.5V5.5Z"
- android:fillColor="#000"/>
- <path
- android:pathData="M10.5,2.5C10.5,2.224 10.724,2 11,2H12C12.276,2 12.5,2.224 12.5,2.5V13.5C12.5,13.776 12.276,14 12,14H11C10.724,14 10.5,13.776 10.5,13.5V2.5Z"
- android:fillAlpha="0.3"
- android:fillColor="#000"/>
- <path
- android:pathData="M0,11.5C0,11.224 0.224,11 0.5,11H1.5C1.776,11 2,11.224 2,11.5V13.5C2,13.776 1.776,14 1.5,14H0.5C0.224,14 0,13.776 0,13.5V11.5Z"
- android:fillColor="#000"/>
- <path
- android:pathData="M3.5,8.5C3.5,8.224 3.724,8 4,8H5C5.276,8 5.5,8.224 5.5,8.5V13.5C5.5,13.776 5.276,14 5,14H4C3.724,14 3.5,13.776 3.5,13.5V8.5Z"
- android:fillColor="#000"/>
- <path
- android:pathData="M14.5,0C14.224,0 14,0.224 14,0.5V3H16V0.5C16,0.224 15.776,0 15.5,0H14.5Z"
- android:fillAlpha="0.3"
- android:fillColor="#000"/>
- <path
- android:pathData="M16,13C16,12.448 16.448,12 17,12C17.552,12 18,12.448 18,13C18,13.552 17.552,14 17,14C16.448,14 16,13.552 16,13Z"
- android:fillColor="#000"/>
- <path
- android:pathData="M16,5C16,4.724 16.224,4.5 16.5,4.5H17.5C17.776,4.5 18,4.724 18,5V10C18,10.276 17.776,10.5 17.5,10.5H16.5C16.224,10.5 16,10.276 16,10V5Z"
- android:fillColor="#000"/>
+ android:fillColor="#000"
+ android:pathData="M19.499,5C19.089,5 18.749,5.34 18.749,5.75L18.749,8.75C18.749,9.16 19.089,9.5 19.499,9.5C19.909,9.5 20.249,9.16 20.249,8.75L20.249,5.75C20.249,5.34 19.909,5 19.499,5ZM19.499,12C19.909,12 20.249,11.66 20.249,11.25C20.249,10.84 19.909,10.5 19.499,10.5C19.089,10.5 18.749,10.84 18.749,11.25C18.749,11.66 19.089,12 19.499,12Z" />
</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_mobile_4_4_bar.xml b/packages/SettingsLib/res/drawable/ic_mobile_4_4_bar.xml
index 1ebd3965f36f..fc807fa90b44 100644
--- a/packages/SettingsLib/res/drawable/ic_mobile_4_4_bar.xml
+++ b/packages/SettingsLib/res/drawable/ic_mobile_4_4_bar.xml
@@ -1,5 +1,5 @@
<!--
- Copyright (C) 2023 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,20 +14,20 @@
limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="14dp"
- android:height="14dp"
- android:viewportWidth="14.0"
- android:viewportHeight="14.0">
+ android:width="17dp"
+ android:height="12dp"
+ android:viewportWidth="16.0"
+ android:viewportHeight="12.0">
<path
- android:pathData="M8.25,3L9.25,3A0.5,0.5 0,0 1,9.75 3.5L9.75,13.5A0.5,0.5 0,0 1,9.25 14L8.25,14A0.5,0.5 0,0 1,7.75 13.5L7.75,3.5A0.5,0.5 0,0 1,8.25 3z"
+ android:pathData="M1.249,7C1.939,7 2.499,7.56 2.499,8.25L2.499,10.75C2.499,11.44 1.939,12 1.249,12C0.559,12 -0.001,11.44 -0.001,10.75L-0.001,8.25C-0.001,7.56 0.559,7 1.249,7Z"
android:fillColor="#000"/>
<path
- android:pathData="M11.75,0L12.75,0A0.5,0.5 0,0 1,13.25 0.5L13.25,13.5A0.5,0.5 0,0 1,12.75 14L11.75,14A0.5,0.5 0,0 1,11.25 13.5L11.25,0.5A0.5,0.5 0,0 1,11.75 0z"
+ android:pathData="M5.749,4.5C6.439,4.5 6.999,5.06 6.999,5.75L6.999,10.75C6.999,11.44 6.439,12 5.749,12C5.059,12 4.499,11.44 4.499,10.75L4.499,5.75C4.499,5.06 5.059,4.5 5.749,4.5Z"
android:fillColor="#000"/>
<path
- android:pathData="M1.25,10L2.25,10A0.5,0.5 0,0 1,2.75 10.5L2.75,13.5A0.5,0.5 0,0 1,2.25 14L1.25,14A0.5,0.5 0,0 1,0.75 13.5L0.75,10.5A0.5,0.5 0,0 1,1.25 10z"
+ android:pathData="M10.249,2C10.939,2 11.499,2.56 11.499,3.25L11.499,10.75C11.499,11.44 10.939,12 10.249,12C9.559,12 8.999,11.44 8.999,10.75L8.999,3.25C8.999,2.56 9.559,2 10.249,2Z"
android:fillColor="#000"/>
<path
- android:pathData="M4.75,6.5L5.75,6.5A0.5,0.5 0,0 1,6.25 7L6.25,13.5A0.5,0.5 0,0 1,5.75 14L4.75,14A0.5,0.5 0,0 1,4.25 13.5L4.25,7A0.5,0.5 0,0 1,4.75 6.5z"
+ android:pathData="M14.749,0C15.439,0 15.999,0.56 15.999,1.25L15.999,10.75C15.999,11.44 15.439,12 14.749,12C14.059,12 13.499,11.44 13.499,10.75L13.499,1.25C13.499,0.56 14.059,0 14.749,0Z"
android:fillColor="#000"/>
</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_mobile_4_4_bar_error.xml b/packages/SettingsLib/res/drawable/ic_mobile_4_4_bar_error.xml
index 4473c29d0866..d23680d17b9c 100644
--- a/packages/SettingsLib/res/drawable/ic_mobile_4_4_bar_error.xml
+++ b/packages/SettingsLib/res/drawable/ic_mobile_4_4_bar_error.xml
@@ -1,24 +1,47 @@
+<!--
+ 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.
+-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="15dp"
- android:height="14dp"
- android:viewportWidth="15.0"
- android:viewportHeight="14.0">
+ android:width="17dp"
+ android:height="12dp"
+ android:viewportWidth="16.0"
+ android:viewportHeight="12.0">
+ <group>
+ <!-- clip-out the circle which will contain the exclamation point (below this group) -->
+ <clip-path
+ android:pathData="
+ M0,0
+ V13.5,0
+ H13.5,20
+ V0,20
+ H0,0
+ M14.999,13.5C17.761,13.5 19.999,11.261 19.999,8.5C19.999,5.739 17.761,3.5 14.999,3.5C12.238,3.5 9.999,5.739 9.999,8.5C9.999,11.261 12.238,13.5 14.999,13.5Z" />
+ <path
+ android:pathData="M1.249,7C1.939,7 2.499,7.56 2.499,8.25L2.499,10.75C2.499,11.44 1.939,12 1.249,12C0.559,12 -0.001,11.44 -0.001,10.75L-0.001,8.25C-0.001,7.56 0.559,7 1.249,7Z"
+ android:fillColor="#000"/>
+ <path
+ android:pathData="M5.749,4.5C6.439,4.5 6.999,5.06 6.999,5.75L6.999,10.75C6.999,11.44 6.439,12 5.749,12C5.059,12 4.499,11.44 4.499,10.75L4.499,5.75C4.499,5.06 5.059,4.5 5.749,4.5Z"
+ android:fillColor="#000"/>
+ <path
+ android:pathData="M10.249,2C10.939,2 11.499,2.56 11.499,3.25L11.499,10.75C11.499,11.44 10.939,12 10.249,12C9.559,12 8.999,11.44 8.999,10.75L8.999,3.25C8.999,2.56 9.559,2 10.249,2Z"
+ android:fillColor="#000"/>
+ <path
+ android:pathData="M14.749,0C15.439,0 15.999,0.56 15.999,1.25L15.999,10.75C15.999,11.44 15.439,12 14.749,12C14.059,12 13.499,11.44 13.499,10.75L13.499,1.25C13.499,0.56 14.059,0 14.749,0Z"
+ android:fillColor="#000"/>
+ </group>
<path
- android:pathData="M7,3.5C7,3.224 7.224,3 7.5,3H8.5C8.776,3 9,3.224 9,3.5V13.5C9,13.776 8.776,14 8.5,14H7.5C7.224,14 7,13.776 7,13.5V3.5Z"
- android:fillColor="#000"/>
- <path
- android:pathData="M0,10.5C0,10.224 0.224,10 0.5,10H1.5C1.776,10 2,10.224 2,10.5V13.5C2,13.776 1.776,14 1.5,14H0.5C0.224,14 0,13.776 0,13.5V10.5Z"
- android:fillColor="#000"/>
- <path
- android:pathData="M3.5,7C3.5,6.724 3.724,6.5 4,6.5H5C5.276,6.5 5.5,6.724 5.5,7V13.5C5.5,13.776 5.276,14 5,14H4C3.724,14 3.5,13.776 3.5,13.5V7Z"
- android:fillColor="#000"/>
- <path
- android:pathData="M11,0C10.724,0 10.5,0.224 10.5,0.5V3H12.5V0.5C12.5,0.224 12.276,0 12,0H11Z"
- android:fillColor="#000"/>
- <path
- android:pathData="M12.25,13C12.25,12.448 12.698,12 13.25,12C13.802,12 14.25,12.448 14.25,13C14.25,13.552 13.802,14 13.25,14C12.698,14 12.25,13.552 12.25,13Z"
- android:fillColor="#000"/>
- <path
- android:pathData="M12.25,5C12.25,4.724 12.474,4.5 12.75,4.5H13.75C14.026,4.5 14.25,4.724 14.25,5V10C14.25,10.276 14.026,10.5 13.75,10.5H12.75C12.474,10.5 12.25,10.276 12.25,10V5Z"
+ android:pathData="M14.999,5C14.589,5 14.249,5.34 14.249,5.75L14.249,8.75C14.249,9.16 14.589,9.5 14.999,9.5C15.409,9.5 15.749,9.16 15.749,8.75L15.749,5.75C15.749,5.34 15.409,5 14.999,5ZM14.999,12C15.409,12 15.749,11.66 15.749,11.25C15.749,10.84 15.409,10.5 14.999,10.5C14.589,10.5 14.249,10.84 14.249,11.25C14.249,11.66 14.589,12 14.999,12Z"
android:fillColor="#000"/>
</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_mobile_4_5_bar.xml b/packages/SettingsLib/res/drawable/ic_mobile_4_5_bar.xml
index 1ed6ac86b21a..b1336d70fc39 100644
--- a/packages/SettingsLib/res/drawable/ic_mobile_4_5_bar.xml
+++ b/packages/SettingsLib/res/drawable/ic_mobile_4_5_bar.xml
@@ -1,5 +1,5 @@
<!--
- Copyright (C) 2023 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,24 +14,24 @@
limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="16dp"
- android:height="14dp"
- android:viewportWidth="16.0"
- android:viewportHeight="14.0">
+ android:width="21dp"
+ android:height="12dp"
+ android:viewportWidth="20.5"
+ android:viewportHeight="12.0">
<path
- android:pathData="M7.5,5L8.5,5A0.5,0.5 0,0 1,9 5.5L9,13.5A0.5,0.5 0,0 1,8.5 14L7.5,14A0.5,0.5 0,0 1,7 13.5L7,5.5A0.5,0.5 0,0 1,7.5 5z"
+ android:pathData="M1.249,7C1.939,7 2.499,7.56 2.499,8.25L2.499,10.75C2.499,11.44 1.939,12 1.249,12C0.559,12 -0.001,11.44 -0.001,10.75L-0.001,8.25C-0.001,7.56 0.559,7 1.249,7Z"
android:fillColor="#000"/>
<path
- android:pathData="M11,2L12,2A0.5,0.5 0,0 1,12.5 2.5L12.5,13.5A0.5,0.5 0,0 1,12 14L11,14A0.5,0.5 0,0 1,10.5 13.5L10.5,2.5A0.5,0.5 0,0 1,11 2z"
+ android:pathData="M5.749,5C6.439,5 6.999,5.56 6.999,6.25L6.999,10.75C6.999,11.44 6.439,12 5.749,12C5.059,12 4.499,11.44 4.499,10.75L4.499,6.25C4.499,5.56 5.059,5 5.749,5Z"
android:fillColor="#000"/>
<path
- android:pathData="M0.5,11L1.5,11A0.5,0.5 0,0 1,2 11.5L2,13.5A0.5,0.5 0,0 1,1.5 14L0.5,14A0.5,0.5 0,0 1,0 13.5L0,11.5A0.5,0.5 0,0 1,0.5 11z"
+ android:pathData="M10.249,3C10.939,3 11.499,3.56 11.499,4.25L11.499,10.75C11.499,11.44 10.939,12 10.249,12C9.559,12 8.999,11.44 8.999,10.75L8.999,4.25C8.999,3.56 9.559,3 10.249,3Z"
android:fillColor="#000"/>
<path
- android:pathData="M14.5,0L15.5,0A0.5,0.5 0,0 1,16 0.5L16,13.5A0.5,0.5 0,0 1,15.5 14L14.5,14A0.5,0.5 0,0 1,14 13.5L14,0.5A0.5,0.5 0,0 1,14.5 0z"
- android:fillAlpha="0.24"
+ android:pathData="M14.749,1.5C15.439,1.5 15.999,2.06 15.999,2.75L15.999,10.75C15.999,11.44 15.439,12 14.749,12C14.059,12 13.499,11.44 13.499,10.75L13.499,2.75C13.499,2.06 14.059,1.5 14.749,1.5Z"
android:fillColor="#000"/>
<path
- android:pathData="M4,8L5,8A0.5,0.5 0,0 1,5.5 8.5L5.5,13.5A0.5,0.5 0,0 1,5 14L4,14A0.5,0.5 0,0 1,3.5 13.5L3.5,8.5A0.5,0.5 0,0 1,4 8z"
+ android:pathData="M19.249,0C19.939,0 20.499,0.56 20.499,1.25L20.499,10.75C20.499,11.44 19.939,12 19.249,12C18.559,12 17.999,11.44 17.999,10.75L17.999,1.25C17.999,0.56 18.559,0 19.249,0Z"
+ android:fillAlpha="0.45"
android:fillColor="#000"/>
</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_mobile_4_5_bar_error.xml b/packages/SettingsLib/res/drawable/ic_mobile_4_5_bar_error.xml
index 703e3acd5f75..bf62535b9574 100644
--- a/packages/SettingsLib/res/drawable/ic_mobile_4_5_bar_error.xml
+++ b/packages/SettingsLib/res/drawable/ic_mobile_4_5_bar_error.xml
@@ -1,28 +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.
+-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="18dp"
- android:height="14dp"
- android:viewportWidth="18.0"
- android:viewportHeight="14.0">
+ android:width="21dp"
+ android:height="12dp"
+ android:viewportHeight="12.0"
+ android:viewportWidth="20.5">
+ <!-- clip-out the circle which will contain the exclamation point (below this group) -->
+ <group>
+ <clip-path android:pathData="
+ M0,0
+ H20.5
+ V12.0
+ H0
+ Z
+ M19.499,13.5C22.261,13.5 24.499,11.261 24.499,8.5C24.499,5.739 22.261,3.5 19.499,3.5C16.738,3.5 14.499,5.739 14.499,8.5C14.499,11.261 16.738,13.5 19.499,13.5Z" />
+ <path
+ android:fillColor="#000"
+ android:pathData="M1.249,7C1.939,7 2.499,7.56 2.499,8.25L2.499,10.75C2.499,11.44 1.939,12 1.249,12C0.559,12 -0.001,11.44 -0.001,10.75L-0.001,8.25C-0.001,7.56 0.559,7 1.249,7Z" />
+ <path
+ android:fillColor="#000"
+ android:pathData="M5.749,5C6.439,5 6.999,5.56 6.999,6.25L6.999,10.75C6.999,11.44 6.439,12 5.749,12C5.059,12 4.499,11.44 4.499,10.75L4.499,6.25C4.499,5.56 5.059,5 5.749,5Z" />
+ <path
+ android:fillColor="#000"
+ android:pathData="M10.249,3C10.939,3 11.499,3.56 11.499,4.25L11.499,10.75C11.499,11.44 10.939,12 10.249,12C9.559,12 8.999,11.44 8.999,10.75L8.999,4.25C8.999,3.56 9.559,3 10.249,3Z" />
+ <path
+ android:fillColor="#000"
+ android:pathData="M14.749,1.5C15.439,1.5 15.999,2.06 15.999,2.75L15.999,10.75C15.999,11.44 15.439,12 14.749,12C14.059,12 13.499,11.44 13.499,10.75L13.499,2.75C13.499,2.06 14.059,1.5 14.749,1.5Z" />
+ <path
+ android:fillAlpha="0.45"
+ android:fillColor="#000"
+ android:pathData="M19.249,0C19.939,0 20.499,0.56 20.499,1.25L20.499,10.75C20.499,11.44 19.939,12 19.249,12C18.559,12 17.999,11.44 17.999,10.75L17.999,1.25C17.999,0.56 18.559,0 19.249,0Z" />
+ </group>
<path
- android:pathData="M7,5.5C7,5.224 7.224,5 7.5,5H8.5C8.776,5 9,5.224 9,5.5V13.5C9,13.776 8.776,14 8.5,14H7.5C7.224,14 7,13.776 7,13.5V5.5Z"
- android:fillColor="#000"/>
- <path
- android:pathData="M10.5,2.5C10.5,2.224 10.724,2 11,2H12C12.276,2 12.5,2.224 12.5,2.5V13.5C12.5,13.776 12.276,14 12,14H11C10.724,14 10.5,13.776 10.5,13.5V2.5Z"
- android:fillColor="#000"/>
- <path
- android:pathData="M0,11.5C0,11.224 0.224,11 0.5,11H1.5C1.776,11 2,11.224 2,11.5V13.5C2,13.776 1.776,14 1.5,14H0.5C0.224,14 0,13.776 0,13.5V11.5Z"
- android:fillColor="#000"/>
- <path
- android:pathData="M3.5,8.5C3.5,8.224 3.724,8 4,8H5C5.276,8 5.5,8.224 5.5,8.5V13.5C5.5,13.776 5.276,14 5,14H4C3.724,14 3.5,13.776 3.5,13.5V8.5Z"
- android:fillColor="#000"/>
- <path
- android:pathData="M14.5,0C14.224,0 14,0.224 14,0.5V3H16V0.5C16,0.224 15.776,0 15.5,0H14.5Z"
- android:fillAlpha="0.3"
- android:fillColor="#000"/>
- <path
- android:pathData="M16,13C16,12.448 16.448,12 17,12C17.552,12 18,12.448 18,13C18,13.552 17.552,14 17,14C16.448,14 16,13.552 16,13Z"
- android:fillColor="#000"/>
- <path
- android:pathData="M16,5C16,4.724 16.224,4.5 16.5,4.5H17.5C17.776,4.5 18,4.724 18,5V10C18,10.276 17.776,10.5 17.5,10.5H16.5C16.224,10.5 16,10.276 16,10V5Z"
- android:fillColor="#000"/>
+ android:fillColor="#000"
+ android:pathData="M19.499,5C19.089,5 18.749,5.34 18.749,5.75L18.749,8.75C18.749,9.16 19.089,9.5 19.499,9.5C19.909,9.5 20.249,9.16 20.249,8.75L20.249,5.75C20.249,5.34 19.909,5 19.499,5ZM19.499,12C19.909,12 20.249,11.66 20.249,11.25C20.249,10.84 19.909,10.5 19.499,10.5C19.089,10.5 18.749,10.84 18.749,11.25C18.749,11.66 19.089,12 19.499,12Z" />
</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_mobile_5_5_bar.xml b/packages/SettingsLib/res/drawable/ic_mobile_5_5_bar.xml
index 420ffb601e8f..fa9bedc021d8 100644
--- a/packages/SettingsLib/res/drawable/ic_mobile_5_5_bar.xml
+++ b/packages/SettingsLib/res/drawable/ic_mobile_5_5_bar.xml
@@ -1,5 +1,5 @@
<!--
- Copyright (C) 2023 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,23 +14,23 @@
limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="16dp"
- android:height="14dp"
- android:viewportWidth="16.0"
- android:viewportHeight="14.0">
+ android:width="21dp"
+ android:height="12dp"
+ android:viewportWidth="20.5"
+ android:viewportHeight="12.0">
<path
- android:pathData="M7.5,5L8.5,5A0.5,0.5 0,0 1,9 5.5L9,13.5A0.5,0.5 0,0 1,8.5 14L7.5,14A0.5,0.5 0,0 1,7 13.5L7,5.5A0.5,0.5 0,0 1,7.5 5z"
+ android:pathData="M1.249,7C1.939,7 2.499,7.56 2.499,8.25L2.499,10.75C2.499,11.44 1.939,12 1.249,12C0.559,12 -0.001,11.44 -0.001,10.75L-0.001,8.25C-0.001,7.56 0.559,7 1.249,7Z"
android:fillColor="#000"/>
<path
- android:pathData="M11,2L12,2A0.5,0.5 0,0 1,12.5 2.5L12.5,13.5A0.5,0.5 0,0 1,12 14L11,14A0.5,0.5 0,0 1,10.5 13.5L10.5,2.5A0.5,0.5 0,0 1,11 2z"
+ android:pathData="M5.749,5C6.439,5 6.999,5.56 6.999,6.25L6.999,10.75C6.999,11.44 6.439,12 5.749,12C5.059,12 4.499,11.44 4.499,10.75L4.499,6.25C4.499,5.56 5.059,5 5.749,5Z"
android:fillColor="#000"/>
<path
- android:pathData="M0.5,11L1.5,11A0.5,0.5 0,0 1,2 11.5L2,13.5A0.5,0.5 0,0 1,1.5 14L0.5,14A0.5,0.5 0,0 1,0 13.5L0,11.5A0.5,0.5 0,0 1,0.5 11z"
+ android:pathData="M10.249,3C10.939,3 11.499,3.56 11.499,4.25L11.499,10.75C11.499,11.44 10.939,12 10.249,12C9.559,12 8.999,11.44 8.999,10.75L8.999,4.25C8.999,3.56 9.559,3 10.249,3Z"
android:fillColor="#000"/>
<path
- android:pathData="M14.5,0L15.5,0A0.5,0.5 0,0 1,16 0.5L16,13.5A0.5,0.5 0,0 1,15.5 14L14.5,14A0.5,0.5 0,0 1,14 13.5L14,0.5A0.5,0.5 0,0 1,14.5 0z"
+ android:pathData="M14.749,1.5C15.439,1.5 15.999,2.06 15.999,2.75L15.999,10.75C15.999,11.44 15.439,12 14.749,12C14.059,12 13.499,11.44 13.499,10.75L13.499,2.75C13.499,2.06 14.059,1.5 14.749,1.5Z"
android:fillColor="#000"/>
<path
- android:pathData="M4,8L5,8A0.5,0.5 0,0 1,5.5 8.5L5.5,13.5A0.5,0.5 0,0 1,5 14L4,14A0.5,0.5 0,0 1,3.5 13.5L3.5,8.5A0.5,0.5 0,0 1,4 8z"
+ android:pathData="M19.249,0C19.939,0 20.499,0.56 20.499,1.25L20.499,10.75C20.499,11.44 19.939,12 19.249,12C18.559,12 17.999,11.44 17.999,10.75L17.999,1.25C17.999,0.56 18.559,0 19.249,0Z"
android:fillColor="#000"/>
</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_mobile_5_5_bar_error.xml b/packages/SettingsLib/res/drawable/ic_mobile_5_5_bar_error.xml
index e63ca77e9db1..1728bc78b22b 100644
--- a/packages/SettingsLib/res/drawable/ic_mobile_5_5_bar_error.xml
+++ b/packages/SettingsLib/res/drawable/ic_mobile_5_5_bar_error.xml
@@ -1,27 +1,49 @@
+<!--
+ 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.
+-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="18dp"
- android:height="14dp"
- android:viewportWidth="18.0"
- android:viewportHeight="14.0">
+ android:width="21dp"
+ android:height="12dp"
+ android:viewportHeight="12.0"
+ android:viewportWidth="20.5">
+ <!-- clip-out the circle which will contain the exclamation point (below this group) -->
+ <group>
+ <clip-path android:pathData="
+ M0,0
+ H20.5
+ V12.0
+ H0
+ Z
+ M19.499,13.5C22.261,13.5 24.499,11.261 24.499,8.5C24.499,5.739 22.261,3.5 19.499,3.5C16.738,3.5 14.499,5.739 14.499,8.5C14.499,11.261 16.738,13.5 19.499,13.5Z" />
+ <path
+ android:fillColor="#000"
+ android:pathData="M1.249,7C1.939,7 2.499,7.56 2.499,8.25L2.499,10.75C2.499,11.44 1.939,12 1.249,12C0.559,12 -0.001,11.44 -0.001,10.75L-0.001,8.25C-0.001,7.56 0.559,7 1.249,7Z" />
+ <path
+ android:fillColor="#000"
+ android:pathData="M5.749,5C6.439,5 6.999,5.56 6.999,6.25L6.999,10.75C6.999,11.44 6.439,12 5.749,12C5.059,12 4.499,11.44 4.499,10.75L4.499,6.25C4.499,5.56 5.059,5 5.749,5Z" />
+ <path
+ android:fillColor="#000"
+ android:pathData="M10.249,3C10.939,3 11.499,3.56 11.499,4.25L11.499,10.75C11.499,11.44 10.939,12 10.249,12C9.559,12 8.999,11.44 8.999,10.75L8.999,4.25C8.999,3.56 9.559,3 10.249,3Z" />
+ <path
+ android:fillColor="#000"
+ android:pathData="M14.749,1.5C15.439,1.5 15.999,2.06 15.999,2.75L15.999,10.75C15.999,11.44 15.439,12 14.749,12C14.059,12 13.499,11.44 13.499,10.75L13.499,2.75C13.499,2.06 14.059,1.5 14.749,1.5Z" />
+ <path
+ android:fillColor="#000"
+ android:pathData="M19.249,0C19.939,0 20.499,0.56 20.499,1.25L20.499,10.75C20.499,11.44 19.939,12 19.249,12C18.559,12 17.999,11.44 17.999,10.75L17.999,1.25C17.999,0.56 18.559,0 19.249,0Z" />
+ </group>
<path
- android:pathData="M7,5.5C7,5.224 7.224,5 7.5,5H8.5C8.776,5 9,5.224 9,5.5V13.5C9,13.776 8.776,14 8.5,14H7.5C7.224,14 7,13.776 7,13.5V5.5Z"
- android:fillColor="#000"/>
- <path
- android:pathData="M10.5,2.5C10.5,2.224 10.724,2 11,2H12C12.276,2 12.5,2.224 12.5,2.5V13.5C12.5,13.776 12.276,14 12,14H11C10.724,14 10.5,13.776 10.5,13.5V2.5Z"
- android:fillColor="#000"/>
- <path
- android:pathData="M0,11.5C0,11.224 0.224,11 0.5,11H1.5C1.776,11 2,11.224 2,11.5V13.5C2,13.776 1.776,14 1.5,14H0.5C0.224,14 0,13.776 0,13.5V11.5Z"
- android:fillColor="#000"/>
- <path
- android:pathData="M3.5,8.5C3.5,8.224 3.724,8 4,8H5C5.276,8 5.5,8.224 5.5,8.5V13.5C5.5,13.776 5.276,14 5,14H4C3.724,14 3.5,13.776 3.5,13.5V8.5Z"
- android:fillColor="#000"/>
- <path
- android:pathData="M14.5,0C14.224,0 14,0.224 14,0.5V3H16V0.5C16,0.224 15.776,0 15.5,0H14.5Z"
- android:fillColor="#000"/>
- <path
- android:pathData="M16,13C16,12.448 16.448,12 17,12C17.552,12 18,12.448 18,13C18,13.552 17.552,14 17,14C16.448,14 16,13.552 16,13Z"
- android:fillColor="#000"/>
- <path
- android:pathData="M16,5C16,4.724 16.224,4.5 16.5,4.5H17.5C17.776,4.5 18,4.724 18,5V10C18,10.276 17.776,10.5 17.5,10.5H16.5C16.224,10.5 16,10.276 16,10V5Z"
- android:fillColor="#000"/>
+ android:fillColor="#000"
+ android:pathData="M19.499,5C19.089,5 18.749,5.34 18.749,5.75L18.749,8.75C18.749,9.16 19.089,9.5 19.499,9.5C19.909,9.5 20.249,9.16 20.249,8.75L20.249,5.75C20.249,5.34 19.909,5 19.499,5ZM19.499,12C19.909,12 20.249,11.66 20.249,11.25C20.249,10.84 19.909,10.5 19.499,10.5C19.089,10.5 18.749,10.84 18.749,11.25C18.749,11.66 19.089,12 19.499,12Z" />
</vector>
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index 4b0400fb3441..91ec83690722 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -889,6 +889,9 @@
<!-- Preference category for monitoring debugging development settings. [CHAR LIMIT=25] -->
<string name="debug_monitoring_category">Monitoring</string>
+ <!-- Preference category to alter window management settings, [CHAR LIMIT=50] -->
+ <string name="window_management_category">Window Management</string>
+
<!-- UI debug setting: always enable strict mode? [CHAR LIMIT=25] -->
<string name="strict_mode">Strict mode enabled</string>
<!-- UI debug setting: show strict mode summary [CHAR LIMIT=50] -->
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
index a00484ac28ab..522a436b0732 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
@@ -555,22 +555,17 @@ public class BluetoothUtils {
* connected 2) is Hearing Aid or LE Audio OR 3) connected profile matches currentAudioProfile
*
* @param cachedDevice the CachedBluetoothDevice
- * @param audioManager audio manager to get the current audio profile
+ * @param isOngoingCall get the current audio profile based on if in phone call
* @return if the device is AvailableMediaBluetoothDevice
*/
@WorkerThread
public static boolean isAvailableMediaBluetoothDevice(
- CachedBluetoothDevice cachedDevice, AudioManager audioManager) {
- int audioMode = audioManager.getMode();
+ CachedBluetoothDevice cachedDevice, boolean isOngoingCall) {
int currentAudioProfile;
- if (audioMode == AudioManager.MODE_RINGTONE
- || audioMode == AudioManager.MODE_IN_CALL
- || audioMode == AudioManager.MODE_IN_COMMUNICATION) {
- // in phone call
+ if (isOngoingCall) {
currentAudioProfile = BluetoothProfile.HEADSET;
} else {
- // without phone call
currentAudioProfile = BluetoothProfile.A2DP;
}
@@ -859,22 +854,17 @@ public class BluetoothUtils {
* currentAudioProfile
*
* @param cachedDevice the CachedBluetoothDevice
- * @param audioManager audio manager to get the current audio profile
+ * @param isOngoingCall get the current audio profile based on if in phone call
* @return if the device is AvailableMediaBluetoothDevice
*/
@WorkerThread
public static boolean isConnectedBluetoothDevice(
- CachedBluetoothDevice cachedDevice, AudioManager audioManager) {
- int audioMode = audioManager.getMode();
+ CachedBluetoothDevice cachedDevice, boolean isOngoingCall) {
int currentAudioProfile;
- if (audioMode == AudioManager.MODE_RINGTONE
- || audioMode == AudioManager.MODE_IN_CALL
- || audioMode == AudioManager.MODE_IN_COMMUNICATION) {
- // in phone call
+ if (isOngoingCall) {
currentAudioProfile = BluetoothProfile.HEADSET;
} else {
- // without phone call
currentAudioProfile = BluetoothProfile.A2DP;
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
index 7374f80fd9db..bb96041739eb 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
@@ -16,8 +16,7 @@
package com.android.settingslib.bluetooth;
-import static com.android.settingslib.flags.Flags.enableSetPreferredTransportForLeAudioDevice;
-import static com.android.settingslib.flags.Flags.ignoreA2dpDisconnectionForAndroidAuto;
+import static com.android.settingslib.media.flags.Flags.enableTvMediaOutputDialog;
import android.annotation.CallbackExecutor;
import android.annotation.StringRes;
@@ -53,7 +52,7 @@ import androidx.annotation.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
import com.android.settingslib.R;
import com.android.settingslib.Utils;
-import com.android.settingslib.media.flags.Flags;
+import com.android.settingslib.flags.Flags;
import com.android.settingslib.utils.ThreadUtils;
import com.android.settingslib.widget.AdaptiveOutlineDrawable;
@@ -264,7 +263,7 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice>
mHandler.removeMessages(profile.getProfileId());
if (profile.getConnectionPolicy(mDevice) >
BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) {
- if (ignoreA2dpDisconnectionForAndroidAuto()
+ if (Flags.ignoreA2dpDisconnectionForAndroidAuto()
&& profile instanceof A2dpProfile && isAndroidAuto()) {
Log.w(TAG,
"onProfileStateChanged(): Skip setting A2DP "
@@ -306,7 +305,7 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice>
mLocalNapRoleConnected = true;
}
}
- if (enableSetPreferredTransportForLeAudioDevice()
+ if (Flags.enableSetPreferredTransportForLeAudioDevice()
&& profile instanceof HidProfile) {
updatePreferredTransport();
}
@@ -322,7 +321,7 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice>
mLocalNapRoleConnected = false;
}
- if (enableSetPreferredTransportForLeAudioDevice()
+ if (Flags.enableSetPreferredTransportForLeAudioDevice()
&& profile instanceof LeAudioProfile) {
updatePreferredTransport();
}
@@ -1345,6 +1344,8 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice>
if (mBluetoothManager == null) {
mBluetoothManager = LocalBluetoothManager.getInstance(mContext, null);
}
+ boolean isTempBond = Flags.enableTemporaryBondDevicesUi()
+ && BluetoothUtils.isTemporaryBondDevice(getDevice());
if (BluetoothUtils.hasConnectedBroadcastSource(this, mBluetoothManager)) {
// Gets summary for the buds which are in the audio sharing.
int groupId = BluetoothUtils.getGroupId(this);
@@ -1363,14 +1364,23 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice>
shortSummary);
} else {
// The buds are not primary buds
- return getSummaryWithBatteryInfo(
- R.string.bluetooth_active_media_only_battery_level_untethered,
- R.string.bluetooth_active_media_only_battery_level,
- R.string.bluetooth_active_media_only_no_battery_level,
- leftBattery,
- rightBattery,
- batteryLevelPercentageString,
- shortSummary);
+ return isTempBond
+ ? getSummaryWithBatteryInfo(
+ R.string.bluetooth_guest_media_only_battery_level_untethered,
+ R.string.bluetooth_guest_media_only_battery_level,
+ R.string.bluetooth_guest_media_only_no_battery_level,
+ leftBattery,
+ rightBattery,
+ batteryLevelPercentageString,
+ shortSummary)
+ : getSummaryWithBatteryInfo(
+ R.string.bluetooth_active_media_only_battery_level_untethered,
+ R.string.bluetooth_active_media_only_battery_level,
+ R.string.bluetooth_active_media_only_no_battery_level,
+ leftBattery,
+ rightBattery,
+ batteryLevelPercentageString,
+ shortSummary);
}
} else {
// Gets summary for the buds which are not in the audio sharing.
@@ -1381,16 +1391,28 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice>
&& profile.isEnabled(getDevice()))) {
// The buds support le audio.
if (isConnected()) {
- return getSummaryWithBatteryInfo(
- R.string.bluetooth_battery_level_untethered_lea_support,
- R.string.bluetooth_battery_level_lea_support,
- R.string.bluetooth_no_battery_level_lea_support,
- leftBattery,
- rightBattery,
- batteryLevelPercentageString,
- shortSummary);
+ return isTempBond
+ ? getSummaryWithBatteryInfo(
+ R.string.bluetooth_guest_battery_level_untethered_lea_support,
+ R.string.bluetooth_guest_battery_level_lea_support,
+ R.string.bluetooth_guest_no_battery_level_lea_support,
+ leftBattery,
+ rightBattery,
+ batteryLevelPercentageString,
+ shortSummary)
+ : getSummaryWithBatteryInfo(
+ R.string.bluetooth_battery_level_untethered_lea_support,
+ R.string.bluetooth_battery_level_lea_support,
+ R.string.bluetooth_no_battery_level_lea_support,
+ leftBattery,
+ rightBattery,
+ batteryLevelPercentageString,
+ shortSummary);
} else {
- return mContext.getString(R.string.bluetooth_saved_device_lea_support);
+ return isTempBond
+ ? mContext.getString(
+ R.string.bluetooth_guest_saved_device_lea_support)
+ : mContext.getString(R.string.bluetooth_saved_device_lea_support);
}
}
}
@@ -1509,11 +1531,19 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice>
leftBattery = getLeftBatteryLevel();
rightBattery = getRightBatteryLevel();
+ boolean isTempBond = Flags.enableTemporaryBondDevicesUi()
+ && BluetoothUtils.isTemporaryBondDevice(getDevice());
// Set default string with battery level in device connected situation.
if (isTwsBatteryAvailable(leftBattery, rightBattery)) {
- stringRes = R.string.bluetooth_battery_level_untethered;
+ stringRes =
+ isTempBond
+ ? R.string.bluetooth_guest_battery_level_untethered
+ : R.string.bluetooth_battery_level_untethered;
} else if (batteryLevelPercentageString != null && !shortSummary) {
- stringRes = R.string.bluetooth_battery_level;
+ stringRes =
+ isTempBond
+ ? R.string.bluetooth_guest_battery_level
+ : R.string.bluetooth_battery_level;
}
// Set active string in following device connected situation, also show battery
@@ -1529,11 +1559,20 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice>
|| (mIsActiveDeviceA2dp && !isOnCall)
|| mIsActiveDeviceLeAudio) {
if (isTwsBatteryAvailable(leftBattery, rightBattery) && !shortSummary) {
- stringRes = R.string.bluetooth_active_battery_level_untethered;
+ stringRes =
+ isTempBond
+ ? R.string.bluetooth_guest_battery_level_untethered
+ : R.string.bluetooth_active_battery_level_untethered;
} else if (batteryLevelPercentageString != null && !shortSummary) {
- stringRes = R.string.bluetooth_active_battery_level;
+ stringRes =
+ isTempBond
+ ? R.string.bluetooth_guest_battery_level
+ : R.string.bluetooth_active_battery_level;
} else {
- stringRes = R.string.bluetooth_active_no_battery_level;
+ stringRes =
+ isTempBond
+ ? R.string.bluetooth_guest_no_battery_level
+ : R.string.bluetooth_active_no_battery_level;
}
}
@@ -1559,7 +1598,7 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice>
|| stringRes == R.string.bluetooth_active_battery_level_untethered_left
|| stringRes == R.string.bluetooth_active_battery_level_untethered_right
|| stringRes == R.string.bluetooth_battery_level_untethered;
- if (isTvSummary && summaryIncludesBatteryLevel && Flags.enableTvMediaOutputDialog()) {
+ if (isTvSummary && summaryIncludesBatteryLevel && enableTvMediaOutputDialog()) {
return getTvBatterySummary(
getMinBatteryLevelWithMemberDevices(),
leftBattery,
@@ -1949,6 +1988,17 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice>
}
/**
+ * @return {@code true} if {@code cachedBluetoothDevice} has member which is LeAudio device
+ */
+ public boolean hasConnectedLeAudioMemberDevice() {
+ LeAudioProfile leAudio = mProfileManager.getLeAudioProfile();
+ return leAudio != null && getMemberDevice().stream().anyMatch(
+ cachedDevice -> cachedDevice != null && cachedDevice.getDevice() != null
+ && leAudio.getConnectionStatus(cachedDevice.getDevice())
+ == BluetoothProfile.STATE_CONNECTED);
+ }
+
+ /**
* @return {@code true} if {@code cachedBluetoothDevice} supports broadcast assistant profile
*/
public boolean isConnectedLeAudioBroadcastAssistantDevice() {
diff --git a/packages/SettingsLib/src/com/android/settingslib/display/DisplayDensityUtils.java b/packages/SettingsLib/src/com/android/settingslib/display/DisplayDensityUtils.java
index 58e9355800d7..985599c952d1 100644
--- a/packages/SettingsLib/src/com/android/settingslib/display/DisplayDensityUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/display/DisplayDensityUtils.java
@@ -57,7 +57,7 @@ public class DisplayDensityUtils {
* Summaries for scales smaller than "default" in order of smallest to
* largest.
*/
- private static final int[] SUMMARIES_SMALLER = new int[] {
+ private static final int[] SUMMARIES_SMALLER = new int[]{
R.string.screen_zoom_summary_small
};
@@ -65,7 +65,7 @@ public class DisplayDensityUtils {
* Summaries for scales larger than "default" in order of smallest to
* largest.
*/
- private static final int[] SUMMARIES_LARGER = new int[] {
+ private static final int[] SUMMARIES_LARGER = new int[]{
R.string.screen_zoom_summary_large,
R.string.screen_zoom_summary_very_large,
R.string.screen_zoom_summary_extremely_large,
@@ -108,7 +108,8 @@ public class DisplayDensityUtils {
* Creates an instance that stores the density values for the smallest display that satisfies
* the predicate. It is enough to store the values for one display because the same density
* should be set to all the displays that satisfy the predicate.
- * @param context The context
+ *
+ * @param context The context
* @param predicate Determines what displays the density should be set for. The default display
* must satisfy this predicate.
*/
@@ -127,14 +128,10 @@ public class DisplayDensityUtils {
mCurrentIndex = -1;
return;
}
- if (!mPredicate.test(defaultDisplayInfo)) {
- throw new IllegalArgumentException(
- "Predicate must not filter out the default display.");
- }
- int idOfSmallestDisplay = Display.DEFAULT_DISPLAY;
- int minDimensionPx = Math.min(defaultDisplayInfo.logicalWidth,
- defaultDisplayInfo.logicalHeight);
+ int idOfSmallestDisplay = Display.INVALID_DISPLAY;
+ int minDimensionPx = Integer.MAX_VALUE;
+ DisplayInfo smallestDisplayInfo = null;
for (Display display : mDisplayManager.getDisplays(
DisplayManager.DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED)) {
DisplayInfo info = new DisplayInfo();
@@ -149,9 +146,19 @@ public class DisplayDensityUtils {
if (minDimension < minDimensionPx) {
minDimensionPx = minDimension;
idOfSmallestDisplay = display.getDisplayId();
+ smallestDisplayInfo = info;
}
}
+ if (smallestDisplayInfo == null) {
+ Log.w(LOG_TAG, "No display satisfies the predicate");
+ mEntries = null;
+ mValues = null;
+ mDefaultDensity = 0;
+ mCurrentIndex = -1;
+ return;
+ }
+
final int defaultDensity =
DisplayDensityUtils.getDefaultDensityForDisplay(idOfSmallestDisplay);
if (defaultDensity <= 0) {
@@ -165,7 +172,12 @@ public class DisplayDensityUtils {
final Resources res = context.getResources();
- final int currentDensity = defaultDisplayInfo.logicalDensityDpi;
+ int currentDensity;
+ if (mPredicate.test(defaultDisplayInfo)) {
+ currentDensity = defaultDisplayInfo.logicalDensityDpi;
+ } else {
+ currentDensity = smallestDisplayInfo.logicalDensityDpi;
+ }
int currentDensityIndex = -1;
// Compute number of "larger" and "smaller" scales for this display.
@@ -266,16 +278,16 @@ public class DisplayDensityUtils {
* Returns the default density for the specified display.
*
* @param displayId the identifier of the display
- * @return the default density of the specified display, or {@code -1} if
- * the display does not exist or the density could not be obtained
+ * @return the default density of the specified display, or {@code -1} if the display does not
+ * exist or the density could not be obtained
*/
private static int getDefaultDensityForDisplay(int displayId) {
- try {
- final IWindowManager wm = WindowManagerGlobal.getWindowManagerService();
- return wm.getInitialDisplayDensity(displayId);
- } catch (RemoteException exc) {
- return -1;
- }
+ try {
+ final IWindowManager wm = WindowManagerGlobal.getWindowManagerService();
+ return wm.getInitialDisplayDensity(displayId);
+ } catch (RemoteException exc) {
+ return -1;
+ }
}
/**
diff --git a/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java b/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java
index 51259e2f311d..e7887ed26cc3 100644
--- a/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java
+++ b/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java
@@ -295,9 +295,9 @@ public class DreamBackend {
@WhenToDream
public int getWhenToDreamSetting() {
return isActivatedOnDock() && isActivatedOnSleep() ? WHILE_CHARGING_OR_DOCKED
- : isActivatedOnDock() ? WHILE_DOCKED
- : isActivatedOnPostured() ? WHILE_POSTURED
- : isActivatedOnSleep() ? WHILE_CHARGING
+ : isActivatedOnSleep() ? WHILE_CHARGING
+ : isActivatedOnDock() ? WHILE_DOCKED
+ : isActivatedOnPostured() ? WHILE_POSTURED
: NEVER;
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/graph/SignalDrawable.java b/packages/SettingsLib/src/com/android/settingslib/graph/SignalDrawable.java
index 13a06017abbc..671dfa230f0d 100644
--- a/packages/SettingsLib/src/com/android/settingslib/graph/SignalDrawable.java
+++ b/packages/SettingsLib/src/com/android/settingslib/graph/SignalDrawable.java
@@ -128,12 +128,20 @@ public class SignalDrawable extends DrawableWrapper {
@Override
public int getIntrinsicWidth() {
- return mIntrinsicSize;
+ if (newStatusBarIcons()) {
+ return super.getIntrinsicWidth();
+ } else {
+ return mIntrinsicSize;
+ }
}
@Override
public int getIntrinsicHeight() {
- return mIntrinsicSize;
+ if (newStatusBarIcons()) {
+ return super.getIntrinsicHeight();
+ } else {
+ return mIntrinsicSize;
+ }
}
private void updateAnimation() {
diff --git a/packages/SettingsLib/src/com/android/settingslib/mobile/TelephonyIcons.java b/packages/SettingsLib/src/com/android/settingslib/mobile/TelephonyIcons.java
index 094567c400a3..9ca46238c2ce 100644
--- a/packages/SettingsLib/src/com/android/settingslib/mobile/TelephonyIcons.java
+++ b/packages/SettingsLib/src/com/android/settingslib/mobile/TelephonyIcons.java
@@ -18,6 +18,7 @@ package com.android.settingslib.mobile;
import com.android.settingslib.R;
import com.android.settingslib.SignalIcon.MobileIconGroup;
+import com.android.settingslib.flags.Flags;
import java.util.HashMap;
import java.util.Map;
@@ -29,22 +30,47 @@ public class TelephonyIcons {
//***** Data connection icons
public static final int FLIGHT_MODE_ICON = R.drawable.stat_sys_airplane_mode;
- public static final int ICON_LTE = R.drawable.ic_lte_mobiledata;
- public static final int ICON_LTE_PLUS = R.drawable.ic_lte_plus_mobiledata;
- public static final int ICON_G = R.drawable.ic_g_mobiledata;
- public static final int ICON_E = R.drawable.ic_e_mobiledata;
- public static final int ICON_H = R.drawable.ic_h_mobiledata;
- public static final int ICON_H_PLUS = R.drawable.ic_h_plus_mobiledata;
- public static final int ICON_3G = R.drawable.ic_3g_mobiledata;
- public static final int ICON_4G = R.drawable.ic_4g_mobiledata;
- public static final int ICON_4G_PLUS = R.drawable.ic_4g_plus_mobiledata;
- public static final int ICON_4G_LTE = R.drawable.ic_4g_lte_mobiledata;
- public static final int ICON_4G_LTE_PLUS = R.drawable.ic_4g_lte_plus_mobiledata;
- public static final int ICON_5G_E = R.drawable.ic_5g_e_mobiledata;
- public static final int ICON_1X = R.drawable.ic_1x_mobiledata;
- public static final int ICON_5G = R.drawable.ic_5g_mobiledata;
- public static final int ICON_5G_PLUS = R.drawable.ic_5g_plus_mobiledata;
- public static final int ICON_CWF = R.drawable.ic_carrier_wifi;
+ public static final int ICON_LTE =
+ flagged(R.drawable.ic_lte_mobiledata, R.drawable.ic_lte_mobiledata_updated);
+ public static final int ICON_LTE_PLUS =
+ flagged(R.drawable.ic_lte_plus_mobiledata, R.drawable.ic_lte_plus_mobiledata_updated);
+ public static final int ICON_G =
+ flagged(R.drawable.ic_g_mobiledata, R.drawable.ic_g_mobiledata_updated);
+ public static final int ICON_E =
+ flagged(R.drawable.ic_e_mobiledata, R.drawable.ic_e_mobiledata_updated);
+ public static final int ICON_H =
+ flagged(R.drawable.ic_h_mobiledata, R.drawable.ic_h_mobiledata_updated);
+ public static final int ICON_H_PLUS =
+ flagged(R.drawable.ic_h_plus_mobiledata, R.drawable.ic_h_plus_mobiledata_updated);
+ public static final int ICON_3G =
+ flagged(R.drawable.ic_3g_mobiledata, R.drawable.ic_3g_mobiledata_updated);
+ public static final int ICON_4G =
+ flagged(R.drawable.ic_4g_mobiledata, R.drawable.ic_4g_mobiledata_updated);
+ public static final int ICON_4G_PLUS =
+ flagged(R.drawable.ic_4g_plus_mobiledata, R.drawable.ic_4g_plus_mobiledata_updated);
+ public static final int ICON_4G_LTE =
+ flagged(R.drawable.ic_4g_lte_mobiledata, R.drawable.ic_4g_lte_mobiledata_updated);
+ public static final int ICON_4G_LTE_PLUS =
+ flagged(R.drawable.ic_4g_lte_plus_mobiledata,
+ R.drawable.ic_4g_lte_plus_mobiledata_updated);
+ public static final int ICON_5G_E =
+ flagged(R.drawable.ic_5g_e_mobiledata, R.drawable.ic_5g_e_mobiledata_updated);
+ public static final int ICON_1X =
+ flagged(R.drawable.ic_1x_mobiledata, R.drawable.ic_1x_mobiledata_updated);
+ public static final int ICON_5G =
+ flagged(R.drawable.ic_5g_mobiledata, R.drawable.ic_5g_mobiledata_updated);
+ public static final int ICON_5G_PLUS =
+ flagged(R.drawable.ic_5g_plus_mobiledata, R.drawable.ic_5g_plus_mobiledata_updated);
+ public static final int ICON_CWF =
+ flagged(R.drawable.ic_carrier_wifi, R.drawable.ic_carrier_wifi_updated);
+
+ /** Make it slightly more obvious which resource we are using */
+ private static int flagged(int oldIcon, int newIcon) {
+ if (Flags.newStatusBarIcons()) {
+ return newIcon;
+ }
+ return oldIcon;
+ }
public static final MobileIconGroup CARRIER_NETWORK_CHANGE = new MobileIconGroup(
"CARRIER_NETWORK_CHANGE",
diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/data/repository/ZenModeRepository.kt b/packages/SettingsLib/src/com/android/settingslib/notification/data/repository/ZenModeRepository.kt
index f446bb8e32d1..c4e724554c04 100644
--- a/packages/SettingsLib/src/com/android/settingslib/notification/data/repository/ZenModeRepository.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/notification/data/repository/ZenModeRepository.kt
@@ -93,10 +93,9 @@ class ZenModeRepositoryImpl(
IntentFilter().apply {
addAction(NotificationManager.ACTION_INTERRUPTION_FILTER_CHANGED)
addAction(NotificationManager.ACTION_NOTIFICATION_POLICY_CHANGED)
- if (android.app.Flags.modesApi())
- addAction(
- NotificationManager.ACTION_CONSOLIDATED_NOTIFICATION_POLICY_CHANGED
- )
+ addAction(
+ NotificationManager.ACTION_CONSOLIDATED_NOTIFICATION_POLICY_CHANGED
+ )
},
/* broadcastPermission = */ null,
/* scheduler = */ backgroundHandler,
@@ -109,16 +108,13 @@ class ZenModeRepositoryImpl(
}
override val consolidatedNotificationPolicy: StateFlow<NotificationManager.Policy?> by lazy {
- if (android.app.Flags.modesApi())
- flowFromBroadcast(NotificationManager.ACTION_CONSOLIDATED_NOTIFICATION_POLICY_CHANGED) {
- // If available, get the value from extras to avoid a potential binder call.
- it?.extras?.getParcelable(EXTRA_NOTIFICATION_POLICY)
- ?: notificationManager.consolidatedNotificationPolicy
- }
- else
- flowFromBroadcast(NotificationManager.ACTION_NOTIFICATION_POLICY_CHANGED) {
- notificationManager.consolidatedNotificationPolicy
- }
+ flowFromBroadcast(NotificationManager.ACTION_CONSOLIDATED_NOTIFICATION_POLICY_CHANGED) {
+ // If available, get the value from extras to avoid a potential binder call.
+ it?.extras?.getParcelable(
+ EXTRA_NOTIFICATION_POLICY,
+ NotificationManager.Policy::class.java
+ ) ?: notificationManager.consolidatedNotificationPolicy
+ }
}
override val globalZenMode: StateFlow<Int?> by lazy {
diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/modes/EnableDndDialogFactory.java b/packages/SettingsLib/src/com/android/settingslib/notification/modes/EnableDndDialogFactory.java
index f0e7fb851d5f..52d62b6226b8 100644
--- a/packages/SettingsLib/src/com/android/settingslib/notification/modes/EnableDndDialogFactory.java
+++ b/packages/SettingsLib/src/com/android/settingslib/notification/modes/EnableDndDialogFactory.java
@@ -19,7 +19,6 @@ package com.android.settingslib.notification.modes;
import android.app.ActivityManager;
import android.app.AlarmManager;
import android.app.AlertDialog;
-import android.app.Flags;
import android.app.NotificationManager;
import android.content.Context;
import android.content.DialogInterface;
@@ -42,8 +41,6 @@ import android.widget.RadioGroup;
import android.widget.ScrollView;
import android.widget.TextView;
-import androidx.annotation.Nullable;
-
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.policy.PhoneWindow;
import com.android.settingslib.R;
@@ -80,7 +77,6 @@ public class EnableDndDialogFactory {
private static final int SECONDS_MS = 1000;
private static final int MINUTES_MS = 60 * SECONDS_MS;
- @Nullable
private final EnableDndDialogMetricsLogger mMetricsLogger;
@VisibleForTesting
@@ -152,16 +148,10 @@ public class EnableDndDialogFactory {
Slog.d(TAG, "Invalid manual condition: " + tag.condition);
}
// always triggers priority-only dnd with chosen condition
- if (Flags.modesApi()) {
- mNotificationManager.setZenMode(
- Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS,
- getRealConditionId(tag.condition), TAG,
- /* fromUser= */ true);
- } else {
- mNotificationManager.setZenMode(
- Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS,
- getRealConditionId(tag.condition), TAG);
- }
+ mNotificationManager.setZenMode(
+ Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS,
+ getRealConditionId(tag.condition), TAG,
+ /* fromUser= */ true);
}
});
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/display/DisplayDensityUtilsTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/display/DisplayDensityUtilsTest.java
index bba278a0a661..73cf28f86e00 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/display/DisplayDensityUtilsTest.java
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/display/DisplayDensityUtilsTest.java
@@ -88,8 +88,8 @@ public class DisplayDensityUtilsTest {
@Test
public void createDisplayDensityUtil_onlyDefaultDisplay() throws RemoteException {
- var info = createDisplayInfoForDisplay(Display.DEFAULT_DISPLAY, Display.TYPE_INTERNAL, 2560,
- 1600, 320);
+ var info = createDisplayInfoForDisplay(
+ Display.DEFAULT_DISPLAY, Display.TYPE_INTERNAL, 2560, 1600, 320);
var display = new Display(mDisplayManagerGlobal, info.displayId, info,
(DisplayAdjustments) null);
doReturn(new Display[]{display}).when(mDisplayManager).getDisplays(any());
@@ -126,6 +126,33 @@ public class DisplayDensityUtilsTest {
assertThat(mDisplayDensityUtils.getValues()).isEqualTo(new int[]{330, 390, 426, 462, 500});
}
+ @Test
+ public void createDisplayDensityUtil_forExternalDisplay() throws RemoteException {
+ // Default display
+ var defaultDisplayInfo = createDisplayInfoForDisplay(Display.DEFAULT_DISPLAY,
+ Display.TYPE_INTERNAL, 2000, 2000, 390);
+ var defaultDisplay = new Display(mDisplayManagerGlobal, defaultDisplayInfo.displayId,
+ defaultDisplayInfo,
+ (DisplayAdjustments) null);
+ doReturn(defaultDisplay).when(mDisplayManager).getDisplay(defaultDisplayInfo.displayId);
+
+ // Create external display
+ var externalDisplayInfo = createDisplayInfoForDisplay(/* displayId= */ 2,
+ Display.TYPE_EXTERNAL, 1920, 1080, 85);
+ var externalDisplay = new Display(mDisplayManagerGlobal, externalDisplayInfo.displayId,
+ externalDisplayInfo,
+ (DisplayAdjustments) null);
+
+ doReturn(new Display[]{externalDisplay, defaultDisplay}).when(mDisplayManager).getDisplays(
+ any());
+ doReturn(externalDisplay).when(mDisplayManager).getDisplay(externalDisplayInfo.displayId);
+
+ mDisplayDensityUtils = new DisplayDensityUtils(mContext,
+ (info) -> info.displayId == externalDisplayInfo.displayId);
+
+ assertThat(mDisplayDensityUtils.getValues()).isEqualTo(new int[]{72, 85, 94, 102, 112});
+ }
+
private DisplayInfo createDisplayInfoForDisplay(int displayId, int displayType,
int width, int height, int density) throws RemoteException {
var displayInfo = new DisplayInfo();
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 7c46db96595f..ebe6128e5582 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
@@ -87,7 +87,6 @@ public class BluetoothUtilsTest {
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
private BluetoothDevice mBluetoothDevice;
- @Mock private AudioManager mAudioManager;
@Mock private PackageManager mPackageManager;
@Mock private LeAudioProfile mA2dpProfile;
@Mock private LeAudioProfile mLeAudioProfile;
@@ -446,13 +445,12 @@ public class BluetoothUtilsTest {
assertThat(
BluetoothUtils.isAvailableMediaBluetoothDevice(
- mCachedBluetoothDevice, mAudioManager))
+ mCachedBluetoothDevice, /* isOngoingCall= */ false))
.isTrue();
}
@Test
public void isAvailableMediaBluetoothDevice_isHeadset_isConnectedA2dpDevice_returnFalse() {
- when(mAudioManager.getMode()).thenReturn(AudioManager.MODE_RINGTONE);
when(mCachedBluetoothDevice.isConnectedA2dpDevice()).thenReturn(true);
when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice);
when(mBluetoothDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
@@ -460,13 +458,12 @@ public class BluetoothUtilsTest {
assertThat(
BluetoothUtils.isAvailableMediaBluetoothDevice(
- mCachedBluetoothDevice, mAudioManager))
+ mCachedBluetoothDevice, /* isOngoingCall= */ true))
.isFalse();
}
@Test
public void isAvailableMediaBluetoothDevice_isA2dp_isConnectedA2dpDevice_returnTrue() {
- when(mAudioManager.getMode()).thenReturn(AudioManager.MODE_NORMAL);
when(mCachedBluetoothDevice.isConnectedA2dpDevice()).thenReturn(true);
when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice);
when(mBluetoothDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
@@ -474,13 +471,12 @@ public class BluetoothUtilsTest {
assertThat(
BluetoothUtils.isAvailableMediaBluetoothDevice(
- mCachedBluetoothDevice, mAudioManager))
+ mCachedBluetoothDevice, /* isOngoingCall= */ false))
.isTrue();
}
@Test
public void isAvailableMediaBluetoothDevice_isHeadset_isConnectedHfpDevice_returnTrue() {
- when(mAudioManager.getMode()).thenReturn(AudioManager.MODE_RINGTONE);
when(mCachedBluetoothDevice.isConnectedHfpDevice()).thenReturn(true);
when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice);
when(mBluetoothDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
@@ -488,7 +484,7 @@ public class BluetoothUtilsTest {
assertThat(
BluetoothUtils.isAvailableMediaBluetoothDevice(
- mCachedBluetoothDevice, mAudioManager))
+ mCachedBluetoothDevice, /* isOngoingCall= */ true))
.isTrue();
}
@@ -499,56 +495,52 @@ public class BluetoothUtilsTest {
when(mBluetoothDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
when(mBluetoothDevice.isConnected()).thenReturn(true);
- assertThat(BluetoothUtils.isConnectedBluetoothDevice(mCachedBluetoothDevice, mAudioManager))
- .isFalse();
+ assertThat(BluetoothUtils.isConnectedBluetoothDevice(
+ mCachedBluetoothDevice, /* isOngoingCall= */ false)).isFalse();
}
@Test
public void isConnectedBluetoothDevice_isHeadset_isConnectedA2dpDevice_returnTrue() {
- when(mAudioManager.getMode()).thenReturn(AudioManager.MODE_RINGTONE);
when(mCachedBluetoothDevice.isConnectedA2dpDevice()).thenReturn(true);
when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice);
when(mBluetoothDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
when(mBluetoothDevice.isConnected()).thenReturn(true);
- assertThat(BluetoothUtils.isConnectedBluetoothDevice(mCachedBluetoothDevice, mAudioManager))
- .isTrue();
+ assertThat(BluetoothUtils.isConnectedBluetoothDevice(
+ mCachedBluetoothDevice, /* isOngoingCall= */ true)).isTrue();
}
@Test
public void isConnectedBluetoothDevice_isA2dp_isConnectedA2dpDevice_returnFalse() {
- when(mAudioManager.getMode()).thenReturn(AudioManager.MODE_NORMAL);
when(mCachedBluetoothDevice.isConnectedA2dpDevice()).thenReturn(true);
when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice);
when(mBluetoothDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
when(mBluetoothDevice.isConnected()).thenReturn(true);
- assertThat(BluetoothUtils.isConnectedBluetoothDevice(mCachedBluetoothDevice, mAudioManager))
- .isFalse();
+ assertThat(BluetoothUtils.isConnectedBluetoothDevice(
+ mCachedBluetoothDevice, /* isOngoingCall= */ false)).isFalse();
}
@Test
public void isConnectedBluetoothDevice_isHeadset_isConnectedHfpDevice_returnFalse() {
- when(mAudioManager.getMode()).thenReturn(AudioManager.MODE_RINGTONE);
when(mCachedBluetoothDevice.isConnectedHfpDevice()).thenReturn(true);
when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice);
when(mBluetoothDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
when(mBluetoothDevice.isConnected()).thenReturn(true);
- assertThat(BluetoothUtils.isConnectedBluetoothDevice(mCachedBluetoothDevice, mAudioManager))
- .isFalse();
+ assertThat(BluetoothUtils.isConnectedBluetoothDevice(
+ mCachedBluetoothDevice, /* isOngoingCall= */ true)).isFalse();
}
@Test
public void isConnectedBluetoothDevice_isNotConnected_returnFalse() {
- when(mAudioManager.getMode()).thenReturn(AudioManager.MODE_RINGTONE);
when(mCachedBluetoothDevice.isConnectedA2dpDevice()).thenReturn(true);
when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice);
when(mBluetoothDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
when(mBluetoothDevice.isConnected()).thenReturn(false);
- assertThat(BluetoothUtils.isConnectedBluetoothDevice(mCachedBluetoothDevice, mAudioManager))
- .isFalse();
+ assertThat(BluetoothUtils.isConnectedBluetoothDevice(
+ mCachedBluetoothDevice, /* isOngoingCall= */ true)).isFalse();
}
@Test
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java
index d933a1ced8bc..f6e26a7200ef 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java
@@ -17,6 +17,7 @@ package com.android.settingslib.bluetooth;
import static com.android.settingslib.flags.Flags.FLAG_ENABLE_LE_AUDIO_SHARING;
import static com.android.settingslib.flags.Flags.FLAG_ENABLE_SET_PREFERRED_TRANSPORT_FOR_LE_AUDIO_DEVICE;
+import static com.android.settingslib.flags.Flags.FLAG_ENABLE_TEMPORARY_BOND_DEVICES_UI;
import static com.google.common.truth.Truth.assertThat;
@@ -78,11 +79,14 @@ public class CachedBluetoothDeviceTest {
private static final String TWS_BATTERY_RIGHT = "25";
private static final String TWS_LOW_BATTERY_THRESHOLD_LOW = "10";
private static final String TWS_LOW_BATTERY_THRESHOLD_HIGH = "25";
+ private static final String TEMP_BOND_METADATA =
+ "<TEMP_BOND_TYPE>le_audio_sharing</TEMP_BOND_TYPE>";
private static final short RSSI_1 = 10;
private static final short RSSI_2 = 11;
private static final boolean JUSTDISCOVERED_1 = true;
private static final boolean JUSTDISCOVERED_2 = false;
private static final int LOW_BATTERY_COLOR = android.R.color.holo_red_dark;
+ private static final int METADATA_FAST_PAIR_CUSTOMIZED_FIELDS = 25;
@Mock
private LocalBluetoothProfileManager mProfileManager;
@Mock
@@ -128,6 +132,7 @@ public class CachedBluetoothDeviceTest {
mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_TV_MEDIA_OUTPUT_DIALOG);
mSetFlagsRule.enableFlags(FLAG_ENABLE_SET_PREFERRED_TRANSPORT_FOR_LE_AUDIO_DEVICE);
mSetFlagsRule.enableFlags(FLAG_ENABLE_LE_AUDIO_SHARING);
+ mSetFlagsRule.enableFlags(FLAG_ENABLE_TEMPORARY_BOND_DEVICES_UI);
mContext = RuntimeEnvironment.application;
mAudioManager = mContext.getSystemService(AudioManager.class);
mShadowBluetoothAdapter = Shadow.extract(BluetoothAdapter.getDefaultAdapter());
@@ -2075,6 +2080,87 @@ public class CachedBluetoothDeviceTest {
}
@Test
+ public void getConnectionSummary_GuestDeviceBroadcastPrimary_activeDevice_returnActive() {
+ when(mBroadcast.isEnabled(any())).thenReturn(true);
+ when(mCachedDevice.getDevice()).thenReturn(mDevice);
+ Settings.Secure.putInt(
+ mContext.getContentResolver(),
+ BluetoothUtils.getPrimaryGroupIdUriForBroadcast(),
+ BluetoothCsipSetCoordinator.GROUP_ID_INVALID);
+ when(mDevice.getMetadata(METADATA_FAST_PAIR_CUSTOMIZED_FIELDS))
+ .thenReturn(TEMP_BOND_METADATA.getBytes());
+
+ List<Long> bisSyncState = new ArrayList<>();
+ bisSyncState.add(1L);
+ when(mLeBroadcastReceiveState.getBisSyncState()).thenReturn(bisSyncState);
+ List<BluetoothLeBroadcastReceiveState> sourceList = new ArrayList<>();
+ sourceList.add(mLeBroadcastReceiveState);
+ when(mAssistant.getAllSources(any())).thenReturn(sourceList);
+
+ when(mCachedDevice.getGroupId()).thenReturn(1);
+ when(mCachedDevice.isActiveDevice(BluetoothProfile.LE_AUDIO)).thenReturn(true);
+
+ assertThat(mCachedDevice.getConnectionSummary(false))
+ .isEqualTo(mContext.getString(R.string.bluetooth_active_no_battery_level));
+ }
+
+ @Test
+ public void getConnectionSummary_GuestDeviceBroadcastSecondary_activeDevice_returnGuestMedia() {
+ when(mBroadcast.isEnabled(any())).thenReturn(true);
+ when(mCachedDevice.getDevice()).thenReturn(mDevice);
+ Settings.Secure.putInt(
+ mContext.getContentResolver(),
+ BluetoothUtils.getPrimaryGroupIdUriForBroadcast(),
+ 1);
+ when(mDevice.getMetadata(METADATA_FAST_PAIR_CUSTOMIZED_FIELDS))
+ .thenReturn(TEMP_BOND_METADATA.getBytes());
+
+ List<Long> bisSyncState = new ArrayList<>();
+ bisSyncState.add(1L);
+ when(mLeBroadcastReceiveState.getBisSyncState()).thenReturn(bisSyncState);
+ List<BluetoothLeBroadcastReceiveState> sourceList = new ArrayList<>();
+ sourceList.add(mLeBroadcastReceiveState);
+ when(mAssistant.getAllSources(any())).thenReturn(sourceList);
+
+ when(mCachedDevice.getGroupId()).thenReturn(BluetoothCsipSetCoordinator.GROUP_ID_INVALID);
+
+ assertThat(mCachedDevice.getConnectionSummary(false))
+ .isEqualTo(
+ mContext.getString(R.string.bluetooth_guest_media_only_no_battery_level));
+ }
+
+ @Test
+ public void getConnectionSummary_GuestDeviceSupportsBroadcastConnected_returnGuestSupportLe() {
+ when(mBroadcast.isEnabled(any())).thenReturn(true);
+ when(mCachedDevice.getDevice()).thenReturn(mDevice);
+ when(mLeAudioProfile.isEnabled(mDevice)).thenReturn(true);
+ when(mDevice.getMetadata(METADATA_FAST_PAIR_CUSTOMIZED_FIELDS))
+ .thenReturn(TEMP_BOND_METADATA.getBytes());
+
+ when(mCachedDevice.getProfiles()).thenReturn(ImmutableList.of(mLeAudioProfile));
+ when(mCachedDevice.isConnected()).thenReturn(true);
+
+ assertThat(mCachedDevice.getConnectionSummary(false))
+ .isEqualTo(
+ mContext.getString(R.string.bluetooth_guest_no_battery_level_lea_support));
+ }
+
+ @Test
+ public void getConnectionSummary_GuestDeviceSupportsBroadcastNotConnected_returnSavedGuest() {
+ when(mBroadcast.isEnabled(any())).thenReturn(true);
+ when(mCachedDevice.getDevice()).thenReturn(mDevice);
+ when(mLeAudioProfile.isEnabled(mDevice)).thenReturn(true);
+ when(mDevice.getMetadata(METADATA_FAST_PAIR_CUSTOMIZED_FIELDS))
+ .thenReturn(TEMP_BOND_METADATA.getBytes());
+
+ when(mCachedDevice.getProfiles()).thenReturn(ImmutableList.of(mLeAudioProfile));
+ when(mCachedDevice.isConnected()).thenReturn(false);
+
+ assertThat(mCachedDevice.getConnectionSummary(false))
+ .isEqualTo(mContext.getString(R.string.bluetooth_guest_saved_device_lea_support));
+ }
+
+ @Test
public void isHearingDevice_supportHearingRelatedProfiles_returnTrue() {
when(mCachedDevice.getProfiles()).thenReturn(
ImmutableList.of(mHapClientProfile, mHearingAidProfile));
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/dream/DreamBackendTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/dream/DreamBackendTest.java
index 00ae96cfab50..c21eb0ce777a 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/dream/DreamBackendTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/dream/DreamBackendTest.java
@@ -16,6 +16,8 @@
package com.android.settingslib.dream;
+import static android.service.dreams.Flags.FLAG_ALLOW_DREAM_WHEN_POSTURED;
+
import static com.android.settingslib.dream.DreamBackend.COMPLICATION_TYPE_DATE;
import static com.android.settingslib.dream.DreamBackend.COMPLICATION_TYPE_HOME_CONTROLS;
import static com.android.settingslib.dream.DreamBackend.COMPLICATION_TYPE_TIME;
@@ -28,6 +30,7 @@ import static org.mockito.Mockito.when;
import android.content.ContentResolver;
import android.content.Context;
import android.content.res.Resources;
+import android.platform.test.annotations.EnableFlags;
import android.provider.Settings;
import org.junit.After;
@@ -173,6 +176,58 @@ public final class DreamBackendTest {
.containsExactlyElementsIn(enabledComplications);
}
+ @Test
+ @EnableFlags(FLAG_ALLOW_DREAM_WHEN_POSTURED)
+ public void testChargingAndPosturedBothEnabled() {
+ Settings.Secure.putInt(
+ mContext.getContentResolver(),
+ Settings.Secure.SCREENSAVER_ACTIVATE_ON_SLEEP,
+ 1
+ );
+ Settings.Secure.putInt(
+ mContext.getContentResolver(),
+ Settings.Secure.SCREENSAVER_ACTIVATE_ON_POSTURED,
+ 1
+ );
+
+ assertThat(mBackend.getWhenToDreamSetting()).isEqualTo(DreamBackend.WHILE_CHARGING);
+ }
+
+ @Test
+ public void testChargingAndDockedBothEnabled() {
+ Settings.Secure.putInt(
+ mContext.getContentResolver(),
+ Settings.Secure.SCREENSAVER_ACTIVATE_ON_SLEEP,
+ 1
+ );
+ Settings.Secure.putInt(
+ mContext.getContentResolver(),
+ Settings.Secure.SCREENSAVER_ACTIVATE_ON_DOCK,
+ 1
+ );
+
+ assertThat(mBackend.getWhenToDreamSetting()).isEqualTo(
+ DreamBackend.WHILE_CHARGING_OR_DOCKED);
+ }
+
+ @Test
+ @EnableFlags(FLAG_ALLOW_DREAM_WHEN_POSTURED)
+ public void testPosturedAndDockedBothEnabled() {
+ Settings.Secure.putInt(
+ mContext.getContentResolver(),
+ Settings.Secure.SCREENSAVER_ACTIVATE_ON_POSTURED,
+ 1
+ );
+ Settings.Secure.putInt(
+ mContext.getContentResolver(),
+ Settings.Secure.SCREENSAVER_ACTIVATE_ON_DOCK,
+ 1
+ );
+
+ assertThat(mBackend.getWhenToDreamSetting()).isEqualTo(
+ DreamBackend.WHILE_DOCKED);
+ }
+
private void setControlsEnabledOnLockscreen(boolean enabled) {
Settings.Secure.putInt(
mContext.getContentResolver(),
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/data/repository/ZenModeRepositoryTest.kt b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/data/repository/ZenModeRepositoryTest.kt
index 388af61c6273..b364368df473 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/data/repository/ZenModeRepositoryTest.kt
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/data/repository/ZenModeRepositoryTest.kt
@@ -91,7 +91,6 @@ class ZenModeRepositoryTest {
)
}
- @EnableFlags(android.app.Flags.FLAG_MODES_API)
@Test
fun consolidatedPolicyChanges_repositoryEmits_flagsOn() {
testScope.runTest {
@@ -110,7 +109,6 @@ class ZenModeRepositoryTest {
}
}
- @EnableFlags(android.app.Flags.FLAG_MODES_API)
@Test
fun consolidatedPolicyChanges_repositoryEmitsFromExtras() {
testScope.runTest {
diff --git a/packages/SystemUI/OWNERS b/packages/SystemUI/OWNERS
index 236654deefb5..f5c0233d56b1 100644
--- a/packages/SystemUI/OWNERS
+++ b/packages/SystemUI/OWNERS
@@ -8,7 +8,6 @@ achalke@google.com
acul@google.com
adamcohen@google.com
aioana@google.com
-alexchau@google.com
alexflo@google.com
andonian@google.com
amiko@google.com
@@ -91,10 +90,8 @@ rahulbanerjee@google.com
rgl@google.com
roosa@google.com
saff@google.com
-samcackett@google.com
santie@google.com
shanh@google.com
-silvajordan@google.com
snoeberger@google.com
spdonghao@google.com
steell@google.com
@@ -106,7 +103,6 @@ thiruram@google.com
tracyzhou@google.com
tsuji@google.com
twickham@google.com
-uwaisashraf@google.com
vadimt@google.com
valiiftime@google.com
vanjan@google.com
@@ -121,3 +117,11 @@ yuandizhou@google.com
yurilin@google.com
yuzhechen@google.com
zakcohen@google.com
+
+# Overview eng team
+alexchau@google.com
+samcackett@google.com
+silvajordan@google.com
+uwaisashraf@google.com
+vinayjoglekar@google.com
+willosborn@google.com
diff --git a/packages/SystemUI/aconfig/Android.bp b/packages/SystemUI/aconfig/Android.bp
index 088ec136f24e..f5bff859269f 100644
--- a/packages/SystemUI/aconfig/Android.bp
+++ b/packages/SystemUI/aconfig/Android.bp
@@ -28,6 +28,7 @@ package {
"//frameworks/libs/systemui/tracinglib:__subpackages__",
"//frameworks/base/services/accessibility:__subpackages__",
"//frameworks/base/services/tests:__subpackages__",
+ "//packages/apps/Settings:__subpackages__",
"//platform_testing:__subpackages__",
"//vendor:__subpackages__",
"//cts:__subpackages__",
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 2c3a5eacf940..29b578ae6e48 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -67,6 +67,13 @@ flag {
}
flag {
+ name: "notifications_redesign_guts"
+ namespace: "systemui"
+ description: "Notifications Redesign: Update the look of the notification guts (that appear on long press). This includes using the new cache for app icons."
+ bug: "394822197"
+}
+
+flag {
name: "notification_row_content_binder_refactor"
namespace: "systemui"
description: "Convert the NotificationContentInflater to Kotlin and restructure it to support modern views"
@@ -967,6 +974,26 @@ flag {
}
flag {
+ name: "use_notif_inflation_thread_for_footer"
+ namespace: "systemui"
+ description: "use the @NotifInflation thread for FooterView and EmptyShadeView inflation"
+ bug: "375320642"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
+ name: "use_notif_inflation_thread_for_row"
+ namespace: "systemui"
+ description: "use the @NotifInflation thread for ExpandableNotificationRow inflation"
+ bug: "375320642"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "notify_power_manager_user_activity_background"
namespace: "systemui"
description: "Decide whether to notify the user activity to power manager in the background thread."
@@ -1970,6 +1997,16 @@ flag {
}
flag {
+ name: "hardware_color_styles"
+ namespace: "systemui"
+ description: "Enables loading initial colors based ion hardware color"
+ bug: "347286986"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "shade_launch_accessibility"
namespace: "systemui"
description: "Intercept accessibility focus events for the Shade during launch animations to avoid stray TalkBack events."
@@ -1980,6 +2017,16 @@ flag {
}
flag {
+ name: "expand_collapse_privacy_dialog"
+ namespace: "systemui"
+ description: "Add expand and collapse actions to accessibility, to allow announcement in TalkBack when state changes."
+ bug: "380161221"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "show_locked_by_your_watch_keyguard_indicator"
namespace: "systemui"
description: "Show a Locked by your watch indicator on the keyguard when the device is locked by the watch."
@@ -1999,3 +2046,23 @@ flag {
description: "Enables the clock fidget animation"
bug: "364664389"
}
+
+flag {
+ name: "notifications_launch_radius"
+ namespace: "systemui"
+ description: "Fixes a discrepancy in corner radius between expanding notification and opening window during launch animations."
+ bug: "396054791"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
+ name: "skip_hide_sensitive_notif_animation"
+ namespace: "systemui"
+ description: "Skip hide sensitive notification animation when the showing layout is not changed."
+ bug: "390624334"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewTransitionAnimatorController.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewTransitionAnimatorController.kt
index 65cd3c79cd16..444389fb26ea 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewTransitionAnimatorController.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewTransitionAnimatorController.kt
@@ -36,6 +36,7 @@ import android.view.ViewGroupOverlay
import android.widget.FrameLayout
import com.android.internal.jank.Cuj.CujType
import com.android.internal.jank.InteractionJankMonitor
+import com.android.systemui.Flags
import java.util.LinkedList
import kotlin.math.min
import kotlin.math.roundToInt
@@ -58,7 +59,7 @@ open class GhostedViewTransitionAnimatorController
@JvmOverloads
constructor(
/** The view that will be ghosted and from which the background will be extracted. */
- private val ghostedView: View,
+ transitioningView: View,
/** The [CujType] associated to this launch animation. */
private val launchCujType: Int? = null,
@@ -75,11 +76,24 @@ constructor(
private val isEphemeral: Boolean = false,
private var interactionJankMonitor: InteractionJankMonitor =
InteractionJankMonitor.getInstance(),
+
+ /** [ViewTransitionRegistry] to store the mapping of transitioning view and its token */
+ private val transitionRegistry: IViewTransitionRegistry? =
+ if (Flags.decoupleViewControllerInAnimlib()) {
+ ViewTransitionRegistry.instance
+ } else {
+ null
+ }
) : ActivityTransitionAnimator.Controller {
override val isLaunching: Boolean = true
/** The container to which we will add the ghost view and expanding background. */
- override var transitionContainer = ghostedView.rootView as ViewGroup
+ override var transitionContainer: ViewGroup
+ get() = ghostedView.rootView as ViewGroup
+ set(_) {
+ // empty, should never be set to avoid memory leak
+ }
+
private val transitionContainerOverlay: ViewGroupOverlay
get() = transitionContainer.overlay
@@ -138,9 +152,33 @@ constructor(
}
}
+ /** [ViewTransitionToken] to be used for storing transitioning view in [transitionRegistry] */
+ private val transitionToken =
+ if (Flags.decoupleViewControllerInAnimlib()) {
+ ViewTransitionToken(transitioningView::class.java)
+ } else {
+ null
+ }
+
+ /** The view that will be ghosted and from which the background will be extracted */
+ private val ghostedView: View
+ get() =
+ if (Flags.decoupleViewControllerInAnimlib()) {
+ transitionRegistry?.getView(transitionToken!!)
+ } else {
+ _ghostedView
+ }!!
+
+ private val _ghostedView =
+ if (Flags.decoupleViewControllerInAnimlib()) {
+ null
+ } else {
+ transitioningView
+ }
+
init {
// Make sure the View we launch from implements LaunchableView to avoid visibility issues.
- if (ghostedView !is LaunchableView) {
+ if (transitioningView !is LaunchableView) {
throw IllegalArgumentException(
"A GhostedViewLaunchAnimatorController was created from a View that does not " +
"implement LaunchableView. This can lead to subtle bugs where the visibility " +
@@ -148,6 +186,10 @@ constructor(
)
}
+ if (Flags.decoupleViewControllerInAnimlib()) {
+ transitionRegistry?.register(transitionToken!!, transitioningView)
+ }
+
/** Find the first view with a background in [view] and its children. */
fun findBackground(view: View): Drawable? {
if (view.background != null) {
@@ -184,6 +226,7 @@ constructor(
if (TransitionAnimator.returnAnimationsEnabled()) {
ghostedView.removeOnAttachStateChangeListener(detachListener)
}
+ transitionToken?.let { token -> transitionRegistry?.unregister(token) }
}
/**
@@ -237,7 +280,7 @@ constructor(
val insets = backgroundInsets
val boundCorrections: Rect =
if (ghostedView is LaunchableView) {
- ghostedView.getPaddingForLaunchAnimation()
+ (ghostedView as LaunchableView).getPaddingForLaunchAnimation()
} else {
Rect()
}
@@ -387,8 +430,8 @@ constructor(
if (ghostedView is LaunchableView) {
// Restore the ghosted view visibility.
- ghostedView.setShouldBlockVisibilityChanges(false)
- ghostedView.onActivityLaunchAnimationEnd()
+ (ghostedView as LaunchableView).setShouldBlockVisibilityChanges(false)
+ (ghostedView as LaunchableView).onActivityLaunchAnimationEnd()
} else {
// Make the ghosted view visible. We ensure that the view is considered VISIBLE by
// accessibility by first making it INVISIBLE then VISIBLE (see b/204944038#comment17
@@ -398,7 +441,7 @@ constructor(
ghostedView.invalidate()
}
- if (isEphemeral) {
+ if (isEphemeral || Flags.decoupleViewControllerInAnimlib()) {
onDispose()
}
}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/IViewTransitionRegistry.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/IViewTransitionRegistry.kt
new file mode 100644
index 000000000000..af3ca87bf788
--- /dev/null
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/IViewTransitionRegistry.kt
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.animation
+
+import android.view.View
+
+/** Represents a Registry for holding a transitioning view mapped to a token */
+interface IViewTransitionRegistry {
+
+ /**
+ * Registers the transitioning [view] mapped to a [token]
+ *
+ * @param token The token corresponding to the transitioning view
+ * @param view The view undergoing transition
+ */
+ fun register(token: ViewTransitionToken, view: View)
+
+ /**
+ * Unregisters the transitioned view from its corresponding [token]
+ *
+ * @param token The token corresponding to the transitioning view
+ */
+ fun unregister(token: ViewTransitionToken)
+
+ /**
+ * Extracts a transitioning view from registry using its corresponding [token]
+ *
+ * @param token The token corresponding to the transitioning view
+ */
+ fun getView(token: ViewTransitionToken): View?
+
+ /**
+ * Return token mapped to the [view], if it is present in the registry
+ *
+ * @param view the transitioning view whose token we are requesting
+ * @return token associated with the [view] if present, else null
+ */
+ fun getViewToken(view: View): ViewTransitionToken?
+
+ /** Event call to run on registry update (on both [register] and [unregister]) */
+ fun onRegistryUpdate()
+}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt
index b9f9bc7e2daa..5b073e49192a 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt
@@ -27,9 +27,8 @@ import android.graphics.fonts.FontVariationAxis
import android.text.Layout
import android.util.Log
import android.util.LruCache
-
-private const val DEFAULT_ANIMATION_DURATION: Long = 300
-private const val TYPEFACE_CACHE_MAX_ENTRIES = 5
+import androidx.annotation.VisibleForTesting
+import com.android.app.animation.Interpolators
typealias GlyphCallback = (TextAnimator.PositionedGlyph, Float) -> Unit
@@ -76,6 +75,10 @@ class TypefaceVariantCacheImpl(var baseTypeface: Typeface, override val animatio
cache.put(fvar, it)
}
}
+
+ companion object {
+ private const val TYPEFACE_CACHE_MAX_ENTRIES = 5
+ }
}
/**
@@ -108,25 +111,12 @@ class TextAnimator(
private val typefaceCache: TypefaceVariantCache,
private val invalidateCallback: () -> Unit = {},
) {
- // Following two members are for mutable for testing purposes.
- public var textInterpolator = TextInterpolator(layout, typefaceCache)
- public var animator =
- ValueAnimator.ofFloat(1f).apply {
- duration = DEFAULT_ANIMATION_DURATION
- addUpdateListener {
- textInterpolator.progress = it.animatedValue as Float
- textInterpolator.linearProgress =
- it.currentPlayTime.toFloat() / it.duration.toFloat()
- invalidateCallback()
- }
- addListener(
- object : AnimatorListenerAdapter() {
- override fun onAnimationEnd(animation: Animator) = textInterpolator.rebase()
+ @VisibleForTesting var textInterpolator = TextInterpolator(layout, typefaceCache)
+ @VisibleForTesting var createAnimator: () -> ValueAnimator = { ValueAnimator.ofFloat(1f) }
- override fun onAnimationCancel(animation: Animator) = textInterpolator.rebase()
- }
- )
- }
+ var animator: ValueAnimator? = null
+
+ val fontVariationUtils = FontVariationUtils()
sealed class PositionedGlyph {
/** Mutable X coordinate of the glyph position relative from drawing offset. */
@@ -165,8 +155,6 @@ class TextAnimator(
protected set
}
- private val fontVariationUtils = FontVariationUtils()
-
fun updateLayout(layout: Layout, textSize: Float = -1f) {
textInterpolator.layout = layout
@@ -178,9 +166,8 @@ class TextAnimator(
}
}
- fun isRunning(): Boolean {
- return animator.isRunning
- }
+ val isRunning: Boolean
+ get() = animator?.isRunning ?: false
/**
* GlyphFilter applied just before drawing to canvas for tweaking positions and text size.
@@ -237,110 +224,110 @@ class TextAnimator(
fun draw(c: Canvas) = textInterpolator.draw(c)
- /**
- * Set text style with animation.
- *
- * ```
- * By passing -1 to weight, the view preserve the current weight.
- * By passing -1 to textSize, the view preserve the current text size.
- * By passing -1 to duration, the default text animation, 1000ms, is used.
- * By passing false to animate, the text will be updated without animation.
- * ```
- *
- * @param fvar an optional text fontVariationSettings.
- * @param textSize an optional font size.
- * @param colors an optional colors array that must be the same size as numLines passed to the
- * TextInterpolator
- * @param strokeWidth an optional paint stroke width
- * @param animate an optional boolean indicating true for showing style transition as animation,
- * false for immediate style transition. True by default.
- * @param duration an optional animation duration in milliseconds. This is ignored if animate is
- * false.
- * @param interpolator an optional time interpolator. If null is passed, last set interpolator
- * will be used. This is ignored if animate is false.
- */
- fun setTextStyle(
- fvar: String? = "",
- textSize: Float = -1f,
- color: Int? = null,
- strokeWidth: Float = -1f,
- animate: Boolean = true,
- duration: Long = -1L,
- interpolator: TimeInterpolator? = null,
- delay: Long = 0,
- onAnimationEnd: Runnable? = null,
+ /** Style spec to use when rendering the font */
+ data class Style(
+ val fVar: String? = null,
+ val textSize: Float? = null,
+ val color: Int? = null,
+ val strokeWidth: Float? = null,
) {
- setTextStyleInternal(
- fvar,
- textSize,
- color,
- strokeWidth,
- animate,
- duration,
- interpolator,
- delay,
- onAnimationEnd,
- updateLayoutOnFailure = true,
- )
+ fun withUpdatedFVar(
+ fontVariationUtils: FontVariationUtils,
+ weight: Int = -1,
+ width: Int = -1,
+ opticalSize: Int = -1,
+ roundness: Int = -1,
+ ): Style {
+ return this.copy(
+ fVar =
+ fontVariationUtils.updateFontVariation(
+ weight = weight,
+ width = width,
+ opticalSize = opticalSize,
+ roundness = roundness,
+ )
+ )
+ }
}
- private fun setTextStyleInternal(
- fvar: String?,
- textSize: Float,
- color: Int?,
- strokeWidth: Float,
- animate: Boolean,
- duration: Long,
- interpolator: TimeInterpolator?,
- delay: Long,
- onAnimationEnd: Runnable?,
- updateLayoutOnFailure: Boolean,
+ /** Animation Spec for use when style changes should be animated */
+ data class Animation(
+ val animate: Boolean = true,
+ val startDelay: Long = 0,
+ val duration: Long = DEFAULT_ANIMATION_DURATION,
+ val interpolator: TimeInterpolator = Interpolators.LINEAR,
+ val onAnimationEnd: Runnable? = null,
) {
- try {
- if (animate) {
- animator.cancel()
- textInterpolator.rebase()
+ fun configureAnimator(animator: Animator) {
+ animator.startDelay = startDelay
+ animator.duration = duration
+ animator.interpolator = interpolator
+ if (onAnimationEnd != null) {
+ animator.addListener(
+ object : AnimatorListenerAdapter() {
+ override fun onAnimationEnd(animation: Animator) {
+ onAnimationEnd.run()
+ }
+ }
+ )
}
+ }
- if (textSize >= 0) {
- textInterpolator.targetPaint.textSize = textSize
- }
- if (!fvar.isNullOrBlank()) {
- textInterpolator.targetPaint.typeface = typefaceCache.getTypefaceForVariant(fvar)
- }
- if (color != null) {
- textInterpolator.targetPaint.color = color
- }
- if (strokeWidth >= 0F) {
- textInterpolator.targetPaint.strokeWidth = strokeWidth
+ companion object {
+ val DISABLED = Animation(animate = false)
+ }
+ }
+
+ /** Sets the text style, optionally with animation */
+ fun setTextStyle(style: Style, animation: Animation = Animation.DISABLED) {
+ animator?.cancel()
+ setTextStyleInternal(style, rebase = animation.animate)
+
+ if (animation.animate) {
+ animator = buildAnimator(animation).apply { start() }
+ } else {
+ textInterpolator.progress = 1f
+ textInterpolator.rebase()
+ invalidateCallback()
+ }
+ }
+
+ /** Builds a ValueAnimator from the specified animation parameters */
+ private fun buildAnimator(animation: Animation): ValueAnimator {
+ return createAnimator().apply {
+ duration = DEFAULT_ANIMATION_DURATION
+ animation.configureAnimator(this)
+
+ addUpdateListener {
+ textInterpolator.progress = it.animatedValue as Float
+ textInterpolator.linearProgress = it.currentPlayTime / it.duration.toFloat()
+ invalidateCallback()
}
- textInterpolator.onTargetPaintModified()
- if (animate) {
- animator.startDelay = delay
- animator.duration = if (duration == -1L) DEFAULT_ANIMATION_DURATION else duration
- interpolator?.let { animator.interpolator = it }
- if (onAnimationEnd != null) {
- animator.addListener(
- object : AnimatorListenerAdapter() {
- override fun onAnimationEnd(animation: Animator) {
- onAnimationEnd.run()
- animator.removeListener(this)
- }
-
- override fun onAnimationCancel(animation: Animator) {
- animator.removeListener(this)
- }
- }
- )
+ addListener(
+ object : AnimatorListenerAdapter() {
+ override fun onAnimationEnd(animator: Animator) = textInterpolator.rebase()
+
+ override fun onAnimationCancel(animator: Animator) = textInterpolator.rebase()
}
- animator.start()
- } else {
- // No animation is requested, thus set base and target state to the same state.
- textInterpolator.progress = 1f
- textInterpolator.rebase()
- invalidateCallback()
+ )
+ }
+ }
+
+ private fun setTextStyleInternal(
+ style: Style,
+ rebase: Boolean,
+ updateLayoutOnFailure: Boolean = true,
+ ) {
+ try {
+ if (rebase) textInterpolator.rebase()
+ style.color?.let { textInterpolator.targetPaint.color = it }
+ style.textSize?.let { textInterpolator.targetPaint.textSize = it }
+ style.strokeWidth?.let { textInterpolator.targetPaint.strokeWidth = it }
+ style.fVar?.let {
+ textInterpolator.targetPaint.typeface = typefaceCache.getTypefaceForVariant(it)
}
+ textInterpolator.onTargetPaintModified()
} catch (ex: IllegalArgumentException) {
if (updateLayoutOnFailure) {
Log.e(
@@ -351,81 +338,15 @@ class TextAnimator(
)
updateLayout(textInterpolator.layout)
- setTextStyleInternal(
- fvar,
- textSize,
- color,
- strokeWidth,
- animate,
- duration,
- interpolator,
- delay,
- onAnimationEnd,
- updateLayoutOnFailure = false,
- )
+ setTextStyleInternal(style, rebase, updateLayoutOnFailure = false)
} else {
throw ex
}
}
}
- /**
- * Set text style with animation. Similar as
- *
- * ```
- * fun setTextStyle(
- * fvar: String? = "",
- * textSize: Float = -1f,
- * color: Int? = null,
- * strokeWidth: Float = -1f,
- * animate: Boolean = true,
- * duration: Long = -1L,
- * interpolator: TimeInterpolator? = null,
- * delay: Long = 0,
- * onAnimationEnd: Runnable? = null
- * )
- * ```
- *
- * @param weight an optional style value for `wght` in fontVariationSettings.
- * @param width an optional style value for `wdth` in fontVariationSettings.
- * @param opticalSize an optional style value for `opsz` in fontVariationSettings.
- * @param roundness an optional style value for `ROND` in fontVariationSettings.
- */
- fun setTextStyle(
- weight: Int = -1,
- width: Int = -1,
- opticalSize: Int = -1,
- roundness: Int = -1,
- textSize: Float = -1f,
- color: Int? = null,
- strokeWidth: Float = -1f,
- animate: Boolean = true,
- duration: Long = -1L,
- interpolator: TimeInterpolator? = null,
- delay: Long = 0,
- onAnimationEnd: Runnable? = null,
- ) {
- setTextStyleInternal(
- fvar =
- fontVariationUtils.updateFontVariation(
- weight = weight,
- width = width,
- opticalSize = opticalSize,
- roundness = roundness,
- ),
- textSize = textSize,
- color = color,
- strokeWidth = strokeWidth,
- animate = animate,
- duration = duration,
- interpolator = interpolator,
- delay = delay,
- onAnimationEnd = onAnimationEnd,
- updateLayoutOnFailure = true,
- )
- }
-
companion object {
private val TAG = TextAnimator::class.simpleName!!
+ const val DEFAULT_ANIMATION_DURATION = 300L
}
}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ViewTransitionRegistry.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ViewTransitionRegistry.kt
index 58c2a1c98ec4..86c7f76c6bee 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/ViewTransitionRegistry.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ViewTransitionRegistry.kt
@@ -24,14 +24,14 @@ import java.lang.ref.WeakReference
* A registry to temporarily store the view being transitioned into a Dialog (using
* [DialogTransitionAnimator]) or an Activity (using [ActivityTransitionAnimator])
*/
-class ViewTransitionRegistry {
+class ViewTransitionRegistry : IViewTransitionRegistry {
/**
* A map of a unique token to a WeakReference of the View being transitioned. WeakReference
* ensures that Views are garbage collected whenever they become eligible and avoid any
* memory leaks
*/
- private val registry by lazy { mutableMapOf<ViewTransitionToken, WeakReference<View>>() }
+ private val registry by lazy { mutableMapOf<ViewTransitionToken, WeakReference<View>>() }
/**
* A [View.OnAttachStateChangeListener] to be attached to all views stored in the registry to
@@ -45,8 +45,7 @@ class ViewTransitionRegistry {
}
override fun onViewDetachedFromWindow(view: View) {
- (view.getTag(R.id.tag_view_transition_token)
- as? ViewTransitionToken)?.let { token -> unregister(token) }
+ getViewToken(view)?.let { token -> unregister(token) }
}
}
}
@@ -57,12 +56,12 @@ class ViewTransitionRegistry {
* @param token unique token associated with the transitioning view
* @param view view undergoing transitions
*/
- fun register(token: ViewTransitionToken, view: View) {
+ override fun register(token: ViewTransitionToken, view: View) {
// token embedded as a view tag enables to use a single listener for all views
view.setTag(R.id.tag_view_transition_token, token)
view.addOnAttachStateChangeListener(listener)
registry[token] = WeakReference(view)
- emitCountForTrace()
+ onRegistryUpdate()
}
/**
@@ -70,30 +69,51 @@ class ViewTransitionRegistry {
*
* @param token unique token associated with the transitioning view
*/
- fun unregister(token: ViewTransitionToken) {
+ override fun unregister(token: ViewTransitionToken) {
registry.remove(token)?.let {
it.get()?.let { view ->
view.removeOnAttachStateChangeListener(listener)
view.setTag(R.id.tag_view_transition_token, null)
}
it.clear()
+ onRegistryUpdate()
}
- emitCountForTrace()
}
/**
* Access a view from registry using unique "token" associated with it
* WARNING - this returns a StrongReference to the View stored in the registry
*/
- fun getView(token: ViewTransitionToken): View? {
+ override fun getView(token: ViewTransitionToken): View? {
return registry[token]?.get()
}
/**
+ * Return token mapped to the [view], if it is present in the registry
+ *
+ * @param view the transitioning view whose token we are requesting
+ * @return token associated with the [view] if present, else null
+ */
+ override fun getViewToken(view: View): ViewTransitionToken? {
+ return (view.getTag(R.id.tag_view_transition_token) as? ViewTransitionToken)?.let { token ->
+ getView(token)?.let { token }
+ }
+ }
+
+ /** Event call to run on registry update (on both [register] and [unregister]) */
+ override fun onRegistryUpdate() {
+ emitCountForTrace()
+ }
+
+ /**
* Utility function to emit number of non-null views in the registry whenever the registry is
* updated (via [register] or [unregister])
*/
private fun emitCountForTrace() {
Trace.setCounter("transition_registry_view_count", registry.count().toLong())
}
+
+ companion object {
+ val instance by lazy(LazyThreadSafetyMode.SYNCHRONIZED) { ViewTransitionRegistry() }
+ }
}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ViewTransitionToken.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ViewTransitionToken.kt
index c211a8ed1de2..e011df01504f 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/ViewTransitionToken.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ViewTransitionToken.kt
@@ -16,17 +16,19 @@
package com.android.systemui.animation
+import java.util.UUID
+
/**
* A token uniquely mapped to a View in [ViewTransitionRegistry]. This token is guaranteed to be
* unique as timestamp is appended to the token string
*
- * @constructor creates an instance of [ViewTransitionToken] with token as "timestamp" or
- * "ClassName_timestamp"
+ * @constructor creates an instance of [ViewTransitionToken] with token as "UUID" or
+ * "ClassName_UUID"
*
* @property token String value of a unique token
*/
@JvmInline
value class ViewTransitionToken private constructor(val token: String) {
- constructor() : this(token = System.currentTimeMillis().toString())
- constructor(clazz: Class<*>) : this(token = clazz.simpleName + "_${System.currentTimeMillis()}")
+ constructor() : this(token = UUID.randomUUID().toString())
+ constructor(clazz: Class<*>) : this(token = clazz.simpleName + "_${UUID.randomUUID()}")
}
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/Expandable.kt b/packages/SystemUI/compose/core/src/com/android/compose/animation/Expandable.kt
index ae75e6c089ca..873991923e51 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/animation/Expandable.kt
+++ b/packages/SystemUI/compose/core/src/com/android/compose/animation/Expandable.kt
@@ -31,15 +31,13 @@ import androidx.compose.foundation.layout.defaultMinSize
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.requiredSize
import androidx.compose.foundation.shape.RoundedCornerShape
-import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.contentColorFor
import androidx.compose.material3.minimumInteractiveComponentSize
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.DisposableEffect
-import androidx.compose.runtime.State
-import androidx.compose.runtime.derivedStateOf
+import androidx.compose.runtime.Stable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.movableContentOf
import androidx.compose.runtime.mutableStateOf
@@ -63,9 +61,17 @@ import androidx.compose.ui.graphics.drawOutline
import androidx.compose.ui.graphics.drawscope.ContentDrawScope
import androidx.compose.ui.graphics.drawscope.Stroke
import androidx.compose.ui.graphics.drawscope.scale
+import androidx.compose.ui.graphics.drawscope.translate
+import androidx.compose.ui.graphics.layer.GraphicsLayer
+import androidx.compose.ui.graphics.layer.drawLayer
+import androidx.compose.ui.graphics.rememberGraphicsLayer
import androidx.compose.ui.layout.boundsInRoot
import androidx.compose.ui.layout.findRootCoordinates
+import androidx.compose.ui.layout.layout
import androidx.compose.ui.layout.onGloballyPositioned
+import androidx.compose.ui.layout.onPlaced
+import androidx.compose.ui.node.DrawModifierNode
+import androidx.compose.ui.node.ModifierNodeElement
import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.Density
@@ -76,6 +82,8 @@ import androidx.lifecycle.setViewTreeLifecycleOwner
import androidx.lifecycle.setViewTreeViewModelStoreOwner
import androidx.savedstate.findViewTreeSavedStateRegistryOwner
import androidx.savedstate.setViewTreeSavedStateRegistryOwner
+import com.android.compose.modifiers.thenIf
+import com.android.compose.ui.graphics.FullScreenComposeViewInOverlay
import com.android.systemui.animation.Expandable
import com.android.systemui.animation.TransitionAnimator
import kotlin.math.max
@@ -123,6 +131,9 @@ fun Expandable(
borderStroke: BorderStroke? = null,
onClick: ((Expandable) -> Unit)? = null,
interactionSource: MutableInteractionSource? = null,
+ // TODO(b/285250939): Default this to true then remove once the Compose QS expandables have
+ // proven that the new implementation is robust.
+ useModifierBasedImplementation: Boolean = false,
content: @Composable (Expandable) -> Unit,
) {
Expandable(
@@ -130,6 +141,7 @@ fun Expandable(
modifier,
onClick,
interactionSource,
+ useModifierBasedImplementation,
content,
)
}
@@ -158,16 +170,26 @@ fun Expandable(
* @sample com.android.systemui.compose.gallery.ActivityLaunchScreen
* @sample com.android.systemui.compose.gallery.DialogLaunchScreen
*/
-@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun Expandable(
controller: ExpandableController,
modifier: Modifier = Modifier,
onClick: ((Expandable) -> Unit)? = null,
interactionSource: MutableInteractionSource? = null,
+ // TODO(b/285250939): Default this to true then remove once the Compose QS expandables have
+ // proven that the new implementation is robust.
+ useModifierBasedImplementation: Boolean = false,
content: @Composable (Expandable) -> Unit,
) {
val controller = controller as ExpandableControllerImpl
+
+ if (useModifierBasedImplementation) {
+ Box(modifier.expandable(controller, onClick, interactionSource)) {
+ WrappedContent(controller.expandable, controller.contentColor, content)
+ }
+ return
+ }
+
val color = controller.color
val contentColor = controller.contentColor
val shape = controller.shape
@@ -175,21 +197,7 @@ fun Expandable(
val wrappedContent =
remember(content) {
movableContentOf { expandable: Expandable ->
- CompositionLocalProvider(LocalContentColor provides contentColor) {
- // We make sure that the content itself (wrapped by the background) is at least
- // 40.dp, which is the same as the M3 buttons. This applies even if onClick is
- // null, to make it easier to write expandables that are sometimes clickable and
- // sometimes not. There shouldn't be any Expandable smaller than 40dp because if
- // the expandable is not clickable directly, then something in its content
- // should be (and with a size >= 40dp).
- val minSize = 40.dp
- Box(
- Modifier.defaultMinSize(minWidth = minSize, minHeight = minSize),
- contentAlignment = Alignment.Center,
- ) {
- content(expandable)
- }
- }
+ WrappedContent(expandable, contentColor, content)
}
}
@@ -209,11 +217,7 @@ fun Expandable(
// Make sure we don't read animatorState directly here to avoid recomposition every time the
// state changes (i.e. every frame of the animation).
- val isAnimating by remember {
- derivedStateOf {
- controller.animatorState.value != null && controller.overlay.value != null
- }
- }
+ val isAnimating = controller.isAnimating
// If this expandable is expanded when it's being directly clicked on, let's ensure that it has
// the minimum interactive size followed by all M3 components (48.dp).
@@ -237,58 +241,36 @@ fun Expandable(
// animating.
AnimatedContentInOverlay(
color,
- controller.boundsInComposeViewRoot.value.size,
- controller.animatorState,
- controller.overlay.value
+ controller.boundsInComposeViewRoot.size,
+ controller.overlay
?: error("AnimatedContentInOverlay shouldn't be composed with null overlay."),
controller,
wrappedContent,
controller.composeViewRoot,
- { controller.currentComposeViewInOverlay.value = it },
+ { controller.currentComposeViewInOverlay = it },
controller.density,
)
}
- controller.isDialogShowing.value -> {
+ controller.isDialogShowing -> {
Box(
modifier
.updateExpandableSize()
.then(minInteractiveSizeModifier)
.drawWithContent { /* Don't draw anything when the dialog is shown. */ }
- .onGloballyPositioned {
- controller.boundsInComposeViewRoot.value = it.boundsInRoot()
- }
+ .onGloballyPositioned { controller.boundsInComposeViewRoot = it.boundsInRoot() }
) {
wrappedContent(controller.expandable)
}
}
else -> {
- val clickModifier =
- if (onClick != null) {
- if (interactionSource != null) {
- // If the caller provided an interaction source, then that means that they
- // will draw the click indication themselves.
- Modifier.clickable(interactionSource, indication = null) {
- onClick(controller.expandable)
- }
- } else {
- // If no interaction source is provided, we draw the default indication (a
- // ripple) and make sure it's clipped by the expandable shape.
- Modifier.clip(shape).clickable { onClick(controller.expandable) }
- }
- } else {
- Modifier
- }
-
Box(
modifier
.updateExpandableSize()
.then(minInteractiveSizeModifier)
- .then(clickModifier)
+ .then(clickModifier(controller, onClick, interactionSource))
.background(color, shape)
.border(controller)
- .onGloballyPositioned {
- controller.boundsInComposeViewRoot.value = it.boundsInRoot()
- }
+ .onGloballyPositioned { controller.boundsInComposeViewRoot = it.boundsInRoot() }
) {
wrappedContent(controller.expandable)
}
@@ -296,12 +278,182 @@ fun Expandable(
}
}
+@Composable
+private fun WrappedContent(
+ expandable: Expandable,
+ contentColor: Color,
+ content: @Composable (Expandable) -> Unit,
+) {
+ CompositionLocalProvider(LocalContentColor provides contentColor) {
+ // We make sure that the content itself (wrapped by the background) is at least 40.dp, which
+ // is the same as the M3 buttons. This applies even if onClick is null, to make it easier to
+ // write expandables that are sometimes clickable and sometimes not. There shouldn't be any
+ // Expandable smaller than 40dp because if the expandable is not clickable directly, then
+ // something in its content should be (and with a size >= 40dp).
+ val minSize = 40.dp
+ Box(
+ Modifier.defaultMinSize(minWidth = minSize, minHeight = minSize),
+ contentAlignment = Alignment.Center,
+ ) {
+ content(expandable)
+ }
+ }
+}
+
+@Composable
+@Stable
+private fun Modifier.expandable(
+ controller: ExpandableController,
+ onClick: ((Expandable) -> Unit)? = null,
+ interactionSource: MutableInteractionSource? = null,
+): Modifier {
+ val controller = controller as ExpandableControllerImpl
+
+ val isAnimating = controller.isAnimating
+ val drawInOverlayModifier =
+ if (isAnimating) {
+ val graphicsLayer = rememberGraphicsLayer()
+
+ FullScreenComposeViewInOverlay { view ->
+ Modifier.then(DrawExpandableInOverlayElement(view, controller, graphicsLayer))
+ }
+
+ Modifier.drawWithContent { graphicsLayer.record { this@drawWithContent.drawContent() } }
+ } else {
+ null
+ }
+
+ return this.thenIf(onClick != null) { Modifier.minimumInteractiveComponentSize() }
+ .thenIf(!isAnimating) {
+ Modifier.border(controller)
+ .then(clickModifier(controller, onClick, interactionSource))
+ .background(controller.color, controller.shape)
+ }
+ .thenIf(drawInOverlayModifier != null) { drawInOverlayModifier!! }
+ .onPlaced { controller.boundsInComposeViewRoot = it.boundsInRoot() }
+ .thenIf(!isAnimating && controller.isDialogShowing) {
+ Modifier.layout { measurable, constraints ->
+ measurable.measure(constraints).run {
+ layout(width, height) { /* Do not place/draw. */ }
+ }
+ }
+ }
+}
+
+private data class DrawExpandableInOverlayElement(
+ private val overlayComposeView: ComposeView,
+ private val controller: ExpandableControllerImpl,
+ private val contentGraphicsLayer: GraphicsLayer,
+) : ModifierNodeElement<DrawExpandableInOverlayNode>() {
+ override fun create(): DrawExpandableInOverlayNode {
+ return DrawExpandableInOverlayNode(overlayComposeView, controller, contentGraphicsLayer)
+ }
+
+ override fun update(node: DrawExpandableInOverlayNode) {
+ node.update(overlayComposeView, controller, contentGraphicsLayer)
+ }
+}
+
+private class DrawExpandableInOverlayNode(
+ composeView: ComposeView,
+ controller: ExpandableControllerImpl,
+ private var contentGraphicsLayer: GraphicsLayer,
+) : Modifier.Node(), DrawModifierNode {
+ private var controller = controller
+ set(value) {
+ resetCurrentNodeInOverlay()
+ field = value
+ setCurrentNodeInOverlay()
+ }
+
+ private var composeViewLocationOnScreen = composeView.locationOnScreen
+
+ fun update(
+ composeView: ComposeView,
+ controller: ExpandableControllerImpl,
+ contentGraphicsLayer: GraphicsLayer,
+ ) {
+ this.controller = controller
+ this.composeViewLocationOnScreen = composeView.locationOnScreen
+ this.contentGraphicsLayer = contentGraphicsLayer
+ }
+
+ override fun onAttach() {
+ setCurrentNodeInOverlay()
+ }
+
+ override fun onDetach() {
+ resetCurrentNodeInOverlay()
+ }
+
+ private fun setCurrentNodeInOverlay() {
+ controller.currentNodeInOverlay = this
+ }
+
+ private fun resetCurrentNodeInOverlay() {
+ if (controller.currentNodeInOverlay == this) {
+ controller.currentNodeInOverlay = null
+ }
+ }
+
+ override fun ContentDrawScope.draw() {
+ val state = controller.animatorState ?: return
+ val topOffset = state.top.toFloat() - composeViewLocationOnScreen[1]
+ val leftOffset = state.left.toFloat() - composeViewLocationOnScreen[0]
+
+ translate(top = topOffset, left = leftOffset) {
+ // Background.
+ this@draw.drawBackground(
+ state,
+ controller.color,
+ controller.borderStroke,
+ size = Size(state.width.toFloat(), state.height.toFloat()),
+ )
+
+ // Content, scaled & centered w.r.t. the animated state bounds.
+ val contentSize = controller.boundsInComposeViewRoot.size
+ val contentWidth = contentSize.width
+ val contentHeight = contentSize.height
+ val scale = min(state.width / contentWidth, state.height / contentHeight)
+ scale(scale, pivot = Offset(state.width / 2f, state.height / 2f)) {
+ translate(
+ left = (state.width - contentWidth) / 2f,
+ top = (state.height - contentHeight) / 2f,
+ ) {
+ drawLayer(contentGraphicsLayer)
+ }
+ }
+ }
+ }
+}
+
+private fun clickModifier(
+ controller: ExpandableControllerImpl,
+ onClick: ((Expandable) -> Unit)?,
+ interactionSource: MutableInteractionSource?,
+): Modifier {
+ if (onClick == null) {
+ return Modifier
+ }
+
+ if (interactionSource != null) {
+ // If the caller provided an interaction source, then that means that they will draw the
+ // click indication themselves.
+ return Modifier.clickable(interactionSource, indication = null) {
+ onClick(controller.expandable)
+ }
+ }
+
+ // If no interaction source is provided, we draw the default indication (a ripple) and make sure
+ // it's clipped by the expandable shape.
+ return Modifier.clip(controller.shape).clickable { onClick(controller.expandable) }
+}
+
/** Draw [content] in [overlay] while respecting its screen position given by [animatorState]. */
@Composable
private fun AnimatedContentInOverlay(
color: Color,
sizeInOriginalLayout: Size,
- animatorState: State<TransitionAnimator.State?>,
overlay: ViewGroupOverlay,
controller: ExpandableControllerImpl,
content: @Composable (Expandable) -> Unit,
@@ -324,7 +476,7 @@ private fun AnimatedContentInOverlay(
// so that its content is laid out exactly the same way.
.requiredSize(with(density) { sizeInOriginalLayout.toDpSize() })
.drawWithContent {
- val animatorState = animatorState.value ?: return@drawWithContent
+ val animatorState = controller.animatorState ?: return@drawWithContent
// Scale the content with the background while keeping its aspect ratio.
val widthRatio =
@@ -348,7 +500,8 @@ private fun AnimatedContentInOverlay(
setContent {
Box(
Modifier.fillMaxSize().drawWithContent {
- val animatorState = animatorState.value ?: return@drawWithContent
+ val animatorState =
+ controller.animatorState ?: return@drawWithContent
if (!animatorState.visible) {
return@drawWithContent
}
@@ -385,7 +538,7 @@ private fun AnimatedContentInOverlay(
overlay.add(composeViewInOverlay)
val startState =
- animatorState.value
+ controller.animatorState
?: throw IllegalStateException(
"AnimatedContentInOverlay shouldn't be composed with null animatorState."
)
@@ -444,6 +597,7 @@ private fun ContentDrawScope.drawBackground(
animatorState: TransitionAnimator.State,
color: Color,
border: BorderStroke?,
+ size: Size = this.size,
) {
val topRadius = animatorState.topCornerRadius
val bottomRadius = animatorState.bottomCornerRadius
@@ -452,7 +606,7 @@ private fun ContentDrawScope.drawBackground(
val cornerRadius = CornerRadius(topRadius)
// Draw the background.
- drawRoundRect(color, cornerRadius = cornerRadius)
+ drawRoundRect(color, cornerRadius = cornerRadius, size = size)
// Draw the border.
if (border != null) {
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/ExpandableController.kt b/packages/SystemUI/compose/core/src/com/android/compose/animation/ExpandableController.kt
index c5d2802c8941..a03c89626cd7 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/animation/ExpandableController.kt
+++ b/packages/SystemUI/compose/core/src/com/android/compose/animation/ExpandableController.kt
@@ -25,16 +25,21 @@ import androidx.compose.foundation.BorderStroke
import androidx.compose.material3.contentColorFor
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
-import androidx.compose.runtime.MutableState
+import androidx.compose.runtime.Stable
import androidx.compose.runtime.State
+import androidx.compose.runtime.derivedStateOf
+import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Rect
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Outline
import androidx.compose.ui.graphics.Shape
+import androidx.compose.ui.node.DrawModifierNode
+import androidx.compose.ui.node.invalidateDraw
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.platform.LocalView
@@ -49,10 +54,14 @@ import com.android.systemui.animation.TransitionAnimator
import kotlin.math.roundToInt
/** A controller that can control animated launches from an [Expandable]. */
+@Stable
interface ExpandableController {
/** The [Expandable] controlled by this controller. */
val expandable: Expandable
+ /** Whether this controller is currently animating a launch. */
+ val isAnimating: Boolean
+
/** Called when the [Expandable] stop being included in the composition. */
fun onDispose()
}
@@ -73,24 +82,9 @@ fun rememberExpandableController(
val density = LocalDensity.current
val layoutDirection = LocalLayoutDirection.current
- // The current animation state, if we are currently animating a dialog or activity.
- val animatorState = remember { mutableStateOf<TransitionAnimator.State?>(null) }
-
- // Whether a dialog controlled by this ExpandableController is currently showing.
- val isDialogShowing = remember { mutableStateOf(false) }
-
- // The overlay in which we should animate the launch.
- val overlay = remember { mutableStateOf<ViewGroupOverlay?>(null) }
-
- // The current [ComposeView] being animated in the [overlay], if any.
- val currentComposeViewInOverlay = remember { mutableStateOf<View?>(null) }
-
- // The bounds in [composeViewRoot] of the expandable controlled by this controller.
- val boundsInComposeViewRoot = remember { mutableStateOf(Rect.Zero) }
-
// Whether this composable is still composed. We only do the dialog exit animation if this is
// true.
- val isComposed = remember { mutableStateOf(true) }
+ var isComposed by remember { mutableStateOf(true) }
val controller =
remember(
@@ -109,19 +103,14 @@ fun rememberExpandableController(
borderStroke,
composeViewRoot,
density,
- animatorState,
- isDialogShowing,
- overlay,
- currentComposeViewInOverlay,
- boundsInComposeViewRoot,
layoutDirection,
- isComposed,
+ { isComposed },
)
}
DisposableEffect(Unit) {
onDispose {
- isComposed.value = false
+ isComposed = false
if (TransitionAnimator.returnAnimationsEnabled()) {
controller.onDispose()
}
@@ -138,17 +127,35 @@ internal class ExpandableControllerImpl(
internal val borderStroke: BorderStroke?,
internal val composeViewRoot: View,
internal val density: Density,
- internal val animatorState: MutableState<TransitionAnimator.State?>,
- internal val isDialogShowing: MutableState<Boolean>,
- internal val overlay: MutableState<ViewGroupOverlay?>,
- internal val currentComposeViewInOverlay: MutableState<View?>,
- internal val boundsInComposeViewRoot: MutableState<Rect>,
private val layoutDirection: LayoutDirection,
- private val isComposed: State<Boolean>,
+ private val isComposed: () -> Boolean,
) : ExpandableController {
+ /** The current animation state, if we are currently animating a dialog or activity. */
+ var animatorState by mutableStateOf<TransitionAnimator.State?>(null)
+ private set
+
+ /** Whether a dialog controlled by this ExpandableController is currently showing. */
+ var isDialogShowing by mutableStateOf(false)
+ private set
+
+ /** The overlay in which we should animate the launch. */
+ var overlay by mutableStateOf<ViewGroupOverlay?>(null)
+ private set
+
+ /** The current [ComposeView] being animated in the [overlay], if any. */
+ var currentComposeViewInOverlay by mutableStateOf<View?>(null)
+
+ /** The bounds in [composeViewRoot] of the expandable controlled by this controller. */
+ var boundsInComposeViewRoot by mutableStateOf(Rect.Zero)
+
/** The [ActivityTransitionAnimator.Controller] to be cleaned up [onDispose]. */
private var activityControllerForDisposal: ActivityTransitionAnimator.Controller? = null
+ /**
+ * The current [DrawModifierNode] in the overlay, drawing the expandable during a transition.
+ */
+ internal var currentNodeInOverlay: DrawModifierNode? = null
+
override val expandable: Expandable =
object : Expandable {
override fun activityTransitionController(
@@ -158,7 +165,7 @@ internal class ExpandableControllerImpl(
returnCujType: Int?,
isEphemeral: Boolean,
): ActivityTransitionAnimator.Controller? {
- if (!isComposed.value) {
+ if (!isComposed()) {
return null
}
@@ -174,7 +181,7 @@ internal class ExpandableControllerImpl(
override fun dialogTransitionController(
cuj: DialogCuj?
): DialogTransitionAnimator.Controller? {
- if (!isComposed.value) {
+ if (!isComposed()) {
return null
}
@@ -182,6 +189,8 @@ internal class ExpandableControllerImpl(
}
}
+ override val isAnimating: Boolean by derivedStateOf { animatorState != null && overlay != null }
+
override fun onDispose() {
activityControllerForDisposal?.onDispose()
activityControllerForDisposal = null
@@ -204,7 +213,11 @@ internal class ExpandableControllerImpl(
override val isLaunching: Boolean = true
override fun onTransitionAnimationEnd(isExpandingFullyAbove: Boolean) {
- animatorState.value = null
+ animatorState = null
+
+ // Force invalidate the drawing done in the overlay whenever the animation state
+ // changes.
+ currentNodeInOverlay?.invalidateDraw()
}
override fun onTransitionAnimationProgress(
@@ -214,7 +227,7 @@ internal class ExpandableControllerImpl(
) {
// We copy state given that it's always the same object that is mutated by
// ActivityTransitionAnimator.
- animatorState.value =
+ animatorState =
TransitionAnimator.State(
state.top,
state.bottom,
@@ -227,13 +240,15 @@ internal class ExpandableControllerImpl(
// Force measure and layout the ComposeView in the overlay whenever the animation
// state changes.
- currentComposeViewInOverlay.value?.let {
- measureAndLayoutComposeViewInOverlay(it, state)
- }
+ currentComposeViewInOverlay?.let { measureAndLayoutComposeViewInOverlay(it, state) }
+
+ // Force invalidate the drawing done in the overlay whenever the animation state
+ // changes.
+ currentNodeInOverlay?.invalidateDraw()
}
override fun createAnimatorState(): TransitionAnimator.State {
- val boundsInRoot = boundsInComposeViewRoot.value
+ val boundsInRoot = boundsInComposeViewRoot
val outline =
shape.createOutline(
Size(boundsInRoot.width, boundsInRoot.height),
@@ -285,7 +300,7 @@ internal class ExpandableControllerImpl(
private fun rootLocationOnScreen(): Offset {
composeViewRoot.getLocationOnScreen(rootLocationOnScreen)
- val boundsInRoot = boundsInComposeViewRoot.value
+ val boundsInRoot = boundsInComposeViewRoot
val x = rootLocationOnScreen[0] + boundsInRoot.left
val y = rootLocationOnScreen[1] + boundsInRoot.top
return Offset(x, y)
@@ -319,14 +334,14 @@ internal class ExpandableControllerImpl(
override fun onTransitionAnimationStart(isExpandingFullyAbove: Boolean) {
delegate.onTransitionAnimationStart(isExpandingFullyAbove)
- overlay.value = transitionContainer.overlay as ViewGroupOverlay
+ overlay = transitionContainer.overlay as ViewGroupOverlay
cujType?.let { InteractionJankMonitor.getInstance().begin(composeViewRoot, it) }
}
override fun onTransitionAnimationEnd(isExpandingFullyAbove: Boolean) {
cujType?.let { InteractionJankMonitor.getInstance().end(it) }
delegate.onTransitionAnimationEnd(isExpandingFullyAbove)
- overlay.value = null
+ overlay = null
}
}
}
@@ -339,14 +354,14 @@ internal class ExpandableControllerImpl(
override fun startDrawingInOverlayOf(viewGroup: ViewGroup) {
val newOverlay = viewGroup.overlay as ViewGroupOverlay
- if (newOverlay != overlay.value) {
- overlay.value = newOverlay
+ if (newOverlay != overlay) {
+ overlay = newOverlay
}
}
override fun stopDrawingInOverlay() {
- if (overlay.value != null) {
- overlay.value = null
+ if (overlay != null) {
+ overlay = null
}
}
@@ -357,7 +372,7 @@ internal class ExpandableControllerImpl(
delegate.onTransitionAnimationEnd(isExpandingFullyAbove)
// Make sure we don't draw this expandable when the dialog is showing.
- isDialogShowing.value = true
+ isDialogShowing = true
}
}
}
@@ -367,16 +382,17 @@ internal class ExpandableControllerImpl(
return object : TransitionAnimator.Controller by delegate {
override fun onTransitionAnimationEnd(isExpandingFullyAbove: Boolean) {
delegate.onTransitionAnimationEnd(isExpandingFullyAbove)
- isDialogShowing.value = false
+ isDialogShowing = false
}
}
}
- override fun shouldAnimateExit(): Boolean =
- isComposed.value && composeViewRoot.isAttachedToWindow && composeViewRoot.isShown
+ override fun shouldAnimateExit(): Boolean {
+ return isComposed() && composeViewRoot.isAttachedToWindow && composeViewRoot.isShown
+ }
override fun onExitAnimationCancelled() {
- isDialogShowing.value = false
+ isDialogShowing = false
}
override fun jankConfigurationBuilder(): InteractionJankMonitor.Configuration.Builder? {
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/ui/graphics/DrawInOverlay.kt b/packages/SystemUI/compose/core/src/com/android/compose/ui/graphics/DrawInOverlay.kt
index f5c3a834a8d7..089da4b932b2 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/ui/graphics/DrawInOverlay.kt
+++ b/packages/SystemUI/compose/core/src/com/android/compose/ui/graphics/DrawInOverlay.kt
@@ -42,13 +42,19 @@ import androidx.savedstate.setViewTreeSavedStateRegistryOwner
@Composable
fun Modifier.drawInOverlay(): Modifier {
val containerState = remember { ContainerState() }
+ FullScreenComposeViewInOverlay { Modifier.container(containerState) }
+ return this.drawInContainer(containerState, enabled = { true })
+}
+
+@Composable
+internal fun FullScreenComposeViewInOverlay(modifier: (ComposeView) -> Modifier = { Modifier }) {
val context = LocalContext.current
val localView = LocalView.current
val compositionContext = rememberCompositionContext()
val displayMetrics = context.resources.displayMetrics
val displaySize = IntSize(displayMetrics.widthPixels, displayMetrics.heightPixels)
- DisposableEffect(containerState, context, localView, compositionContext, displaySize) {
+ DisposableEffect(context, localView, compositionContext, displaySize) {
val overlay = localView.rootView.overlay as ViewGroupOverlay
val view =
ComposeView(context).apply {
@@ -59,7 +65,8 @@ fun Modifier.drawInOverlay(): Modifier {
setViewTreeViewModelStoreOwner(localView.findViewTreeViewModelStoreOwner())
setViewTreeSavedStateRegistryOwner(localView.findViewTreeSavedStateRegistryOwner())
- setContent { Box(Modifier.fillMaxSize().container(containerState)) }
+ val view = this
+ setContent { Box(modifier(view).fillMaxSize()) }
}
overlay.add(view)
@@ -74,6 +81,4 @@ fun Modifier.drawInOverlay(): Modifier {
onDispose { overlay.remove(view) }
}
-
- return this.drawInContainer(containerState, enabled = { true })
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt
index 2f38dc2a825e..fad8ae7e3ba2 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt
@@ -24,7 +24,6 @@ import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.ui.Modifier
-import androidx.compose.ui.platform.testTag
import com.android.compose.animation.scene.ContentScope
import com.android.compose.animation.scene.ElementKey
import com.android.compose.animation.scene.UserAction
@@ -103,8 +102,6 @@ private fun ContentScope.BouncerScene(
viewModel,
dialogFactory,
Modifier.element(Bouncer.Elements.Content)
- // TODO(b/393516240): Use the same sysuiResTag() as views instead.
- .testTag(Bouncer.Elements.Content.testTag)
.overscroll(verticalOverscrollEffect)
.sysuiResTag(Bouncer.TestTags.Root)
.fillMaxSize(),
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
index 0b17a3f71bda..9b76f15b3cd6 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
@@ -42,6 +42,7 @@ import com.android.compose.animation.scene.MutableSceneTransitionLayoutState
import com.android.compose.animation.scene.SceneKey
import com.android.compose.animation.scene.SceneTransitionLayout
import com.android.compose.animation.scene.Swipe
+import com.android.compose.animation.scene.UserActionResult
import com.android.compose.animation.scene.observableTransitionState
import com.android.compose.animation.scene.rememberMutableSceneTransitionLayoutState
import com.android.compose.animation.scene.transitions
@@ -87,10 +88,26 @@ val sceneTransitionsV2 = transitions {
spec = tween(durationMillis = TransitionDuration.TO_GLANCEABLE_HUB_DURATION_MS)
fade(AllElements)
}
+ to(CommunalScenes.Communal, key = CommunalTransitionKeys.Swipe) {
+ spec = tween(durationMillis = TransitionDuration.TO_GLANCEABLE_HUB_DURATION_MS)
+ translate(Communal.Elements.Grid, Edge.End)
+ timestampRange(startMillis = 167, endMillis = 334) { fade(AllElements) }
+ }
to(CommunalScenes.Blank) {
spec = tween(durationMillis = TO_GONE_DURATION.toInt(DurationUnit.MILLISECONDS))
fade(AllElements)
}
+ to(CommunalScenes.Blank, key = CommunalTransitionKeys.Swipe) {
+ spec = tween(durationMillis = TransitionDuration.TO_GLANCEABLE_HUB_DURATION_MS)
+ translate(Communal.Elements.Grid, Edge.End)
+ timestampRange(endMillis = 167) {
+ fade(Communal.Elements.Grid)
+ fade(Communal.Elements.IndicationArea)
+ fade(Communal.Elements.LockIcon)
+ fade(Communal.Elements.StatusBar)
+ }
+ timestampRange(startMillis = 167, endMillis = 334) { fade(Communal.Elements.Scrim) }
+ }
}
val sceneTransitions = transitions {
@@ -165,6 +182,7 @@ fun CommunalContainer(
viewModel.communalBackground.collectAsStateWithLifecycle(
initialValue = CommunalBackgroundType.ANIMATED
)
+ val swipeToHubEnabled by viewModel.swipeToHubEnabled.collectAsStateWithLifecycle()
val state: MutableSceneTransitionLayoutState =
rememberMutableSceneTransitionLayoutState(
initialScene = currentSceneKey,
@@ -200,15 +218,27 @@ fun CommunalContainer(
scene(
CommunalScenes.Blank,
userActions =
- if (viewModel.swipeToHubEnabled())
- mapOf(Swipe.Start(fromSource = Edge.End) to CommunalScenes.Communal)
- else emptyMap(),
+ if (swipeToHubEnabled) {
+ mapOf(
+ Swipe.Start(fromSource = Edge.End) to
+ UserActionResult(CommunalScenes.Communal, CommunalTransitionKeys.Swipe)
+ )
+ } else {
+ emptyMap()
+ },
) {
// This scene shows nothing only allowing for transitions to the communal scene.
Box(modifier = Modifier.fillMaxSize())
}
- scene(CommunalScenes.Communal, userActions = mapOf(Swipe.End to CommunalScenes.Blank)) {
+ scene(
+ CommunalScenes.Communal,
+ userActions =
+ mapOf(
+ Swipe.End to
+ UserActionResult(CommunalScenes.Blank, CommunalTransitionKeys.Swipe)
+ ),
+ ) {
CommunalScene(
backgroundType = backgroundType,
colors = colors,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt
index aa07370aa9cf..5e61af634bbc 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt
@@ -19,7 +19,6 @@ package com.android.systemui.keyguard.ui.composable
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
-import androidx.compose.ui.platform.testTag
import com.android.compose.animation.scene.ContentScope
import com.android.compose.animation.scene.UserAction
import com.android.compose.animation.scene.UserActionResult
@@ -56,11 +55,7 @@ constructor(
@Composable
override fun ContentScope.Content(modifier: Modifier) {
- LockscreenScene(
- lockscreenContent = lockscreenContent,
- // TODO(b/393516240): Use the same sysuiResTag() as views instead.
- modifier = modifier.testTag(key.rootElementKey.testTag),
- )
+ LockscreenScene(lockscreenContent = lockscreenContent, modifier = modifier)
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeOverlay.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeOverlay.kt
index 64f3cb13662a..297995becfb2 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeOverlay.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeOverlay.kt
@@ -23,7 +23,7 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
-import androidx.compose.ui.layout.layoutId
+import androidx.compose.ui.platform.LocalResources
import androidx.compose.ui.res.dimensionResource
import com.android.compose.animation.scene.ContentScope
import com.android.compose.animation.scene.ElementKey
@@ -34,6 +34,13 @@ import com.android.systemui.keyguard.ui.composable.blueprint.rememberBurnIn
import com.android.systemui.keyguard.ui.composable.section.DefaultClockSection
import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel
import com.android.systemui.lifecycle.rememberViewModel
+import com.android.systemui.media.controls.ui.composable.MediaCarousel
+import com.android.systemui.media.controls.ui.composable.isLandscape
+import com.android.systemui.media.controls.ui.controller.MediaCarouselController
+import com.android.systemui.media.controls.ui.view.MediaHost
+import com.android.systemui.media.controls.ui.view.MediaHostState.Companion.COLLAPSED
+import com.android.systemui.media.controls.ui.view.MediaHostState.Companion.EXPANDED
+import com.android.systemui.media.dagger.MediaModule.QUICK_QS_PANEL
import com.android.systemui.notifications.ui.viewmodel.NotificationsShadeOverlayActionsViewModel
import com.android.systemui.notifications.ui.viewmodel.NotificationsShadeOverlayContentViewModel
import com.android.systemui.res.R
@@ -42,10 +49,11 @@ import com.android.systemui.scene.shared.model.Overlays
import com.android.systemui.scene.ui.composable.Overlay
import com.android.systemui.shade.ui.composable.OverlayShade
import com.android.systemui.shade.ui.composable.OverlayShadeHeader
-import com.android.systemui.shade.ui.composable.SingleShadeMeasurePolicy
import com.android.systemui.statusbar.notification.stack.ui.view.NotificationScrollView
+import com.android.systemui.util.Utils
import dagger.Lazy
import javax.inject.Inject
+import javax.inject.Named
import kotlinx.coroutines.flow.Flow
@SysUISingleton
@@ -58,6 +66,8 @@ constructor(
private val stackScrollView: Lazy<NotificationScrollView>,
private val clockSection: DefaultClockSection,
private val keyguardClockViewModel: KeyguardClockViewModel,
+ private val mediaCarouselController: MediaCarouselController,
+ @Named(QUICK_QS_PANEL) private val mediaHost: Lazy<MediaHost>,
) : Overlay {
override val key = Overlays.NotificationsShade
@@ -84,6 +94,11 @@ constructor(
viewModel.notificationsPlaceholderViewModelFactory.create()
}
+ val usingCollapsedLandscapeMedia =
+ Utils.useCollapsedMediaInLandscape(LocalResources.current)
+ mediaHost.get().expansion =
+ if (usingCollapsedLandscapeMedia && isLandscape()) COLLAPSED else EXPANDED
+
OverlayShade(
panelElement = NotificationsShade.Elements.Panel,
alignmentOnWideScreens = Alignment.TopStart,
@@ -96,9 +111,7 @@ constructor(
}
OverlayShadeHeader(
viewModel = headerViewModel,
- modifier =
- Modifier.element(NotificationsShade.Elements.StatusBar)
- .layoutId(SingleShadeMeasurePolicy.LayoutId.ShadeHeader),
+ modifier = Modifier.element(NotificationsShade.Elements.StatusBar),
)
},
) {
@@ -116,6 +129,19 @@ constructor(
}
}
+ MediaCarousel(
+ isVisible = viewModel.showMedia,
+ mediaHost = mediaHost.get(),
+ carouselController = mediaCarouselController,
+ usingCollapsedLandscapeMedia = usingCollapsedLandscapeMedia,
+ modifier =
+ Modifier.padding(
+ top = notificationStackPadding,
+ start = notificationStackPadding,
+ end = notificationStackPadding,
+ ),
+ )
+
NotificationScrollingStack(
shadeSession = shadeSession,
stackScrollView = stackScrollView.get(),
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeOverlay.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeOverlay.kt
index afdb3cbba60e..da0b8ac7bda3 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeOverlay.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeOverlay.kt
@@ -38,7 +38,6 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.layout.boundsInWindow
-import androidx.compose.ui.layout.layoutId
import androidx.compose.ui.layout.onPlaced
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.dp
@@ -66,7 +65,6 @@ import com.android.systemui.scene.ui.composable.Overlay
import com.android.systemui.shade.ui.composable.OverlayShade
import com.android.systemui.shade.ui.composable.OverlayShadeHeader
import com.android.systemui.shade.ui.composable.QuickSettingsOverlayHeader
-import com.android.systemui.shade.ui.composable.SingleShadeMeasurePolicy
import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimBounds
import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimShape
import com.android.systemui.statusbar.notification.stack.ui.view.NotificationScrollView
@@ -108,6 +106,11 @@ constructor(
rememberViewModel("QuickSettingsShadeOverlayContainer") {
quickSettingsContainerViewModelFactory.create(supportsBrightnessMirroring = true)
}
+ val hunPlaceholderViewModel =
+ rememberViewModel("QuickSettingsShadeOverlayPlaceholder") {
+ notificationsPlaceholderViewModelFactory.create()
+ }
+
val panelCornerRadius =
with(LocalDensity.current) { OverlayShade.Dimensions.PanelCornerRadius.toPx().toInt() }
val showBrightnessMirror =
@@ -119,13 +122,6 @@ constructor(
DisposableEffect(Unit) { onDispose { contentViewModel.onPanelShapeChanged(null) } }
Box(modifier = modifier.graphicsLayer { alpha = contentAlphaFromBrightnessMirror }) {
- SnoozeableHeadsUpNotificationSpace(
- stackScrollView = notificationStackScrollView.get(),
- viewModel =
- rememberViewModel("QuickSettingsShadeOverlayPlaceholder") {
- notificationsPlaceholderViewModelFactory.create()
- },
- )
OverlayShade(
panelElement = QuickSettingsShade.Elements.Panel,
alignmentOnWideScreens = Alignment.TopEnd,
@@ -133,50 +129,34 @@ constructor(
header = {
OverlayShadeHeader(
viewModel = quickSettingsContainerViewModel.shadeHeaderViewModel,
- modifier =
- Modifier.element(QuickSettingsShade.Elements.StatusBar)
- .layoutId(SingleShadeMeasurePolicy.LayoutId.ShadeHeader),
+ modifier = Modifier.element(QuickSettingsShade.Elements.StatusBar),
)
},
) {
- ShadeBody(
+ QuickSettingsContainer(
viewModel = quickSettingsContainerViewModel,
modifier =
Modifier.onPlaced { coordinates ->
- val boundsInWindow = coordinates.boundsInWindow()
- val shadeScrimBounds =
- ShadeScrimBounds(
- left = boundsInWindow.left,
- top = boundsInWindow.top,
- right = boundsInWindow.right,
- bottom = boundsInWindow.bottom,
- )
val shape =
ShadeScrimShape(
- bounds = shadeScrimBounds,
+ bounds = ShadeScrimBounds(coordinates.boundsInWindow()),
topRadius = 0,
bottomRadius = panelCornerRadius,
)
contentViewModel.onPanelShapeChanged(shape)
},
- header = {
- if (quickSettingsContainerViewModel.showHeader) {
- QuickSettingsOverlayHeader(
- viewModel = quickSettingsContainerViewModel.shadeHeaderViewModel,
- modifier =
- Modifier.element(QuickSettingsShade.Elements.Header)
- .padding(top = QuickSettingsShade.Dimensions.Padding),
- )
- }
- },
)
}
+ SnoozeableHeadsUpNotificationSpace(
+ stackScrollView = notificationStackScrollView.get(),
+ viewModel = hunPlaceholderViewModel,
+ )
}
}
}
-// The possible states of the `ShadeBody`.
-sealed interface ShadeBodyState {
+/** The possible states of the `ShadeBody`. */
+private sealed interface ShadeBodyState {
data object Editing : ShadeBodyState
data object TileDetails : ShadeBodyState
@@ -185,10 +165,9 @@ sealed interface ShadeBodyState {
}
@Composable
-fun ContentScope.ShadeBody(
+fun ContentScope.QuickSettingsContainer(
viewModel: QuickSettingsContainerViewModel,
modifier: Modifier = Modifier,
- header: @Composable () -> Unit,
) {
val isEditing by viewModel.editModeViewModel.isEditing.collectAsStateWithLifecycle()
val tileDetails =
@@ -216,11 +195,10 @@ fun ContentScope.ShadeBody(
TileDetails(modifier = modifier, viewModel.detailsViewModel)
}
- else -> {
+ ShadeBodyState.Default -> {
QuickSettingsLayout(
viewModel = viewModel,
modifier = modifier.sysuiResTag("quick_settings_panel"),
- header = header,
)
}
}
@@ -232,7 +210,6 @@ fun ContentScope.ShadeBody(
fun ContentScope.QuickSettingsLayout(
viewModel: QuickSettingsContainerViewModel,
modifier: Modifier = Modifier,
- header: @Composable () -> Unit,
) {
Column(
verticalArrangement = Arrangement.spacedBy(QuickSettingsShade.Dimensions.Padding),
@@ -244,7 +221,14 @@ fun ContentScope.QuickSettingsLayout(
bottom = QuickSettingsShade.Dimensions.Padding,
),
) {
- header()
+ if (viewModel.showHeader) {
+ QuickSettingsOverlayHeader(
+ viewModel = viewModel.shadeHeaderViewModel,
+ modifier =
+ Modifier.element(QuickSettingsShade.Elements.Header)
+ .padding(top = QuickSettingsShade.Dimensions.Padding),
+ )
+ }
Toolbar(
modifier =
Modifier.fillMaxWidth().requiredHeight(QuickSettingsShade.Dimensions.ToolbarHeight),
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneTransitionLayoutDataSource.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneTransitionLayoutDataSource.kt
index 8aa5bc7b7c6f..60eaa28e3822 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneTransitionLayoutDataSource.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneTransitionLayoutDataSource.kt
@@ -21,6 +21,7 @@ import com.android.compose.animation.scene.MutableSceneTransitionLayoutState
import com.android.compose.animation.scene.OverlayKey
import com.android.compose.animation.scene.SceneKey
import com.android.compose.animation.scene.TransitionKey
+import com.android.compose.animation.scene.content.state.TransitionState
import com.android.compose.animation.scene.observableTransitionState
import com.android.systemui.scene.shared.model.SceneDataSource
import kotlinx.coroutines.CoroutineScope
@@ -103,4 +104,8 @@ class SceneTransitionLayoutDataSource(
override fun instantlyHideOverlay(overlay: OverlayKey) {
state.snapTo(overlays = state.currentOverlays - overlay)
}
+
+ override fun freezeAndAnimateToCurrentState() {
+ (state.transitionState as? TransitionState.Transition)?.freezeAndAnimateToCurrentState()
+ }
}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
index 05958a212f47..907b5bc2143a 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
@@ -169,7 +169,7 @@ internal fun Modifier.element(
Modifier.maybeElevateInContent(layoutImpl, content, key, currentTransitionStates)
}
.then(ElementModifier(layoutImpl, currentTransitionStates, content, key))
- .thenIf(layoutImpl.implicitTestTags) { Modifier.testTag(key.testTag) }
+ .testTag(key.testTag)
}
/**
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 d47210cfc428..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
@@ -65,8 +65,6 @@ fun SceneTransitionLayout(
swipeSourceDetector: SwipeSourceDetector = DefaultEdgeDetector,
swipeDetector: SwipeDetector = DefaultSwipeDetector,
@FloatRange(from = 0.0, to = 0.5) transitionInterceptionThreshold: Float = 0.05f,
- // TODO(b/240432457) Remove this once test utils can access the internal STLForTesting().
- implicitTestTags: Boolean = false,
builder: SceneTransitionLayoutScope<ContentScope>.() -> Unit,
) {
SceneTransitionLayoutForTesting(
@@ -75,7 +73,6 @@ fun SceneTransitionLayout(
swipeSourceDetector,
swipeDetector,
transitionInterceptionThreshold,
- implicitTestTags = implicitTestTags,
onLayoutImpl = null,
builder = builder,
)
@@ -728,8 +725,10 @@ class FixedDistance(private val distance: Dp) : UserActionDistance {
}
/**
- * An internal version of [SceneTransitionLayout] to be used for tests, that provides access to the
- * internal [SceneTransitionLayoutImpl] and implicitly tags all scenes and elements.
+ * An internal version of [SceneTransitionLayout] to be used for tests.
+ *
+ * Important: You should use this only in tests and if you need to access the underlying
+ * [SceneTransitionLayoutImpl]. In other cases, you should use [SceneTransitionLayout].
*/
@Composable
internal fun SceneTransitionLayoutForTesting(
@@ -742,7 +741,6 @@ internal fun SceneTransitionLayoutForTesting(
sharedElementMap: MutableMap<ElementKey, Element> = remember { mutableMapOf() },
ancestors: List<Ancestor> = remember { emptyList() },
lookaheadScope: LookaheadScope? = null,
- implicitTestTags: Boolean = true,
builder: SceneTransitionLayoutScope<InternalContentScope>.() -> Unit,
) {
val density = LocalDensity.current
@@ -767,7 +765,6 @@ internal fun SceneTransitionLayoutForTesting(
directionChangeSlop = directionChangeSlop,
defaultEffectFactory = defaultEffectFactory,
decayAnimationSpec = decayAnimationSpec,
- implicitTestTags = implicitTestTags,
)
.also { onLayoutImpl?.invoke(it) }
}
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 e3c4eb0f8bea..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
@@ -122,9 +122,6 @@ internal class SceneTransitionLayoutImpl(
* This is used to enable transformations and shared elements across NestedSTLs.
*/
internal val ancestors: List<Ancestor> = emptyList(),
-
- /** Whether elements and scene should be tagged using `Modifier.testTag`. */
- internal val implicitTestTags: Boolean = false,
lookaheadScope: LookaheadScope? = null,
defaultEffectFactory: OverscrollFactory,
) {
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/UserActionDistanceScopeImpl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/UserActionDistanceScopeImpl.kt
index fffc7f988acf..2d2a81542f84 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/UserActionDistanceScopeImpl.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/UserActionDistanceScopeImpl.kt
@@ -16,6 +16,8 @@
package com.android.compose.animation.scene
+import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
+import androidx.compose.material3.MotionScheme
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.LayoutDirection
@@ -59,4 +61,8 @@ internal class PropertyTransformationScopeImpl(private val layoutImpl: SceneTran
override val layoutDirection: LayoutDirection
get() = layoutImpl.layoutDirection
+
+ @ExperimentalMaterial3ExpressiveApi
+ override val motionScheme: MotionScheme
+ get() = layoutImpl.state.motionScheme
}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Content.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Content.kt
index 64cfe38d3dd5..95d6440d585e 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Content.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Content.kt
@@ -171,7 +171,7 @@ internal sealed class Content(
.thenIf(layoutImpl.state.isElevationPossible(content = key, element = null)) {
Modifier.container(containerState)
}
- .thenIf(layoutImpl.implicitTestTags) { Modifier.testTag(key.testTag) }
+ .testTag(key.testTag)
) {
CompositionLocalProvider(LocalOverscrollFactory provides lastFactory) {
scope.content()
@@ -290,7 +290,6 @@ internal class ContentScopeImpl(
sharedElementMap = layoutImpl.elements,
ancestors = ancestors,
lookaheadScope = layoutImpl.lookaheadScope,
- implicitTestTags = layoutImpl.implicitTestTags,
)
}
}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Transformation.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Transformation.kt
index e0b42189854a..613afe211d87 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Transformation.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Transformation.kt
@@ -18,7 +18,8 @@ package com.android.compose.animation.scene.transformation
import androidx.compose.animation.core.Easing
import androidx.compose.animation.core.LinearEasing
-import androidx.compose.ui.geometry.Offset
+import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
+import androidx.compose.material3.MotionScheme
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.LayoutDirection
@@ -29,8 +30,8 @@ import com.android.compose.animation.scene.ContentKey
import com.android.compose.animation.scene.ElementKey
import com.android.compose.animation.scene.ElementMatcher
import com.android.compose.animation.scene.ElementStateScope
-import com.android.compose.animation.scene.Scale
import com.android.compose.animation.scene.content.state.TransitionState
+import com.android.compose.animation.scene.transformation.PropertyTransformation.Property
import kotlinx.coroutines.CoroutineScope
/** A transformation applied to one or more elements during a transition. */
@@ -126,9 +127,13 @@ interface CustomPropertyTransformation<T> : PropertyTransformation<T> {
): T
}
+@OptIn(ExperimentalMaterial3ExpressiveApi::class)
interface PropertyTransformationScope : Density, ElementStateScope {
/** The current [direction][LayoutDirection] of the layout. */
val layoutDirection: LayoutDirection
+
+ /** The [MotionScheme] in use by the [SceneTransitionLayout]. */
+ val motionScheme: MotionScheme
}
/** Defines the transformation-type to be applied to all elements matching [matcher]. */
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
index 8fce7087dba6..698a80829284 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
@@ -227,7 +227,7 @@ class ElementTest {
to = SceneB,
transitionLayout = { state ->
coroutineScope = rememberCoroutineScope()
- SceneTransitionLayoutForTesting(state) {
+ SceneTransitionLayout(state) {
scene(SceneA) {
Box(Modifier.size(layoutSize)) {
// Transformed element
@@ -633,7 +633,7 @@ class ElementTest {
val scope =
rule.setContentAndCreateMainScope {
- SceneTransitionLayoutForTesting(state) {
+ SceneTransitionLayout(state) {
scene(SceneA) { Box(Modifier.element(TestElements.Foo).size(20.dp)) }
scene(SceneB) {}
}
@@ -674,7 +674,7 @@ class ElementTest {
CompositionLocalProvider(
LocalOverscrollFactory provides rememberOffsetOverscrollEffectFactory()
) {
- SceneTransitionLayoutForTesting(state, Modifier.size(layoutWidth, layoutHeight)) {
+ SceneTransitionLayout(state, Modifier.size(layoutWidth, layoutHeight)) {
scene(key = SceneA, userActions = mapOf(Swipe.Down to SceneB)) {
Spacer(Modifier.fillMaxSize())
}
@@ -734,7 +734,7 @@ class ElementTest {
CompositionLocalProvider(
LocalOverscrollFactory provides rememberOffsetOverscrollEffectFactory()
) {
- SceneTransitionLayoutForTesting(state, Modifier.size(layoutWidth, layoutHeight)) {
+ SceneTransitionLayout(state, Modifier.size(layoutWidth, layoutHeight)) {
scene(key = SceneA, userActions = mapOf(Swipe.Down to SceneB)) {
Spacer(
Modifier.overscroll(verticalOverscrollEffect)
@@ -834,7 +834,7 @@ class ElementTest {
CompositionLocalProvider(
LocalOverscrollFactory provides rememberOffsetOverscrollEffectFactory()
) {
- SceneTransitionLayoutForTesting(state, Modifier.size(layoutWidth, layoutHeight)) {
+ SceneTransitionLayout(state, Modifier.size(layoutWidth, layoutHeight)) {
scene(key = SceneA, userActions = mapOf(Swipe.Down to SceneB)) {
Spacer(Modifier.fillMaxSize())
}
@@ -893,7 +893,7 @@ class ElementTest {
CompositionLocalProvider(
LocalOverscrollFactory provides rememberOffsetOverscrollEffectFactory()
) {
- SceneTransitionLayoutForTesting(
+ SceneTransitionLayout(
state = state,
modifier = Modifier.size(layoutWidth, layoutHeight),
) {
@@ -970,7 +970,7 @@ class ElementTest {
rule.setContent {
touchSlop = LocalViewConfiguration.current.touchSlop
- SceneTransitionLayoutForTesting(
+ SceneTransitionLayout(
state = state,
modifier = Modifier.size(layoutWidth, layoutHeight),
) {
@@ -1057,7 +1057,7 @@ class ElementTest {
rule.setContent {
coroutineScope = rememberCoroutineScope()
- SceneTransitionLayoutForTesting(state) {
+ SceneTransitionLayout(state) {
scene(SceneA) {
Box(Modifier.size(layoutSize)) {
Box(
@@ -1374,7 +1374,7 @@ class ElementTest {
val scope =
rule.setContentAndCreateMainScope {
- SceneTransitionLayoutForTesting(state, Modifier.size(layoutSize)) {
+ SceneTransitionLayout(state, Modifier.size(layoutSize)) {
scene(SceneA) {
Box(Modifier.fillMaxSize()) { Foo(Modifier.align(Alignment.TopStart)) }
}
@@ -1742,7 +1742,7 @@ class ElementTest {
val scope =
rule.setContentAndCreateMainScope {
- SceneTransitionLayoutForTesting(state, Modifier.size(200.dp)) {
+ SceneTransitionLayout(state, Modifier.size(200.dp)) {
scene(SceneA) { Foo(offset = 0.dp) }
scene(SceneB) { Foo(offset = 20.dp) }
scene(SceneC) { Foo(offset = 40.dp) }
@@ -1828,7 +1828,7 @@ class ElementTest {
val scope =
rule.setContentAndCreateMainScope {
- SceneTransitionLayoutForTesting(state) {
+ SceneTransitionLayout(state) {
scene(SceneB) { Foo(Modifier.offset(40.dp, 60.dp)) }
// Define A after B so that Foo is placed in A during A <=> B.
@@ -1887,7 +1887,7 @@ class ElementTest {
val scope =
rule.setContentAndCreateMainScope {
- SceneTransitionLayoutForTesting(state) {
+ SceneTransitionLayout(state) {
scene(SceneA) { Foo() }
scene(SceneB) { Foo(Modifier.offset(40.dp, 60.dp)) }
}
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/OverlayTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/OverlayTest.kt
index 98ecb644878b..04c762f43907 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/OverlayTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/OverlayTest.kt
@@ -90,7 +90,7 @@ class OverlayTest {
lateinit var coroutineScope: CoroutineScope
rule.setContent {
coroutineScope = rememberCoroutineScope()
- SceneTransitionLayoutForTesting(state, Modifier.size(200.dp)) {
+ SceneTransitionLayout(state, Modifier.size(200.dp)) {
scene(SceneA) { Box(Modifier.fillMaxSize()) { Foo() } }
overlay(OverlayA) { Foo() }
}
@@ -132,7 +132,7 @@ class OverlayTest {
lateinit var coroutineScope: CoroutineScope
rule.setContent {
coroutineScope = rememberCoroutineScope()
- SceneTransitionLayoutForTesting(state, Modifier.size(200.dp)) {
+ SceneTransitionLayout(state, Modifier.size(200.dp)) {
scene(SceneA) { Box(Modifier.fillMaxSize()) { Foo() } }
overlay(OverlayA) { Foo() }
overlay(OverlayB) { Foo() }
@@ -230,7 +230,7 @@ class OverlayTest {
lateinit var coroutineScope: CoroutineScope
rule.setContent {
coroutineScope = rememberCoroutineScope()
- SceneTransitionLayoutForTesting(state, Modifier.size(200.dp)) {
+ SceneTransitionLayout(state, Modifier.size(200.dp)) {
scene(SceneA) { Box(Modifier.fillMaxSize()) { MovableBar() } }
overlay(OverlayA) { MovableBar() }
overlay(OverlayB) { MovableBar() }
@@ -302,7 +302,7 @@ class OverlayTest {
}
var alignment by mutableStateOf(Alignment.Center)
rule.setContent {
- SceneTransitionLayoutForTesting(state, Modifier.size(200.dp)) {
+ SceneTransitionLayout(state, Modifier.size(200.dp)) {
scene(SceneA) { Box(Modifier.fillMaxSize()) { Foo() } }
overlay(OverlayA, alignment = alignment) { Foo() }
}
@@ -761,7 +761,7 @@ class OverlayTest {
val movableElementChildTag = "movableElementChildTag"
val scope =
rule.setContentAndCreateMainScope {
- SceneTransitionLayoutForTesting(state) {
+ SceneTransitionLayout(state) {
scene(SceneA) {
MovableElement(key, Modifier) {
content { Box(Modifier.testTag(movableElementChildTag).size(100.dp)) }
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/PredictiveBackHandlerTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/PredictiveBackHandlerTest.kt
index 366b11d9fabd..2bf235846b32 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/PredictiveBackHandlerTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/PredictiveBackHandlerTest.kt
@@ -250,7 +250,7 @@ class PredictiveBackHandlerTest {
}
rule.setContent {
- SceneTransitionLayoutForTesting(state, Modifier.size(200.dp)) {
+ SceneTransitionLayout(state, Modifier.size(200.dp)) {
scene(SceneA) { Box(Modifier.fillMaxSize()) }
overlay(OverlayA) { Box(Modifier.fillMaxSize()) }
overlay(OverlayB) { Box(Modifier.fillMaxSize()) }
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt
index 5cbc98fa4c9d..3578be4b36a7 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt
@@ -97,7 +97,7 @@ class SceneTransitionLayoutTest {
MutableSceneTransitionLayoutStateForTests(SceneA, EmptyTestTransitions)
}
- SceneTransitionLayoutForTesting(state = layoutState, modifier = Modifier.size(LayoutSize)) {
+ SceneTransitionLayout(state = layoutState, modifier = Modifier.size(LayoutSize)) {
scene(SceneA, userActions = mapOf(Back to SceneB)) {
Box(Modifier.fillMaxSize()) {
SharedFoo(size = 50.dp, childOffset = 0.dp, Modifier.align(Alignment.TopEnd))
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt
index 11abbbec79bf..751b31481e3a 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt
@@ -763,7 +763,7 @@ class SwipeToSceneTest {
var touchSlop = 0f
rule.setContent {
touchSlop = LocalViewConfiguration.current.touchSlop
- SceneTransitionLayoutForTesting(state, Modifier.size(layoutSize)) {
+ SceneTransitionLayout(state, Modifier.size(layoutSize)) {
scene(SceneA, userActions = mapOf(Swipe.Start to SceneB, Swipe.End to SceneC)) {
Box(Modifier.fillMaxSize())
}
@@ -837,7 +837,7 @@ class SwipeToSceneTest {
rule.setContent {
touchSlop = LocalViewConfiguration.current.touchSlop
CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Rtl) {
- SceneTransitionLayoutForTesting(state, Modifier.size(layoutSize)) {
+ SceneTransitionLayout(state, Modifier.size(layoutSize)) {
scene(SceneA, userActions = mapOf(Swipe.Start to SceneB, Swipe.End to SceneC)) {
Box(Modifier.fillMaxSize())
}
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/NestedElementTransformationTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/NestedElementTransformationTest.kt
index 8b568928bde0..bb511bc27317 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/NestedElementTransformationTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/NestedElementTransformationTest.kt
@@ -40,7 +40,7 @@ import com.android.compose.animation.scene.MutableSceneTransitionLayoutState
import com.android.compose.animation.scene.MutableSceneTransitionLayoutStateForTests
import com.android.compose.animation.scene.Scale
import com.android.compose.animation.scene.SceneKey
-import com.android.compose.animation.scene.SceneTransitionLayoutForTesting
+import com.android.compose.animation.scene.SceneTransitionLayout
import com.android.compose.animation.scene.SceneTransitions
import com.android.compose.animation.scene.TestScenes
import com.android.compose.animation.scene.testNestedTransition
@@ -114,7 +114,7 @@ class NestedElementTransformationTest {
@Composable
(states: List<MutableSceneTransitionLayoutState>) -> Unit =
{ states ->
- SceneTransitionLayoutForTesting(states[0]) {
+ SceneTransitionLayout(states[0]) {
scene(TestScenes.SceneA, content = { TestElement(elementVariant0A) })
scene(
TestScenes.SceneB,
diff --git a/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestContentScope.kt b/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestContentScope.kt
index e56d1bed4c25..6d47babd716a 100644
--- a/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestContentScope.kt
+++ b/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestContentScope.kt
@@ -30,7 +30,5 @@ fun TestContentScope(
content: @Composable ContentScope.() -> Unit,
) {
val state = rememberMutableSceneTransitionLayoutState(currentScene)
- SceneTransitionLayout(state, modifier, implicitTestTags = true) {
- scene(currentScene, content = content)
- }
+ SceneTransitionLayout(state, modifier) { scene(currentScene, content = content) }
}
diff --git a/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestTransition.kt b/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestTransition.kt
index a362a370328a..f94a7ed77341 100644
--- a/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestTransition.kt
+++ b/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestTransition.kt
@@ -137,7 +137,7 @@ fun ComposeContentTestRule.testTransition(
},
changeState = changeState,
transitionLayout = { state ->
- SceneTransitionLayout(state, layoutModifier, implicitTestTags = true) {
+ SceneTransitionLayout(state, layoutModifier) {
scene(fromScene, content = fromSceneContent)
scene(toScene, content = toSceneContent)
}
@@ -163,7 +163,7 @@ fun ComposeContentTestRule.testShowOverlayTransition(
)
},
transitionLayout = { state ->
- SceneTransitionLayout(state, implicitTestTags = true) {
+ SceneTransitionLayout(state) {
scene(fromScene) { fromSceneContent() }
overlay(overlay) { overlayContent() }
}
@@ -191,7 +191,7 @@ fun ComposeContentTestRule.testHideOverlayTransition(
)
},
transitionLayout = { state ->
- SceneTransitionLayout(state, implicitTestTags = true) {
+ SceneTransitionLayout(state) {
scene(toScene) { toSceneContent() }
overlay(overlay) { overlayContent() }
}
@@ -223,7 +223,7 @@ fun ComposeContentTestRule.testReplaceOverlayTransition(
)
},
transitionLayout = { state ->
- SceneTransitionLayout(state, implicitTestTags = true) {
+ SceneTransitionLayout(state) {
scene(currentScene) { currentSceneContent() }
overlay(from, alignment = fromAlignment) { fromContent() }
overlay(to, alignment = toAlignment) { toContent() }
@@ -273,7 +273,7 @@ fun MotionTestRule<ComposeToolkit>.recordTransition(
}
}
- SceneTransitionLayout(state, layoutModifier, implicitTestTags = true) {
+ SceneTransitionLayout(state, layoutModifier) {
scene(fromScene, content = fromSceneContent)
scene(toScene, content = toSceneContent)
}
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
index b76656d78cc4..4bf0ceb51784 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
@@ -366,7 +366,7 @@ constructor(
fun animateCharge(isDozing: () -> Boolean) {
// Skip charge animation if dozing animation is already playing.
- if (textAnimator == null || textAnimator!!.isRunning()) {
+ if (textAnimator == null || textAnimator!!.isRunning) {
return
}
@@ -444,29 +444,28 @@ constructor(
delay: Long,
onAnimationEnd: Runnable?,
) {
- textAnimator?.let {
- it.setTextStyle(
- weight = weight,
- color = color,
+ val style = TextAnimator.Style(color = color)
+ val animation =
+ TextAnimator.Animation(
animate = animate && isAnimationEnabled,
duration = duration,
- interpolator = interpolator,
- delay = delay,
+ interpolator = interpolator ?: Interpolators.LINEAR,
+ startDelay = delay,
onAnimationEnd = onAnimationEnd,
)
+ textAnimator?.let {
+ it.setTextStyle(
+ style.withUpdatedFVar(it.fontVariationUtils, weight = weight),
+ animation,
+ )
it.glyphFilter = glyphFilter
}
?: run {
// when the text animator is set, update its start values
onTextAnimatorInitialized = { textAnimator ->
textAnimator.setTextStyle(
- weight = weight,
- color = color,
- animate = false,
- duration = duration,
- interpolator = interpolator,
- delay = delay,
- onAnimationEnd = onAnimationEnd,
+ style.withUpdatedFVar(textAnimator.fontVariationUtils, weight = weight),
+ animation.copy(animate = false),
)
textAnimator.glyphFilter = glyphFilter
}
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/SimpleDigitalHandLayerController.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/SimpleDigitalHandLayerController.kt
index a5adfa2a1ac6..0b7ea1a335ef 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/SimpleDigitalHandLayerController.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/SimpleDigitalHandLayerController.kt
@@ -21,6 +21,7 @@ import android.view.ViewGroup
import android.view.animation.Interpolator
import android.widget.RelativeLayout
import androidx.annotation.VisibleForTesting
+import com.android.systemui.animation.TextAnimator
import com.android.systemui.customization.R
import com.android.systemui.log.core.Logger
import com.android.systemui.plugins.clocks.AlarmData
@@ -65,7 +66,7 @@ data class DigitalAlignment(
data class FontTextStyle(
val lineHeight: Float? = null,
val fontSizeScale: Float? = null,
- val transitionDuration: Long = -1L,
+ val transitionDuration: Long = TextAnimator.DEFAULT_ANIMATION_DURATION,
val transitionInterpolator: Interpolator? = null,
)
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 92fa6b5be1ed..8317aa39ef2b 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
@@ -33,7 +33,9 @@ import android.util.MathUtils
import android.util.TypedValue
import android.view.View.MeasureSpec.EXACTLY
import android.view.animation.Interpolator
+import android.view.animation.PathInterpolator
import android.widget.TextView
+import com.android.app.animation.Interpolators
import com.android.internal.annotations.VisibleForTesting
import com.android.systemui.animation.GSFAxes
import com.android.systemui.animation.TextAnimator
@@ -84,17 +86,20 @@ open class SimpleDigitalClockTextView(
else -> listOf(FLEX_AOD_SMALL_WEIGHT_AXIS, FLEX_AOD_WIDTH_AXIS)
}
- private var lsFontVariation =
- if (!isLegacyFlex) listOf(LS_WEIGHT_AXIS, WIDTH_AXIS, ROUND_AXIS, SLANT_AXIS).toFVar()
- else listOf(FLEX_LS_WEIGHT_AXIS, FLEX_LS_WIDTH_AXIS, FLEX_ROUND_AXIS, SLANT_AXIS).toFVar()
+ private var lsFontVariation: String
+ private var aodFontVariation: String
+ private var fidgetFontVariation: String
- private var aodFontVariation = run {
+ init {
val roundAxis = if (!isLegacyFlex) ROUND_AXIS else FLEX_ROUND_AXIS
- (fixedAodAxes + listOf(roundAxis, SLANT_AXIS)).toFVar()
- }
+ val lsFontAxes =
+ if (!isLegacyFlex) listOf(LS_WEIGHT_AXIS, WIDTH_AXIS, ROUND_AXIS, SLANT_AXIS)
+ else listOf(FLEX_LS_WEIGHT_AXIS, FLEX_LS_WIDTH_AXIS, FLEX_ROUND_AXIS, SLANT_AXIS)
- // TODO(b/374306512): Fidget endpoint to spec
- private var fidgetFontVariation = aodFontVariation
+ lsFontVariation = lsFontAxes.toFVar()
+ aodFontVariation = (fixedAodAxes + listOf(roundAxis, SLANT_AXIS)).toFVar()
+ fidgetFontVariation = buildFidgetVariation(lsFontAxes).toFVar()
+ }
private val parser = DimensionParser(clockCtx.context)
var maxSingleDigitHeight = -1
@@ -121,7 +126,7 @@ open class SimpleDigitalClockTextView(
protected val logger = ClockLogger(this, clockCtx.messageBuffer, this::class.simpleName!!)
get() = field ?: ClockLogger.INIT_LOGGER
- private var aodDozingInterpolator: Interpolator? = null
+ private var aodDozingInterpolator: Interpolator = Interpolators.LINEAR
@VisibleForTesting lateinit var textAnimator: TextAnimator
@@ -149,7 +154,7 @@ open class SimpleDigitalClockTextView(
lockscreenColor = color
lockScreenPaint.color = lockscreenColor
if (dozeFraction < 1f) {
- textAnimator.setTextStyle(color = lockscreenColor, animate = false)
+ textAnimator.setTextStyle(TextAnimator.Style(color = lockscreenColor))
}
invalidate()
}
@@ -157,10 +162,8 @@ open class SimpleDigitalClockTextView(
fun updateAxes(lsAxes: List<ClockFontAxisSetting>) {
lsFontVariation = lsAxes.toFVar()
aodFontVariation = lsAxes.replace(fixedAodAxes).toFVar()
- logger.i({ "updateAxes(LS = $str1, AOD = $str2)" }) {
- str1 = lsFontVariation
- str2 = aodFontVariation
- }
+ fidgetFontVariation = buildFidgetVariation(lsAxes).toFVar()
+ logger.updateAxes(lsFontVariation, aodFontVariation)
lockScreenPaint.typeface = typefaceCache.getTypefaceForVariant(lsFontVariation)
typeface = lockScreenPaint.typeface
@@ -168,13 +171,28 @@ open class SimpleDigitalClockTextView(
lockScreenPaint.getTextBounds(text, 0, text.length, textBounds)
targetTextBounds.set(textBounds)
- textAnimator.setTextStyle(fvar = lsFontVariation, animate = false)
+ textAnimator.setTextStyle(TextAnimator.Style(fVar = lsFontVariation))
measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED)
recomputeMaxSingleDigitSizes()
requestLayout()
invalidate()
}
+ fun buildFidgetVariation(axes: List<ClockFontAxisSetting>): List<ClockFontAxisSetting> {
+ val result = mutableListOf<ClockFontAxisSetting>()
+ for (axis in axes) {
+ result.add(
+ FIDGET_DISTS.get(axis.key)?.let { (dist, midpoint) ->
+ ClockFontAxisSetting(
+ axis.key,
+ axis.value + dist * if (axis.value > midpoint) -1 else 1,
+ )
+ } ?: axis
+ )
+ }
+ return result
+ }
+
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
logger.onMeasure()
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
@@ -245,40 +263,54 @@ open class SimpleDigitalClockTextView(
fun animateDoze(isDozing: Boolean, isAnimated: Boolean) {
if (!this::textAnimator.isInitialized) return
+ logger.animateDoze()
textAnimator.setTextStyle(
- animate = isAnimated && isAnimationEnabled,
- color = if (isDozing) AOD_COLOR else lockscreenColor,
- textSize = if (isDozing) aodFontSizePx else lockScreenPaint.textSize,
- fvar = if (isDozing) aodFontVariation else lsFontVariation,
- duration = aodStyle.transitionDuration,
- interpolator = aodDozingInterpolator,
+ TextAnimator.Style(
+ fVar = if (isDozing) aodFontVariation else lsFontVariation,
+ color = if (isDozing) AOD_COLOR else lockscreenColor,
+ textSize = if (isDozing) aodFontSizePx else lockScreenPaint.textSize,
+ ),
+ TextAnimator.Animation(
+ animate = isAnimated && isAnimationEnabled,
+ duration = aodStyle.transitionDuration,
+ interpolator = aodDozingInterpolator,
+ ),
)
updateTextBoundsForTextAnimator()
}
fun animateCharge() {
- if (!this::textAnimator.isInitialized || textAnimator.isRunning()) {
+ if (!this::textAnimator.isInitialized || textAnimator.isRunning) {
// Skip charge animation if dozing animation is already playing.
return
}
- logger.d("animateCharge()")
+ logger.animateCharge()
+
+ val lsStyle = TextAnimator.Style(fVar = lsFontVariation)
+ val aodStyle = TextAnimator.Style(fVar = aodFontVariation)
+
textAnimator.setTextStyle(
- fvar = if (dozeFraction == 0F) aodFontVariation else lsFontVariation,
- animate = isAnimationEnabled,
- onAnimationEnd =
- Runnable {
+ if (dozeFraction == 0f) aodStyle else lsStyle,
+ TextAnimator.Animation(
+ animate = isAnimationEnabled,
+ duration = CHARGE_ANIMATION_DURATION,
+ onAnimationEnd = {
textAnimator.setTextStyle(
- fvar = if (dozeFraction == 0F) lsFontVariation else aodFontVariation,
- animate = isAnimationEnabled,
+ if (dozeFraction == 0f) lsStyle else aodStyle,
+ TextAnimator.Animation(
+ animate = isAnimationEnabled,
+ duration = CHARGE_ANIMATION_DURATION,
+ ),
)
updateTextBoundsForTextAnimator()
},
+ ),
)
updateTextBoundsForTextAnimator()
}
fun animateFidget(x: Float, y: Float) {
- if (!this::textAnimator.isInitialized || textAnimator.isRunning()) {
+ if (!this::textAnimator.isInitialized || textAnimator.isRunning) {
// Skip fidget animation if other animation is already playing.
return
}
@@ -286,19 +318,25 @@ open class SimpleDigitalClockTextView(
logger.animateFidget(x, y)
clockCtx.vibrator?.vibrate(FIDGET_HAPTICS)
- // TODO(b/374306512): Duplicated charge animation as placeholder. Implement final version
- // when we have a complete spec. May require additional code to animate individual digits.
+ // TODO(b/374306512): Delay each glyph's animation based on x/y position
textAnimator.setTextStyle(
- fvar = fidgetFontVariation,
- animate = isAnimationEnabled,
- onAnimationEnd =
- Runnable {
+ TextAnimator.Style(fVar = fidgetFontVariation),
+ TextAnimator.Animation(
+ animate = isAnimationEnabled,
+ duration = FIDGET_ANIMATION_DURATION,
+ interpolator = FIDGET_INTERPOLATOR,
+ onAnimationEnd = {
textAnimator.setTextStyle(
- fvar = if (dozeFraction == 0F) lsFontVariation else aodFontVariation,
- animate = isAnimationEnabled,
+ TextAnimator.Style(fVar = lsFontVariation),
+ TextAnimator.Animation(
+ animate = isAnimationEnabled,
+ duration = FIDGET_ANIMATION_DURATION,
+ interpolator = FIDGET_INTERPOLATOR,
+ ),
)
updateTextBoundsForTextAnimator()
},
+ ),
)
updateTextBoundsForTextAnimator()
}
@@ -329,42 +367,20 @@ open class SimpleDigitalClockTextView(
}
private fun getInterpolatedTextBounds(): Rect {
- val interpolatedTextBounds = Rect()
- if (textAnimator.animator.animatedFraction != 1.0f && textAnimator.animator.isRunning) {
- interpolatedTextBounds.left =
- MathUtils.lerp(
- prevTextBounds.left,
- targetTextBounds.left,
- textAnimator.animator.animatedValue as Float,
- )
- .toInt()
-
- interpolatedTextBounds.right =
- MathUtils.lerp(
- prevTextBounds.right,
- targetTextBounds.right,
- textAnimator.animator.animatedValue as Float,
- )
- .toInt()
-
- interpolatedTextBounds.top =
- MathUtils.lerp(
- prevTextBounds.top,
- targetTextBounds.top,
- textAnimator.animator.animatedValue as Float,
- )
- .toInt()
-
- interpolatedTextBounds.bottom =
- MathUtils.lerp(
- prevTextBounds.bottom,
- targetTextBounds.bottom,
- textAnimator.animator.animatedValue as Float,
- )
- .toInt()
- } else {
- interpolatedTextBounds.set(targetTextBounds)
+ val progress = textAnimator.animator?.let { it.animatedValue as Float } ?: 1f
+ if (!textAnimator.isRunning || progress >= 1f) {
+ return Rect(targetTextBounds)
}
+
+ val interpolatedTextBounds = Rect()
+ interpolatedTextBounds.left =
+ MathUtils.lerp(prevTextBounds.left, targetTextBounds.left, progress).toInt()
+ interpolatedTextBounds.right =
+ MathUtils.lerp(prevTextBounds.right, targetTextBounds.right, progress).toInt()
+ interpolatedTextBounds.top =
+ MathUtils.lerp(prevTextBounds.top, targetTextBounds.top, progress).toInt()
+ interpolatedTextBounds.bottom =
+ MathUtils.lerp(prevTextBounds.bottom, targetTextBounds.bottom, progress).toInt()
return interpolatedTextBounds
}
@@ -471,7 +487,7 @@ open class SimpleDigitalClockTextView(
textStyle.lineHeight?.let { lineHeight = it.toInt() }
this.aodStyle = aodStyle ?: textStyle.copy()
- this.aodStyle.transitionInterpolator?.let { aodDozingInterpolator = it }
+ aodDozingInterpolator = this.aodStyle.transitionInterpolator ?: Interpolators.LINEAR
lockScreenPaint.strokeWidth = textBorderWidth
measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED)
setInterpolatorPaint()
@@ -500,7 +516,7 @@ open class SimpleDigitalClockTextView(
recomputeMaxSingleDigitSizes()
if (this::textAnimator.isInitialized) {
- textAnimator.setTextStyle(textSize = lockScreenPaint.textSize, animate = false)
+ textAnimator.setTextStyle(TextAnimator.Style(textSize = lockScreenPaint.textSize))
}
}
@@ -525,10 +541,11 @@ open class SimpleDigitalClockTextView(
textAnimator.textInterpolator.targetPaint.set(lockScreenPaint)
textAnimator.textInterpolator.onTargetPaintModified()
textAnimator.setTextStyle(
- fvar = lsFontVariation,
- textSize = lockScreenPaint.textSize,
- color = lockscreenColor,
- animate = false,
+ TextAnimator.Style(
+ fVar = lsFontVariation,
+ textSize = lockScreenPaint.textSize,
+ color = lockscreenColor,
+ )
)
}
}
@@ -572,6 +589,17 @@ open class SimpleDigitalClockTextView(
.addPrimitive(VibrationEffect.Composition.PRIMITIVE_QUICK_RISE, 1.0f, 43)
.compose()
+ val CHARGE_ANIMATION_DURATION = 500L
+ val FIDGET_ANIMATION_DURATION = 250L
+ val FIDGET_INTERPOLATOR = PathInterpolator(0.26873f, 0f, 0.45042f, 1f)
+ val FIDGET_DISTS =
+ mapOf(
+ GSFAxes.WEIGHT to Pair(200f, 500f),
+ GSFAxes.WIDTH to Pair(30f, 75f),
+ GSFAxes.ROUND to Pair(0f, 50f),
+ GSFAxes.SLANT to Pair(0f, -5f),
+ )
+
val AOD_COLOR = Color.WHITE
val LS_WEIGHT_AXIS = ClockFontAxisSetting(GSFAxes.WEIGHT, 400f)
val AOD_WEIGHT_AXIS = ClockFontAxisSetting(GSFAxes.WEIGHT, 200f)
diff --git a/packages/SystemUI/docs/qs-tiles.md b/packages/SystemUI/docs/qs-tiles.md
index ee388ec8e5c5..87d9b7c3b853 100644
--- a/packages/SystemUI/docs/qs-tiles.md
+++ b/packages/SystemUI/docs/qs-tiles.md
@@ -8,6 +8,14 @@ This document is a more or less comprehensive summary of the state and infrastru
Settings tiles. It provides descriptions about the lifecycle of a tile, how to create new tiles and
how SystemUI manages and displays tiles, among other topics.
+A lot of the tile backend architecture is in the process of being replaced by a new architecture in
+order to align with the
+[recommended architecture](https://developer.android.com/topic/architecture#recommended-app-arch).
+
+While we are in the process of migrating, this document will try to provide a comprehensive
+overview of the current architecture as well as the new one. The sections documenting the new
+architecture are marked with the tag [NEW-ARCH].
+
## What are Quick Settings Tiles?
Quick Settings (from now on, QS) is the expanded panel that contains shortcuts for the user to
@@ -72,6 +80,27 @@ The interfaces in `QSTile` as well as other interfaces described in this documen
implement plugins to add additional tiles or different behavior. For more information,
see [plugins.md](plugins.md)
+
+#### [NEW-ARCH] Tile backend
+Instead of `QSTileImpl` the tile backend is made of a view model called `QSTileViewModelImpl`,
+which in turn is composed of 3 interfaces:
+
+* [`QSTileDataInteractor`](/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileDataInteractor.kt)
+is responsible for providing the data for the tile. It is responsible for fetching the state of
+the tile from the source of truth and providing that information to the tile. Typically the data
+interactor will read system state from a repository or a controller and provide a flow of
+domain-specific data model.
+
+* [`QSTileUserActionInteractor`](/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileUserActionInteractor.kt) is responsible for handling the user actions on the tile.
+This interactor decides what should happen when the user clicks, long clicks on the tile.
+
+* [`QSTileDataToStateMapper`](/packages/SystemUI/src/com/android/systemui/qs/tiles/base/mapper/QSTileMapper.kt)
+is responsible for mapping the data received from the data interactor to a state that the view
+model can use to update the UI.
+
+At the time being, the `QSTileViewModel`s are adapted to `QSTile`. This conversion is done by
+`QSTileViewModelAdapter`.
+
#### Tile State
Each tile has an associated `State` object that is used to communicate information to the
@@ -94,6 +123,11 @@ Additionally. `BooleanState` has a `value` boolean field that usually would be s
to `state == Tile#STATE_ACTIVE`. This is used by accessibility services along
with `expandedAccessibilityClassName`.
+#### [NEW-ARCH] Tile State
+In the new architecture, the mapper generates
+[`QSTileState`](packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileState.kt),
+which again is converted to the old state by `QSTileViewModelAdapter`.
+
#### SystemUI tiles
Each tile defined in SystemUI extends `QSTileImpl`. This abstract class implements some common
@@ -103,6 +137,9 @@ to handle different events (refresh, click, etc.).
For more information on how to implement a tile in SystemUI,
see [Implementing a SystemUI tile](#implementing-a-systemui-tile).
+As mentioned before, when the [NEW-ARCH] migration is complete, we will remove the `QSTileImpl`
+and `QSTileViewModelAdapter` and directly use`QSTileViewModelImpl`.
+
### Tile views
Each Tile has a couple of associated views for displaying it in QS and QQS. These views are updated
@@ -154,37 +191,24 @@ corresponding `QSTile` with its `QSTileView`, doing the following:
#### Life of a tile click
This is a brief run-down of what happens when a user clicks on a tile. Internal changes on the
-device (for example, changes from Settings) will trigger this process starting in step 3. Throughout
-this section, we assume that we are dealing with a `QSTileImpl`.
-
-1. User clicks on tile. The following calls happen in sequence:
- 1. `QSTileViewImpl#onClickListener`.
- 2. `QSTile#click`.
- 3. `QSTileImpl#handleClick`. This last call sets the new state for the device by using the
- associated controller.
-2. State in the device changes. This is normally outside of SystemUI's control.
-3. Controller receives a callback (or `Intent`) indicating the change in the device. The following
- calls happen:
- 1. `QSTileImpl#refreshState`, maybe passing an object with necessary information regarding the
- new state.
- 2. `QSTileImpl#handleRefreshState`
-4. `QSTileImpl#handleUpdateState` is called to update the state with the new information. This
- information can be obtained both from the `Object` passed to `refreshState` as well as from the
- controller.
-5. If the state has changed (in at least one element), `QSTileImpl#handleStateChanged` is called.
- This will trigger a call to all the associated `QSTile.Callback#onStateChanged`, passing the
- new `State`.
-6. `QSTileView#onStateChanged` is called and this calls `QSTileView#handleStateChanged`. This method
- maps the state into the view:
- * The tile colors change to match the new state.
- * `QSIconView.setIcon` is called to apply the correct state to the icon and the correct icon to
- the view.
- * The tile labels change to match the new state.
+device (for example, changes from Settings) will trigger this process starting in step 5.
+
+Step | Legacy Tiles | [NEW-ARCH] Tiles
+-------|-------|---------
+1 | User clicks on tile. | Same as legacy tiles.
+2 | `QSTileViewImpl#onClickListener` | Same as legacy tiles.
+3 | `QSTile#click` | Same as legacy tiles.
+4| `QSTileImpl#handleClick` | `QSTileUserActionInteractor#handleInput`
+5| State in the device changes. This is normally outside of SystemUI's control. Controller receives a callback (or `Intent`) indicating the change in the device. | Same as legacy tiles.
+6 | `QSTile#refreshState`and `QSTileImpl#handleRefreshState` | `QSTileDataInteractor#tileData()`
+7| `QSTileImpl#handleUpdateState` is called to update the state with the new information. This information can be obtained both from the `Object` passed to `refreshState` as well as from the controller. | The data that was generated by the data interactor is read by the `QSTileViewModelImpl.state` flow which calls `QSTileMapper#map` on the data to generate a new `QSTileState`.
+8| If the state has changed (in at least one element `QSTileImpl#handleStateChanged` is called. This will trigger a call to all the associated `QSTile.Callback#onStateChanged`, passing the new `State`. | The newly mapped QSTileState is read by the `QSTileViewModelAdapter` which then maps it to a legacy `State`. Similarly to the legacy tiles, the new state is compared to the old one and if there is a difference, `QSTile.Callback#onStateChanged` is called for all the associated callbacks.
+9 | `QSTileView#onStateChanged` is called and this calls `QSTileView#handleStateChanged`. This method maps the state updating tile color and label, and calling `QSIconView.setIcon` | Same as legacy tiles.
## Third party tiles (TileService)
-A third party tile is any Quick Settings tile that is provided by an app (that's not SystemUI). This
-is implemented by developers
+A third party tile is any Quick Settings tile that is provided by an app (that's not SystemUI).
+This is implemented by developers
subclassing [`TileService`](/core/java/android/service/quicksettings/TileService.java) and
interacting with its API.
@@ -220,9 +244,9 @@ from SystemUI:
* **`onTileAdded`**: called when the tile is added to QS.
* **`onTileRemoved`**: called when the tile is removed from QS.
* **`onStartListening`**: called when QS is opened and the tile is showing. This marks the start of
- the window when calling `getQSTile` is safe and will provide the correct object.
-* **`onStopListening`**: called when QS is closed or the tile is no longer visible by the user. This
- marks the end of the window described in `onStartListening`.
+the window when calling `getQSTile` is safe and will provide the correct object.
+* **`onStopListening`**: called when QS is closed or the tile is no longer visible by the user.
+This marks the end of the window described in `onStartListening`.
* **`onClick`**: called when the user clicks on the tile.
Additionally, the following final methods are provided:
@@ -379,13 +403,14 @@ correct list of tiles.
### QSFactory
+`CurrentTilesInteractorImpl` uses the `QSFactory` interface to create the tiles.
+
This interface provides a way of creating tiles and views from a spec. It can be used in plugins to
provide different definitions for tiles.
-In SystemUI there is only one implementation of this factory and that is the default
-factory (`QSFactoryImpl`) in `CurrentTilesInteractorImpl`.
+In SystemUI there are two implementation of this factory. The first one is `QSFactoryImpl` in used for legacy tiles. The second one is `NewQSFactory` used for [NEW-ARCH] tiles.
-#### QSFactoryImpl
+#### QSFactoryImpl (legacy tiles)
This class implements the following method as specified in the `QSFactory` interface:
@@ -402,6 +427,12 @@ This class implements the following method as specified in the `QSFactory` inter
As part of filtering not valid tiles, custom tiles that don't have a corresponding valid service
component are never instantiated.
+#### NewQSFactory ([NEW-ARCH] tiles)
+
+This class also implements the `createTile` method as specified in the `QSFactory` interface.
+However, it first uses the spec to get a `QSTileViewModel`. The view model is then adapted into a
+`QSTile` using the `QSTileViewModelAdapter`.
+
### Lifecycle of a Tile
We describe first the parts of the lifecycle that are common to SystemUI tiles and third party
@@ -415,7 +446,7 @@ tiles.
2. This updates the flow that `CurrentTilesInteractor` is collecting from, triggering the process
described above.
3. `CurrentTilesInteractor` calls the available `QSFactory` classes in order to find one that will
- be able to create a tile with that spec. Assuming that `QSFactoryImpl` managed to create the
+ be able to create a tile with that spec. Assuming that some factory managed to create the
tile, which is some implementation of `QSTile` (either a SystemUI subclass
of `QSTileImpl` or a `CustomTile`) it will be added to the current list.
If the tile is available, it's stored in a map and things proceed forward.
@@ -452,7 +483,7 @@ in [Third party tiles (TileService)](#third-party-tiles-tileservice).
This section describes necessary and recommended steps when implementing a Quick Settings tile. Some
of them are optional and depend on the requirements of the tile.
-### Implementing a SystemUI tile
+### Implementing a legacy SystemUI tile
1. Create a class (preferably
in [`SystemUI/src/com/android/systemui/qs/tiles`](/packages/SystemUI/src/com/android/systemui/qs/tiles))
@@ -579,6 +610,70 @@ type variable of type `State`.
Provides a default label for this Tile. Used by the QS Panel customizer to show a name next to
each available tile.
+### Implementing a [NEW-ARCH] SystemUI tile
+In the new system the tiles are created in the path
+[`packages/SystemUI/src/com/android/systemui/qs/tiles/impl/<spec>`](packages/SystemUI/src/com/android/systemui/qs/tiles/impl/<spec>)
+where the `<spec>` should be replaced by the spec of the tile e.g. rotation for `RotationLockTile`.
+
+To create a new tile, the developer needs to implement the following data class and interfaces:
+
+[`DataModel`] is a class that describes the system state of the feature that the tile is trying to
+represent. Let's refer to the type of this class as DATA_TYPE. For example a simplified version of
+the data model for a flashlight tile could be a class with a boolean field that represents
+whether the flashlight is on or not.
+
+This file should be placed in the relative path `domain/model/` down from the tile's package.
+
+[`QSTileDataInteractor`] There are two abstract methods that need to be implemented:
+* `fun tileData(user: UserHandle, triggers: Flow<DataUpdateTrigger>): Flow<DATA_TYPE>`: This method
+returns a flow of data that will be used to create the state of the tile. This is where the system
+state is listened to and converted to a flow of data model. Avoid loading data or settings up
+listeners on the main thread. The userHandle is the user for which the tile is created.
+The triggers flow is a flow of events that can be used to trigger a refresh of the data.
+The most common triggers the force update and initial request.
+
+* `fun availability(user: UserHandle): Flow<Boolean>`: This method returns a flow of booleans that
+indicates if the tile should be shown or not. This is where the availability of the system feature
+(e.g. wifi) is checked. The userHandle is the user for which the tile is created.
+
+This file should be placed in the relative path `domain/interactor/` down from the tile's package.
+
+[`QSTileUserActionInteractor`]
+* `fun handleInput(input: QSTileInput)` is the method that needs to be implemented. This is the
+method that will be called when the user interacts with the tile. The input parameter contains
+the type of interaction (click, long click, toggle) and the DATA_TYPE of the latest data when the
+input was received.
+
+This file should be placed in the relative path `/domain/interactor` down from the tile's package.
+
+[`QSTileDataToStateMapper`]
+* `fun map(data: DATA_TYPE): QSTileState` is the method that needs to be implemented. This method
+is responsible for mapping the data received from the data interactor to a state that the view
+model can use to update the UI. This is where for example the icon should be loaded, and the
+label and content description set. The map function will run on UIBackground thread, a single
+thread which has higher priority than the background thread and lower than UI thread. Loading a
+resource on UI thread can cause jank by blocking the UI thread. On the other end of the spectrum,
+loading resources using a background dispatcher may cause jank due to background thread contention
+since it is possible for the background dispatcher to use more than one background thread
+at the same time. In contrast, the UIBackground dispatcher uses a single thread that is
+shared by all tiles. Therefore the system will use UIBackground dispatcher to execute
+the map function.
+
+The most important resource to load in the map function is the icon. We prefer `Icon.Loaded` with a
+resource id over `Icon.Resource`, because then (a) we can guarantee that the drawable loading will
+happen on the UIBackground thread and (b) we can cache the drawables using the resource id.
+
+This file should be placed in the relative path `/ui/mapper` down from the tile's package.
+
+#### Testing a [NEW-ARCH] SystemUI tile
+
+When writing tests, the mapper is usually a good place to start, since that is where the
+business logic decisions are being made that can inform the shape of data interactor.
+
+We suggest taking advantage of the existing class `QSTileStateSubject`. So rather than
+asserting an individual field's value, a test will assert the whole state. That can be
+achieved by `QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)`.
+
### Implementing a third party tile
For information about this, use the Android Developer documentation
diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardMessageAreaControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardMessageAreaControllerTest.java
index bd811814eb24..4140a956182c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardMessageAreaControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardMessageAreaControllerTest.java
@@ -18,9 +18,7 @@ package com.android.keyguard;
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.anyLong;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
@@ -28,8 +26,6 @@ import static org.mockito.Mockito.when;
import android.hardware.biometrics.BiometricSourceType;
import android.testing.TestableLooper;
-import android.text.Editable;
-import android.text.TextWatcher;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
@@ -99,19 +95,6 @@ public class KeyguardMessageAreaControllerTest extends SysuiTestCase {
}
@Test
- public void textChanged_AnnounceForAccessibility() {
- ArgumentCaptor<TextWatcher> textWatcherArgumentCaptor = ArgumentCaptor.forClass(
- TextWatcher.class);
- mMessageAreaController.onViewAttached();
- verify(mKeyguardMessageArea).addTextChangedListener(textWatcherArgumentCaptor.capture());
-
- textWatcherArgumentCaptor.getValue().afterTextChanged(
- Editable.Factory.getInstance().newEditable("abc"));
- verify(mKeyguardMessageArea).removeCallbacks(any(Runnable.class));
- verify(mKeyguardMessageArea).postDelayed(any(Runnable.class), anyLong());
- }
-
- @Test
public void testSetBouncerVisible() {
mMessageAreaController.setIsVisible(true);
verify(mKeyguardMessageArea).setIsVisible(true);
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java
index e4539b75f317..0b13900d826b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java
@@ -45,7 +45,6 @@ import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.graphics.drawable.Drawable;
import android.media.AudioManager;
-import android.os.Handler;
import android.platform.test.annotations.EnableFlags;
import android.provider.Settings;
import android.testing.TestableLooper;
@@ -75,6 +74,8 @@ import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.res.R;
import com.android.systemui.statusbar.phone.SystemUIDialog;
import com.android.systemui.statusbar.phone.SystemUIDialogManager;
+import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.time.FakeSystemClock;
import org.junit.After;
import org.junit.Before;
@@ -107,6 +108,7 @@ public class HearingDevicesDialogDelegateTest extends SysuiTestCase {
private static final String TEST_LABEL = "label";
private static final int TEST_PRESET_INDEX = 1;
private static final String TEST_PRESET_NAME = "test_preset";
+ private final FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock());
@Mock
private SystemUIDialogManager mSystemUIDialogManager;
@@ -150,11 +152,9 @@ public class HearingDevicesDialogDelegateTest extends SysuiTestCase {
private SystemUIDialog mDialog;
private SystemUIDialog.Factory mDialogFactory;
private HearingDevicesDialogDelegate mDialogDelegate;
- private TestableLooper mTestableLooper;
@Before
public void setUp() {
- mTestableLooper = TestableLooper.get(this);
when(mLocalBluetoothManager.getBluetoothAdapter()).thenReturn(mLocalBluetoothAdapter);
when(mLocalBluetoothManager.getProfileManager()).thenReturn(mProfileManager);
when(mProfileManager.getHapClientProfile()).thenReturn(mHapClientProfile);
@@ -186,6 +186,7 @@ public class HearingDevicesDialogDelegateTest extends SysuiTestCase {
public void clickPairNewDeviceButton_intentActionMatch() {
setUpDeviceDialogWithPairNewDeviceButton();
mDialog.show();
+ mExecutor.runAllReady();
getPairNewDeviceButton(mDialog).performClick();
@@ -232,6 +233,7 @@ public class HearingDevicesDialogDelegateTest extends SysuiTestCase {
setUpDeviceDialogWithoutPairNewDeviceButton();
mDialog.show();
+ mExecutor.runAllReady();
assertToolsUi(0);
}
@@ -246,6 +248,7 @@ public class HearingDevicesDialogDelegateTest extends SysuiTestCase {
setUpDeviceDialogWithoutPairNewDeviceButton();
mDialog.show();
+ mExecutor.runAllReady();
assertToolsUi(1);
}
@@ -267,6 +270,7 @@ public class HearingDevicesDialogDelegateTest extends SysuiTestCase {
setUpDeviceDialogWithoutPairNewDeviceButton();
mDialog.show();
+ mExecutor.runAllReady();
assertToolsUi(2);
}
@@ -278,6 +282,7 @@ public class HearingDevicesDialogDelegateTest extends SysuiTestCase {
setUpDeviceDialogWithoutPairNewDeviceButton();
mDialog.show();
+ mExecutor.runAllReady();
ViewGroup presetLayout = getPresetLayout(mDialog);
assertThat(presetLayout.getVisibility()).isEqualTo(View.GONE);
@@ -291,7 +296,7 @@ public class HearingDevicesDialogDelegateTest extends SysuiTestCase {
setUpDeviceDialogWithoutPairNewDeviceButton();
mDialog.show();
- mTestableLooper.processAllMessages();
+ mExecutor.runAllReady();
ViewGroup presetLayout = getPresetLayout(mDialog);
assertThat(presetLayout.getVisibility()).isEqualTo(View.VISIBLE);
@@ -306,6 +311,7 @@ public class HearingDevicesDialogDelegateTest extends SysuiTestCase {
setUpDeviceDialogWithoutPairNewDeviceButton();
mDialog.show();
+ mExecutor.runAllReady();
ViewGroup ambientLayout = getAmbientLayout(mDialog);
assertThat(ambientLayout.getVisibility()).isEqualTo(View.GONE);
@@ -318,6 +324,7 @@ public class HearingDevicesDialogDelegateTest extends SysuiTestCase {
setUpDeviceDialogWithoutPairNewDeviceButton();
mDialog.show();
+ mExecutor.runAllReady();
ViewGroup ambientLayout = getAmbientLayout(mDialog);
assertThat(ambientLayout.getVisibility()).isEqualTo(View.GONE);
@@ -343,6 +350,7 @@ public class HearingDevicesDialogDelegateTest extends SysuiTestCase {
public void onActiveDeviceChanged_presetExist_presetSelected() {
setUpDeviceDialogWithoutPairNewDeviceButton();
mDialog.show();
+ mExecutor.runAllReady();
BluetoothHapPresetInfo info = getTestPresetInfo();
when(mHapClientProfile.getAllPresetInfo(mDevice)).thenReturn(List.of(info));
when(mHapClientProfile.getActivePresetIndex(mDevice)).thenReturn(TEST_PRESET_INDEX);
@@ -351,7 +359,7 @@ public class HearingDevicesDialogDelegateTest extends SysuiTestCase {
assertThat(spinner.getSelectedItemPosition()).isEqualTo(-1);
mDialogDelegate.onActiveDeviceChanged(mCachedDevice, BluetoothProfile.LE_AUDIO);
- mTestableLooper.processAllMessages();
+ mExecutor.runAllReady();
ViewGroup presetLayout = getPresetLayout(mDialog);
assertThat(presetLayout.getVisibility()).isEqualTo(View.VISIBLE);
@@ -381,7 +389,8 @@ public class HearingDevicesDialogDelegateTest extends SysuiTestCase {
mActivityStarter,
mDialogTransitionAnimator,
mLocalBluetoothManager,
- new Handler(mTestableLooper.getLooper()),
+ mExecutor,
+ mExecutor,
mAudioManager,
mUiEventLogger
);
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/activity/data/repository/ActivityManagerRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/activity/data/repository/ActivityManagerRepositoryTest.kt
index d6ba98d65d15..441f807a8ec8 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/activity/data/repository/ActivityManagerRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/activity/data/repository/ActivityManagerRepositoryTest.kt
@@ -31,6 +31,7 @@ import com.android.systemui.kosmos.useUnconfinedTestDispatcher
import com.android.systemui.log.core.Logger
import com.android.systemui.log.logcatLogBuffer
import com.android.systemui.testKosmos
+import com.android.systemui.util.time.fakeSystemClock
import com.google.common.truth.Truth.assertThat
import org.junit.Test
import org.junit.runner.RunWith
@@ -136,6 +137,118 @@ class ActivityManagerRepositoryTest : SysuiTestCase() {
assertThat(latest).isFalse()
}
+ @Test
+ fun createAppVisibilityFlow_fetchesInitialValue_trueWithLastVisibleTime() =
+ kosmos.runTest {
+ whenever(activityManager.getUidImportance(THIS_UID)).thenReturn(IMPORTANCE_FOREGROUND)
+ fakeSystemClock.setCurrentTimeMillis(5000)
+
+ val latest by
+ collectLastValue(underTest.createAppVisibilityFlow(THIS_UID, logger, LOG_TAG))
+
+ assertThat(latest!!.isAppCurrentlyVisible).isTrue()
+ assertThat(latest!!.lastAppVisibleTime).isEqualTo(5000)
+ }
+
+ @Test
+ fun createAppVisibilityFlow_fetchesInitialValue_falseWithoutLastVisibleTime() =
+ kosmos.runTest {
+ whenever(activityManager.getUidImportance(THIS_UID)).thenReturn(IMPORTANCE_GONE)
+ fakeSystemClock.setCurrentTimeMillis(5000)
+
+ val latest by
+ collectLastValue(underTest.createAppVisibilityFlow(THIS_UID, logger, LOG_TAG))
+
+ assertThat(latest!!.isAppCurrentlyVisible).isFalse()
+ assertThat(latest!!.lastAppVisibleTime).isNull()
+ }
+
+ @Test
+ fun createAppVisibilityFlow_getsImportanceUpdates_updatesLastVisibleTimeOnlyWhenVisible() =
+ kosmos.runTest {
+ whenever(activityManager.getUidImportance(THIS_UID)).thenReturn(IMPORTANCE_GONE)
+ fakeSystemClock.setCurrentTimeMillis(5000)
+ val latest by
+ collectLastValue(underTest.createAppVisibilityFlow(THIS_UID, logger, LOG_TAG))
+
+ assertThat(latest!!.isAppCurrentlyVisible).isFalse()
+ assertThat(latest!!.lastAppVisibleTime).isNull()
+
+ val listenerCaptor = argumentCaptor<ActivityManager.OnUidImportanceListener>()
+ verify(activityManager).addOnUidImportanceListener(listenerCaptor.capture(), any())
+ val listener = listenerCaptor.firstValue
+
+ // WHEN the app becomes visible
+ fakeSystemClock.setCurrentTimeMillis(7000)
+ listener.onUidImportance(THIS_UID, IMPORTANCE_FOREGROUND)
+
+ // THEN the status and lastAppVisibleTime are updated
+ assertThat(latest!!.isAppCurrentlyVisible).isTrue()
+ assertThat(latest!!.lastAppVisibleTime).isEqualTo(7000)
+
+ // WHEN the app is no longer visible
+ listener.onUidImportance(THIS_UID, IMPORTANCE_TOP_SLEEPING)
+
+ // THEN the lastAppVisibleTime is preserved
+ assertThat(latest!!.isAppCurrentlyVisible).isFalse()
+ assertThat(latest!!.lastAppVisibleTime).isEqualTo(7000)
+
+ // WHEN the app is visible again
+ fakeSystemClock.setCurrentTimeMillis(9000)
+ listener.onUidImportance(THIS_UID, IMPORTANCE_FOREGROUND)
+
+ // THEN the lastAppVisibleTime is updated
+ assertThat(latest!!.isAppCurrentlyVisible).isTrue()
+ assertThat(latest!!.lastAppVisibleTime).isEqualTo(9000)
+ }
+
+ @Test
+ fun createAppVisibilityFlow_ignoresUpdatesForOtherUids() =
+ kosmos.runTest {
+ val latest by
+ collectLastValue(underTest.createAppVisibilityFlow(THIS_UID, logger, LOG_TAG))
+
+ val listenerCaptor = argumentCaptor<ActivityManager.OnUidImportanceListener>()
+ verify(activityManager).addOnUidImportanceListener(listenerCaptor.capture(), any())
+ val listener = listenerCaptor.firstValue
+
+ listener.onUidImportance(THIS_UID, IMPORTANCE_GONE)
+ assertThat(latest!!.isAppCurrentlyVisible).isFalse()
+
+ // WHEN another UID becomes foreground
+ listener.onUidImportance(THIS_UID + 2, IMPORTANCE_FOREGROUND)
+
+ // THEN this UID still stays not visible
+ assertThat(latest!!.isAppCurrentlyVisible).isFalse()
+ }
+
+ @Test
+ fun createAppVisibilityFlow_securityExceptionOnUidRegistration_ok() =
+ kosmos.runTest {
+ whenever(activityManager.getUidImportance(THIS_UID)).thenReturn(IMPORTANCE_GONE)
+ whenever(activityManager.addOnUidImportanceListener(any(), any()))
+ .thenThrow(SecurityException())
+
+ val latest by
+ collectLastValue(underTest.createAppVisibilityFlow(THIS_UID, logger, LOG_TAG))
+
+ // Verify no crash, and we get a value emitted
+ assertThat(latest!!.isAppCurrentlyVisible).isFalse()
+ }
+
+ /** Regression test for b/216248574. */
+ @Test
+ fun createAppVisibilityFlow_getUidImportanceThrowsException_ok() =
+ kosmos.runTest {
+ whenever(activityManager.getUidImportance(any())).thenThrow(SecurityException())
+
+ val latest by
+ collectLastValue(underTest.createAppVisibilityFlow(THIS_UID, logger, LOG_TAG))
+
+ // Verify no crash, and we get a value emitted
+ assertThat(latest!!.isAppCurrentlyVisible).isFalse()
+ }
+
companion object {
private const val THIS_UID = 558
private const val LOG_TAG = "LogTag"
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/animation/GhostedViewTransitionAnimatorControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/animation/GhostedViewTransitionAnimatorControllerTest.kt
index e492c63d095c..052d520ac92f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/animation/GhostedViewTransitionAnimatorControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/animation/GhostedViewTransitionAnimatorControllerTest.kt
@@ -17,16 +17,20 @@
package com.android.systemui.animation
import android.os.HandlerThread
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
import android.testing.TestableLooper
import android.view.View
import android.widget.FrameLayout
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.internal.jank.InteractionJankMonitor
+import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.view.LaunchableFrameLayout
import com.google.common.truth.Truth.assertThat
import org.junit.Assert.assertThrows
+import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -40,6 +44,14 @@ class GhostedViewTransitionAnimatorControllerTest : SysuiTestCase() {
}
private val interactionJankMonitor = FakeInteractionJankMonitor()
+ private lateinit var transitionRegistry: FakeViewTransitionRegistry
+ private lateinit var transitioningView: View
+
+ @Before
+ fun setup() {
+ transitioningView = LaunchableFrameLayout(mContext)
+ transitionRegistry = FakeViewTransitionRegistry()
+ }
@Test
fun animatingOrphanViewDoesNotCrash() {
@@ -67,7 +79,7 @@ class GhostedViewTransitionAnimatorControllerTest : SysuiTestCase() {
parent.addView((launchView))
val launchController =
GhostedViewTransitionAnimatorController(
- launchView,
+ launchView,
launchCujType = LAUNCH_CUJ,
returnCujType = RETURN_CUJ,
interactionJankMonitor = interactionJankMonitor
@@ -96,6 +108,26 @@ class GhostedViewTransitionAnimatorControllerTest : SysuiTestCase() {
assertThat(interactionJankMonitor.finished).containsExactly(LAUNCH_CUJ, RETURN_CUJ)
}
+ @EnableFlags(Flags.FLAG_DECOUPLE_VIEW_CONTROLLER_IN_ANIMLIB)
+ @Test
+ fun testViewsAreRegisteredInTransitionRegistry() {
+ GhostedViewTransitionAnimatorController(
+ transitioningView = transitioningView,
+ transitionRegistry = transitionRegistry
+ )
+ assertThat(transitionRegistry.registry).isNotEmpty()
+ }
+
+ @DisableFlags(Flags.FLAG_DECOUPLE_VIEW_CONTROLLER_IN_ANIMLIB)
+ @Test
+ fun testNotUseRegistryIfDecouplingFlagDisabled() {
+ GhostedViewTransitionAnimatorController(
+ transitioningView = transitioningView,
+ transitionRegistry = transitionRegistry
+ )
+ assertThat(transitionRegistry.registry).isEmpty()
+ }
+
/**
* A fake implementation of [InteractionJankMonitor] which stores ongoing and finished CUJs and
* allows inspection.
@@ -117,4 +149,30 @@ class GhostedViewTransitionAnimatorControllerTest : SysuiTestCase() {
return true
}
}
+
+ private class FakeViewTransitionRegistry : IViewTransitionRegistry {
+
+ val registry = mutableMapOf<ViewTransitionToken, View>()
+
+ override fun register(token: ViewTransitionToken, view: View) {
+ registry[token] = view
+ view.setTag(R.id.tag_view_transition_token, token)
+ }
+
+ override fun unregister(token: ViewTransitionToken) {
+ registry.remove(token)?.setTag(R.id.tag_view_transition_token, null)
+ }
+
+ override fun getView(token: ViewTransitionToken): View? {
+ return registry[token]
+ }
+
+ override fun getViewToken(view: View): ViewTransitionToken? {
+ return view.getTag(R.id.tag_view_transition_token) as? ViewTransitionToken
+ }
+
+ override fun onRegistryUpdate() {
+ //empty
+ }
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModelTest.kt
index 1a3606e413cc..da25bcac6c95 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModelTest.kt
@@ -35,6 +35,7 @@ import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
@@ -44,6 +45,7 @@ import org.junit.runner.RunWith
import platform.test.runner.parameterized.ParameterizedAndroidJunit4
import platform.test.runner.parameterized.Parameters
+@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(ParameterizedAndroidJunit4::class)
class CommunalTransitionViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
@@ -65,7 +67,7 @@ class CommunalTransitionViewModelTest(flags: FlagsParameterization) : SysuiTestC
private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
private val communalSceneRepository = kosmos.fakeCommunalSceneRepository
- private val sceneInteractor = kosmos.sceneInteractor
+ private val sceneInteractor by lazy { kosmos.sceneInteractor }
private val underTest: CommunalTransitionViewModel by lazy {
kosmos.communalTransitionViewModel
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorTest.kt
index 329627af8ec2..e36d2455d316 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorTest.kt
@@ -61,6 +61,7 @@ import com.android.systemui.user.data.model.SelectionStatus
import com.android.systemui.user.data.repository.fakeUserRepository
import com.android.systemui.util.mockito.eq
import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.test.TestScope
@@ -73,6 +74,7 @@ import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
+@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
class DeviceEntryFaceAuthInteractorTest : SysuiTestCase() {
@@ -80,21 +82,26 @@ class DeviceEntryFaceAuthInteractorTest : SysuiTestCase() {
private val testScope: TestScope = kosmos.testScope
private lateinit var underTest: SystemUIDeviceEntryFaceAuthInteractor
+
private val bouncerRepository = kosmos.fakeKeyguardBouncerRepository
private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
- private val keyguardTransitionInteractor = kosmos.keyguardTransitionInteractor
private val faceAuthRepository = kosmos.fakeDeviceEntryFaceAuthRepository
private val fakeUserRepository = kosmos.fakeUserRepository
private val facePropertyRepository = kosmos.facePropertyRepository
- private val fakeDeviceEntryFingerprintAuthInteractor =
- kosmos.deviceEntryFingerprintAuthInteractor
- private val powerInteractor = kosmos.powerInteractor
private val fakeBiometricSettingsRepository = kosmos.fakeBiometricSettingsRepository
- private val keyguardUpdateMonitor = kosmos.keyguardUpdateMonitor
+ private val keyguardUpdateMonitor by lazy { kosmos.keyguardUpdateMonitor }
private val faceWakeUpTriggersConfig = kosmos.fakeFaceWakeUpTriggersConfig
private val trustManager = kosmos.trustManager
- private val deviceEntryFaceAuthStatusInteractor = kosmos.deviceEntryFaceAuthStatusInteractor
+
+ private val keyguardTransitionInteractor by lazy { kosmos.keyguardTransitionInteractor }
+ private val fakeDeviceEntryFingerprintAuthInteractor by lazy {
+ kosmos.deviceEntryFingerprintAuthInteractor
+ }
+ private val powerInteractor by lazy { kosmos.powerInteractor }
+ private val deviceEntryFaceAuthStatusInteractor by lazy {
+ kosmos.deviceEntryFaceAuthStatusInteractor
+ }
@Before
fun setup() {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/inputdevice/tutorial/domain/interactor/TutorialNotificationCoordinatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/inputdevice/tutorial/domain/interactor/TutorialNotificationCoordinatorTest.kt
index ff5fa3959c6d..7374f181760c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/inputdevice/tutorial/domain/interactor/TutorialNotificationCoordinatorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/inputdevice/tutorial/domain/interactor/TutorialNotificationCoordinatorTest.kt
@@ -37,7 +37,6 @@ import com.android.systemui.testKosmos
import com.android.systemui.touchpad.data.repository.FakeTouchpadRepository
import com.google.common.truth.Truth.assertThat
import kotlin.time.Duration.Companion.hours
-import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.advanceTimeBy
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -51,7 +50,6 @@ import org.mockito.Mockito.times
import org.mockito.junit.MockitoJUnit
import org.mockito.kotlin.any
import org.mockito.kotlin.eq
-import org.mockito.kotlin.firstValue
import org.mockito.kotlin.never
import org.mockito.kotlin.secondValue
import org.mockito.kotlin.verify
@@ -116,7 +114,6 @@ class TutorialNotificationCoordinatorTest : SysuiTestCase() {
fun showTouchpadNotification() = runTestAndClear {
touchpadRepository.setIsAnyTouchpadConnected(true)
testScope.advanceTimeBy(LAUNCH_DELAY)
- mockExistingNotification()
verifyNotification(
R.string.launch_touchpad_tutorial_notification_title,
R.string.launch_touchpad_tutorial_notification_content,
@@ -142,14 +139,10 @@ class TutorialNotificationCoordinatorTest : SysuiTestCase() {
}
@Test
- fun showKeyboardNotificationThenDisconnectKeyboard() = runTestAndClear {
+ fun cancelKeyboardNotificationWhenKeyboardDisconnects() = runTestAndClear {
keyboardRepository.setIsAnyKeyboardConnected(true)
testScope.advanceTimeBy(LAUNCH_DELAY)
- verifyNotification(
- R.string.launch_keyboard_tutorial_notification_title,
- R.string.launch_keyboard_tutorial_notification_content,
- )
- mockExistingNotification()
+ mockNotifications(hasTutorialNotification = true)
// After the keyboard is disconnected, i.e. there is nothing connected, the notification
// should be cancelled
@@ -158,22 +151,71 @@ class TutorialNotificationCoordinatorTest : SysuiTestCase() {
}
@Test
- fun showKeyboardTouchpadNotificationThenDisconnectKeyboard() = runTestAndClear {
+ fun updateNotificationToTouchpadOnlyWhenKeyboardDisconnects() = runTestAndClear {
keyboardRepository.setIsAnyKeyboardConnected(true)
touchpadRepository.setIsAnyTouchpadConnected(true)
testScope.advanceTimeBy(LAUNCH_DELAY)
- mockExistingNotification()
+ mockNotifications(hasTutorialNotification = true)
+
keyboardRepository.setIsAnyKeyboardConnected(false)
verify(notificationManager, times(2))
.notifyAsUser(eq(TAG), eq(NOTIFICATION_ID), notificationCaptor.capture(), any())
- // Connect both device and the first notification is for both
- notificationCaptor.firstValue.verify(
+ // Connect both device and the first notification is for both. After the keyboard is
+ // disconnected, i.e. with only the touchpad left, the notification should be update to
+ // touchpad only
+ notificationCaptor.secondValue.verify(
+ R.string.launch_touchpad_tutorial_notification_title,
+ R.string.launch_touchpad_tutorial_notification_content,
+ )
+ }
+
+ @Test
+ fun updateNotificationToBothDevicesWhenTouchpadConnects() = runTestAndClear {
+ keyboardRepository.setIsAnyKeyboardConnected(true)
+ testScope.advanceTimeBy(LAUNCH_DELAY)
+ mockNotifications(hasTutorialNotification = true)
+
+ touchpadRepository.setIsAnyTouchpadConnected(true)
+
+ verify(notificationManager, times(2))
+ .notifyAsUser(eq(TAG), eq(NOTIFICATION_ID), notificationCaptor.capture(), any())
+ // Update the notification from keyboard to both devices
+ notificationCaptor.secondValue.verify(
R.string.launch_keyboard_touchpad_tutorial_notification_title,
R.string.launch_keyboard_touchpad_tutorial_notification_content,
)
- // After the keyboard is disconnected, i.e. with only the touchpad left, the notification
- // should be update to the one for only touchpad
+ }
+
+ @Test
+ fun doNotShowUpdateNotificationWhenInitialNotificationIsDismissed() = runTestAndClear {
+ keyboardRepository.setIsAnyKeyboardConnected(true)
+ testScope.advanceTimeBy(LAUNCH_DELAY)
+ mockNotifications(hasTutorialNotification = false)
+
+ touchpadRepository.setIsAnyTouchpadConnected(true)
+
+ // There's only one notification being shown throughout this scenario. We don't update the
+ // notification because it has been dismissed when the touchpad connects
+ verifyNotification(
+ R.string.launch_keyboard_tutorial_notification_title,
+ R.string.launch_keyboard_tutorial_notification_content,
+ )
+ }
+
+ @Test
+ fun showTouchpadNotificationAfterDelayAndKeyboardNotificationIsDismissed() = runTestAndClear {
+ keyboardRepository.setIsAnyKeyboardConnected(true)
+ testScope.advanceTimeBy(LAUNCH_DELAY)
+ mockNotifications(hasTutorialNotification = false)
+
+ touchpadRepository.setIsAnyTouchpadConnected(true)
+ testScope.advanceTimeBy(LAUNCH_DELAY)
+
+ verify(notificationManager, times(2))
+ .notifyAsUser(eq(TAG), eq(NOTIFICATION_ID), notificationCaptor.capture(), any())
+ // The keyboard notification was shown and dismissed; the touchpad notification is scheduled
+ // independently
notificationCaptor.secondValue.verify(
R.string.launch_touchpad_tutorial_notification_title,
R.string.launch_touchpad_tutorial_notification_content,
@@ -189,10 +231,12 @@ class TutorialNotificationCoordinatorTest : SysuiTestCase() {
}
}
- // Assume that there's an existing notification when the updater checks activeNotifications
- private fun mockExistingNotification() {
+ // Mock an active notification, so when the updater checks activeNotifications, it returns one
+ // with the given id. Otherwise, return an empty array (i.e. no active notifications)
+ private fun mockNotifications(hasTutorialNotification: Boolean) {
whenever(notification.id).thenReturn(NOTIFICATION_ID)
- whenever(notificationManager.activeNotifications).thenReturn(arrayOf(notification))
+ val notifications = if (hasTutorialNotification) arrayOf(notification) else emptyArray()
+ whenever(notificationManager.activeNotifications).thenReturn(notifications)
}
private fun verifyNotification(@StringRes titleResId: Int, @StringRes contentResId: Int) {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/source/TestShortcuts.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/source/TestShortcuts.kt
index 183e4d6f624b..98486a22854a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/source/TestShortcuts.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/source/TestShortcuts.kt
@@ -541,7 +541,7 @@ object TestShortcuts {
simpleShortcutCategory(System, "System apps", "Take a note"),
simpleShortcutCategory(System, "System controls", "Take screenshot"),
simpleShortcutCategory(System, "System controls", "Go back"),
- simpleShortcutCategory(MultiTasking, "Split screen", "Switch to full screen"),
+ simpleShortcutCategory(MultiTasking, "Split screen", "Use full screen"),
simpleShortcutCategory(
MultiTasking,
"Split screen",
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 6eef5eb09812..b91e259003a3 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
@@ -337,6 +337,43 @@ class ShortcutCustomizationViewModelTest : SysuiTestCase() {
}
}
+ @Test
+ fun uiState_pressedKeysDescription_emptyByDefault() {
+ testScope.runTest {
+ val uiState by collectLastValue(viewModel.shortcutCustomizationUiState)
+ viewModel.onShortcutCustomizationRequested(standardAddShortcutRequest)
+
+ assertThat((uiState as AddShortcutDialog).pressedKeysDescription).isEmpty()
+ }
+ }
+
+ @Test
+ fun uiState_pressedKeysDescription_updatesToNonEmptyDescriptionWhenKeyCombinationIsPressed() {
+ testScope.runTest {
+ val uiState by collectLastValue(viewModel.shortcutCustomizationUiState)
+ viewModel.onShortcutCustomizationRequested(standardAddShortcutRequest)
+ viewModel.onShortcutKeyCombinationSelected(keyDownEventWithActionKeyPressed)
+ viewModel.onShortcutKeyCombinationSelected(keyUpEventWithActionKeyPressed)
+
+ // Note that Action Key is excluded as it's already displayed on the UI
+ assertThat((uiState as AddShortcutDialog).pressedKeysDescription)
+ .isEqualTo("Ctrl, plus A")
+ }
+ }
+
+ @Test
+ fun uiState_pressedKeysDescription_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).pressedKeysDescription).isEmpty()
+ }
+ }
+
private suspend fun openAddShortcutDialogAndSetShortcut() {
openAddShortcutDialogAndPressKeyCombination()
viewModel.onSetShortcut()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractorTest.kt
index 9b80ca303cd3..63229dbb47a4 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractorTest.kt
@@ -229,6 +229,39 @@ class FromAlternateBouncerTransitionInteractorTest(flags: FlagsParameterization)
}
@Test
+ @EnableFlags(FLAG_GLANCEABLE_HUB_V2)
+ fun transitionToDreaming() =
+ kosmos.runTest {
+ fakePowerRepository.updateWakefulness(
+ WakefulnessState.AWAKE,
+ WakeSleepReason.POWER_BUTTON,
+ WakeSleepReason.POWER_BUTTON,
+ false,
+ )
+ fakeKeyguardRepository.setKeyguardOccluded(false)
+ fakeKeyguardBouncerRepository.setAlternateVisible(true)
+ runCurrent()
+
+ transitionRepository.sendTransitionSteps(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.ALTERNATE_BOUNCER,
+ testScope,
+ )
+ reset(transitionRepository)
+
+ fakeKeyguardRepository.setKeyguardOccluded(true)
+ fakeKeyguardRepository.setDreaming(true)
+ fakeKeyguardBouncerRepository.setAlternateVisible(false)
+ testScope.advanceTimeBy(200) // advance past delay
+
+ assertThat(transitionRepository)
+ .startedTransition(
+ from = KeyguardState.ALTERNATE_BOUNCER,
+ to = KeyguardState.DREAMING,
+ )
+ }
+
+ @Test
@DisableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
fun transitionToGone_whenOpeningGlanceableHubEditMode() =
testScope.runTest {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractorTest.kt
index 282bebcd629a..a08c0dea6fa6 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractorTest.kt
@@ -30,6 +30,7 @@ import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepos
import com.android.systemui.keyguard.data.repository.keyguardClockRepository
import com.android.systemui.keyguard.data.repository.keyguardRepository
import com.android.systemui.keyguard.shared.model.ClockSize
+import com.android.systemui.keyguard.shared.model.ClockSizeSetting
import com.android.systemui.keyguard.shared.model.DozeStateModel
import com.android.systemui.keyguard.shared.model.DozeTransitionModel
import com.android.systemui.keyguard.shared.model.KeyguardState
@@ -68,6 +69,7 @@ class KeyguardClockInteractorTest : SysuiTestCase() {
fun clockSize_sceneContainerFlagOff_basedOnRepository() =
testScope.runTest {
val value by collectLastValue(underTest.clockSize)
+ kosmos.fakeKeyguardClockRepository.setSelectedClockSize(ClockSizeSetting.DYNAMIC)
kosmos.keyguardClockRepository.setClockSize(ClockSize.LARGE)
assertThat(value).isEqualTo(ClockSize.LARGE)
@@ -76,6 +78,17 @@ class KeyguardClockInteractorTest : SysuiTestCase() {
}
@Test
+ @DisableSceneContainer
+ fun clockSize_sceneContainerFlagOff_smallClockSettingSelected_SMALL() =
+ testScope.runTest {
+ val value by collectLastValue(underTest.clockSize)
+ kosmos.fakeKeyguardClockRepository.setSelectedClockSize(ClockSizeSetting.SMALL)
+ kosmos.keyguardClockRepository.setClockSize(ClockSize.LARGE)
+
+ assertThat(value).isEqualTo(ClockSize.SMALL)
+ }
+
+ @Test
@EnableSceneContainer
fun clockSize_forceSmallClock_SMALL() =
testScope.runTest {
@@ -91,61 +104,80 @@ class KeyguardClockInteractorTest : SysuiTestCase() {
@Test
@EnableSceneContainer
- fun clockSize_SceneContainerFlagOn_shadeModeSingle_hasNotifs_SMALL() =
+ fun clockSize_sceneContainerFlagOn_shadeModeSingle_hasNotifs_SMALL() =
testScope.runTest {
val value by collectLastValue(underTest.clockSize)
kosmos.shadeRepository.setShadeLayoutWide(false)
kosmos.activeNotificationListRepository.setActiveNotifs(1)
+
assertThat(value).isEqualTo(ClockSize.SMALL)
}
@Test
@EnableSceneContainer
- fun clockSize_SceneContainerFlagOn_shadeModeSingle_hasMedia_SMALL() =
+ fun clockSize_sceneContainerFlagOn_shadeModeSingle_hasMedia_SMALL() =
testScope.runTest {
val value by collectLastValue(underTest.clockSize)
kosmos.shadeRepository.setShadeLayoutWide(false)
val userMedia = MediaData().copy(active = true)
kosmos.mediaFilterRepository.addSelectedUserMediaEntry(userMedia)
+
assertThat(value).isEqualTo(ClockSize.SMALL)
}
@Test
@EnableSceneContainer
- fun clockSize_SceneContainerFlagOn_shadeModeSplit_isMediaVisible_SMALL() =
+ fun clockSize_sceneContainerFlagOn_shadeModeSplit_isMediaVisible_SMALL() =
testScope.runTest {
val value by collectLastValue(underTest.clockSize)
val userMedia = MediaData().copy(active = true)
kosmos.shadeRepository.setShadeLayoutWide(true)
kosmos.mediaFilterRepository.addSelectedUserMediaEntry(userMedia)
kosmos.keyguardRepository.setIsDozing(false)
+
assertThat(value).isEqualTo(ClockSize.SMALL)
}
@Test
@EnableSceneContainer
- fun clockSize_SceneContainerFlagOn_shadeModeSplit_noMedia_LARGE() =
+ fun clockSize_sceneContainerFlagOn_shadeModeSplit_noMedia_LARGE() =
testScope.runTest {
val value by collectLastValue(underTest.clockSize)
kosmos.shadeRepository.setShadeLayoutWide(true)
kosmos.keyguardRepository.setIsDozing(false)
+
assertThat(value).isEqualTo(ClockSize.LARGE)
}
@Test
@EnableSceneContainer
- fun clockSize_SceneContainerFlagOn_shadeModeSplit_isDozing_LARGE() =
+ fun clockSize_sceneContainerFlagOn_shadeModeSplit_isDozing_LARGE() =
testScope.runTest {
val value by collectLastValue(underTest.clockSize)
val userMedia = MediaData().copy(active = true)
kosmos.shadeRepository.setShadeLayoutWide(true)
kosmos.mediaFilterRepository.addSelectedUserMediaEntry(userMedia)
kosmos.keyguardRepository.setIsDozing(true)
+
assertThat(value).isEqualTo(ClockSize.LARGE)
}
@Test
@EnableSceneContainer
+ fun clockSize_sceneContainerFlagOn_shadeModeSplit_smallClockSettingSelectd_SMALL() =
+ testScope.runTest {
+ val value by collectLastValue(underTest.clockSize)
+ val userMedia = MediaData().copy(active = true)
+ kosmos.fakeKeyguardClockRepository.setSelectedClockSize(ClockSizeSetting.SMALL)
+ kosmos.shadeRepository.setShadeLayoutWide(true)
+ kosmos.mediaFilterRepository.addSelectedUserMediaEntry(userMedia)
+ kosmos.keyguardRepository.setIsDozing(true)
+
+ assertThat(value).isEqualTo(ClockSize.SMALL)
+ }
+
+ @Test
+ @EnableSceneContainer
fun clockShouldBeCentered_sceneContainerFlagOn_notSplitMode_true() =
testScope.runTest {
val value by collectLastValue(underTest.clockShouldBeCentered)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt
index 29e95cd911f8..0b42898d82ae 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt
@@ -23,6 +23,7 @@ import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectValues
import com.android.systemui.flags.DisableSceneContainer
import com.android.systemui.flags.EnableSceneContainer
+import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.Edge
import com.android.systemui.keyguard.shared.model.KeyguardState
@@ -46,20 +47,31 @@ import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import junit.framework.Assert.assertEquals
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Assert.assertThrows
+import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
class KeyguardTransitionInteractorTest : SysuiTestCase() {
- val kosmos = testKosmos()
- val underTest = kosmos.keyguardTransitionInteractor
- val repository = kosmos.fakeKeyguardTransitionRepository
- val testScope = kosmos.testScope
+
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+
+ private lateinit var repository: FakeKeyguardTransitionRepository
+ private lateinit var underTest: KeyguardTransitionInteractor
+
+ @Before
+ fun setup() {
+ repository = kosmos.fakeKeyguardTransitionRepository
+ underTest = kosmos.keyguardTransitionInteractor
+ }
@Test
fun transitionCollectorsReceivesOnlyAppropriateEvents() =
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/scenetransition/LockscreenSceneTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/scenetransition/LockscreenSceneTransitionInteractorTest.kt
index 26fe379f00bf..3cff0fc96af4 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/scenetransition/LockscreenSceneTransitionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/scenetransition/LockscreenSceneTransitionInteractorTest.kt
@@ -1358,6 +1358,45 @@ class LockscreenSceneTransitionInteractorTest : SysuiTestCase() {
)
}
+ /**
+ * When a transition away from the lockscreen is interrupted by an `Idle(Lockscreen)`, a
+ * `sceneState` that was set during the transition is consumed and passed to KTF.
+ */
+ @Test
+ fun transition_from_ls_scene_sceneStateSet_then_interrupted_by_idle_on_ls() =
+ testScope.runTest {
+ val currentStep by collectLastValue(kosmos.realKeyguardTransitionRepository.transitions)
+ sceneTransitions.value =
+ ObservableTransitionState.Transition(
+ Scenes.Lockscreen,
+ Scenes.Gone,
+ flowOf(Scenes.Lockscreen),
+ progress,
+ false,
+ flowOf(false),
+ )
+ progress.value = 0.4f
+ assertTransition(
+ step = currentStep!!,
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.UNDEFINED,
+ state = TransitionState.RUNNING,
+ progress = 0.4f,
+ )
+
+ val sceneState = KeyguardState.AOD
+ underTest.onSceneAboutToChange(toScene = Scenes.Lockscreen, sceneState = sceneState)
+ sceneTransitions.value = ObservableTransitionState.Idle(Scenes.Lockscreen)
+
+ assertTransition(
+ step = currentStep!!,
+ from = KeyguardState.UNDEFINED,
+ to = KeyguardState.AOD,
+ state = TransitionState.FINISHED,
+ progress = 1f,
+ )
+ }
+
private fun assertTransition(
step: TransitionStep,
from: KeyguardState? = null,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DozingToDreamingTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DozingToDreamingTransitionViewModelTest.kt
deleted file mode 100644
index 052dfd52887f..000000000000
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DozingToDreamingTransitionViewModelTest.kt
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Copyright (C) 2025 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.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.fakeKeyguardTransitionRepository
-import com.android.systemui.keyguard.shared.model.KeyguardState
-import com.android.systemui.kosmos.collectValues
-import com.android.systemui.kosmos.runTest
-import com.android.systemui.kosmos.testScope
-import com.android.systemui.testKosmos
-import com.google.common.truth.Truth.assertThat
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@SmallTest
-@RunWith(AndroidJUnit4::class)
-class DozingToDreamingTransitionViewModelTest : SysuiTestCase() {
- val kosmos = testKosmos()
-
- val underTest by lazy { kosmos.dozingToDreamingTransitionViewModel }
-
- @Test
- fun notificationShadeAlpha() =
- kosmos.runTest {
- val values by collectValues(underTest.notificationAlpha)
- assertThat(values).isEmpty()
-
- fakeKeyguardTransitionRepository.sendTransitionSteps(
- from = KeyguardState.DOZING,
- to = KeyguardState.DREAMING,
- testScope,
- )
-
- assertThat(values).isNotEmpty()
- values.forEach { assertThat(it).isEqualTo(0) }
- }
-}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelTest.kt
index 8a599a1bd948..20d015f4d77c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelTest.kt
@@ -27,7 +27,6 @@ import com.android.systemui.keyguard.data.repository.fakeKeyguardClockRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.keyguard.data.repository.keyguardClockRepository
import com.android.systemui.keyguard.shared.model.ClockSize
-import com.android.systemui.keyguard.shared.model.ClockSizeSetting
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel.ClockLayout
import com.android.systemui.kosmos.testScope
@@ -55,17 +54,18 @@ import platform.test.runner.parameterized.Parameters
@SmallTest
@RunWith(ParameterizedAndroidJunit4::class)
class KeyguardClockViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
- val kosmos = testKosmos()
- val testScope = kosmos.testScope
- val underTest by lazy { kosmos.keyguardClockViewModel }
- val res = context.resources
- @Mock lateinit var clockController: ClockController
- @Mock lateinit var largeClock: ClockFaceController
- @Mock lateinit var smallClock: ClockFaceController
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+ private val underTest by lazy { kosmos.keyguardClockViewModel }
+ private val res = context.resources
- var config = ClockConfig("TEST", "Test", "")
- var faceConfig = ClockFaceConfig()
+ @Mock private lateinit var clockController: ClockController
+ @Mock private lateinit var largeClock: ClockFaceController
+ @Mock private lateinit var smallClock: ClockFaceController
+
+ private var config = ClockConfig("TEST", "Test", "")
+ private var faceConfig = ClockFaceConfig()
init {
mSetFlagsRule.setFlagsParameterization(flags)
@@ -196,35 +196,6 @@ class KeyguardClockViewModelTest(flags: FlagsParameterization) : SysuiTestCase()
}
@Test
- fun testClockSize_alwaysSmallClockSize() =
- testScope.runTest {
- val value by collectLastValue(underTest.clockSize)
-
- with(kosmos) {
- fakeKeyguardClockRepository.setSelectedClockSize(ClockSizeSetting.SMALL)
- keyguardClockRepository.setClockSize(ClockSize.LARGE)
- }
-
- assertThat(value).isEqualTo(ClockSize.SMALL)
- }
-
- @Test
- @DisableSceneContainer
- fun testClockSize_dynamicClockSize() =
- testScope.runTest {
- with(kosmos) {
- val value by collectLastValue(underTest.clockSize)
- fakeKeyguardClockRepository.setSelectedClockSize(ClockSizeSetting.DYNAMIC)
-
- keyguardClockRepository.setClockSize(ClockSize.SMALL)
- assertThat(value).isEqualTo(ClockSize.SMALL)
-
- keyguardClockRepository.setClockSize(ClockSize.LARGE)
- assertThat(value).isEqualTo(ClockSize.LARGE)
- }
- }
-
- @Test
fun isLargeClockVisible_whenLargeClockSize_isTrue() =
testScope.runTest {
val value by collectLastValue(underTest.isLargeClockVisible)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelTest.kt
index 25c157208513..0b34a01a0fe0 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelTest.kt
@@ -23,6 +23,7 @@ import com.android.systemui.SysuiTestCase
import com.android.systemui.biometrics.authController
import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
import com.android.systemui.flags.DisableSceneContainer
+import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.flags.andSceneContainer
import com.android.systemui.keyguard.data.repository.fakeKeyguardClockRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
@@ -47,6 +48,7 @@ import com.android.systemui.shade.data.repository.shadeRepository
import com.android.systemui.shade.domain.interactor.enableDualShade
import com.android.systemui.shade.domain.interactor.enableSingleShade
import com.android.systemui.shade.domain.interactor.enableSplitShade
+import com.android.systemui.shade.domain.interactor.shadeModeInteractor
import com.android.systemui.shade.shared.model.ShadeMode
import com.android.systemui.testKosmos
import com.android.systemui.unfold.fakeUnfoldTransitionProgressProvider
@@ -123,6 +125,7 @@ class LockscreenContentViewModelTest(flags: FlagsParameterization) : SysuiTestCa
}
@Test
+ @EnableSceneContainer
fun notificationsPlacement_dualShadeSmallClock_below() =
kosmos.runTest {
setupState(
@@ -135,6 +138,7 @@ class LockscreenContentViewModelTest(flags: FlagsParameterization) : SysuiTestCa
}
@Test
+ @EnableSceneContainer
fun notificationsPlacement_dualShadeLargeClock_topStart() =
kosmos.runTest {
setupState(
@@ -156,6 +160,7 @@ class LockscreenContentViewModelTest(flags: FlagsParameterization) : SysuiTestCa
}
@Test
+ @EnableSceneContainer
fun areNotificationsVisible_dualShadeWideOnLockscreen_true() =
kosmos.runTest {
setupState(
@@ -298,6 +303,7 @@ class LockscreenContentViewModelTest(flags: FlagsParameterization) : SysuiTestCa
) {
val isShadeLayoutWide by collectLastValue(kosmos.shadeRepository.isShadeLayoutWide)
val collectedClockSize by collectLastValue(kosmos.keyguardClockInteractor.clockSize)
+ val collectedShadeMode by collectLastValue(kosmos.shadeModeInteractor.shadeMode)
when (shadeMode) {
ShadeMode.Dual -> kosmos.enableDualShade(wideLayout = shadeLayoutWide)
ShadeMode.Single -> kosmos.enableSingleShade()
@@ -309,6 +315,7 @@ class LockscreenContentViewModelTest(flags: FlagsParameterization) : SysuiTestCa
if (shadeLayoutWide != null) {
assertThat(isShadeLayoutWide).isEqualTo(shadeLayoutWide)
}
+ assertThat(collectedShadeMode).isEqualTo(shadeMode)
assertThat(collectedClockSize).isEqualTo(clockSize)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/dialog/MediaOutputAdapterLegacyTest.java
index f5a71113235a..a7a0c24e2163 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/dialog/MediaOutputAdapterLegacyTest.java
@@ -33,8 +33,6 @@ import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-import android.app.WallpaperColors;
-import android.graphics.Bitmap;
import android.graphics.drawable.Icon;
import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.EnableFlags;
@@ -68,7 +66,7 @@ import java.util.stream.Collectors;
@SmallTest
@RunWith(AndroidJUnit4.class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
-public class MediaOutputAdapterTest extends SysuiTestCase {
+public class MediaOutputAdapterLegacyTest extends SysuiTestCase {
private static final String TEST_DEVICE_NAME_1 = "test_device_name_1";
private static final String TEST_DEVICE_NAME_2 = "test_device_name_2";
@@ -92,8 +90,8 @@ public class MediaOutputAdapterTest extends SysuiTestCase {
@Captor
private ArgumentCaptor<SeekBar.OnSeekBarChangeListener> mOnSeekBarChangeListenerCaptor;
- private MediaOutputAdapter mMediaOutputAdapter;
- private MediaOutputAdapter.MediaDeviceViewHolder mViewHolder;
+ private MediaOutputAdapterLegacy mMediaOutputAdapter;
+ private MediaOutputAdapterLegacy.MediaDeviceViewHolderLegacy mViewHolder;
private List<MediaDevice> mMediaDevices = new ArrayList<>();
private List<MediaItem> mMediaItems = new ArrayList<>();
MediaOutputSeekbar mSpyMediaOutputSeekbar;
@@ -124,9 +122,9 @@ public class MediaOutputAdapterTest extends SysuiTestCase {
mMediaItems.add(MediaItem.createDeviceMediaItem(mMediaDevice1, true));
mMediaItems.add(MediaItem.createDeviceMediaItem(mMediaDevice2, false));
- mMediaOutputAdapter = new MediaOutputAdapter(mMediaSwitchingController);
+ mMediaOutputAdapter = new MediaOutputAdapterLegacy(mMediaSwitchingController);
mMediaOutputAdapter.updateItems();
- mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter
+ mViewHolder = (MediaOutputAdapterLegacy.MediaDeviceViewHolderLegacy) mMediaOutputAdapter
.onCreateViewHolder(new LinearLayout(mContext), 0);
mSpyMediaOutputSeekbar = spy(mViewHolder.mSeekBar);
}
@@ -150,9 +148,9 @@ public class MediaOutputAdapterTest extends SysuiTestCase {
@Test
public void onBindViewHolder_bindPairNew_verifyView() {
- mMediaOutputAdapter = new MediaOutputAdapter(mMediaSwitchingController);
+ mMediaOutputAdapter = new MediaOutputAdapterLegacy(mMediaSwitchingController);
mMediaOutputAdapter.updateItems();
- mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter
+ mViewHolder = (MediaOutputAdapterLegacy.MediaDeviceViewHolderLegacy) mMediaOutputAdapter
.onCreateViewHolder(new LinearLayout(mContext), 0);
mMediaItems.add(MediaItem.createPairNewDeviceMediaItem());
mMediaItems.add(MediaItem.createPairNewDeviceMediaItem());
@@ -175,9 +173,9 @@ public class MediaOutputAdapterTest extends SysuiTestCase {
.map((item) -> item.getMediaDevice().get())
.collect(Collectors.toList()));
when(mMediaSwitchingController.getSessionName()).thenReturn(TEST_SESSION_NAME);
- mMediaOutputAdapter = new MediaOutputAdapter(mMediaSwitchingController);
+ mMediaOutputAdapter = new MediaOutputAdapterLegacy(mMediaSwitchingController);
mMediaOutputAdapter.updateItems();
- mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter
+ mViewHolder = (MediaOutputAdapterLegacy.MediaDeviceViewHolderLegacy) mMediaOutputAdapter
.onCreateViewHolder(new LinearLayout(mContext), 0);
mMediaOutputAdapter.getItemCount();
mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0);
@@ -197,9 +195,9 @@ public class MediaOutputAdapterTest extends SysuiTestCase {
.map((item) -> item.getMediaDevice().get())
.collect(Collectors.toList()));
when(mMediaSwitchingController.getSessionName()).thenReturn(null);
- mMediaOutputAdapter = new MediaOutputAdapter(mMediaSwitchingController);
+ mMediaOutputAdapter = new MediaOutputAdapterLegacy(mMediaSwitchingController);
mMediaOutputAdapter.updateItems();
- mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter
+ mViewHolder = (MediaOutputAdapterLegacy.MediaDeviceViewHolderLegacy) mMediaOutputAdapter
.onCreateViewHolder(new LinearLayout(mContext), 0);
mMediaOutputAdapter.getItemCount();
mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0);
@@ -225,7 +223,7 @@ public class MediaOutputAdapterTest extends SysuiTestCase {
@Test
public void onBindViewHolder_bindNonRemoteConnectedDevice_verifyView() {
when(mMediaSwitchingController.isActiveRemoteDevice(mMediaDevice1)).thenReturn(false);
- mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter
+ mViewHolder = (MediaOutputAdapterLegacy.MediaDeviceViewHolderLegacy) mMediaOutputAdapter
.onCreateViewHolder(new LinearLayout(mContext), 0);
mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0);
@@ -245,7 +243,7 @@ public class MediaOutputAdapterTest extends SysuiTestCase {
when(mMediaSwitchingController.getSelectableMediaDevice())
.thenReturn(ImmutableList.of(mMediaDevice2));
when(mMediaSwitchingController.isCurrentConnectedDeviceRemote()).thenReturn(true);
- mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter
+ mViewHolder = (MediaOutputAdapterLegacy.MediaDeviceViewHolderLegacy) mMediaOutputAdapter
.onCreateViewHolder(new LinearLayout(mContext), 0);
mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0);
@@ -264,7 +262,7 @@ public class MediaOutputAdapterTest extends SysuiTestCase {
when(mMediaSwitchingController.getSelectableMediaDevice())
.thenReturn(ImmutableList.of(mMediaDevice2));
when(mMediaSwitchingController.isCurrentConnectedDeviceRemote()).thenReturn(true);
- mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter
+ mViewHolder = (MediaOutputAdapterLegacy.MediaDeviceViewHolderLegacy) mMediaOutputAdapter
.onCreateViewHolder(new LinearLayout(mContext), 0);
mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0);
@@ -276,7 +274,7 @@ public class MediaOutputAdapterTest extends SysuiTestCase {
public void onBindViewHolder_bindSingleConnectedRemoteDevice_verifyView() {
when(mMediaSwitchingController.getSelectableMediaDevice()).thenReturn(ImmutableList.of());
when(mMediaSwitchingController.isCurrentConnectedDeviceRemote()).thenReturn(true);
- mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter
+ mViewHolder = (MediaOutputAdapterLegacy.MediaDeviceViewHolderLegacy) mMediaOutputAdapter
.onCreateViewHolder(new LinearLayout(mContext), 0);
mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0);
@@ -294,7 +292,7 @@ public class MediaOutputAdapterTest extends SysuiTestCase {
when(mMediaDevice1.hasOngoingSession()).thenReturn(true);
when(mMediaSwitchingController.getSelectableMediaDevice()).thenReturn(ImmutableList.of());
when(mMediaSwitchingController.isCurrentConnectedDeviceRemote()).thenReturn(true);
- mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter
+ mViewHolder = (MediaOutputAdapterLegacy.MediaDeviceViewHolderLegacy) mMediaOutputAdapter
.onCreateViewHolder(new LinearLayout(mContext), 0);
mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0);
@@ -315,7 +313,7 @@ public class MediaOutputAdapterTest extends SysuiTestCase {
when(mMediaDevice1.isHostForOngoingSession()).thenReturn(true);
when(mMediaSwitchingController.getSelectableMediaDevice()).thenReturn(ImmutableList.of());
when(mMediaSwitchingController.isCurrentConnectedDeviceRemote()).thenReturn(true);
- mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter
+ mViewHolder = (MediaOutputAdapterLegacy.MediaDeviceViewHolderLegacy) mMediaOutputAdapter
.onCreateViewHolder(new LinearLayout(mContext), 0);
mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0);
@@ -348,7 +346,7 @@ public class MediaOutputAdapterTest extends SysuiTestCase {
when(mMediaDevice1.isMutingExpectedDevice()).thenReturn(true);
when(mMediaSwitchingController.isCurrentConnectedDeviceRemote()).thenReturn(false);
when(mMediaSwitchingController.isActiveRemoteDevice(mMediaDevice1)).thenReturn(false);
- mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter
+ mViewHolder = (MediaOutputAdapterLegacy.MediaDeviceViewHolderLegacy) mMediaOutputAdapter
.onCreateViewHolder(new LinearLayout(mContext), 0);
mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0);
@@ -498,7 +496,7 @@ public class MediaOutputAdapterTest extends SysuiTestCase {
when(mMediaDevice1.getSubtextString()).thenReturn(TEST_CUSTOM_SUBTEXT);
when(mMediaDevice1.hasOngoingSession()).thenReturn(true);
when(mMediaDevice1.getSelectionBehavior()).thenReturn(SELECTION_BEHAVIOR_GO_TO_APP);
- mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter
+ mViewHolder = (MediaOutputAdapterLegacy.MediaDeviceViewHolderLegacy) mMediaOutputAdapter
.onCreateViewHolder(new LinearLayout(mContext), 0);
mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0);
@@ -522,7 +520,7 @@ public class MediaOutputAdapterTest extends SysuiTestCase {
when(mMediaDevice2.getSubtext()).thenReturn(SUBTEXT_SUBSCRIPTION_REQUIRED);
when(mMediaDevice2.getSubtextString()).thenReturn(deviceStatus);
when(mMediaDevice2.getSelectionBehavior()).thenReturn(SELECTION_BEHAVIOR_GO_TO_APP);
- mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter
+ mViewHolder = (MediaOutputAdapterLegacy.MediaDeviceViewHolderLegacy) mMediaOutputAdapter
.onCreateViewHolder(new LinearLayout(mContext), 0);
mMediaOutputAdapter.onBindViewHolder(mViewHolder, 1);
@@ -545,7 +543,7 @@ public class MediaOutputAdapterTest extends SysuiTestCase {
when(mMediaDevice2.getSubtext()).thenReturn(SUBTEXT_AD_ROUTING_DISALLOWED);
when(mMediaDevice2.getSubtextString()).thenReturn(deviceStatus);
when(mMediaDevice2.getSelectionBehavior()).thenReturn(SELECTION_BEHAVIOR_NONE);
- mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter
+ mViewHolder = (MediaOutputAdapterLegacy.MediaDeviceViewHolderLegacy) mMediaOutputAdapter
.onCreateViewHolder(new LinearLayout(mContext), 0);
mMediaOutputAdapter.onBindViewHolder(mViewHolder, 1);
@@ -567,7 +565,7 @@ public class MediaOutputAdapterTest extends SysuiTestCase {
when(mMediaDevice1.getSubtextString()).thenReturn(TEST_CUSTOM_SUBTEXT);
when(mMediaDevice1.hasOngoingSession()).thenReturn(true);
when(mMediaDevice1.getSelectionBehavior()).thenReturn(SELECTION_BEHAVIOR_GO_TO_APP);
- mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter
+ mViewHolder = (MediaOutputAdapterLegacy.MediaDeviceViewHolderLegacy) mMediaOutputAdapter
.onCreateViewHolder(new LinearLayout(mContext), 0);
mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0);
@@ -627,9 +625,9 @@ public class MediaOutputAdapterTest extends SysuiTestCase {
@Test
public void onItemClick_clickPairNew_verifyLaunchBluetoothPairing() {
- mMediaOutputAdapter = new MediaOutputAdapter(mMediaSwitchingController);
+ mMediaOutputAdapter = new MediaOutputAdapterLegacy(mMediaSwitchingController);
mMediaOutputAdapter.updateItems();
- mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter
+ mViewHolder = (MediaOutputAdapterLegacy.MediaDeviceViewHolderLegacy) mMediaOutputAdapter
.onCreateViewHolder(new LinearLayout(mContext), 0);
mMediaItems.add(MediaItem.createPairNewDeviceMediaItem());
mMediaOutputAdapter.updateItems();
@@ -645,9 +643,9 @@ public class MediaOutputAdapterTest extends SysuiTestCase {
assertThat(mMediaDevice2.getState()).isEqualTo(
LocalMediaManager.MediaDeviceState.STATE_DISCONNECTED);
when(mMediaDevice2.getSelectionBehavior()).thenReturn(SELECTION_BEHAVIOR_TRANSFER);
- mMediaOutputAdapter = new MediaOutputAdapter(mMediaSwitchingController);
+ mMediaOutputAdapter = new MediaOutputAdapterLegacy(mMediaSwitchingController);
mMediaOutputAdapter.updateItems();
- mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter
+ mViewHolder = (MediaOutputAdapterLegacy.MediaDeviceViewHolderLegacy) mMediaOutputAdapter
.onCreateViewHolder(new LinearLayout(mContext), 0);
mMediaOutputAdapter.getItemCount();
mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0);
@@ -663,11 +661,12 @@ public class MediaOutputAdapterTest extends SysuiTestCase {
assertThat(mMediaDevice2.getState()).isEqualTo(
LocalMediaManager.MediaDeviceState.STATE_DISCONNECTED);
when(mMediaDevice2.getSelectionBehavior()).thenReturn(SELECTION_BEHAVIOR_TRANSFER);
- mMediaOutputAdapter = new MediaOutputAdapter(mMediaSwitchingController);
+ mMediaOutputAdapter = new MediaOutputAdapterLegacy(mMediaSwitchingController);
mMediaOutputAdapter.updateItems();
- mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter
+ mViewHolder = (MediaOutputAdapterLegacy.MediaDeviceViewHolderLegacy) mMediaOutputAdapter
.onCreateViewHolder(new LinearLayout(mContext), 0);
- MediaOutputAdapter.MediaDeviceViewHolder spyMediaDeviceViewHolder = spy(mViewHolder);
+ MediaOutputAdapterLegacy.MediaDeviceViewHolderLegacy spyMediaDeviceViewHolder = spy(
+ mViewHolder);
mMediaOutputAdapter.getItemCount();
mMediaOutputAdapter.onBindViewHolder(spyMediaDeviceViewHolder, 0);
@@ -684,11 +683,12 @@ public class MediaOutputAdapterTest extends SysuiTestCase {
when(mMediaDevice2.getState()).thenReturn(
LocalMediaManager.MediaDeviceState.STATE_DISCONNECTED);
when(mMediaDevice2.getSelectionBehavior()).thenReturn(SELECTION_BEHAVIOR_GO_TO_APP);
- mMediaOutputAdapter = new MediaOutputAdapter(mMediaSwitchingController);
+ mMediaOutputAdapter = new MediaOutputAdapterLegacy(mMediaSwitchingController);
mMediaOutputAdapter.updateItems();
- mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter
+ mViewHolder = (MediaOutputAdapterLegacy.MediaDeviceViewHolderLegacy) mMediaOutputAdapter
.onCreateViewHolder(new LinearLayout(mContext), 0);
- MediaOutputAdapter.MediaDeviceViewHolder spyMediaDeviceViewHolder = spy(mViewHolder);
+ MediaOutputAdapterLegacy.MediaDeviceViewHolderLegacy spyMediaDeviceViewHolder = spy(
+ mViewHolder);
mMediaOutputAdapter.onBindViewHolder(spyMediaDeviceViewHolder, 1);
spyMediaDeviceViewHolder.mContainerLayout.performClick();
@@ -715,7 +715,7 @@ public class MediaOutputAdapterTest extends SysuiTestCase {
List<MediaDevice> selectableDevices = new ArrayList<>();
selectableDevices.add(mMediaDevice2);
when(mMediaSwitchingController.getSelectableMediaDevice()).thenReturn(selectableDevices);
- mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter
+ mViewHolder = (MediaOutputAdapterLegacy.MediaDeviceViewHolderLegacy) mMediaOutputAdapter
.onCreateViewHolder(new LinearLayout(mContext), 0);
mMediaOutputAdapter.onBindViewHolder(mViewHolder, 1);
@@ -859,7 +859,7 @@ public class MediaOutputAdapterTest extends SysuiTestCase {
when(mMediaSwitchingController.getSelectedMediaDevice())
.thenReturn(ImmutableList.of(mMediaDevice1));
when(mMediaSwitchingController.isCurrentConnectedDeviceRemote()).thenReturn(true);
- mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter
+ mViewHolder = (MediaOutputAdapterLegacy.MediaDeviceViewHolderLegacy) mMediaOutputAdapter
.onCreateViewHolder(new LinearLayout(mContext), 0);
mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0);
@@ -899,16 +899,6 @@ public class MediaOutputAdapterTest extends SysuiTestCase {
}
@Test
- public void updateColorScheme_triggerController() {
- WallpaperColors wallpaperColors = WallpaperColors.fromBitmap(
- Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888));
-
- mMediaOutputAdapter.updateColorScheme(wallpaperColors, true);
-
- verify(mMediaSwitchingController).setCurrentColorScheme(wallpaperColors, true);
- }
-
- @Test
public void updateItems_controllerItemsUpdated_notUpdatesInAdapterUntilUpdateItems() {
mMediaOutputAdapter.updateItems();
List<MediaItem> updatedList = new ArrayList<>();
@@ -990,7 +980,7 @@ public class MediaOutputAdapterTest extends SysuiTestCase {
public void multipleSelectedDevices_verifySessionView() {
initializeSession();
- mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter
+ mViewHolder = (MediaOutputAdapterLegacy.MediaDeviceViewHolderLegacy) mMediaOutputAdapter
.onCreateViewHolder(
new LinearLayout(mContext), MediaItem.MediaItemType.TYPE_DEVICE);
mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0);
@@ -1011,7 +1001,7 @@ public class MediaOutputAdapterTest extends SysuiTestCase {
public void multipleSelectedDevices_verifyCollapsedView() {
initializeSession();
- mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter
+ mViewHolder = (MediaOutputAdapterLegacy.MediaDeviceViewHolderLegacy) mMediaOutputAdapter
.onCreateViewHolder(
new LinearLayout(mContext), MediaItem.MediaItemType.TYPE_DEVICE);
mMediaOutputAdapter.onBindViewHolder(mViewHolder, 1);
@@ -1024,13 +1014,13 @@ public class MediaOutputAdapterTest extends SysuiTestCase {
@Test
public void multipleSelectedDevices_expandIconClicked_verifyInitialView() {
initializeSession();
- mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter
+ mViewHolder = (MediaOutputAdapterLegacy.MediaDeviceViewHolderLegacy) mMediaOutputAdapter
.onCreateViewHolder(
new LinearLayout(mContext), MediaItem.MediaItemType.TYPE_DEVICE);
mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0);
mViewHolder.mEndTouchArea.performClick();
- mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter
+ mViewHolder = (MediaOutputAdapterLegacy.MediaDeviceViewHolderLegacy) mMediaOutputAdapter
.onCreateViewHolder(
new LinearLayout(mContext), MediaItem.MediaItemType.TYPE_DEVICE);
mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0);
@@ -1047,13 +1037,13 @@ public class MediaOutputAdapterTest extends SysuiTestCase {
@Test
public void multipleSelectedDevices_expandIconClicked_verifyCollapsedView() {
initializeSession();
- mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter
+ mViewHolder = (MediaOutputAdapterLegacy.MediaDeviceViewHolderLegacy) mMediaOutputAdapter
.onCreateViewHolder(
new LinearLayout(mContext), MediaItem.MediaItemType.TYPE_DEVICE);
mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0);
mViewHolder.mEndTouchArea.performClick();
- mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter
+ mViewHolder = (MediaOutputAdapterLegacy.MediaDeviceViewHolderLegacy) mMediaOutputAdapter
.onCreateViewHolder(
new LinearLayout(mContext), MediaItem.MediaItemType.TYPE_DEVICE);
mMediaOutputAdapter.onBindViewHolder(mViewHolder, 1);
@@ -1075,7 +1065,7 @@ public class MediaOutputAdapterTest extends SysuiTestCase {
when(mMediaSwitchingController.getSelectedMediaDevice()).thenReturn(selectedDevices);
when(mMediaSwitchingController.getDeselectableMediaDevice()).thenReturn(new ArrayList<>());
- mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter
+ mViewHolder = (MediaOutputAdapterLegacy.MediaDeviceViewHolderLegacy) mMediaOutputAdapter
.onCreateViewHolder(
new LinearLayout(mContext), MediaItem.MediaItemType.TYPE_DEVICE);
mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0);
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayContentViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayContentViewModelTest.kt
index 0bba8bba2419..b23cd5e5547f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayContentViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayContentViewModelTest.kt
@@ -16,6 +16,7 @@
package com.android.systemui.notifications.ui.viewmodel
+import android.app.StatusBarManager.DISABLE2_QUICK_SETTINGS
import android.testing.TestableLooper
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
@@ -28,6 +29,8 @@ import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.kosmos.runCurrent
import com.android.systemui.kosmos.testScope
import com.android.systemui.lifecycle.activateIn
+import com.android.systemui.media.controls.data.repository.mediaFilterRepository
+import com.android.systemui.media.controls.shared.model.MediaData
import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest
import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
import com.android.systemui.power.domain.interactor.powerInteractor
@@ -39,10 +42,13 @@ import com.android.systemui.shade.data.repository.shadeRepository
import com.android.systemui.shade.domain.interactor.enableDualShade
import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.shade.ui.viewmodel.notificationsShadeOverlayContentViewModel
+import com.android.systemui.statusbar.disableflags.data.repository.fakeDisableFlagsRepository
import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository
import com.android.systemui.statusbar.notification.data.repository.setActiveNotifs
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.update
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
@@ -50,6 +56,7 @@ import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
@TestableLooper.RunWithLooper
@@ -155,6 +162,36 @@ class NotificationsShadeOverlayContentViewModelTest : SysuiTestCase() {
assertThat(underTest.showClock).isFalse()
}
+ @Test
+ fun showMedia_activeMedia_true() =
+ testScope.runTest {
+ kosmos.mediaFilterRepository.addSelectedUserMediaEntry(MediaData(active = true))
+ runCurrent()
+
+ assertThat(underTest.showMedia).isTrue()
+ }
+
+ @Test
+ fun showMedia_noActiveMedia_false() =
+ testScope.runTest {
+ kosmos.mediaFilterRepository.addSelectedUserMediaEntry(MediaData(active = false))
+ runCurrent()
+
+ assertThat(underTest.showMedia).isFalse()
+ }
+
+ @Test
+ fun showMedia_qsDisabled_false() =
+ testScope.runTest {
+ kosmos.mediaFilterRepository.addSelectedUserMediaEntry(MediaData(active = true))
+ kosmos.fakeDisableFlagsRepository.disableFlags.update {
+ it.copy(disable2 = DISABLE2_QUICK_SETTINGS)
+ }
+ runCurrent()
+
+ assertThat(underTest.showMedia).isFalse()
+ }
+
private fun TestScope.lockDevice() {
val currentScene by collectLastValue(sceneInteractor.currentScene)
kosmos.powerInteractor.setAsleepForTest()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/domain/interactor/GridLayoutTypeInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/domain/interactor/GridLayoutTypeInteractorTest.kt
index c775bfd75f6e..9e400a6c0a4c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/domain/interactor/GridLayoutTypeInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/domain/interactor/GridLayoutTypeInteractorTest.kt
@@ -19,6 +19,7 @@ package com.android.systemui.qs.panels.domain.interactor
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.collectLastValue
import com.android.systemui.kosmos.runTest
@@ -34,6 +35,7 @@ import org.junit.runner.RunWith
@SmallTest
@RunWith(AndroidJUnit4::class)
+@EnableSceneContainer
class GridLayoutTypeInteractorTest : SysuiTestCase() {
val kosmos = testKosmos()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/domain/interactor/QSColumnsInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/domain/interactor/QSColumnsInteractorTest.kt
index 2e7aeb433e04..9fe783b98046 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/domain/interactor/QSColumnsInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/domain/interactor/QSColumnsInteractorTest.kt
@@ -22,6 +22,7 @@ import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.common.ui.data.repository.configurationRepository
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.kosmos.testCase
import com.android.systemui.kosmos.testScope
import com.android.systemui.qs.panels.data.repository.QSColumnsRepository
@@ -76,6 +77,7 @@ class QSColumnsInteractorTest : SysuiTestCase() {
}
@Test
+ @EnableSceneContainer
fun withDualShade_returnsCorrectValue() =
with(kosmos) {
testScope.runTest {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/MediaInRowInLandscapeViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/MediaInRowInLandscapeViewModelTest.kt
index fdbf42c9afd8..d5e502e99de5 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/MediaInRowInLandscapeViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/MediaInRowInLandscapeViewModelTest.kt
@@ -21,6 +21,7 @@ import android.content.res.mainResources
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
+import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.kosmos.testScope
import com.android.systemui.lifecycle.activateIn
import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager.Companion.LOCATION_QQS
@@ -36,6 +37,7 @@ import com.android.systemui.shade.shared.model.ShadeMode
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlin.test.Test
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -43,6 +45,7 @@ import org.junit.runner.RunWith
import platform.test.runner.parameterized.ParameterizedAndroidJunit4
import platform.test.runner.parameterized.Parameters
+@OptIn(ExperimentalCoroutinesApi::class)
@RunWith(ParameterizedAndroidJunit4::class)
@SmallTest
class MediaInRowInLandscapeViewModelTest(private val testData: TestData) : SysuiTestCase() {
@@ -63,6 +66,7 @@ class MediaInRowInLandscapeViewModelTest(private val testData: TestData) : Sysui
}
@Test
+ @EnableSceneContainer
fun shouldMediaShowInRow() =
with(kosmos) {
testScope.runTest {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/QSColumnsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/QSColumnsViewModelTest.kt
index 241cdbfbef83..4912c319bf2e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/QSColumnsViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/QSColumnsViewModelTest.kt
@@ -21,6 +21,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.common.ui.data.repository.configurationRepository
+import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.runCurrent
import com.android.systemui.kosmos.testCase
@@ -88,6 +89,7 @@ class QSColumnsViewModelTest : SysuiTestCase() {
}
@Test
+ @EnableSceneContainer
fun mediaLocationNull_dualShade_alwaysDualShadeColumns() =
with(kosmos) {
testScope.runTest {
@@ -111,6 +113,7 @@ class QSColumnsViewModelTest : SysuiTestCase() {
}
@Test
+ @EnableSceneContainer
fun mediaLocationQS_dualShade_alwaysDualShadeColumns() =
with(kosmos) {
testScope.runTest {
@@ -133,6 +136,7 @@ class QSColumnsViewModelTest : SysuiTestCase() {
}
@Test
+ @EnableSceneContainer
fun mediaLocationQQS_dualShade_alwaysDualShadeColumns() =
with(kosmos) {
testScope.runTest {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt
index 80c7026b0cea..23a0f6224fb7 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt
@@ -757,4 +757,46 @@ class SceneInteractorTest : SysuiTestCase() {
verify(processor, never()).onSceneAboutToChange(any(), any())
}
+
+ @Test
+ fun changeScene_sameScene_withFreeze() =
+ kosmos.runTest {
+ val currentScene by collectLastValue(underTest.currentScene)
+ assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
+ val processor = mock<SceneInteractor.OnSceneAboutToChangeListener>()
+ underTest.registerSceneStateProcessor(processor)
+ verify(processor, never()).onSceneAboutToChange(any(), any())
+ assertThat(fakeSceneDataSource.freezeAndAnimateToCurrentStateCallCount).isEqualTo(0)
+
+ underTest.changeScene(
+ toScene = Scenes.Lockscreen,
+ loggingReason = "test",
+ sceneState = KeyguardState.AOD,
+ forceSettleToTargetScene = true,
+ )
+
+ verify(processor).onSceneAboutToChange(Scenes.Lockscreen, KeyguardState.AOD)
+ assertThat(fakeSceneDataSource.freezeAndAnimateToCurrentStateCallCount).isEqualTo(1)
+ }
+
+ @Test
+ fun changeScene_sameScene_withoutFreeze() =
+ kosmos.runTest {
+ val currentScene by collectLastValue(underTest.currentScene)
+ assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
+ val processor = mock<SceneInteractor.OnSceneAboutToChangeListener>()
+ underTest.registerSceneStateProcessor(processor)
+ verify(processor, never()).onSceneAboutToChange(any(), any())
+ assertThat(fakeSceneDataSource.freezeAndAnimateToCurrentStateCallCount).isEqualTo(0)
+
+ underTest.changeScene(
+ toScene = Scenes.Lockscreen,
+ loggingReason = "test",
+ sceneState = KeyguardState.AOD,
+ forceSettleToTargetScene = false,
+ )
+
+ verify(processor, never()).onSceneAboutToChange(any(), any())
+ assertThat(fakeSceneDataSource.freezeAndAnimateToCurrentStateCallCount).isEqualTo(0)
+ }
}
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 35368ca8734d..9498daaf0b07 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
@@ -60,6 +60,7 @@ class ShadeDisplaysRepositoryTest : SysuiTestCase() {
policies,
shadeOnDefaultDisplayWhenLocked = shadeOnDefaultDisplayWhenLocked,
keyguardRepository,
+ displayRepository,
)
@Test
@@ -90,6 +91,30 @@ class ShadeDisplaysRepositoryTest : SysuiTestCase() {
}
@Test
+ fun displayId_afterDisplayDisconnected_fallsBackToDefaultDisplay() =
+ testScope.runTest {
+ val underTest = createUnderTest()
+ globalSettings.putString(
+ DEVELOPMENT_SHADE_DISPLAY_AWARENESS,
+ FakeShadeDisplayPolicy.name,
+ )
+ val displayId by collectLastValue(underTest.displayId)
+
+ displayRepository.addDisplay(displayId = 1)
+
+ FakeShadeDisplayPolicy.setDisplayId(1)
+ assertThat(displayId).isEqualTo(1)
+
+ // Let's disconnect and make sure it goes back to the default one
+ displayRepository.removeDisplay(displayId = 1)
+ assertThat(displayId).isEqualTo(Display.DEFAULT_DISPLAY)
+
+ // Let's re-connect it and make sure it goes back to the non-default one
+ displayRepository.addDisplay(displayId = 1)
+ assertThat(displayId).isEqualTo(1)
+ }
+
+ @Test
fun policy_updatesBasedOnSettingValue_defaultDisplay() =
testScope.runTest {
val underTest = createUnderTest()
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 668f568d7f46..d26e195d360a 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
@@ -20,6 +20,7 @@ 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.flags.EnableSceneContainer
import com.android.systemui.kosmos.testScope
import com.android.systemui.shade.shared.model.ShadeMode
import com.android.systemui.testKosmos
@@ -31,6 +32,7 @@ import org.junit.runner.RunWith
@SmallTest
@RunWith(AndroidJUnit4::class)
+@EnableSceneContainer
class ShadeModeInteractorImplTest : SysuiTestCase() {
private val kosmos = testKosmos()
@@ -80,7 +82,7 @@ class ShadeModeInteractorImplTest : SysuiTestCase() {
}
@Test
- fun isDualShade_settingEnabled_returnsTrue() =
+ fun isDualShade_settingEnabledSceneContainerEnabled_returnsTrue() =
testScope.runTest {
// TODO(b/391578667): Add a test case for user switching once the bug is fixed.
val shadeMode by collectLastValue(underTest.shadeMode)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/startable/ShadeStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/startable/ShadeStartableTest.kt
index b8f66acf6413..dde867814159 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/startable/ShadeStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/startable/ShadeStartableTest.kt
@@ -48,6 +48,7 @@ import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.test.TestScope
@@ -59,6 +60,7 @@ import org.mockito.kotlin.verify
import platform.test.runner.parameterized.ParameterizedAndroidJunit4
import platform.test.runner.parameterized.Parameters
+@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(ParameterizedAndroidJunit4::class)
class ShadeStartableTest(flags: FlagsParameterization) : SysuiTestCase() {
@@ -103,6 +105,7 @@ class ShadeStartableTest(flags: FlagsParameterization) : SysuiTestCase() {
}
@Test
+ @EnableSceneContainer
fun hydrateShadeMode_dualShadeEnabled() =
testScope.runTest {
overrideResource(R.bool.config_use_split_notification_shade, false)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/plugins/PluginInstanceTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/plugins/PluginInstanceTest.java
index 93ba8e1317fa..064fd485dab4 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/plugins/PluginInstanceTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/plugins/PluginInstanceTest.java
@@ -28,6 +28,7 @@ import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.util.Log;
+import androidx.test.filters.FlakyTest;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
@@ -52,6 +53,7 @@ import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Supplier;
@SmallTest
+@FlakyTest(bugId = 395832204)
@RunWith(AndroidJUnit4.class)
public class PluginInstanceTest extends SysuiTestCase {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/domain/interactor/SingleNotificationChipInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/domain/interactor/SingleNotificationChipInteractorTest.kt
index cabe4afdea60..5d1950670777 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/domain/interactor/SingleNotificationChipInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/domain/interactor/SingleNotificationChipInteractorTest.kt
@@ -31,6 +31,7 @@ import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
import com.android.systemui.statusbar.notification.data.model.activeNotificationModel
import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel
import com.android.systemui.testKosmos
+import com.android.systemui.util.time.fakeSystemClock
import com.google.common.truth.Truth.assertThat
import org.junit.Test
import org.junit.runner.RunWith
@@ -183,7 +184,7 @@ class SingleNotificationChipInteractorTest : SysuiTestCase() {
statusBarChipIcon = null,
promotedContent = PROMOTED_CONTENT,
),
- 32L,
+ creationTime = 32L,
)
val latest by collectLastValue(underTest.notificationChip)
@@ -246,7 +247,7 @@ class SingleNotificationChipInteractorTest : SysuiTestCase() {
statusBarChipIcon = mock(),
promotedContent = PROMOTED_CONTENT,
)
- val underTest = factory.create(startingNotif, 123L)
+ val underTest = factory.create(startingNotif, creationTime = 123L)
val latest by collectLastValue(underTest.notificationChip)
assertThat(latest).isNotNull()
@@ -306,9 +307,10 @@ class SingleNotificationChipInteractorTest : SysuiTestCase() {
}
@Test
- fun notificationChip_appIsVisibleOnCreation_emitsIsAppVisibleTrue() =
+ fun notificationChip_appIsVisibleOnCreation_emitsIsAppVisibleTrueWithTime() =
kosmos.runTest {
activityManagerRepository.fake.startingIsAppVisibleValue = true
+ fakeSystemClock.setCurrentTimeMillis(9000)
val underTest =
factory.create(
@@ -325,12 +327,14 @@ class SingleNotificationChipInteractorTest : SysuiTestCase() {
assertThat(latest).isNotNull()
assertThat(latest!!.isAppVisible).isTrue()
+ assertThat(latest!!.lastAppVisibleTime).isEqualTo(9000)
}
@Test
- fun notificationChip_appNotVisibleOnCreation_emitsIsAppVisibleFalse() =
+ fun notificationChip_appNotVisibleOnCreation_emitsIsAppVisibleFalseWithNoTime() =
kosmos.runTest {
activityManagerRepository.fake.startingIsAppVisibleValue = false
+ fakeSystemClock.setCurrentTimeMillis(9000)
val underTest =
factory.create(
@@ -347,11 +351,15 @@ class SingleNotificationChipInteractorTest : SysuiTestCase() {
assertThat(latest).isNotNull()
assertThat(latest!!.isAppVisible).isFalse()
+ assertThat(latest!!.lastAppVisibleTime).isNull()
}
@Test
fun notificationChip_updatesWhenAppIsVisible() =
kosmos.runTest {
+ activityManagerRepository.fake.startingIsAppVisibleValue = false
+ fakeSystemClock.setCurrentTimeMillis(9000)
+
val underTest =
factory.create(
activeNotificationModel(
@@ -365,32 +373,39 @@ class SingleNotificationChipInteractorTest : SysuiTestCase() {
val latest by collectLastValue(underTest.notificationChip)
- activityManagerRepository.fake.setIsAppVisible(UID, false)
+ activityManagerRepository.fake.setIsAppVisible(UID, isAppVisible = false)
assertThat(latest!!.isAppVisible).isFalse()
+ assertThat(latest!!.lastAppVisibleTime).isNull()
- activityManagerRepository.fake.setIsAppVisible(UID, true)
+ fakeSystemClock.setCurrentTimeMillis(11000)
+ activityManagerRepository.fake.setIsAppVisible(UID, isAppVisible = true)
assertThat(latest!!.isAppVisible).isTrue()
+ assertThat(latest!!.lastAppVisibleTime).isEqualTo(11000)
- activityManagerRepository.fake.setIsAppVisible(UID, false)
+ fakeSystemClock.setCurrentTimeMillis(13000)
+ activityManagerRepository.fake.setIsAppVisible(UID, isAppVisible = false)
assertThat(latest!!.isAppVisible).isFalse()
+ assertThat(latest!!.lastAppVisibleTime).isEqualTo(11000)
+
+ fakeSystemClock.setCurrentTimeMillis(15000)
+ activityManagerRepository.fake.setIsAppVisible(UID, isAppVisible = true)
+ assertThat(latest!!.isAppVisible).isTrue()
+ assertThat(latest!!.lastAppVisibleTime).isEqualTo(15000)
}
- // Note: This test is theoretically impossible because the notification key should contain the
- // UID, so if the UID changes then the key would also change and a new interactor would be
- // created. But, test it just in case.
@Test
- fun notificationChip_updatedUid_rechecksAppVisibility_oldObserverUnregistered() =
+ fun notificationChip_updatedUid_newUidIsIgnoredButOtherDataNotIgnored() =
kosmos.runTest {
activityManagerRepository.fake.startingIsAppVisibleValue = false
- val hiddenUid = 100
- val shownUid = 101
+ val originalUid = 100
+ val newUid = 101
val underTest =
factory.create(
activeNotificationModel(
key = "notif",
- uid = hiddenUid,
+ uid = originalUid,
statusBarChipIcon = mock(),
promotedContent = PROMOTED_CONTENT,
),
@@ -402,16 +417,34 @@ class SingleNotificationChipInteractorTest : SysuiTestCase() {
// WHEN the notif gets a new UID that starts as visible
activityManagerRepository.fake.startingIsAppVisibleValue = true
+ val newPromotedContentBuilder =
+ PromotedNotificationContentModel.Builder("notif").apply {
+ this.shortCriticalText = "Arrived"
+ }
+ val newPromotedContent = newPromotedContentBuilder.build()
underTest.setNotification(
activeNotificationModel(
key = "notif",
- uid = shownUid,
+ uid = newUid,
statusBarChipIcon = mock(),
- promotedContent = PROMOTED_CONTENT,
+ promotedContent = newPromotedContent,
)
)
- // THEN we re-fetch the app visibility state with the new UID
+ // THEN we do update other fields like promoted content
+ assertThat(latest!!.promotedContent).isEqualTo(newPromotedContent)
+
+ // THEN we don't fetch the app visibility state for the new UID
+ assertThat(latest!!.isAppVisible).isFalse()
+
+ // AND don't listen to updates for the new UID
+ activityManagerRepository.fake.setIsAppVisible(newUid, isAppVisible = false)
+ activityManagerRepository.fake.setIsAppVisible(newUid, isAppVisible = true)
+ assertThat(latest!!.isAppVisible).isFalse()
+
+ // AND we still use updates from the old UID
+ // TODO(b/364653005): This particular behavior isn't great, can we do better?
+ activityManagerRepository.fake.setIsAppVisible(originalUid, isAppVisible = true)
assertThat(latest!!.isAppVisible).isTrue()
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/domain/interactor/StatusBarNotificationChipsInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/domain/interactor/StatusBarNotificationChipsInteractorTest.kt
index d8e4cd927bec..7ed2bd38bcd2 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/domain/interactor/StatusBarNotificationChipsInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/domain/interactor/StatusBarNotificationChipsInteractorTest.kt
@@ -333,7 +333,7 @@ class StatusBarNotificationChipsInteractorTest : SysuiTestCase() {
@Test
@EnableFlags(StatusBarNotifChips.FLAG_NAME)
- fun shownNotificationChips_sortedBasedOnFirstAppearanceTime() =
+ fun shownNotificationChips_sortedByFirstAppearanceTime() =
kosmos.runTest {
val latest by collectLastValue(underTest.shownNotificationChips)
@@ -349,8 +349,7 @@ class StatusBarNotificationChipsInteractorTest : SysuiTestCase() {
promotedContent = PromotedNotificationContentModel.Builder("notif1").build(),
)
setNotifs(listOf(notif1))
- assertThat(latest).hasSize(1)
- assertThat(latest!![0].key).isEqualTo("notif1")
+ assertThat(latest!!.map { it.key }).containsExactly("notif1").inOrder()
// WHEN we add notif2 at t=2000
fakeSystemClock.advanceTime(1000)
@@ -362,26 +361,20 @@ class StatusBarNotificationChipsInteractorTest : SysuiTestCase() {
)
setNotifs(listOf(notif1, notif2))
- // THEN notif2 is ranked above notif1 because it appeared later
- assertThat(latest).hasSize(2)
- assertThat(latest!![0].key).isEqualTo("notif2")
- assertThat(latest!![1].key).isEqualTo("notif1")
+ // THEN notif2 is ranked above notif1 because notif2 appeared later
+ assertThat(latest!!.map { it.key }).containsExactly("notif2", "notif1").inOrder()
// WHEN notif1 and notif2 swap places
setNotifs(listOf(notif2, notif1))
// THEN notif2 is still ranked above notif1 to preserve chip ordering
- assertThat(latest).hasSize(2)
- assertThat(latest!![0].key).isEqualTo("notif2")
- assertThat(latest!![1].key).isEqualTo("notif1")
+ assertThat(latest!!.map { it.key }).containsExactly("notif2", "notif1").inOrder()
// WHEN notif1 and notif2 swap places again
setNotifs(listOf(notif1, notif2))
// THEN notif2 is still ranked above notif1 to preserve chip ordering
- assertThat(latest).hasSize(2)
- assertThat(latest!![0].key).isEqualTo("notif2")
- assertThat(latest!![1].key).isEqualTo("notif1")
+ assertThat(latest!!.map { it.key }).containsExactly("notif2", "notif1").inOrder()
// WHEN notif1 gets an update
val notif1NewPromotedContent =
@@ -400,9 +393,7 @@ class StatusBarNotificationChipsInteractorTest : SysuiTestCase() {
)
// THEN notif2 is still ranked above notif1 to preserve chip ordering
- assertThat(latest).hasSize(2)
- assertThat(latest!![0].key).isEqualTo("notif2")
- assertThat(latest!![1].key).isEqualTo("notif1")
+ assertThat(latest!!.map { it.key }).containsExactly("notif2", "notif1").inOrder()
// WHEN notif1 disappears and then reappears
fakeSystemClock.advanceTime(1000)
@@ -413,9 +404,238 @@ class StatusBarNotificationChipsInteractorTest : SysuiTestCase() {
setNotifs(listOf(notif2, notif1))
// THEN notif1 is now ranked first
- assertThat(latest).hasSize(2)
- assertThat(latest!![0].key).isEqualTo("notif1")
- assertThat(latest!![1].key).isEqualTo("notif2")
+ assertThat(latest!!.map { it.key }).containsExactly("notif1", "notif2").inOrder()
+ }
+
+ @Test
+ @EnableFlags(StatusBarNotifChips.FLAG_NAME)
+ fun shownNotificationChips_sortedByLastAppVisibleTime() =
+ kosmos.runTest {
+ val latest by collectLastValue(underTest.shownNotificationChips)
+
+ val notif1Info = NotifInfo("notif1", mock<StatusBarIconView>(), uid = 100)
+ val notif2Info = NotifInfo("notif2", mock<StatusBarIconView>(), uid = 200)
+
+ activityManagerRepository.fake.startingIsAppVisibleValue = false
+ fakeSystemClock.setCurrentTimeMillis(1000)
+ val notif1 =
+ activeNotificationModel(
+ key = notif1Info.key,
+ uid = notif1Info.uid,
+ statusBarChipIcon = notif1Info.icon,
+ promotedContent =
+ PromotedNotificationContentModel.Builder(notif1Info.key).build(),
+ )
+ val notif2 =
+ activeNotificationModel(
+ key = notif2Info.key,
+ uid = notif2Info.uid,
+ statusBarChipIcon = notif2Info.icon,
+ promotedContent =
+ PromotedNotificationContentModel.Builder(notif2Info.key).build(),
+ )
+ setNotifs(listOf(notif1, notif2))
+ assertThat(latest!!.map { it.key }).containsExactly("notif1", "notif2").inOrder()
+
+ // WHEN notif2's app becomes visible
+ fakeSystemClock.advanceTime(1000)
+ activityManagerRepository.fake.setIsAppVisible(notif2Info.uid, isAppVisible = true)
+
+ // THEN notif2 is no longer shown
+ assertThat(latest!!.map { it.key }).containsExactly("notif1").inOrder()
+
+ // WHEN notif2's app is no longer visible
+ fakeSystemClock.advanceTime(1000)
+ activityManagerRepository.fake.setIsAppVisible(notif2Info.uid, isAppVisible = false)
+
+ // THEN notif2 is ranked above notif1 because it was more recently visible
+ assertThat(latest!!.map { it.key }).containsExactly("notif2", "notif1").inOrder()
+
+ // WHEN the app associated with notif1 becomes visible then un-visible
+ fakeSystemClock.advanceTime(1000)
+ activityManagerRepository.fake.setIsAppVisible(notif1Info.uid, isAppVisible = true)
+ fakeSystemClock.advanceTime(1000)
+ activityManagerRepository.fake.setIsAppVisible(notif1Info.uid, isAppVisible = false)
+
+ // THEN notif1 is now ranked above notif2 because it was more recently visible
+ assertThat(latest!!.map { it.key }).containsExactly("notif1", "notif2").inOrder()
+ }
+
+ @Test
+ @EnableFlags(StatusBarNotifChips.FLAG_NAME)
+ fun shownNotificationChips_newNotificationTakesPriorityOverLastAppVisible() =
+ kosmos.runTest {
+ val latest by collectLastValue(underTest.shownNotificationChips)
+
+ val notif1Info = NotifInfo("notif1", mock<StatusBarIconView>(), uid = 100)
+ val notif2Info = NotifInfo("notif2", mock<StatusBarIconView>(), uid = 200)
+ val notif3Info = NotifInfo("notif3", mock<StatusBarIconView>(), uid = 300)
+
+ activityManagerRepository.fake.startingIsAppVisibleValue = false
+ fakeSystemClock.setCurrentTimeMillis(1000)
+ val notif1 =
+ activeNotificationModel(
+ key = notif1Info.key,
+ uid = notif1Info.uid,
+ statusBarChipIcon = notif1Info.icon,
+ promotedContent =
+ PromotedNotificationContentModel.Builder(notif1Info.key).build(),
+ )
+ val notif2 =
+ activeNotificationModel(
+ key = notif2Info.key,
+ uid = notif2Info.uid,
+ statusBarChipIcon = notif2Info.icon,
+ promotedContent =
+ PromotedNotificationContentModel.Builder(notif2Info.key).build(),
+ )
+ setNotifs(listOf(notif1, notif2))
+ assertThat(latest!!.map { it.key }).containsExactly("notif1", "notif2").inOrder()
+
+ // WHEN notif2's app becomes visible then not visible
+ fakeSystemClock.advanceTime(1000)
+ activityManagerRepository.fake.setIsAppVisible(notif2Info.uid, isAppVisible = true)
+ fakeSystemClock.advanceTime(1000)
+ activityManagerRepository.fake.setIsAppVisible(notif2Info.uid, isAppVisible = false)
+
+ // THEN notif2 is ranked above notif1 because it was more recently visible
+ assertThat(latest!!.map { it.key }).containsExactly("notif2", "notif1").inOrder()
+
+ // WHEN a new notif3 appears
+ fakeSystemClock.advanceTime(1000)
+ val notif3 =
+ activeNotificationModel(
+ key = notif3Info.key,
+ uid = notif3Info.uid,
+ statusBarChipIcon = notif3Info.icon,
+ promotedContent =
+ PromotedNotificationContentModel.Builder(notif3Info.key).build(),
+ )
+ setNotifs(listOf(notif1, notif2, notif3))
+
+ // THEN notif3 is ranked above everything else
+ // AND notif2 is still before notif1 because it was more recently visible
+ assertThat(latest!!.map { it.key })
+ .containsExactly("notif3", "notif2", "notif1")
+ .inOrder()
+ }
+
+ @Test
+ @EnableFlags(StatusBarNotifChips.FLAG_NAME)
+ fun shownNotificationChips_fullSort() =
+ kosmos.runTest {
+ val latest by collectLastValue(underTest.shownNotificationChips)
+
+ val notif1Info = NotifInfo("notif1", mock<StatusBarIconView>(), uid = 100)
+ val notif2Info = NotifInfo("notif2", mock<StatusBarIconView>(), uid = 200)
+ val notif3Info = NotifInfo("notif3", mock<StatusBarIconView>(), uid = 300)
+
+ // First, add notif1 at t=1000
+ activityManagerRepository.fake.startingIsAppVisibleValue = false
+ fakeSystemClock.setCurrentTimeMillis(1000)
+ val notif1 =
+ activeNotificationModel(
+ key = notif1Info.key,
+ uid = notif1Info.uid,
+ statusBarChipIcon = notif1Info.icon,
+ promotedContent =
+ PromotedNotificationContentModel.Builder(notif1Info.key).build(),
+ )
+ setNotifs(listOf(notif1))
+
+ // WHEN we add notif2 at t=2000
+ fakeSystemClock.advanceTime(1000)
+ val notif2 =
+ activeNotificationModel(
+ key = notif2Info.key,
+ uid = notif2Info.uid,
+ statusBarChipIcon = notif2Info.icon,
+ promotedContent =
+ PromotedNotificationContentModel.Builder(notif2Info.key).build(),
+ )
+ setNotifs(listOf(notif1, notif2))
+
+ // THEN notif2 is ranked above notif1 because notif2 appeared later
+ assertThat(latest!!.map { it.key }).containsExactly("notif2", "notif1").inOrder()
+
+ // WHEN notif2's app becomes visible then un-visible
+ fakeSystemClock.advanceTime(1000)
+ activityManagerRepository.fake.setIsAppVisible(notif2Info.uid, isAppVisible = true)
+ fakeSystemClock.advanceTime(1000)
+ activityManagerRepository.fake.setIsAppVisible(notif2Info.uid, isAppVisible = false)
+
+ // THEN notif2 is ranked above notif1 because it was more recently visible
+ assertThat(latest!!.map { it.key }).containsExactly("notif2", "notif1").inOrder()
+
+ // WHEN the app associated with notif1 becomes visible then un-visible
+ fakeSystemClock.advanceTime(1000)
+ activityManagerRepository.fake.setIsAppVisible(notif1Info.uid, isAppVisible = true)
+ fakeSystemClock.advanceTime(1000)
+ activityManagerRepository.fake.setIsAppVisible(notif1Info.uid, isAppVisible = false)
+
+ // THEN notif1 is ranked above notif2 because it was more recently visible
+ assertThat(latest!!.map { it.key }).containsExactly("notif1", "notif2").inOrder()
+
+ // WHEN notif2 gets an update
+ val notif2NewPromotedContent =
+ PromotedNotificationContentModel.Builder("notif2").apply {
+ this.shortCriticalText = "Arrived"
+ }
+ setNotifs(
+ listOf(
+ notif1,
+ activeNotificationModel(
+ key = notif2Info.key,
+ uid = notif2Info.uid,
+ statusBarChipIcon = notif2Info.icon,
+ promotedContent = notif2NewPromotedContent.build(),
+ ),
+ )
+ )
+
+ // THEN notif1 is still ranked above notif2 to preserve chip ordering
+ assertThat(latest!!.map { it.key }).containsExactly("notif1", "notif2").inOrder()
+
+ // WHEN a new notification appears
+ fakeSystemClock.advanceTime(1000)
+ val notif3 =
+ activeNotificationModel(
+ key = notif3Info.key,
+ uid = notif3Info.uid,
+ statusBarChipIcon = notif3Info.icon,
+ promotedContent =
+ PromotedNotificationContentModel.Builder(notif3Info.key).build(),
+ )
+ setNotifs(listOf(notif1, notif2, notif3))
+
+ // THEN it's ranked first because it's new
+ assertThat(latest!!.map { it.key })
+ .containsExactly("notif3", "notif1", "notif2")
+ .inOrder()
+
+ // WHEN notif2 becomes visible then un-visible again
+ fakeSystemClock.advanceTime(1000)
+ activityManagerRepository.fake.setIsAppVisible(notif2Info.uid, isAppVisible = true)
+ fakeSystemClock.advanceTime(1000)
+ activityManagerRepository.fake.setIsAppVisible(notif2Info.uid, isAppVisible = false)
+
+ // THEN it moves to the front
+ assertThat(latest!!.map { it.key })
+ .containsExactly("notif2", "notif3", "notif1")
+ .inOrder()
+
+ // WHEN notif1 disappears and then reappears
+ fakeSystemClock.advanceTime(1000)
+ setNotifs(listOf(notif2, notif3))
+ assertThat(latest!!.map { it.key }).containsExactly("notif2", "notif3").inOrder()
+
+ fakeSystemClock.advanceTime(1000)
+ setNotifs(listOf(notif2, notif1, notif3))
+
+ // THEN notif1 is now ranked first
+ assertThat(latest!!.map { it.key })
+ .containsExactly("notif1", "notif2", "notif3")
+ .inOrder()
}
@Test
@@ -495,4 +715,6 @@ class StatusBarNotificationChipsInteractorTest : SysuiTestCase() {
.apply { notifs.forEach { addIndividualNotif(it) } }
.build()
}
+
+ private data class NotifInfo(val key: String, val icon: StatusBarIconView, val uid: Int)
}
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 aaa9b58a45df..7cf817a06225 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
@@ -49,8 +49,11 @@ import com.android.systemui.statusbar.notification.shared.ActiveNotificationMode
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.android.systemui.util.time.fakeSystemClock
import com.google.common.truth.Truth.assertThat
import kotlin.test.Test
+import kotlin.time.Duration.Companion.minutes
+import kotlin.time.Duration.Companion.seconds
import kotlinx.coroutines.flow.MutableStateFlow
import org.junit.Before
import org.junit.runner.RunWith
@@ -286,13 +289,15 @@ class NotifChipsViewModelTest : SysuiTestCase() {
fun chips_hasShortCriticalText_usesTextInsteadOfTime() =
kosmos.runTest {
val latest by collectLastValue(underTest.chips)
+ val currentTime = 30.minutes.inWholeMilliseconds
+ fakeSystemClock.setCurrentTimeMillis(currentTime)
val promotedContentBuilder =
PromotedNotificationContentModel.Builder("notif").apply {
this.shortCriticalText = "Arrived"
this.time =
PromotedNotificationContentModel.When(
- time = 6543L,
+ time = currentTime + 30.minutes.inWholeMilliseconds,
mode = PromotedNotificationContentModel.When.Mode.BasicTime,
)
}
@@ -340,13 +345,15 @@ class NotifChipsViewModelTest : SysuiTestCase() {
fun chips_basicTime_timeHiddenIfAutomaticallyPromoted() =
kosmos.runTest {
val latest by collectLastValue(underTest.chips)
+ val currentTime = 30.minutes.inWholeMilliseconds
+ fakeSystemClock.setCurrentTimeMillis(currentTime)
val promotedContentBuilder =
PromotedNotificationContentModel.Builder("notif").apply {
this.wasPromotedAutomatically = true
this.time =
PromotedNotificationContentModel.When(
- time = 6543L,
+ time = currentTime + 30.minutes.inWholeMilliseconds,
mode = PromotedNotificationContentModel.When.Mode.BasicTime,
)
}
@@ -370,13 +377,15 @@ class NotifChipsViewModelTest : SysuiTestCase() {
fun chips_basicTime_timeShownIfNotAutomaticallyPromoted() =
kosmos.runTest {
val latest by collectLastValue(underTest.chips)
+ val currentTime = 30.minutes.inWholeMilliseconds
+ fakeSystemClock.setCurrentTimeMillis(currentTime)
val promotedContentBuilder =
PromotedNotificationContentModel.Builder("notif").apply {
this.wasPromotedAutomatically = false
this.time =
PromotedNotificationContentModel.When(
- time = 6543L,
+ time = currentTime + 30.minutes.inWholeMilliseconds,
mode = PromotedNotificationContentModel.When.Mode.BasicTime,
)
}
@@ -397,18 +406,117 @@ class NotifChipsViewModelTest : SysuiTestCase() {
@Test
@DisableFlags(FLAG_PROMOTE_NOTIFICATIONS_AUTOMATICALLY)
- fun chips_basicTime_isShortTimeDelta() =
+ fun chips_basicTime_timeInFuture_isShortTimeDelta() =
kosmos.runTest {
val latest by collectLastValue(underTest.chips)
+ val currentTime = 3.minutes.inWholeMilliseconds
+ fakeSystemClock.setCurrentTimeMillis(currentTime)
val promotedContentBuilder =
PromotedNotificationContentModel.Builder("notif").apply {
this.time =
PromotedNotificationContentModel.When(
- time = 6543L,
+ time = currentTime + 13.minutes.inWholeMilliseconds,
mode = PromotedNotificationContentModel.When.Mode.BasicTime,
)
}
+
+ setNotifs(
+ listOf(
+ activeNotificationModel(
+ key = "notif",
+ statusBarChipIcon = createStatusBarIconViewOrNull(),
+ promotedContent = promotedContentBuilder.build(),
+ )
+ )
+ )
+
+ assertThat(latest).hasSize(1)
+ assertThat(latest!![0])
+ .isInstanceOf(OngoingActivityChipModel.Active.ShortTimeDelta::class.java)
+ }
+
+ @Test
+ @DisableFlags(FLAG_PROMOTE_NOTIFICATIONS_AUTOMATICALLY)
+ fun chips_basicTime_timeLessThanOneMinInFuture_isIconOnly() =
+ kosmos.runTest {
+ val latest by collectLastValue(underTest.chips)
+ val currentTime = 3.minutes.inWholeMilliseconds
+ fakeSystemClock.setCurrentTimeMillis(currentTime)
+
+ val promotedContentBuilder =
+ PromotedNotificationContentModel.Builder("notif").apply {
+ this.time =
+ PromotedNotificationContentModel.When(
+ time = currentTime + 500,
+ mode = PromotedNotificationContentModel.When.Mode.BasicTime,
+ )
+ }
+
+ setNotifs(
+ listOf(
+ activeNotificationModel(
+ key = "notif",
+ statusBarChipIcon = createStatusBarIconViewOrNull(),
+ promotedContent = promotedContentBuilder.build(),
+ )
+ )
+ )
+
+ assertThat(latest).hasSize(1)
+ assertThat(latest!![0])
+ .isInstanceOf(OngoingActivityChipModel.Active.IconOnly::class.java)
+ }
+
+ @Test
+ @DisableFlags(FLAG_PROMOTE_NOTIFICATIONS_AUTOMATICALLY)
+ fun chips_basicTime_timeIsNow_isIconOnly() =
+ kosmos.runTest {
+ val latest by collectLastValue(underTest.chips)
+ val currentTime = 62.seconds.inWholeMilliseconds
+ fakeSystemClock.setCurrentTimeMillis(currentTime)
+
+ val promotedContentBuilder =
+ PromotedNotificationContentModel.Builder("notif").apply {
+ this.time =
+ PromotedNotificationContentModel.When(
+ time = currentTime,
+ mode = PromotedNotificationContentModel.When.Mode.BasicTime,
+ )
+ }
+
+ setNotifs(
+ listOf(
+ activeNotificationModel(
+ key = "notif",
+ statusBarChipIcon = createStatusBarIconViewOrNull(),
+ promotedContent = promotedContentBuilder.build(),
+ )
+ )
+ )
+
+ assertThat(latest).hasSize(1)
+ assertThat(latest!![0])
+ .isInstanceOf(OngoingActivityChipModel.Active.IconOnly::class.java)
+ }
+
+ @Test
+ @DisableFlags(FLAG_PROMOTE_NOTIFICATIONS_AUTOMATICALLY)
+ fun chips_basicTime_timeInPast_isIconOnly() =
+ kosmos.runTest {
+ val latest by collectLastValue(underTest.chips)
+ val currentTime = 62.minutes.inWholeMilliseconds
+ fakeSystemClock.setCurrentTimeMillis(currentTime)
+
+ val promotedContentBuilder =
+ PromotedNotificationContentModel.Builder("notif").apply {
+ this.time =
+ PromotedNotificationContentModel.When(
+ time = currentTime - 2.minutes.inWholeMilliseconds,
+ mode = PromotedNotificationContentModel.When.Mode.BasicTime,
+ )
+ }
+
setNotifs(
listOf(
activeNotificationModel(
@@ -421,6 +529,45 @@ class NotifChipsViewModelTest : SysuiTestCase() {
assertThat(latest).hasSize(1)
assertThat(latest!![0])
+ .isInstanceOf(OngoingActivityChipModel.Active.IconOnly::class.java)
+ }
+
+ // Not necessarily the behavior we *want* to have, but it's the currently implemented behavior.
+ @Test
+ @DisableFlags(FLAG_PROMOTE_NOTIFICATIONS_AUTOMATICALLY)
+ fun chips_basicTime_timeIsInFuture_thenTimeAdvances_stillShortTimeDelta() =
+ kosmos.runTest {
+ val latest by collectLastValue(underTest.chips)
+ val currentTime = 30.minutes.inWholeMilliseconds
+ fakeSystemClock.setCurrentTimeMillis(currentTime)
+
+ val promotedContentBuilder =
+ PromotedNotificationContentModel.Builder("notif").apply {
+ this.time =
+ PromotedNotificationContentModel.When(
+ time = currentTime + 3.minutes.inWholeMilliseconds,
+ mode = PromotedNotificationContentModel.When.Mode.BasicTime,
+ )
+ }
+
+ setNotifs(
+ listOf(
+ activeNotificationModel(
+ key = "notif",
+ statusBarChipIcon = createStatusBarIconViewOrNull(),
+ promotedContent = promotedContentBuilder.build(),
+ )
+ )
+ )
+
+ assertThat(latest).hasSize(1)
+ assertThat(latest!![0])
+ .isInstanceOf(OngoingActivityChipModel.Active.ShortTimeDelta::class.java)
+
+ fakeSystemClock.advanceTime(5.minutes.inWholeMilliseconds)
+
+ assertThat(latest).hasSize(1)
+ assertThat(latest!![0])
.isInstanceOf(OngoingActivityChipModel.Active.ShortTimeDelta::class.java)
}
@@ -429,12 +576,14 @@ class NotifChipsViewModelTest : SysuiTestCase() {
fun chips_countUpTime_isTimer() =
kosmos.runTest {
val latest by collectLastValue(underTest.chips)
+ val currentTime = 30.minutes.inWholeMilliseconds
+ fakeSystemClock.setCurrentTimeMillis(currentTime)
val promotedContentBuilder =
PromotedNotificationContentModel.Builder("notif").apply {
this.time =
PromotedNotificationContentModel.When(
- time = 6543L,
+ time = currentTime + 10.minutes.inWholeMilliseconds,
mode = PromotedNotificationContentModel.When.Mode.CountUp,
)
}
@@ -457,12 +606,14 @@ class NotifChipsViewModelTest : SysuiTestCase() {
fun chips_countDownTime_isTimer() =
kosmos.runTest {
val latest by collectLastValue(underTest.chips)
+ val currentTime = 30.minutes.inWholeMilliseconds
+ fakeSystemClock.setCurrentTimeMillis(currentTime)
val promotedContentBuilder =
PromotedNotificationContentModel.Builder("notif").apply {
this.time =
PromotedNotificationContentModel.When(
- time = 6543L,
+ time = currentTime + 10.minutes.inWholeMilliseconds,
mode = PromotedNotificationContentModel.When.Mode.CountDown,
)
}
@@ -485,12 +636,14 @@ class NotifChipsViewModelTest : SysuiTestCase() {
fun chips_noHeadsUp_showsTime() =
kosmos.runTest {
val latest by collectLastValue(underTest.chips)
+ val currentTime = 30.minutes.inWholeMilliseconds
+ fakeSystemClock.setCurrentTimeMillis(currentTime)
val promotedContentBuilder =
PromotedNotificationContentModel.Builder("notif").apply {
this.time =
PromotedNotificationContentModel.When(
- time = 6543L,
+ time = currentTime + 10.minutes.inWholeMilliseconds,
mode = PromotedNotificationContentModel.When.Mode.BasicTime,
)
}
@@ -517,12 +670,14 @@ class NotifChipsViewModelTest : SysuiTestCase() {
fun chips_hasHeadsUpBySystem_showsTime() =
kosmos.runTest {
val latest by collectLastValue(underTest.chips)
+ val currentTime = 30.minutes.inWholeMilliseconds
+ fakeSystemClock.setCurrentTimeMillis(currentTime)
val promotedContentBuilder =
PromotedNotificationContentModel.Builder("notif").apply {
this.time =
PromotedNotificationContentModel.When(
- time = 6543L,
+ time = currentTime + 10.minutes.inWholeMilliseconds,
mode = PromotedNotificationContentModel.When.Mode.BasicTime,
)
}
@@ -556,12 +711,14 @@ class NotifChipsViewModelTest : SysuiTestCase() {
fun chips_hasHeadsUpByUser_forOtherNotif_showsTime() =
kosmos.runTest {
val latest by collectLastValue(underTest.chips)
+ val currentTime = 30.minutes.inWholeMilliseconds
+ fakeSystemClock.setCurrentTimeMillis(currentTime)
val promotedContentBuilder =
PromotedNotificationContentModel.Builder("notif").apply {
this.time =
PromotedNotificationContentModel.When(
- time = 6543L,
+ time = currentTime + 10.minutes.inWholeMilliseconds,
mode = PromotedNotificationContentModel.When.Mode.BasicTime,
)
}
@@ -569,7 +726,7 @@ class NotifChipsViewModelTest : SysuiTestCase() {
PromotedNotificationContentModel.Builder("other notif").apply {
this.time =
PromotedNotificationContentModel.When(
- time = 654321L,
+ time = currentTime + 10.minutes.inWholeMilliseconds,
mode = PromotedNotificationContentModel.When.Mode.BasicTime,
)
}
@@ -610,12 +767,14 @@ class NotifChipsViewModelTest : SysuiTestCase() {
fun chips_hasHeadsUpByUser_forThisNotif_onlyShowsIcon() =
kosmos.runTest {
val latest by collectLastValue(underTest.chips)
+ val currentTime = 30.minutes.inWholeMilliseconds
+ fakeSystemClock.setCurrentTimeMillis(currentTime)
val promotedContentBuilder =
PromotedNotificationContentModel.Builder("notif").apply {
this.time =
PromotedNotificationContentModel.When(
- time = 6543L,
+ time = currentTime + 10.minutes.inWholeMilliseconds,
mode = PromotedNotificationContentModel.When.Mode.BasicTime,
)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/featurepods/media/domain/interactor/MediaControlChipInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/featurepods/media/domain/interactor/MediaControlChipInteractorTest.kt
index 1a5f57dd43f8..6409a20d5156 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/featurepods/media/domain/interactor/MediaControlChipInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/featurepods/media/domain/interactor/MediaControlChipInteractorTest.kt
@@ -17,100 +17,119 @@
package com.android.systemui.statusbar.featurepods.media.domain.interactor
import android.graphics.drawable.Drawable
-import androidx.test.ext.junit.runners.AndroidJUnit4
+import android.platform.test.flag.junit.FlagsParameterization
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.flags.parameterizeSceneContainerFlag
+import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.collectLastValue
import com.android.systemui.kosmos.runTest
import com.android.systemui.kosmos.useUnconfinedTestDispatcher
import com.android.systemui.media.controls.data.repository.mediaFilterRepository
+import com.android.systemui.media.controls.domain.pipeline.MediaDataManager
import com.android.systemui.media.controls.shared.model.MediaAction
import com.android.systemui.media.controls.shared.model.MediaButton
import com.android.systemui.media.controls.shared.model.MediaData
import com.android.systemui.media.controls.shared.model.MediaDataLoadingModel
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
+import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Captor
+import org.mockito.MockitoAnnotations
import org.mockito.kotlin.mock
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
+@RunWith(ParameterizedAndroidJunit4::class)
@SmallTest
-@RunWith(AndroidJUnit4::class)
-class MediaControlChipInteractorTest : SysuiTestCase() {
-
+class MediaControlChipInteractorTest(flags: FlagsParameterization) : SysuiTestCase() {
private val kosmos = testKosmos().useUnconfinedTestDispatcher()
- private val underTest = kosmos.mediaControlChipInteractor
+ private val mediaFilterRepository = kosmos.mediaFilterRepository
+ private val Kosmos.underTest by Kosmos.Fixture { kosmos.mediaControlChipInteractor }
+ @Captor lateinit var listener: ArgumentCaptor<MediaDataManager.Listener>
+
+ companion object {
+ @JvmStatic
+ @Parameters(name = "{0}")
+ fun getParams(): List<FlagsParameterization> {
+ return parameterizeSceneContainerFlag()
+ }
+ }
+
+ @Before
+ fun setUp() {
+ kosmos.underTest.initialize()
+ MockitoAnnotations.initMocks(this)
+ }
+
+ init {
+ mSetFlagsRule.setFlagsParameterization(flags)
+ }
@Test
- fun mediaControlModel_noActiveMedia_null() =
+ fun mediaControlChipModel_noActiveMedia_null() =
kosmos.runTest {
- val model by collectLastValue(underTest.mediaControlModel)
+ val model by collectLastValue(underTest.mediaControlChipModel)
assertThat(model).isNull()
}
@Test
- fun mediaControlModel_activeMedia_notNull() =
+ fun mediaControlChipModel_activeMedia_notNull() =
kosmos.runTest {
- val model by collectLastValue(underTest.mediaControlModel)
+ val model by collectLastValue(underTest.mediaControlChipModel)
val userMedia = MediaData(active = true)
- val instanceId = userMedia.instanceId
- mediaFilterRepository.addSelectedUserMediaEntry(userMedia)
- mediaFilterRepository.addMediaDataLoadingState(MediaDataLoadingModel.Loaded(instanceId))
+ updateMedia(userMedia)
assertThat(model).isNotNull()
}
@Test
- fun mediaControlModel_mediaRemoved_null() =
+ fun mediaControlChipModel_mediaRemoved_null() =
kosmos.runTest {
- val model by collectLastValue(underTest.mediaControlModel)
+ val model by collectLastValue(underTest.mediaControlChipModel)
val userMedia = MediaData(active = true)
- val instanceId = userMedia.instanceId
- mediaFilterRepository.addSelectedUserMediaEntry(userMedia)
- mediaFilterRepository.addMediaDataLoadingState(MediaDataLoadingModel.Loaded(instanceId))
+ updateMedia(userMedia)
assertThat(model).isNotNull()
- assertThat(mediaFilterRepository.removeSelectedUserMediaEntry(instanceId, userMedia))
- .isTrue()
- mediaFilterRepository.addMediaDataLoadingState(
- MediaDataLoadingModel.Removed(instanceId)
- )
+ removeMedia(userMedia)
assertThat(model).isNull()
}
@Test
- fun mediaControlModel_songNameChanged_emitsUpdatedModel() =
+ fun mediaControlChipModel_songNameChanged_emitsUpdatedModel() =
kosmos.runTest {
- val model by collectLastValue(underTest.mediaControlModel)
+ val model by collectLastValue(underTest.mediaControlChipModel)
val initialSongName = "Initial Song"
val newSongName = "New Song"
val userMedia = MediaData(active = true, song = initialSongName)
- val instanceId = userMedia.instanceId
- mediaFilterRepository.addSelectedUserMediaEntry(userMedia)
- mediaFilterRepository.addMediaDataLoadingState(MediaDataLoadingModel.Loaded(instanceId))
+ updateMedia(userMedia)
assertThat(model).isNotNull()
assertThat(model?.songName).isEqualTo(initialSongName)
val updatedUserMedia = userMedia.copy(song = newSongName)
- mediaFilterRepository.addSelectedUserMediaEntry(updatedUserMedia)
+ updateMedia(updatedUserMedia)
assertThat(model?.songName).isEqualTo(newSongName)
}
@Test
- fun mediaControlModel_playPauseActionChanges_emitsUpdatedModel() =
+ fun mediaControlChipModel_playPauseActionChanges_emitsUpdatedModel() =
kosmos.runTest {
- val model by collectLastValue(underTest.mediaControlModel)
+ val model by collectLastValue(underTest.mediaControlChipModel)
val mockDrawable = mock<Drawable>()
@@ -123,9 +142,7 @@ class MediaControlChipInteractorTest : SysuiTestCase() {
)
val mediaButton = MediaButton(playOrPause = initialAction)
val userMedia = MediaData(active = true, semanticActions = mediaButton)
- val instanceId = userMedia.instanceId
- mediaFilterRepository.addSelectedUserMediaEntry(userMedia)
- mediaFilterRepository.addMediaDataLoadingState(MediaDataLoadingModel.Loaded(instanceId))
+ updateMedia(userMedia)
assertThat(model).isNotNull()
assertThat(model?.playOrPause).isEqualTo(initialAction)
@@ -139,15 +156,15 @@ class MediaControlChipInteractorTest : SysuiTestCase() {
)
val updatedMediaButton = MediaButton(playOrPause = newAction)
val updatedUserMedia = userMedia.copy(semanticActions = updatedMediaButton)
- mediaFilterRepository.addSelectedUserMediaEntry(updatedUserMedia)
+ updateMedia(updatedUserMedia)
assertThat(model?.playOrPause).isEqualTo(newAction)
}
@Test
- fun mediaControlModel_playPauseActionRemoved_playPauseNull() =
+ fun mediaControlChipModel_playPauseActionRemoved_playPauseNull() =
kosmos.runTest {
- val model by collectLastValue(underTest.mediaControlModel)
+ val model by collectLastValue(underTest.mediaControlChipModel)
val mockDrawable = mock<Drawable>()
@@ -160,16 +177,36 @@ class MediaControlChipInteractorTest : SysuiTestCase() {
)
val mediaButton = MediaButton(playOrPause = initialAction)
val userMedia = MediaData(active = true, semanticActions = mediaButton)
- val instanceId = userMedia.instanceId
- mediaFilterRepository.addSelectedUserMediaEntry(userMedia)
- mediaFilterRepository.addMediaDataLoadingState(MediaDataLoadingModel.Loaded(instanceId))
+ updateMedia(userMedia)
assertThat(model).isNotNull()
assertThat(model?.playOrPause).isEqualTo(initialAction)
val updatedUserMedia = userMedia.copy(semanticActions = MediaButton())
- mediaFilterRepository.addSelectedUserMediaEntry(updatedUserMedia)
+ updateMedia(updatedUserMedia)
assertThat(model?.playOrPause).isNull()
}
+
+ private fun updateMedia(mediaData: MediaData) {
+ if (SceneContainerFlag.isEnabled) {
+ val instanceId = mediaData.instanceId
+ mediaFilterRepository.addSelectedUserMediaEntry(mediaData)
+ mediaFilterRepository.addMediaDataLoadingState(MediaDataLoadingModel.Loaded(instanceId))
+ } else {
+ kosmos.underTest.updateMediaControlChipModelLegacy(mediaData)
+ }
+ }
+
+ private fun removeMedia(mediaData: MediaData) {
+ if (SceneContainerFlag.isEnabled) {
+ val instanceId = mediaData.instanceId
+ mediaFilterRepository.removeSelectedUserMediaEntry(instanceId, mediaData)
+ mediaFilterRepository.addMediaDataLoadingState(
+ MediaDataLoadingModel.Removed(instanceId)
+ )
+ } else {
+ kosmos.underTest.updateMediaControlChipModelLegacy(null)
+ }
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/featurepods/media/ui/viewmodel/MediaControlChipViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/featurepods/media/ui/viewmodel/MediaControlChipViewModelTest.kt
index 8650e4b8cfce..d36dbbe8d36f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/featurepods/media/ui/viewmodel/MediaControlChipViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/featurepods/media/ui/viewmodel/MediaControlChipViewModelTest.kt
@@ -16,26 +16,57 @@
package com.android.systemui.statusbar.featurepods.media.ui.viewmodel
-import androidx.test.ext.junit.runners.AndroidJUnit4
+import android.platform.test.flag.junit.FlagsParameterization
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.flags.parameterizeSceneContainerFlag
+import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.collectLastValue
import com.android.systemui.kosmos.runTest
import com.android.systemui.kosmos.useUnconfinedTestDispatcher
import com.android.systemui.media.controls.data.repository.mediaFilterRepository
+import com.android.systemui.media.controls.domain.pipeline.MediaDataManager
import com.android.systemui.media.controls.shared.model.MediaData
import com.android.systemui.media.controls.shared.model.MediaDataLoadingModel
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
+import com.android.systemui.statusbar.featurepods.media.domain.interactor.mediaControlChipInteractor
import com.android.systemui.statusbar.featurepods.popups.shared.model.PopupChipModel
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlin.test.Test
+import org.junit.Before
import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Captor
+import org.mockito.MockitoAnnotations
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
@SmallTest
-@RunWith(AndroidJUnit4::class)
-class MediaControlChipViewModelTest : SysuiTestCase() {
+@RunWith(ParameterizedAndroidJunit4::class)
+class MediaControlChipViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
private val kosmos = testKosmos().useUnconfinedTestDispatcher()
- private val underTest = kosmos.mediaControlChipViewModel
+ private val mediaControlChipInteractor by lazy { kosmos.mediaControlChipInteractor }
+ private val Kosmos.underTest by Kosmos.Fixture { kosmos.mediaControlChipViewModel }
+ @Captor lateinit var listener: ArgumentCaptor<MediaDataManager.Listener>
+
+ companion object {
+ @JvmStatic
+ @Parameters(name = "{0}")
+ fun getParams(): List<FlagsParameterization> {
+ return parameterizeSceneContainerFlag()
+ }
+ }
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ mediaControlChipInteractor.initialize()
+ }
+
+ init {
+ mSetFlagsRule.setFlagsParameterization(flags)
+ }
@Test
fun chip_noActiveMedia_IsHidden() =
@@ -51,10 +82,7 @@ class MediaControlChipViewModelTest : SysuiTestCase() {
val chip by collectLastValue(underTest.chip)
val userMedia = MediaData(active = true, song = "test")
- val instanceId = userMedia.instanceId
-
- mediaFilterRepository.addSelectedUserMediaEntry(userMedia)
- mediaFilterRepository.addMediaDataLoadingState(MediaDataLoadingModel.Loaded(instanceId))
+ updateMedia(userMedia)
assertThat(chip).isInstanceOf(PopupChipModel.Shown::class.java)
}
@@ -67,16 +95,25 @@ class MediaControlChipViewModelTest : SysuiTestCase() {
val initialSongName = "Initial Song"
val newSongName = "New Song"
val userMedia = MediaData(active = true, song = initialSongName)
- val instanceId = userMedia.instanceId
-
- mediaFilterRepository.addSelectedUserMediaEntry(userMedia)
- mediaFilterRepository.addMediaDataLoadingState(MediaDataLoadingModel.Loaded(instanceId))
-
+ updateMedia(userMedia)
+ assertThat(chip).isInstanceOf(PopupChipModel.Shown::class.java)
assertThat((chip as PopupChipModel.Shown).chipText).isEqualTo(initialSongName)
val updatedUserMedia = userMedia.copy(song = newSongName)
- mediaFilterRepository.addSelectedUserMediaEntry(updatedUserMedia)
+ updateMedia(updatedUserMedia)
assertThat((chip as PopupChipModel.Shown).chipText).isEqualTo(newSongName)
}
+
+ private fun updateMedia(mediaData: MediaData) {
+ if (SceneContainerFlag.isEnabled) {
+ val instanceId = mediaData.instanceId
+ kosmos.mediaFilterRepository.addSelectedUserMediaEntry(mediaData)
+ kosmos.mediaFilterRepository.addMediaDataLoadingState(
+ MediaDataLoadingModel.Loaded(instanceId)
+ )
+ } else {
+ mediaControlChipInteractor.updateMediaControlChipModelLegacy(mediaData)
+ }
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/AssistantFeedbackControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/AssistantFeedbackControllerTest.java
index d66b010daefd..a58f7f72f08a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/AssistantFeedbackControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/AssistantFeedbackControllerTest.java
@@ -51,8 +51,10 @@ import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
+import com.android.systemui.statusbar.notification.people.NotificationPersonExtractor;
import com.android.systemui.util.DeviceConfigProxyFake;
+import org.jetbrains.annotations.NotNull;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/DynamicChildBindControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/DynamicChildBindControllerTest.java
index 77fd06757595..8520508c7611 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/DynamicChildBindControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/DynamicChildBindControllerTest.java
@@ -132,7 +132,7 @@ public class DynamicChildBindControllerTest extends SysuiTestCase {
LayoutInflater inflater = LayoutInflater.from(mContext);
inflater.setFactory2(
new RowInflaterTask.RowAsyncLayoutInflater(entry, new FakeSystemClock(), mock(
- RowInflaterTaskLogger.class)));
+ RowInflaterTaskLogger.class), mContext.getUser()));
ExpandableNotificationRow row = (ExpandableNotificationRow)
inflater.inflate(R.layout.status_bar_notification_row, null);
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/BundleEntryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/BundleEntryTest.kt
new file mode 100644
index 000000000000..426af264da07
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/BundleEntryTest.kt
@@ -0,0 +1,75 @@
+/*
+ * 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.collection
+
+import android.platform.test.annotations.EnableFlags
+import android.platform.test.flag.junit.SetFlagsRule
+import android.testing.TestableLooper.RunWithLooper
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.notification.shared.NotificationBundleUi
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@RunWithLooper
+class BundleEntryTest : SysuiTestCase() {
+ private lateinit var underTest: BundleEntry
+
+ @get:Rule
+ val setFlagsRule = SetFlagsRule()
+
+ @Before
+ fun setUp() {
+ underTest = BundleEntry("key")
+ }
+
+ @Test
+ @EnableFlags(NotificationBundleUi.FLAG_NAME)
+ fun getParent_adapter() {
+ assertThat(underTest.entryAdapter.parent).isEqualTo(GroupEntry.ROOT_ENTRY)
+ }
+
+ @Test
+ @EnableFlags(NotificationBundleUi.FLAG_NAME)
+ fun isTopLevelEntry_adapter() {
+ assertThat(underTest.entryAdapter.isTopLevelEntry).isTrue()
+ }
+
+ @Test
+ @EnableFlags(NotificationBundleUi.FLAG_NAME)
+ fun getRow_adapter() {
+ assertThat(underTest.entryAdapter.row).isNull()
+ }
+
+ @Test
+ @EnableFlags(NotificationBundleUi.FLAG_NAME)
+ fun getGroupRoot_adapter() {
+ assertThat(underTest.entryAdapter.groupRoot).isEqualTo(underTest.entryAdapter)
+ }
+
+ @Test
+ @EnableFlags(NotificationBundleUi.FLAG_NAME)
+ fun getKey_adapter() {
+ assertThat(underTest.entryAdapter.key).isEqualTo("key")
+ }
+} \ No newline at end of file
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/HighPriorityProviderTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/HighPriorityProviderTest.java
index 8e95ac599ce1..76e2d619a4df 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/HighPriorityProviderTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/HighPriorityProviderTest.java
@@ -30,6 +30,9 @@ import static org.mockito.Mockito.when;
import android.app.Notification;
import android.app.NotificationChannel;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
@@ -39,8 +42,10 @@ import com.android.systemui.statusbar.RankingBuilder;
import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider;
import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
+import com.android.systemui.statusbar.notification.shared.NotificationBundleUi;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@@ -57,6 +62,9 @@ public class HighPriorityProviderTest extends SysuiTestCase {
@Mock private GroupMembershipManager mGroupMembershipManager;
private HighPriorityProvider mHighPriorityProvider;
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
@@ -210,6 +218,7 @@ public class HighPriorityProviderTest extends SysuiTestCase {
}
@Test
+ @DisableFlags(NotificationBundleUi.FLAG_NAME)
public void testIsHighPriority_checkChildrenToCalculatePriority_legacy() {
// GIVEN: a summary with low priority has a highPriorityChild and a lowPriorityChild
final NotificationEntry summary = createNotifEntry(false);
@@ -247,20 +256,18 @@ public class HighPriorityProviderTest extends SysuiTestCase {
}
@Test
- public void testIsHighPriority_checkChildrenToCalculatePriority() {
+ public void testIsHighPriority_checkChildrenViewsToCalculatePriority() {
// GIVEN:
// parent with summary = lowPrioritySummary
// NotificationEntry = lowPriorityChild
// NotificationEntry = highPriorityChild
+ List<NotificationEntry> children = List.of(createNotifEntry(false), createNotifEntry(true));
final NotificationEntry lowPrioritySummary = createNotifEntry(false);
final GroupEntry parentEntry = new GroupEntryBuilder()
.setSummary(lowPrioritySummary)
+ .setChildren(children)
.build();
- when(mGroupMembershipManager.getChildren(parentEntry)).thenReturn(
- new ArrayList<>(
- List.of(
- createNotifEntry(false),
- createNotifEntry(true))));
+ when(mGroupMembershipManager.getChildren(parentEntry)).thenReturn(children);
// THEN the GroupEntry parentEntry is high priority since it has a high priority child
assertTrue(mHighPriorityProvider.isHighPriority(parentEntry));
@@ -272,10 +279,11 @@ public class HighPriorityProviderTest extends SysuiTestCase {
// parent with summary = lowPrioritySummary
// NotificationEntry = lowPriorityChild
final NotificationEntry lowPrioritySummary = createNotifEntry(false);
+ final NotificationEntry lowPriorityChild = createNotifEntry(false);
final GroupEntry parentEntry = new GroupEntryBuilder()
.setSummary(lowPrioritySummary)
+ .setChildren(List.of(lowPriorityChild))
.build();
- final NotificationEntry lowPriorityChild = createNotifEntry(false);
when(mGroupMembershipManager.getChildren(parentEntry)).thenReturn(
new ArrayList<>(List.of(lowPriorityChild)));
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/ColorizedFgsCoordinatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/ColorizedFgsCoordinatorTest.kt
index e93c74252251..7fa157fa7cb3 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/ColorizedFgsCoordinatorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/ColorizedFgsCoordinatorTest.kt
@@ -27,14 +27,29 @@ import android.testing.TestableLooper.RunWithLooper
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.applicationCoroutineScope
+import com.android.systemui.kosmos.collectLastValue
+import com.android.systemui.kosmos.runTest
import com.android.systemui.kosmos.useUnconfinedTestDispatcher
+import com.android.systemui.statusbar.chips.notification.domain.interactor.statusBarNotificationChipsInteractor
+import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips
+import com.android.systemui.statusbar.core.StatusBarRootModernization
+import com.android.systemui.statusbar.notification.buildNotificationEntry
+import com.android.systemui.statusbar.notification.buildOngoingCallEntry
+import com.android.systemui.statusbar.notification.buildPromotedOngoingEntry
import com.android.systemui.statusbar.notification.collection.buildEntry
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner
import com.android.systemui.statusbar.notification.collection.notifPipeline
+import com.android.systemui.statusbar.notification.domain.interactor.renderNotificationListInteractor
import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUi
+import com.android.systemui.statusbar.notification.promoted.domain.interactor.promotedNotificationsInteractor
+import com.android.systemui.statusbar.phone.ongoingcall.StatusBarChipsModernization
import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.withArgCaptor
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Before
@@ -59,7 +74,13 @@ class ColorizedFgsCoordinatorTest : SysuiTestCase() {
fun setup() {
allowTestableLooperAsMainThread()
- colorizedFgsCoordinator = ColorizedFgsCoordinator()
+ kosmos.statusBarNotificationChipsInteractor.start()
+
+ colorizedFgsCoordinator =
+ ColorizedFgsCoordinator(
+ kosmos.applicationCoroutineScope,
+ kosmos.promotedNotificationsInteractor,
+ )
colorizedFgsCoordinator.attach(notifPipeline)
sectioner = colorizedFgsCoordinator.sectioner
}
@@ -178,6 +199,37 @@ class ColorizedFgsCoordinatorTest : SysuiTestCase() {
verify(notifPipeline, never()).addPromoter(any())
}
+ @Test
+ @EnableFlags(
+ PromotedNotificationUi.FLAG_NAME,
+ StatusBarNotifChips.FLAG_NAME,
+ StatusBarChipsModernization.FLAG_NAME,
+ StatusBarRootModernization.FLAG_NAME,
+ )
+ fun comparatorPutsCallBeforeOther() =
+ kosmos.runTest {
+ // GIVEN a call and a promoted ongoing notification
+ val callEntry = buildOngoingCallEntry(promoted = false)
+ val ronEntry = buildPromotedOngoingEntry()
+ val otherEntry = buildNotificationEntry(tag = "other")
+
+ kosmos.renderNotificationListInteractor.setRenderedList(
+ listOf(callEntry, ronEntry, otherEntry)
+ )
+
+ val orderedChipNotificationKeys by
+ collectLastValue(kosmos.promotedNotificationsInteractor.orderedChipNotificationKeys)
+
+ // THEN the order of the notification keys should be the call then the RON
+ assertThat(orderedChipNotificationKeys)
+ .containsExactly("0|test_pkg|0|call|0", "0|test_pkg|0|ron|0")
+
+ // VERIFY that the comparator puts the call before the ron
+ assertThat(sectioner.comparator!!.compare(callEntry, ronEntry)).isLessThan(0)
+ // VERIFY that the comparator puts the ron before the other
+ assertThat(sectioner.comparator!!.compare(ronEntry, otherEntry)).isLessThan(0)
+ }
+
private fun makeCallStyle(): Notification.CallStyle {
val pendingIntent =
PendingIntent.getBroadcast(mContext, 0, Intent("action"), PendingIntent.FLAG_IMMUTABLE)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerTest.kt
index db5921d8bd36..3dd0982ba2ff 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerTest.kt
@@ -17,23 +17,29 @@
package com.android.systemui.statusbar.notification.collection.render
import android.os.Build
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
+import android.platform.test.flag.junit.SetFlagsRule
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.dump.DumpManager
import com.android.systemui.log.assertLogsWtf
+import com.android.systemui.statusbar.notification.collection.GroupEntry
import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder
import com.android.systemui.statusbar.notification.collection.ListEntry
import com.android.systemui.statusbar.notification.collection.NotifPipeline
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeRenderListListener
import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager.OnGroupExpansionChangeListener
+import com.android.systemui.statusbar.notification.shared.NotificationBundleUi
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.withArgCaptor
import com.google.common.truth.Truth.assertThat
import org.junit.Assume
import org.junit.Before
+import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito.never
@@ -44,6 +50,9 @@ import org.mockito.Mockito.`when` as whenever
@SmallTest
@RunWith(AndroidJUnit4::class)
class GroupExpansionManagerTest : SysuiTestCase() {
+ @get:Rule
+ val setFlagsRule = SetFlagsRule()
+
private lateinit var underTest: GroupExpansionManagerImpl
private val dumpManager: DumpManager = mock()
@@ -52,8 +61,8 @@ class GroupExpansionManagerTest : SysuiTestCase() {
private val pipeline: NotifPipeline = mock()
private lateinit var beforeRenderListListener: OnBeforeRenderListListener
- private val summary1 = notificationEntry("foo", 1)
- private val summary2 = notificationEntry("bar", 1)
+ private val summary1 = notificationSummaryEntry("foo", 1)
+ private val summary2 = notificationSummaryEntry("bar", 1)
private val entries =
listOf<ListEntry>(
GroupEntryBuilder()
@@ -82,15 +91,25 @@ class GroupExpansionManagerTest : SysuiTestCase() {
private fun notificationEntry(pkg: String, id: Int) =
NotificationEntryBuilder().setPkg(pkg).setId(id).build().apply { row = mock() }
+ private fun notificationSummaryEntry(pkg: String, id: Int) =
+ NotificationEntryBuilder().setPkg(pkg).setId(id).setParent(GroupEntry.ROOT_ENTRY).build()
+ .apply { row = mock() }
+
@Before
fun setUp() {
whenever(groupMembershipManager.getGroupSummary(summary1)).thenReturn(summary1)
whenever(groupMembershipManager.getGroupSummary(summary2)).thenReturn(summary2)
+ whenever(groupMembershipManager.getGroupRoot(summary1.entryAdapter))
+ .thenReturn(summary1.entryAdapter)
+ whenever(groupMembershipManager.getGroupRoot(summary2.entryAdapter))
+ .thenReturn(summary2.entryAdapter)
+
underTest = GroupExpansionManagerImpl(dumpManager, groupMembershipManager)
}
@Test
+ @DisableFlags(NotificationBundleUi.FLAG_NAME)
fun notifyOnlyOnChange() {
var listenerCalledCount = 0
underTest.registerGroupExpansionChangeListener { _, _ -> listenerCalledCount++ }
@@ -108,6 +127,25 @@ class GroupExpansionManagerTest : SysuiTestCase() {
}
@Test
+ @EnableFlags(NotificationBundleUi.FLAG_NAME)
+ fun notifyOnlyOnChange_withEntryAdapter() {
+ var listenerCalledCount = 0
+ underTest.registerGroupExpansionChangeListener { _, _ -> listenerCalledCount++ }
+
+ underTest.setGroupExpanded(summary1.entryAdapter, false)
+ assertThat(listenerCalledCount).isEqualTo(0)
+ underTest.setGroupExpanded(summary1.entryAdapter, true)
+ assertThat(listenerCalledCount).isEqualTo(1)
+ underTest.setGroupExpanded(summary2.entryAdapter, true)
+ assertThat(listenerCalledCount).isEqualTo(2)
+ underTest.setGroupExpanded(summary1.entryAdapter, true)
+ assertThat(listenerCalledCount).isEqualTo(2)
+ underTest.setGroupExpanded(summary2.entryAdapter, false)
+ assertThat(listenerCalledCount).isEqualTo(3)
+ }
+
+ @Test
+ @DisableFlags(NotificationBundleUi.FLAG_NAME)
fun expandUnattachedEntry() {
// First, expand the entry when it is attached.
underTest.setGroupExpanded(summary1, true)
@@ -122,6 +160,22 @@ class GroupExpansionManagerTest : SysuiTestCase() {
}
@Test
+ @EnableFlags(NotificationBundleUi.FLAG_NAME)
+ fun expandUnattachedEntryAdapter() {
+ // First, expand the entry when it is attached.
+ underTest.setGroupExpanded(summary1.entryAdapter, true)
+ assertThat(underTest.isGroupExpanded(summary1.entryAdapter)).isTrue()
+
+ // Un-attach it, and un-expand it.
+ NotificationEntryBuilder.setNewParent(summary1, null)
+ underTest.setGroupExpanded(summary1.entryAdapter, false)
+
+ // Expanding again should throw.
+ assertLogsWtf { underTest.setGroupExpanded(summary1.entryAdapter, true) }
+ }
+
+ @Test
+ @DisableFlags(NotificationBundleUi.FLAG_NAME)
fun syncWithPipeline() {
underTest.attach(pipeline)
beforeRenderListListener = withArgCaptor {
@@ -143,4 +197,28 @@ class GroupExpansionManagerTest : SysuiTestCase() {
verify(listener).onGroupExpansionChange(summary1.row, false)
verifyNoMoreInteractions(listener)
}
+
+ @Test
+ @EnableFlags(NotificationBundleUi.FLAG_NAME)
+ fun syncWithPipeline_withEntryAdapter() {
+ underTest.attach(pipeline)
+ beforeRenderListListener = withArgCaptor {
+ verify(pipeline).addOnBeforeRenderListListener(capture())
+ }
+
+ val listener: OnGroupExpansionChangeListener = mock()
+ underTest.registerGroupExpansionChangeListener(listener)
+
+ beforeRenderListListener.onBeforeRenderList(entries)
+ verify(listener, never()).onGroupExpansionChange(any(), any())
+
+ // Expand one of the groups.
+ underTest.setGroupExpanded(summary1.entryAdapter, true)
+ verify(listener).onGroupExpansionChange(summary1.row, true)
+
+ // Empty the pipeline list and verify that the group is no longer expanded.
+ beforeRenderListListener.onBeforeRenderList(emptyList())
+ verify(listener).onGroupExpansionChange(summary1.row, false)
+ verifyNoMoreInteractions(listener)
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManagerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManagerTest.kt
index 2cbcc5a8d925..dcbf44e6e301 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManagerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManagerTest.kt
@@ -16,34 +16,46 @@
package com.android.systemui.statusbar.notification.collection.render
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
+import android.platform.test.flag.junit.SetFlagsRule
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.statusbar.notification.collection.GroupEntry
import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
+import com.android.systemui.statusbar.notification.shared.NotificationBundleUi
import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@SmallTest
@RunWith(AndroidJUnit4::class)
class GroupMembershipManagerTest : SysuiTestCase() {
+
+ @get:Rule
+ val setFlagsRule = SetFlagsRule()
+
private var underTest = GroupMembershipManagerImpl()
@Test
+ @DisableFlags(NotificationBundleUi.FLAG_NAME)
fun isChildInGroup_topLevel() {
val topLevelEntry = NotificationEntryBuilder().setParent(GroupEntry.ROOT_ENTRY).build()
assertThat(underTest.isChildInGroup(topLevelEntry)).isFalse()
}
@Test
+ @DisableFlags(NotificationBundleUi.FLAG_NAME)
fun isChildInGroup_noParent() {
val noParentEntry = NotificationEntryBuilder().setParent(null).build()
assertThat(underTest.isChildInGroup(noParentEntry)).isFalse()
}
@Test
+ @DisableFlags(NotificationBundleUi.FLAG_NAME)
fun isChildInGroup_summary() {
val groupKey = "group"
val summary =
@@ -57,12 +69,14 @@ class GroupMembershipManagerTest : SysuiTestCase() {
}
@Test
+ @DisableFlags(NotificationBundleUi.FLAG_NAME)
fun isGroupSummary_topLevelEntry() {
val entry = NotificationEntryBuilder().setParent(GroupEntry.ROOT_ENTRY).build()
assertThat(underTest.isGroupSummary(entry)).isFalse()
}
@Test
+ @DisableFlags(NotificationBundleUi.FLAG_NAME)
fun isGroupSummary_summary() {
val groupKey = "group"
val summary =
@@ -76,6 +90,7 @@ class GroupMembershipManagerTest : SysuiTestCase() {
}
@Test
+ @DisableFlags(NotificationBundleUi.FLAG_NAME)
fun isGroupSummary_child() {
val groupKey = "group"
val summary =
@@ -90,12 +105,14 @@ class GroupMembershipManagerTest : SysuiTestCase() {
}
@Test
+ @DisableFlags(NotificationBundleUi.FLAG_NAME)
fun getGroupSummary_topLevelEntry() {
val entry = NotificationEntryBuilder().setParent(GroupEntry.ROOT_ENTRY).build()
assertThat(underTest.getGroupSummary(entry)).isNull()
}
@Test
+ @DisableFlags(NotificationBundleUi.FLAG_NAME)
fun getGroupSummary_summary() {
val groupKey = "group"
val summary =
@@ -109,6 +126,7 @@ class GroupMembershipManagerTest : SysuiTestCase() {
}
@Test
+ @DisableFlags(NotificationBundleUi.FLAG_NAME)
fun getGroupSummary_child() {
val groupKey = "group"
val summary =
@@ -121,4 +139,104 @@ class GroupMembershipManagerTest : SysuiTestCase() {
assertThat(underTest.getGroupSummary(entry)).isEqualTo(summary)
}
+
+ @Test
+ @EnableFlags(NotificationBundleUi.FLAG_NAME)
+ fun isChildEntryAdapterInGroup_topLevel() {
+ val topLevelEntry = NotificationEntryBuilder().setParent(GroupEntry.ROOT_ENTRY).build()
+ assertThat(underTest.isChildInGroup(topLevelEntry.entryAdapter)).isFalse()
+ }
+
+ @Test
+ @EnableFlags(NotificationBundleUi.FLAG_NAME)
+ fun isChildEntryAdapterInGroup_noParent() {
+ val noParentEntry = NotificationEntryBuilder().setParent(null).build()
+ assertThat(underTest.isChildInGroup(noParentEntry.entryAdapter)).isFalse()
+ }
+
+ @Test
+ @EnableFlags(NotificationBundleUi.FLAG_NAME)
+ fun isChildEntryAdapterInGroup_summary() {
+ val groupKey = "group"
+ val summary =
+ NotificationEntryBuilder()
+ .setGroup(mContext, groupKey)
+ .setGroupSummary(mContext, true)
+ .build()
+ GroupEntryBuilder().setKey(groupKey).setSummary(summary).build()
+
+ assertThat(underTest.isChildInGroup(summary.entryAdapter)).isFalse()
+ }
+
+ @Test
+ @EnableFlags(NotificationBundleUi.FLAG_NAME)
+ fun isGroupRoot_topLevelEntry() {
+ val entry = NotificationEntryBuilder().setParent(GroupEntry.ROOT_ENTRY).build()
+ assertThat(underTest.isGroupRoot(entry.entryAdapter)).isFalse()
+ }
+
+ @Test
+ @EnableFlags(NotificationBundleUi.FLAG_NAME)
+ fun isGroupRoot_summary() {
+ val groupKey = "group"
+ val summary =
+ NotificationEntryBuilder()
+ .setGroup(mContext, groupKey)
+ .setGroupSummary(mContext, true)
+ .build()
+ GroupEntryBuilder().setKey(groupKey).setSummary(summary).build()
+
+ assertThat(underTest.isGroupRoot(summary.entryAdapter)).isTrue()
+ }
+
+ @Test
+ @EnableFlags(NotificationBundleUi.FLAG_NAME)
+ fun isGroupRoot_child() {
+ val groupKey = "group"
+ val summary =
+ NotificationEntryBuilder()
+ .setGroup(mContext, groupKey)
+ .setGroupSummary(mContext, true)
+ .build()
+ val entry = NotificationEntryBuilder().setGroup(mContext, groupKey).build()
+ GroupEntryBuilder().setKey(groupKey).setSummary(summary).addChild(entry).build()
+
+ assertThat(underTest.isGroupRoot(entry.entryAdapter)).isFalse()
+ }
+
+ @Test
+ @EnableFlags(NotificationBundleUi.FLAG_NAME)
+ fun getGroupRoot_topLevelEntry() {
+ val entry = NotificationEntryBuilder().setParent(GroupEntry.ROOT_ENTRY).build()
+ assertThat(underTest.getGroupRoot(entry.entryAdapter)).isNull()
+ }
+
+ @Test
+ @EnableFlags(NotificationBundleUi.FLAG_NAME)
+ fun getGroupRoot_summary() {
+ val groupKey = "group"
+ val summary =
+ NotificationEntryBuilder()
+ .setGroup(mContext, groupKey)
+ .setGroupSummary(mContext, true)
+ .build()
+ GroupEntryBuilder().setKey(groupKey).setSummary(summary).build()
+
+ assertThat(underTest.getGroupRoot(summary.entryAdapter)).isEqualTo(summary.entryAdapter)
+ }
+
+ @Test
+ @EnableFlags(NotificationBundleUi.FLAG_NAME)
+ fun getGroupRoot_child() {
+ val groupKey = "group"
+ val summary =
+ NotificationEntryBuilder()
+ .setGroup(mContext, groupKey)
+ .setGroupSummary(mContext, true)
+ .build()
+ val entry = NotificationEntryBuilder().setGroup(mContext, groupKey).build()
+ GroupEntryBuilder().setKey(groupKey).setSummary(summary).addChild(entry).build()
+
+ assertThat(underTest.getGroupRoot(entry.entryAdapter)).isEqualTo(summary.entryAdapter)
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/emptyshade/ui/viewmodel/EmptyShadeViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/emptyshade/ui/viewmodel/EmptyShadeViewModelTest.kt
index 5ba972def76d..7cbc839c0ab5 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/emptyshade/ui/viewmodel/EmptyShadeViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/emptyshade/ui/viewmodel/EmptyShadeViewModelTest.kt
@@ -140,7 +140,7 @@ class EmptyShadeViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
@Test
@EnableFlags(ModesEmptyShadeFix.FLAG_NAME)
- @DisableFlags(Flags.FLAG_MODES_UI, Flags.FLAG_MODES_API)
+ @DisableFlags(Flags.FLAG_MODES_UI)
fun text_changesWhenNotifsHiddenInShade() =
testScope.runTest {
val text by collectLastValue(underTest.text)
@@ -163,7 +163,7 @@ class EmptyShadeViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
}
@Test
- @EnableFlags(ModesEmptyShadeFix.FLAG_NAME, Flags.FLAG_MODES_UI, Flags.FLAG_MODES_API)
+ @EnableFlags(ModesEmptyShadeFix.FLAG_NAME, Flags.FLAG_MODES_UI)
fun text_changesWhenLocaleChanges() =
testScope.runTest {
val text by collectLastValue(underTest.text)
@@ -186,7 +186,7 @@ class EmptyShadeViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
}
@Test
- @EnableFlags(ModesEmptyShadeFix.FLAG_NAME, Flags.FLAG_MODES_UI, Flags.FLAG_MODES_API)
+ @EnableFlags(ModesEmptyShadeFix.FLAG_NAME, Flags.FLAG_MODES_UI)
fun text_reflectsModesHidingNotifications() =
testScope.runTest {
val text by collectLastValue(underTest.text)
@@ -250,7 +250,7 @@ class EmptyShadeViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
}
@Test
- @EnableFlags(ModesEmptyShadeFix.FLAG_NAME, Flags.FLAG_MODES_UI, Flags.FLAG_MODES_API)
+ @EnableFlags(ModesEmptyShadeFix.FLAG_NAME, Flags.FLAG_MODES_UI)
fun onClick_whenHistoryDisabled_leadsToSettingsPage() =
testScope.runTest {
val onClick by collectLastValue(underTest.onClick)
@@ -264,7 +264,7 @@ class EmptyShadeViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
}
@Test
- @EnableFlags(ModesEmptyShadeFix.FLAG_NAME, Flags.FLAG_MODES_UI, Flags.FLAG_MODES_API)
+ @EnableFlags(ModesEmptyShadeFix.FLAG_NAME, Flags.FLAG_MODES_UI)
fun onClick_whenHistoryEnabled_leadsToHistoryPage() =
testScope.runTest {
val onClick by collectLastValue(underTest.onClick)
@@ -279,7 +279,7 @@ class EmptyShadeViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
}
@Test
- @EnableFlags(ModesEmptyShadeFix.FLAG_NAME, Flags.FLAG_MODES_UI, Flags.FLAG_MODES_API)
+ @EnableFlags(ModesEmptyShadeFix.FLAG_NAME, Flags.FLAG_MODES_UI)
fun onClick_whenOneModeHidingNotifications_leadsToModeSettings() =
testScope.runTest {
val onClick by collectLastValue(underTest.onClick)
@@ -305,7 +305,7 @@ class EmptyShadeViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
}
@Test
- @EnableFlags(ModesEmptyShadeFix.FLAG_NAME, Flags.FLAG_MODES_UI, Flags.FLAG_MODES_API)
+ @EnableFlags(ModesEmptyShadeFix.FLAG_NAME, Flags.FLAG_MODES_UI)
fun onClick_whenMultipleModesHidingNotifications_leadsToGeneralModesSettings() =
testScope.runTest {
val onClick by collectLastValue(underTest.onClick)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerImplTest.kt
index 339f8fac3820..e22acd53e584 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerImplTest.kt
@@ -106,11 +106,15 @@ class HeadsUpManagerImplTest(flags: FlagsParameterization) : SysuiTestCase() {
this.addOverride(R.integer.touch_acceptance_delay, TEST_TOUCH_ACCEPTANCE_TIME)
this.addOverride(
R.integer.heads_up_notification_minimum_time,
- TEST_MINIMUM_DISPLAY_TIME,
+ TEST_MINIMUM_DISPLAY_TIME_DEFAULT,
)
this.addOverride(
R.integer.heads_up_notification_minimum_time_with_throttling,
- TEST_MINIMUM_DISPLAY_TIME,
+ TEST_MINIMUM_DISPLAY_TIME_DEFAULT,
+ )
+ this.addOverride(
+ R.integer.heads_up_notification_minimum_time_for_user_initiated,
+ TEST_MINIMUM_DISPLAY_TIME_FOR_USER_INITIATED,
)
this.addOverride(R.integer.heads_up_notification_decay, TEST_AUTO_DISMISS_TIME)
this.addOverride(
@@ -414,7 +418,7 @@ class HeadsUpManagerImplTest(flags: FlagsParameterization) : SysuiTestCase() {
}
@Test
- fun testRemoveNotification_beforeMinimumDisplayTime() {
+ fun testRemoveNotification_beforeMinimumDisplayTime_notUserInitiatedHun() {
val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
useAccessibilityTimeout(false)
@@ -429,18 +433,22 @@ class HeadsUpManagerImplTest(flags: FlagsParameterization) : SysuiTestCase() {
assertThat(removedImmediately).isFalse()
assertThat(underTest.isHeadsUpEntry(entry.key)).isTrue()
- systemClock.advanceTime(((TEST_MINIMUM_DISPLAY_TIME + TEST_AUTO_DISMISS_TIME) / 2).toLong())
+ systemClock.advanceTime(
+ ((TEST_MINIMUM_DISPLAY_TIME_DEFAULT + TEST_AUTO_DISMISS_TIME) / 2).toLong()
+ )
assertThat(underTest.isHeadsUpEntry(entry.key)).isFalse()
}
@Test
- fun testRemoveNotification_afterMinimumDisplayTime() {
+ fun testRemoveNotification_afterMinimumDisplayTime_notUserInitiatedHun() {
val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
useAccessibilityTimeout(false)
underTest.showNotification(entry)
- systemClock.advanceTime(((TEST_MINIMUM_DISPLAY_TIME + TEST_AUTO_DISMISS_TIME) / 2).toLong())
+ systemClock.advanceTime(
+ ((TEST_MINIMUM_DISPLAY_TIME_DEFAULT + TEST_AUTO_DISMISS_TIME) / 2).toLong()
+ )
assertThat(underTest.isHeadsUpEntry(entry.key)).isTrue()
@@ -455,6 +463,57 @@ class HeadsUpManagerImplTest(flags: FlagsParameterization) : SysuiTestCase() {
}
@Test
+ @EnableFlags(StatusBarNotifChips.FLAG_NAME)
+ fun testRemoveNotification_beforeMinimumDisplayTime_forUserInitiatedHun() {
+ useAccessibilityTimeout(false)
+
+ val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
+ entry.row = testHelper.createRow()
+ underTest.showNotification(entry, isPinnedByUser = true)
+
+ val removedImmediately =
+ underTest.removeNotification(
+ entry.key,
+ /* releaseImmediately = */ false,
+ "beforeMinimumDisplayTime",
+ )
+ assertThat(removedImmediately).isFalse()
+ assertThat(underTest.isHeadsUpEntry(entry.key)).isTrue()
+
+ systemClock.advanceTime(
+ ((TEST_MINIMUM_DISPLAY_TIME_FOR_USER_INITIATED + TEST_AUTO_DISMISS_TIME) / 2).toLong()
+ )
+
+ assertThat(underTest.isHeadsUpEntry(entry.key)).isFalse()
+ }
+
+ @Test
+ @EnableFlags(StatusBarNotifChips.FLAG_NAME)
+ fun testRemoveNotification_afterMinimumDisplayTime_forUserInitiatedHun() {
+ useAccessibilityTimeout(false)
+
+ val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
+ entry.row = testHelper.createRow()
+ underTest.showNotification(entry, isPinnedByUser = true)
+
+ systemClock.advanceTime(
+ ((TEST_MINIMUM_DISPLAY_TIME_FOR_USER_INITIATED + TEST_AUTO_DISMISS_TIME) / 2).toLong()
+ )
+
+ assertThat(underTest.isHeadsUpEntry(entry.key)).isTrue()
+
+ val removedImmediately =
+ underTest.removeNotification(
+ entry.key,
+ /* releaseImmediately = */ false,
+ "afterMinimumDisplayTime",
+ )
+
+ assertThat(removedImmediately).isTrue()
+ assertThat(underTest.isHeadsUpEntry(entry.key)).isFalse()
+ }
+
+ @Test
fun testRemoveNotification_releaseImmediately() {
val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
@@ -1047,16 +1106,21 @@ class HeadsUpManagerImplTest(flags: FlagsParameterization) : SysuiTestCase() {
}
companion object {
- const val TEST_TOUCH_ACCEPTANCE_TIME = 200
- const val TEST_A11Y_AUTO_DISMISS_TIME = 1000
- const val TEST_EXTENSION_TIME = 500
+ private const val TEST_TOUCH_ACCEPTANCE_TIME = 200
+ private const val TEST_A11Y_AUTO_DISMISS_TIME = 1000
+ private const val TEST_EXTENSION_TIME = 500
- const val TEST_MINIMUM_DISPLAY_TIME = 400
- const val TEST_AUTO_DISMISS_TIME = 600
- const val TEST_STICKY_AUTO_DISMISS_TIME = 800
+ private const val TEST_MINIMUM_DISPLAY_TIME_DEFAULT = 400
+ private const val TEST_MINIMUM_DISPLAY_TIME_FOR_USER_INITIATED = 500
+ private const val TEST_AUTO_DISMISS_TIME = 600
+ private const val TEST_STICKY_AUTO_DISMISS_TIME = 800
init {
- assertThat(TEST_MINIMUM_DISPLAY_TIME).isLessThan(TEST_AUTO_DISMISS_TIME)
+ assertThat(TEST_MINIMUM_DISPLAY_TIME_DEFAULT)
+ .isLessThan(TEST_MINIMUM_DISPLAY_TIME_FOR_USER_INITIATED)
+ assertThat(TEST_MINIMUM_DISPLAY_TIME_DEFAULT).isLessThan(TEST_AUTO_DISMISS_TIME)
+ assertThat(TEST_MINIMUM_DISPLAY_TIME_FOR_USER_INITIATED)
+ .isLessThan(TEST_AUTO_DISMISS_TIME)
assertThat(TEST_AUTO_DISMISS_TIME).isLessThan(TEST_STICKY_AUTO_DISMISS_TIME)
assertThat(TEST_STICKY_AUTO_DISMISS_TIME).isLessThan(TEST_A11Y_AUTO_DISMISS_TIME)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/people/PeopleNotificationIdentifierTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/people/PeopleNotificationIdentifierTest.kt
new file mode 100644
index 000000000000..75f5de0118d4
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/people/PeopleNotificationIdentifierTest.kt
@@ -0,0 +1,162 @@
+/*
+ * 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.people
+
+import android.app.Notification
+import android.app.NotificationChannel
+import android.content.pm.ShortcutInfo
+import android.service.notification.NotificationListenerService.Ranking
+import android.service.notification.StatusBarNotification
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.RankingBuilder
+import com.android.systemui.statusbar.notification.collection.GroupEntry
+import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
+import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManagerImpl
+import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_FULL_PERSON
+import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_IMPORTANT_PERSON
+import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_NON_PERSON
+import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_PERSON
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito
+import org.mockito.Mockito.mock
+
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class PeopleNotificationIdentifierTest : SysuiTestCase() {
+
+ private lateinit var underTest: PeopleNotificationIdentifierImpl
+
+ private val summary1 = notificationEntry("foo", 1, summary = true)
+ private val summary2 = notificationEntry("bar", 1, summary = true)
+ private val entries =
+ listOf<GroupEntry>(
+ GroupEntryBuilder()
+ .setSummary(summary1)
+ .setChildren(
+ listOf(
+ notificationEntry("foo", 2),
+ notificationEntry("foo", 3),
+ notificationEntry("foo", 4)
+ )
+ )
+ .build(),
+ GroupEntryBuilder()
+ .setSummary(summary2)
+ .setChildren(
+ listOf(
+ notificationEntry("bar", 2),
+ notificationEntry("bar", 3),
+ notificationEntry("bar", 4)
+ )
+ )
+ .build()
+ )
+
+ private fun notificationEntry(
+ pkg: String,
+ id: Int,
+ summary: Boolean = false
+ ): NotificationEntry {
+ val sbn = mock(StatusBarNotification::class.java)
+ Mockito.`when`(sbn.key).thenReturn("key")
+ Mockito.`when`(sbn.notification).thenReturn(mock(Notification::class.java))
+ if (summary)
+ Mockito.`when`(sbn.notification.isGroupSummary).thenReturn(true)
+ return NotificationEntryBuilder().setPkg(pkg)
+ .setId(id)
+ .setSbn(sbn)
+ .build().apply {
+ row = mock(ExpandableNotificationRow::class.java)
+ }
+ }
+
+ private fun personRanking(entry: NotificationEntry, personType: Int): Ranking {
+ val channel = NotificationChannel("person", "person", 4)
+ channel.setConversationId("parent", "person")
+ channel.setImportantConversation(true)
+
+ val br = RankingBuilder(entry.ranking)
+
+ when (personType) {
+ TYPE_NON_PERSON -> br.setIsConversation(false)
+ TYPE_PERSON -> {
+ br.setIsConversation(true)
+ br.setShortcutInfo(null)
+ }
+
+ TYPE_IMPORTANT_PERSON -> {
+ br.setIsConversation(true)
+ br.setShortcutInfo(mock(ShortcutInfo::class.java))
+ br.setChannel(channel)
+ }
+
+ else -> {
+ br.setIsConversation(true)
+ br.setShortcutInfo(mock(ShortcutInfo::class.java))
+ }
+ }
+
+ return br.build()
+ }
+
+ @Before
+ fun setUp() {
+ val personExtractor = object : NotificationPersonExtractor {
+ public override fun isPersonNotification(sbn: StatusBarNotification): Boolean {
+ return true
+ }
+ }
+
+ underTest = PeopleNotificationIdentifierImpl(
+ personExtractor,
+ GroupMembershipManagerImpl()
+ )
+ }
+
+ private val Ranking.personTypeInfo
+ get() = when {
+ !isConversation -> TYPE_NON_PERSON
+ conversationShortcutInfo == null -> TYPE_PERSON
+ channel?.isImportantConversation == true -> TYPE_IMPORTANT_PERSON
+ else -> TYPE_FULL_PERSON
+ }
+
+ @Test
+ fun getPeopleNotificationType_entryIsImportant() {
+ summary1.setRanking(personRanking(summary1, TYPE_IMPORTANT_PERSON))
+
+ assertThat(underTest.getPeopleNotificationType(summary1)).isEqualTo(TYPE_IMPORTANT_PERSON)
+ }
+
+ @Test
+ fun getPeopleNotificationType_importantChild() {
+ entries.get(0).getChildren().get(0).setRanking(
+ personRanking(entries.get(0).getChildren().get(0), TYPE_IMPORTANT_PERSON)
+ )
+
+ assertThat(entries.get(0).summary?.let { underTest.getPeopleNotificationType(it) })
+ .isEqualTo(TYPE_IMPORTANT_PERSON)
+ }
+} \ No newline at end of file
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/domain/interactor/PromotedNotificationsInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/domain/interactor/PromotedNotificationsInteractorTest.kt
new file mode 100644
index 000000000000..aa6e76d08c17
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/domain/interactor/PromotedNotificationsInteractorTest.kt
@@ -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.promoted.domain.interactor
+
+import android.platform.test.annotations.EnableFlags
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.kosmos.collectLastValue
+import com.android.systemui.kosmos.runTest
+import com.android.systemui.kosmos.useUnconfinedTestDispatcher
+import com.android.systemui.statusbar.chips.notification.domain.interactor.statusBarNotificationChipsInteractor
+import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips
+import com.android.systemui.statusbar.core.StatusBarRootModernization
+import com.android.systemui.statusbar.notification.buildNotificationEntry
+import com.android.systemui.statusbar.notification.buildOngoingCallEntry
+import com.android.systemui.statusbar.notification.buildPromotedOngoingEntry
+import com.android.systemui.statusbar.notification.domain.interactor.renderNotificationListInteractor
+import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUi
+import com.android.systemui.statusbar.phone.ongoingcall.StatusBarChipsModernization
+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)
+@EnableFlags(
+ PromotedNotificationUi.FLAG_NAME,
+ StatusBarNotifChips.FLAG_NAME,
+ StatusBarChipsModernization.FLAG_NAME,
+ StatusBarRootModernization.FLAG_NAME,
+)
+class PromotedNotificationsInteractorTest : SysuiTestCase() {
+ private val kosmos = testKosmos().useUnconfinedTestDispatcher()
+
+ private val Kosmos.underTest by Fixture { promotedNotificationsInteractor }
+
+ @Before
+ fun setUp() {
+ kosmos.statusBarNotificationChipsInteractor.start()
+ }
+
+ @Test
+ fun orderedChipNotificationKeys_containsNonPromotedCalls() =
+ kosmos.runTest {
+ // GIVEN a call and a promoted ongoing notification
+ val callEntry = buildOngoingCallEntry(promoted = false)
+ val ronEntry = buildPromotedOngoingEntry()
+ val otherEntry = buildNotificationEntry(tag = "other")
+
+ renderNotificationListInteractor.setRenderedList(
+ listOf(callEntry, ronEntry, otherEntry)
+ )
+
+ val orderedChipNotificationKeys by
+ collectLastValue(underTest.orderedChipNotificationKeys)
+
+ // THEN the order of the notification keys should be the call then the RON
+ assertThat(orderedChipNotificationKeys)
+ .containsExactly("0|test_pkg|0|call|0", "0|test_pkg|0|ron|0")
+ }
+
+ @Test
+ fun orderedChipNotificationKeys_containsPromotedCalls() =
+ kosmos.runTest {
+ // GIVEN a call and a promoted ongoing notification
+ val callEntry = buildOngoingCallEntry(promoted = true)
+ val ronEntry = buildPromotedOngoingEntry()
+ val otherEntry = buildNotificationEntry(tag = "other")
+
+ renderNotificationListInteractor.setRenderedList(
+ listOf(callEntry, ronEntry, otherEntry)
+ )
+
+ val orderedChipNotificationKeys by
+ collectLastValue(underTest.orderedChipNotificationKeys)
+
+ // THEN the order of the notification keys should be the call then the RON
+ assertThat(orderedChipNotificationKeys)
+ .containsExactly("0|test_pkg|0|call|0", "0|test_pkg|0|ron|0")
+ }
+
+ @Test
+ fun topPromotedNotificationContent_skipsNonPromotedCalls() =
+ kosmos.runTest {
+ // GIVEN a non-promoted call and a promoted ongoing notification
+ val callEntry = buildOngoingCallEntry(promoted = false)
+ val ronEntry = buildPromotedOngoingEntry()
+ val otherEntry = buildNotificationEntry(tag = "other")
+
+ renderNotificationListInteractor.setRenderedList(
+ listOf(callEntry, ronEntry, otherEntry)
+ )
+
+ val topPromotedNotificationContent by
+ collectLastValue(underTest.topPromotedNotificationContent)
+
+ // THEN the ron is first because the call has no content
+ assertThat(topPromotedNotificationContent?.identity?.key)
+ .isEqualTo("0|test_pkg|0|ron|0")
+ }
+
+ @Test
+ fun topPromotedNotificationContent_includesPromotedCalls() =
+ kosmos.runTest {
+ // GIVEN a promoted call and a promoted ongoing notification
+ val callEntry = buildOngoingCallEntry(promoted = true)
+ val ronEntry = buildPromotedOngoingEntry()
+ val otherEntry = buildNotificationEntry(tag = "other")
+
+ renderNotificationListInteractor.setRenderedList(
+ listOf(callEntry, ronEntry, otherEntry)
+ )
+
+ val topPromotedNotificationContent by
+ collectLastValue(underTest.topPromotedNotificationContent)
+
+ // THEN the call is the top notification
+ assertThat(topPromotedNotificationContent?.identity?.key)
+ .isEqualTo("0|test_pkg|0|call|0")
+ }
+
+ @Test
+ fun topPromotedNotificationContent_nullWithNoPromotedNotifications() =
+ kosmos.runTest {
+ // GIVEN a a non-promoted call and no promoted ongoing entry
+ val callEntry = buildOngoingCallEntry(promoted = false)
+ val otherEntry = buildNotificationEntry(tag = "other")
+
+ renderNotificationListInteractor.setRenderedList(listOf(callEntry, otherEntry))
+
+ val topPromotedNotificationContent by
+ collectLastValue(underTest.topPromotedNotificationContent)
+
+ // THEN there is no top promoted notification
+ assertThat(topPromotedNotificationContent).isNull()
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java
index 7d406b4b397c..9f35d631bd45 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java
@@ -67,6 +67,7 @@ import com.android.systemui.media.controls.util.MediaFeatureFlag;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips;
import com.android.systemui.statusbar.notification.ConversationNotificationProcessor;
+import com.android.systemui.statusbar.notification.collection.EntryAdapter;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.promoted.FakePromotedNotificationContentExtractor;
import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUi;
@@ -248,14 +249,13 @@ public class NotificationContentInflaterTest extends SysuiTestCase {
true /* isNewView */, (v, p, r) -> true,
new InflationCallback() {
@Override
- public void handleInflationException(NotificationEntry entry,
- Exception e) {
+ public void handleInflationException(Exception e) {
countDownLatch.countDown();
throw new RuntimeException("No Exception expected");
}
@Override
- public void onAsyncInflationFinished(NotificationEntry entry) {
+ public void onAsyncInflationFinished() {
countDownLatch.countDown();
}
}, mRow.getPrivateLayout(), null, null, new HashMap<>(),
@@ -539,8 +539,7 @@ public class NotificationContentInflaterTest extends SysuiTestCase {
inflater.setInflateSynchronously(true);
InflationCallback callback = new InflationCallback() {
@Override
- public void handleInflationException(NotificationEntry entry,
- Exception e) {
+ public void handleInflationException(Exception e) {
if (!expectingException) {
exceptionHolder.setException(e);
}
@@ -548,7 +547,7 @@ public class NotificationContentInflaterTest extends SysuiTestCase {
}
@Override
- public void onAsyncInflationFinished(NotificationEntry entry) {
+ public void onAsyncInflationFinished() {
if (expectingException) {
exceptionHolder.setException(new RuntimeException(
"Inflation finished even though there should be an error"));
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 d43cc78e20dc..4c1f4f17e00c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.kt
@@ -71,6 +71,10 @@ import com.android.systemui.statusbar.notification.collection.provider.HighPrior
import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor
import com.android.systemui.statusbar.notification.headsup.HeadsUpManager
import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier
+import com.android.systemui.statusbar.notification.row.icon.AppIconProvider
+import com.android.systemui.statusbar.notification.row.icon.NotificationIconStyleProvider
+import com.android.systemui.statusbar.notification.row.icon.appIconProvider
+import com.android.systemui.statusbar.notification.row.icon.notificationIconStyleProvider
import com.android.systemui.statusbar.notification.stack.NotificationListContainer
import com.android.systemui.statusbar.policy.DeviceProvisionedController
import com.android.systemui.testKosmos
@@ -203,6 +207,8 @@ class NotificationGutsManagerTest(flags: FlagsParameterization) : SysuiTestCase(
accessibilityManager,
highPriorityProvider,
iNotificationManager,
+ kosmos.appIconProvider,
+ kosmos.notificationIconStyleProvider,
userManager,
peopleSpaceWidgetManager,
launcherApps,
@@ -512,6 +518,8 @@ class NotificationGutsManagerTest(flags: FlagsParameterization) : SysuiTestCase(
.bindNotification(
any<PackageManager>(),
any<INotificationManager>(),
+ any<AppIconProvider>(),
+ any<NotificationIconStyleProvider>(),
eq(onUserInteractionCallback),
eq(channelEditorDialogController),
eq(statusBarNotification.packageName),
@@ -550,6 +558,8 @@ class NotificationGutsManagerTest(flags: FlagsParameterization) : SysuiTestCase(
.bindNotification(
any<PackageManager>(),
any<INotificationManager>(),
+ any<AppIconProvider>(),
+ any<NotificationIconStyleProvider>(),
eq(onUserInteractionCallback),
eq(channelEditorDialogController),
eq(statusBarNotification.packageName),
@@ -586,6 +596,8 @@ class NotificationGutsManagerTest(flags: FlagsParameterization) : SysuiTestCase(
.bindNotification(
any<PackageManager>(),
any<INotificationManager>(),
+ any<AppIconProvider>(),
+ any<NotificationIconStyleProvider>(),
eq(onUserInteractionCallback),
eq(channelEditorDialogController),
eq(statusBarNotification.packageName),
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.kt
index 2945fa98caad..96ae07035ed2 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.kt
@@ -64,6 +64,10 @@ import com.android.systemui.statusbar.RankingBuilder
import com.android.systemui.statusbar.notification.AssistantFeedbackController
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
+import com.android.systemui.statusbar.notification.row.icon.AppIconProvider
+import com.android.systemui.statusbar.notification.row.icon.NotificationIconStyleProvider
+import com.android.systemui.statusbar.notification.row.icon.appIconProvider
+import com.android.systemui.statusbar.notification.row.icon.notificationIconStyleProvider
import com.android.telecom.telecomManager
import com.google.common.truth.Truth.assertThat
import java.util.concurrent.CountDownLatch
@@ -862,6 +866,8 @@ class NotificationInfoTest : SysuiTestCase() {
private fun bindNotification(
pm: PackageManager = this.mockPackageManager,
iNotificationManager: INotificationManager = this.mockINotificationManager,
+ appIconProvider: AppIconProvider = kosmos.appIconProvider,
+ iconStyleProvider: NotificationIconStyleProvider = kosmos.notificationIconStyleProvider,
onUserInteractionCallback: OnUserInteractionCallback = this.onUserInteractionCallback,
channelEditorDialogController: ChannelEditorDialogController =
this.channelEditorDialogController,
@@ -882,6 +888,8 @@ class NotificationInfoTest : SysuiTestCase() {
underTest.bindNotification(
pm,
iNotificationManager,
+ appIconProvider,
+ iconStyleProvider,
onUserInteractionCallback,
channelEditorDialogController,
pkg,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImplTest.kt
index 82eca3735a71..ce3aee1d88d2 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImplTest.kt
@@ -41,6 +41,7 @@ import com.android.systemui.statusbar.NotificationLockscreenUserManager.REDACTIO
import com.android.systemui.statusbar.NotificationLockscreenUserManager.RedactionType
import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips
import com.android.systemui.statusbar.notification.ConversationNotificationProcessor
+import com.android.systemui.statusbar.notification.collection.EntryAdapter
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.promoted.FakePromotedNotificationContentExtractor
import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUi
@@ -223,12 +224,12 @@ class NotificationRowContentBinderImplTest : SysuiTestCase() {
remoteViewClickHandler = { _, _, _ -> true },
callback =
object : InflationCallback {
- override fun handleInflationException(entry: NotificationEntry, e: Exception) {
+ override fun handleInflationException(e: Exception) {
countDownLatch.countDown()
throw RuntimeException("No Exception expected")
}
- override fun onAsyncInflationFinished(entry: NotificationEntry) {
+ override fun onAsyncInflationFinished() {
countDownLatch.countDown()
}
},
@@ -675,14 +676,14 @@ class NotificationRowContentBinderImplTest : SysuiTestCase() {
inflater.setInflateSynchronously(true)
val callback: InflationCallback =
object : InflationCallback {
- override fun handleInflationException(entry: NotificationEntry, e: Exception) {
+ override fun handleInflationException(e: Exception) {
if (!expectingException) {
exceptionHolder.exception = e
}
countDownLatch.countDown()
}
- override fun onAsyncInflationFinished(entry: NotificationEntry) {
+ override fun onAsyncInflationFinished() {
if (expectingException) {
exceptionHolder.exception =
RuntimeException(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
index c39b252cd795..f2131da8f0bb 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
@@ -615,7 +615,7 @@ public class NotificationTestHelper {
LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(
Context.LAYOUT_INFLATER_SERVICE);
inflater.setFactory2(new RowInflaterTask.RowAsyncLayoutInflater(entry, mSystemClock,
- mRowInflaterTaskLogger));
+ mRowInflaterTaskLogger, UserHandle.of(entry.getSbn().getNormalizedUserId())));
mRow = (ExpandableNotificationRow) inflater.inflate(
R.layout.status_bar_notification_row,
null /* root */,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/PromotedNotificationInfoTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/PromotedNotificationInfoTest.java
index acdbd6237733..5638e0b434aa 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/PromotedNotificationInfoTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/PromotedNotificationInfoTest.java
@@ -48,6 +48,8 @@ import com.android.systemui.res.R;
import com.android.systemui.statusbar.notification.AssistantFeedbackController;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
+import com.android.systemui.statusbar.notification.row.icon.AppIconProvider;
+import com.android.systemui.statusbar.notification.row.icon.NotificationIconStyleProvider;
import org.junit.Before;
import org.junit.Rule;
@@ -57,8 +59,6 @@ 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
@@ -82,6 +82,10 @@ public class PromotedNotificationInfoTest extends SysuiTestCase {
@Mock
private INotificationManager mMockINotificationManager;
@Mock
+ private AppIconProvider mMockAppIconProvider;
+ @Mock
+ private NotificationIconStyleProvider mMockIconStyleProvider;
+ @Mock
private PackageManager mMockPackageManager;
@Mock
private OnUserInteractionCallback mOnUserInteractionCallback;
@@ -127,10 +131,11 @@ public class PromotedNotificationInfoTest extends SysuiTestCase {
public void testBindNotification_setsOnClickListenerForFeedback() throws Exception {
// Bind the notification to the Info object
- final CountDownLatch latch = new CountDownLatch(1);
mInfo.bindNotification(
mMockPackageManager,
mMockINotificationManager,
+ mMockAppIconProvider,
+ mMockIconStyleProvider,
mOnUserInteractionCallback,
mChannelEditorDialogController,
TEST_PACKAGE_NAME,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModelTest.kt
index d570f18e35d8..6381b4e0fef7 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModelTest.kt
@@ -57,11 +57,12 @@ class NotificationShelfViewModelTest : SysuiTestCase() {
statusBarStateController = mock()
whenever(screenOffAnimationController.allowWakeUpIfDozing()).thenReturn(true)
}
- private val underTest = kosmos.notificationShelfViewModel
private val deviceEntryFaceAuthRepository = kosmos.fakeDeviceEntryFaceAuthRepository
private val keyguardRepository = kosmos.fakeKeyguardRepository
- private val keyguardTransitionController = kosmos.lockscreenShadeTransitionController
private val powerRepository = kosmos.fakePowerRepository
+ private val keyguardTransitionController by lazy { kosmos.lockscreenShadeTransitionController }
+
+ private val underTest by lazy { kosmos.notificationShelfViewModel }
@Test
fun canModifyColorOfNotifications_whenKeyguardNotShowing() =
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt
index 256da253588c..9c5d65ec12ec 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt
@@ -1,5 +1,6 @@
package com.android.systemui.statusbar.notification.stack
+import android.os.UserHandle
import android.platform.test.annotations.EnableFlags
import android.service.notification.StatusBarNotification
import android.testing.TestableLooper.RunWithLooper
@@ -21,6 +22,7 @@ import com.android.systemui.statusbar.StatusBarIconView
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
import com.android.systemui.statusbar.notification.row.ExpandableView
+import com.android.systemui.statusbar.notification.shared.NotificationBundleUi
import com.android.systemui.statusbar.notification.shared.NotificationMinimalism
import com.android.systemui.statusbar.notification.shelf.NotificationShelfIconContainer
import com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm.StackScrollAlgorithmState
@@ -978,7 +980,10 @@ open class NotificationShelfTest : SysuiTestCase() {
) {
val sbnMock: StatusBarNotification = mock()
val mockEntry = mock<NotificationEntry>().apply { whenever(this.sbn).thenReturn(sbnMock) }
- val row = ExpandableNotificationRow(mContext, null, mockEntry)
+ val row = when (NotificationBundleUi.isEnabled) {
+ true -> ExpandableNotificationRow(mContext, null, UserHandle.CURRENT)
+ false -> ExpandableNotificationRow(mContext, null, mockEntry)
+ }
whenever(ambientState.lastVisibleBackgroundChild).thenReturn(row)
whenever(ambientState.isExpansionChanging).thenReturn(true)
whenever(ambientState.expansionFraction).thenReturn(expansionFraction)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackTest.java
index 8ec17dadcfe7..345ddae42798 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackTest.java
@@ -46,9 +46,11 @@ import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry.NotifEntryAdapter;
import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.NotificationContentView;
+import com.android.systemui.statusbar.notification.shared.NotificationBundleUi;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.util.concurrency.FakeExecutor;
@@ -133,9 +135,11 @@ public class StatusBarRemoteInputCallbackTest extends SysuiTestCase {
final ExpandableNotificationRow enr = mock(ExpandableNotificationRow.class);
final NotificationContentView privateLayout = mock(NotificationContentView.class);
final NotificationEntry enrEntry = mock(NotificationEntry.class);
+ final NotifEntryAdapter enrEntryAdapter = mock(NotifEntryAdapter.class);
when(enr.getPrivateLayout()).thenReturn(privateLayout);
when(enr.getEntry()).thenReturn(enrEntry);
+ when(enr.getEntryAdapter()).thenReturn(enrEntryAdapter);
when(enr.isChildInGroup()).thenReturn(true);
when(enr.areChildrenExpanded()).thenReturn(false);
@@ -144,7 +148,11 @@ public class StatusBarRemoteInputCallbackTest extends SysuiTestCase {
enr, mock(View.class), false, onExpandedVisibleRunner);
// THEN
- verify(mGroupExpansionManager).toggleGroupExpansion(enrEntry);
+ if (NotificationBundleUi.isEnabled()) {
+ verify(mGroupExpansionManager).toggleGroupExpansion(enrEntryAdapter);
+ } else {
+ verify(mGroupExpansionManager).toggleGroupExpansion(enrEntry);
+ }
verify(enr).setUserExpanded(true);
verify(privateLayout).setOnExpandedVisibleListener(onExpandedVisibleRunner);
}
@@ -169,7 +177,8 @@ public class StatusBarRemoteInputCallbackTest extends SysuiTestCase {
enr, mock(View.class), false, onExpandedVisibleRunner);
// THEN
- verify(mGroupExpansionManager, never()).toggleGroupExpansion(any());
+ verify(mGroupExpansionManager, never()).toggleGroupExpansion(any(NotificationEntry.class));
+ verify(mGroupExpansionManager, never()).toggleGroupExpansion(any(NotifEntryAdapter.class));
verify(enr).setUserExpanded(true);
verify(privateLayout).setOnExpandedVisibleListener(onExpandedVisibleRunner);
}
@@ -193,7 +202,8 @@ public class StatusBarRemoteInputCallbackTest extends SysuiTestCase {
enr, mock(View.class), false, onExpandedVisibleRunner);
// THEN
- verify(mGroupExpansionManager, never()).toggleGroupExpansion(any());
+ verify(mGroupExpansionManager, never()).toggleGroupExpansion(any(NotificationEntry.class));
+ verify(mGroupExpansionManager, never()).toggleGroupExpansion(any(NotifEntryAdapter.class));
verify(enr).setUserExpanded(true);
verify(privateLayout).setOnExpandedVisibleListener(onExpandedVisibleRunner);
}
@@ -207,9 +217,11 @@ public class StatusBarRemoteInputCallbackTest extends SysuiTestCase {
final ExpandableNotificationRow enr = mock(ExpandableNotificationRow.class);
final NotificationContentView privateLayout = mock(NotificationContentView.class);
final NotificationEntry enrEntry = mock(NotificationEntry.class);
+ final NotifEntryAdapter enrEntryAdapter = mock(NotifEntryAdapter.class);
when(enr.getPrivateLayout()).thenReturn(privateLayout);
when(enr.getEntry()).thenReturn(enrEntry);
+ when(enr.getEntryAdapter()).thenReturn(enrEntryAdapter);
when(enr.isChildInGroup()).thenReturn(true);
when(enr.areChildrenExpanded()).thenReturn(false);
@@ -218,7 +230,11 @@ public class StatusBarRemoteInputCallbackTest extends SysuiTestCase {
enr, mock(View.class), false, onExpandedVisibleRunner);
// THEN
- verify(mGroupExpansionManager).toggleGroupExpansion(enrEntry);
+ if (NotificationBundleUi.isEnabled()) {
+ verify(mGroupExpansionManager).toggleGroupExpansion(enrEntryAdapter);
+ } else {
+ verify(mGroupExpansionManager).toggleGroupExpansion(enrEntry);
+ }
verify(enr, never()).setUserExpanded(anyBoolean());
verify(privateLayout, never()).setOnExpandedVisibleListener(any());
}
@@ -244,6 +260,7 @@ public class StatusBarRemoteInputCallbackTest extends SysuiTestCase {
// THEN
verify(mGroupExpansionManager, never()).toggleGroupExpansion(enrEntry);
+ verify(mGroupExpansionManager, never()).toggleGroupExpansion(any(NotifEntryAdapter.class));
verify(enr, never()).setUserExpanded(anyBoolean());
verify(privateLayout, never()).setOnExpandedVisibleListener(any());
}
@@ -272,7 +289,8 @@ public class StatusBarRemoteInputCallbackTest extends SysuiTestCase {
verify(enr).toggleExpansionState();
verify(privateLayout).setOnExpandedVisibleListener(onExpandedVisibleRunner);
verify(enr, never()).setUserExpanded(anyBoolean());
- verify(mGroupExpansionManager, never()).toggleGroupExpansion(any());
+ verify(mGroupExpansionManager, never()).toggleGroupExpansion(any(NotificationEntry.class));
+ verify(mGroupExpansionManager, never()).toggleGroupExpansion(any(NotifEntryAdapter.class));
}
@Test
@@ -299,7 +317,8 @@ public class StatusBarRemoteInputCallbackTest extends SysuiTestCase {
verify(enr, never()).toggleExpansionState();
verify(privateLayout, never()).setOnExpandedVisibleListener(onExpandedVisibleRunner);
verify(enr, never()).setUserExpanded(anyBoolean());
- verify(mGroupExpansionManager, never()).toggleGroupExpansion(any());
+ verify(mGroupExpansionManager, never()).toggleGroupExpansion(any(NotificationEntry.class));
+ verify(mGroupExpansionManager, never()).toggleGroupExpansion(any(NotifEntryAdapter.class));
}
@Test
@@ -326,7 +345,8 @@ public class StatusBarRemoteInputCallbackTest extends SysuiTestCase {
verify(enr).toggleExpansionState();
verify(privateLayout).setOnExpandedVisibleListener(onExpandedVisibleRunner);
verify(enr, never()).setUserExpanded(anyBoolean());
- verify(mGroupExpansionManager, never()).toggleGroupExpansion(any());
+ verify(mGroupExpansionManager, never()).toggleGroupExpansion(any(NotificationEntry.class));
+ verify(mGroupExpansionManager, never()).toggleGroupExpansion(any(NotifEntryAdapter.class));
}
@Test
@@ -353,6 +373,7 @@ public class StatusBarRemoteInputCallbackTest extends SysuiTestCase {
verify(enr, never()).toggleExpansionState();
verify(privateLayout, never()).setOnExpandedVisibleListener(onExpandedVisibleRunner);
verify(enr, never()).setUserExpanded(anyBoolean());
- verify(mGroupExpansionManager, never()).toggleGroupExpansion(any());
+ verify(mGroupExpansionManager, never()).toggleGroupExpansion(any(NotificationEntry.class));
+ verify(mGroupExpansionManager, never()).toggleGroupExpansion(any(NotifEntryAdapter.class));
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt
index 4009144757f7..8c70da718c08 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt
@@ -719,7 +719,6 @@ class MobileIconInteractorTest : SysuiTestCase() {
job.cancel()
}
- @EnableFlags(com.android.internal.telephony.flags.Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG)
@Test
fun satBasedIcon_isUsedWhenNonTerrestrial() =
testScope.runTest {
@@ -733,7 +732,6 @@ class MobileIconInteractorTest : SysuiTestCase() {
assertThat(latest).isInstanceOf(SignalIconModel.Satellite::class.java)
}
- @EnableFlags(com.android.internal.telephony.flags.Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG)
@DisableFlags(com.android.internal.telephony.flags.Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
@Test
// See b/346904529 for more context
@@ -756,10 +754,7 @@ class MobileIconInteractorTest : SysuiTestCase() {
assertThat(latest!!.level).isEqualTo(4)
}
- @EnableFlags(
- com.android.internal.telephony.flags.Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG,
- com.android.internal.telephony.flags.Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN,
- )
+ @EnableFlags(com.android.internal.telephony.flags.Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
@Test
// See b/346904529 for more context
fun satBasedIcon_doesNotInflateSignalStrength_flagOn() =
@@ -781,7 +776,6 @@ class MobileIconInteractorTest : SysuiTestCase() {
assertThat(latest!!.level).isEqualTo(4)
}
- @EnableFlags(com.android.internal.telephony.flags.Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG)
@DisableFlags(com.android.internal.telephony.flags.Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
@Test
fun satBasedIcon_usesPrimaryLevel_flagOff() =
@@ -799,10 +793,7 @@ class MobileIconInteractorTest : SysuiTestCase() {
assertThat(latest!!.level).isEqualTo(4)
}
- @EnableFlags(
- com.android.internal.telephony.flags.Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG,
- com.android.internal.telephony.flags.Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN,
- )
+ @EnableFlags(com.android.internal.telephony.flags.Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
@Test
fun satBasedIcon_usesSatelliteLevel_flagOn() =
testScope.runTest {
@@ -823,7 +814,6 @@ class MobileIconInteractorTest : SysuiTestCase() {
* Context (b/377518113), this test will not be needed after FLAG_CARRIER_ROAMING_NB_IOT_NTN is
* rolled out. The new API should report 0 automatically if not in service.
*/
- @EnableFlags(com.android.internal.telephony.flags.Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG)
@DisableFlags(com.android.internal.telephony.flags.Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
@Test
fun satBasedIcon_reportsLevelZeroWhenOutOfService() =
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt
index 61ed04c6b59d..8ea888e47ffc 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt
@@ -856,7 +856,6 @@ class MobileIconViewModelTest : SysuiTestCase() {
.isEqualTo(Icon.Resource(R.drawable.mobile_network_type_background, null))
}
- @EnableFlags(com.android.internal.telephony.flags.Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG)
@Test
fun nonTerrestrial_defaultProperties() =
testScope.runTest {
@@ -877,7 +876,6 @@ class MobileIconViewModelTest : SysuiTestCase() {
assertThat(activityContainerVisible).isFalse()
}
- @EnableFlags(com.android.internal.telephony.flags.Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG)
@Test
fun nonTerrestrial_ignoresDefaultProperties() =
testScope.runTest {
@@ -905,7 +903,6 @@ class MobileIconViewModelTest : SysuiTestCase() {
assertThat(activityContainerVisible).isFalse()
}
- @EnableFlags(com.android.internal.telephony.flags.Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG)
@DisableFlags(com.android.internal.telephony.flags.Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
@Test
fun nonTerrestrial_usesSatelliteIcon_flagOff() =
@@ -940,10 +937,7 @@ class MobileIconViewModelTest : SysuiTestCase() {
assertThat(latest!!.icon.res).isEqualTo(R.drawable.ic_satellite_connected_2)
}
- @EnableFlags(
- com.android.internal.telephony.flags.Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG,
- com.android.internal.telephony.flags.Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN,
- )
+ @EnableFlags(com.android.internal.telephony.flags.Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
@Test
fun nonTerrestrial_usesSatelliteIcon_flagOn() =
testScope.runTest {
@@ -972,7 +966,6 @@ class MobileIconViewModelTest : SysuiTestCase() {
assertThat(latest!!.icon.res).isEqualTo(R.drawable.ic_satellite_connected_2)
}
- @EnableFlags(com.android.internal.telephony.flags.Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG)
@DisableFlags(com.android.internal.telephony.flags.Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
@Test
fun satelliteIcon_ignoresInflateSignalStrength_flagOff() =
@@ -1010,10 +1003,7 @@ class MobileIconViewModelTest : SysuiTestCase() {
assertThat(latest!!.icon.res).isEqualTo(R.drawable.ic_satellite_connected_2)
}
- @EnableFlags(
- com.android.internal.telephony.flags.Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG,
- com.android.internal.telephony.flags.Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN,
- )
+ @EnableFlags(com.android.internal.telephony.flags.Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
@Test
fun satelliteIcon_ignoresInflateSignalStrength_flagOn() =
testScope.runTest {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeHomeStatusBarViewModel.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeHomeStatusBarViewModel.kt
index 183cd8f1ae8b..eb961bd5f4ae 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeHomeStatusBarViewModel.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeHomeStatusBarViewModel.kt
@@ -57,7 +57,7 @@ class FakeHomeStatusBarViewModel(
override val ongoingActivityChipsLegacy =
MutableStateFlow(MultipleOngoingActivityChipsModelLegacy())
- override val statusBarPopupChips = MutableStateFlow(emptyList<PopupChipModel.Shown>())
+ override val popupChips = emptyList<PopupChipModel.Shown>()
override val mediaProjectionStopDialogDueToCallEndedState =
MutableStateFlow(MediaProjectionStopDialogModel.Hidden)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorTest.kt
index ff1ffccfb2de..22e28d883c97 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorTest.kt
@@ -469,7 +469,7 @@ class ZenModeInteractorTest : SysuiTestCase() {
}
@Test
- @EnableFlags(ModesEmptyShadeFix.FLAG_NAME, Flags.FLAG_MODES_UI, Flags.FLAG_MODES_API)
+ @EnableFlags(ModesEmptyShadeFix.FLAG_NAME, Flags.FLAG_MODES_UI)
fun modesHidingNotifications_onlyIncludesModesWithNotifListSuppression() =
kosmos.runTest {
val modesHidingNotifications by collectLastValue(underTest.modesHidingNotifications)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/theme/HardwareColorRule.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/theme/HardwareColorRule.java
new file mode 100644
index 000000000000..ecd04a47b8ae
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/theme/HardwareColorRule.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.theme;
+
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+
+
+public class HardwareColorRule implements TestRule {
+ public String color = "";
+ public String[] options = {};
+ public boolean isTesting = false;
+
+ @Override
+ public Statement apply(Statement base, Description description) {
+ HardwareColors hardwareColors = description.getAnnotation(HardwareColors.class);
+ if (hardwareColors != null) {
+ color = hardwareColors.color();
+ options = hardwareColors.options();
+ isTesting = true;
+ }
+ return base;
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/theme/HardwareColors.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/theme/HardwareColors.java
new file mode 100644
index 000000000000..0b8df2e2670e
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/theme/HardwareColors.java
@@ -0,0 +1,30 @@
+/*
+ * 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.theme;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.METHOD)
+public @interface HardwareColors {
+ String color();
+ String[] options();
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
index 5cd0846ded7e..9a0b8125fb25 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
@@ -64,6 +64,7 @@ import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
+import com.android.systemui.flags.SystemPropertiesHelper;
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
import com.android.systemui.monet.DynamicColors;
@@ -77,6 +78,7 @@ import com.android.systemui.util.settings.SecureSettings;
import com.google.common.util.concurrent.MoreExecutors;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
@@ -98,6 +100,9 @@ public class ThemeOverlayControllerTest extends SysuiTestCase {
private static final UserHandle MANAGED_USER_HANDLE = UserHandle.of(100);
private static final UserHandle PRIVATE_USER_HANDLE = UserHandle.of(101);
+ @Rule
+ public HardwareColorRule rule = new HardwareColorRule();
+
@Mock
private JavaAdapter mJavaAdapter;
@Mock
@@ -148,13 +153,17 @@ public class ThemeOverlayControllerTest extends SysuiTestCase {
@Captor
private ArgumentCaptor<ContentObserver> mSettingsObserver;
+ @Mock
+ private SystemPropertiesHelper mSystemProperties;
+
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
+
when(mFeatureFlags.isEnabled(Flags.MONET)).thenReturn(true);
when(mWakefulnessLifecycle.getWakefulness()).thenReturn(WAKEFULNESS_AWAKE);
when(mUiModeManager.getContrast()).thenReturn(0.5f);
- when(mDeviceProvisionedController.isCurrentUserSetup()).thenReturn(true);
+
when(mResources.getColor(eq(android.R.color.system_accent1_500), any()))
.thenReturn(Color.RED);
when(mResources.getColor(eq(android.R.color.system_accent2_500), any()))
@@ -166,11 +175,20 @@ public class ThemeOverlayControllerTest extends SysuiTestCase {
when(mResources.getColor(eq(android.R.color.system_neutral2_500), any()))
.thenReturn(Color.BLACK);
+ when(mResources.getStringArray(com.android.internal.R.array.theming_defaults))
+ .thenReturn(rule.options);
+
+ // should fallback to `*|TONAL_SPOT|home_wallpaper`
+ when(mSystemProperties.get("ro.boot.hardware.color")).thenReturn(rule.color);
+ // will try set hardware colors as boot ONLY if user is not set yet
+ when(mDeviceProvisionedController.isCurrentUserSetup()).thenReturn(!rule.isTesting);
+
mThemeOverlayController = new ThemeOverlayController(mContext,
mBroadcastDispatcher, mBgHandler, mMainExecutor, mBgExecutor, mThemeOverlayApplier,
mSecureSettings, mWallpaperManager, mUserManager, mDeviceProvisionedController,
mUserTracker, mDumpManager, mFeatureFlags, mResources, mWakefulnessLifecycle,
- mJavaAdapter, mKeyguardTransitionInteractor, mUiModeManager, mActivityManager) {
+ mJavaAdapter, mKeyguardTransitionInteractor, mUiModeManager, mActivityManager,
+ mSystemProperties) {
@VisibleForTesting
protected boolean isNightMode() {
return false;
@@ -214,11 +232,58 @@ public class ThemeOverlayControllerTest extends SysuiTestCase {
public void start_checksWallpaper() {
ArgumentCaptor<Runnable> registrationRunnable = ArgumentCaptor.forClass(Runnable.class);
verify(mBgExecutor).execute(registrationRunnable.capture());
+ registrationRunnable.getValue().run();
+ verify(mWallpaperManager).getWallpaperColors(eq(WallpaperManager.FLAG_SYSTEM));
+ }
+
+ @Test
+ @HardwareColors(color = "BLK", options = {
+ "BLK|MONOCHROMATIC|#FF0000",
+ "*|VIBRANT|home_wallpaper"
+ })
+ @EnableFlags(com.android.systemui.Flags.FLAG_HARDWARE_COLOR_STYLES)
+ public void start_checkHardwareColor() {
+ // getWallpaperColors should not be called
+ ArgumentCaptor<Runnable> registrationRunnable = ArgumentCaptor.forClass(Runnable.class);
+ verify(mMainExecutor).execute(registrationRunnable.capture());
+ registrationRunnable.getValue().run();
+ verify(mWallpaperManager, never()).getWallpaperColors(anyInt());
+
+ assertThat(mThemeOverlayController.mThemeStyle).isEqualTo(Style.MONOCHROMATIC);
+ assertThat(mThemeOverlayController.mCurrentColors.get(0).getMainColors().get(
+ 0).toArgb()).isEqualTo(Color.RED);
+ }
+
+ @Test
+ @HardwareColors(color = "", options = {
+ "BLK|MONOCHROMATIC|#FF0000",
+ "*|VIBRANT|home_wallpaper"
+ })
+ @EnableFlags(com.android.systemui.Flags.FLAG_HARDWARE_COLOR_STYLES)
+ public void start_wildcardColor() {
+ // getWallpaperColors will be called because we srt wildcard to `home_wallpaper`
+ ArgumentCaptor<Runnable> registrationRunnable = ArgumentCaptor.forClass(Runnable.class);
+ verify(mMainExecutor).execute(registrationRunnable.capture());
+ registrationRunnable.getValue().run();
+ verify(mWallpaperManager).getWallpaperColors(eq(WallpaperManager.FLAG_SYSTEM));
+ assertThat(mThemeOverlayController.mThemeStyle).isEqualTo(Style.VIBRANT);
+ }
+
+ @Test
+ @HardwareColors(color = "NONEXISTENT", options = {})
+ @EnableFlags(com.android.systemui.Flags.FLAG_HARDWARE_COLOR_STYLES)
+ public void start_fallbackColor() {
+ // getWallpaperColors will be called because we default color source is `home_wallpaper`
+ ArgumentCaptor<Runnable> registrationRunnable = ArgumentCaptor.forClass(Runnable.class);
+ verify(mMainExecutor).execute(registrationRunnable.capture());
registrationRunnable.getValue().run();
verify(mWallpaperManager).getWallpaperColors(eq(WallpaperManager.FLAG_SYSTEM));
+
+ assertThat(mThemeOverlayController.mThemeStyle).isEqualTo(Style.TONAL_SPOT);
}
+
@Test
public void onWallpaperColorsChanged_setsTheme_whenForeground() {
// Should ask for a new theme when wallpaper colors change
@@ -287,9 +352,9 @@ public class ThemeOverlayControllerTest extends SysuiTestCase {
WallpaperColors mainColors = new WallpaperColors(Color.valueOf(Color.RED),
Color.valueOf(Color.BLUE), null);
- String jsonString =
- "{\"android.theme.customization.system_palette\":\"override.package.name\","
- + "\"android.theme.customization.color_source\":\"preset\"}";
+ String jsonString = createJsonString(TestColorSource.preset, "override.package.name",
+ "TONAL_SPOT");
+
when(mSecureSettings.getStringForUser(
eq(Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES), anyInt()))
.thenReturn(jsonString);
@@ -313,11 +378,7 @@ public class ThemeOverlayControllerTest extends SysuiTestCase {
WallpaperColors mainColors = new WallpaperColors(Color.valueOf(Color.RED),
Color.valueOf(Color.BLUE), null);
- String jsonString =
- "{\"android.theme.customization.color_source\":\"home_wallpaper\","
- + "\"android.theme.customization.system_palette\":\"A16B00\","
- + "\"android.theme.customization.accent_color\":\"A16B00\","
- + "\"android.theme.customization.color_index\":\"2\"}";
+ String jsonString = createJsonString(TestColorSource.home_wallpaper);
when(mSecureSettings.getStringForUser(
eq(Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES), anyInt()))
@@ -348,11 +409,7 @@ public class ThemeOverlayControllerTest extends SysuiTestCase {
WallpaperColors mainColors = new WallpaperColors(Color.valueOf(Color.RED),
Color.valueOf(Color.BLUE), null);
- String jsonString =
- "{\"android.theme.customization.color_source\":\"home_wallpaper\","
- + "\"android.theme.customization.system_palette\":\"A16B00\","
- + "\"android.theme.customization.accent_color\":\"A16B00\","
- + "\"android.theme.customization.color_index\":\"2\"}";
+ String jsonString = createJsonString(TestColorSource.home_wallpaper);
when(mSecureSettings.getStringForUser(
eq(Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES), anyInt()))
@@ -381,11 +438,7 @@ public class ThemeOverlayControllerTest extends SysuiTestCase {
// Should ask for a new theme when wallpaper colors change
WallpaperColors mainColors = new WallpaperColors(Color.valueOf(Color.RED),
Color.valueOf(Color.BLUE), null);
- String jsonString =
- "{\"android.theme.customization.color_source\":\"lock_wallpaper\","
- + "\"android.theme.customization.system_palette\":\"A16B00\","
- + "\"android.theme.customization.accent_color\":\"A16B00\","
- + "\"android.theme.customization.color_index\":\"2\"}";
+ String jsonString = createJsonString(TestColorSource.lock_wallpaper);
when(mSecureSettings.getStringForUser(
eq(Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES), anyInt()))
.thenReturn(jsonString);
@@ -404,11 +457,7 @@ public class ThemeOverlayControllerTest extends SysuiTestCase {
// Should ask for a new theme when wallpaper colors change
WallpaperColors mainColors = new WallpaperColors(Color.valueOf(Color.RED),
Color.valueOf(Color.BLUE), null);
- String jsonString =
- "{\"android.theme.customization.color_source\":\"lock_wallpaper\","
- + "\"android.theme.customization.system_palette\":\"A16B00\","
- + "\"android.theme.customization.accent_color\":\"A16B00\","
- + "\"android.theme.customization.color_index\":\"2\"}";
+ String jsonString = createJsonString(TestColorSource.lock_wallpaper);
when(mSecureSettings.getStringForUser(
eq(Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES), anyInt()))
.thenReturn(jsonString);
@@ -455,8 +504,8 @@ public class ThemeOverlayControllerTest extends SysuiTestCase {
@Test
public void onSettingChanged_invalidStyle() {
when(mDeviceProvisionedController.isUserSetup(anyInt())).thenReturn(true);
- String jsonString = "{\"android.theme.customization.system_palette\":\"A16B00\","
- + "\"android.theme.customization.theme_style\":\"some_invalid_name\"}";
+ String jsonString = createJsonString(TestColorSource.home_wallpaper, "A16B00",
+ "some_invalid_name");
when(mSecureSettings.getStringForUser(
eq(Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES), anyInt()))
@@ -473,11 +522,7 @@ public class ThemeOverlayControllerTest extends SysuiTestCase {
WallpaperColors mainColors = new WallpaperColors(Color.valueOf(Color.RED),
Color.valueOf(Color.BLUE), null);
- String jsonString =
- "{\"android.theme.customization.color_source\":\"home_wallpaper\","
- + "\"android.theme.customization.system_palette\":\"A16B00\","
- + "\"android.theme.customization.accent_color\":\"A16B00\","
- + "\"android.theme.customization.color_index\":\"2\"}";
+ String jsonString = createJsonString(TestColorSource.home_wallpaper);
when(mSecureSettings.getStringForUser(
eq(Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES), anyInt()))
@@ -506,11 +551,7 @@ public class ThemeOverlayControllerTest extends SysuiTestCase {
// Should ask for a new theme when wallpaper colors change
WallpaperColors mainColors = new WallpaperColors(Color.valueOf(Color.RED),
Color.valueOf(Color.BLUE), null);
- String jsonString =
- "{\"android.theme.customization.color_source\":\"home_wallpaper\","
- + "\"android.theme.customization.system_palette\":\"A16B00\","
- + "\"android.theme.customization.accent_color\":\"A16B00\","
- + "\"android.theme.customization.color_index\":\"2\"}";
+ String jsonString = createJsonString(TestColorSource.home_wallpaper);
when(mSecureSettings.getStringForUser(
eq(Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES), anyInt()))
.thenReturn(jsonString);
@@ -537,11 +578,7 @@ public class ThemeOverlayControllerTest extends SysuiTestCase {
// Should ask for a new theme when wallpaper colors change
WallpaperColors mainColors = new WallpaperColors(Color.valueOf(Color.RED),
Color.valueOf(Color.BLUE), null);
- String jsonString =
- "{\"android.theme.customization.color_source\":\"home_wallpaper\","
- + "\"android.theme.customization.system_palette\":\"A16B00\","
- + "\"android.theme.customization.accent_color\":\"A16B00\","
- + "\"android.theme.customization.color_index\":\"2\"}";
+ String jsonString = createJsonString(TestColorSource.home_wallpaper);
when(mSecureSettings.getStringForUser(
eq(Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES), anyInt()))
.thenReturn(jsonString);
@@ -570,11 +607,7 @@ public class ThemeOverlayControllerTest extends SysuiTestCase {
WallpaperColors mainColors = new WallpaperColors(Color.valueOf(Color.RED),
Color.valueOf(Color.BLUE), null);
- String jsonString =
- "{\"android.theme.customization.color_source\":\"home_wallpaper\","
- + "\"android.theme.customization.system_palette\":\"A16B00\","
- + "\"android.theme.customization.accent_color\":\"A16B00\","
- + "\"android.theme.customization.color_index\":\"2\"}";
+ String jsonString = createJsonString(TestColorSource.home_wallpaper);
when(mSecureSettings.getStringForUser(
eq(Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES), anyInt()))
@@ -599,7 +632,6 @@ public class ThemeOverlayControllerTest extends SysuiTestCase {
}
-
@Test
@EnableFlags(com.android.systemui.shared.Flags.FLAG_NEW_CUSTOMIZATION_PICKER_UI)
public void onWallpaperColorsChanged_homeWallpaperWithSameColor_shouldKeepThemeAndReapply() {
@@ -608,11 +640,7 @@ public class ThemeOverlayControllerTest extends SysuiTestCase {
WallpaperColors mainColors = new WallpaperColors(Color.valueOf(Color.RED),
Color.valueOf(0xffa16b00), null);
- String jsonString =
- "{\"android.theme.customization.color_source\":\"home_wallpaper\","
- + "\"android.theme.customization.system_palette\":\"A16B00\","
- + "\"android.theme.customization.accent_color\":\"A16B00\","
- + "\"android.theme.customization.color_index\":\"2\"}";
+ String jsonString = createJsonString(TestColorSource.home_wallpaper);
when(mSecureSettings.getStringForUser(
eq(Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES), anyInt()))
@@ -642,11 +670,7 @@ public class ThemeOverlayControllerTest extends SysuiTestCase {
WallpaperColors mainColors = new WallpaperColors(Color.valueOf(Color.RED),
Color.valueOf(Color.BLUE), null);
- String jsonString =
- "{\"android.theme.customization.color_source\":\"home_wallpaper\","
- + "\"android.theme.customization.system_palette\":\"A16B00\","
- + "\"android.theme.customization.accent_color\":\"A16B00\","
- + "\"android.theme.customization.color_index\":\"2\"}";
+ String jsonString = createJsonString(TestColorSource.home_wallpaper);
when(mSecureSettings.getStringForUser(
eq(Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES), anyInt()))
@@ -676,11 +700,7 @@ public class ThemeOverlayControllerTest extends SysuiTestCase {
WallpaperColors mainColors = new WallpaperColors(Color.valueOf(Color.RED),
Color.valueOf(Color.BLUE), null);
- String jsonString =
- "{\"android.theme.customization.color_source\":\"home_wallpaper\","
- + "\"android.theme.customization.system_palette\":\"A16B00\","
- + "\"android.theme.customization.accent_color\":\"A16B00\","
- + "\"android.theme.customization.color_index\":\"2\"}";
+ String jsonString = createJsonString(TestColorSource.home_wallpaper);
when(mSecureSettings.getStringForUser(
eq(Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES), anyInt()))
@@ -711,11 +731,7 @@ public class ThemeOverlayControllerTest extends SysuiTestCase {
WallpaperColors mainColors = new WallpaperColors(Color.valueOf(Color.RED),
Color.valueOf(0xffa16b00), null);
- String jsonString =
- "{\"android.theme.customization.color_source\":\"home_wallpaper\","
- + "\"android.theme.customization.system_palette\":\"A16B00\","
- + "\"android.theme.customization.accent_color\":\"A16B00\","
- + "\"android.theme.customization.color_index\":\"2\"}";
+ String jsonString = createJsonString(TestColorSource.home_wallpaper);
when(mSecureSettings.getStringForUser(
eq(Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES), anyInt()))
@@ -745,11 +761,7 @@ public class ThemeOverlayControllerTest extends SysuiTestCase {
WallpaperColors mainColors = new WallpaperColors(Color.valueOf(Color.RED),
Color.valueOf(Color.BLUE), null);
- String jsonString =
- "{\"android.theme.customization.color_source\":\"home_wallpaper\","
- + "\"android.theme.customization.system_palette\":\"A16B00\","
- + "\"android.theme.customization.accent_color\":\"A16B00\","
- + "\"android.theme.customization.color_index\":\"2\"}";
+ String jsonString = createJsonString(TestColorSource.home_wallpaper);
when(mSecureSettings.getStringForUser(
eq(Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES), anyInt()))
@@ -886,7 +898,8 @@ public class ThemeOverlayControllerTest extends SysuiTestCase {
mBroadcastDispatcher, mBgHandler, executor, executor, mThemeOverlayApplier,
mSecureSettings, mWallpaperManager, mUserManager, mDeviceProvisionedController,
mUserTracker, mDumpManager, mFeatureFlags, mResources, mWakefulnessLifecycle,
- mJavaAdapter, mKeyguardTransitionInteractor, mUiModeManager, mActivityManager) {
+ mJavaAdapter, mKeyguardTransitionInteractor, mUiModeManager, mActivityManager,
+ mSystemProperties) {
@VisibleForTesting
protected boolean isNightMode() {
return false;
@@ -926,7 +939,8 @@ public class ThemeOverlayControllerTest extends SysuiTestCase {
mBroadcastDispatcher, mBgHandler, executor, executor, mThemeOverlayApplier,
mSecureSettings, mWallpaperManager, mUserManager, mDeviceProvisionedController,
mUserTracker, mDumpManager, mFeatureFlags, mResources, mWakefulnessLifecycle,
- mJavaAdapter, mKeyguardTransitionInteractor, mUiModeManager, mActivityManager) {
+ mJavaAdapter, mKeyguardTransitionInteractor, mUiModeManager, mActivityManager,
+ mSystemProperties) {
@VisibleForTesting
protected boolean isNightMode() {
return false;
@@ -992,7 +1006,7 @@ public class ThemeOverlayControllerTest extends SysuiTestCase {
clearInvocations(mThemeOverlayApplier);
// Device went to sleep and second set of colors was applied.
- mainColors = new WallpaperColors(Color.valueOf(Color.BLUE),
+ mainColors = new WallpaperColors(Color.valueOf(Color.BLUE),
Color.valueOf(Color.RED), null);
mColorsListener.getValue().onColorsChanged(mainColors, WallpaperManager.FLAG_SYSTEM,
USER_SYSTEM);
@@ -1018,7 +1032,7 @@ public class ThemeOverlayControllerTest extends SysuiTestCase {
clearInvocations(mThemeOverlayApplier);
// Device went to sleep and second set of colors was applied.
- mainColors = new WallpaperColors(Color.valueOf(Color.BLUE),
+ mainColors = new WallpaperColors(Color.valueOf(Color.BLUE),
Color.valueOf(Color.RED), null);
mColorsListener.getValue().onColorsChanged(mainColors, WallpaperManager.FLAG_SYSTEM,
USER_SYSTEM);
@@ -1034,8 +1048,9 @@ public class ThemeOverlayControllerTest extends SysuiTestCase {
WallpaperColors mainColors = new WallpaperColors(Color.valueOf(Color.RED),
Color.valueOf(Color.BLUE), null);
- String jsonString =
- "{\"android.theme.customization.system_palette\":\"00FF00\"}";
+ String jsonString = createJsonString(TestColorSource.home_wallpaper, "00FF00",
+ "TONAL_SPOT");
+
when(mSecureSettings.getStringForUser(
eq(Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES), anyInt()))
.thenReturn(jsonString);
@@ -1115,4 +1130,25 @@ public class ThemeOverlayControllerTest extends SysuiTestCase {
+ DynamicColors.getCustomColorsMapped(false).size() * 2)
).setResourceValue(any(String.class), eq(TYPE_INT_COLOR_ARGB8), anyInt(), eq(null));
}
+
+ private enum TestColorSource {
+ preset,
+ home_wallpaper,
+ lock_wallpaper
+ }
+
+ private String createJsonString(TestColorSource colorSource, String seedColorHex,
+ String style) {
+ return "{\"android.theme.customization.color_source\":\"" + colorSource.toString() + "\","
+ + "\"android.theme.customization.system_palette\":\"" + seedColorHex + "\","
+ + "\"android.theme.customization.accent_color\":\"" + seedColorHex + "\","
+ + "\"android.theme.customization.color_index\":\"2\","
+ + "\"android.theme.customization.theme_style\":\"" + style + "\"}";
+ }
+
+ private String createJsonString(TestColorSource colorSource) {
+ return createJsonString(colorSource, "A16B00", "TONAL_SPOT");
+ }
+
+
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioSharingStreamSliderViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioSharingStreamSliderViewModelTest.kt
index e484d8090c64..04ab98889755 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioSharingStreamSliderViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioSharingStreamSliderViewModelTest.kt
@@ -19,21 +19,17 @@ package com.android.systemui.volume.panel.component.volume.slider.ui.viewmodel
import android.bluetooth.BluetoothDevice
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
-import com.android.internal.logging.uiEventLogger
import com.android.settingslib.bluetooth.CachedBluetoothDevice
import com.android.systemui.SysuiTestCase
import com.android.systemui.common.shared.model.Icon
-import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.haptics.slider.sliderHapticsViewModelFactory
-import com.android.systemui.kosmos.testScope
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.collectLastValue
+import com.android.systemui.kosmos.runCurrent
+import com.android.systemui.kosmos.runTest
import com.android.systemui.res.R
import com.android.systemui.testKosmos
import com.android.systemui.volume.data.repository.audioSharingRepository
-import com.android.systemui.volume.domain.interactor.audioSharingInteractor
import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.test.runCurrent
-import kotlinx.coroutines.test.runTest
-import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.kotlin.mock
@@ -43,47 +39,30 @@ import org.mockito.kotlin.mock
class AudioSharingStreamSliderViewModelTest : SysuiTestCase() {
private val kosmos = testKosmos()
- private val testScope = kosmos.testScope
- private lateinit var stream: AudioSharingStreamSliderViewModel
-
- @Before
- fun setUp() {
- stream = audioSharingStreamSliderViewModel()
- }
-
- private fun audioSharingStreamSliderViewModel(): AudioSharingStreamSliderViewModel {
- return AudioSharingStreamSliderViewModel(
- testScope.backgroundScope,
- context,
- kosmos.audioSharingInteractor,
- kosmos.uiEventLogger,
- kosmos.sliderHapticsViewModelFactory,
- )
- }
+ private val underTest: AudioSharingStreamSliderViewModel =
+ with(kosmos) { audioSharingStreamSliderViewModelFactory.create(applicationCoroutineScope) }
@Test
fun slider_media_inAudioSharing() =
- with(kosmos) {
- testScope.runTest {
- val audioSharingSlider by collectLastValue(stream.slider)
+ kosmos.runTest {
+ val audioSharingSlider by collectLastValue(underTest.slider)
- val bluetoothDevice: BluetoothDevice = mock {}
- val cachedDevice: CachedBluetoothDevice = mock {
- on { groupId }.thenReturn(123)
- on { device }.thenReturn(bluetoothDevice)
- on { name }.thenReturn("my headset 2")
- }
- audioSharingRepository.setSecondaryDevice(cachedDevice)
+ val bluetoothDevice: BluetoothDevice = mock {}
+ val cachedDevice: CachedBluetoothDevice = mock {
+ on { groupId }.thenReturn(123)
+ on { device }.thenReturn(bluetoothDevice)
+ on { name }.thenReturn("my headset 2")
+ }
+ audioSharingRepository.setSecondaryDevice(cachedDevice)
- audioSharingRepository.setInAudioSharing(true)
- audioSharingRepository.setSecondaryGroupId(123)
+ audioSharingRepository.setInAudioSharing(true)
+ audioSharingRepository.setSecondaryGroupId(123)
- runCurrent()
+ runCurrent()
- assertThat(audioSharingSlider!!.label).isEqualTo("my headset 2")
- assertThat(audioSharingSlider!!.icon)
- .isEqualTo(Icon.Resource(R.drawable.ic_volume_media_bt, null))
- }
+ assertThat(audioSharingSlider!!.label).isEqualTo("my headset 2")
+ assertThat(audioSharingSlider!!.icon)
+ .isEqualTo(Icon.Resource(R.drawable.ic_volume_media_bt, null))
}
}
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 7c166de81502..cc6a7b93eef3 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
@@ -42,7 +42,6 @@ import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
-import org.junit.Ignore
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.kotlin.any
@@ -53,7 +52,6 @@ import org.mockito.kotlin.whenever
@SmallTest
@RunWith(AndroidJUnit4::class)
class WallpaperRepositoryImplTest : SysuiTestCase() {
-
private var isWallpaperSupported = true
private val kosmos =
testKosmos().apply {
@@ -293,12 +291,9 @@ class WallpaperRepositoryImplTest : SysuiTestCase() {
Intent(Intent.ACTION_WALLPAPER_CHANGED),
)
assertThat(latest).isTrue()
- assertThat(underTest.sendLockscreenLayoutJob).isNotNull()
- assertThat(underTest.sendLockscreenLayoutJob!!.isActive).isEqualTo(true)
}
@Test
- @Ignore("ag/31591766")
@EnableFlags(SharedFlags.FLAG_EXTENDED_WALLPAPER_EFFECTS)
fun shouldSendNotificationLayout_setNotExtendedEffectsWallpaper_cancelSendLayoutJob() =
testScope.runTest {
@@ -315,8 +310,6 @@ class WallpaperRepositoryImplTest : SysuiTestCase() {
Intent(Intent.ACTION_WALLPAPER_CHANGED),
)
assertThat(latest).isTrue()
- assertThat(underTest.sendLockscreenLayoutJob).isNotNull()
- assertThat(underTest.sendLockscreenLayoutJob!!.isActive).isEqualTo(true)
whenever(kosmos.wallpaperManager.getWallpaperInfoForUser(any()))
.thenReturn(UNSUPPORTED_WP)
@@ -327,7 +320,6 @@ class WallpaperRepositoryImplTest : SysuiTestCase() {
runCurrent()
assertThat(latest).isFalse()
- assertThat(underTest.sendLockscreenLayoutJob?.isCancelled).isEqualTo(true)
}
private companion object {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/wallpapers/domain/interactor/WallpaperFocalAreaInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/wallpapers/domain/interactor/WallpaperFocalAreaInteractorTest.kt
index cd6e18a69c4d..31a611cc984b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/wallpapers/domain/interactor/WallpaperFocalAreaInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/wallpapers/domain/interactor/WallpaperFocalAreaInteractorTest.kt
@@ -30,13 +30,8 @@ import com.android.systemui.kosmos.testScope
import com.android.systemui.res.R
import com.android.systemui.shade.data.repository.ShadeRepository
import com.android.systemui.shade.data.repository.shadeRepository
-import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository
-import com.android.systemui.statusbar.notification.data.repository.setActiveNotifs
-import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor
import com.android.systemui.testKosmos
-import com.android.systemui.wallpapers.data.repository.fakeWallpaperFocalAreaRepository
import com.android.systemui.wallpapers.data.repository.wallpaperFocalAreaRepository
-import com.android.systemui.wallpapers.data.repository.wallpaperRepository
import com.android.systemui.wallpapers.ui.viewmodel.wallpaperFocalAreaViewModel
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -77,40 +72,26 @@ class WallpaperFocalAreaInteractorTest : SysuiTestCase() {
.thenReturn(2f)
underTest =
WallpaperFocalAreaInteractor(
- applicationScope = testScope.backgroundScope,
context = kosmos.mockedContext,
- wallpaperFocalAreaRepository = kosmos.fakeWallpaperFocalAreaRepository,
+ wallpaperFocalAreaRepository = kosmos.wallpaperFocalAreaRepository,
shadeRepository = kosmos.shadeRepository,
- activeNotificationsInteractor = kosmos.activeNotificationsInteractor,
- wallpaperRepository = kosmos.wallpaperRepository,
)
}
- private fun overrideMockedResources(overrideResources: OverrideResources) {
- val displayMetrics =
- DisplayMetrics().apply {
- widthPixels = overrideResources.screenWidth
- heightPixels = overrideResources.screenHeight
- density = 2f
- }
- whenever(mockedResources.displayMetrics).thenReturn(displayMetrics)
- whenever(mockedResources.getBoolean(R.bool.center_align_focal_area_shape))
- .thenReturn(overrideResources.centerAlignFocalArea)
- }
-
@Test
fun focalAreaBounds_withoutNotifications_inHandheldDevices() =
testScope.runTest {
overrideMockedResources(
+ mockedResources,
OverrideResources(
screenWidth = 1000,
screenHeight = 2000,
centerAlignFocalArea = false,
- )
+ ),
)
val bounds by collectLastValue(underTest.wallpaperFocalAreaBounds)
kosmos.shadeRepository.setShadeLayoutWide(false)
- kosmos.activeNotificationListRepository.setActiveNotifs(0)
+
kosmos.wallpaperFocalAreaRepository.setShortcutAbsoluteTop(1800F)
kosmos.wallpaperFocalAreaRepository.setNotificationDefaultTop(400F)
kosmos.wallpaperFocalAreaRepository.setNotificationStackAbsoluteBottom(400F)
@@ -122,15 +103,15 @@ class WallpaperFocalAreaInteractorTest : SysuiTestCase() {
fun focalAreaBounds_withNotifications_inHandheldDevices() =
testScope.runTest {
overrideMockedResources(
+ mockedResources,
OverrideResources(
screenWidth = 1000,
screenHeight = 2000,
centerAlignFocalArea = false,
- )
+ ),
)
val bounds by collectLastValue(underTest.wallpaperFocalAreaBounds)
kosmos.shadeRepository.setShadeLayoutWide(false)
- kosmos.activeNotificationListRepository.setActiveNotifs(1)
kosmos.wallpaperFocalAreaRepository.setShortcutAbsoluteTop(1800F)
kosmos.wallpaperFocalAreaRepository.setNotificationDefaultTop(400F)
kosmos.wallpaperFocalAreaRepository.setNotificationStackAbsoluteBottom(600F)
@@ -139,58 +120,38 @@ class WallpaperFocalAreaInteractorTest : SysuiTestCase() {
}
@Test
- fun focalAreaBounds_withNotifications_inUnfoldLandscape() =
+ fun focalAreaBounds_inUnfoldLandscape() =
testScope.runTest {
overrideMockedResources(
+ mockedResources,
OverrideResources(
screenWidth = 2000,
screenHeight = 1600,
centerAlignFocalArea = false,
- )
+ ),
)
val bounds by collectLastValue(underTest.wallpaperFocalAreaBounds)
kosmos.shadeRepository.setShadeLayoutWide(true)
- kosmos.activeNotificationListRepository.setActiveNotifs(1)
- kosmos.wallpaperFocalAreaRepository.setShortcutAbsoluteTop(1400F)
- kosmos.wallpaperFocalAreaRepository.setNotificationDefaultTop(400F)
- kosmos.wallpaperFocalAreaRepository.setNotificationStackAbsoluteBottom(600F)
-
- assertThat(bounds).isEqualTo(RectF(500f, 600F, 1000F, 1100F))
- }
-
- @Test
- fun focalAreaBounds_withoutNotifications_inUnfoldLandscape() =
- testScope.runTest {
- overrideMockedResources(
- OverrideResources(
- screenWidth = 2000,
- screenHeight = 1600,
- centerAlignFocalArea = false,
- )
- )
- val bounds by collectLastValue(underTest.wallpaperFocalAreaBounds)
- kosmos.shadeRepository.setShadeLayoutWide(true)
- kosmos.activeNotificationListRepository.setActiveNotifs(0)
kosmos.wallpaperFocalAreaRepository.setShortcutAbsoluteTop(1400F)
kosmos.wallpaperFocalAreaRepository.setNotificationDefaultTop(400F)
kosmos.wallpaperFocalAreaRepository.setNotificationStackAbsoluteBottom(400F)
- assertThat(bounds).isEqualTo(RectF(1000f, 600F, 1500F, 1100F))
+ assertThat(bounds).isEqualTo(RectF(600f, 600F, 1400F, 1100F))
}
@Test
fun focalAreaBounds_withNotifications_inUnfoldPortrait() =
testScope.runTest {
overrideMockedResources(
+ mockedResources,
OverrideResources(
screenWidth = 1600,
screenHeight = 2000,
centerAlignFocalArea = false,
- )
+ ),
)
val bounds by collectLastValue(underTest.wallpaperFocalAreaBounds)
kosmos.shadeRepository.setShadeLayoutWide(false)
- kosmos.activeNotificationListRepository.setActiveNotifs(1)
kosmos.wallpaperFocalAreaRepository.setShortcutAbsoluteTop(1800F)
kosmos.wallpaperFocalAreaRepository.setNotificationDefaultTop(400F)
kosmos.wallpaperFocalAreaRepository.setNotificationStackAbsoluteBottom(600F)
@@ -202,15 +163,15 @@ class WallpaperFocalAreaInteractorTest : SysuiTestCase() {
fun focalAreaBounds_withoutNotifications_inUnfoldPortrait() =
testScope.runTest {
overrideMockedResources(
+ mockedResources,
OverrideResources(
screenWidth = 1600,
screenHeight = 2000,
centerAlignFocalArea = false,
- )
+ ),
)
val bounds by collectLastValue(underTest.wallpaperFocalAreaBounds)
kosmos.shadeRepository.setShadeLayoutWide(false)
- kosmos.activeNotificationListRepository.setActiveNotifs(0)
kosmos.wallpaperFocalAreaRepository.setShortcutAbsoluteTop(1800F)
kosmos.wallpaperFocalAreaRepository.setNotificationDefaultTop(400F)
kosmos.wallpaperFocalAreaRepository.setNotificationStackAbsoluteBottom(600F)
@@ -219,18 +180,18 @@ class WallpaperFocalAreaInteractorTest : SysuiTestCase() {
}
@Test
- fun focalAreaBounds_withNotifications_inTabletLandscape() =
+ fun focalAreaBounds_inTabletLandscape() =
testScope.runTest {
overrideMockedResources(
+ mockedResources,
OverrideResources(
screenWidth = 3000,
screenHeight = 2000,
centerAlignFocalArea = true,
- )
+ ),
)
val bounds by collectLastValue(underTest.wallpaperFocalAreaBounds)
kosmos.shadeRepository.setShadeLayoutWide(true)
- kosmos.activeNotificationListRepository.setActiveNotifs(1)
kosmos.wallpaperFocalAreaRepository.setShortcutAbsoluteTop(1800F)
kosmos.wallpaperFocalAreaRepository.setNotificationDefaultTop(200F)
kosmos.wallpaperFocalAreaRepository.setNotificationStackAbsoluteBottom(200F)
@@ -239,35 +200,16 @@ class WallpaperFocalAreaInteractorTest : SysuiTestCase() {
}
@Test
- fun focalAreaBounds_withoutNotifications_inTabletLandscape() =
- testScope.runTest {
- overrideMockedResources(
- OverrideResources(
- screenWidth = 3000,
- screenHeight = 2000,
- centerAlignFocalArea = true,
- )
- )
- val bounds by collectLastValue(underTest.wallpaperFocalAreaBounds)
- kosmos.shadeRepository.setShadeLayoutWide(true)
- kosmos.activeNotificationListRepository.setActiveNotifs(0)
- kosmos.wallpaperFocalAreaRepository.setShortcutAbsoluteTop(1800F)
- kosmos.wallpaperFocalAreaRepository.setNotificationDefaultTop(400F)
- kosmos.wallpaperFocalAreaRepository.setNotificationStackAbsoluteBottom(400F)
-
- assertThat(bounds).isEqualTo(RectF(1000f, 600F, 2000F, 1400F))
- }
-
- @Test
fun onTap_inFocalBounds() =
testScope.runTest {
kosmos.wallpaperFocalAreaRepository.setTapPosition(PointF(0F, 0F))
overrideMockedResources(
+ mockedResources,
OverrideResources(
screenWidth = 1000,
screenHeight = 2000,
centerAlignFocalArea = false,
- )
+ ),
)
kosmos.wallpaperFocalAreaRepository.setWallpaperFocalAreaBounds(
RectF(250f, 700F, 750F, 1400F)
@@ -287,11 +229,12 @@ class WallpaperFocalAreaInteractorTest : SysuiTestCase() {
testScope.runTest {
kosmos.wallpaperFocalAreaRepository.setTapPosition(PointF(0F, 0F))
overrideMockedResources(
+ mockedResources,
OverrideResources(
screenWidth = 1000,
screenHeight = 2000,
centerAlignFocalArea = false,
- )
+ ),
)
kosmos.wallpaperFocalAreaViewModel = mock()
kosmos.wallpaperFocalAreaRepository.setWallpaperFocalAreaBounds(
@@ -309,4 +252,21 @@ class WallpaperFocalAreaInteractorTest : SysuiTestCase() {
val screenHeight: Int,
val centerAlignFocalArea: Boolean,
)
+
+ companion object {
+ fun overrideMockedResources(
+ mockedResources: Resources,
+ overrideResources: OverrideResources,
+ ) {
+ val displayMetrics =
+ DisplayMetrics().apply {
+ widthPixels = overrideResources.screenWidth
+ heightPixels = overrideResources.screenHeight
+ density = 2f
+ }
+ whenever(mockedResources.displayMetrics).thenReturn(displayMetrics)
+ whenever(mockedResources.getBoolean(R.bool.center_align_focal_area_shape))
+ .thenReturn(overrideResources.centerAlignFocalArea)
+ }
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/wallpapers/ui/viewmodel/WallpaperFocalAreaViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/wallpapers/ui/viewmodel/WallpaperFocalAreaViewModelTest.kt
new file mode 100644
index 000000000000..3cd20721a15b
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/wallpapers/ui/viewmodel/WallpaperFocalAreaViewModelTest.kt
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.wallpapers.ui.viewmodel
+
+import android.content.mockedContext
+import android.content.res.Resources
+import android.graphics.RectF
+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.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.shade.data.repository.shadeRepository
+import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository
+import com.android.systemui.statusbar.notification.data.repository.setActiveNotifs
+import com.android.systemui.testKosmos
+import com.android.systemui.wallpapers.data.repository.wallpaperFocalAreaRepository
+import com.android.systemui.wallpapers.domain.interactor.WallpaperFocalAreaInteractor
+import com.android.systemui.wallpapers.domain.interactor.WallpaperFocalAreaInteractorTest.Companion.overrideMockedResources
+import com.android.systemui.wallpapers.domain.interactor.WallpaperFocalAreaInteractorTest.OverrideResources
+import com.android.systemui.wallpapers.domain.interactor.wallpaperFocalAreaInteractor
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
+
+@ExperimentalCoroutinesApi
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class WallpaperFocalAreaViewModelTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+ private lateinit var mockedResources: Resources
+ lateinit var underTest: WallpaperFocalAreaViewModel
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+ mockedResources = mock<Resources>()
+ overrideMockedResources(
+ mockedResources,
+ OverrideResources(screenWidth = 1000, screenHeight = 2000, centerAlignFocalArea = false),
+ )
+ whenever(kosmos.mockedContext.resources).thenReturn(mockedResources)
+ whenever(
+ mockedResources.getFloat(
+ Resources.getSystem()
+ .getIdentifier(
+ /* name= */ "config_wallpaperMaxScale",
+ /* defType= */ "dimen",
+ /* defPackage= */ "android",
+ )
+ )
+ )
+ .thenReturn(2f)
+ kosmos.wallpaperFocalAreaInteractor =
+ WallpaperFocalAreaInteractor(
+ context = kosmos.mockedContext,
+ wallpaperFocalAreaRepository = kosmos.wallpaperFocalAreaRepository,
+ shadeRepository = kosmos.shadeRepository,
+ )
+ underTest =
+ WallpaperFocalAreaViewModel(
+ wallpaperFocalAreaInteractor = kosmos.wallpaperFocalAreaInteractor,
+ keyguardTransitionInteractor = kosmos.keyguardTransitionInteractor,
+ )
+ }
+
+ @Test
+ fun focalAreaBoundsSent_whenFinishTransitioningToLockscreen() =
+ testScope.runTest {
+ overrideMockedResources(
+ mockedResources,
+ OverrideResources(
+ screenWidth = 1600,
+ screenHeight = 2000,
+ centerAlignFocalArea = false,
+ ),
+ )
+ val bounds by collectLastValue(underTest.wallpaperFocalAreaBounds)
+
+ kosmos.fakeKeyguardTransitionRepository.sendTransitionSteps(
+ listOf(
+ TransitionStep(transitionState = TransitionState.STARTED, to = LOCKSCREEN),
+ TransitionStep(transitionState = TransitionState.FINISHED, to = LOCKSCREEN),
+ ),
+ testScope,
+ )
+
+ setTestFocalAreaBounds()
+
+ assertThat(bounds).isEqualTo(RectF(400F, 510F, 1200F, 700F))
+ }
+
+ @Test
+ fun focalAreaBoundsNotSent_whenNotFinishTransitioningToLockscreen() =
+ testScope.runTest {
+ overrideMockedResources(
+ mockedResources,
+ OverrideResources(
+ screenWidth = 1600,
+ screenHeight = 2000,
+ centerAlignFocalArea = false,
+ ),
+ )
+ val bounds by collectLastValue(underTest.wallpaperFocalAreaBounds)
+
+ kosmos.fakeKeyguardTransitionRepository.sendTransitionSteps(
+ listOf(TransitionStep(transitionState = TransitionState.STARTED, to = LOCKSCREEN)),
+ testScope,
+ )
+ setTestFocalAreaBounds()
+
+ assertThat(bounds).isEqualTo(null)
+ }
+
+ private fun setTestFocalAreaBounds() {
+ kosmos.shadeRepository.setShadeLayoutWide(false)
+ kosmos.activeNotificationListRepository.setActiveNotifs(0)
+ kosmos.wallpaperFocalAreaRepository.setShortcutAbsoluteTop(400F)
+ kosmos.wallpaperFocalAreaRepository.setNotificationDefaultTop(20F)
+ kosmos.wallpaperFocalAreaRepository.setNotificationStackAbsoluteBottom(20F)
+ }
+}
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockLogger.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockLogger.kt
index 9a837446a802..3ed321e48cd3 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockLogger.kt
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockLogger.kt
@@ -83,6 +83,13 @@ class ClockLogger(private val view: View?, buffer: MessageBuffer, tag: String) :
}
}
+ fun updateAxes(lsFVar: String, aodFVar: String) {
+ i({ "updateAxes(LS = $str1, AOD = $str2)" }) {
+ str1 = lsFVar
+ str2 = aodFVar
+ }
+ }
+
fun addView(child: View) {
d({ "addView($str1 @$int1)" }) {
str1 = child::class.simpleName!!
@@ -90,6 +97,14 @@ class ClockLogger(private val view: View?, buffer: MessageBuffer, tag: String) :
}
}
+ fun animateDoze() {
+ d("animateDoze()")
+ }
+
+ fun animateCharge() {
+ d("animateCharge()")
+ }
+
fun animateFidget(x: Float, y: Float) {
d({ "animateFidget($str1, $str2)" }) {
str1 = x.toString()
diff --git a/packages/SystemUI/res-keyguard/drawable/pin_bouncer_confirm.xml b/packages/SystemUI/res-keyguard/drawable/pin_bouncer_confirm.xml
new file mode 100644
index 000000000000..a27e29f1beb6
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/drawable/pin_bouncer_confirm.xml
@@ -0,0 +1,31 @@
+<!--
+ ~ 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.
+ -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="40dp"
+ android:height="40dp"
+ android:viewportHeight="40"
+ android:viewportWidth="40">
+ <group>
+ <clip-path android:pathData="M8,12h24.5v15.5h-24.5z" />
+ <path
+ android:fillColor="#000000"
+ android:pathData="M30.75,12C29.79,12 29,12.79 29,13.75V25.75C29,26.71 29.79,27.5 30.75,27.5C31.71,27.5 32.5,26.71 32.5,25.75V13.75C32.5,12.79 31.71,12 30.75,12Z" />
+ <path
+ android:fillColor="#000000"
+ android:pathData="M20.98,12.92C20.3,12.24 19.19,12.24 18.51,12.92C17.83,13.6 17.83,14.71 18.51,15.39L21.12,18H9.75C8.79,18 8,18.79 8,19.75C8,20.71 8.79,21.5 9.75,21.5H21.11L18.51,24.1C18.18,24.43 18,24.87 18,25.34C18,25.81 18.18,26.25 18.52,26.58C18.86,26.92 19.31,27.09 19.75,27.09C20.19,27.09 20.65,26.92 20.99,26.58L26.61,20.96C27.28,20.29 27.28,19.21 26.61,18.55L20.98,12.92Z" />
+ </group>
+</vector>
diff --git a/packages/SystemUI/res-keyguard/drawable/pin_bouncer_delete_filled.xml b/packages/SystemUI/res-keyguard/drawable/pin_bouncer_delete_filled.xml
new file mode 100644
index 000000000000..86f95bc97169
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/drawable/pin_bouncer_delete_filled.xml
@@ -0,0 +1,25 @@
+<!--
+ ~ 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.
+ -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="40dp"
+ android:height="40dp"
+ android:viewportHeight="40"
+ android:viewportWidth="40">
+ <path
+ android:fillColor="#000000"
+ android:pathData="M22.167,21.9L25.531,25.265C25.795,25.502 26.112,25.621 26.481,25.621C26.851,25.621 27.167,25.502 27.431,25.265C27.669,25.001 27.788,24.684 27.788,24.315C27.788,23.919 27.669,23.589 27.431,23.325L24.067,20L27.392,16.675C27.656,16.411 27.788,16.094 27.788,15.725C27.788,15.356 27.656,15.039 27.392,14.775C27.128,14.511 26.798,14.379 26.402,14.379C26.033,14.379 25.729,14.511 25.492,14.775L22.167,18.1L18.802,14.735C18.538,14.498 18.222,14.379 17.852,14.379C17.483,14.379 17.166,14.498 16.902,14.735C16.665,14.999 16.546,15.329 16.546,15.725C16.546,16.094 16.665,16.411 16.902,16.675L20.267,20L16.902,23.325C16.665,23.589 16.546,23.906 16.546,24.275C16.546,24.644 16.665,24.961 16.902,25.225C17.166,25.489 17.483,25.621 17.852,25.621C18.248,25.621 18.578,25.489 18.842,25.225L22.167,21.9ZM14.012,32.667C13.59,32.667 13.181,32.574 12.785,32.39C12.416,32.179 12.099,31.915 11.835,31.598L4.394,21.623C4.024,21.148 3.84,20.607 3.84,20C3.84,19.393 4.024,18.852 4.394,18.377L11.835,8.402C12.073,8.085 12.39,7.835 12.785,7.65C13.181,7.439 13.59,7.333 14.012,7.333H32.142C32.907,7.333 33.554,7.597 34.081,8.125C34.609,8.653 34.873,9.286 34.873,10.025V29.975C34.873,30.714 34.609,31.347 34.081,31.875C33.554,32.403 32.907,32.667 32.142,32.667H14.012Z" />
+</vector>
diff --git a/packages/SystemUI/res-keyguard/drawable/pin_bouncer_delete_outline.xml b/packages/SystemUI/res-keyguard/drawable/pin_bouncer_delete_outline.xml
new file mode 100644
index 000000000000..7f551f4d3c60
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/drawable/pin_bouncer_delete_outline.xml
@@ -0,0 +1,31 @@
+<!--
+ ~ 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.
+ -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="40dp"
+ android:height="40dp"
+ android:viewportHeight="40"
+ android:viewportWidth="40">
+ <group>
+ <clip-path android:pathData="M5,7h29.89v25h-29.89z" />
+ <path
+ android:fillColor="#000000"
+ android:pathData="M30.96,32H15.59C14.21,32 12.89,31.34 12.06,30.24L5.78,21.86C4.74,20.47 4.74,18.54 5.78,17.15L12.06,8.77C12.89,7.67 14.21,7 15.59,7H30.96C33.13,7 34.89,8.76 34.89,10.93V28.08C34.89,30.25 33.13,32.01 30.96,32.01V32ZM14.46,28.44C14.73,28.79 15.15,29 15.59,29H30.96C31.47,29 31.89,28.58 31.89,28.07V10.93C31.89,10.42 31.47,10 30.96,10H15.59C15.15,10 14.73,10.21 14.46,10.56L8.18,18.94C7.93,19.27 7.93,19.72 8.18,20.05L14.46,28.43V28.44Z" />
+ <path
+ android:fillColor="#000000"
+ android:pathData="M22.46,21.27L25.36,24.17C25.6,24.43 25.89,24.56 26.25,24.56C26.61,24.56 26.9,24.43 27.14,24.17C27.4,23.93 27.53,23.64 27.53,23.28C27.53,22.92 27.4,22.63 27.14,22.39L24.24,19.49L27.14,16.59C27.38,16.35 27.49,16.06 27.49,15.7C27.49,15.34 27.37,15.05 27.14,14.81C26.91,14.57 26.61,14.46 26.25,14.46C25.89,14.46 25.59,14.58 25.33,14.81L22.46,17.71L19.56,14.81C19.32,14.55 19.03,14.42 18.67,14.42C18.31,14.42 18.02,14.55 17.78,14.81C17.52,15.05 17.39,15.34 17.39,15.7C17.39,16.06 17.52,16.35 17.78,16.59L20.68,19.49L17.78,22.39C17.52,22.63 17.39,22.92 17.39,23.28C17.39,23.64 17.52,23.93 17.78,24.17C18.02,24.41 18.31,24.52 18.67,24.52C19.03,24.52 19.32,24.4 19.56,24.17L22.46,21.27Z" />
+ </group>
+</vector>
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_bouncer_message_area.xml b/packages/SystemUI/res-keyguard/layout/keyguard_bouncer_message_area.xml
index 0b35559148af..87d06bfde743 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_bouncer_message_area.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_bouncer_message_area.xml
@@ -21,7 +21,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/keyguard_lock_padding"
- android:importantForAccessibility="no"
+ android:accessibilityLiveRegion="polite"
android:ellipsize="marquee"
android:focusable="false"
android:gravity="center"
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 f231df2f1a10..c7f320c69113 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_password_motion_layout.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_password_motion_layout.xml
@@ -67,6 +67,7 @@
<com.android.systemui.bouncer.ui.BouncerMessageView
android:id="@+id/bouncer_message_view"
android:screenReaderFocusable="true"
+ android:accessibilityLiveRegion="polite"
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 04457229d573..9359838238af 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_password_view.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_password_view.xml
@@ -32,6 +32,7 @@
<com.android.systemui.bouncer.ui.BouncerMessageView
android:id="@+id/bouncer_message_view"
android:screenReaderFocusable="true"
+ android:accessibilityLiveRegion="polite"
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 b184344f2f24..6cbe96a8cb50 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_pattern_motion_layout.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_pattern_motion_layout.xml
@@ -68,6 +68,7 @@
<com.android.systemui.bouncer.ui.BouncerMessageView
android:id="@+id/bouncer_message_view"
android:screenReaderFocusable="true"
+ android:accessibilityLiveRegion="polite"
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 0e15ff66f3ee..cf388875a174 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_pattern_view.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_pattern_view.xml
@@ -36,6 +36,7 @@
<com.android.systemui.bouncer.ui.BouncerMessageView
android:id="@+id/bouncer_message_view"
android:screenReaderFocusable="true"
+ android:accessibilityLiveRegion="polite"
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 f6ac02aee657..33eab179c3f7 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_pin_motion_layout.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_pin_motion_layout.xml
@@ -75,6 +75,7 @@
<com.android.systemui.bouncer.ui.BouncerMessageView
android:id="@+id/bouncer_message_view"
android:screenReaderFocusable="true"
+ android:accessibilityLiveRegion="polite"
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 ba4da794d777..fd5eeb8b9408 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_pin_view.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_pin_view.xml
@@ -33,6 +33,7 @@
<com.android.systemui.bouncer.ui.BouncerMessageView
android:id="@+id/bouncer_message_view"
android:screenReaderFocusable="true"
+ android:accessibilityLiveRegion="polite"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" />
diff --git a/packages/SystemUI/res-keyguard/values/dimens.xml b/packages/SystemUI/res-keyguard/values/dimens.xml
index bfb37a0d97a7..6d446453d9f7 100644
--- a/packages/SystemUI/res-keyguard/values/dimens.xml
+++ b/packages/SystemUI/res-keyguard/values/dimens.xml
@@ -31,6 +31,10 @@
<!-- height for the keyguard pin input field -->
<dimen name="keyguard_pin_field_height">56dp</dimen>
+ <dimen name="keyguard_pattern_dot_size">16dp</dimen>
+ <dimen name="keyguard_pattern_activated_dot_size">24dp</dimen>
+ <dimen name="keyguard_pattern_stroke_width">32dp</dimen>
+
<!-- height for the keyguard password input field -->
<dimen name="keyguard_password_field_height">56dp</dimen>
diff --git a/packages/SystemUI/res/drawable/ic_legacy_speaker_mute.xml b/packages/SystemUI/res/drawable/ic_legacy_speaker_mute.xml
new file mode 100644
index 000000000000..4e402cf530e4
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_legacy_speaker_mute.xml
@@ -0,0 +1,25 @@
+<!--
+ ~ Copyright (C) 2022 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.
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:tint="?android:attr/textColorPrimary"
+ android:autoMirrored="true">
+ <path android:fillColor="#FFFFFFFF"
+ android:pathData="M19.8,22.6 L16.775,19.575Q16.15,19.975 15.45,20.263Q14.75,20.55 14,20.725V18.675Q14.35,18.55 14.688,18.425Q15.025,18.3 15.325,18.125L12,14.8V20L7,15H3V9H6.2L1.4,4.2L2.8,2.8L21.2,21.2ZM19.6,16.8 L18.15,15.35Q18.575,14.575 18.788,13.725Q19,12.875 19,11.975Q19,9.625 17.625,7.775Q16.25,5.925 14,5.275V3.225Q17.1,3.925 19.05,6.362Q21,8.8 21,11.975Q21,13.3 20.638,14.525Q20.275,15.75 19.6,16.8ZM16.25,13.45 L14,11.2V7.95Q15.175,8.5 15.838,9.6Q16.5,10.7 16.5,12Q16.5,12.375 16.438,12.738Q16.375,13.1 16.25,13.45ZM12,9.2 L9.4,6.6 12,4ZM10,15.15V12.8L8.2,11H5V13H7.85ZM9.1,11.9Z"/>
+</vector> \ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/ic_legacy_speaker_on.xml b/packages/SystemUI/res/drawable/ic_legacy_speaker_on.xml
new file mode 100644
index 000000000000..2a90e051b83b
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_legacy_speaker_on.xml
@@ -0,0 +1,25 @@
+<!--
+ ~ Copyright (C) 2022 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.
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:tint="?android:attr/textColorPrimary"
+ android:autoMirrored="true">
+ <path android:fillColor="#FFFFFFFF"
+ android:pathData="M14,20.725V18.675Q16.25,18.025 17.625,16.175Q19,14.325 19,11.975Q19,9.625 17.625,7.775Q16.25,5.925 14,5.275V3.225Q17.1,3.925 19.05,6.362Q21,8.8 21,11.975Q21,15.15 19.05,17.587Q17.1,20.025 14,20.725ZM3,15V9H7L12,4V20L7,15ZM14,16V7.95Q15.175,8.5 15.838,9.6Q16.5,10.7 16.5,12Q16.5,13.275 15.838,14.362Q15.175,15.45 14,16ZM10,8.85 L7.85,11H5V13H7.85L10,15.15ZM7.5,12Z"/>
+</vector> \ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/ic_legacy_volume_ringer_vibrate.xml b/packages/SystemUI/res/drawable/ic_legacy_volume_ringer_vibrate.xml
new file mode 100644
index 000000000000..b18e0a7aae60
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_legacy_volume_ringer_vibrate.xml
@@ -0,0 +1,27 @@
+<!--
+ Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:height="19dp"
+ android:width="19dp"
+ android:viewportHeight="24.0"
+ android:viewportWidth="24.0"
+ android:tint="?android:attr/textColorPrimary">
+
+ <path
+ android:fillColor="#FFFFFFFF"
+ android:pathData="M1,9h2v6H1V9zM4,17h2V7H4V17zM21,9v6h2V9H21zM18,17h2V7h-2V17zM17,5.5v13c0,0.83 -0.67,1.5 -1.5,1.5h-7C7.67,20 7,19.33 7,18.5v-13C7,4.67 7.67,4 8.5,4h7C16.33,4 17,4.67 17,5.5zM15,6H9v12h6V6z"/>
+
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_speaker_mute.xml b/packages/SystemUI/res/drawable/ic_speaker_mute.xml
index 4e402cf530e4..bf31580f3bb0 100644
--- a/packages/SystemUI/res/drawable/ic_speaker_mute.xml
+++ b/packages/SystemUI/res/drawable/ic_speaker_mute.xml
@@ -1,5 +1,5 @@
<!--
- ~ Copyright (C) 2022 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,12 +14,15 @@
~ limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24"
- android:viewportHeight="24"
- android:tint="?android:attr/textColorPrimary"
- android:autoMirrored="true">
- <path android:fillColor="#FFFFFFFF"
- android:pathData="M19.8,22.6 L16.775,19.575Q16.15,19.975 15.45,20.263Q14.75,20.55 14,20.725V18.675Q14.35,18.55 14.688,18.425Q15.025,18.3 15.325,18.125L12,14.8V20L7,15H3V9H6.2L1.4,4.2L2.8,2.8L21.2,21.2ZM19.6,16.8 L18.15,15.35Q18.575,14.575 18.788,13.725Q19,12.875 19,11.975Q19,9.625 17.625,7.775Q16.25,5.925 14,5.275V3.225Q17.1,3.925 19.05,6.362Q21,8.8 21,11.975Q21,13.3 20.638,14.525Q20.275,15.75 19.6,16.8ZM16.25,13.45 L14,11.2V7.95Q15.175,8.5 15.838,9.6Q16.5,10.7 16.5,12Q16.5,12.375 16.438,12.738Q16.375,13.1 16.25,13.45ZM12,9.2 L9.4,6.6 12,4ZM10,15.15V12.8L8.2,11H5V13H7.85ZM9.1,11.9Z"/>
-</vector> \ No newline at end of file
+ android:width="20dp"
+ android:height="20dp"
+ android:viewportWidth="20"
+ android:viewportHeight="20">
+ <group>
+ <clip-path
+ android:pathData="M0,0h20v20h-20z"/>
+ <path
+ android:pathData="M16,18.125L13.771,15.896C13.465,16.09 13.097,16.278 12.667,16.458C12.25,16.625 11.861,16.75 11.5,16.833V15.292C11.667,15.222 11.861,15.146 12.083,15.063C12.319,14.965 12.514,14.875 12.667,14.792L10,12.125V16.021L6,12.021H3V8.021H5.875L1.875,4L2.938,2.938L17.063,17.063L16,18.125ZM15.875,13.771L14.792,12.688C15.014,12.285 15.188,11.861 15.313,11.417C15.438,10.958 15.5,10.493 15.5,10.021C15.5,8.785 15.125,7.688 14.375,6.729C13.639,5.757 12.681,5.09 11.5,4.729V3.188C13.125,3.507 14.444,4.313 15.458,5.604C16.486,6.896 17,8.368 17,10.021C17,10.688 16.903,11.34 16.708,11.979C16.514,12.604 16.236,13.201 15.875,13.771ZM13.292,11.188L11.5,9.396V6.854C12.125,7.132 12.611,7.563 12.958,8.146C13.319,8.715 13.5,9.34 13.5,10.021C13.5,10.215 13.486,10.41 13.458,10.604C13.431,10.799 13.375,10.993 13.292,11.188ZM10,7.896L8.063,5.958L10,4.021V7.896Z"
+ android:fillColor="#ECDFE5"/>
+ </group>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_speaker_on.xml b/packages/SystemUI/res/drawable/ic_speaker_on.xml
index 2a90e051b83b..f0d057e1b093 100644
--- a/packages/SystemUI/res/drawable/ic_speaker_on.xml
+++ b/packages/SystemUI/res/drawable/ic_speaker_on.xml
@@ -1,5 +1,5 @@
<!--
- ~ Copyright (C) 2022 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,12 +14,15 @@
~ limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24"
- android:viewportHeight="24"
- android:tint="?android:attr/textColorPrimary"
- android:autoMirrored="true">
- <path android:fillColor="#FFFFFFFF"
- android:pathData="M14,20.725V18.675Q16.25,18.025 17.625,16.175Q19,14.325 19,11.975Q19,9.625 17.625,7.775Q16.25,5.925 14,5.275V3.225Q17.1,3.925 19.05,6.362Q21,8.8 21,11.975Q21,15.15 19.05,17.587Q17.1,20.025 14,20.725ZM3,15V9H7L12,4V20L7,15ZM14,16V7.95Q15.175,8.5 15.838,9.6Q16.5,10.7 16.5,12Q16.5,13.275 15.838,14.362Q15.175,15.45 14,16ZM10,8.85 L7.85,11H5V13H7.85L10,15.15ZM7.5,12Z"/>
-</vector> \ No newline at end of file
+ android:width="20dp"
+ android:height="20dp"
+ android:viewportWidth="20"
+ android:viewportHeight="20">
+ <group>
+ <clip-path
+ android:pathData="M0,0h20v20h-20z"/>
+ <path
+ android:pathData="M11.5,16.833V15.271C12.694,14.951 13.66,14.306 14.396,13.333C15.132,12.347 15.5,11.236 15.5,10C15.5,8.764 15.125,7.667 14.375,6.708C13.639,5.736 12.681,5.069 11.5,4.708V3.146C13.111,3.493 14.431,4.306 15.458,5.583C16.486,6.861 17,8.326 17,9.979C17,11.632 16.486,13.104 15.458,14.396C14.444,15.674 13.125,16.486 11.5,16.833ZM3,11.979V7.979H6L10,3.979V15.979L6,11.979H3ZM11.5,13.125V6.833C12.125,7.111 12.611,7.535 12.958,8.104C13.319,8.674 13.5,9.299 13.5,9.979C13.5,10.66 13.319,11.285 12.958,11.854C12.611,12.41 12.125,12.833 11.5,13.125Z"
+ android:fillColor="#ECDFE5"/>
+ </group>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_volume_ringer_vibrate.xml b/packages/SystemUI/res/drawable/ic_volume_ringer_vibrate.xml
index b18e0a7aae60..2cbbb0da8de7 100644
--- a/packages/SystemUI/res/drawable/ic_volume_ringer_vibrate.xml
+++ b/packages/SystemUI/res/drawable/ic_volume_ringer_vibrate.xml
@@ -1,27 +1,28 @@
<!--
- Copyright (C) 2017 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
+ ~ 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.
+ -->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:height="19dp"
- android:width="19dp"
- android:viewportHeight="24.0"
- android:viewportWidth="24.0"
- android:tint="?android:attr/textColorPrimary">
-
+ android:width="20dp"
+ android:height="20dp"
+ android:viewportWidth="20"
+ android:viewportHeight="20">
+ <group>
+ <clip-path
+ android:pathData="M0,0h20v20h-20z"/>
<path
- android:fillColor="#FFFFFFFF"
- android:pathData="M1,9h2v6H1V9zM4,17h2V7H4V17zM21,9v6h2V9H21zM18,17h2V7h-2V17zM17,5.5v13c0,0.83 -0.67,1.5 -1.5,1.5h-7C7.67,20 7,19.33 7,18.5v-13C7,4.67 7.67,4 8.5,4h7C16.33,4 17,4.67 17,5.5zM15,6H9v12h6V6z"/>
-
+ android:pathData="M0,12.5V7.5H1.5V12.5H0ZM2.5,14V6H4V14H2.5ZM18.5,12.5V7.5H20V12.5H18.5ZM16,14V6H17.5V14H16ZM6.5,17C6.083,17 5.729,16.854 5.438,16.563C5.146,16.271 5,15.917 5,15.5V4.5C5,4.083 5.146,3.729 5.438,3.438C5.729,3.146 6.083,3 6.5,3H13.5C13.917,3 14.271,3.146 14.563,3.438C14.854,3.729 15,4.083 15,4.5V15.5C15,15.917 14.854,16.271 14.563,16.563C14.271,16.854 13.917,17 13.5,17H6.5Z"
+ android:fillColor="#ECDFE5"/>
+ </group>
</vector>
diff --git a/packages/SystemUI/res/drawable/mobile_network_type_background_updated.xml b/packages/SystemUI/res/drawable/mobile_network_type_background_updated.xml
new file mode 100644
index 000000000000..7b55b3c6fb56
--- /dev/null
+++ b/packages/SystemUI/res/drawable/mobile_network_type_background_updated.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ 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.
+ ~
+ -->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle"
+ >
+ <corners
+ android:topLeftRadius="0dp"
+ android:topRightRadius="@dimen/status_bar_mobile_container_corner_radius_updated"
+ android:bottomRightRadius="0dp"
+ android:bottomLeftRadius="@dimen/status_bar_mobile_container_corner_radius_updated"/>
+ <solid android:color="#FFF" />
+ <padding
+ android:left="2sp"
+ android:right="2sp"/>
+</shape>
diff --git a/packages/SystemUI/res/drawable/stat_sys_ringer_silent.xml b/packages/SystemUI/res/drawable/stat_sys_ringer_silent.xml
index b83f15a1a247..c0cb5ef61ca8 100644
--- a/packages/SystemUI/res/drawable/stat_sys_ringer_silent.xml
+++ b/packages/SystemUI/res/drawable/stat_sys_ringer_silent.xml
@@ -14,4 +14,4 @@ Copyright (C) 2015 The Android Open Source Project
limitations under the License.
-->
<inset xmlns:android="http://schemas.android.com/apk/res/android"
- android:drawable="@drawable/ic_speaker_mute" />
+ android:drawable="@drawable/ic_legacy_speaker_mute" />
diff --git a/packages/SystemUI/res/drawable/stat_sys_ringer_vibrate.xml b/packages/SystemUI/res/drawable/stat_sys_ringer_vibrate.xml
index 21a4c1703d31..4d68d674c259 100644
--- a/packages/SystemUI/res/drawable/stat_sys_ringer_vibrate.xml
+++ b/packages/SystemUI/res/drawable/stat_sys_ringer_vibrate.xml
@@ -16,4 +16,4 @@
<inset xmlns:android="http://schemas.android.com/apk/res/android"
android:insetLeft="2.5dp"
android:insetRight="2.5dp"
- android:drawable="@drawable/ic_volume_ringer_vibrate" /> \ No newline at end of file
+ android:drawable="@drawable/ic_legacy_volume_ringer_vibrate" /> \ No newline at end of file
diff --git a/packages/SystemUI/res/layout/bluetooth_tile_dialog.xml b/packages/SystemUI/res/layout/bluetooth_tile_dialog.xml
index 1f937174dea3..795b7b4aed35 100644
--- a/packages/SystemUI/res/layout/bluetooth_tile_dialog.xml
+++ b/packages/SystemUI/res/layout/bluetooth_tile_dialog.xml
@@ -121,8 +121,8 @@
android:contentDescription="@string/turn_on_bluetooth"
android:switchMinWidth="@dimen/settingslib_switch_track_width"
android:theme="@style/MainSwitch.Settingslib"
- android:thumb="@drawable/settingslib_thumb_selector"
- android:track="@drawable/settingslib_track_selector"
+ android:thumb="@drawable/settingslib_switch_thumb"
+ android:track="@drawable/settingslib_switch_track"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/bluetooth_toggle_title"
app:layout_constraintTop_toTopOf="parent" />
@@ -163,8 +163,8 @@
android:contentDescription="@string/turn_on_bluetooth_auto_tomorrow"
android:switchMinWidth="@dimen/settingslib_switch_track_width"
android:theme="@style/MainSwitch.Settingslib"
- android:thumb="@drawable/settingslib_thumb_selector"
- android:track="@drawable/settingslib_track_selector"
+ android:thumb="@drawable/settingslib_switch_thumb"
+ android:track="@drawable/settingslib_switch_track"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/bluetooth_auto_on_toggle_title"
app:layout_constraintTop_toBottomOf="@+id/bluetooth_toggle" />
diff --git a/packages/SystemUI/res/layout/internet_connectivity_dialog.xml b/packages/SystemUI/res/layout/internet_connectivity_dialog.xml
index 5922a7dcdcf0..67e97010ff22 100644
--- a/packages/SystemUI/res/layout/internet_connectivity_dialog.xml
+++ b/packages/SystemUI/res/layout/internet_connectivity_dialog.xml
@@ -131,7 +131,7 @@
style="@style/InternetDialog.Network">
<FrameLayout
- android:layout_width="24dp"
+ android:layout_width="wrap_content"
android:layout_height="24dp"
android:clickable="false"
android:layout_gravity="center_vertical|start">
diff --git a/packages/SystemUI/res/layout/notification_2025_hybrid.xml b/packages/SystemUI/res/layout/notification_2025_hybrid.xml
index 8fd10fb3ddb8..8c34cd4165e0 100644
--- a/packages/SystemUI/res/layout/notification_2025_hybrid.xml
+++ b/packages/SystemUI/res/layout/notification_2025_hybrid.xml
@@ -29,7 +29,6 @@
android:layout_height="wrap_content"
android:singleLine="true"
android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Notification.Title"
- android:textSize="@*android:dimen/notification_2025_title_text_size"
android:paddingEnd="4dp"
/>
<TextView
diff --git a/packages/SystemUI/res/layout/notification_2025_hybrid_conversation.xml b/packages/SystemUI/res/layout/notification_2025_hybrid_conversation.xml
index 35f2ef901bdd..a338e4c70cfa 100644
--- a/packages/SystemUI/res/layout/notification_2025_hybrid_conversation.xml
+++ b/packages/SystemUI/res/layout/notification_2025_hybrid_conversation.xml
@@ -54,7 +54,6 @@
android:singleLine="true"
android:paddingEnd="4dp"
android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Notification.Title"
- android:textSize="@*android:dimen/notification_2025_title_text_size"
/>
<TextView
diff --git a/packages/SystemUI/res/layout/volume_ringer_drawer_legacy.xml b/packages/SystemUI/res/layout/volume_ringer_drawer_legacy.xml
index 0efbc6d651dc..c7e0f7d337c0 100644
--- a/packages/SystemUI/res/layout/volume_ringer_drawer_legacy.xml
+++ b/packages/SystemUI/res/layout/volume_ringer_drawer_legacy.xml
@@ -67,7 +67,7 @@
android:layout_width="@dimen/volume_ringer_drawer_icon_size"
android:layout_height="@dimen/volume_ringer_drawer_icon_size"
android:layout_gravity="center"
- android:src="@drawable/ic_volume_ringer_vibrate"
+ android:src="@drawable/ic_legacy_volume_ringer_vibrate"
android:tint="?android:attr/textColorPrimary" />
</FrameLayout>
@@ -85,7 +85,7 @@
android:layout_width="@dimen/volume_ringer_drawer_icon_size"
android:layout_height="@dimen/volume_ringer_drawer_icon_size"
android:layout_gravity="center"
- android:src="@drawable/ic_speaker_mute"
+ android:src="@drawable/ic_legacy_speaker_mute"
android:tint="?android:attr/textColorPrimary" />
</FrameLayout>
@@ -103,7 +103,7 @@
android:layout_width="@dimen/volume_ringer_drawer_icon_size"
android:layout_height="@dimen/volume_ringer_drawer_icon_size"
android:layout_gravity="center"
- android:src="@drawable/ic_speaker_on"
+ android:src="@drawable/ic_legacy_speaker_on"
android:tint="?android:attr/textColorPrimary" />
</FrameLayout>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 09aa2241e42b..8d10e393b5ca 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -119,7 +119,7 @@
<!-- Tiles native to System UI. Order should match "quick_settings_tiles_default" -->
<string name="quick_settings_tiles_stock" translatable="false">
- internet,bt,flashlight,dnd,alarm,airplane,controls,wallet,rotation,battery,cast,screenrecord,mictoggle,cameratoggle,location,hotspot,inversion,saver,dark,work,night,reverse,reduce_brightness,qr_code_scanner,onehanded,color_correction,dream,font_scaling,record_issue,hearing_devices,notes
+ internet,bt,flashlight,dnd,alarm,airplane,controls,wallet,rotation,battery,cast,screenrecord,mictoggle,cameratoggle,location,hotspot,inversion,saver,dark,work,night,reverse,reduce_brightness,qr_code_scanner,onehanded,color_correction,dream,font_scaling,record_issue,hearing_devices,notes,desktopeffects
</string>
<!-- The tiles to display in QuickSettings -->
@@ -175,6 +175,9 @@
<!-- Minimum display time for a heads up notification if throttling is enabled, in milliseconds. -->
<integer name="heads_up_notification_minimum_time_with_throttling">500</integer>
+ <!-- Minimum display time for a heads up notification that was shown from a user action (like tapping on a different part of the UI), in milliseconds. -->
+ <integer name="heads_up_notification_minimum_time_for_user_initiated">3000</integer>
+
<!-- Display time for a sticky heads up notification, in milliseconds. -->
<integer name="sticky_heads_up_notification_time">60000</integer>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 2bac87d01bdd..7d0c393f53b5 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -203,16 +203,32 @@
<!-- Size of the view displaying the mobile signal icon in the status bar. This value should
match the core/status_bar_system_icon_size and change to sp unit -->
<dimen name="status_bar_mobile_signal_size">15sp</dimen>
- <dimen name="status_bar_mobile_signal_size_updated">14sp</dimen>
+ <dimen name="status_bar_mobile_signal_size_updated">12sp</dimen>
+
<!-- Size of the view displaying the mobile signal icon in the status bar. This value should
- match the viewport height of mobile signal drawables such as ic_lte_mobiledata -->
+ match the viewport height of mobile signal drawables such as ic_lte_mobiledata
+ Note: can be removed once new_status_bar_icons is rolled out -->
<dimen name="status_bar_mobile_type_size">16sp</dimen>
<!-- Size of the view that contains the network type. Should be equal to
- status_bar_mobile_type_size + 2, to account for 1sp top and bottom padding -->
+ status_bar_mobile_type_size + 2, to account for 1sp top and bottom padding
+ Note: can be removed once new_status_bar_icons is rolled out -->
<dimen name="status_bar_mobile_container_height">18sp</dimen>
<!-- Corner radius for the background of the network type indicator. Should be equal to
- status_bar_mobile_container_height / 2 -->
+ status_bar_mobile_container_height / 2
+ Note: can be removed once new_status_bar_icons is rolled out -->
<dimen name="status_bar_mobile_container_corner_radius">9sp</dimen>
+
+ <!-- Size of the view displaying the mobile signal icon in the status bar. This value should
+ match the viewport height of mobile signal drawables such as ic_lte_mobiledata -->
+ <dimen name="status_bar_mobile_type_size_updated">12sp</dimen>
+ <!-- Size of the view that contains the network type. Should be equal to
+ status_bar_mobile_type_size + 2, to account for 1sp top and bottom padding -->
+ <dimen name="status_bar_mobile_container_height_updated">14sp</dimen>
+ <dimen name="status_bar_mobile_container_margin_end">2sp</dimen>
+ <!-- Corner radius for the background of the network type indicator. Should be equal to
+ status_bar_mobile_container_height / 2 -->
+ <dimen name="status_bar_mobile_container_corner_radius_updated">7sp</dimen>
+
<!-- Size of the view displaying the mobile roam icon in the status bar. This value should
match the viewport size of drawable stat_sys_roaming -->
<dimen name="status_bar_mobile_roam_size">8sp</dimen>
@@ -1806,7 +1822,8 @@
<!-- The activity chip side padding, used with the default phone icon. -->
<dimen name="ongoing_activity_chip_side_padding">12dp</dimen>
<!-- The activity chip side padding, used with an icon that has embedded padding (e.g. if the icon comes from the notification's smallIcon field). If the icon has padding, the chip itself can have less padding. -->
- <dimen name="ongoing_activity_chip_side_padding_for_embedded_padding_icon">6dp</dimen>
+ <dimen name="ongoing_activity_chip_side_padding_for_embedded_padding_icon">2dp</dimen>
+ <dimen name="ongoing_activity_chip_side_padding_for_embedded_padding_icon_legacy">6dp</dimen>
<!-- The icon size, used with the default phone icon. -->
<dimen name="ongoing_activity_chip_icon_size">16dp</dimen>
<!-- The icon size, used with an icon that has embedded padding. (If the icon has embedded padding, we need to make the whole icon larger so the icon itself doesn't look small.) -->
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index e077b41a6f59..1d68b414ced5 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -574,11 +574,12 @@
<!-- Content description of the bluetooth device settings gear icon. [CHAR LIMIT=NONE] -->
<string name="accessibility_bluetooth_device_settings_gear">Click to configure device detail</string>
+ <!-- Content description of the bluetooth device settings gear icon. [CHAR LIMIT=NONE] [BACKUP_MESSAGE_ID=3314916468105272540] -->
+ <string name="accessibility_bluetooth_device_settings_gear_with_name"><xliff:g id="device_name">%s</xliff:g>. Configure device detail</string>
<!-- Content description of the bluetooth device settings see all. [CHAR LIMIT=NONE] -->
- <string name="accessibility_bluetooth_device_settings_see_all">Click to see all devices</string>
+ <string name="accessibility_bluetooth_device_settings_see_all">See all devices</string>
<!-- Content description of the bluetooth device settings pair new device. [CHAR LIMIT=NONE] -->
- <string name="accessibility_bluetooth_device_settings_pair_new_device">Click to pair new device</string>
-
+ <string name="accessibility_bluetooth_device_settings_pair_new_device">Pair new device</string>
<!-- Content description of the battery when battery state is unknown for accessibility (not shown on the screen). [CHAR LIMIT=NONE] -->
<string name="accessibility_battery_unknown">Battery percentage unknown.</string>
@@ -2341,7 +2342,9 @@
<!-- User visible title for the keyboard shortcut that enters split screen with current app on the left [CHAR LIMIT=70] -->
<string name="system_multitasking_lhs">Use split screen with app on the left</string>
<!-- User visible title for the keyboard shortcut that switches from split screen to full screen [CHAR LIMIT=70] -->
- <string name="system_multitasking_full_screen">Switch to full screen</string>
+ <string name="system_multitasking_full_screen">Use full screen</string>
+ <!-- User visible title for the keyboard shortcut that switches to desktop view [CHAR LIMIT=70] -->
+ <string name="system_multitasking_desktop_view">Use desktop view</string>
<!-- User visible title for the keyboard shortcut that switches to app on right or below while using split screen [CHAR LIMIT=70] -->
<string name="system_multitasking_splitscreen_focus_rhs">Switch to app on right or below while using split screen</string>
<!-- User visible title for the keyboard shortcut that switches to app on left or above while using split screen [CHAR LIMIT=70] -->
diff --git a/packages/SystemUI/res/values/tiles_states_strings.xml b/packages/SystemUI/res/values/tiles_states_strings.xml
index d885e00fbe82..faf06f3d39f0 100644
--- a/packages/SystemUI/res/values/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values/tiles_states_strings.xml
@@ -358,4 +358,14 @@
<item>Off</item>
<item>On</item>
</string-array>
+
+ <!-- State names for desktop effects tile: unavailable, off, on.
+ This subtitle is shown when the tile is in that particular state but does not set its own
+ subtitle, so some of these may never appear on screen. They should still be translated as
+ if they could appear. [CHAR LIMIT=32] -->
+ <string-array name="tile_states_desktopeffects">
+ <item>Unavailable</item>
+ <item>Off</item>
+ <item>On</item>
+ </string-array>
</resources> \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardMessageAreaController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardMessageAreaController.java
index f528ec8af134..860a496ef18b 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardMessageAreaController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardMessageAreaController.java
@@ -20,22 +20,16 @@ import android.content.res.ColorStateList;
import android.content.res.Configuration;
import android.hardware.biometrics.BiometricSourceType;
import android.os.SystemClock;
-import android.text.Editable;
-import android.text.TextUtils;
-import android.text.TextWatcher;
import android.util.Log;
import android.util.Pair;
import android.view.View;
import androidx.annotation.Nullable;
-import androidx.annotation.VisibleForTesting;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
import com.android.systemui.util.ViewController;
-import java.lang.ref.WeakReference;
-
import javax.inject.Inject;
/**
@@ -54,39 +48,8 @@ public class KeyguardMessageAreaController<T extends KeyguardMessageArea>
private Pair<BiometricSourceType, Long> mMessageBiometricSource = null;
private static final Long SKIP_SHOWING_FACE_MESSAGE_AFTER_FP_MESSAGE_MS = 3500L;
- /**
- * Delay before speaking an accessibility announcement. Used to prevent
- * lift-to-type from interrupting itself.
- */
- private static final long ANNOUNCEMENT_DELAY = 250;
private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
private final ConfigurationController mConfigurationController;
- private final AnnounceRunnable mAnnounceRunnable;
- private final TextWatcher mTextWatcher = new TextWatcher() {
- @Override
- public void afterTextChanged(Editable editable) {
- CharSequence msg = editable;
- if (!TextUtils.isEmpty(msg)) {
- mView.removeCallbacks(mAnnounceRunnable);
- mAnnounceRunnable.setTextToAnnounce(msg);
- mView.postDelayed(() -> {
- if (msg == mView.getText()) {
- mAnnounceRunnable.run();
- }
- }, ANNOUNCEMENT_DELAY);
- }
- }
-
- @Override
- public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
- /* no-op */
- }
-
- @Override
- public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
- /* no-op */
- }
- };
private KeyguardUpdateMonitorCallback mInfoCallback = new KeyguardUpdateMonitorCallback() {
public void onFinishedGoingToSleep(int why) {
@@ -122,7 +85,6 @@ public class KeyguardMessageAreaController<T extends KeyguardMessageArea>
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
mConfigurationController = configurationController;
- mAnnounceRunnable = new AnnounceRunnable(mView);
}
@Override
@@ -131,14 +93,12 @@ public class KeyguardMessageAreaController<T extends KeyguardMessageArea>
mKeyguardUpdateMonitor.registerCallback(mInfoCallback);
mView.setSelected(mKeyguardUpdateMonitor.isDeviceInteractive());
mView.onThemeChanged();
- mView.addTextChangedListener(mTextWatcher);
}
@Override
protected void onViewDetached() {
mConfigurationController.removeCallback(mConfigurationListener);
mKeyguardUpdateMonitor.removeCallback(mInfoCallback);
- mView.removeTextChangedListener(mTextWatcher);
}
/**
@@ -232,30 +192,4 @@ public class KeyguardMessageAreaController<T extends KeyguardMessageArea>
view, mKeyguardUpdateMonitor, mConfigurationController);
}
}
-
- /**
- * Runnable used to delay accessibility announcements.
- */
- @VisibleForTesting
- public static class AnnounceRunnable implements Runnable {
- private final WeakReference<View> mHost;
- private CharSequence mTextToAnnounce;
-
- AnnounceRunnable(View host) {
- mHost = new WeakReference<>(host);
- }
-
- /** Sets the text to announce. */
- public void setTextToAnnounce(CharSequence textToAnnounce) {
- mTextToAnnounce = textToAnnounce;
- }
-
- @Override
- public void run() {
- final View host = mHost.get();
- if (host != null && host.isVisibleToUser()) {
- host.announceForAccessibility(mTextToAnnounce);
- }
- }
- }
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java
index 5d63c2a92ba8..4a4cb7a232c5 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java
@@ -43,6 +43,8 @@ import com.android.internal.widget.LockPatternView;
import com.android.settingslib.animation.AppearAnimationCreator;
import com.android.settingslib.animation.AppearAnimationUtils;
import com.android.settingslib.animation.DisappearAnimationUtils;
+import com.android.systemui.Flags;
+import com.android.systemui.bouncer.shared.constants.PatternBouncerConstants.ColorId;
import com.android.systemui.res.R;
import com.android.systemui.statusbar.policy.DevicePostureController.DevicePostureInt;
@@ -227,6 +229,18 @@ public class KeyguardPatternView extends KeyguardInputView
super.onFinishInflate();
mLockPatternView = findViewById(R.id.lockPatternView);
+ if (Flags.bouncerUiRevamp2()) {
+ mLockPatternView.setDotColors(mContext.getColor(ColorId.dotColor), mContext.getColor(
+ ColorId.activatedDotColor));
+ mLockPatternView.setColors(mContext.getColor(ColorId.pathColor), 0, 0);
+ mLockPatternView.setDotSizes(
+ getResources().getDimensionPixelSize(R.dimen.keyguard_pattern_dot_size),
+ getResources().getDimensionPixelSize(
+ R.dimen.keyguard_pattern_activated_dot_size));
+ mLockPatternView.setPathWidth(
+ getResources().getDimensionPixelSize(R.dimen.keyguard_pattern_stroke_width));
+ mLockPatternView.setKeepDotActivated(true);
+ }
mEcaView = findViewById(R.id.keyguard_selector_fade_container);
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
index fbe9edfd6680..04d4c2a3cdf9 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
@@ -41,6 +41,7 @@ import androidx.annotation.CallSuper;
import com.android.app.animation.Interpolators;
import com.android.internal.widget.LockscreenCredential;
+import com.android.systemui.Flags;
import com.android.systemui.res.R;
import java.util.ArrayList;
@@ -178,7 +179,15 @@ public abstract class KeyguardPinBasedInputView extends KeyguardAbsKeyInputView
mOkButton = findViewById(R.id.key_enter);
+ if (Flags.bouncerUiRevamp2()) {
+ mOkButton.setImageResource(R.drawable.pin_bouncer_confirm);
+ }
mDeleteButton = findViewById(R.id.delete_button);
+ if (Flags.bouncerUiRevamp2()) {
+ mDeleteButton.setDrawableForTransparentMode(R.drawable.pin_bouncer_delete_filled);
+ mDeleteButton.setDefaultDrawable(R.drawable.pin_bouncer_delete_outline);
+ mDeleteButton.setImageResource(R.drawable.pin_bouncer_delete_outline);
+ }
mDeleteButton.setVisibility(View.VISIBLE);
mButtons[0] = findViewById(R.id.key0);
diff --git a/packages/SystemUI/src/com/android/keyguard/NumPadAnimator.java b/packages/SystemUI/src/com/android/keyguard/NumPadAnimator.java
index 2f74158107f2..69e4fd7c3d53 100644
--- a/packages/SystemUI/src/com/android/keyguard/NumPadAnimator.java
+++ b/packages/SystemUI/src/com/android/keyguard/NumPadAnimator.java
@@ -15,6 +15,7 @@
*/
package com.android.keyguard;
+import android.animation.Animator;
import android.animation.AnimatorSet;
import android.animation.ArgbEvaluator;
import android.animation.ValueAnimator;
@@ -33,6 +34,9 @@ import com.android.systemui.Flags;
import com.android.systemui.bouncer.shared.constants.PinBouncerConstants.Animation;
import com.android.systemui.bouncer.shared.constants.PinBouncerConstants.Color;
+import java.util.ArrayList;
+import java.util.List;
+
/**
* Provides background color and radius animations for key pad buttons.
*/
@@ -141,6 +145,7 @@ class NumPadAnimator {
mExpandAnimator.addUpdateListener(
anim -> mBackground.setCornerRadius((float) anim.getAnimatedValue()));
+ List<Animator> expandAnimators = new ArrayList<>();
ValueAnimator expandBackgroundColorAnimator = ValueAnimator.ofObject(new ArgbEvaluator(),
mNormalBackgroundColor, mPressedBackgroundColor);
expandBackgroundColorAnimator.setDuration(Animation.expansionColorDuration);
@@ -162,10 +167,27 @@ class NumPadAnimator {
}
});
+ expandAnimators.add(mExpandAnimator);
+ expandAnimators.add(expandBackgroundColorAnimator);
+ expandAnimators.add(expandTextColorAnimator);
+
+ if (Flags.bouncerUiRevamp2()) {
+ ValueAnimator expandTextScaleAnimator = ValueAnimator.ofFloat(
+ Animation.normalTextScaleX, Animation.pressedTextScaleX);
+ expandTextScaleAnimator.setInterpolator(Animation.expansionInterpolator);
+ expandTextScaleAnimator.setDuration(Animation.expansionDuration);
+ expandTextScaleAnimator.addUpdateListener(valueAnimator -> {
+ if (mDigitTextView != null) {
+ mDigitTextView.setTextScaleX((Float) valueAnimator.getAnimatedValue());
+ }
+ });
+ expandAnimators.add(expandTextScaleAnimator);
+ }
+
mExpandAnimatorSet = new AnimatorSet();
- mExpandAnimatorSet.playTogether(mExpandAnimator,
- expandBackgroundColorAnimator, expandTextColorAnimator);
+ mExpandAnimatorSet.playTogether(expandAnimators);
+ List<Animator> contractAnimators = new ArrayList<>();
mContractAnimator = ValueAnimator.ofFloat(1f, 0f);
mContractAnimator.setStartDelay(Animation.contractionStartDelay);
mContractAnimator.setDuration(Animation.contractionDuration);
@@ -195,9 +217,24 @@ class NumPadAnimator {
}
});
+ contractAnimators.add(mContractAnimator);
+ contractAnimators.add(contractBackgroundColorAnimator);
+ contractAnimators.add(contractTextColorAnimator);
+
+ if (Flags.bouncerUiRevamp2()) {
+ ValueAnimator contractTextScaleAnimator = ValueAnimator.ofFloat(
+ Animation.pressedTextScaleX, Animation.normalTextScaleX);
+ contractTextScaleAnimator.setInterpolator(Animation.contractionRadiusInterpolator);
+ contractTextScaleAnimator.setDuration(Animation.contractionDuration);
+ contractTextScaleAnimator.addUpdateListener(valueAnimator -> {
+ if (mDigitTextView != null) {
+ mDigitTextView.setTextScaleX((Float) valueAnimator.getAnimatedValue());
+ }
+ });
+ contractAnimators.add(contractTextScaleAnimator);
+ }
mContractAnimatorSet = new AnimatorSet();
- mContractAnimatorSet.playTogether(mContractAnimator,
- contractBackgroundColorAnimator, contractTextColorAnimator);
+ mContractAnimatorSet.playTogether(contractAnimators);
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/NumPadButton.java b/packages/SystemUI/src/com/android/keyguard/NumPadButton.java
index 0ff93236a856..584ebb50520a 100644
--- a/packages/SystemUI/src/com/android/keyguard/NumPadButton.java
+++ b/packages/SystemUI/src/com/android/keyguard/NumPadButton.java
@@ -25,6 +25,7 @@ import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.accessibility.AccessibilityNodeInfo;
+import androidx.annotation.DrawableRes;
import androidx.annotation.Nullable;
import com.android.systemui.Flags;
@@ -42,6 +43,12 @@ public class NumPadButton extends AlphaOptimizedImageButton implements NumPadAni
private int mStyleAttr;
private boolean mIsTransparentMode;
+ @DrawableRes
+ private int mDrawableForTransparentMode = 0;
+
+ @DrawableRes
+ private int mDefaultDrawable = 0;
+
public NumPadButton(Context context, AttributeSet attrs) {
super(context, attrs);
mStyleAttr = attrs.getStyleAttribute();
@@ -123,8 +130,14 @@ public class NumPadButton extends AlphaOptimizedImageButton implements NumPadAni
mIsTransparentMode = isTransparentMode;
if (isTransparentMode) {
+ if (mDrawableForTransparentMode != 0) {
+ setImageResource(mDrawableForTransparentMode);
+ }
setBackgroundColor(getResources().getColor(android.R.color.transparent));
} else {
+ if (mDefaultDrawable != 0) {
+ setImageResource(mDefaultDrawable);
+ }
Drawable bgDrawable = getContext().getDrawable(R.drawable.num_pad_key_background);
if (Flags.bouncerUiRevamp2() && bgDrawable != null) {
bgDrawable.setTint(Color.actionBg);
@@ -154,4 +167,19 @@ public class NumPadButton extends AlphaOptimizedImageButton implements NumPadAni
super.onInitializeAccessibilityNodeInfo(info);
info.setTextEntryKey(true);
}
+
+ /**
+ * Drawable to use when transparent mode is enabled
+ */
+ public void setDrawableForTransparentMode(@DrawableRes int drawableResId) {
+ mDrawableForTransparentMode = drawableResId;
+ }
+
+ /**
+ * Drawable to use when transparent mode is not enabled.
+ */
+ public void setDefaultDrawable(@DrawableRes int drawableResId) {
+ mDefaultDrawable = drawableResId;
+ setImageResource(mDefaultDrawable);
+ }
}
diff --git a/packages/SystemUI/src/com/android/keyguard/NumPadKey.java b/packages/SystemUI/src/com/android/keyguard/NumPadKey.java
index b152ff348e22..56aadc342424 100644
--- a/packages/SystemUI/src/com/android/keyguard/NumPadKey.java
+++ b/packages/SystemUI/src/com/android/keyguard/NumPadKey.java
@@ -148,7 +148,7 @@ public class NumPadKey extends ViewGroup implements NumPadAnimationListener {
if (bouncerUiRevamp2()) {
mDigitText.setTypeface(
- Typeface.create(FontStyles.GSF_LABEL_LARGE_EMPHASIZED, Typeface.NORMAL));
+ Typeface.create(FontStyles.GSF_LABEL_SMALL_EMPHASIZED, Typeface.NORMAL));
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java
index 438184d4d2d6..22ecb0af8c18 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java
@@ -30,7 +30,6 @@ import android.content.pm.ResolveInfo;
import android.content.res.Resources;
import android.media.AudioManager;
import android.os.Bundle;
-import android.os.Handler;
import android.provider.Settings;
import android.util.Log;
import android.view.LayoutInflater;
@@ -49,6 +48,7 @@ import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
+import androidx.annotation.WorkerThread;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
@@ -57,7 +57,6 @@ import com.android.settingslib.bluetooth.BluetoothCallback;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
-import com.android.settingslib.utils.ThreadUtils;
import com.android.systemui.accessibility.hearingaid.HearingDevicesListAdapter.HearingDeviceItemCallback;
import com.android.systemui.animation.DialogTransitionAnimator;
import com.android.systemui.bluetooth.qsdialog.ActiveHearingDeviceItemFactory;
@@ -67,6 +66,7 @@ import com.android.systemui.bluetooth.qsdialog.DeviceItem;
import com.android.systemui.bluetooth.qsdialog.DeviceItemFactory;
import com.android.systemui.bluetooth.qsdialog.DeviceItemType;
import com.android.systemui.bluetooth.qsdialog.SavedHearingDeviceItemFactory;
+import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.res.R;
@@ -79,6 +79,7 @@ import dagger.assisted.AssistedInject;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
+import java.util.concurrent.Executor;
import java.util.stream.Collectors;
/**
@@ -101,7 +102,8 @@ public class HearingDevicesDialogDelegate implements SystemUIDialog.Delegate,
private final DialogTransitionAnimator mDialogTransitionAnimator;
private final ActivityStarter mActivityStarter;
private final LocalBluetoothManager mLocalBluetoothManager;
- private final Handler mMainHandler;
+ private final Executor mMainExecutor;
+ private final Executor mBgExecutor;
private final AudioManager mAudioManager;
private final LocalBluetoothProfileManager mProfileManager;
private final HearingDevicesUiEventLogger mUiEventLogger;
@@ -109,8 +111,6 @@ public class HearingDevicesDialogDelegate implements SystemUIDialog.Delegate,
private final int mLaunchSourceId;
private SystemUIDialog mDialog;
-
- private List<DeviceItem> mHearingDeviceItemList;
private HearingDevicesListAdapter mDeviceListAdapter;
private View mPresetLayout;
@@ -122,14 +122,14 @@ public class HearingDevicesDialogDelegate implements SystemUIDialog.Delegate,
@Override
public void onPresetInfoUpdated(List<BluetoothHapPresetInfo> presetInfos,
int activePresetIndex) {
- mMainHandler.post(
+ mMainExecutor.execute(
() -> refreshPresetUi(presetInfos, activePresetIndex));
}
@Override
public void onPresetCommandFailed(int reason) {
mPresetController.refreshPresetInfo();
- mMainHandler.post(() -> {
+ mMainExecutor.execute(() -> {
showErrorToast(R.string.hearing_devices_presets_error);
});
}
@@ -166,7 +166,8 @@ public class HearingDevicesDialogDelegate implements SystemUIDialog.Delegate,
ActivityStarter activityStarter,
DialogTransitionAnimator dialogTransitionAnimator,
@Nullable LocalBluetoothManager localBluetoothManager,
- @Main Handler handler,
+ @Main Executor mainExecutor,
+ @Background Executor bgExecutor,
AudioManager audioManager,
HearingDevicesUiEventLogger uiEventLogger) {
mShowPairNewDevice = showPairNewDevice;
@@ -174,7 +175,8 @@ public class HearingDevicesDialogDelegate implements SystemUIDialog.Delegate,
mActivityStarter = activityStarter;
mDialogTransitionAnimator = dialogTransitionAnimator;
mLocalBluetoothManager = localBluetoothManager;
- mMainHandler = handler;
+ mMainExecutor = mainExecutor;
+ mBgExecutor = bgExecutor;
mAudioManager = audioManager;
mProfileManager = localBluetoothManager.getProfileManager();
mUiEventLogger = uiEventLogger;
@@ -227,9 +229,10 @@ public class HearingDevicesDialogDelegate implements SystemUIDialog.Delegate,
@Override
public void onActiveDeviceChanged(@Nullable CachedBluetoothDevice activeDevice,
int bluetoothProfile) {
- refreshDeviceUi();
- mMainHandler.post(() -> {
- CachedBluetoothDevice device = getActiveHearingDevice();
+ List<DeviceItem> hearingDeviceItemList = getHearingDeviceItemList();
+ refreshDeviceUi(hearingDeviceItemList);
+ mMainExecutor.execute(() -> {
+ CachedBluetoothDevice device = getActiveHearingDevice(hearingDeviceItemList);
if (mPresetController != null) {
mPresetController.setDevice(device);
mPresetLayout.setVisibility(
@@ -244,13 +247,15 @@ public class HearingDevicesDialogDelegate implements SystemUIDialog.Delegate,
@Override
public void onProfileConnectionStateChanged(@NonNull CachedBluetoothDevice cachedDevice,
int state, int bluetoothProfile) {
- refreshDeviceUi();
+ List<DeviceItem> hearingDeviceItemList = getHearingDeviceItemList();
+ refreshDeviceUi(hearingDeviceItemList);
}
@Override
public void onAclConnectionStateChanged(@NonNull CachedBluetoothDevice cachedDevice,
int state) {
- refreshDeviceUi();
+ List<DeviceItem> hearingDeviceItemList = getHearingDeviceItemList();
+ refreshDeviceUi(hearingDeviceItemList);
}
@Override
@@ -280,18 +285,25 @@ public class HearingDevicesDialogDelegate implements SystemUIDialog.Delegate,
mUiEventLogger.log(HearingDevicesUiEvent.HEARING_DEVICES_DIALOG_SHOW, mLaunchSourceId);
- setupDeviceListView(dialog);
- setupPairNewDeviceButton(dialog);
- setupPresetSpinner(dialog);
- if (com.android.settingslib.flags.Flags.hearingDevicesAmbientVolumeControl()) {
- setupAmbientControls();
- }
- setupRelatedToolsView(dialog);
+ mBgExecutor.execute(() -> {
+ List<DeviceItem> hearingDeviceItemList = getHearingDeviceItemList();
+ CachedBluetoothDevice activeHearingDevice = getActiveHearingDevice(
+ hearingDeviceItemList);
+ mMainExecutor.execute(() -> {
+ setupDeviceListView(dialog, hearingDeviceItemList);
+ setupPairNewDeviceButton(dialog);
+ setupPresetSpinner(dialog, activeHearingDevice);
+ if (com.android.settingslib.flags.Flags.hearingDevicesAmbientVolumeControl()) {
+ setupAmbientControls(activeHearingDevice);
+ }
+ setupRelatedToolsView(dialog);
+ });
+ });
}
@Override
public void onStart(@NonNull SystemUIDialog dialog) {
- ThreadUtils.postOnBackgroundThread(() -> {
+ mBgExecutor.execute(() -> {
if (mLocalBluetoothManager != null) {
mLocalBluetoothManager.getEventManager().registerCallback(this);
}
@@ -306,7 +318,7 @@ public class HearingDevicesDialogDelegate implements SystemUIDialog.Delegate,
@Override
public void onStop(@NonNull SystemUIDialog dialog) {
- ThreadUtils.postOnBackgroundThread(() -> {
+ mBgExecutor.execute(() -> {
if (mLocalBluetoothManager != null) {
mLocalBluetoothManager.getEventManager().unregisterCallback(this);
}
@@ -319,17 +331,18 @@ public class HearingDevicesDialogDelegate implements SystemUIDialog.Delegate,
});
}
- private void setupDeviceListView(SystemUIDialog dialog) {
+ private void setupDeviceListView(SystemUIDialog dialog,
+ List<DeviceItem> hearingDeviceItemList) {
final RecyclerView deviceList = dialog.requireViewById(R.id.device_list);
deviceList.setLayoutManager(new LinearLayoutManager(dialog.getContext()));
- mHearingDeviceItemList = getHearingDeviceItemList();
- mDeviceListAdapter = new HearingDevicesListAdapter(mHearingDeviceItemList, this);
+ mDeviceListAdapter = new HearingDevicesListAdapter(hearingDeviceItemList, this);
deviceList.setAdapter(mDeviceListAdapter);
}
- private void setupPresetSpinner(SystemUIDialog dialog) {
+ private void setupPresetSpinner(SystemUIDialog dialog,
+ CachedBluetoothDevice activeHearingDevice) {
mPresetController = new HearingDevicesPresetsController(mProfileManager, mPresetCallback);
- mPresetController.setDevice(getActiveHearingDevice());
+ mPresetController.setDevice(activeHearingDevice);
mPresetSpinner = dialog.requireViewById(R.id.preset_spinner);
mPresetInfoAdapter = new HearingDevicesSpinnerAdapter(dialog.getContext());
@@ -367,12 +380,12 @@ public class HearingDevicesDialogDelegate implements SystemUIDialog.Delegate,
mPresetLayout.setVisibility(mPresetController.isPresetControlAvailable() ? VISIBLE : GONE);
}
- private void setupAmbientControls() {
+ private void setupAmbientControls(CachedBluetoothDevice activeHearingDevice) {
final AmbientVolumeLayout ambientLayout = mDialog.requireViewById(R.id.ambient_layout);
mAmbientController = new AmbientVolumeUiController(
mDialog.getContext(), mLocalBluetoothManager, ambientLayout);
mAmbientController.setShowUiWhenLocalDataExist(false);
- mAmbientController.loadDevice(getActiveHearingDevice());
+ mAmbientController.loadDevice(activeHearingDevice);
}
private void setupPairNewDeviceButton(SystemUIDialog dialog) {
@@ -429,10 +442,11 @@ public class HearingDevicesDialogDelegate implements SystemUIDialog.Delegate,
}
}
- private void refreshDeviceUi() {
- mHearingDeviceItemList = getHearingDeviceItemList();
- mMainHandler.post(() -> {
- mDeviceListAdapter.refreshDeviceItemList(mHearingDeviceItemList);
+ private void refreshDeviceUi(List<DeviceItem> hearingDeviceItemList) {
+ mMainExecutor.execute(() -> {
+ if (mDeviceListAdapter != null) {
+ mDeviceListAdapter.refreshDeviceItemList(hearingDeviceItemList);
+ }
});
}
@@ -463,21 +477,27 @@ public class HearingDevicesDialogDelegate implements SystemUIDialog.Delegate,
}
@Nullable
- private CachedBluetoothDevice getActiveHearingDevice() {
- return mHearingDeviceItemList.stream()
+ private static CachedBluetoothDevice getActiveHearingDevice(
+ List<DeviceItem> hearingDeviceItemList) {
+ return hearingDeviceItemList.stream()
.filter(item -> item.getType() == DeviceItemType.ACTIVE_MEDIA_BLUETOOTH_DEVICE)
.map(DeviceItem::getCachedBluetoothDevice)
.findFirst()
.orElse(null);
}
+ @WorkerThread
private DeviceItem createHearingDeviceItem(CachedBluetoothDevice cachedDevice) {
final Context context = mDialog.getContext();
if (cachedDevice == null) {
return null;
}
+ int mode = mAudioManager.getMode();
+ boolean isOngoingCall = mode == AudioManager.MODE_RINGTONE
+ || mode == AudioManager.MODE_IN_CALL
+ || mode == AudioManager.MODE_IN_COMMUNICATION;
for (DeviceItemFactory itemFactory : mHearingDeviceItemFactoryList) {
- if (itemFactory.isFilterMatched(context, cachedDevice, mAudioManager)) {
+ if (itemFactory.isFilterMatched(context, cachedDevice, isOngoingCall)) {
return itemFactory.create(context, cachedDevice);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/activity/data/model/AppVisibilityModel.kt b/packages/SystemUI/src/com/android/systemui/activity/data/model/AppVisibilityModel.kt
new file mode 100644
index 000000000000..2d21d655561f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/activity/data/model/AppVisibilityModel.kt
@@ -0,0 +1,29 @@
+/*
+ * 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.activity.data.model
+
+/** Describes an app's previous and current visibility to the user. */
+data class AppVisibilityModel(
+ /** True if the app is currently visible to the user and false otherwise. */
+ val isAppCurrentlyVisible: Boolean = false,
+ /**
+ * The last time this app became visible to the user, in
+ * [com.android.systemui.util.time.SystemClock.currentTimeMillis] units. Null if the app hasn't
+ * become visible since the flow started collection.
+ */
+ val lastAppVisibleTime: Long? = null,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/activity/data/repository/ActivityManagerRepository.kt b/packages/SystemUI/src/com/android/systemui/activity/data/repository/ActivityManagerRepository.kt
index 94614b70beda..11831eabf19d 100644
--- a/packages/SystemUI/src/com/android/systemui/activity/data/repository/ActivityManagerRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/activity/data/repository/ActivityManagerRepository.kt
@@ -18,9 +18,11 @@ package com.android.systemui.activity.data.repository
import android.app.ActivityManager
import android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND
+import com.android.systemui.activity.data.model.AppVisibilityModel
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.log.core.Logger
+import com.android.systemui.util.time.SystemClock
import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
import javax.inject.Inject
import kotlin.coroutines.CoroutineContext
@@ -29,9 +31,23 @@ import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.flow.scan
/** Repository for interfacing with [ActivityManager]. */
interface ActivityManagerRepository {
+
+ /**
+ * Given a UID, creates a flow that emits details about when the process with the given UID was
+ * and is visible to the user.
+ *
+ * @param identifyingLogTag a tag identifying who created this flow, used for logging.
+ */
+ fun createAppVisibilityFlow(
+ creationUid: Int,
+ logger: Logger,
+ identifyingLogTag: String,
+ ): Flow<AppVisibilityModel>
+
/**
* Given a UID, creates a flow that emits true when the process with the given UID is visible to
* the user and false otherwise.
@@ -50,8 +66,38 @@ class ActivityManagerRepositoryImpl
@Inject
constructor(
@Background private val backgroundContext: CoroutineContext,
+ private val systemClock: SystemClock,
private val activityManager: ActivityManager,
) : ActivityManagerRepository {
+
+ override fun createAppVisibilityFlow(
+ creationUid: Int,
+ logger: Logger,
+ identifyingLogTag: String,
+ ): Flow<AppVisibilityModel> {
+ return createIsAppVisibleFlow(creationUid, logger, identifyingLogTag)
+ .distinctUntilChanged()
+ .scan(initial = AppVisibilityModel()) {
+ oldState: AppVisibilityModel,
+ newIsVisible: Boolean ->
+ if (newIsVisible) {
+ val lastAppVisibleTime = systemClock.currentTimeMillis()
+ logger.d({ "$str1: Setting lastAppVisibleTime=$long1" }) {
+ str1 = identifyingLogTag
+ long1 = lastAppVisibleTime
+ }
+ AppVisibilityModel(
+ isAppCurrentlyVisible = true,
+ lastAppVisibleTime = lastAppVisibleTime,
+ )
+ } else {
+ // Reset the current status while maintaining the lastAppVisibleTime
+ oldState.copy(isAppCurrentlyVisible = false)
+ }
+ }
+ .distinctUntilChanged()
+ }
+
override fun createIsAppVisibleFlow(
creationUid: Int,
logger: Logger,
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothDetailsContentManager.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothDetailsContentManager.kt
index eebcf0b0f0c1..576acd2e5304 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothDetailsContentManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothDetailsContentManager.kt
@@ -383,6 +383,11 @@ constructor(
actionIcon.setImageResource(item.actionIconRes)
actionIcon.drawable?.setTint(tintColor)
+ actionIconView.contentDescription =
+ resources.getString(
+ R.string.accessibility_bluetooth_device_settings_gear_with_name,
+ item.deviceName,
+ )
divider.setBackgroundColor(tintColor)
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothDetailsContentViewModel.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothDetailsContentViewModel.kt
index ff2d9efa1b58..1c9cf8d14f74 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothDetailsContentViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothDetailsContentViewModel.kt
@@ -31,6 +31,7 @@ import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.internal.jank.InteractionJankMonitor
import com.android.internal.logging.UiEventLogger
import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast
+import com.android.settingslib.volume.domain.interactor.AudioModeInteractor
import com.android.systemui.Prefs
import com.android.systemui.animation.DialogCuj
import com.android.systemui.animation.DialogTransitionAnimator
@@ -71,6 +72,7 @@ constructor(
private val bluetoothStateInteractor: BluetoothStateInteractor,
private val bluetoothAutoOnInteractor: BluetoothAutoOnInteractor,
private val audioSharingInteractor: AudioSharingInteractor,
+ private val audioModeInteractor: AudioModeInteractor,
private val audioSharingButtonViewModelFactory: AudioSharingButtonViewModel.Factory,
private val bluetoothDeviceMetadataInteractor: BluetoothDeviceMetadataInteractor,
private val dialogTransitionAnimator: DialogTransitionAnimator,
@@ -167,6 +169,7 @@ constructor(
// the device item list and animate the progress bar.
merge(
deviceItemInteractor.deviceItemUpdateRequest,
+ audioModeInteractor.isOngoingCall,
bluetoothDeviceMetadataInteractor.metadataUpdate,
if (
audioSharingInteractor.audioSharingAvailable() &&
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactory.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactory.kt
index 095e6e741584..bfbc27d3a086 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactory.kt
@@ -18,7 +18,6 @@ package com.android.systemui.bluetooth.qsdialog
import android.bluetooth.BluetoothDevice
import android.content.Context
-import android.media.AudioManager
import com.android.settingslib.bluetooth.BluetoothUtils
import com.android.settingslib.bluetooth.CachedBluetoothDevice
import com.android.settingslib.bluetooth.LocalBluetoothManager
@@ -43,7 +42,7 @@ abstract class DeviceItemFactory {
abstract fun isFilterMatched(
context: Context,
cachedDevice: CachedBluetoothDevice,
- audioManager: AudioManager,
+ isOngoingCall: Boolean,
audioSharingAvailable: Boolean,
): Boolean
@@ -51,8 +50,8 @@ abstract class DeviceItemFactory {
fun isFilterMatched(
context: Context,
cachedDevice: CachedBluetoothDevice,
- audioManager: AudioManager,
- ): Boolean = isFilterMatched(context, cachedDevice, audioManager, false)
+ isOngoingCall: Boolean,
+ ): Boolean = isFilterMatched(context, cachedDevice, isOngoingCall, false)
abstract fun create(context: Context, cachedDevice: CachedBluetoothDevice): DeviceItem
@@ -88,11 +87,11 @@ internal open class ActiveMediaDeviceItemFactory : DeviceItemFactory() {
override fun isFilterMatched(
context: Context,
cachedDevice: CachedBluetoothDevice,
- audioManager: AudioManager,
+ isOngoingCall: Boolean,
audioSharingAvailable: Boolean,
): Boolean {
return BluetoothUtils.isActiveMediaDevice(cachedDevice) &&
- BluetoothUtils.isAvailableMediaBluetoothDevice(cachedDevice, audioManager)
+ BluetoothUtils.isAvailableMediaBluetoothDevice(cachedDevice, isOngoingCall)
}
override fun create(context: Context, cachedDevice: CachedBluetoothDevice): DeviceItem {
@@ -113,10 +112,11 @@ internal class AudioSharingMediaDeviceItemFactory(
override fun isFilterMatched(
context: Context,
cachedDevice: CachedBluetoothDevice,
- audioManager: AudioManager,
+ isOngoingCall: Boolean,
audioSharingAvailable: Boolean,
): Boolean {
return audioSharingAvailable &&
+ !isOngoingCall &&
BluetoothUtils.hasConnectedBroadcastSource(cachedDevice, localBluetoothManager)
}
@@ -140,11 +140,12 @@ internal class AvailableAudioSharingMediaDeviceItemFactory(
override fun isFilterMatched(
context: Context,
cachedDevice: CachedBluetoothDevice,
- audioManager: AudioManager,
+ isOngoingCall: Boolean,
audioSharingAvailable: Boolean,
): Boolean {
return audioSharingAvailable &&
- super.isFilterMatched(context, cachedDevice, audioManager, true) &&
+ !isOngoingCall &&
+ super.isFilterMatched(context, cachedDevice, false, true) &&
BluetoothUtils.isAvailableAudioSharingMediaBluetoothDevice(
cachedDevice,
localBluetoothManager,
@@ -170,7 +171,7 @@ internal class ActiveHearingDeviceItemFactory : ActiveMediaDeviceItemFactory() {
override fun isFilterMatched(
context: Context,
cachedDevice: CachedBluetoothDevice,
- audioManager: AudioManager,
+ isOngoingCall: Boolean,
audioSharingAvailable: Boolean,
): Boolean {
return BluetoothUtils.isActiveMediaDevice(cachedDevice) &&
@@ -182,11 +183,11 @@ open class AvailableMediaDeviceItemFactory : DeviceItemFactory() {
override fun isFilterMatched(
context: Context,
cachedDevice: CachedBluetoothDevice,
- audioManager: AudioManager,
+ isOngoingCall: Boolean,
audioSharingAvailable: Boolean,
): Boolean {
return !BluetoothUtils.isActiveMediaDevice(cachedDevice) &&
- BluetoothUtils.isAvailableMediaBluetoothDevice(cachedDevice, audioManager)
+ BluetoothUtils.isAvailableMediaBluetoothDevice(cachedDevice, isOngoingCall)
}
override fun create(context: Context, cachedDevice: CachedBluetoothDevice): DeviceItem {
@@ -206,7 +207,7 @@ internal class AvailableHearingDeviceItemFactory : AvailableMediaDeviceItemFacto
override fun isFilterMatched(
context: Context,
cachedDevice: CachedBluetoothDevice,
- audioManager: AudioManager,
+ isOngoingCall: Boolean,
audioSharingAvailable: Boolean,
): Boolean {
return !BluetoothUtils.isActiveMediaDevice(cachedDevice) &&
@@ -218,14 +219,14 @@ internal class ConnectedDeviceItemFactory : DeviceItemFactory() {
override fun isFilterMatched(
context: Context,
cachedDevice: CachedBluetoothDevice,
- audioManager: AudioManager,
+ isOngoingCall: Boolean,
audioSharingAvailable: Boolean,
): Boolean {
return if (Flags.enableHideExclusivelyManagedBluetoothDevice()) {
!BluetoothUtils.isExclusivelyManagedBluetoothDevice(context, cachedDevice.device) &&
- BluetoothUtils.isConnectedBluetoothDevice(cachedDevice, audioManager)
+ BluetoothUtils.isConnectedBluetoothDevice(cachedDevice, isOngoingCall)
} else {
- BluetoothUtils.isConnectedBluetoothDevice(cachedDevice, audioManager)
+ BluetoothUtils.isConnectedBluetoothDevice(cachedDevice, isOngoingCall)
}
}
@@ -246,7 +247,7 @@ internal open class SavedDeviceItemFactory : DeviceItemFactory() {
override fun isFilterMatched(
context: Context,
cachedDevice: CachedBluetoothDevice,
- audioManager: AudioManager,
+ isOngoingCall: Boolean,
audioSharingAvailable: Boolean,
): Boolean {
return if (Flags.enableHideExclusivelyManagedBluetoothDevice()) {
@@ -275,7 +276,7 @@ internal class SavedHearingDeviceItemFactory : SavedDeviceItemFactory() {
override fun isFilterMatched(
context: Context,
cachedDevice: CachedBluetoothDevice,
- audioManager: AudioManager,
+ isOngoingCall: Boolean,
audioSharingAvailable: Boolean,
): Boolean {
return if (Flags.enableHideExclusivelyManagedBluetoothDevice()) {
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemInteractor.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemInteractor.kt
index 1e0ba8e75714..b606c19b3503 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemInteractor.kt
@@ -19,10 +19,10 @@ package com.android.systemui.bluetooth.qsdialog
import android.bluetooth.BluetoothAdapter
import android.bluetooth.BluetoothDevice
import android.content.Context
-import android.media.AudioManager
import com.android.settingslib.bluetooth.BluetoothCallback
import com.android.settingslib.bluetooth.CachedBluetoothDevice
import com.android.settingslib.bluetooth.LocalBluetoothManager
+import com.android.settingslib.volume.domain.interactor.AudioModeInteractor
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
import com.android.systemui.dagger.SysUISingleton
@@ -39,6 +39,7 @@ import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.shareIn
import kotlinx.coroutines.isActive
@@ -51,7 +52,6 @@ class DeviceItemInteractor
constructor(
private val bluetoothTileDialogRepository: BluetoothTileDialogRepository,
private val audioSharingInteractor: AudioSharingInteractor,
- private val audioManager: AudioManager,
private val bluetoothAdapter: BluetoothAdapter? = BluetoothAdapter.getDefaultAdapter(),
private val localBluetoothManager: LocalBluetoothManager?,
private val systemClock: SystemClock,
@@ -60,6 +60,7 @@ constructor(
private val deviceItemDisplayPriority: List<@JvmSuppressWildcards DeviceItemType>,
@Application private val coroutineScope: CoroutineScope,
@Background private val backgroundDispatcher: CoroutineDispatcher,
+ private val audioModeInteractor: AudioModeInteractor,
) {
private val mutableDeviceItemUpdate: MutableSharedFlow<List<DeviceItem>> =
@@ -118,8 +119,12 @@ constructor(
internal suspend fun updateDeviceItems(context: Context, trigger: DeviceFetchTrigger) {
withContext(backgroundDispatcher) {
+ if (!isActive) {
+ return@withContext
+ }
val start = systemClock.elapsedRealtime()
val audioSharingAvailable = audioSharingInteractor.audioSharingAvailable()
+ val isOngoingCall = audioModeInteractor.isOngoingCall.first()
val deviceItems =
bluetoothTileDialogRepository.cachedDevices
.mapNotNull { cachedDevice ->
@@ -128,7 +133,7 @@ constructor(
it.isFilterMatched(
context,
cachedDevice,
- audioManager,
+ isOngoingCall,
audioSharingAvailable,
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/shared/constants/KeyguardBouncerConstants.kt b/packages/SystemUI/src/com/android/systemui/bouncer/shared/constants/KeyguardBouncerConstants.kt
index e949dc6a1935..3ef50f68cba4 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/shared/constants/KeyguardBouncerConstants.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/shared/constants/KeyguardBouncerConstants.kt
@@ -87,6 +87,14 @@ private fun <T> c(old: T, new: T): T {
}
}
+object PatternBouncerConstants {
+ object ColorId {
+ @JvmField val dotColor = colors.materialColorOnSurfaceVariant
+ @JvmField val activatedDotColor = colors.materialColorOnPrimary
+ @JvmField val pathColor = colors.materialColorPrimary
+ }
+}
+
object PinBouncerConstants {
@JvmField
val pinShapes = c(old = R.array.bouncer_pin_shapes, new = R.array.updated_bouncer_pin_shapes)
@@ -126,5 +134,8 @@ object PinBouncerConstants {
@JvmField
val contractionColorInterpolator =
c(old = Interpolators.LINEAR, new = Interpolators.STANDARD)!!
+
+ const val pressedTextScaleX = 1.35f
+ const val normalTextScaleX = 1.0f
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/KeyguardBouncerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/KeyguardBouncerViewBinder.kt
index 434a9ce58c3b..7d8945a5b4a7 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/KeyguardBouncerViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/KeyguardBouncerViewBinder.kt
@@ -191,7 +191,6 @@ object KeyguardBouncerViewBinder {
.filter { it == EXPANSION_VISIBLE }
.collect {
securityContainerController.onResume(KeyguardSecurityView.SCREEN_ON)
- view.announceForAccessibility(securityContainerController.title)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSceneRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSceneRepository.kt
index 0a9bd4214a12..bf4445ba18db 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSceneRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSceneRepository.kt
@@ -151,5 +151,7 @@ constructor(
override fun instantlyShowOverlay(overlay: OverlayKey) = Unit
override fun instantlyHideOverlay(overlay: OverlayKey) = Unit
+
+ override fun freezeAndAnimateToCurrentState() = Unit
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalTransitionKeys.kt b/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalTransitionKeys.kt
index ca49de3b1510..a84c45732169 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalTransitionKeys.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalTransitionKeys.kt
@@ -31,4 +31,6 @@ object CommunalTransitionKeys {
val ToEditMode = TransitionKey("ToEditMode")
/** Transition to the glanceable hub after exiting edit mode */
val FromEditMode = TransitionKey("FromEditMode")
+ /** Swipes the glanceable hub in/out of view */
+ val Swipe = TransitionKey("Swipe")
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
index 2169881d11c5..cce1ae1a2947 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
@@ -359,7 +359,13 @@ constructor(
/** See [CommunalSettingsInteractor.isV2FlagEnabled] */
fun v2FlagEnabled(): Boolean = communalSettingsInteractor.isV2FlagEnabled()
- fun swipeToHubEnabled(): Boolean = swipeToHub
+ val swipeToHubEnabled: StateFlow<Boolean> by lazy {
+ if (v2FlagEnabled()) {
+ communalInteractor.shouldShowCommunal
+ } else {
+ MutableStateFlow(swipeToHub)
+ }
+ }
companion object {
const val POPUP_AUTO_HIDE_TIMEOUT_MS = 12000L
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
index 8bff090959ab..3c68e3a09f02 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
@@ -74,7 +74,6 @@ import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.dagger.CentralSurfacesModule;
import com.android.systemui.statusbar.dagger.StartCentralSurfacesModule;
-import com.android.systemui.statusbar.notification.dagger.NotificationStackModule;
import com.android.systemui.statusbar.notification.dagger.ReferenceNotificationsModule;
import com.android.systemui.statusbar.notification.headsup.HeadsUpModule;
import com.android.systemui.statusbar.phone.CentralSurfaces;
@@ -170,7 +169,6 @@ import javax.inject.Named;
WallpaperModule.class,
ShortcutHelperModule.class,
ContextualEducationModule.class,
- NotificationStackModule.class,
})
public abstract class ReferenceSystemUIModule {
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt b/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt
index ebe228dab05a..26501596aa1a 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt
@@ -45,7 +45,6 @@ class FlagDependencies @Inject constructor(featureFlags: FeatureFlagsClassic, ha
// Internal notification backend dependencies
crossAppPoliteNotifications dependsOn politeNotifications
vibrateWhileUnlockedToken dependsOn politeNotifications
- modesUi dependsOn modesApi
// Internal notification frontend dependencies
NotificationAvalancheSuppression.token dependsOn VisualInterruptionRefactor.token
@@ -71,9 +70,6 @@ class FlagDependencies @Inject constructor(featureFlags: FeatureFlagsClassic, ha
private inline val modesUi
get() = FlagToken(android.app.Flags.FLAG_MODES_UI, android.app.Flags.modesUi())
- private inline val modesApi
- get() = FlagToken(android.app.Flags.FLAG_MODES_API, android.app.Flags.modesApi())
-
private inline val communalHub
get() = FlagToken(FLAG_COMMUNAL_HUB, communalHub())
}
diff --git a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/domain/interactor/TutorialSchedulerInteractor.kt b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/domain/interactor/TutorialSchedulerInteractor.kt
index b712fdeaf623..0d57a57c3416 100644
--- a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/domain/interactor/TutorialSchedulerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/domain/interactor/TutorialSchedulerInteractor.kt
@@ -102,10 +102,9 @@ constructor(
}
// This flow is used by the notification updater once an initial notification is launched. It
- // listens to the device connection changes for both keyboard and touchpad. When either of the
- // device is disconnected, resolve the tutorial type base on the latest connection state.
- // Dropping the initial state because it's the existing notification. Filtering out BOTH because
- // we only care about disconnections.
+ // listens to the changes of keyboard and touchpad connection and resolve the tutorial type base
+ // on the latest connection state.
+ // Dropping the initial state because it represents the existing notification.
val tutorialTypeUpdates: Flow<TutorialType> =
keyboardRepository.isAnyKeyboardConnected
.combine(touchpadRepository.isAnyTouchpadConnected, ::Pair)
@@ -118,7 +117,6 @@ constructor(
}
}
.drop(1)
- .filter { it != TutorialType.BOTH }
private suspend fun waitForDeviceConnection(deviceType: DeviceType) =
isAnyDeviceConnected[deviceType]!!.filter { it }.first()
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 464201f6ec12..b787fc2a2b17 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
@@ -19,6 +19,7 @@ package com.android.systemui.keyboard.shortcut.data.source
import android.content.Context
import android.content.res.Resources
import android.view.KeyEvent.KEYCODE_D
+import android.view.KeyEvent.KEYCODE_DPAD_DOWN
import android.view.KeyEvent.KEYCODE_DPAD_LEFT
import android.view.KeyEvent.KEYCODE_DPAD_RIGHT
import android.view.KeyEvent.KEYCODE_DPAD_UP
@@ -73,6 +74,15 @@ constructor(@Main private val resources: Resources, @Application private val con
command(META_META_ON or META_CTRL_ON, KEYCODE_DPAD_UP)
}
)
+ if (DesktopModeStatus.canEnterDesktopMode(context)) {
+ // Switch to desktop view
+ // - Meta + Ctrl + Down arrow
+ add(
+ shortcutInfo(resources.getString(R.string.system_multitasking_desktop_view)) {
+ command(META_META_ON or META_CTRL_ON, KEYCODE_DPAD_DOWN)
+ }
+ )
+ }
if (enableMoveToNextDisplayShortcut()) {
// Move a window to the next display:
// - Meta + Ctrl + D
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutHelperCategoriesInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutHelperCategoriesInteractor.kt
index 61d11f4df5e0..f89421f9b73e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutHelperCategoriesInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutHelperCategoriesInteractor.kt
@@ -17,19 +17,16 @@
package com.android.systemui.keyboard.shortcut.domain.interactor
import android.content.Context
-import android.view.KeyEvent.META_META_ON
import com.android.systemui.Flags.keyboardShortcutHelperShortcutCustomizer
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.keyboard.shortcut.data.repository.ShortcutCategoriesRepository
-import com.android.systemui.keyboard.shortcut.data.repository.ShortcutHelperKeys
-import com.android.systemui.keyboard.shortcut.data.repository.ShortcutHelperKeys.metaModifierIconResId
+import com.android.systemui.keyboard.shortcut.extensions.toContentDescription
import com.android.systemui.keyboard.shortcut.qualifiers.CustomShortcutCategories
import com.android.systemui.keyboard.shortcut.qualifiers.DefaultShortcutCategories
import com.android.systemui.keyboard.shortcut.shared.model.Shortcut
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategory
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.res.R
import dagger.Lazy
@@ -105,8 +102,6 @@ constructor(
context.getString(R.string.shortcut_helper_key_combinations_and_conjunction)
val orConjunction =
context.getString(R.string.shortcut_helper_key_combinations_or_separator)
- val forwardSlash =
- context.getString(R.string.shortcut_helper_key_combinations_forward_slash)
return buildString {
append("$label, $pressKey")
commands.forEachIndexed { i, shortcutCommand ->
@@ -117,29 +112,7 @@ constructor(
if (j > 0) {
append(" $andConjunction")
}
- if (shortcutKey is ShortcutKey.Text) {
- // Special handling for "/" as TalkBack will not read punctuation by
- // default.
- if (shortcutKey.value.equals("/")) {
- append(" $forwardSlash")
- } else {
- append(" ${shortcutKey.value}")
- }
- } else if (shortcutKey is ShortcutKey.Icon.ResIdIcon) {
- val keyLabel =
- if (shortcutKey.drawableResId == metaModifierIconResId) {
- ShortcutHelperKeys.modifierLabels[META_META_ON]
- } else {
- val keyCode =
- ShortcutHelperKeys.keyIcons.entries
- .firstOrNull { it.value == shortcutKey.drawableResId }
- ?.key
- ShortcutHelperKeys.specialKeyLabels[keyCode]
- }
- if (keyLabel != null) {
- append(" ${keyLabel.invoke(context)}")
- }
- } // No-Op when shortcutKey is ShortcutKey.Icon.DrawableIcon
+ shortcutKey.toContentDescription(context)?.let { append(" $it") }
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/extensions/ShortcutKeyExtensions.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/extensions/ShortcutKeyExtensions.kt
new file mode 100644
index 000000000000..5637747d5aba
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/extensions/ShortcutKeyExtensions.kt
@@ -0,0 +1,62 @@
+/*
+ * 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.keyboard.shortcut.extensions
+
+import android.content.Context
+import android.view.KeyEvent.META_META_ON
+import com.android.systemui.keyboard.shortcut.data.repository.ShortcutHelperKeys
+import com.android.systemui.keyboard.shortcut.data.repository.ShortcutHelperKeys.metaModifierIconResId
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutKey
+import com.android.systemui.res.R
+
+fun ShortcutKey.toContentDescription(context: Context): String? {
+ val forwardSlash = context.getString(R.string.shortcut_helper_key_combinations_forward_slash)
+ when (this) {
+ is ShortcutKey.Text -> {
+ // Special handling for "/" as TalkBack will not read punctuation by
+ // default.
+ return if (this.value == "/") {
+ forwardSlash
+ } else {
+ this.value
+ }
+ }
+
+ is ShortcutKey.Icon.ResIdIcon -> {
+ val keyLabel =
+ if (this.drawableResId == metaModifierIconResId) {
+ ShortcutHelperKeys.modifierLabels[META_META_ON]
+ } else {
+ val keyCode =
+ ShortcutHelperKeys.keyIcons.entries
+ .firstOrNull { it.value == this.drawableResId }
+ ?.key
+ ShortcutHelperKeys.specialKeyLabels[keyCode]
+ }
+
+ if (keyLabel != null) {
+ return keyLabel.invoke(context)
+ }
+ }
+
+ is ShortcutKey.Icon.DrawableIcon -> {
+ // No-Op when shortcutKey is ShortcutKey.Icon.DrawableIcon
+ }
+ }
+
+ return null
+}
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 66e45056989d..7e0fa2f125b0 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
@@ -60,6 +60,7 @@ 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.hideFromAccessibility
import androidx.compose.ui.semantics.liveRegion
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.text.font.FontWeight
@@ -127,6 +128,7 @@ private fun AddShortcutDialog(
shouldShowError = uiState.errorMessage.isNotEmpty(),
onShortcutKeyCombinationSelected = onShortcutKeyCombinationSelected,
pressedKeys = uiState.pressedKeys,
+ contentDescription = uiState.pressedKeysDescription,
onConfirmSetShortcut = onConfirmSetShortcut,
onClearSelectedKeyCombination = onClearSelectedKeyCombination,
)
@@ -267,6 +269,7 @@ private fun SelectedKeyCombinationContainer(
shouldShowError: Boolean,
onShortcutKeyCombinationSelected: (KeyEvent) -> Boolean,
pressedKeys: List<ShortcutKey>,
+ contentDescription: String,
onConfirmSetShortcut: () -> Unit,
onClearSelectedKeyCombination: () -> Unit,
) {
@@ -313,6 +316,7 @@ private fun SelectedKeyCombinationContainer(
} else {
null
},
+ contentDescription = contentDescription,
)
}
@@ -331,8 +335,7 @@ private fun ErrorIcon(shouldShowError: Boolean) {
@Composable
private fun PressedKeysTextContainer(pressedKeys: List<ShortcutKey>) {
Row(
- modifier =
- Modifier.semantics(mergeDescendants = true) { liveRegion = LiveRegionMode.Polite },
+ modifier = Modifier.semantics { hideFromAccessibility() },
verticalAlignment = Alignment.CenterVertically,
) {
pressedKeys.forEachIndexed { keyIndex, key ->
@@ -495,6 +498,7 @@ private fun OutlinedInputField(
trailingIcon: @Composable () -> Unit,
isError: Boolean,
modifier: Modifier = Modifier,
+ contentDescription: String,
) {
OutlinedTextField(
value = "",
@@ -502,7 +506,10 @@ private fun OutlinedInputField(
placeholder = if (content == null) placeholder else null,
prefix = content,
singleLine = true,
- modifier = modifier,
+ modifier =
+ modifier.semantics(mergeDescendants = true) {
+ this.contentDescription = contentDescription
+ },
trailingIcon = trailingIcon,
colors =
OutlinedTextFieldDefaults.colors()
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/model/ShortcutCustomizationUiState.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/model/ShortcutCustomizationUiState.kt
index 36c5ae0717be..688573df9ee7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/model/ShortcutCustomizationUiState.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/model/ShortcutCustomizationUiState.kt
@@ -24,6 +24,7 @@ sealed interface ShortcutCustomizationUiState {
val errorMessage: String = "",
val defaultCustomShortcutModifierKey: ShortcutKey.Icon.ResIdIcon,
val pressedKeys: List<ShortcutKey> = emptyList(),
+ val pressedKeysDescription: String = "",
) : ShortcutCustomizationUiState
data object DeleteShortcutDialog : ShortcutCustomizationUiState
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 f4ba99c6a394..aeedc4b7d618 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
@@ -26,6 +26,7 @@ import androidx.compose.ui.input.key.nativeKeyCode
import androidx.compose.ui.input.key.type
import com.android.systemui.keyboard.shared.model.ShortcutCustomizationRequestResult
import com.android.systemui.keyboard.shortcut.domain.interactor.ShortcutCustomizationInteractor
+import com.android.systemui.keyboard.shortcut.extensions.toContentDescription
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
@@ -185,7 +186,11 @@ constructor(
_shortcutCustomizationUiState.update { uiState ->
if (uiState is AddShortcutDialog) {
- uiState.copy(pressedKeys = keys, errorMessage = errorMessage)
+ uiState.copy(
+ pressedKeys = keys,
+ errorMessage = errorMessage,
+ pressedKeysDescription = getAccessibilityDescForPressedKeys(keys),
+ )
} else {
uiState
}
@@ -193,11 +198,25 @@ constructor(
}
}
+ private fun getAccessibilityDescForPressedKeys(keys: List<ShortcutKey>): String {
+ val andConjunction =
+ context.getString(R.string.shortcut_helper_key_combinations_and_conjunction)
+ return buildString {
+ keys.forEach { key ->
+ key.toContentDescription(context)?.let {
+ if (isNotEmpty()) {
+ append(", $andConjunction ")
+ }
+ append(it)
+ }
+ }
+ }
+ }
+
private suspend fun getErrorMessageForPressedKeys(keys: List<ShortcutKey>): String {
return if (keys.isEmpty() or isSelectedKeyCombinationAvailable()) {
""
- }
- else {
+ } else {
context.getString(R.string.shortcut_customizer_key_combination_in_use_error_message)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/LockscreenSceneTransitionRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/LockscreenSceneTransitionRepository.kt
index 80bdc65f9b97..f69229213690 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/LockscreenSceneTransitionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/LockscreenSceneTransitionRepository.kt
@@ -25,11 +25,17 @@ import kotlinx.coroutines.flow.MutableStateFlow
class LockscreenSceneTransitionRepository @Inject constructor() {
/**
- * This [KeyguardState] will indicate which sub state within KTF should be navigated to when the
- * next transition into the Lockscreen scene is started. It will be consumed exactly once and
- * after that the state will be set back to [DEFAULT_STATE].
+ * This [KeyguardState] will indicate which sub-state within KTF should be navigated to next.
+ *
+ * This can be either starting a transition to the `Lockscreen` scene or cancelling a transition
+ * from the `Lockscreen` scene and returning back to it.
+ *
+ * A `null` value means that no explicit target state was set and therefore the [DEFAULT_STATE]
+ * should be used.
+ *
+ * Once consumed, this state should be reset to `null`.
*/
- val nextLockscreenTargetState: MutableStateFlow<KeyguardState> = MutableStateFlow(DEFAULT_STATE)
+ val nextLockscreenTargetState: MutableStateFlow<KeyguardState?> = MutableStateFlow(null)
companion object {
val DEFAULT_STATE = KeyguardState.LOCKSCREEN
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt
index bf0f25ff089e..a3796ab5ee27 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt
@@ -118,26 +118,19 @@ constructor(
powerInteractor.isAwake,
keyguardInteractor.isAodAvailable,
communalSceneInteractor.isIdleOnCommunal,
- communalInteractor.editModeOpen,
+ keyguardInteractor.isDreaming,
keyguardInteractor.isKeyguardOccluded,
)
.filterRelevantKeyguardStateAnd {
- (isAlternateBouncerShowing, isPrimaryBouncerShowing, _, _, _) ->
+ (isAlternateBouncerShowing, isPrimaryBouncerShowing, _, _, _, _) ->
!isAlternateBouncerShowing && !isPrimaryBouncerShowing
}
- .collect {
- (
- _,
- _,
- isAwake,
- isAodAvailable,
- isIdleOnCommunal,
- isCommunalEditMode,
- isOccluded) ->
+ .collect { (_, _, isAwake, isAodAvailable, isIdleOnCommunal, isDreaming, isOccluded)
+ ->
// When unlocking over glanceable hub to enter edit mode, transitioning directly
// to GONE prevents the lockscreen flash. Let listenForAlternateBouncerToGone
// handle it.
- if (isCommunalEditMode) return@collect
+ if (communalInteractor.editModeOpen.value) return@collect
val hubV2 = communalSettingsInteractor.isV2FlagEnabled()
val to =
if (!isAwake) {
@@ -150,8 +143,10 @@ constructor(
if (!hubV2 && isIdleOnCommunal) {
if (SceneContainerFlag.isEnabled) return@collect
KeyguardState.GLANCEABLE_HUB
- } else if (isOccluded) {
+ } else if (isOccluded && !isDreaming) {
KeyguardState.OCCLUDED
+ } else if (hubV2 && isDreaming) {
+ KeyguardState.DREAMING
} else if (hubV2 && isIdleOnCommunal) {
if (SceneContainerFlag.isEnabled) return@collect
KeyguardState.GLANCEABLE_HUB
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
index 0700ec639153..6f5f662d6fa3 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
@@ -159,7 +159,6 @@ constructor(
val isKeyguardOccludedLegacy = keyguardInteractor.isKeyguardOccluded.value
val primaryBouncerShowing = keyguardInteractor.primaryBouncerShowing.value
val isKeyguardGoingAway = keyguardInteractor.isKeyguardGoingAway.value
- val canStartDreaming = dreamManager.canStartDreaming(false)
if (!deviceEntryInteractor.isLockscreenEnabled()) {
if (!SceneContainerFlag.isEnabled) {
@@ -192,13 +191,6 @@ constructor(
if (!SceneContainerFlag.isEnabled) {
transitionToGlanceableHub()
}
- } else if (canStartDreaming) {
- // If we're waking up to dream, transition directly to dreaming without
- // showing the lockscreen.
- startTransitionTo(
- KeyguardState.DREAMING,
- ownerReason = "moving from doze to dream",
- )
} else {
startTransitionTo(KeyguardState.LOCKSCREEN)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractor.kt
index 673fa9730c53..63cf4f72e415 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractor.kt
@@ -24,7 +24,6 @@ import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.keyguard.data.repository.KeyguardClockRepository
import com.android.systemui.keyguard.shared.model.ClockSize
import com.android.systemui.keyguard.shared.model.ClockSizeSetting
-import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
import com.android.systemui.keyguard.shared.model.KeyguardState.DOZING
import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
@@ -33,12 +32,13 @@ import com.android.systemui.media.controls.domain.pipeline.interactor.MediaCarou
import com.android.systemui.plugins.clocks.ClockController
import com.android.systemui.plugins.clocks.ClockId
import com.android.systemui.scene.shared.flag.SceneContainerFlag
-import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.shade.domain.interactor.ShadeModeInteractor
import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor
import com.android.systemui.statusbar.notification.domain.interactor.HeadsUpNotificationInteractor
import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUiAod
import com.android.systemui.statusbar.notification.promoted.domain.interactor.AODPromotedNotificationInteractor
import com.android.systemui.util.kotlin.combine
+import com.android.systemui.utils.coroutines.flow.flatMapLatestConflated
import com.android.systemui.wallpapers.domain.interactor.WallpaperFocalAreaInteractor
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
@@ -61,7 +61,7 @@ constructor(
mediaCarouselInteractor: MediaCarouselInteractor,
activeNotificationsInteractor: ActiveNotificationsInteractor,
aodPromotedNotificationInteractor: AODPromotedNotificationInteractor,
- shadeInteractor: ShadeInteractor,
+ shadeModeInteractor: ShadeModeInteractor,
keyguardInteractor: KeyguardInteractor,
keyguardTransitionInteractor: KeyguardTransitionInteractor,
headsUpNotificationInteractor: HeadsUpNotificationInteractor,
@@ -70,8 +70,13 @@ constructor(
private val wallpaperFocalAreaInteractor: WallpaperFocalAreaInteractor,
) {
private val isOnAod: Flow<Boolean> =
- keyguardTransitionInteractor.currentKeyguardState.map { it == KeyguardState.AOD }
+ keyguardTransitionInteractor.currentKeyguardState.map { it == AOD }
+ /**
+ * The clock size setting explicitly selected by the user. When it is `SMALL`, the large clock
+ * is never shown. When it is `DYNAMIC`, the clock size gets determined based on a combination
+ * of system signals.
+ */
val selectedClockSize: StateFlow<ClockSizeSetting> = keyguardClockRepository.selectedClockSize
val currentClockId: Flow<ClockId> = keyguardClockRepository.currentClockId
@@ -103,36 +108,46 @@ constructor(
activeNotificationsInteractor.areAnyNotificationsPresent
}
- val clockSize: StateFlow<ClockSize> =
+ private val dynamicClockSize: Flow<ClockSize> =
if (SceneContainerFlag.isEnabled) {
combine(
- shadeInteractor.isShadeLayoutWide,
- areAnyNotificationsPresent,
- mediaCarouselInteractor.hasActiveMediaOrRecommendation,
- keyguardInteractor.isDozing,
- isOnAod,
- ) { isShadeLayoutWide, hasNotifs, hasMedia, isDozing, isOnAod ->
- return@combine when {
- keyguardClockRepository.shouldForceSmallClock && !isOnAod -> ClockSize.SMALL
- !isShadeLayoutWide && (hasNotifs || hasMedia) -> ClockSize.SMALL
- !isShadeLayoutWide -> ClockSize.LARGE
- hasMedia && !isDozing -> ClockSize.SMALL
- else -> ClockSize.LARGE
- }
+ shadeModeInteractor.isShadeLayoutWide,
+ areAnyNotificationsPresent,
+ mediaCarouselInteractor.hasActiveMediaOrRecommendation,
+ keyguardInteractor.isDozing,
+ isOnAod,
+ ) { isShadeLayoutWide, hasNotifs, hasMedia, isDozing, isOnAod ->
+ when {
+ keyguardClockRepository.shouldForceSmallClock && !isOnAod -> ClockSize.SMALL
+ !isShadeLayoutWide && (hasNotifs || hasMedia) -> ClockSize.SMALL
+ !isShadeLayoutWide -> ClockSize.LARGE
+ hasMedia && !isDozing -> ClockSize.SMALL
+ else -> ClockSize.LARGE
}
- .stateIn(
- scope = applicationScope,
- started = SharingStarted.WhileSubscribed(),
- initialValue = ClockSize.LARGE,
- )
+ }
} else {
keyguardClockRepository.clockSize
}
+ val clockSize: StateFlow<ClockSize> =
+ selectedClockSize
+ .flatMapLatestConflated { selectedSize ->
+ if (selectedSize == ClockSizeSetting.SMALL) {
+ flowOf(ClockSize.SMALL)
+ } else {
+ dynamicClockSize
+ }
+ }
+ .stateIn(
+ scope = applicationScope,
+ started = SharingStarted.Eagerly,
+ initialValue = ClockSize.LARGE,
+ )
+
val clockShouldBeCentered: Flow<Boolean> =
if (SceneContainerFlag.isEnabled) {
combine(
- shadeInteractor.isShadeLayoutWide,
+ shadeModeInteractor.isShadeLayoutWide,
areAnyNotificationsPresent,
isAodPromotedNotificationPresent,
isOnAod,
@@ -156,7 +171,7 @@ constructor(
}
} else {
combine(
- shadeInteractor.isShadeLayoutWide,
+ shadeModeInteractor.isShadeLayoutWide,
areAnyNotificationsPresent,
isAodPromotedNotificationPresent,
keyguardInteractor.dozeTransitionModel,
@@ -203,7 +218,7 @@ constructor(
val renderedClockId: ClockId
get() {
- return clock?.let { clock -> clock.config.id }
+ return clock?.config?.id
?: run {
Log.e(TAG, "No clock is available")
"MISSING_CLOCK_ID"
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/scenetransition/LockscreenSceneTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/scenetransition/LockscreenSceneTransitionInteractor.kt
index 5f821022d580..1b70ff84f09d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/scenetransition/LockscreenSceneTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/scenetransition/LockscreenSceneTransitionInteractor.kt
@@ -119,7 +119,8 @@ constructor(
} else {
val targetState =
if (idle.currentScene == Scenes.Lockscreen) {
- transitionInteractor.startedKeyguardTransitionStep.value.from
+ repository.nextLockscreenTargetState.value
+ ?: transitionInteractor.startedKeyguardTransitionStep.value.from
} else {
UNDEFINED
}
@@ -197,11 +198,11 @@ constructor(
TransitionInfo(
ownerName = this::class.java.simpleName,
from = UNDEFINED,
- to = repository.nextLockscreenTargetState.value,
+ to = repository.nextLockscreenTargetState.value ?: DEFAULT_STATE,
animator = null,
modeOnCanceled = TransitionModeOnCanceled.RESET,
)
- repository.nextLockscreenTargetState.value = DEFAULT_STATE
+ repository.nextLockscreenTargetState.value = null
startTransition(newTransition)
}
@@ -215,7 +216,7 @@ constructor(
animator = null,
modeOnCanceled = TransitionModeOnCanceled.RESET,
)
- repository.nextLockscreenTargetState.value = DEFAULT_STATE
+ repository.nextLockscreenTargetState.value = null
startTransition(newTransition)
}
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 8e385385b8c4..da87e38daa9b 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
@@ -370,6 +370,14 @@ object KeyguardRootViewBinder {
repeatOnLifecycle(Lifecycle.State.STARTED) {
if (wallpaperFocalAreaViewModel.hasFocalArea.value) {
launch {
+ wallpaperFocalAreaViewModel.wallpaperFocalAreaBounds.collect {
+ wallpaperFocalAreaBounds ->
+ wallpaperFocalAreaViewModel.setFocalAreaBounds(
+ wallpaperFocalAreaBounds
+ )
+ }
+ }
+ launch {
wallpaperFocalAreaViewModel.wallpaperFocalAreaBounds
.filterNotNull()
.collect { wallpaperFocalAreaViewModel.setFocalAreaBounds(it) }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModel.kt
index aed86648e3cf..0a087404c075 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModel.kt
@@ -26,7 +26,6 @@ import com.android.systemui.keyguard.domain.interactor.BurnInInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.BurnInModel
-import com.android.systemui.keyguard.shared.model.ClockSize
import com.android.systemui.keyguard.shared.model.Edge
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.ui.StateToValue
@@ -35,6 +34,7 @@ import com.android.systemui.shade.ShadeDisplayAware
import javax.inject.Inject
import kotlin.math.max
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
@@ -51,6 +51,7 @@ import kotlinx.coroutines.flow.stateIn
* Models UI state for elements that need to apply anti-burn-in tactics when showing in AOD
* (always-on display).
*/
+@OptIn(ExperimentalCoroutinesApi::class)
@SysUISingleton
class AodBurnInViewModel
@Inject
@@ -184,10 +185,9 @@ constructor(
keyguardClockViewModel.currentClock.value
?.config
?.useAlternateSmartspaceAODTransition == true
- // Only scale large non-weather clocks
- // elements in large weather clock will translate the same as smartspace
- val useScaleOnly =
- (!useAltAod) && keyguardClockViewModel.clockSize.value == ClockSize.LARGE
+ // Only scale large non-weather clocks elements in large weather clock will translate
+ // the same as smartspace
+ val useScaleOnly = (!useAltAod) && keyguardClockViewModel.isLargeClockVisible.value
val burnInY = MathUtils.lerp(0, burnIn.translationY, interpolated).toInt()
val translationY = max(params.topInset - params.minViewY, burnInY)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToDreamingTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToDreamingTransitionViewModel.kt
index 9018c58a7e36..e6a85c6860c5 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToDreamingTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToDreamingTransitionViewModel.kt
@@ -39,6 +39,4 @@ constructor(animationFlow: KeyguardTransitionAnimationFlow) {
)
val lockscreenAlpha: Flow<Float> = transitionAnimation.immediatelyTransitionTo(0f)
- // Notifications should not be shown while transitioning to dream.
- val notificationAlpha = transitionAnimation.immediatelyTransitionTo(0f)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt
index 88fdc83fa7a0..cf5cc264be8d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt
@@ -18,7 +18,6 @@ package com.android.systemui.keyguard.ui.viewmodel
import android.content.Context
import android.content.res.Resources
-import androidx.annotation.VisibleForTesting
import androidx.constraintlayout.helper.widget.Layer
import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
import com.android.systemui.customization.R as customR
@@ -27,11 +26,10 @@ import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
import com.android.systemui.keyguard.shared.model.ClockSize
-import com.android.systemui.keyguard.shared.model.ClockSizeSetting
import com.android.systemui.plugins.clocks.ClockPreviewConfig
import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.shade.ShadeDisplayAware
-import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.shade.domain.interactor.ShadeModeInteractor
import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerAlwaysOnDisplayViewModel
import com.android.systemui.statusbar.ui.SystemBarUtilsProxy
import javax.inject.Inject
@@ -47,11 +45,11 @@ import kotlinx.coroutines.flow.stateIn
class KeyguardClockViewModel
@Inject
constructor(
- val context: Context,
+ private val context: Context,
keyguardClockInteractor: KeyguardClockInteractor,
@Application private val applicationScope: CoroutineScope,
aodNotificationIconViewModel: NotificationIconContainerAlwaysOnDisplayViewModel,
- @get:VisibleForTesting val shadeInteractor: ShadeInteractor,
+ private val shadeModeInteractor: ShadeModeInteractor,
private val systemBarUtils: SystemBarUtilsProxy,
@ShadeDisplayAware configurationInteractor: ConfigurationInteractor,
// TODO: b/374267505 - Use ShadeDisplayAware resources here.
@@ -59,17 +57,7 @@ constructor(
) {
var burnInLayer: Layer? = null
- val clockSize: StateFlow<ClockSize> =
- combine(keyguardClockInteractor.selectedClockSize, keyguardClockInteractor.clockSize) {
- selectedSize,
- clockSize ->
- if (selectedSize == ClockSizeSetting.SMALL) ClockSize.SMALL else clockSize
- }
- .stateIn(
- scope = applicationScope,
- started = SharingStarted.Eagerly,
- initialValue = ClockSize.LARGE,
- )
+ val clockSize: StateFlow<ClockSize> = keyguardClockInteractor.clockSize
val isLargeClockVisible: StateFlow<Boolean> =
clockSize
@@ -118,7 +106,7 @@ constructor(
combine(
isLargeClockVisible,
clockShouldBeCentered,
- shadeInteractor.isShadeLayoutWide,
+ shadeModeInteractor.isShadeLayoutWide,
currentClock,
) { isLargeClockVisible, clockShouldBeCentered, isShadeLayoutWide, currentClock ->
if (currentClock?.config?.useCustomClockScene == true) {
@@ -163,7 +151,7 @@ constructor(
fun getSmallClockTopMargin(): Int {
return ClockPreviewConfig(
context,
- shadeInteractor.isShadeLayoutWide.value,
+ shadeModeInteractor.isShadeLayoutWide.value,
SceneContainerFlag.isEnabled,
)
.getSmallClockTopPadding(systemBarUtils.getStatusBarHeaderHeightKeyguard())
@@ -172,7 +160,7 @@ constructor(
val smallClockTopMargin =
combine(
configurationInteractor.onAnyConfigurationChange,
- shadeInteractor.isShadeLayoutWide,
+ shadeModeInteractor.isShadeLayoutWide,
) { _, _ ->
getSmallClockTopMargin()
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataManager.kt
index c3182bf7a320..1466d8b4288e 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataManager.kt
@@ -143,6 +143,12 @@ interface MediaDataManager {
* place immediately.
*/
override fun onSmartspaceMediaDataRemoved(key: String, immediately: Boolean) {}
+
+ /**
+ * Called whenever the current active media notification changes. Should only be used if
+ * [SceneContainerFlag] is disabled
+ */
+ override fun onCurrentActiveMediaChanged(key: String?, data: MediaData?) {}
}
companion object {
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 1464849156dc..59f98d83e149 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
@@ -1434,6 +1434,9 @@ class MediaDataProcessor(
* place immediately.
*/
fun onSmartspaceMediaDataRemoved(key: String, immediately: Boolean = true) {}
+
+ /** Called whenever the current active media notification changes */
+ fun onCurrentActiveMediaChanged(key: String?, data: MediaData?) {}
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt
index 173b600de06b..93c4bafe4273 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt
@@ -87,6 +87,7 @@ import com.android.systemui.shared.system.SysUiStatsLog.SMART_SPACE_CARD_REPORTE
import com.android.systemui.shared.system.SysUiStatsLog.SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__DREAM_OVERLAY as SSPACE_CARD_REPORTED__DREAM_OVERLAY
import com.android.systemui.shared.system.SysUiStatsLog.SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__LOCKSCREEN as SSPACE_CARD_REPORTED__LOCKSCREEN
import com.android.systemui.shared.system.SysUiStatsLog.SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__SHADE
+import com.android.systemui.statusbar.featurepods.media.domain.interactor.MediaControlChipInteractor
import com.android.systemui.statusbar.notification.collection.provider.OnReorderingAllowedListener
import com.android.systemui.statusbar.notification.collection.provider.VisualStabilityProvider
import com.android.systemui.statusbar.policy.ConfigurationController
@@ -155,6 +156,7 @@ constructor(
private val mediaCarouselViewModel: MediaCarouselViewModel,
private val mediaViewControllerFactory: Provider<MediaViewController>,
private val deviceEntryInteractor: DeviceEntryInteractor,
+ private val mediaControlChipInteractor: MediaControlChipInteractor,
) : Dumpable {
/** The current width of the carousel */
var currentCarouselWidth: Int = 0
@@ -957,6 +959,9 @@ constructor(
}
}
mediaCarouselScrollHandler.onPlayersChanged()
+ mediaControlChipInteractor.updateMediaControlChipModelLegacy(
+ MediaPlayerData.getFirstActiveMediaData()
+ )
MediaPlayerData.updateVisibleMediaPlayers()
// Automatically scroll to the active player if needed
if (shouldScrollToKey) {
@@ -1015,6 +1020,9 @@ constructor(
)
updatePageIndicator()
mediaCarouselScrollHandler.onPlayersChanged()
+ mediaControlChipInteractor.updateMediaControlChipModelLegacy(
+ MediaPlayerData.getFirstActiveMediaData()
+ )
mediaFrame.requiresRemeasuring = true
onUiExecutionEnd?.run()
}
@@ -1023,6 +1031,9 @@ constructor(
updatePlayer(key, data, isSsReactivated, curVisibleMediaKey, existingPlayer)
updatePageIndicator()
mediaCarouselScrollHandler.onPlayersChanged()
+ mediaControlChipInteractor.updateMediaControlChipModelLegacy(
+ MediaPlayerData.getFirstActiveMediaData()
+ )
mediaFrame.requiresRemeasuring = true
onUiExecutionEnd?.run()
}
@@ -1036,6 +1047,9 @@ constructor(
}
updatePageIndicator()
mediaCarouselScrollHandler.onPlayersChanged()
+ mediaControlChipInteractor.updateMediaControlChipModelLegacy(
+ MediaPlayerData.getFirstActiveMediaData()
+ )
mediaFrame.requiresRemeasuring = true
onUiExecutionEnd?.run()
}
@@ -1194,6 +1208,9 @@ constructor(
mediaContent.removeView(removed.recommendationViewHolder?.recommendations)
removed.onDestroy()
mediaCarouselScrollHandler.onPlayersChanged()
+ mediaControlChipInteractor.updateMediaControlChipModelLegacy(
+ MediaPlayerData.getFirstActiveMediaData()
+ )
updatePageIndicator()
if (dismissMediaData) {
@@ -1928,6 +1945,16 @@ internal object MediaPlayerData {
fun visiblePlayerKeys() = visibleMediaPlayers.values
+ /** Returns the [MediaData] associated with the first mediaPlayer in the mediaCarousel. */
+ fun getFirstActiveMediaData(): MediaData? {
+ mediaPlayers.entries.forEach { entry ->
+ if (!entry.key.isSsMediaRec && entry.key.data.active) {
+ return entry.key.data
+ }
+ }
+ return null
+ }
+
/** Returns the index of the first non-timeout media. */
fun firstActiveMediaIndex(): Int {
mediaPlayers.entries.forEachIndexed { index, e ->
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/util/MediaViewModelListUpdateCallback.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/util/MediaViewModelListUpdateCallback.kt
index 709723fa9480..6022b7b1fc13 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/util/MediaViewModelListUpdateCallback.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/util/MediaViewModelListUpdateCallback.kt
@@ -18,6 +18,7 @@ package com.android.systemui.media.controls.ui.util
import androidx.recyclerview.widget.ListUpdateCallback
import com.android.systemui.media.controls.ui.viewmodel.MediaCommonViewModel
+import kotlin.math.min
/** A [ListUpdateCallback] to apply media events needed to reach the new state. */
class MediaViewModelListUpdateCallback(
@@ -46,7 +47,7 @@ class MediaViewModelListUpdateCallback(
}
override fun onChanged(position: Int, count: Int, payload: Any?) {
- for (i in position until position + count) {
+ for (i in position until min(position + count, new.size)) {
onUpdated(new[i], position)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapterBase.java
index f5e62323e769..c58ba377fb68 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapterBase.java
@@ -20,23 +20,16 @@ import static com.android.settingslib.media.MediaDevice.SelectionBehavior.SELECT
import static com.android.settingslib.media.MediaDevice.SelectionBehavior.SELECTION_BEHAVIOR_NONE;
import static com.android.settingslib.media.MediaDevice.SelectionBehavior.SELECTION_BEHAVIOR_TRANSFER;
-import android.annotation.DrawableRes;
-import android.annotation.StringRes;
import android.content.Context;
-import android.content.res.ColorStateList;
-import android.graphics.drawable.AnimatedVectorDrawable;
import android.graphics.drawable.Drawable;
+import android.text.TextUtils;
import android.util.Log;
import android.view.View;
-import android.view.ViewGroup;
-import android.widget.CheckBox;
-import android.widget.TextView;
import androidx.annotation.DoNotInline;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
-import androidx.core.widget.CompoundButtonCompat;
import androidx.recyclerview.widget.RecyclerView;
import com.android.internal.annotations.VisibleForTesting;
@@ -49,23 +42,64 @@ import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
/**
- * Adapter for media output dialog.
+ * A parent RecyclerView adapter for the media output dialog device list. This class doesn't
+ * manipulate the layout directly.
*/
-public class MediaOutputAdapter extends MediaOutputBaseAdapter {
+public abstract class MediaOutputAdapterBase extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
+ record OngoingSessionStatus(boolean host) {}
- private static final String TAG = "MediaOutputAdapter";
+ record GroupStatus(Boolean selected, Boolean deselectable) {}
+
+ enum ConnectionState {
+ CONNECTED,
+ CONNECTING,
+ DISCONNECTED,
+ }
+
+ protected final MediaSwitchingController mController;
+ private int mCurrentActivePosition;
+ private boolean mIsDragging;
+ private static final String TAG = "MediaOutputAdapterBase";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
- private static final float DEVICE_DISABLED_ALPHA = 0.5f;
- private static final float DEVICE_ACTIVE_ALPHA = 1f;
- protected List<MediaItem> mMediaItemList = new CopyOnWriteArrayList<>();
+ protected final List<MediaItem> mMediaItemList = new CopyOnWriteArrayList<>();
private boolean mShouldGroupSelectedMediaItems = Flags.enableOutputSwitcherDeviceGrouping();
- public MediaOutputAdapter(MediaSwitchingController controller) {
- super(controller);
+ public MediaOutputAdapterBase(MediaSwitchingController controller) {
+ mController = controller;
+ mCurrentActivePosition = -1;
+ mIsDragging = false;
setHasStableIds(true);
}
- @Override
+ boolean isCurrentlyConnected(MediaDevice device) {
+ return TextUtils.equals(device.getId(),
+ mController.getCurrentConnectedMediaDevice().getId())
+ || (mController.getSelectedMediaDevice().size() == 1
+ && isDeviceIncluded(mController.getSelectedMediaDevice(), device));
+ }
+
+ boolean isDeviceIncluded(List<MediaDevice> deviceList, MediaDevice targetDevice) {
+ for (MediaDevice device : deviceList) {
+ if (TextUtils.equals(device.getId(), targetDevice.getId())) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ boolean isDragging() {
+ return mIsDragging;
+ }
+
+ void setIsDragging(boolean isDragging) {
+ mIsDragging = isDragging;
+ }
+
+ int getCurrentActivePosition() {
+ return mCurrentActivePosition;
+ }
+
+ /** Refreshes the RecyclerView dataset and forces re-render. */
public void updateItems() {
mMediaItemList.clear();
mMediaItemList.addAll(mController.getMediaItemList());
@@ -79,47 +113,6 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter {
}
@Override
- public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup,
- int viewType) {
- super.onCreateViewHolder(viewGroup, viewType);
- switch (viewType) {
- case MediaItem.MediaItemType.TYPE_GROUP_DIVIDER:
- return new MediaGroupDividerViewHolder(mHolderView);
- case MediaItem.MediaItemType.TYPE_PAIR_NEW_DEVICE:
- case MediaItem.MediaItemType.TYPE_DEVICE:
- default:
- return new MediaDeviceViewHolder(mHolderView);
- }
- }
-
- @Override
- public void onBindViewHolder(@NonNull RecyclerView.ViewHolder viewHolder, int position) {
- if (position >= mMediaItemList.size()) {
- if (DEBUG) {
- Log.d(TAG, "Incorrect position: " + position + " list size: "
- + mMediaItemList.size());
- }
- return;
- }
- MediaItem currentMediaItem = mMediaItemList.get(position);
- switch (currentMediaItem.getMediaItemType()) {
- case MediaItem.MediaItemType.TYPE_GROUP_DIVIDER:
- ((MediaGroupDividerViewHolder) viewHolder).onBind(currentMediaItem.getTitle());
- break;
- case MediaItem.MediaItemType.TYPE_PAIR_NEW_DEVICE:
- ((MediaDeviceViewHolder) viewHolder).onBindPairNewDevice();
- break;
- case MediaItem.MediaItemType.TYPE_DEVICE:
- ((MediaDeviceViewHolder) viewHolder).onBind(
- currentMediaItem,
- position);
- break;
- default:
- Log.d(TAG, "Incorrect position: " + position);
- }
- }
-
- @Override
public long getItemId(int position) {
if (position >= mMediaItemList.size()) {
Log.d(TAG, "Incorrect position for item id: " + position);
@@ -145,15 +138,17 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter {
return mMediaItemList.size();
}
- class MediaDeviceViewHolder extends MediaDeviceBaseViewHolder {
+ abstract class MediaDeviceViewHolderBase extends RecyclerView.ViewHolder {
+
+ Context mContext;
- MediaDeviceViewHolder(View view) {
+ MediaDeviceViewHolderBase(View view, Context context) {
super(view);
+ mContext = context;
}
- void onBind(MediaItem mediaItem, int position) {
+ void renderItem(MediaItem mediaItem, int position) {
MediaDevice device = mediaItem.getMediaDevice().get();
- super.onBind(device, position);
boolean isMutingExpectedDeviceExist = mController.hasMutingExpectedDevice();
final boolean currentlyConnected = isCurrentlyConnected(device);
boolean isSelected = isDeviceIncluded(mController.getSelectedMediaDevice(), device);
@@ -198,7 +193,6 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter {
if (mCurrentActivePosition == position) {
mCurrentActivePosition = -1;
}
- mItemLayout.setVisibility(View.VISIBLE);
if (mController.isAnyDeviceTransferring()) {
if (device.getState() == MediaDeviceState.STATE_CONNECTING) {
@@ -265,37 +259,15 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter {
}
}
- private void renderDeviceItem(boolean hideGroupItem, MediaDevice device,
+ protected abstract void renderDeviceItem(boolean hideGroupItem, MediaDevice device,
ConnectionState connectionState, boolean restrictVolumeAdjustment,
GroupStatus groupStatus, OngoingSessionStatus ongoingSessionStatus,
View.OnClickListener clickListener, boolean deviceDisabled, String subtitle,
- Drawable deviceStatusIcon) {
- if (hideGroupItem) {
- mItemLayout.setVisibility(View.GONE);
- return;
- }
- updateTitle(device.getName());
- updateTitleIcon(device, connectionState, restrictVolumeAdjustment);
- updateSeekBar(device, connectionState, restrictVolumeAdjustment,
- getDeviceItemContentDescription(device));
- updateEndArea(device, connectionState, groupStatus, ongoingSessionStatus);
- updateLoadingIndicator(connectionState);
- updateFullItemClickListener(clickListener);
- updateContentAlpha(deviceDisabled);
- updateSubtitle(subtitle);
- updateDeviceStatusIcon(deviceStatusIcon);
- updateItemBackground(connectionState);
- }
+ Drawable deviceStatusIcon);
- private void renderDeviceGroupItem() {
- String sessionName = mController.getSessionName() == null ? ""
- : mController.getSessionName().toString();
- updateTitle(sessionName);
- updateUnmutedVolumeIcon(null /* device */);
- updateGroupSeekBar(getGroupItemContentDescription(sessionName));
- updateEndAreaForDeviceGroup();
- updateItemBackground(ConnectionState.CONNECTED);
- }
+ protected abstract void renderDeviceGroupItem();
+
+ protected abstract void disableSeekBar();
private OngoingSessionStatus getOngoingSessionStatus(MediaDevice device) {
return device.hasOngoingSession() ? new OngoingSessionStatus(
@@ -322,95 +294,6 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter {
return !mController.getSelectableMediaDevice().isEmpty();
}
- /** Renders the right side round pill button / checkbox. */
- private void updateEndArea(@NonNull MediaDevice device, ConnectionState connectionState,
- @Nullable GroupStatus groupStatus,
- @Nullable OngoingSessionStatus ongoingSessionStatus) {
- boolean showEndArea = false;
- boolean isCheckbox = false;
- // If both group status and the ongoing session status are present, only the ongoing
- // session controls are displayed. The current layout design doesn't allow both group
- // and ongoing session controls to be rendered simultaneously.
- if (ongoingSessionStatus != null && connectionState == ConnectionState.CONNECTED) {
- showEndArea = true;
- updateEndAreaForOngoingSession(device, ongoingSessionStatus.host());
- } else if (groupStatus != null && shouldShowGroupCheckbox(groupStatus)) {
- showEndArea = true;
- isCheckbox = true;
- updateEndAreaForGroupCheckBox(device, groupStatus);
- }
- updateEndAreaVisibility(showEndArea, isCheckbox);
- }
-
- private boolean shouldShowGroupCheckbox(@NonNull GroupStatus groupStatus) {
- if (Flags.enableOutputSwitcherDeviceGrouping()) {
- return isGroupCheckboxEnabled(groupStatus);
- }
- return true;
- }
-
- private boolean isGroupCheckboxEnabled(@NonNull GroupStatus groupStatus) {
- boolean disabled = groupStatus.selected() && !groupStatus.deselectable();
- return !disabled;
- }
-
- public void setCheckBoxColor(CheckBox checkBox, int color) {
- int[][] states = {{android.R.attr.state_checked}, {}};
- int[] colors = {color, color};
- CompoundButtonCompat.setButtonTintList(checkBox, new
- ColorStateList(states, colors));
- }
-
- private void updateContentAlpha(boolean deviceDisabled) {
- float alphaValue = deviceDisabled ? DEVICE_DISABLED_ALPHA : DEVICE_ACTIVE_ALPHA;
- mTitleIcon.setAlpha(alphaValue);
- mTitleText.setAlpha(alphaValue);
- mSubTitleText.setAlpha(alphaValue);
- mStatusIcon.setAlpha(alphaValue);
- }
-
- private void updateEndAreaForDeviceGroup() {
- updateEndAreaWithIcon(
- v -> {
- mShouldGroupSelectedMediaItems = false;
- notifyDataSetChanged();
- },
- R.drawable.media_output_item_expand_group,
- R.string.accessibility_expand_group);
- updateEndAreaVisibility(true /* showEndArea */, false /* isCheckbox */);
- }
-
- private void updateEndAreaForOngoingSession(@NonNull MediaDevice device, boolean isHost) {
- updateEndAreaWithIcon(
- v -> mController.tryToLaunchInAppRoutingIntent(device.getId(), v),
- isHost ? R.drawable.media_output_status_edit_session
- : R.drawable.ic_sound_bars_anim,
- R.string.accessibility_open_application);
- }
-
- private void updateEndAreaWithIcon(View.OnClickListener clickListener,
- @DrawableRes int iconDrawableId,
- @StringRes int accessibilityStringId) {
- updateEndAreaColor(mController.getColorSeekbarProgress());
- mEndClickIcon.setImageTintList(
- ColorStateList.valueOf(mController.getColorItemContent()));
- mEndClickIcon.setOnClickListener(clickListener);
- mEndTouchArea.setOnClickListener(v -> mEndClickIcon.performClick());
- Drawable drawable = mContext.getDrawable(iconDrawableId);
- mEndClickIcon.setImageDrawable(drawable);
- if (drawable instanceof AnimatedVectorDrawable) {
- ((AnimatedVectorDrawable) drawable).start();
- }
- if (Flags.enableOutputSwitcherDeviceGrouping()) {
- mEndClickIcon.setContentDescription(mContext.getString(accessibilityStringId));
- }
- }
-
- public void updateEndAreaColor(int color) {
- mEndTouchArea.setBackgroundTintList(
- ColorStateList.valueOf(color));
- }
-
@Nullable
private View.OnClickListener getClickListenerBasedOnSelectionBehavior(
@NonNull MediaDevice device) {
@@ -427,57 +310,12 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter {
}
}
- void updateDeviceStatusIcon(@Nullable Drawable deviceStatusIcon) {
- if (deviceStatusIcon == null) {
- mStatusIcon.setVisibility(View.GONE);
- } else {
- mStatusIcon.setImageDrawable(deviceStatusIcon);
- mStatusIcon.setImageTintList(
- ColorStateList.valueOf(mController.getColorItemContent()));
- if (deviceStatusIcon instanceof AnimatedVectorDrawable) {
- ((AnimatedVectorDrawable) deviceStatusIcon).start();
- }
- mStatusIcon.setVisibility(View.VISIBLE);
- }
- }
-
- public void updateEndAreaForGroupCheckBox(@NonNull MediaDevice device,
- @NonNull GroupStatus groupStatus) {
- boolean isEnabled = isGroupCheckboxEnabled(groupStatus);
- mEndTouchArea.setOnClickListener(
- isEnabled ? (v) -> mCheckBox.performClick() : null);
- mEndTouchArea.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
- updateEndAreaColor(groupStatus.selected() ? mController.getColorSeekbarProgress()
- : mController.getColorItemBackground());
- mEndTouchArea.setContentDescription(getDeviceItemContentDescription(device));
- mCheckBox.setOnCheckedChangeListener(null);
- mCheckBox.setChecked(groupStatus.selected());
- mCheckBox.setOnCheckedChangeListener(
- isEnabled ? (buttonView, isChecked) -> onGroupActionTriggered(
- !groupStatus.selected(), device) : null);
- mCheckBox.setEnabled(isEnabled);
- setCheckBoxColor(mCheckBox, mController.getColorItemContent());
- }
-
- private void updateFullItemClickListener(@Nullable View.OnClickListener listener) {
- mContainerLayout.setOnClickListener(listener);
- updateIconAreaClickListener(listener);
- }
-
- /** Binds a ViewHolder for a "Connect a device" item. */
- void onBindPairNewDevice() {
- mTitleText.setTextColor(mController.getColorItemContent());
- mCheckBox.setVisibility(View.GONE);
- updateTitle(mContext.getText(R.string.media_output_dialog_pairing_new));
- updateItemBackground(ConnectionState.DISCONNECTED);
- final Drawable addDrawable = mContext.getDrawable(R.drawable.ic_add);
- mTitleIcon.setImageDrawable(addDrawable);
- mTitleIcon.setImageTintList(
- ColorStateList.valueOf(mController.getColorItemContent()));
- mContainerLayout.setOnClickListener(mController::launchBluetoothPairing);
+ protected void onExpandGroupButtonClicked() {
+ mShouldGroupSelectedMediaItems = false;
+ notifyDataSetChanged();
}
- private void onGroupActionTriggered(boolean isChecked, MediaDevice device) {
+ protected void onGroupActionTriggered(boolean isChecked, MediaDevice device) {
disableSeekBar();
if (isChecked && isDeviceIncluded(mController.getSelectableMediaDevice(), device)) {
mController.addDeviceToPlayMedia(device);
@@ -523,32 +361,18 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter {
notifyDataSetChanged();
}
- private String getDeviceItemContentDescription(@NonNull MediaDevice device) {
+ protected String getDeviceItemContentDescription(@NonNull MediaDevice device) {
return mContext.getString(
device.getDeviceType() == MediaDevice.MediaDeviceType.TYPE_BLUETOOTH_DEVICE
? R.string.accessibility_bluetooth_name
: R.string.accessibility_cast_name, device.getName());
}
- private String getGroupItemContentDescription(String sessionName) {
+ protected String getGroupItemContentDescription(String sessionName) {
return mContext.getString(R.string.accessibility_cast_name, sessionName);
}
}
- class MediaGroupDividerViewHolder extends RecyclerView.ViewHolder {
- final TextView mTitleText;
-
- MediaGroupDividerViewHolder(@NonNull View itemView) {
- super(itemView);
- mTitleText = itemView.requireViewById(R.id.title);
- }
-
- void onBind(String groupDividerTitle) {
- mTitleText.setTextColor(mController.getColorItemContent());
- mTitleText.setText(groupDividerTitle);
- }
- }
-
@RequiresApi(34)
private static class Api34Impl {
@DoNotInline
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapterLegacy.java
index f97b3d3d5e38..565b2e41f75a 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapterLegacy.java
@@ -18,14 +18,18 @@ package com.android.systemui.media.dialog;
import android.animation.Animator;
import android.animation.ValueAnimator;
-import android.app.WallpaperColors;
+import android.annotation.DrawableRes;
+import android.annotation.StringRes;
import android.content.Context;
import android.content.res.ColorStateList;
+import android.graphics.drawable.AnimatedVectorDrawable;
import android.graphics.drawable.ClipDrawable;
+import android.graphics.drawable.Drawable;
import android.graphics.drawable.GradientDrawable;
import android.graphics.drawable.Icon;
import android.graphics.drawable.LayerDrawable;
import android.text.TextUtils;
+import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -40,6 +44,7 @@ import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
+import androidx.core.widget.CompoundButtonCompat;
import androidx.recyclerview.widget.RecyclerView;
import com.android.media.flags.Flags;
@@ -48,82 +53,67 @@ import com.android.settingslib.media.MediaDevice;
import com.android.settingslib.utils.ThreadUtils;
import com.android.systemui.res.R;
-import java.util.List;
-
/**
- * Base adapter for media output dialog.
+ * A RecyclerView adapter for the legacy UI media output dialog device list.
*/
-public abstract class MediaOutputBaseAdapter extends
- RecyclerView.Adapter<RecyclerView.ViewHolder> {
-
- record OngoingSessionStatus(boolean host) {}
-
- record GroupStatus(Boolean selected, Boolean deselectable) {}
-
- enum ConnectionState {
- CONNECTED,
- CONNECTING,
- DISCONNECTED,
- }
-
- protected final MediaSwitchingController mController;
+public class MediaOutputAdapterLegacy extends MediaOutputAdapterBase {
+ private static final String TAG = "MediaOutputAdapterL";
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
private static final int UNMUTE_DEFAULT_VOLUME = 2;
-
- Context mContext;
+ private static final float DEVICE_DISABLED_ALPHA = 0.5f;
+ private static final float DEVICE_ACTIVE_ALPHA = 1f;
View mHolderView;
- boolean mIsDragging;
- int mCurrentActivePosition;
private boolean mIsInitVolumeFirstTime;
- public MediaOutputBaseAdapter(MediaSwitchingController controller) {
- mController = controller;
- mIsDragging = false;
- mCurrentActivePosition = -1;
+ public MediaOutputAdapterLegacy(MediaSwitchingController controller) {
+ super(controller);
mIsInitVolumeFirstTime = true;
}
- /**
- * Refresh current dataset
- */
- public abstract void updateItems();
-
@Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup,
int viewType) {
- mContext = viewGroup.getContext();
- mHolderView = LayoutInflater.from(mContext).inflate(MediaItem.getMediaLayoutId(viewType),
- viewGroup, false);
- return null;
- }
-
- void updateColorScheme(WallpaperColors wallpaperColors, boolean isDarkTheme) {
- mController.setCurrentColorScheme(wallpaperColors, isDarkTheme);
- }
+ Context context = viewGroup.getContext();
+ mHolderView = LayoutInflater.from(viewGroup.getContext()).inflate(
+ MediaItem.getMediaLayoutId(viewType),
+ viewGroup, false);
- boolean isCurrentlyConnected(MediaDevice device) {
- return TextUtils.equals(device.getId(),
- mController.getCurrentConnectedMediaDevice().getId())
- || (mController.getSelectedMediaDevice().size() == 1
- && isDeviceIncluded(mController.getSelectedMediaDevice(), device));
+ switch (viewType) {
+ case MediaItem.MediaItemType.TYPE_GROUP_DIVIDER:
+ return new MediaGroupDividerViewHolderLegacy(mHolderView);
+ case MediaItem.MediaItemType.TYPE_PAIR_NEW_DEVICE:
+ case MediaItem.MediaItemType.TYPE_DEVICE:
+ default:
+ return new MediaDeviceViewHolderLegacy(mHolderView, context);
+ }
}
- boolean isDeviceIncluded(List<MediaDevice> deviceList, MediaDevice targetDevice) {
- for (MediaDevice device : deviceList) {
- if (TextUtils.equals(device.getId(), targetDevice.getId())) {
- return true;
+ @Override
+ public void onBindViewHolder(@NonNull RecyclerView.ViewHolder viewHolder, int position) {
+ if (position >= getItemCount()) {
+ if (DEBUG) {
+ Log.d(TAG, "Incorrect position: " + position + " list size: "
+ + getItemCount());
}
+ return;
+ }
+ MediaItem currentMediaItem = mMediaItemList.get(position);
+ switch (currentMediaItem.getMediaItemType()) {
+ case MediaItem.MediaItemType.TYPE_GROUP_DIVIDER:
+ ((MediaGroupDividerViewHolderLegacy) viewHolder).onBind(
+ currentMediaItem.getTitle());
+ break;
+ case MediaItem.MediaItemType.TYPE_PAIR_NEW_DEVICE:
+ ((MediaDeviceViewHolderLegacy) viewHolder).onBindPairNewDevice();
+ break;
+ case MediaItem.MediaItemType.TYPE_DEVICE:
+ ((MediaDeviceViewHolderLegacy) viewHolder).onBindDevice(currentMediaItem, position);
+ break;
+ default:
+ Log.d(TAG, "Incorrect position: " + position);
}
- return false;
- }
-
- boolean isDragging() {
- return mIsDragging;
- }
-
- int getCurrentActivePosition() {
- return mCurrentActivePosition;
}
public MediaSwitchingController getController() {
@@ -133,7 +123,7 @@ public abstract class MediaOutputBaseAdapter extends
/**
* ViewHolder for binding device view.
*/
- abstract class MediaDeviceBaseViewHolder extends RecyclerView.ViewHolder {
+ class MediaDeviceViewHolderLegacy extends MediaDeviceViewHolderBase {
private static final int ANIM_DURATION = 500;
@@ -158,8 +148,8 @@ public abstract class MediaOutputBaseAdapter extends
private ValueAnimator mVolumeAnimator;
private int mLatestUpdateVolume = -1;
- MediaDeviceBaseViewHolder(View view) {
- super(view);
+ MediaDeviceViewHolderLegacy(View view, Context context) {
+ super(view, context);
mContainerLayout = view.requireViewById(R.id.device_container);
mItemLayout = view.requireViewById(R.id.item_layout);
mTitleText = view.requireViewById(R.id.title);
@@ -180,8 +170,10 @@ public abstract class MediaOutputBaseAdapter extends
initAnimator();
}
- void onBind(MediaDevice device, int position) {
+ void onBindDevice(MediaItem mediaItem, int position) {
+ MediaDevice device = mediaItem.getMediaDevice().get();
mDeviceId = device.getId();
+ mItemLayout.setVisibility(View.VISIBLE);
mCheckBox.setVisibility(View.GONE);
mStatusIcon.setVisibility(View.GONE);
mEndTouchArea.setVisibility(View.GONE);
@@ -196,6 +188,54 @@ public abstract class MediaOutputBaseAdapter extends
mSeekBar.setProgressTintList(
ColorStateList.valueOf(mController.getColorSeekbarProgress()));
enableFocusPropertyForView(mContainerLayout);
+ renderItem(mediaItem, position);
+ }
+
+ /** Binds a ViewHolder for a "Connect a device" item. */
+ void onBindPairNewDevice() {
+ mTitleText.setTextColor(mController.getColorItemContent());
+ mCheckBox.setVisibility(View.GONE);
+ updateTitle(mContext.getText(R.string.media_output_dialog_pairing_new));
+ updateItemBackground(ConnectionState.DISCONNECTED);
+ final Drawable addDrawable = mContext.getDrawable(R.drawable.ic_add);
+ mTitleIcon.setImageDrawable(addDrawable);
+ mTitleIcon.setImageTintList(
+ ColorStateList.valueOf(mController.getColorItemContent()));
+ mContainerLayout.setOnClickListener(mController::launchBluetoothPairing);
+ }
+
+ @Override
+ protected void renderDeviceItem(boolean hideGroupItem, MediaDevice device,
+ ConnectionState connectionState, boolean restrictVolumeAdjustment,
+ GroupStatus groupStatus, OngoingSessionStatus ongoingSessionStatus,
+ View.OnClickListener clickListener, boolean deviceDisabled, String subtitle,
+ Drawable deviceStatusIcon) {
+ if (hideGroupItem) {
+ mItemLayout.setVisibility(View.GONE);
+ return;
+ }
+ updateTitle(device.getName());
+ updateTitleIcon(device, connectionState, restrictVolumeAdjustment);
+ updateSeekBar(device, connectionState, restrictVolumeAdjustment,
+ getDeviceItemContentDescription(device));
+ updateEndArea(device, connectionState, groupStatus, ongoingSessionStatus);
+ updateLoadingIndicator(connectionState);
+ updateFullItemClickListener(clickListener);
+ updateContentAlpha(deviceDisabled);
+ updateSubtitle(subtitle);
+ updateDeviceStatusIcon(deviceStatusIcon);
+ updateItemBackground(connectionState);
+ }
+
+ @Override
+ protected void renderDeviceGroupItem() {
+ String sessionName = mController.getSessionName() == null ? ""
+ : mController.getSessionName().toString();
+ updateTitle(sessionName);
+ updateUnmutedVolumeIcon(null /* device */);
+ updateGroupSeekBar(getGroupItemContentDescription(sessionName));
+ updateEndAreaForDeviceGroup();
+ updateItemBackground(ConnectionState.CONNECTED);
}
void updateTitle(CharSequence title) {
@@ -303,7 +343,7 @@ public abstract class MediaOutputBaseAdapter extends
private void initializeSeekbarVolume(
@Nullable MediaDevice device, int currentVolume,
boolean isCurrentSeekbarInvisible) {
- if (!mIsDragging) {
+ if (!isDragging()) {
if (mSeekBar.getVolume() != currentVolume && (mLatestUpdateVolume == -1
|| currentVolume == mLatestUpdateVolume)) {
// Update only if volume of device and value of volume bar doesn't match.
@@ -459,6 +499,132 @@ public abstract class MediaOutputBaseAdapter extends
: R.drawable.media_output_icon_volume;
}
+ private void updateContentAlpha(boolean deviceDisabled) {
+ float alphaValue = deviceDisabled ? DEVICE_DISABLED_ALPHA : DEVICE_ACTIVE_ALPHA;
+ mTitleIcon.setAlpha(alphaValue);
+ mTitleText.setAlpha(alphaValue);
+ mSubTitleText.setAlpha(alphaValue);
+ mStatusIcon.setAlpha(alphaValue);
+ }
+
+ private void updateDeviceStatusIcon(@Nullable Drawable deviceStatusIcon) {
+ if (deviceStatusIcon == null) {
+ mStatusIcon.setVisibility(View.GONE);
+ } else {
+ mStatusIcon.setImageDrawable(deviceStatusIcon);
+ mStatusIcon.setImageTintList(
+ ColorStateList.valueOf(mController.getColorItemContent()));
+ if (deviceStatusIcon instanceof AnimatedVectorDrawable) {
+ ((AnimatedVectorDrawable) deviceStatusIcon).start();
+ }
+ mStatusIcon.setVisibility(View.VISIBLE);
+ }
+ }
+
+
+ /** Renders the right side round pill button / checkbox. */
+ private void updateEndArea(@NonNull MediaDevice device, ConnectionState connectionState,
+ @Nullable GroupStatus groupStatus,
+ @Nullable OngoingSessionStatus ongoingSessionStatus) {
+ boolean showEndArea = false;
+ boolean isCheckbox = false;
+ // If both group status and the ongoing session status are present, only the ongoing
+ // session controls are displayed. The current layout design doesn't allow both group
+ // and ongoing session controls to be rendered simultaneously.
+ if (ongoingSessionStatus != null && connectionState == ConnectionState.CONNECTED) {
+ showEndArea = true;
+ updateEndAreaForOngoingSession(device, ongoingSessionStatus.host());
+ } else if (groupStatus != null && shouldShowGroupCheckbox(groupStatus)) {
+ showEndArea = true;
+ isCheckbox = true;
+ updateEndAreaForGroupCheckBox(device, groupStatus);
+ }
+ updateEndAreaVisibility(showEndArea, isCheckbox);
+ }
+
+ private void updateEndAreaForDeviceGroup() {
+ updateEndAreaWithIcon(
+ v -> {
+ onExpandGroupButtonClicked();
+ },
+ R.drawable.media_output_item_expand_group,
+ R.string.accessibility_expand_group);
+ updateEndAreaVisibility(true /* showEndArea */, false /* isCheckbox */);
+ }
+
+ private void updateEndAreaForOngoingSession(@NonNull MediaDevice device, boolean isHost) {
+ updateEndAreaWithIcon(
+ v -> mController.tryToLaunchInAppRoutingIntent(device.getId(), v),
+ isHost ? R.drawable.media_output_status_edit_session
+ : R.drawable.ic_sound_bars_anim,
+ R.string.accessibility_open_application);
+ }
+
+ private void updateEndAreaWithIcon(View.OnClickListener clickListener,
+ @DrawableRes int iconDrawableId,
+ @StringRes int accessibilityStringId) {
+ updateEndAreaColor(mController.getColorSeekbarProgress());
+ mEndClickIcon.setImageTintList(
+ ColorStateList.valueOf(mController.getColorItemContent()));
+ mEndClickIcon.setOnClickListener(clickListener);
+ mEndTouchArea.setOnClickListener(v -> mEndClickIcon.performClick());
+ Drawable drawable = mContext.getDrawable(iconDrawableId);
+ mEndClickIcon.setImageDrawable(drawable);
+ if (drawable instanceof AnimatedVectorDrawable) {
+ ((AnimatedVectorDrawable) drawable).start();
+ }
+ if (Flags.enableOutputSwitcherDeviceGrouping()) {
+ mEndClickIcon.setContentDescription(mContext.getString(accessibilityStringId));
+ }
+ }
+
+ private void updateEndAreaForGroupCheckBox(@NonNull MediaDevice device,
+ @NonNull GroupStatus groupStatus) {
+ boolean isEnabled = isGroupCheckboxEnabled(groupStatus);
+ mEndTouchArea.setOnClickListener(
+ isEnabled ? (v) -> mCheckBox.performClick() : null);
+ mEndTouchArea.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
+ updateEndAreaColor(groupStatus.selected() ? mController.getColorSeekbarProgress()
+ : mController.getColorItemBackground());
+ mEndTouchArea.setContentDescription(getDeviceItemContentDescription(device));
+ mCheckBox.setOnCheckedChangeListener(null);
+ mCheckBox.setChecked(groupStatus.selected());
+ mCheckBox.setOnCheckedChangeListener(
+ isEnabled ? (buttonView, isChecked) -> onGroupActionTriggered(
+ !groupStatus.selected(), device) : null);
+ mCheckBox.setEnabled(isEnabled);
+ setCheckBoxColor(mCheckBox, mController.getColorItemContent());
+ }
+
+ private void setCheckBoxColor(CheckBox checkBox, int color) {
+ int[][] states = {{android.R.attr.state_checked}, {}};
+ int[] colors = {color, color};
+ CompoundButtonCompat.setButtonTintList(checkBox, new
+ ColorStateList(states, colors));
+ }
+
+ private boolean shouldShowGroupCheckbox(@NonNull GroupStatus groupStatus) {
+ if (Flags.enableOutputSwitcherDeviceGrouping()) {
+ return isGroupCheckboxEnabled(groupStatus);
+ }
+ return true;
+ }
+
+ private boolean isGroupCheckboxEnabled(@NonNull GroupStatus groupStatus) {
+ boolean disabled = groupStatus.selected() && !groupStatus.deselectable();
+ return !disabled;
+ }
+
+ private void updateEndAreaColor(int color) {
+ mEndTouchArea.setBackgroundTintList(
+ ColorStateList.valueOf(color));
+ }
+
+ private void updateFullItemClickListener(@Nullable View.OnClickListener listener) {
+ mContainerLayout.setOnClickListener(listener);
+ updateIconAreaClickListener(listener);
+ }
+
void updateIconAreaClickListener(@Nullable View.OnClickListener listener) {
mIconAreaLayout.setOnClickListener(listener);
}
@@ -498,6 +664,7 @@ public abstract class MediaOutputBaseAdapter extends
});
}
+ @Override
protected void disableSeekBar() {
mSeekBar.setEnabled(false);
mSeekBar.setOnTouchListener((v, event) -> true);
@@ -589,7 +756,7 @@ public abstract class MediaOutputBaseAdapter extends
int currentVolume = MediaOutputSeekbar.scaleProgressToVolume(
seekBar.getProgress());
mStartFromMute = (currentVolume == 0);
- mIsDragging = true;
+ setIsDragging(true);
}
@Override
@@ -604,11 +771,25 @@ public abstract class MediaOutputBaseAdapter extends
}
mTitleIcon.setVisibility(View.VISIBLE);
mVolumeValueText.setVisibility(View.GONE);
- mIsDragging = false;
+ setIsDragging(false);
}
protected boolean shouldHandleProgressChanged() {
return mMediaDevice != null;
}
};
}
+
+ class MediaGroupDividerViewHolderLegacy extends RecyclerView.ViewHolder {
+ final TextView mTitleText;
+
+ MediaGroupDividerViewHolderLegacy(@NonNull View itemView) {
+ super(itemView);
+ mTitleText = itemView.requireViewById(R.id.title);
+ }
+
+ void onBind(String groupDividerTitle) {
+ mTitleText.setTextColor(mController.getColorItemContent());
+ mTitleText.setText(groupDividerTitle);
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java
index 64256f97fd78..d791361d555f 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java
@@ -105,7 +105,7 @@ public abstract class MediaOutputBaseDialog extends SystemUIDialog
private boolean mIsLeBroadcastCallbackRegistered;
private boolean mDismissing;
- MediaOutputBaseAdapter mAdapter;
+ MediaOutputAdapterBase mAdapter;
protected Executor mExecutor;
@@ -342,7 +342,7 @@ public abstract class MediaOutputBaseDialog extends SystemUIDialog
WallpaperColors wallpaperColors = WallpaperColors.fromBitmap(icon.getBitmap());
colorSetUpdated = !wallpaperColors.equals(mWallpaperColors);
if (colorSetUpdated) {
- mAdapter.updateColorScheme(wallpaperColors, isDarkThemeOn);
+ mMediaSwitchingController.setCurrentColorScheme(wallpaperColors, isDarkThemeOn);
updateButtonBackgroundColorFilter();
updateDialogBackgroundColor();
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialog.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialog.java
index 9b5b872a00db..9ade9e275ca1 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialog.java
@@ -245,7 +245,7 @@ public class MediaOutputBroadcastDialog extends MediaOutputBaseDialog {
broadcastSender,
mediaSwitchingController, /* includePlaybackAndAppMetadata */
true);
- mAdapter = new MediaOutputAdapter(mMediaSwitchingController);
+ mAdapter = new MediaOutputAdapterLegacy(mMediaSwitchingController);
// TODO(b/226710953): Move the part to MediaOutputBaseDialog for every class
// that extends MediaOutputBaseDialog
if (!aboveStatusbar) {
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java
index c9af7b322811..2e602be4556e 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java
@@ -53,7 +53,7 @@ public class MediaOutputDialog extends MediaOutputBaseDialog {
super(context, broadcastSender, mediaSwitchingController, includePlaybackAndAppMetadata);
mDialogTransitionAnimator = dialogTransitionAnimator;
mUiEventLogger = uiEventLogger;
- mAdapter = new MediaOutputAdapter(mMediaSwitchingController);
+ mAdapter = new MediaOutputAdapterLegacy(mMediaSwitchingController);
if (!aboveStatusbar) {
getWindow().setType(WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY);
}
diff --git a/packages/SystemUI/src/com/android/systemui/modes/shared/ModesUi.kt b/packages/SystemUI/src/com/android/systemui/modes/shared/ModesUi.kt
index a0663d72a076..e293e202633e 100644
--- a/packages/SystemUI/src/com/android/systemui/modes/shared/ModesUi.kt
+++ b/packages/SystemUI/src/com/android/systemui/modes/shared/ModesUi.kt
@@ -25,7 +25,7 @@ object ModesUi {
/** Is the refactor enabled */
@JvmStatic
inline val isEnabled
- get() = Flags.modesApi() && Flags.modesUi()
+ get() = Flags.modesUi()
/**
* 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/notifications/ui/viewmodel/NotificationsShadeOverlayContentViewModel.kt b/packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayContentViewModel.kt
index c7b165415aea..c43c1a999fcb 100644
--- a/packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayContentViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayContentViewModel.kt
@@ -20,12 +20,15 @@ import androidx.compose.runtime.getValue
import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.systemui.lifecycle.ExclusiveActivatable
import com.android.systemui.lifecycle.Hydrator
+import com.android.systemui.media.controls.domain.pipeline.interactor.MediaCarouselInteractor
import com.android.systemui.scene.domain.interactor.SceneInteractor
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.shade.ui.viewmodel.ShadeHeaderViewModel
+import com.android.systemui.statusbar.disableflags.domain.interactor.DisableFlagsInteractor
import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel
+import com.android.systemui.utils.coroutines.flow.flatMapLatestConflated
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import kotlinx.coroutines.awaitCancellation
@@ -33,6 +36,7 @@ import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.flowOf
/**
* Models UI state used to render the content of the notifications shade overlay.
@@ -47,6 +51,8 @@ constructor(
val notificationsPlaceholderViewModelFactory: NotificationsPlaceholderViewModel.Factory,
val sceneInteractor: SceneInteractor,
private val shadeInteractor: ShadeInteractor,
+ disableFlagsInteractor: DisableFlagsInteractor,
+ mediaCarouselInteractor: MediaCarouselInteractor,
activeNotificationsInteractor: ActiveNotificationsInteractor,
) : ExclusiveActivatable() {
@@ -69,6 +75,22 @@ constructor(
),
)
+ val showMedia: Boolean by
+ hydrator.hydratedStateOf(
+ traceName = "showMedia",
+ initialValue =
+ disableFlagsInteractor.disableFlags.value.isQuickSettingsEnabled() &&
+ mediaCarouselInteractor.hasActiveMediaOrRecommendation.value,
+ source =
+ disableFlagsInteractor.disableFlags.flatMapLatestConflated {
+ if (it.isQuickSettingsEnabled()) {
+ mediaCarouselInteractor.hasActiveMediaOrRecommendation
+ } else {
+ flowOf(false)
+ }
+ },
+ )
+
override suspend fun onActivated(): Nothing {
coroutineScope {
launch { hydrator.activate() }
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogV2.kt b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogV2.kt
index 57d40638b8df..9117afb1de6f 100644
--- a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogV2.kt
+++ b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogV2.kt
@@ -39,6 +39,11 @@ import androidx.annotation.DrawableRes
import androidx.annotation.WorkerThread
import androidx.core.view.ViewCompat
import androidx.core.view.accessibility.AccessibilityNodeInfoCompat
+import androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat
+import androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_COLLAPSE
+import androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_EXPAND
+import androidx.core.view.accessibility.AccessibilityViewCommand
+import com.android.systemui.Flags
import com.android.systemui.animation.ViewHierarchyAnimator
import com.android.systemui.res.R
import com.android.systemui.statusbar.phone.SystemUIDialog
@@ -282,49 +287,95 @@ class PrivacyDialogV2(
val expandToggle =
itemHeader.findViewById<ImageView>(R.id.privacy_dialog_item_header_expand_toggle)!!
- expandToggle.setImageResource(R.drawable.privacy_dialog_expand_toggle_down)
expandToggle.visibility = View.VISIBLE
-
- ViewCompat.replaceAccessibilityAction(
- itemCard,
- AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_CLICK,
- context.getString(R.string.privacy_dialog_expand_action),
- null,
- )
-
val expandedLayout =
itemCard.findViewById<View>(R.id.privacy_dialog_item_header_expanded_layout)!!
expandedLayout.setOnClickListener {
// Stop clicks from propagating
}
- itemCard.setOnClickListener {
- if (expandedLayout.visibility == View.VISIBLE) {
- expandedLayout.visibility = View.GONE
- expandToggle.setImageResource(R.drawable.privacy_dialog_expand_toggle_down)
- ViewCompat.replaceAccessibilityAction(
- it!!,
- AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_CLICK,
- context.getString(R.string.privacy_dialog_expand_action),
- null,
- )
- } else {
- expandedLayout.visibility = View.VISIBLE
- expandToggle.setImageResource(R.drawable.privacy_dialog_expand_toggle_up)
- ViewCompat.replaceAccessibilityAction(
- it!!,
- AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_CLICK,
- context.getString(R.string.privacy_dialog_collapse_action),
- null,
- )
+ if (Flags.expandCollapsePrivacyDialog()) {
+ updateExpansion(ACTION_COLLAPSE, itemCard, expandedLayout, expandToggle)
+
+ itemCard.setOnClickListener {
+ if (expandedLayout.visibility == View.VISIBLE) {
+ updateExpansion(ACTION_COLLAPSE, it!!, expandedLayout, expandToggle)
+ } else {
+ updateExpansion(ACTION_EXPAND, it!!, expandedLayout, expandToggle)
+ }
}
- ViewHierarchyAnimator.animateNextUpdate(
- rootView = window!!.decorView,
- excludedViews = setOf(expandedLayout),
+ } else {
+ expandToggle.setImageResource(R.drawable.privacy_dialog_expand_toggle_down)
+ ViewCompat.replaceAccessibilityAction(
+ itemCard,
+ AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_CLICK,
+ context.getString(R.string.privacy_dialog_expand_action),
+ null,
)
+
+ itemCard.setOnClickListener {
+ if (expandedLayout.visibility == View.VISIBLE) {
+ expandedLayout.visibility = View.GONE
+ expandToggle.setImageResource(R.drawable.privacy_dialog_expand_toggle_down)
+ ViewCompat.replaceAccessibilityAction(
+ it!!,
+ AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_CLICK,
+ context.getString(R.string.privacy_dialog_expand_action),
+ null,
+ )
+ } else {
+ expandedLayout.visibility = View.VISIBLE
+ expandToggle.setImageResource(R.drawable.privacy_dialog_expand_toggle_up)
+ ViewCompat.replaceAccessibilityAction(
+ it!!,
+ AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_CLICK,
+ context.getString(R.string.privacy_dialog_collapse_action),
+ null,
+ )
+ }
+ ViewHierarchyAnimator.animateNextUpdate(
+ rootView = window!!.decorView,
+ excludedViews = setOf(expandedLayout),
+ )
+ }
}
}
+ private fun updateExpansion(
+ newState: AccessibilityActionCompat,
+ itemCard: View,
+ expandedLayout: View,
+ expandToggle: ImageView,
+ ) {
+ expandedLayout.visibility = if (newState == ACTION_COLLAPSE) View.GONE else View.VISIBLE
+ expandToggle.setImageResource(
+ if (newState == ACTION_COLLAPSE) R.drawable.privacy_dialog_expand_toggle_down
+ else R.drawable.privacy_dialog_expand_toggle_up
+ )
+ val accessibilityString =
+ context.getString(
+ if (newState == ACTION_COLLAPSE) R.string.privacy_dialog_expand_action
+ else R.string.privacy_dialog_collapse_action
+ )
+ ViewCompat.replaceAccessibilityAction(
+ itemCard,
+ AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_CLICK,
+ accessibilityString,
+ null,
+ )
+ val expandCollapseAccessibilityListener =
+ AccessibilityViewCommand { view: View, _: AccessibilityViewCommand.CommandArguments? ->
+ view.callOnClick()
+ }
+ ViewCompat.replaceAccessibilityAction(
+ itemCard,
+ if (newState == ACTION_COLLAPSE) ACTION_EXPAND else ACTION_COLLAPSE,
+ accessibilityString,
+ expandCollapseAccessibilityListener,
+ )
+ ViewCompat.removeAccessibilityAction(itemCard, newState.id)
+ }
+
private fun updateIconView(iconView: ImageView, indicatorIcon: Drawable, active: Boolean) {
indicatorIcon.setTint(getForegroundColor(active))
val backgroundIcon = getMutableDrawable(R.drawable.privacy_dialog_background_circle)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeViewModel.kt
index 2082423f1fd3..31323c749238 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeViewModel.kt
@@ -42,6 +42,8 @@ import javax.inject.Inject
import javax.inject.Named
import kotlinx.coroutines.CoroutineDispatcher
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.StateFlow
@@ -53,6 +55,7 @@ import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
+@OptIn(ExperimentalCoroutinesApi::class)
@SysUISingleton
class EditModeViewModel
@Inject
@@ -73,11 +76,9 @@ constructor(
private val _isEditing = MutableStateFlow(false)
/**
- * Whether we should be editing right now. Use [startEditing] and [stopEditing] to change this
+ * Whether we should be editing right now. Use [startEditing] and [stopEditing] to change this.
*/
val isEditing = _isEditing.asStateFlow()
- private val minimumTiles: Int
- get() = minTilesInteractor.minNumberOfTiles
val gridLayout: StateFlow<GridLayout> =
gridLayoutTypeInteractor.layout
@@ -99,7 +100,7 @@ constructor(
* * Tiles that are not available will be filtered out. None of them can be current (as they
* cannot be created), and they won't be able to be added.
*/
- val tiles =
+ val tiles: Flow<List<EditTileViewModel>> =
isEditing.flatMapLatest {
if (it) {
val editTilesData = editTilesListInteractor.getTilesToEdit()
@@ -114,10 +115,10 @@ constructor(
currentTilesInteractor.currentTiles
.map { tiles ->
val currentSpecs = tiles.map { it.spec }
- val canRemoveTiles = currentSpecs.size > minimumTiles
+ val canRemoveTiles = currentSpecs.size > minTilesInteractor.minNumberOfTiles
val allTiles = editTilesData.stockTiles + editTilesData.customTiles
- val allTilesMap = allTiles.associate { it.tileSpec to it }
- val currentTiles = currentSpecs.map { allTilesMap.get(it) }.filterNotNull()
+ val allTilesMap = allTiles.associateBy { it.tileSpec }
+ val currentTiles = currentSpecs.mapNotNull { allTilesMap[it] }
val nonCurrentTiles = allTiles.filter { it.tileSpec !in currentSpecs }
(currentTiles + nonCurrentTiles)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/SubtitleArrayMapping.kt b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/SubtitleArrayMapping.kt
index c9a0635021da..61a8fa3d2a6e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/SubtitleArrayMapping.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/SubtitleArrayMapping.kt
@@ -55,6 +55,7 @@ object SubtitleArrayMapping {
subtitleIdsMap["font_scaling"] = R.array.tile_states_font_scaling
subtitleIdsMap["hearing_devices"] = R.array.tile_states_hearing_devices
subtitleIdsMap["notes"] = R.array.tile_states_notes
+ subtitleIdsMap["desktopeffects"] = R.array.tile_states_desktopeffects
}
/** Get the subtitle resource id of the given tile */
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateLegacy.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateLegacy.java
index c6bcab48fa68..75cb8ddca484 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateLegacy.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateLegacy.java
@@ -589,8 +589,10 @@ public class InternetDialogDelegateLegacy implements
}
mSecondaryMobileNetworkLayout = mDialogView.findViewById(
R.id.secondary_mobile_network_layout);
- mSecondaryMobileNetworkLayout.setOnClickListener(
- this::onClickConnectedSecondarySub);
+ if (mCanConfigMobileData) {
+ mSecondaryMobileNetworkLayout.setOnClickListener(
+ this::onClickConnectedSecondarySub);
+ }
mSecondaryMobileNetworkLayout.setBackground(mBackgroundOn);
TextView mSecondaryMobileTitleText = mDialogView.requireViewById(
@@ -623,6 +625,8 @@ public class InternetDialogDelegateLegacy implements
mDialogView.requireViewById(R.id.secondary_settings_icon);
mSecondaryMobileSettingsIcon.setColorFilter(
dialog.getContext().getColor(R.color.connected_network_primary_color));
+ mSecondaryMobileSettingsIcon.setVisibility(mCanConfigMobileData ?
+ View.VISIBLE : View.INVISIBLE);
// set secondary visual for default data sub
mMobileNetworkLayout.setBackground(mBackgroundOff);
diff --git a/packages/SystemUI/src/com/android/systemui/scene/data/repository/SceneContainerRepository.kt b/packages/SystemUI/src/com/android/systemui/scene/data/repository/SceneContainerRepository.kt
index caa7bbae0420..e357f63479dc 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/data/repository/SceneContainerRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/data/repository/SceneContainerRepository.kt
@@ -163,4 +163,12 @@ constructor(
fun setTransitionState(transitionState: Flow<ObservableTransitionState>?) {
_transitionState.value = transitionState
}
+
+ /**
+ * If currently in a transition between contents, cancel that transition and go back to the
+ * pre-transition state.
+ */
+ fun freezeAndAnimateToCurrentState() {
+ dataSource.freezeAndAnimateToCurrentState()
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt
index e9e7deca0abf..01180859b1d2 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt
@@ -234,6 +234,10 @@ constructor(
* The change is animated. Therefore, it will be some time before the UI will switch to the
* desired scene. Once enough of the transition has occurred, the [currentScene] will become
* [toScene] (unless the transition is canceled by user action or another call to this method).
+ *
+ * If [forceSettleToTargetScene] is `true` and the target scene is the same as the current
+ * scene, any current transition will be canceled and an animation to the target scene will be
+ * started.
*/
@JvmOverloads
fun changeScene(
@@ -241,9 +245,19 @@ constructor(
loggingReason: String,
transitionKey: TransitionKey? = null,
sceneState: Any? = null,
+ forceSettleToTargetScene: Boolean = false,
) {
val currentSceneKey = currentScene.value
val resolvedScene = sceneFamilyResolvers.get()[toScene]?.resolvedScene?.value ?: toScene
+
+ if (resolvedScene == currentSceneKey && forceSettleToTargetScene) {
+ logger.logSceneChangeCancellation(scene = resolvedScene, sceneState = sceneState)
+ onSceneAboutToChangeListener.forEach {
+ it.onSceneAboutToChange(resolvedScene, sceneState)
+ }
+ repository.freezeAndAnimateToCurrentState()
+ }
+
if (
!validateSceneChange(
from = currentSceneKey,
@@ -523,14 +537,32 @@ constructor(
}
if (from == to) {
+ logger.logSceneChangeRejection(
+ from = from,
+ to = to,
+ originalChangeReason = loggingReason,
+ rejectionReason = "${from.debugName} is the same as ${to.debugName}",
+ )
return false
}
if (to !in repository.allContentKeys) {
+ logger.logSceneChangeRejection(
+ from = from,
+ to = to,
+ originalChangeReason = loggingReason,
+ rejectionReason = "${to.debugName} isn't present in allContentKeys",
+ )
return false
}
if (disabledContentInteractor.isDisabled(to)) {
+ logger.logSceneChangeRejection(
+ from = from,
+ to = to,
+ originalChangeReason = loggingReason,
+ rejectionReason = "${to.debugName} is currently disabled",
+ )
return false
}
@@ -580,14 +612,58 @@ constructor(
}
if (to != null && disabledContentInteractor.isDisabled(to)) {
+ logger.logSceneChangeRejection(
+ from = from,
+ to = to,
+ originalChangeReason = loggingReason,
+ rejectionReason = "${to.debugName} is currently disabled",
+ )
return false
}
- val isFromValid = (from == null) || (from in currentOverlays.value)
- val isToValid =
- (to == null) || (to !in currentOverlays.value && to in repository.allContentKeys)
+ return when {
+ to != null && from != null && to == from -> {
+ logger.logSceneChangeRejection(
+ from = from,
+ to = to,
+ originalChangeReason = loggingReason,
+ rejectionReason = "${from.debugName} is the same as ${to.debugName}",
+ )
+ false
+ }
- return isFromValid && isToValid && from != to
+ to != null && to !in repository.allContentKeys -> {
+ logger.logSceneChangeRejection(
+ from = from,
+ to = to,
+ originalChangeReason = loggingReason,
+ rejectionReason = "${to.debugName} is not in allContentKeys",
+ )
+ false
+ }
+
+ from != null && from !in currentOverlays.value -> {
+ logger.logSceneChangeRejection(
+ from = from,
+ to = to,
+ originalChangeReason = loggingReason,
+ rejectionReason = "${from.debugName} is not a current overlay",
+ )
+ false
+ }
+
+ to != null && to in currentOverlays.value -> {
+ logger.logSceneChangeRejection(
+ from = from,
+ to = to,
+ originalChangeReason = loggingReason,
+ rejectionReason = "${to.debugName} is already a current overlay",
+ )
+ false
+ }
+
+ else -> true
+ }
}
/** Returns a flow indicating if the currently visible scene can be resolved from [family]. */
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/resolver/HomeSceneFamilyResolver.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/resolver/HomeSceneFamilyResolver.kt
index 140b231593bd..aab37d433e4f 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/resolver/HomeSceneFamilyResolver.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/resolver/HomeSceneFamilyResolver.kt
@@ -16,7 +16,6 @@
package com.android.systemui.scene.domain.resolver
-import android.util.Log
import com.android.compose.animation.scene.SceneKey
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
@@ -84,7 +83,7 @@ constructor(
isDreamingWithOverlay: Boolean,
isAbleToDream: Boolean,
): SceneKey {
- val result = when {
+ return when {
// Dream can run even if Keyguard is disabled, thus it has the highest priority here.
isDreamingWithOverlay && isAbleToDream -> Scenes.Dream
!isKeyguardEnabled -> Scenes.Gone
@@ -93,21 +92,9 @@ constructor(
!isUnlocked -> Scenes.Lockscreen
else -> Scenes.Gone
}
- Log.d(TAG, "homeScene emitting $result, values:")
- Log.d(TAG, " isKeyguardEnabled=$isKeyguardEnabled")
- Log.d(TAG, " canSwipeToEnter=$canSwipeToEnter")
- Log.d(TAG, " isDeviceEntered=$isDeviceEntered" )
- Log.d(TAG, " isUnlocked=$isUnlocked")
- Log.d(TAG, " isDreamingWithOverlay=$isDreamingWithOverlay")
- Log.d(TAG, " isAbleToDream=$isAbleToDream")
- Log.d(TAG, "")
- return result
}
companion object {
-
- private const val TAG = "HomeSceneFamilyResolver"
-
val homeScenes =
setOf(
Scenes.Gone,
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 94e32fcb9ac6..218ad477c45e 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
@@ -90,6 +90,7 @@ import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.combine
@@ -553,6 +554,7 @@ constructor(
targetSceneKey = Scenes.Lockscreen,
loggingReason = "device is starting to sleep",
sceneState = keyguardInteractor.asleepKeyguardState.value,
+ freezeAndAnimateToCurrentState = true,
)
} else {
val canSwipeToEnter = deviceEntryInteractor.canSwipeToEnter.value
@@ -610,15 +612,24 @@ constructor(
private fun handleShadeTouchability() {
applicationScope.launch {
- shadeInteractor.isShadeTouchable
- .distinctUntilChanged()
- .filter { !it }
- .collect {
- switchToScene(
- targetSceneKey = Scenes.Lockscreen,
- loggingReason = "device became non-interactive (SceneContainerStartable)",
- )
+ repeatWhen(deviceEntryInteractor.isDeviceEntered.map { !it }) {
+ // Run logic only when the device isn't entered.
+ repeatWhen(
+ sceneInteractor.transitionState.map { !it.isTransitioning(to = Scenes.Gone) }
+ ) {
+ // Run logic only when not transitioning to gone.
+ shadeInteractor.isShadeTouchable
+ .distinctUntilChanged()
+ .filter { !it }
+ .collect {
+ switchToScene(
+ targetSceneKey = Scenes.Lockscreen,
+ loggingReason =
+ "device became non-interactive (SceneContainerStartable)",
+ )
+ }
}
+ }
}
}
@@ -923,11 +934,13 @@ constructor(
targetSceneKey: SceneKey,
loggingReason: String,
sceneState: Any? = null,
+ freezeAndAnimateToCurrentState: Boolean = false,
) {
sceneInteractor.changeScene(
toScene = targetSceneKey,
loggingReason = loggingReason,
sceneState = sceneState,
+ forceSettleToTargetScene = freezeAndAnimateToCurrentState,
)
}
@@ -1013,6 +1026,14 @@ constructor(
}
}
+ private suspend fun repeatWhen(condition: Flow<Boolean>, block: suspend () -> Unit) {
+ condition.distinctUntilChanged().collectLatest { conditionMet ->
+ if (conditionMet) {
+ block()
+ }
+ }
+ }
+
companion object {
private const val TAG = "SceneContainerStartable"
}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/logger/SceneLogger.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/logger/SceneLogger.kt
index d00585858ccb..73c71f6088e1 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/shared/logger/SceneLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/shared/logger/SceneLogger.kt
@@ -16,6 +16,7 @@
package com.android.systemui.scene.shared.logger
+import com.android.compose.animation.scene.ContentKey
import com.android.compose.animation.scene.ObservableTransitionState
import com.android.compose.animation.scene.OverlayKey
import com.android.compose.animation.scene.SceneKey
@@ -74,6 +75,50 @@ class SceneLogger @Inject constructor(@SceneFrameworkLog private val logBuffer:
)
}
+ fun logSceneChangeCancellation(scene: SceneKey, sceneState: Any?) {
+ logBuffer.log(
+ tag = TAG,
+ level = LogLevel.INFO,
+ messageInitializer = {
+ str1 = scene.debugName
+ str2 = sceneState?.toString()
+ },
+ messagePrinter = { "CANCELED scene change. scene: $str1, sceneState: $str2" },
+ )
+ }
+
+ fun logSceneChangeRejection(
+ from: ContentKey?,
+ to: ContentKey?,
+ originalChangeReason: String,
+ rejectionReason: String,
+ ) {
+ logBuffer.log(
+ tag = TAG,
+ level = LogLevel.INFO,
+ messageInitializer = {
+ str1 = "${from?.debugName ?: "<none>"} → ${to?.debugName ?: "<none>"}"
+ str2 = rejectionReason
+ str3 = originalChangeReason
+ bool1 = to is OverlayKey
+ },
+ messagePrinter = {
+ buildString {
+ append("REJECTED ")
+ append(
+ if (bool1) {
+ "overlay "
+ } else {
+ "scene "
+ }
+ )
+ append("change $str1 because \"$str2\" ")
+ append("(original change reason: \"$str3\")")
+ }
+ },
+ )
+ }
+
fun logSceneTransition(transitionState: ObservableTransitionState) {
when (transitionState) {
is ObservableTransitionState.Transition -> {
diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneDataSource.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneDataSource.kt
index daf2d7f698b6..42c4b24a72d3 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneDataSource.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneDataSource.kt
@@ -83,4 +83,10 @@ interface SceneDataSource {
/** Asks for [overlay] to be instantly hidden, without an animated transition of any kind. */
fun instantlyHideOverlay(overlay: OverlayKey)
+
+ /**
+ * If currently in a transition between contents, cancel that transition and go back to the
+ * pre-transition state.
+ */
+ fun freezeAndAnimateToCurrentState()
}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneDataSourceDelegator.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneDataSourceDelegator.kt
index dcb699539760..d6dce38d0bbf 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneDataSourceDelegator.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneDataSourceDelegator.kt
@@ -82,6 +82,10 @@ class SceneDataSourceDelegator(applicationScope: CoroutineScope, config: SceneCo
delegateMutable.value.instantlyHideOverlay(overlay)
}
+ override fun freezeAndAnimateToCurrentState() {
+ delegateMutable.value.freezeAndAnimateToCurrentState()
+ }
+
/**
* Binds the current, dependency injection provided [SceneDataSource] to the given object.
*
@@ -120,5 +124,7 @@ class SceneDataSourceDelegator(applicationScope: CoroutineScope, config: SceneCo
override fun instantlyShowOverlay(overlay: OverlayKey) = Unit
override fun instantlyHideOverlay(overlay: OverlayKey) = Unit
+
+ override fun freezeAndAnimateToCurrentState() = Unit
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
index 305e71e48702..3be2f1b7b957 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
@@ -231,6 +231,9 @@ constructor(
*/
private var isDreaming = false
+ /** True if we should allow swiping open the glanceable hub. */
+ private var swipeToHubEnabled = false
+
/** Observes and logs state when the lifecycle that controls the [touchMonitor] updates. */
private val touchLifecycleLogger: LifecycleObserver = LifecycleEventObserver { _, event ->
logger.d({
@@ -438,6 +441,7 @@ constructor(
},
)
collectFlow(containerView, keyguardInteractor.isDreaming, { isDreaming = it })
+ collectFlow(containerView, communalViewModel.swipeToHubEnabled, { swipeToHubEnabled = it })
communalContainerWrapper = CommunalWrapper(containerView.context)
communalContainerWrapper?.addView(communalContainerView)
@@ -520,10 +524,7 @@ constructor(
val glanceableHubV2 = communalSettingsInteractor.isV2FlagEnabled()
if (
!hubShowing &&
- (touchOnNotifications ||
- touchOnUmo ||
- touchOnSmartspace ||
- !communalViewModel.swipeToHubEnabled())
+ (touchOnNotifications || touchOnUmo || touchOnSmartspace || !swipeToHubEnabled)
) {
logger.d({
"Lockscreen touch ignored: touchOnNotifications: $bool1, touchOnUmo: $bool2, " +
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 5746cef41d6b..131efaac3d6a 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -2201,7 +2201,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
@Override
@Deprecated
public void onStatusBarLongPress(MotionEvent event) {
- mShadeLog.d("Status Bar was long pressed.");
+ Log.i(TAG, "Status Bar was long pressed.");
ShadeExpandsOnStatusBarLongPress.assertInNewMode();
mStatusBarLongPressDowntime = event.getDownTime();
if (isTracking()) {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt
index f926d39760fe..96b224fbd4f3 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt
@@ -42,12 +42,12 @@ import com.android.systemui.shade.display.ShadeDisplayPolicyModule
import com.android.systemui.shade.domain.interactor.ShadeDialogContextInteractor
import com.android.systemui.shade.domain.interactor.ShadeDialogContextInteractorImpl
import com.android.systemui.shade.domain.interactor.ShadeDisplaysInteractor
-import com.android.systemui.shade.domain.interactor.ShadeExpandedStateInteractor
import com.android.systemui.shade.shared.flag.ShadeWindowGoesAround
+import com.android.systemui.statusbar.notification.stack.NotificationStackRebindingHider
+import com.android.systemui.statusbar.notification.stack.NotificationStackRebindingHiderImpl
import com.android.systemui.statusbar.phone.ConfigurationControllerImpl
import com.android.systemui.statusbar.phone.ConfigurationForwarder
import com.android.systemui.statusbar.policy.ConfigurationController
-import dagger.BindsOptionalOf
import dagger.Module
import dagger.Provides
import dagger.multibindings.ClassKey
@@ -67,7 +67,7 @@ import javax.inject.Qualifier
* By using this dedicated module, we ensure the notification shade window always utilizes the
* correct display context and resources, regardless of the display it's on.
*/
-@Module(includes = [OptionalShadeDisplayAwareBindings::class, ShadeDisplayPolicyModule::class])
+@Module(includes = [ShadeDisplayPolicyModule::class])
object ShadeDisplayAwareModule {
/** Creates a new context for the shade window. */
@@ -242,17 +242,6 @@ object ShadeDisplayAwareModule {
}
}
- @Provides
- @IntoMap
- @ClassKey(ShadeDisplaysInteractor::class)
- fun provideShadeDisplaysInteractor(impl: Provider<ShadeDisplaysInteractor>): CoreStartable {
- return if (ShadeWindowGoesAround.isEnabled) {
- impl.get()
- } else {
- CoreStartable.NOP
- }
- }
-
/**
* Provided for making classes easier to test. In tests, a custom method to wait for the next
* frame can be easily provided.
@@ -264,11 +253,25 @@ object ShadeDisplayAwareModule {
fun provideShadeOnDefaultDisplayWhenLocked(): Boolean = true
}
+/** Module that should be included only if the shade window [WindowRootView] is available. */
@Module
-internal interface OptionalShadeDisplayAwareBindings {
- @BindsOptionalOf fun bindOptionalOfWindowRootView(): WindowRootView
+object ShadeDisplayAwareWithShadeWindowModule {
+ @Provides
+ @IntoMap
+ @ClassKey(ShadeDisplaysInteractor::class)
+ fun provideShadeDisplaysInteractor(impl: Provider<ShadeDisplaysInteractor>): CoreStartable {
+ return if (ShadeWindowGoesAround.isEnabled) {
+ impl.get()
+ } else {
+ CoreStartable.NOP
+ }
+ }
- @BindsOptionalOf fun bindOptionalOShadeExpandedStateInteractor(): ShadeExpandedStateInteractor
+ @Provides
+ @SysUISingleton
+ fun bindNotificationStackRebindingHider(
+ impl: NotificationStackRebindingHiderImpl
+ ): NotificationStackRebindingHider = impl
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayChangeLatencyTracker.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayChangeLatencyTracker.kt
index 13b540aa54ba..5fda998dac2d 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayChangeLatencyTracker.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayChangeLatencyTracker.kt
@@ -24,8 +24,6 @@ import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.scene.ui.view.WindowRootView
import com.android.systemui.shade.data.repository.ShadeDisplaysRepository
-import com.android.systemui.util.kotlin.getOrNull
-import java.util.Optional
import java.util.concurrent.CancellationException
import javax.inject.Inject
import kotlin.time.Duration.Companion.seconds
@@ -51,22 +49,13 @@ import kotlinx.coroutines.withTimeout
class ShadeDisplayChangeLatencyTracker
@Inject
constructor(
- optionalShadeRootView: Optional<WindowRootView>,
+ private val shadeRootView: WindowRootView,
@ShadeDisplayAware private val configurationRepository: ConfigurationRepository,
private val latencyTracker: LatencyTracker,
@Background private val bgScope: CoroutineScope,
private val choreographerUtils: ChoreographerUtils,
) {
- private val shadeRootView =
- optionalShadeRootView.getOrNull()
- ?: error(
- """
- ShadeRootView must be provided for ShadeDisplayChangeLatencyTracker to work.
- If it is not, it means this is being instantiated in a SystemUI variant that shouldn't.
- """
- .trimIndent()
- )
/**
* We need to keep this always up to date eagerly to avoid delays receiving the new display ID.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt
index 7d4b0ed6304c..c44e066aad3a 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt
@@ -54,7 +54,12 @@ import javax.inject.Provider
/** Module for classes related to the notification shade. */
@Module(
includes =
- [StartShadeModule::class, ShadeViewProviderModule::class, WindowRootViewBlurModule::class]
+ [
+ StartShadeModule::class,
+ ShadeViewProviderModule::class,
+ WindowRootViewBlurModule::class,
+ ShadeDisplayAwareWithShadeWindowModule::class,
+ ]
)
abstract class ShadeModule {
companion object {
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 4eb707297073..2a14ca44386d 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,6 +20,7 @@ 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.display.data.repository.DisplayRepository
import com.android.systemui.keyguard.data.repository.KeyguardRepository
import com.android.systemui.shade.ShadeOnDefaultDisplayWhenLocked
import com.android.systemui.shade.display.ShadeDisplayPolicy
@@ -45,7 +46,12 @@ interface ShadeDisplaysRepository {
val currentPolicy: ShadeDisplayPolicy
}
-/** Keeps the policy and propagates the display id for the shade from it. */
+/**
+ * Keeps the policy and propagates the display id for the shade from it.
+ *
+ * If the display set by the policy is not available (e.g. after the cable is disconnected), this
+ * falls back to the [Display.DEFAULT_DISPLAY].
+ */
@SysUISingleton
class ShadeDisplaysRepositoryImpl
@Inject
@@ -56,6 +62,7 @@ constructor(
policies: Set<@JvmSuppressWildcards ShadeDisplayPolicy>,
@ShadeOnDefaultDisplayWhenLocked private val shadeOnDefaultDisplayWhenLocked: Boolean,
keyguardRepository: KeyguardRepository,
+ displayRepository: DisplayRepository,
) : ShadeDisplaysRepository {
private val policy: StateFlow<ShadeDisplayPolicy> =
@@ -73,7 +80,12 @@ constructor(
.distinctUntilChanged()
.stateIn(bgScope, SharingStarted.Eagerly, defaultPolicy)
- private val displayIdFromPolicy: Flow<Int> = policy.flatMapLatest { it.displayId }
+ private val displayIdFromPolicy: Flow<Int> =
+ policy
+ .flatMapLatest { it.displayId }
+ .combine(displayRepository.displayIds) { policyDisplayId, availableIds ->
+ if (policyDisplayId !in availableIds) Display.DEFAULT_DISPLAY else policyDisplayId
+ }
private val keyguardAwareDisplayPolicy: Flow<Int> =
if (!shadeOnDefaultDisplayWhenLocked) {
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 677e41a47afe..0002fba02be4 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/display/ShadeDisplayPolicy.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/display/ShadeDisplayPolicy.kt
@@ -57,7 +57,7 @@ interface ShadeExpansionIntent {
@Module(includes = [AllShadeDisplayPoliciesModule::class])
interface ShadeDisplayPolicyModule {
- @Binds fun provideDefaultPolicy(impl: DefaultDisplayShadePolicy): ShadeDisplayPolicy
+ @Binds fun provideDefaultPolicy(impl: StatusBarTouchShadeDisplayPolicy): ShadeDisplayPolicy
@Binds
fun provideShadeExpansionIntent(impl: StatusBarTouchShadeDisplayPolicy): ShadeExpansionIntent
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 1f534a5c191a..4ebdb6023268 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/display/StatusBarTouchShadeDisplayPolicy.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/display/StatusBarTouchShadeDisplayPolicy.kt
@@ -26,7 +26,6 @@ import com.android.systemui.display.data.repository.DisplayRepository
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
-import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.shade.shared.flag.ShadeWindowGoesAround
import dagger.Lazy
import java.util.concurrent.atomic.AtomicReference
@@ -53,7 +52,6 @@ class StatusBarTouchShadeDisplayPolicy
constructor(
displayRepository: DisplayRepository,
@Background private val backgroundScope: CoroutineScope,
- private val shadeInteractor: Lazy<ShadeInteractor>,
private val qsShadeElement: Lazy<QSShadeElement>,
private val notificationElement: Lazy<NotificationShadeElement>,
) : ShadeDisplayPolicy, ShadeExpansionIntent {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractor.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractor.kt
index e746274a39c1..9a5c96824e77 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractor.kt
@@ -39,9 +39,7 @@ import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotif
import com.android.systemui.statusbar.notification.row.NotificationRebindingTracker
import com.android.systemui.statusbar.notification.stack.NotificationStackRebindingHider
import com.android.systemui.statusbar.phone.ConfigurationForwarder
-import com.android.systemui.util.kotlin.getOrNull
import com.android.window.flags.Flags
-import java.util.Optional
import javax.inject.Inject
import kotlin.coroutines.CoroutineContext
import kotlin.time.Duration.Companion.seconds
@@ -63,17 +61,14 @@ constructor(
@Background private val bgScope: CoroutineScope,
@Main private val mainThreadContext: CoroutineContext,
private val shadeDisplayChangeLatencyTracker: ShadeDisplayChangeLatencyTracker,
- shadeExpandedInteractor: Optional<ShadeExpandedStateInteractor>,
+ private val shadeExpandedInteractor: ShadeExpandedStateInteractor,
private val shadeExpansionIntent: ShadeExpansionIntent,
private val activeNotificationsInteractor: ActiveNotificationsInteractor,
private val notificationRebindingTracker: NotificationRebindingTracker,
- notificationStackRebindingHider: Optional<NotificationStackRebindingHider>,
+ private val notificationStackRebindingHider: NotificationStackRebindingHider,
@ShadeDisplayAware private val configForwarder: ConfigurationForwarder,
) : CoreStartable {
- private val shadeExpandedInteractor = requireOptional(shadeExpandedInteractor)
- private val notificationStackRebindingHider = requireOptional(notificationStackRebindingHider)
-
private val hasActiveNotifications: Boolean
get() = activeNotificationsInteractor.areAnyNotificationsPresentValue
@@ -224,24 +219,5 @@ constructor(
const val TAG = "ShadeDisplaysInteractor"
const val COLLAPSE_EXPAND_REASON = "Shade window move"
val TIMEOUT = 1.seconds
-
- /**
- * [ShadeDisplaysInteractor] is bound in the SystemUI module for all variants, but needs
- * some specific dependencies to be bound from each variant (e.g.
- * [ShadeExpandedStateInteractor] or [NotificationStackRebindingHider]). When those are not
- * bound, this class is not expected to be instantiated, and trying to instantiate it would
- * crash.
- */
- inline fun <reified T> requireOptional(optional: Optional<T>): T {
- return optional.getOrNull()
- ?: error(
- """
- ${T::class.java.simpleName} must be provided for ShadeDisplaysInteractor to work.
- If it is not, it means this is being instantiated in a SystemUI variant that
- shouldn't.
- """
- .trimIndent()
- )
- }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImpl.kt
index 9d81be2091c2..e8b5d5bdf7df 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImpl.kt
@@ -16,7 +16,6 @@
package com.android.systemui.shade.domain.interactor
-import android.util.Log
import com.android.app.tracing.coroutines.flow.flowName
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
@@ -39,7 +38,6 @@ import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.stateIn
/** The non-empty [ShadeInteractor] implementation. */
@@ -100,31 +98,17 @@ constructor(
override val isShadeTouchable: Flow<Boolean> =
combine(
- powerInteractor.isAsleep.onEach {
- Log.d(TAG, "isShadeTouchable: upstream isAsleep=$it")
- },
- keyguardTransitionInteractor
- .isInTransition(Edge.create(to = KeyguardState.AOD))
- .onEach { Log.d(TAG, "isShadeTouchable: upstream isTransitioningToAod=$it") },
- keyguardRepository.dozeTransitionModel
- .map { it.to == DozeStateModel.DOZE_PULSING }
- .onEach { Log.d(TAG, "isShadeTouchable: upstream isPulsing=$it") },
+ powerInteractor.isAsleep,
+ keyguardTransitionInteractor.isInTransition(Edge.create(to = KeyguardState.AOD)),
+ keyguardRepository.dozeTransitionModel.map { it.to == DozeStateModel.DOZE_PULSING },
) { isAsleep, isTransitioningToAod, isPulsing ->
- val downstream =
- when {
- // If the device is transitioning to AOD, only accept touches if
- // still animating.
- isTransitioningToAod -> dozeParams.shouldControlScreenOff()
- // If the device is asleep, only accept touches if there's a pulse
- isAsleep -> isPulsing
- else -> true
- }
- Log.d(TAG, "isShadeTouchable emitting $downstream, values:")
- Log.d(TAG, " isAsleep=$isAsleep")
- Log.d(TAG, " isTransitioningToAod=$isTransitioningToAod")
- Log.d(TAG, " isPulsing=$isPulsing")
- Log.d(TAG, "")
- downstream
+ when {
+ // If the device is transitioning to AOD, only accept touches if still animating.
+ isTransitioningToAod -> dozeParams.shouldControlScreenOff()
+ // If the device is asleep, only accept touches if there's a pulse
+ isAsleep -> isPulsing
+ else -> true
+ }
}
override val isExpandToQsEnabled: Flow<Boolean> =
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 8f4e8701cad8..1ab0b93da175 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
@@ -22,6 +22,7 @@ import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.log.table.logDiffsForTable
import com.android.systemui.scene.domain.SceneFrameworkTableLog
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.shade.data.repository.ShadeRepository
import com.android.systemui.shade.shared.model.ShadeMode
import com.android.systemui.shared.settings.data.repository.SecureSettingsRepository
@@ -32,6 +33,7 @@ import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.stateIn
/**
@@ -89,10 +91,14 @@ constructor(
) : ShadeModeInteractor {
private val isDualShadeEnabled: Flow<Boolean> =
- secureSettingsRepository.boolSetting(
- Settings.Secure.DUAL_SHADE,
- defaultValue = DUAL_SHADE_ENABLED_DEFAULT,
- )
+ if (SceneContainerFlag.isEnabled) {
+ secureSettingsRepository.boolSetting(
+ Settings.Secure.DUAL_SHADE,
+ defaultValue = DUAL_SHADE_ENABLED_DEFAULT,
+ )
+ } else {
+ flowOf(false)
+ }
override val isShadeLayoutWide: StateFlow<Boolean> = repository.isShadeLayoutWide
diff --git a/packages/SystemUI/src/com/android/systemui/shade/shared/flag/ShadeWindowGoesAround.kt b/packages/SystemUI/src/com/android/systemui/shade/shared/flag/ShadeWindowGoesAround.kt
index c23ff5302b3c..dc444ffc2a34 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/shared/flag/ShadeWindowGoesAround.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/shared/flag/ShadeWindowGoesAround.kt
@@ -16,6 +16,7 @@
package com.android.systemui.shade.shared.flag
+import android.window.DesktopExperienceFlags
import com.android.systemui.Flags
import com.android.systemui.flags.FlagToken
import com.android.systemui.flags.RefactorFlagUtils
@@ -30,10 +31,26 @@ object ShadeWindowGoesAround {
val token: FlagToken
get() = FlagToken(FLAG_NAME, isEnabled)
+ /**
+ * This is defined as [DesktopExperienceFlags] to make it possible to enable it together with
+ * all the other desktop experience flags from the dev settings.
+ *
+ * Alternatively, using adb:
+ * ```bash
+ * adb shell aflags enable com.android.window.flags.show_desktop_experience_dev_option && \
+ * adb shell setprop persist.wm.debug.desktop_experience_devopts 1
+ * ```
+ */
+ val FLAG =
+ DesktopExperienceFlags.DesktopExperienceFlag(
+ Flags::shadeWindowGoesAround,
+ /* shouldOverrideByDevOption= */ true,
+ )
+
/** Is the refactor enabled */
@JvmStatic
inline val isEnabled: Boolean
- get() = Flags.shadeWindowGoesAround()
+ get() = FLAG.isTrue
/**
* 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/chips/notification/domain/interactor/SingleNotificationChipInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/interactor/SingleNotificationChipInteractor.kt
index b1af811178e4..d8c3e2546a8f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/interactor/SingleNotificationChipInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/interactor/SingleNotificationChipInteractor.kt
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.chips.notification.domain.interactor
+import com.android.systemui.activity.data.model.AppVisibilityModel
import com.android.systemui.activity.data.repository.ActivityManagerRepository
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.core.Logger
@@ -30,8 +31,6 @@ import dagger.assisted.AssistedInject
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.flatMapLatest
-import kotlinx.coroutines.flow.map
/**
* Interactor representing a single notification's status bar chip.
@@ -53,6 +52,7 @@ constructor(
@StatusBarChipsLog private val logBuffer: LogBuffer,
) {
private val key = startingModel.key
+ private val uid = startingModel.uid
private val logger = Logger(logBuffer, "Notif".pad())
// [StatusBarChipLogTag] recommends a max tag length of 20, so [extraLogTag] should NOT be the
// top-level tag. It should instead be provided as the first string in each log message.
@@ -88,28 +88,36 @@ constructor(
}
return
}
+
+ if (model.uid != uid) {
+ logger.e({
+ "$str1: received model with different uid, which shouldn't happen. " +
+ "Original UID: $int1, New UID: $int2. " +
+ "Proceeding as usual, but app visibility changes will be for *old* UID."
+ }) {
+ str1 = extraLogTag
+ int1 = uid
+ int2 = model.uid
+ }
+ }
_notificationModel.value = model
}
- private val uid: Flow<Int> = _notificationModel.map { it.uid }
-
- /** True if the application managing the notification is visible to the user. */
- private val isAppVisible: Flow<Boolean> =
- uid.flatMapLatest { currentUid ->
- activityManagerRepository.createIsAppVisibleFlow(currentUid, logger, extraLogTag)
- }
+ /** Details about when the app managing the notification was & is visible to the user. */
+ private val appVisibility: Flow<AppVisibilityModel> =
+ activityManagerRepository.createAppVisibilityFlow(uid, logger, extraLogTag)
/**
* Emits this notification's status bar chip, or null if this notification shouldn't show a
* status bar chip.
*/
val notificationChip: Flow<NotificationChipModel?> =
- combine(_notificationModel, isAppVisible) { notif, isAppVisible ->
- notif.toNotificationChipModel(isAppVisible)
+ combine(_notificationModel, appVisibility) { notif, appVisibility ->
+ notif.toNotificationChipModel(appVisibility)
}
private fun ActiveNotificationModel.toNotificationChipModel(
- isVisible: Boolean
+ appVisibility: AppVisibilityModel
): NotificationChipModel? {
val promotedContent = this.promotedContent
if (promotedContent == null) {
@@ -134,11 +142,13 @@ constructor(
}
return NotificationChipModel(
- key,
- appName,
- statusBarChipIconView,
- promotedContent,
- isVisible,
+ key = key,
+ appName = appName,
+ statusBarChipIconView = statusBarChipIconView,
+ promotedContent = promotedContent,
+ creationTime = creationTime,
+ isAppVisible = appVisibility.isAppCurrentlyVisible,
+ lastAppVisibleTime = appVisibility.lastAppVisibleTime,
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/interactor/StatusBarNotificationChipsInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/interactor/StatusBarNotificationChipsInteractor.kt
index c26d10311f1e..edb44185459c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/interactor/StatusBarNotificationChipsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/interactor/StatusBarNotificationChipsInteractor.kt
@@ -32,6 +32,7 @@ import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotif
import com.android.systemui.util.kotlin.pairwise
import com.android.systemui.util.time.SystemClock
import javax.inject.Inject
+import kotlin.math.max
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
@@ -39,9 +40,11 @@ import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onEach
/** An interactor for the notification chips shown in the status bar. */
@SysUISingleton
@@ -132,9 +135,6 @@ constructor(
}
interactor.setNotification(notif)
}
- logger.d({ "Interactors: $str1" }) {
- str1 = promotedNotificationInteractorMap.keys.joinToString(separator = " /// ")
- }
promotedNotificationInteractors.value =
promotedNotificationInteractorMap.values.toList()
}
@@ -145,26 +145,23 @@ constructor(
* Emits all notifications that are eligible to show as chips in the status bar. This is
* different from which chips will *actually* show, see [shownNotificationChips] for that.
*/
- private val allNotificationChips: Flow<List<NotificationChipModel>> =
+ val allNotificationChips: Flow<List<NotificationChipModel>> =
if (StatusBarNotifChips.isEnabled) {
// For all our current interactors...
- promotedNotificationInteractors.flatMapLatest { intrs ->
- // Stable-sort the promoted notifications by when they first appeared so that:
- // 1) The chips don't switch places if the older chip gets a notification update.
- // 2) The chips don't switch places when the second chip is tapped. (Whichever
- // notification is showing heads-up is considered to be the top notification, which
- // means tapping the second chip would move it to be the first chip if we didn't
- // sort by appearance time here.)
- // 3) Older chips get hidden if there's not enough room for all chips.
- val interactors = intrs.sortedByDescending { it.creationTime }
+ // TODO(b/364653005): When a promoted notification is added or removed, each individual
+ // interactor's [notificationChip] flow becomes un-collected then re-collected, which
+ // can cause some flows to remove then add callbacks when they don't need to. Is there a
+ // better structure for this? Maybe Channels or a StateFlow with a short timeout?
+ promotedNotificationInteractors.flatMapLatest { interactors ->
if (interactors.isNotEmpty()) {
// Combine each interactor's [notificationChip] flow...
val allNotificationChips: List<Flow<NotificationChipModel?>> =
interactors.map { interactor -> interactor.notificationChip }
combine(allNotificationChips) {
- // ... and emit just the non-null chips
- it.filterNotNull()
- }
+ // ... and emit just the non-null & sorted chips
+ it.filterNotNull().sortedWith(chipComparator)
+ }
+ .logSort()
} else {
flowOf(emptyList())
}
@@ -181,4 +178,35 @@ constructor(
// out-of-sync (like a timer that's slightly off)
chipsList.filter { !it.isAppVisible }
}
+
+ /*
+ Stable sort the promoted notifications by two criteria:
+ Criteria #1: Whichever app was most recently visible has higher ranking.
+ - Reasoning: If a user opened the app to see additional information, that's
+ likely the most important ongoing notification.
+ Criteria #2: Whichever notification first appeared more recently has higher ranking.
+ - Reasoning: Older chips get hidden if there's not enough room for all chips.
+ This semi-stable ordering ensures:
+ 1) The chips don't switch places if the older chip gets a notification update.
+ 2) The chips don't switch places when the second chip is tapped. (Whichever
+ notification is showing heads-up is considered to be the top notification, which
+ means tapping the second chip would move it to be the first chip if we didn't
+ sort by appearance time here.)
+ */
+ private val chipComparator =
+ compareByDescending<NotificationChipModel> {
+ max(it.creationTime, it.lastAppVisibleTime ?: Long.MIN_VALUE)
+ }
+
+ private fun Flow<List<NotificationChipModel>>.logSort(): Flow<List<NotificationChipModel>> {
+ return this.distinctUntilChanged().onEach { chips ->
+ val logString =
+ chips.joinToString {
+ "{key=${it.key}. " +
+ "lastVisibleAppTime=${it.lastAppVisibleTime}. " +
+ "creationTime=${it.creationTime}}"
+ }
+ logger.d({ "Sorted chips: $str1" }) { str1 = logString }
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/model/NotificationChipModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/model/NotificationChipModel.kt
index 97c37628f2e1..1f2079d83e6f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/model/NotificationChipModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/model/NotificationChipModel.kt
@@ -26,6 +26,13 @@ data class NotificationChipModel(
val appName: String,
val statusBarChipIconView: StatusBarIconView?,
val promotedContent: PromotedNotificationContentModel,
+ /** The time when the notification first appeared as promoted. */
+ val creationTime: Long,
/** True if the app managing this notification is currently visible to the user. */
val isAppVisible: Boolean,
+ /**
+ * The time when the app managing this notification last appeared as visible, or null if the app
+ * hasn't become visible since the notification became promoted.
+ */
+ val lastAppVisibleTime: Long?,
)
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 3ecbdf82f2cb..11e9fd56288f 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
@@ -35,6 +35,7 @@ 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 com.android.systemui.util.time.SystemClock
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
@@ -51,6 +52,7 @@ constructor(
@Application private val applicationScope: CoroutineScope,
private val notifChipsInteractor: StatusBarNotificationChipsInteractor,
headsUpNotificationInteractor: HeadsUpNotificationInteractor,
+ private val systemClock: SystemClock,
) {
/**
* A flow modeling the notification chips that should be shown. Emits an empty list if there are
@@ -158,16 +160,38 @@ constructor(
clickBehavior,
)
}
+
when (this.promotedContent.time.mode) {
PromotedNotificationContentModel.When.Mode.BasicTime -> {
- return OngoingActivityChipModel.Active.ShortTimeDelta(
- this.key,
- icon,
- colors,
- time = this.promotedContent.time.time,
- onClickListenerLegacy,
- clickBehavior,
- )
+ return if (
+ this.promotedContent.time.time >=
+ systemClock.currentTimeMillis() + FUTURE_TIME_THRESHOLD_MILLIS
+ ) {
+ OngoingActivityChipModel.Active.ShortTimeDelta(
+ this.key,
+ icon,
+ colors,
+ time = this.promotedContent.time.time,
+ onClickListenerLegacy,
+ clickBehavior,
+ )
+ } else {
+ // Don't show a `when` time that's close to now or in the past because it's
+ // likely that the app didn't intentionally set the `when` time to be shown in
+ // the status bar chip.
+ // TODO(b/393369213): If a notification sets a `when` time in the future and
+ // then that time comes and goes, the chip *will* start showing times in the
+ // past. Not going to fix this right now because the Compose implementation
+ // automatically handles this for us and we're hoping to launch the notification
+ // chips at the same time as the Compose chips.
+ return OngoingActivityChipModel.Active.IconOnly(
+ this.key,
+ icon,
+ colors,
+ onClickListenerLegacy,
+ clickBehavior,
+ )
+ }
}
PromotedNotificationContentModel.When.Mode.CountUp -> {
return OngoingActivityChipModel.Active.Timer(
@@ -204,4 +228,12 @@ constructor(
)
)
}
+
+ companion object {
+ /**
+ * Notifications must have a `when` time of at least 1 minute in the future in order for the
+ * status bar chip to show the time.
+ */
+ private const val FUTURE_TIME_THRESHOLD_MILLIS = 60 * 1000
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/binder/OngoingActivityChipBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/binder/OngoingActivityChipBinder.kt
index d41353b2c176..20dec11577df 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/binder/OngoingActivityChipBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/binder/OngoingActivityChipBinder.kt
@@ -408,11 +408,13 @@ object OngoingActivityChipBinder {
private fun View.setBackgroundPaddingForEmbeddedPaddingIcon() {
val sidePadding =
if (StatusBarNotifChips.isEnabled) {
- 0
- } else {
context.resources.getDimensionPixelSize(
R.dimen.ongoing_activity_chip_side_padding_for_embedded_padding_icon
)
+ } else {
+ context.resources.getDimensionPixelSize(
+ R.dimen.ongoing_activity_chip_side_padding_for_embedded_padding_icon_legacy
+ )
}
setPaddingRelative(sidePadding, paddingTop, sidePadding, paddingBottom)
}
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 4a999d5f5e0e..efd402ead979 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
@@ -41,7 +41,6 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.dimensionResource
import androidx.compose.ui.semantics.contentDescription
import androidx.compose.ui.semantics.semantics
-import androidx.compose.ui.unit.dp
import androidx.compose.ui.viewinterop.AndroidView
import com.android.compose.animation.Expandable
import com.android.compose.modifiers.thenIf
@@ -160,7 +159,10 @@ private fun ChipBody(
.padding(
horizontal =
if (hasEmbeddedIcon) {
- 0.dp
+ dimensionResource(
+ R.dimen
+ .ongoing_activity_chip_side_padding_for_embedded_padding_icon
+ )
} else {
dimensionResource(id = R.dimen.ongoing_activity_chip_side_padding)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/OngoingActivityChips.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/OngoingActivityChips.kt
index 4017c436151e..3b8c0f48e40e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/OngoingActivityChips.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/OngoingActivityChips.kt
@@ -24,7 +24,8 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.key
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
-import androidx.compose.ui.unit.dp
+import androidx.compose.ui.res.dimensionResource
+import com.android.systemui.res.R
import com.android.systemui.statusbar.chips.ui.model.MultipleOngoingActivityChipsModel
import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerViewBinder
@@ -35,10 +36,13 @@ fun OngoingActivityChips(
modifier: Modifier = Modifier,
) {
Row(
- // TODO(b/372657935): Remove magic numbers for padding and spacing.
- modifier = modifier.fillMaxHeight().padding(horizontal = 6.dp),
+ modifier =
+ modifier
+ .fillMaxHeight()
+ .padding(start = dimensionResource(R.dimen.ongoing_activity_chip_margin_start)),
verticalAlignment = Alignment.CenterVertically,
- horizontalArrangement = Arrangement.spacedBy(8.dp),
+ horizontalArrangement =
+ Arrangement.spacedBy(dimensionResource(R.dimen.ongoing_activity_chip_margin_start)),
) {
chips.active
.filter { !it.isHidden }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/media/MediaControlChipStartable.kt b/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/media/MediaControlChipStartable.kt
new file mode 100644
index 000000000000..e7bc052114eb
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/media/MediaControlChipStartable.kt
@@ -0,0 +1,43 @@
+/*
+ * 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.featurepods.media
+
+import com.android.systemui.CoreStartable
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.statusbar.featurepods.media.domain.interactor.MediaControlChipInteractor
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
+
+/**
+ * A [CoreStartable] that initializes and starts the media control chip functionality. The media
+ * chip is limited to large screen devices currently. Therefore, this [CoreStartable] should not be
+ * used for phones or smaller form factor devices.
+ */
+@SysUISingleton
+class MediaControlChipStartable
+@Inject
+constructor(
+ @Background val bgScope: CoroutineScope,
+ private val mediaControlChipInteractor: MediaControlChipInteractor,
+) : CoreStartable {
+
+ override fun start() {
+ bgScope.launch { mediaControlChipInteractor.initialize() }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/media/domain/interactor/MediaControlChipInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/media/domain/interactor/MediaControlChipInteractor.kt
index e3e77e16be6d..f439bb297de0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/media/domain/interactor/MediaControlChipInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/media/domain/interactor/MediaControlChipInteractor.kt
@@ -22,13 +22,15 @@ import com.android.systemui.media.controls.data.repository.MediaFilterRepository
import com.android.systemui.media.controls.shared.model.MediaCommonModel
import com.android.systemui.media.controls.shared.model.MediaData
import com.android.systemui.res.R
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.statusbar.featurepods.media.shared.model.MediaControlChipModel
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.combine
-import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
/**
@@ -37,6 +39,8 @@ import kotlinx.coroutines.flow.stateIn
* Provides a [StateFlow] of [MediaControlChipModel] representing the current state of the media
* control chip. Emits a new [MediaControlChipModel] when there is an active media session and the
* corresponding user preference is found, otherwise emits null.
+ *
+ * This functionality is only enabled on large screen devices.
*/
@SysUISingleton
class MediaControlChipInteractor
@@ -45,30 +49,57 @@ constructor(
@Background private val backgroundScope: CoroutineScope,
mediaFilterRepository: MediaFilterRepository,
) {
- private val currentMediaControls: StateFlow<List<MediaCommonModel.MediaControl>> =
- mediaFilterRepository.currentMedia
- .map { mediaList -> mediaList.filterIsInstance<MediaCommonModel.MediaControl>() }
- .stateIn(
- scope = backgroundScope,
- started = SharingStarted.WhileSubscribed(),
- initialValue = emptyList(),
- )
+ private val isEnabled = MutableStateFlow(false)
+
+ private val mediaControlChipModelForScene: Flow<MediaControlChipModel?> =
+ combine(mediaFilterRepository.currentMedia, mediaFilterRepository.selectedUserEntries) {
+ mediaList,
+ userEntries ->
+ mediaList
+ .filterIsInstance<MediaCommonModel.MediaControl>()
+ .mapNotNull { userEntries[it.mediaLoadedModel.instanceId] }
+ .firstOrNull { it.active }
+ ?.toMediaControlChipModel()
+ }
+
+ /**
+ * A flow of [MediaControlChipModel] representing the current state of the media controls chip.
+ * This flow emits null when no active media is playing or when playback information is
+ * unavailable. This flow is only active when [SceneContainerFlag] is disabled.
+ */
+ private val mediaControlChipModelLegacy = MutableStateFlow<MediaControlChipModel?>(null)
+
+ fun updateMediaControlChipModelLegacy(mediaData: MediaData?) {
+ if (!SceneContainerFlag.isEnabled) {
+ mediaControlChipModelLegacy.value = mediaData?.toMediaControlChipModel()
+ }
+ }
+
+ private val _mediaControlChipModel: Flow<MediaControlChipModel?> =
+ if (SceneContainerFlag.isEnabled) {
+ mediaControlChipModelForScene
+ } else {
+ mediaControlChipModelLegacy
+ }
/** The currently active [MediaControlChipModel] */
- val mediaControlModel: StateFlow<MediaControlChipModel?> =
- combine(currentMediaControls, mediaFilterRepository.selectedUserEntries) {
- mediaControls,
- userEntries ->
- mediaControls
- .mapNotNull { userEntries[it.mediaLoadedModel.instanceId] }
- .firstOrNull { it.active }
- ?.toMediaControlChipModel()
+ val mediaControlChipModel: StateFlow<MediaControlChipModel?> =
+ combine(_mediaControlChipModel, isEnabled) { mediaControlChipModel, isEnabled ->
+ if (isEnabled) {
+ mediaControlChipModel
+ } else {
+ null
+ }
}
- .stateIn(
- scope = backgroundScope,
- started = SharingStarted.WhileSubscribed(),
- initialValue = null,
- )
+ .stateIn(backgroundScope, SharingStarted.WhileSubscribed(), null)
+
+ /**
+ * The media control chip may not be enabled on all form factors, so only the relevant form
+ * factors should initialize the interactor. This must be called from a CoreStartable.
+ */
+ fun initialize() {
+ isEnabled.value = true
+ }
}
private fun MediaData.toMediaControlChipModel(): MediaControlChipModel {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/media/ui/viewmodel/MediaControlChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/media/ui/viewmodel/MediaControlChipViewModel.kt
index 19acb2e9839c..90f97df295f5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/media/ui/viewmodel/MediaControlChipViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/media/ui/viewmodel/MediaControlChipViewModel.kt
@@ -55,8 +55,8 @@ constructor(
* whenever the underlying [MediaControlChipModel] changes.
*/
override val chip: StateFlow<PopupChipModel> =
- mediaControlChipInteractor.mediaControlModel
- .map { mediaControlModel -> toPopupChipModel(mediaControlModel) }
+ mediaControlChipInteractor.mediaControlChipModel
+ .map { mediaControlChipModel -> toPopupChipModel(mediaControlChipModel) }
.stateIn(
backgroundScope,
SharingStarted.WhileSubscribed(),
@@ -82,10 +82,6 @@ constructor(
chipId = PopupChipId.MediaControl,
icon = defaultIcon,
chipText = model.songName.toString(),
- isToggled = false,
- // TODO(b/385202114): Show a popup containing the media carousal when the chip is
- // toggled.
- onToggle = {},
hoverBehavior = createHoverBehavior(model),
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/shared/model/PopupChipModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/shared/model/PopupChipModel.kt
index 683b97166f3e..60615536ab67 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/shared/model/PopupChipModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/shared/model/PopupChipModel.kt
@@ -53,10 +53,11 @@ sealed class PopupChipModel {
/** Default icon displayed on the chip */
val icon: Icon,
val chipText: String,
- val isToggled: Boolean = false,
- val onToggle: () -> Unit,
+ val isPopupShown: Boolean = false,
+ val showPopup: () -> Unit = {},
+ val hidePopup: () -> Unit = {},
val hoverBehavior: HoverBehavior = HoverBehavior.None,
) : PopupChipModel() {
- override val logName = "Shown(id=$chipId, toggled=$isToggled)"
+ override val logName = "Shown(id=$chipId, toggled=$isPopupShown)"
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/ui/compose/StatusBarPopup.kt b/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/ui/compose/StatusBarPopup.kt
new file mode 100644
index 000000000000..8a66904ea59b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/ui/compose/StatusBarPopup.kt
@@ -0,0 +1,65 @@
+/*
+ * 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.featurepods.popups.ui.compose
+
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.wrapContentSize
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.res.dimensionResource
+import androidx.compose.ui.unit.Density
+import androidx.compose.ui.unit.IntOffset
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.window.Popup
+import androidx.compose.ui.window.PopupProperties
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.featurepods.popups.shared.model.PopupChipId
+import com.android.systemui.statusbar.featurepods.popups.shared.model.PopupChipModel
+
+/**
+ * Displays a popup in the status bar area. The offset is calculated to draw the popup below the
+ * status bar.
+ */
+@Composable
+fun StatusBarPopup(viewModel: PopupChipModel.Shown) {
+ val density = Density(LocalContext.current)
+ Popup(
+ properties =
+ PopupProperties(
+ focusable = false,
+ dismissOnBackPress = true,
+ dismissOnClickOutside = true,
+ ),
+ offset =
+ IntOffset(
+ x = 0,
+ y = with(density) { dimensionResource(R.dimen.status_bar_height).roundToPx() },
+ ),
+ onDismissRequest = { viewModel.hidePopup() },
+ ) {
+ Box(modifier = Modifier.padding(8.dp).wrapContentSize()) {
+ when (viewModel.chipId) {
+ is PopupChipId.MediaControl -> {
+ // TODO(b/385202114): Populate MediaControlPopup contents.
+ }
+ }
+ // Future popup types will be handled here.
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/ui/compose/StatusBarPopupChip.kt b/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/ui/compose/StatusBarPopupChip.kt
index 34bef9d3ca3a..eb85d2f32f29 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/ui/compose/StatusBarPopupChip.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/ui/compose/StatusBarPopupChip.kt
@@ -52,14 +52,14 @@ import com.android.systemui.statusbar.featurepods.popups.shared.model.PopupChipM
* the chip can show text containing contextual information.
*/
@Composable
-fun StatusBarPopupChip(model: PopupChipModel.Shown, modifier: Modifier = Modifier) {
- val hasHoverBehavior = model.hoverBehavior !is HoverBehavior.None
+fun StatusBarPopupChip(viewModel: PopupChipModel.Shown, modifier: Modifier = Modifier) {
+ val hasHoverBehavior = viewModel.hoverBehavior !is HoverBehavior.None
val hoverInteractionSource = remember { MutableInteractionSource() }
val isHovered by hoverInteractionSource.collectIsHoveredAsState()
- val isToggled = model.isToggled
+ val isPopupShown = viewModel.isPopupShown
val chipBackgroundColor =
- if (isToggled) {
+ if (isPopupShown) {
MaterialTheme.colorScheme.primaryContainer
} else {
MaterialTheme.colorScheme.surfaceContainerHighest
@@ -72,7 +72,7 @@ fun StatusBarPopupChip(model: PopupChipModel.Shown, modifier: Modifier = Modifie
.padding(vertical = 4.dp)
.animateContentSize()
.thenIf(hasHoverBehavior) { Modifier.hoverable(hoverInteractionSource) }
- .clickable { model.onToggle() },
+ .thenIf(!isPopupShown) { Modifier.clickable { viewModel.showPopup() } },
color = chipBackgroundColor,
) {
Row(
@@ -82,14 +82,14 @@ fun StatusBarPopupChip(model: PopupChipModel.Shown, modifier: Modifier = Modifie
) {
val iconColor =
if (isHovered) chipBackgroundColor else contentColorFor(chipBackgroundColor)
- val hoverBehavior = model.hoverBehavior
+ val hoverBehavior = viewModel.hoverBehavior
val iconBackgroundColor = contentColorFor(chipBackgroundColor)
val iconInteractionSource = remember { MutableInteractionSource() }
Icon(
icon =
when {
isHovered && hoverBehavior is HoverBehavior.Button -> hoverBehavior.icon
- else -> model.icon
+ else -> viewModel.icon
},
modifier =
Modifier.thenIf(isHovered) {
@@ -109,7 +109,7 @@ fun StatusBarPopupChip(model: PopupChipModel.Shown, modifier: Modifier = Modifie
)
Text(
- text = model.chipText,
+ text = viewModel.chipText,
style = MaterialTheme.typography.labelLarge,
softWrap = false,
overflow = TextOverflow.Ellipsis,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/ui/compose/StatusBarPopupChipsContainer.kt b/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/ui/compose/StatusBarPopupChipsContainer.kt
index d35674d8dd9f..16538c93cf35 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/ui/compose/StatusBarPopupChipsContainer.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/ui/compose/StatusBarPopupChipsContainer.kt
@@ -31,10 +31,15 @@ fun StatusBarPopupChipsContainer(chips: List<PopupChipModel.Shown>, modifier: Mo
// TODO(b/385353140): Add padding and spacing for this container according to UX specs.
Box {
Row(
- modifier = Modifier.padding(horizontal = 8.dp),
+ modifier = modifier.padding(horizontal = 8.dp),
verticalAlignment = Alignment.CenterVertically,
) {
- chips.forEach { chip -> StatusBarPopupChip(chip) }
+ chips.forEach { chip ->
+ StatusBarPopupChip(chip)
+ if (chip.isPopupShown) {
+ StatusBarPopup(chip)
+ }
+ }
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/ui/viewmodel/StatusBarPopupChipsViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/ui/viewmodel/StatusBarPopupChipsViewModel.kt
index caa8e6cc02c3..33bf90defb48 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/ui/viewmodel/StatusBarPopupChipsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/ui/viewmodel/StatusBarPopupChipsViewModel.kt
@@ -16,49 +16,65 @@
package com.android.systemui.statusbar.featurepods.popups.ui.viewmodel
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Background
+import androidx.compose.runtime.derivedStateOf
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
+import com.android.systemui.lifecycle.ExclusiveActivatable
+import com.android.systemui.lifecycle.Hydrator
import com.android.systemui.statusbar.featurepods.media.ui.viewmodel.MediaControlChipViewModel
import com.android.systemui.statusbar.featurepods.popups.StatusBarPopupChips
import com.android.systemui.statusbar.featurepods.popups.shared.model.PopupChipId
import com.android.systemui.statusbar.featurepods.popups.shared.model.PopupChipModel
-import javax.inject.Inject
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.SharingStarted
-import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.asStateFlow
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.stateIn
/**
* View model deciding which system process chips to show in the status bar. Emits a list of
* PopupChipModels.
*/
-@SysUISingleton
class StatusBarPopupChipsViewModel
-@Inject
-constructor(
- @Background scope: CoroutineScope,
- mediaControlChipViewModel: MediaControlChipViewModel,
-) {
- private data class PopupChipBundle(
- val media: PopupChipModel = PopupChipModel.Hidden(chipId = PopupChipId.MediaControl)
- )
+@AssistedInject
+constructor(mediaControlChip: MediaControlChipViewModel) : ExclusiveActivatable() {
+ private val hydrator: Hydrator = Hydrator("StatusBarPopupChipsViewModel.hydrator")
- private val incomingPopupChipBundle: StateFlow<PopupChipBundle?> =
- mediaControlChipViewModel.chip
- .map { chip -> PopupChipBundle(media = chip) }
- .stateIn(scope, SharingStarted.WhileSubscribed(), PopupChipBundle())
+ /** The ID of the current chip that is showing its popup, or `null` if no chip is shown. */
+ private var currentShownPopupChipId by mutableStateOf<PopupChipId?>(null)
- val shownPopupChips: StateFlow<List<PopupChipModel.Shown>> =
+ private val incomingPopupChipBundle: PopupChipBundle by
+ hydrator.hydratedStateOf(
+ traceName = "incomingPopupChipBundle",
+ initialValue = PopupChipBundle(),
+ source = mediaControlChip.chip.map { chip -> PopupChipBundle(media = chip) },
+ )
+
+ val shownPopupChips: List<PopupChipModel.Shown> by derivedStateOf {
if (StatusBarPopupChips.isEnabled) {
- incomingPopupChipBundle
- .map { bundle ->
- listOfNotNull(bundle?.media).filterIsInstance<PopupChipModel.Shown>()
- }
- .stateIn(scope, SharingStarted.WhileSubscribed(), emptyList())
+ val bundle = incomingPopupChipBundle
+
+ listOfNotNull(bundle.media).filterIsInstance<PopupChipModel.Shown>().map { chip ->
+ chip.copy(
+ isPopupShown = chip.chipId == currentShownPopupChipId,
+ showPopup = { currentShownPopupChipId = chip.chipId },
+ hidePopup = { currentShownPopupChipId = null },
+ )
+ }
} else {
- MutableStateFlow(emptyList<PopupChipModel.Shown>()).asStateFlow()
+ emptyList()
}
+ }
+
+ override suspend fun onActivated(): Nothing {
+ hydrator.activate()
+ }
+
+ private data class PopupChipBundle(
+ val media: PopupChipModel = PopupChipModel.Hidden(chipId = PopupChipId.MediaControl)
+ )
+
+ @AssistedFactory
+ interface Factory {
+ fun create(): StatusBarPopupChipsViewModel
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/BundleEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/BundleEntry.java
index 37485feed792..0e3f103c152e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/BundleEntry.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/BundleEntry.java
@@ -16,11 +16,74 @@
package com.android.systemui.statusbar.notification.collection;
+import static android.app.NotificationChannel.NEWS_ID;
+import static android.app.NotificationChannel.PROMOTIONS_ID;
+import static android.app.NotificationChannel.RECS_ID;
+import static android.app.NotificationChannel.SOCIAL_MEDIA_ID;
+
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
+
+import java.util.List;
+
/**
* Abstract class to represent notification section bundled by AI.
*/
public class BundleEntry extends PipelineEntry {
+ private final String mKey;
+ private final BundleEntryAdapter mEntryAdapter;
+
+ // TODO (b/389839319): implement the row
+ private ExpandableNotificationRow mRow;
+
+ public BundleEntry(String key) {
+ mKey = key;
+ mEntryAdapter = new BundleEntryAdapter();
+ }
+
+ @VisibleForTesting
+ public BundleEntryAdapter getEntryAdapter() {
+ return mEntryAdapter;
+ }
+
public class BundleEntryAdapter implements EntryAdapter {
+
+ /**
+ * TODO (b/394483200): convert to PipelineEntry.ROOT_ENTRY when pipeline is migrated?
+ */
+ @Override
+ public GroupEntry getParent() {
+ return GroupEntry.ROOT_ENTRY;
+ }
+
+ @Override
+ public boolean isTopLevelEntry() {
+ return true;
+ }
+
+ @Override
+ public String getKey() {
+ return mKey;
+ }
+
+ @Override
+ public ExpandableNotificationRow getRow() {
+ return mRow;
+ }
+
+ @Nullable
+ @Override
+ public EntryAdapter getGroupRoot() {
+ return this;
+ }
}
+
+ public static final List<BundleEntry> ROOT_BUNDLES = List.of(
+ new BundleEntry(PROMOTIONS_ID),
+ new BundleEntry(SOCIAL_MEDIA_ID),
+ new BundleEntry(NEWS_ID),
+ new BundleEntry(RECS_ID));
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/EntryAdapter.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/EntryAdapter.java
index b12b1c538a32..4df81c97e21e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/EntryAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/EntryAdapter.java
@@ -16,8 +16,52 @@
package com.android.systemui.statusbar.notification.collection;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
+
/**
* Adapter interface for UI to get relevant info.
*/
public interface EntryAdapter {
+
+ /**
+ * Gets the parent of this entry, or null if the entry's view is not attached
+ */
+ @Nullable PipelineEntry getParent();
+
+ /**
+ * Returns whether the entry is attached and appears at the top level of the shade
+ */
+ boolean isTopLevelEntry();
+
+ /**
+ * @return the unique identifier for this entry
+ */
+ @NonNull String getKey();
+
+ /**
+ * Gets the view that this entry is backing.
+ */
+ @NonNull
+ ExpandableNotificationRow getRow();
+
+ /**
+ * Gets the EntryAdapter that is the nearest root of the collection of rows the given entry
+ * belongs to. If the given entry is a BundleEntry or an isolated child of a BundleEntry, the
+ * BundleEntry will be returned. If the given notification is a group summary NotificationEntry,
+ * or a child of a group summary, the summary NotificationEntry will be returned, even if that
+ * summary belongs to a BundleEntry. If the entry is a notification that does not belong to any
+ * group or bundle grouping, null will be returned.
+ */
+ @Nullable
+ EntryAdapter getGroupRoot();
+
+ /**
+ * Returns whether the entry is attached to the current shade list
+ */
+ default boolean isAttached() {
+ return getParent() != null;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifInflaterImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifInflaterImpl.java
index fc47dc1ed81a..8f3c357a277a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifInflaterImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifInflaterImpl.java
@@ -24,6 +24,7 @@ import com.android.systemui.statusbar.notification.collection.inflation.NotifInf
import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl;
import com.android.systemui.statusbar.notification.row.NotifInflationErrorManager;
import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder;
+import com.android.systemui.statusbar.notification.shared.NotificationBundleUi;
import javax.inject.Inject;
@@ -78,7 +79,7 @@ public class NotifInflaterImpl implements NotifInflater {
requireBinder().inflateViews(
entry,
params,
- wrapInflationCallback(callback));
+ wrapInflationCallback(entry, callback));
} catch (InflationException e) {
mLogger.logInflationException(entry, e);
mNotifErrorManager.setInflationError(entry, e);
@@ -101,17 +102,26 @@ public class NotifInflaterImpl implements NotifInflater {
}
private NotificationRowContentBinder.InflationCallback wrapInflationCallback(
+ final NotificationEntry entry,
InflationCallback callback) {
return new NotificationRowContentBinder.InflationCallback() {
@Override
public void handleInflationException(
NotificationEntry entry,
Exception e) {
+ if (NotificationBundleUi.isEnabled()) {
+ handleInflationException(e);
+ } else {
+ mNotifErrorManager.setInflationError(entry, e);
+ }
+ }
+ @Override
+ public void handleInflationException(Exception e) {
mNotifErrorManager.setInflationError(entry, e);
}
@Override
- public void onAsyncInflationFinished(NotificationEntry entry) {
+ public void onAsyncInflationFinished() {
mNotifErrorManager.clearInflationError(entry);
if (callback != null) {
callback.onInflationFinished(entry, entry.getRowController());
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 7dd82a6b5198..90f9525c7683 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
@@ -29,6 +29,8 @@ import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_NOTIFICAT
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK;
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_STATUS_BAR;
+import static com.android.systemui.statusbar.notification.collection.BundleEntry.ROOT_BUNDLES;
+import static com.android.systemui.statusbar.notification.collection.GroupEntry.ROOT_ENTRY;
import static com.android.systemui.statusbar.notification.collection.NotifCollection.REASON_NOT_CANCELED;
import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_ALERTING;
@@ -107,6 +109,7 @@ public final class NotificationEntry extends ListEntry {
private final String mKey;
private StatusBarNotification mSbn;
private Ranking mRanking;
+ private final NotifEntryAdapter mEntryAdapter;
/*
* Bookkeeping members
@@ -268,9 +271,48 @@ public final class NotificationEntry extends ListEntry {
mKey = sbn.getKey();
setSbn(sbn);
setRanking(ranking);
+ mEntryAdapter = new NotifEntryAdapter();
}
public class NotifEntryAdapter implements EntryAdapter {
+ @Override
+ public PipelineEntry getParent() {
+ return NotificationEntry.this.getParent();
+ }
+
+ @Override
+ public boolean isTopLevelEntry() {
+ return getParent() != null
+ && (getParent() == ROOT_ENTRY || ROOT_BUNDLES.contains(getParent()));
+ }
+
+ @Override
+ public String getKey() {
+ return NotificationEntry.this.getKey();
+ }
+
+ @Override
+ public ExpandableNotificationRow getRow() {
+ return NotificationEntry.this.getRow();
+ }
+
+ @Nullable
+ @Override
+ public EntryAdapter getGroupRoot() {
+ // TODO (b/395857098): for backwards compatibility this will return null if called
+ // on a group summary that's not in a bundles, but it should return itself.
+ if (isTopLevelEntry() || getParent() == null) {
+ return null;
+ }
+ if (NotificationEntry.this.getParent().getSummary() != null) {
+ return NotificationEntry.this.getParent().getSummary().mEntryAdapter;
+ }
+ return null;
+ }
+ }
+
+ public EntryAdapter getEntryAdapter() {
+ return mEntryAdapter;
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/PipelineEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/PipelineEntry.java
index efedfef5cbe9..c5a479180329 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/PipelineEntry.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/PipelineEntry.java
@@ -19,5 +19,5 @@ package com.android.systemui.statusbar.notification.collection;
/**
* Class to represent a notification, group, or bundle in the pipeline.
*/
-public class PipelineEntry {
+public abstract class PipelineEntry {
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ColorizedFgsCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ColorizedFgsCoordinator.java
index 733b986b5422..9df4bf4af4e8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ColorizedFgsCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ColorizedFgsCoordinator.java
@@ -23,6 +23,7 @@ import android.app.Notification;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import com.android.systemui.dagger.qualifiers.Application;
import com.android.systemui.statusbar.notification.collection.ListEntry;
import com.android.systemui.statusbar.notification.collection.NotifPipeline;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
@@ -31,29 +32,50 @@ import com.android.systemui.statusbar.notification.collection.listbuilder.plugga
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner;
import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUi;
+import com.android.systemui.statusbar.notification.promoted.domain.interactor.PromotedNotificationsInteractor;
import com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt;
+import com.android.systemui.util.kotlin.JavaAdapterKt;
-import com.google.common.primitives.Booleans;
+import kotlinx.coroutines.CoroutineScope;
+
+import java.util.Collections;
+import java.util.List;
import javax.inject.Inject;
/**
* Handles sectioning for foreground service notifications.
- * Puts non-min colorized foreground service notifications into the FGS section. See
- * {@link NotifCoordinators} for section ordering priority.
+ * Puts non-min colorized foreground service notifications into the FGS section. See
+ * {@link NotifCoordinators} for section ordering priority.
*/
@CoordinatorScope
public class ColorizedFgsCoordinator implements Coordinator {
private static final String TAG = "ColorizedCoordinator";
+ private final PromotedNotificationsInteractor mPromotedNotificationsInteractor;
+ private final CoroutineScope mMainScope;
+
+ private List<String> mOrderedPromotedNotifKeys = Collections.emptyList();
@Inject
- public ColorizedFgsCoordinator() {
+ public ColorizedFgsCoordinator(
+ @Application CoroutineScope mainScope,
+ PromotedNotificationsInteractor promotedNotificationsInteractor
+ ) {
+ mPromotedNotificationsInteractor = promotedNotificationsInteractor;
+ mMainScope = mainScope;
}
@Override
- public void attach(NotifPipeline pipeline) {
+ public void attach(@NonNull NotifPipeline pipeline) {
if (PromotedNotificationUi.isEnabled()) {
pipeline.addPromoter(mPromotedOngoingPromoter);
+
+ JavaAdapterKt.collectFlow(mMainScope,
+ mPromotedNotificationsInteractor.getOrderedChipNotificationKeys(),
+ (List<String> keys) -> {
+ mOrderedPromotedNotifKeys = keys;
+ mNotifSectioner.invalidateList("updated mOrderedPromotedNotifKeys");
+ });
}
}
@@ -82,12 +104,24 @@ public class ColorizedFgsCoordinator implements Coordinator {
return false;
}
- private NotifComparator mPreferPromoted = new NotifComparator("PreferPromoted") {
+ /** get the sort key for any entry in the ongoing section */
+ private int getSortKey(@Nullable NotificationEntry entry) {
+ if (entry == null) return Integer.MAX_VALUE;
+ // Order all promoted notif keys first, using their order in the list
+ final int index = mOrderedPromotedNotifKeys.indexOf(entry.getKey());
+ if (index >= 0) return index;
+ // Next, prioritize promoted ongoing over other notifications
+ return isPromotedOngoing(entry) ? Integer.MAX_VALUE - 1 : Integer.MAX_VALUE;
+ }
+
+ private final NotifComparator mOngoingComparator = new NotifComparator(
+ "OngoingComparator") {
@Override
public int compare(@NonNull ListEntry o1, @NonNull ListEntry o2) {
- return -1 * Booleans.compare(
- isPromotedOngoing(o1.getRepresentativeEntry()),
- isPromotedOngoing(o2.getRepresentativeEntry()));
+ return Integer.compare(
+ getSortKey(o1.getRepresentativeEntry()),
+ getSortKey(o2.getRepresentativeEntry())
+ );
}
};
@@ -95,7 +129,7 @@ public class ColorizedFgsCoordinator implements Coordinator {
@Override
public NotifComparator getComparator() {
if (PromotedNotificationUi.isEnabled()) {
- return mPreferPromoted;
+ return mOngoingComparator;
} else {
return null;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/HighPriorityProvider.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/HighPriorityProvider.java
index d47fe20911f9..2e3ab926ad57 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/HighPriorityProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/HighPriorityProvider.java
@@ -27,6 +27,7 @@ import com.android.systemui.statusbar.notification.collection.ListEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
+import com.android.systemui.statusbar.notification.shared.NotificationBundleUi;
import java.util.List;
@@ -129,21 +130,42 @@ public class HighPriorityProvider {
>= NotificationManager.IMPORTANCE_DEFAULT);
}
+ /**
+ * Returns whether the given ListEntry has a high priority child or is in a group with a child
+ * that's high priority
+ */
private boolean hasHighPriorityChild(ListEntry entry, boolean allowImplicit) {
- if (entry instanceof NotificationEntry
- && !mGroupMembershipManager.isGroupSummary((NotificationEntry) entry)) {
- return false;
- }
-
- List<NotificationEntry> children = mGroupMembershipManager.getChildren(entry);
- if (children != null) {
- for (NotificationEntry child : children) {
- if (child != entry && isHighPriority(child, allowImplicit)) {
- return true;
+ if (NotificationBundleUi.isEnabled()) {
+ GroupEntry representativeGroupEntry = null;
+ if (entry instanceof GroupEntry) {
+ representativeGroupEntry = (GroupEntry) entry;
+ } else if (entry instanceof NotificationEntry){
+ final NotificationEntry notificationEntry = entry.getRepresentativeEntry();
+ if (notificationEntry.getParent() != null
+ && notificationEntry.getParent().getSummary() != null
+ && notificationEntry.getParent().getSummary() == notificationEntry) {
+ representativeGroupEntry = notificationEntry.getParent();
}
}
+ return representativeGroupEntry != null &&
+ representativeGroupEntry.getChildren().stream().anyMatch(
+ childEntry -> isHighPriority(childEntry, allowImplicit));
+
+ } else {
+ if (entry instanceof NotificationEntry
+ && !mGroupMembershipManager.isGroupSummary((NotificationEntry) entry)) {
+ return false;
+ }
+ List<NotificationEntry> children = mGroupMembershipManager.getChildren(entry);
+ if (children != null) {
+ for (NotificationEntry child : children) {
+ if (child != entry && isHighPriority(child, allowImplicit)) {
+ return true;
+ }
+ }
+ }
+ return false;
}
- return false;
}
private boolean hasHighPriorityCharacteristics(NotificationEntry entry) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManager.java
index 30386ab46382..ea369463da51 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManager.java
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.notification.collection.render;
+import com.android.systemui.statusbar.notification.collection.EntryAdapter;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
@@ -38,6 +39,20 @@ public interface GroupExpansionManager {
boolean isGroupExpanded(NotificationEntry entry);
/**
+ * Whether the parent associated with this notification is expanded.
+ * If this notification is not part of a group or bundle, it will always return false.
+ */
+ boolean isGroupExpanded(EntryAdapter entry);
+
+ /**
+ * Set whether the group/bundle associated with this notification is expanded or not.
+ */
+ void setGroupExpanded(EntryAdapter entry, boolean expanded);
+
+ /** @return group/bundle expansion state after toggling. */
+ boolean toggleGroupExpansion(EntryAdapter entry);
+
+ /**
* Set whether the group associated with this notification is expanded or not.
*/
void setGroupExpanded(NotificationEntry entry, boolean expanded);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerImpl.java
index d1aff80b4e7c..16b98e20498a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerImpl.java
@@ -23,11 +23,13 @@ import androidx.annotation.NonNull;
import com.android.systemui.Dumpable;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.statusbar.notification.collection.EntryAdapter;
import com.android.systemui.statusbar.notification.collection.GroupEntry;
import com.android.systemui.statusbar.notification.collection.ListEntry;
import com.android.systemui.statusbar.notification.collection.NotifPipeline;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeRenderListListener;
+import com.android.systemui.statusbar.notification.shared.NotificationBundleUi;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -55,6 +57,8 @@ public class GroupExpansionManagerImpl implements GroupExpansionManager, Dumpabl
*/
private final Set<NotificationEntry> mExpandedGroups = new HashSet<>();
+ private final Set<EntryAdapter> mExpandedCollections = new HashSet<>();
+
@Inject
public GroupExpansionManagerImpl(DumpManager dumpManager,
GroupMembershipManager groupMembershipManager) {
@@ -63,11 +67,17 @@ public class GroupExpansionManagerImpl implements GroupExpansionManager, Dumpabl
}
/**
- * Cleanup entries from mExpandedGroups that no longer exist in the pipeline.
+ * Cleanup entries from internal tracking that no longer exist in the pipeline.
*/
private final OnBeforeRenderListListener mNotifTracker = (entries) -> {
- if (mExpandedGroups.isEmpty()) {
- return; // nothing to do
+ if (NotificationBundleUi.isEnabled()) {
+ if (mExpandedCollections.isEmpty()) {
+ return; // nothing to do
+ }
+ } else {
+ if (mExpandedGroups.isEmpty()) {
+ return; // nothing to do
+ }
}
final Set<NotificationEntry> renderingSummaries = new HashSet<>();
@@ -77,10 +87,25 @@ public class GroupExpansionManagerImpl implements GroupExpansionManager, Dumpabl
}
}
- // If a group is in mExpandedGroups but not in the pipeline entries, collapse it.
- final var groupsToRemove = setDifference(mExpandedGroups, renderingSummaries);
- for (NotificationEntry entry : groupsToRemove) {
- setGroupExpanded(entry, false);
+ if (NotificationBundleUi.isEnabled()) {
+ for (EntryAdapter entryAdapter : mExpandedCollections) {
+ boolean isInPipeline = false;
+ for (NotificationEntry entry : renderingSummaries) {
+ if (entry.getKey().equals(entryAdapter.getKey())) {
+ isInPipeline = true;
+ break;
+ }
+ }
+ if (!isInPipeline) {
+ setGroupExpanded(entryAdapter, false);
+ }
+ }
+ } else {
+ // If a group is in mExpandedGroups but not in the pipeline entries, collapse it.
+ final var groupsToRemove = setDifference(mExpandedGroups, renderingSummaries);
+ for (NotificationEntry entry : groupsToRemove) {
+ setGroupExpanded(entry, false);
+ }
}
};
@@ -96,11 +121,13 @@ public class GroupExpansionManagerImpl implements GroupExpansionManager, Dumpabl
@Override
public boolean isGroupExpanded(NotificationEntry entry) {
+ NotificationBundleUi.assertInLegacyMode();
return mExpandedGroups.contains(mGroupMembershipManager.getGroupSummary(entry));
}
@Override
public void setGroupExpanded(NotificationEntry entry, boolean expanded) {
+ NotificationBundleUi.assertInLegacyMode();
NotificationEntry groupSummary = mGroupMembershipManager.getGroupSummary(entry);
if (entry.getParent() == null) {
if (expanded) {
@@ -127,14 +154,61 @@ public class GroupExpansionManagerImpl implements GroupExpansionManager, Dumpabl
@Override
public boolean toggleGroupExpansion(NotificationEntry entry) {
+ NotificationBundleUi.assertInLegacyMode();
+ setGroupExpanded(entry, !isGroupExpanded(entry));
+ return isGroupExpanded(entry);
+ }
+
+ @Override
+ public boolean isGroupExpanded(EntryAdapter entry) {
+ NotificationBundleUi.assertInNewMode();
+ return mExpandedCollections.contains(mGroupMembershipManager.getGroupRoot(entry));
+ }
+
+ @Override
+ public void setGroupExpanded(EntryAdapter entry, boolean expanded) {
+ NotificationBundleUi.assertInNewMode();
+ EntryAdapter groupParent = mGroupMembershipManager.getGroupRoot(entry);
+ if (!entry.isAttached()) {
+ if (expanded) {
+ Log.wtf(TAG, "Cannot expand group that is not attached");
+ } else {
+ // The entry is no longer attached, but we still want to make sure we don't have
+ // a stale expansion state.
+ groupParent = entry;
+ }
+ }
+
+ boolean changed;
+ if (expanded) {
+ changed = mExpandedCollections.add(groupParent);
+ } else {
+ changed = mExpandedCollections.remove(groupParent);
+ }
+
+ // Only notify listeners if something changed.
+ if (changed) {
+ sendOnGroupExpandedChange(entry, expanded);
+ }
+ }
+
+ @Override
+ public boolean toggleGroupExpansion(EntryAdapter entry) {
+ NotificationBundleUi.assertInNewMode();
setGroupExpanded(entry, !isGroupExpanded(entry));
return isGroupExpanded(entry);
}
@Override
public void collapseGroups() {
- for (NotificationEntry entry : new ArrayList<>(mExpandedGroups)) {
- setGroupExpanded(entry, false);
+ if (NotificationBundleUi.isEnabled()) {
+ for (EntryAdapter entry : new ArrayList<>(mExpandedCollections)) {
+ setGroupExpanded(entry, false);
+ }
+ } else {
+ for (NotificationEntry entry : new ArrayList<>(mExpandedGroups)) {
+ setGroupExpanded(entry, false);
+ }
}
}
@@ -145,9 +219,21 @@ public class GroupExpansionManagerImpl implements GroupExpansionManager, Dumpabl
for (NotificationEntry entry : mExpandedGroups) {
pw.println(" * " + entry.getKey());
}
+ pw.println(" mExpandedCollection: " + mExpandedCollections.size());
+ for (EntryAdapter entry : mExpandedCollections) {
+ pw.println(" * " + entry.getKey());
+ }
}
private void sendOnGroupExpandedChange(NotificationEntry entry, boolean expanded) {
+ NotificationBundleUi.assertInLegacyMode();
+ for (OnGroupExpansionChangeListener listener : mOnGroupChangeListeners) {
+ listener.onGroupExpansionChange(entry.getRow(), expanded);
+ }
+ }
+
+ private void sendOnGroupExpandedChange(EntryAdapter entry, boolean expanded) {
+ NotificationBundleUi.assertInNewMode();
for (OnGroupExpansionChangeListener listener : mOnGroupChangeListeners) {
listener.onGroupExpansionChange(entry.getRow(), expanded);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManager.java
index 3158782e6fea..69267e5d9e55 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManager.java
@@ -19,6 +19,7 @@ package com.android.systemui.statusbar.notification.collection.render;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import com.android.systemui.statusbar.notification.collection.EntryAdapter;
import com.android.systemui.statusbar.notification.collection.ListEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
@@ -29,6 +30,13 @@ import java.util.List;
* generally assumes that the notification is attached (aka its parent is not null).
*/
public interface GroupMembershipManager {
+
+ /**
+ * @return whether a given entry is the root (GroupEntry or BundleEntry) in a collection which
+ * has children
+ */
+ boolean isGroupRoot(@NonNull EntryAdapter entry);
+
/**
* @return whether a given notification is the summary in a group which has children
*/
@@ -42,16 +50,15 @@ public interface GroupMembershipManager {
NotificationEntry getGroupSummary(@NonNull NotificationEntry entry);
/**
- * Similar to {@link #getGroupSummary(NotificationEntry)} but doesn't get the visual summary
- * but the logical summary, i.e when a child is isolated, it still returns the summary as if
- * it wasn't isolated.
- * TODO: remove this when migrating to the new pipeline, this is taken care of in the
- * dismissal logic built into NotifCollection
+ * Gets the EntryAdapter that is the nearest root of the collection of rows the given entry
+ * belongs to. If the given entry is a BundleEntry or an isolated child of a BundleEntry, the
+ * BundleEntry will be returned. If the given notification is a group summary NotificationEntry,
+ * or a child of a group summary, the summary NotificationEntry will be returned, even if that
+ * summary belongs to a BundleEntry. If the entry is a notification that does not belong to any
+ * group or bundle grouping, null will be returned.
*/
@Nullable
- default NotificationEntry getLogicalGroupSummary(@NonNull NotificationEntry entry) {
- return getGroupSummary(entry);
- }
+ EntryAdapter getGroupRoot(@NonNull EntryAdapter entry);
/**
* @return whether a given notification is a child in a group
@@ -59,9 +66,10 @@ public interface GroupMembershipManager {
boolean isChildInGroup(@NonNull NotificationEntry entry);
/**
- * Whether this is the only child in a group
+ * @return whether a given notification is a child in a group. The group may be a notification
+ * group or a bundle.
*/
- boolean isOnlyChildInGroup(@NonNull NotificationEntry entry);
+ boolean isChildInGroup(@NonNull EntryAdapter entry);
/**
* Get the children that are in the summary's group, not including those isolated.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManagerImpl.java
index da1247953c4c..80a9f8adf8f3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManagerImpl.java
@@ -22,9 +22,11 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.statusbar.notification.collection.EntryAdapter;
import com.android.systemui.statusbar.notification.collection.GroupEntry;
import com.android.systemui.statusbar.notification.collection.ListEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.shared.NotificationBundleUi;
import java.util.List;
@@ -41,6 +43,7 @@ public class GroupMembershipManagerImpl implements GroupMembershipManager {
@Override
public boolean isGroupSummary(@NonNull NotificationEntry entry) {
+ NotificationBundleUi.assertInLegacyMode();
if (entry.getParent() == null) {
// The entry is not attached, so it doesn't count.
return false;
@@ -49,33 +52,47 @@ public class GroupMembershipManagerImpl implements GroupMembershipManager {
return entry.getParent().getSummary() == entry;
}
+ @Override
+ public boolean isGroupRoot(@NonNull EntryAdapter entry) {
+ NotificationBundleUi.assertInNewMode();
+ return entry == entry.getGroupRoot();
+ }
+
@Nullable
@Override
public NotificationEntry getGroupSummary(@NonNull NotificationEntry entry) {
+ NotificationBundleUi.assertInLegacyMode();
if (isTopLevelEntry(entry) || entry.getParent() == null) {
return null;
}
return entry.getParent().getSummary();
}
+ @Nullable
+ @Override
+ public EntryAdapter getGroupRoot(@NonNull EntryAdapter entry) {
+ NotificationBundleUi.assertInNewMode();
+ return entry.getGroupRoot();
+ }
+
@Override
public boolean isChildInGroup(@NonNull NotificationEntry entry) {
+ NotificationBundleUi.assertInLegacyMode();
// An entry is a child if it's not a summary or top level entry, but it is attached.
return !isGroupSummary(entry) && !isTopLevelEntry(entry) && entry.getParent() != null;
}
@Override
- public boolean isOnlyChildInGroup(@NonNull NotificationEntry entry) {
- if (entry.getParent() == null) {
- return false; // The entry is not attached.
- }
-
- return !isGroupSummary(entry) && entry.getParent().getChildren().size() == 1;
+ public boolean isChildInGroup(@NonNull EntryAdapter entry) {
+ NotificationBundleUi.assertInNewMode();
+ // An entry is a child if it's not a group root or top level entry, but it is attached.
+ return entry.isAttached() && entry != getGroupRoot(entry) && !entry.isTopLevelEntry();
}
@Nullable
@Override
public List<NotificationEntry> getChildren(@NonNull ListEntry entry) {
+ NotificationBundleUi.assertInLegacyMode();
if (entry instanceof GroupEntry) {
return ((GroupEntry) entry).getChildren();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationStackOptionalModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationStackOptionalModule.kt
deleted file mode 100644
index bcaf1878a869..000000000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationStackOptionalModule.kt
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright (C) 2025 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.notification.dagger
-
-import com.android.systemui.statusbar.notification.stack.NotificationStackRebindingHider
-import com.android.systemui.statusbar.notification.stack.NotificationStackRebindingHiderImpl
-import dagger.Binds
-import dagger.BindsOptionalOf
-import dagger.Module
-
-/**
- * This is meant to be bound in SystemUI variants with [NotificationStackScrollLayoutController].
- */
-@Module
-interface NotificationStackModule {
- @Binds
- fun bindNotificationStackRebindingHider(
- impl: NotificationStackRebindingHiderImpl
- ): NotificationStackRebindingHider
-}
-
-/** This is meant to be used by all SystemUI variants, also those without NSSL. */
-@Module
-interface NotificationStackOptionalModule {
- @BindsOptionalOf
- fun bindOptionalOfNotificationStackRebindingHider(): NotificationStackRebindingHider
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
index 34f4969127e3..53d5dbc58677 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
@@ -121,7 +121,6 @@ import javax.inject.Provider;
NotificationMemoryModule.class,
NotificationStatsLoggerModule.class,
NotificationsLogModule.class,
- NotificationStackOptionalModule.class,
})
public interface NotificationsModule {
@Binds
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/AvalancheController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/AvalancheController.kt
index ccc2dffcfd7b..dba8a9a6ddec 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/AvalancheController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/AvalancheController.kt
@@ -49,7 +49,7 @@ constructor(
) : Dumpable {
private val tag = "AvalancheController"
- private val debug = Compile.IS_DEBUG && Log.isLoggable(tag, Log.DEBUG)
+ private val debug = false // Compile.IS_DEBUG && Log.isLoggable(tag, Log.DEBUG)
var baseEntryMapStr: () -> String = { "baseEntryMapStr not initialized" }
var enableAtRuntime = true
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 be61dc95fe20..7d74a496853f 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
@@ -46,6 +46,7 @@ import com.android.systemui.shade.ShadeDisplayAware;
import com.android.systemui.shade.domain.interactor.ShadeInteractor;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips;
+import com.android.systemui.statusbar.notification.collection.EntryAdapter;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.coordinator.HeadsUpCoordinator;
import com.android.systemui.statusbar.notification.collection.provider.OnReorderingAllowedListener;
@@ -55,6 +56,7 @@ import com.android.systemui.statusbar.notification.collection.render.GroupMember
import com.android.systemui.statusbar.notification.data.repository.HeadsUpRepository;
import com.android.systemui.statusbar.notification.data.repository.HeadsUpRowRepository;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
+import com.android.systemui.statusbar.notification.shared.NotificationBundleUi;
import com.android.systemui.statusbar.notification.shared.NotificationThrottleHun;
import com.android.systemui.statusbar.phone.ExpandHeadsUpOnInlineReply;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
@@ -118,7 +120,8 @@ public class HeadsUpManagerImpl
@VisibleForTesting
final ArrayMap<String, HeadsUpEntry> mHeadsUpEntryMap = new ArrayMap<>();
private final HeadsUpManagerLogger mLogger;
- private final int mMinimumDisplayTime;
+ private final int mMinimumDisplayTimeDefault;
+ private final int mMinimumDisplayTimeForUserInitiated;
private final int mStickyForSomeTimeAutoDismissTime;
private final int mAutoDismissTime;
private final DelayableExecutor mExecutor;
@@ -215,9 +218,11 @@ public class HeadsUpManagerImpl
mGroupMembershipManager = groupMembershipManager;
mVisualStabilityProvider = visualStabilityProvider;
Resources resources = context.getResources();
- mMinimumDisplayTime = NotificationThrottleHun.isEnabled()
+ mMinimumDisplayTimeDefault = NotificationThrottleHun.isEnabled()
? resources.getInteger(R.integer.heads_up_notification_minimum_time_with_throttling)
: resources.getInteger(R.integer.heads_up_notification_minimum_time);
+ mMinimumDisplayTimeForUserInitiated = resources.getInteger(
+ R.integer.heads_up_notification_minimum_time_for_user_initiated);
mStickyForSomeTimeAutoDismissTime = resources.getInteger(
R.integer.sticky_heads_up_notification_time);
mAutoDismissTime = resources.getInteger(R.integer.heads_up_notification_decay);
@@ -871,14 +876,24 @@ public class HeadsUpManagerImpl
if (!hasPinnedHeadsUp() || topEntry == null) {
return null;
} else {
+ ExpandableNotificationRow topRow = topEntry.getRow();
if (topEntry.rowIsChildInGroup()) {
- final NotificationEntry groupSummary =
- mGroupMembershipManager.getGroupSummary(topEntry);
- if (groupSummary != null) {
- topEntry = groupSummary;
+ if (NotificationBundleUi.isEnabled()) {
+ final EntryAdapter adapter = mGroupMembershipManager.getGroupRoot(
+ topRow.getEntryAdapter());
+ if (adapter != null) {
+ topRow = adapter.getRow();
+ }
+ } else {
+ final NotificationEntry groupSummary =
+ mGroupMembershipManager.getGroupSummary(topEntry);
+ if (groupSummary != null) {
+ topEntry = groupSummary;
+ topRow = topEntry.getRow();
+ }
}
}
- ExpandableNotificationRow topRow = topEntry.getRow();
+
int[] tmpArray = new int[2];
topRow.getLocationOnScreen(tmpArray);
int minX = tmpArray[0];
@@ -1358,7 +1373,12 @@ public class HeadsUpManagerImpl
final long now = mSystemClock.elapsedRealtime();
if (updateEarliestRemovalTime) {
- mEarliestRemovalTime = now + mMinimumDisplayTime;
+ if (StatusBarNotifChips.isEnabled()
+ && mPinnedStatus.getValue() == PinnedStatus.PinnedByUser) {
+ mEarliestRemovalTime = now + mMinimumDisplayTimeForUserInitiated;
+ } else {
+ mEarliestRemovalTime = now + mMinimumDisplayTimeDefault;
+ }
}
if (updatePostTime) {
@@ -1377,7 +1397,7 @@ public class HeadsUpManagerImpl
final long now = mSystemClock.elapsedRealtime();
return NotificationThrottleHun.isEnabled()
? Math.max(finishTime, mEarliestRemovalTime) - now
- : Math.max(finishTime - now, mMinimumDisplayTime);
+ : Math.max(finishTime - now, mMinimumDisplayTimeDefault);
};
scheduleAutoRemovalCallback(finishTimeCalculator, "updateEntry (not sticky)");
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpTouchHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpTouchHelper.java
index 3b6b9ed636fe..47e725cb5e8c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpTouchHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpTouchHelper.java
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.notification.headsup;
+import android.annotation.Nullable;
import android.content.Context;
import android.os.RemoteException;
import android.view.MotionEvent;
@@ -210,10 +211,12 @@ public class HeadsUpTouchHelper implements Gefingerpoken {
if (mHeadsUpManager.shouldSwallowClick(
mPickedChild.getEntry().getSbn().getKey())) {
endMotion();
+ setTrackingHeadsUp(false);
return true;
}
}
endMotion();
+ setTrackingHeadsUp(false);
return false;
}
return false;
@@ -258,7 +261,7 @@ public class HeadsUpTouchHelper implements Gefingerpoken {
void setHeadsUpDraggingStartingHeight(int startHeight);
/** Sets notification that is being expanded. */
- void setTrackedHeadsUp(ExpandableNotificationRow expandableNotificationRow);
+ void setTrackedHeadsUp(@Nullable ExpandableNotificationRow expandableNotificationRow);
/** Called when a MotionEvent is about to trigger expansion. */
void startExpand(float newX, float newY, boolean startTracking, float expandedHeight);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleNotificationIdentifier.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleNotificationIdentifier.kt
index 691f1f452da8..f755dbb48e1d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleNotificationIdentifier.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleNotificationIdentifier.kt
@@ -27,6 +27,7 @@ import com.android.systemui.statusbar.notification.people.PeopleNotificationIden
import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_IMPORTANT_PERSON
import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_NON_PERSON
import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_PERSON
+import com.android.systemui.statusbar.notification.shared.NotificationBundleUi
import javax.inject.Inject
import kotlin.math.max
@@ -112,14 +113,26 @@ class PeopleNotificationIdentifierImpl @Inject constructor(
if (personExtractor.isPersonNotification(sbn)) TYPE_PERSON else TYPE_NON_PERSON
private fun getPeopleTypeOfSummary(entry: NotificationEntry): Int {
- if (!groupManager.isGroupSummary(entry)) {
- return TYPE_NON_PERSON
+ if (NotificationBundleUi.isEnabled) {
+ if (!entry.sbn.notification.isGroupSummary) {
+ return TYPE_NON_PERSON;
+ }
+
+ return getPeopleTypeForChildList(entry.parent?.children)
+ } else {
+ if (!groupManager.isGroupSummary(entry)) {
+ return TYPE_NON_PERSON
+ }
+
+ return getPeopleTypeForChildList(groupManager.getChildren(entry))
}
+ }
- val childTypes = groupManager.getChildren(entry)
- ?.asSequence()
- ?.map { getPeopleNotificationType(it) }
- ?: return TYPE_NON_PERSON
+ private fun getPeopleTypeForChildList(children: List<NotificationEntry>?): Int {
+ val childTypes = children
+ ?.asSequence()
+ ?.map { getPeopleNotificationType(it) }
+ ?: return TYPE_NON_PERSON
var groupType = TYPE_NON_PERSON
for (childType in childTypes) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/AODPromotedNotification.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/AODPromotedNotification.kt
index e5d2361e8524..893570b7fb51 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/AODPromotedNotification.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/AODPromotedNotification.kt
@@ -426,6 +426,8 @@ private class AODPromotedNotificationViewUpdater(root: View) {
chronometer = chronometerStub?.inflate() as Chronometer
chronometerStub = null
+
+ chronometer?.appendFontFeatureSetting("tnum")
}
private fun inflateOldProgressBar() {
@@ -501,6 +503,10 @@ private fun Notification.ProgressStyle.Point.toSkeleton(): Notification.Progress
}
}
+private fun TextView.appendFontFeatureSetting(newSetting: String) {
+ fontFeatureSettings = (fontFeatureSettings?.let { "$it," } ?: "") + newSetting
+}
+
private enum class AodPromotedNotificationColor(val colorInt: Int) {
Background(android.graphics.Color.BLACK),
PrimaryText(android.graphics.Color.WHITE),
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/domain/interactor/AODPromotedNotificationInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/domain/interactor/AODPromotedNotificationInteractor.kt
index 393f95d3ad77..4bc685423659 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/domain/interactor/AODPromotedNotificationInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/domain/interactor/AODPromotedNotificationInteractor.kt
@@ -18,7 +18,6 @@ package com.android.systemui.statusbar.notification.promoted.domain.interactor
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dump.DumpManager
-import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor
import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel
import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel.Style
import com.android.systemui.util.kotlin.FlowDumperImpl
@@ -30,13 +29,12 @@ import kotlinx.coroutines.flow.map
class AODPromotedNotificationInteractor
@Inject
constructor(
- activeNotificationsInteractor: ActiveNotificationsInteractor,
+ promotedNotificationsInteractor: PromotedNotificationsInteractor,
dumpManager: DumpManager,
) : FlowDumperImpl(dumpManager) {
+ /** The content to show as the promoted notification on AOD */
val content: Flow<PromotedNotificationContentModel?> =
- activeNotificationsInteractor.topLevelRepresentativeNotifications.map { notifs ->
- notifs.firstNotNullOfOrNull { it.promotedContent }
- }
+ promotedNotificationsInteractor.topPromotedNotificationContent
val isPresent: Flow<Boolean> =
content
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/domain/interactor/PromotedNotificationsInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/domain/interactor/PromotedNotificationsInteractor.kt
new file mode 100644
index 000000000000..1015cfbefc41
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/domain/interactor/PromotedNotificationsInteractor.kt
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.promoted.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.statusbar.chips.call.domain.interactor.CallChipInteractor
+import com.android.systemui.statusbar.chips.notification.domain.interactor.StatusBarNotificationChipsInteractor
+import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor
+import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel
+import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallModel
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
+
+/**
+ * An interactor that provides details about promoted notification precedence, based on the
+ * presented order of current notification status bar chips.
+ */
+@SysUISingleton
+class PromotedNotificationsInteractor
+@Inject
+constructor(
+ activeNotificationsInteractor: ActiveNotificationsInteractor,
+ callChipInteractor: CallChipInteractor,
+ notifChipsInteractor: StatusBarNotificationChipsInteractor,
+ @Background backgroundDispatcher: CoroutineDispatcher,
+) {
+ /**
+ * This is the ordered list of notifications (and the promoted content) represented as chips in
+ * the status bar.
+ */
+ private val orderedChipNotifications: Flow<List<NotifAndPromotedContent>> =
+ combine(callChipInteractor.ongoingCallState, notifChipsInteractor.allNotificationChips) {
+ callState,
+ notifChips ->
+ buildList {
+ val callData = callState.getNotifData()?.also { add(it) }
+ addAll(
+ notifChips.mapNotNull {
+ when (it.key) {
+ callData?.key -> null // do not re-add the same call
+ else -> NotifAndPromotedContent(it.key, it.promotedContent)
+ }
+ }
+ )
+ }
+ }
+
+ private fun OngoingCallModel.getNotifData(): NotifAndPromotedContent? =
+ when (this) {
+ is OngoingCallModel.InCall -> NotifAndPromotedContent(notificationKey, promotedContent)
+ is OngoingCallModel.InCallWithVisibleApp ->
+ // TODO(b/395989259): support InCallWithVisibleApp when it has notif data
+ null
+ is OngoingCallModel.NoCall -> null
+ }
+
+ /**
+ * The top promoted notification represented by a chip, with the order determined by the order
+ * of the chips, not the notifications.
+ */
+ private val topPromotedChipNotification: Flow<PromotedNotificationContentModel?> =
+ orderedChipNotifications
+ .map { list -> list.firstNotNullOfOrNull { it.promotedContent } }
+ .distinctUntilNewInstance()
+
+ /** This is the top-most promoted notification, which should avoid regular changing. */
+ val topPromotedNotificationContent: Flow<PromotedNotificationContentModel?> =
+ combine(
+ topPromotedChipNotification,
+ activeNotificationsInteractor.topLevelRepresentativeNotifications,
+ ) { topChipNotif, topLevelNotifs ->
+ topChipNotif ?: topLevelNotifs.firstNotNullOfOrNull { it.promotedContent }
+ }
+ // #equals() can be a bit expensive on this object, but this flow will regularly try to
+ // emit the same immutable instance over and over, so just prevent that.
+ .distinctUntilNewInstance()
+
+ /**
+ * This is the ordered list of notifications (and the promoted content) represented as chips in
+ * the status bar. Flows on the background context.
+ */
+ val orderedChipNotificationKeys: Flow<List<String>> =
+ orderedChipNotifications
+ .map { list -> list.map { it.key } }
+ .distinctUntilChanged()
+ .flowOn(backgroundDispatcher)
+
+ /**
+ * Returns flow where all subsequent repetitions of the same object instance are filtered out.
+ */
+ private fun <T> Flow<T>.distinctUntilNewInstance() = distinctUntilChanged { a, b -> a === b }
+
+ /**
+ * A custom pair, but providing clearer semantic names, and implementing equality as being the
+ * same instance of the promoted content model, which allows us to use distinctUntilChanged() on
+ * flows containing this without doing pixel comparisons on the Bitmaps inside Icon objects
+ * provided by the Notification.
+ */
+ private data class NotifAndPromotedContent(
+ val key: String,
+ val promotedContent: PromotedNotificationContentModel?,
+ ) {
+ /**
+ * Define the equals of this object to only check the reference equality of the promoted
+ * content so that we can mark.
+ */
+ override fun equals(other: Any?): Boolean {
+ return when {
+ other == null -> false
+ other === this -> true
+ other !is NotifAndPromotedContent -> return false
+ else -> key == other.key && promotedContent === other.promotedContent
+ }
+ }
+
+ /** Define the hashCode to be very quick, even if it increases collisions. */
+ override fun hashCode(): Int {
+ var result = key.hashCode()
+ result = 31 * result + (promotedContent?.identity?.hashCode() ?: 0)
+ return result
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/AsyncRowInflater.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/AsyncRowInflater.kt
new file mode 100644
index 000000000000..c3b241154e0e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/AsyncRowInflater.kt
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.row
+
+import android.content.Context
+import android.util.Log
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.annotation.LayoutRes
+import androidx.annotation.UiThread
+import com.android.app.tracing.coroutines.launchTraced
+import com.android.app.tracing.coroutines.withContextTraced
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.dagger.qualifiers.NotifInflation
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Job
+
+@SysUISingleton
+class AsyncRowInflater
+@Inject
+constructor(
+ @Application private val applicationScope: CoroutineScope,
+ @Main private val mainCoroutineDispatcher: CoroutineDispatcher,
+ @NotifInflation private val inflationCoroutineDispatcher: CoroutineDispatcher,
+) {
+ /**
+ * Inflate the layout on the background thread, and invoke the listener on the main thread when
+ * finished.
+ *
+ * If the inflation fails on the background, it will be retried once on the main thread.
+ */
+ @UiThread
+ fun inflate(
+ context: Context,
+ layoutFactory: LayoutInflater.Factory2,
+ @LayoutRes resId: Int,
+ parent: ViewGroup,
+ listener: OnInflateFinishedListener,
+ ): Job {
+ val inflater = BasicRowInflater(context).apply { factory2 = layoutFactory }
+ return applicationScope.launchTraced("AsyncRowInflater-bg", inflationCoroutineDispatcher) {
+ val view =
+ try {
+ inflater.inflate(resId, parent, false)
+ } catch (ex: RuntimeException) {
+ // Probably a Looper failure, retry on the UI thread
+ Log.w(
+ "AsyncRowInflater",
+ "Failed to inflate resource in the background!" +
+ " Retrying on the UI thread",
+ ex,
+ )
+ null
+ }
+ withContextTraced("AsyncRowInflater-ui", mainCoroutineDispatcher) {
+ // If the inflate failed on the inflation thread, try again on the main thread
+ val finalView = view ?: inflater.inflate(resId, parent, false)
+ // Inform the listener of the completion
+ listener.onInflateFinished(finalView, resId, parent)
+ }
+ }
+ }
+
+ /**
+ * Callback interface (identical to the one from AsyncLayoutInflater) for receiving the inflated
+ * view
+ */
+ interface OnInflateFinishedListener {
+ @UiThread fun onInflateFinished(view: View, @LayoutRes resId: Int, parent: ViewGroup?)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/BasicRowInflater.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/BasicRowInflater.kt
new file mode 100644
index 000000000000..79d50b8398bc
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/BasicRowInflater.kt
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.statusbar.notification.row
+
+import android.content.Context
+import android.util.AttributeSet
+import android.view.LayoutInflater
+import android.view.View
+
+/**
+ * A [LayoutInflater] that is copy of
+ * [androidx.asynclayoutinflater.view.AsyncLayoutInflater.BasicInflater]
+ */
+internal class BasicRowInflater(context: Context) : LayoutInflater(context) {
+ override fun cloneInContext(newContext: Context): LayoutInflater {
+ return BasicRowInflater(newContext)
+ }
+
+ @Throws(ClassNotFoundException::class)
+ override fun onCreateView(name: String, attrs: AttributeSet): View {
+ for (prefix in sClassPrefixList) {
+ try {
+ val view = createView(name, prefix, attrs)
+ if (view != null) {
+ return view
+ }
+ } catch (e: ClassNotFoundException) {
+ // In this case we want to let the base class take a crack at it.
+ }
+ }
+
+ return super.onCreateView(name, attrs)
+ }
+
+ companion object {
+ private val sClassPrefixList = arrayOf("android.widget.", "android.webkit.", "android.app.")
+ }
+}
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 9bf07689dbdb..d383bee64530 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
@@ -105,6 +105,7 @@ import com.android.systemui.statusbar.notification.NotificationFadeAware;
import com.android.systemui.statusbar.notification.NotificationTransitionAnimatorController;
import com.android.systemui.statusbar.notification.NotificationUtils;
import com.android.systemui.statusbar.notification.SourceType;
+import com.android.systemui.statusbar.notification.collection.EntryAdapter;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.provider.NotificationDismissibilityProvider;
import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager;
@@ -120,6 +121,7 @@ import com.android.systemui.statusbar.notification.row.shared.LockscreenOtpRedac
import com.android.systemui.statusbar.notification.row.wrapper.NotificationCompactMessagingTemplateViewWrapper;
import com.android.systemui.statusbar.notification.row.wrapper.NotificationViewWrapper;
import com.android.systemui.statusbar.notification.shared.NotificationAddXOnHoverToDismiss;
+import com.android.systemui.statusbar.notification.shared.NotificationBundleUi;
import com.android.systemui.statusbar.notification.shared.NotificationContentAlphaOptimization;
import com.android.systemui.statusbar.notification.shared.TransparentHeaderFix;
import com.android.systemui.statusbar.notification.stack.AmbientState;
@@ -268,6 +270,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
private String mLoggingKey;
private NotificationGuts mGuts;
private NotificationEntry mEntry;
+ private EntryAdapter mEntryAdapter;
private String mAppName;
private NotificationRebindingTracker mRebindingTracker;
private FalsingManager mFalsingManager;
@@ -390,11 +393,17 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
}
private void toggleExpansionState(View v, boolean shouldLogExpandClickMetric) {
- if (!shouldShowPublic() && (!mIsMinimized || isExpanded())
- && mGroupMembershipManager.isGroupSummary(mEntry)) {
+ boolean isGroupRoot = NotificationBundleUi.isEnabled()
+ ? mGroupMembershipManager.isGroupRoot(mEntryAdapter)
+ : mGroupMembershipManager.isGroupSummary(mEntry);
+ if (!shouldShowPublic() && (!mIsMinimized || isExpanded()) && isGroupRoot) {
mGroupExpansionChanging = true;
- final boolean wasExpanded = mGroupExpansionManager.isGroupExpanded(mEntry);
- boolean nowExpanded = mGroupExpansionManager.toggleGroupExpansion(mEntry);
+ final boolean wasExpanded = NotificationBundleUi.isEnabled()
+ ? mGroupExpansionManager.isGroupExpanded(mEntryAdapter)
+ : mGroupExpansionManager.isGroupExpanded(mEntry);
+ boolean nowExpanded = NotificationBundleUi.isEnabled()
+ ? mGroupExpansionManager.toggleGroupExpansion(mEntryAdapter)
+ : mGroupExpansionManager.toggleGroupExpansion(mEntry);
mOnExpandClickListener.onExpandClicked(mEntry, v, nowExpanded);
if (shouldLogExpandClickMetric) {
mMetricsLogger.action(MetricsEvent.ACTION_NOTIFICATION_GROUP_EXPANDER, nowExpanded);
@@ -910,6 +919,12 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
return mEntry;
}
+ @Nullable
+ public EntryAdapter getEntryAdapter() {
+ NotificationBundleUi.assertInNewMode();
+ return mEntryAdapter;
+ }
+
@Override
public boolean isHeadsUp() {
return mIsHeadsUp;
@@ -2010,11 +2025,25 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
*
* @param context context context of the view
* @param attrs attributes used to initialize parent view
+ * @param user the user the row is associated to
+ */
+ public ExpandableNotificationRow(Context context, AttributeSet attrs, UserHandle user) {
+ this(context, attrs, userContextForEntry(context, user));
+ NotificationBundleUi.assertInNewMode();
+ }
+
+ /**
+ * Constructs an ExpandableNotificationRow. Used by layout inflation (with a custom {@code
+ * AsyncLayoutFactory} in {@link RowInflaterTask}.
+ *
+ * @param context context context of the view
+ * @param attrs attributes used to initialize parent view
* @param entry notification that the row will be associated to (determines the user for the
* ImageResolver)
*/
public ExpandableNotificationRow(Context context, AttributeSet attrs, NotificationEntry entry) {
this(context, attrs, userContextForEntry(context, entry));
+ NotificationBundleUi.assertInLegacyMode();
}
private static Context userContextForEntry(Context base, NotificationEntry entry) {
@@ -2025,6 +2054,13 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
UserHandle.of(entry.getSbn().getNormalizedUserId()), /* flags= */ 0);
}
+ private static Context userContextForEntry(Context base, UserHandle user) {
+ if (base.getUserId() == user.getIdentifier()) {
+ return base;
+ }
+ return base.createContextAsUser(user, /* flags= */ 0);
+ }
+
private ExpandableNotificationRow(Context sysUiContext, AttributeSet attrs,
Context userContext) {
super(sysUiContext, attrs);
@@ -2067,7 +2103,14 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
IStatusBarService statusBarService,
UiEventLogger uiEventLogger,
NotificationRebindingTracker notificationRebindingTracker) {
- mEntry = entry;
+
+ if (NotificationBundleUi.isEnabled()) {
+ // TODO (b/395857098): remove when all usages are migrated
+ mEntryAdapter = entry.getEntryAdapter();
+ mEntry = entry;
+ } else {
+ mEntry = entry;
+ }
mAppName = appName;
mRebindingTracker = notificationRebindingTracker;
if (mMenuRow == null) {
@@ -2740,6 +2783,11 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
invalidateOutline();
mBackgroundNormal.setExpandAnimationSize(params.getWidth(), actualHeight);
+
+ if (Flags.notificationsLaunchRadius()) {
+ mBackgroundNormal.setRadius(params.getTopCornerRadius(),
+ params.getBottomCornerRadius());
+ }
}
public void setExpandAnimationRunning(boolean expandAnimationRunning) {
@@ -2871,8 +2919,12 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
public void setUserExpanded(boolean userExpanded, boolean allowChildExpansion) {
if (mIsSummaryWithChildren && !shouldShowPublic() && allowChildExpansion
&& !mChildrenContainer.showingAsLowPriority()) {
- final boolean wasExpanded = mGroupExpansionManager.isGroupExpanded(mEntry);
- mGroupExpansionManager.setGroupExpanded(mEntry, userExpanded);
+ final boolean wasExpanded = isGroupExpanded();
+ if (NotificationBundleUi.isEnabled()) {
+ mGroupExpansionManager.setGroupExpanded(mEntryAdapter, userExpanded);
+ } else {
+ mGroupExpansionManager.setGroupExpanded(mEntry, userExpanded);
+ }
onExpansionChanged(true /* userAction */, wasExpanded);
return;
}
@@ -3026,6 +3078,9 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
@Override
public boolean isGroupExpanded() {
+ if (NotificationBundleUi.isEnabled()) {
+ return mGroupExpansionManager.isGroupExpanded(mEntryAdapter);
+ }
return mGroupExpansionManager.isGroupExpanded(mEntry);
}
@@ -3182,12 +3237,20 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
}
public void setSensitive(boolean sensitive, boolean hideSensitive) {
+ if (notificationsRedesignTemplates()
+ && sensitive == mSensitive && hideSensitive == mSensitiveHiddenInGeneral) {
+ return; // nothing has changed
+ }
+
int intrinsicBefore = getIntrinsicHeight();
mSensitive = sensitive;
mSensitiveHiddenInGeneral = hideSensitive;
int intrinsicAfter = getIntrinsicHeight();
if (intrinsicBefore != intrinsicAfter) {
notifyHeightChanged(/* needsAnimation= */ true);
+ } else if (notificationsRedesignTemplates()) {
+ // Just request the correct layout, even if the height hasn't changed
+ getShowingLayout().requestSelectLayout(/* needsAnimation= */ true);
}
}
@@ -3222,11 +3285,14 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
}
boolean oldShowingPublic = mShowingPublic;
mShowingPublic = mSensitive && hideSensitive;
- if (mShowingPublicInitialized && mShowingPublic == oldShowingPublic) {
+ boolean isShowingLayoutNotChanged = mShowingPublic == oldShowingPublic;
+ if (mShowingPublicInitialized && isShowingLayoutNotChanged) {
return;
}
- if (!animated) {
+ final boolean shouldSkipHideSensitiveAnimation =
+ Flags.skipHideSensitiveNotifAnimation() && isShowingLayoutNotChanged;
+ if (!animated || shouldSkipHideSensitiveAnimation) {
if (!NotificationContentAlphaOptimization.isEnabled()
|| mShowingPublic != oldShowingPublic) {
// Don't reset the alpha or cancel the animation if the showing layout doesn't
@@ -3337,7 +3403,11 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
public void makeActionsVisibile() {
setUserExpanded(true, true);
if (isChildInGroup()) {
- mGroupExpansionManager.setGroupExpanded(mEntry, true);
+ if (NotificationBundleUi.isEnabled()) {
+ mGroupExpansionManager.setGroupExpanded(mEntryAdapter, true);
+ } else {
+ mGroupExpansionManager.setGroupExpanded(mEntry, true);
+ }
}
notifyHeightChanged(/* needsAnimation= */ false);
}
@@ -3761,7 +3831,9 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
public void onExpandedByGesture(boolean userExpanded) {
int event = MetricsEvent.ACTION_NOTIFICATION_GESTURE_EXPANDER;
- if (mGroupMembershipManager.isGroupSummary(mEntry)) {
+ if (NotificationBundleUi.isEnabled()
+ ? mGroupMembershipManager.isGroupRoot(mEntryAdapter)
+ : mGroupMembershipManager.isGroupSummary(mEntry)) {
event = MetricsEvent.ACTION_NOTIFICATION_GROUP_GESTURE_EXPANDER;
}
mMetricsLogger.action(event, userExpanded);
@@ -3797,7 +3869,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
private void onExpansionChanged(boolean userAction, boolean wasExpanded) {
boolean nowExpanded = isExpanded();
if (mIsSummaryWithChildren && (!mIsMinimized || wasExpanded)) {
- nowExpanded = mGroupExpansionManager.isGroupExpanded(mEntry);
+ nowExpanded = isGroupExpanded();
}
// Note: nowExpanded is going to be true here on the first expansion of minimized groups,
// even though the group itself is not expanded. Use mGroupExpansionManager to get the real
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 e311b53bfa64..0c1dd2e026b6 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
@@ -56,6 +56,7 @@ import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.notification.ConversationNotificationProcessor;
import com.android.systemui.statusbar.notification.InflationException;
import com.android.systemui.statusbar.notification.NmSummarizationUiFlag;
+import com.android.systemui.statusbar.notification.collection.EntryAdapter;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.promoted.PromotedNotificationContentExtractor;
import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUiForceExpanded;
@@ -1457,12 +1458,12 @@ public class NotificationContentInflater implements NotificationRowContentBinder
}
@Override
- public void handleInflationException(NotificationEntry entry, Exception e) {
+ public void handleInflationException(Exception e) {
handleError(e);
}
@Override
- public void onAsyncInflationFinished(NotificationEntry entry) {
+ public void onAsyncInflationFinished() {
mEntry.onInflationTaskFinished();
mRow.onNotificationUpdated();
if (mCallback != null) {
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 430e5e4f1520..6e638f5de209 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
@@ -18,22 +18,13 @@ package com.android.systemui.statusbar.notification.row;
import static android.app.AppOpsManager.OP_CAMERA;
import static android.app.AppOpsManager.OP_RECORD_AUDIO;
import static android.app.AppOpsManager.OP_SYSTEM_ALERT_WINDOW;
-import static android.service.notification.Adjustment.KEY_SUMMARIZATION;
-import static android.service.notification.Adjustment.KEY_TYPE;
-import static android.service.notification.NotificationAssistantService.ACTION_NOTIFICATION_ASSISTANT_FEEDBACK_SETTINGS;
-import static android.service.notification.NotificationAssistantService.EXTRA_NOTIFICATION_ADJUSTMENT;
-import static android.service.notification.NotificationAssistantService.EXTRA_NOTIFICATION_KEY;
-import android.annotation.FlaggedApi;
import android.app.INotificationManager;
import android.app.NotificationChannel;
-import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
-import android.content.pm.ActivityInfo;
import android.content.pm.LauncherApps;
import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
import android.content.pm.ShortcutManager;
import android.net.Uri;
import android.os.Bundle;
@@ -41,7 +32,6 @@ import android.os.Handler;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
-import android.service.notification.NotificationAssistantService;
import android.service.notification.StatusBarNotification;
import android.util.ArraySet;
import android.util.IconDrawableFactory;
@@ -81,13 +71,14 @@ import com.android.systemui.statusbar.notification.collection.provider.HighPrior
import com.android.systemui.statusbar.notification.collection.render.NotifGutsViewListener;
import com.android.systemui.statusbar.notification.collection.render.NotifGutsViewManager;
import com.android.systemui.statusbar.notification.headsup.HeadsUpManager;
+import com.android.systemui.statusbar.notification.row.icon.AppIconProvider;
+import com.android.systemui.statusbar.notification.row.icon.NotificationIconStyleProvider;
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.util.kotlin.JavaAdapter;
import com.android.systemui.wmshell.BubblesManager;
-import java.util.List;
import java.util.Optional;
import javax.inject.Inject;
@@ -131,6 +122,8 @@ public class NotificationGutsManager implements NotifGutsViewManager, CoreStarta
private final Optional<BubblesManager> mBubblesManagerOptional;
private Runnable mOpenRunnable;
private final INotificationManager mNotificationManager;
+ private final AppIconProvider mAppIconProvider;
+ private final NotificationIconStyleProvider mIconStyleProvider;
private final PeopleSpaceWidgetManager mPeopleSpaceWidgetManager;
private final UserManager mUserManager;
@@ -154,6 +147,8 @@ public class NotificationGutsManager implements NotifGutsViewManager, CoreStarta
AccessibilityManager accessibilityManager,
HighPriorityProvider highPriorityProvider,
INotificationManager notificationManager,
+ AppIconProvider appIconProvider,
+ NotificationIconStyleProvider iconStyleProvider,
UserManager userManager,
PeopleSpaceWidgetManager peopleSpaceWidgetManager,
LauncherApps launcherApps,
@@ -180,6 +175,8 @@ public class NotificationGutsManager implements NotifGutsViewManager, CoreStarta
mAccessibilityManager = accessibilityManager;
mHighPriorityProvider = highPriorityProvider;
mNotificationManager = notificationManager;
+ mAppIconProvider = appIconProvider;
+ mIconStyleProvider = iconStyleProvider;
mUserManager = userManager;
mPeopleSpaceWidgetManager = peopleSpaceWidgetManager;
mLauncherApps = launcherApps;
@@ -427,6 +424,8 @@ public class NotificationGutsManager implements NotifGutsViewManager, CoreStarta
notificationInfoView.bindNotification(
pmUser,
mNotificationManager,
+ mAppIconProvider,
+ mIconStyleProvider,
mOnUserInteractionCallback,
mChannelEditorDialogController,
packageName,
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 49b682d0a5d2..661122510c6c 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
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.notification.row;
+import static android.app.Flags.notificationsRedesignThemedAppIcons;
import static android.app.Notification.EXTRA_BUILDER_APPLICATION_INFO;
import static android.app.NotificationChannel.SYSTEM_RESERVED_IDS;
import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
@@ -25,11 +26,13 @@ import static android.service.notification.Adjustment.KEY_SUMMARIZATION;
import static android.service.notification.Adjustment.KEY_TYPE;
import static com.android.app.animation.Interpolators.FAST_OUT_SLOW_IN;
+import static com.android.systemui.Flags.notificationsRedesignGuts;
import static java.lang.annotation.RetentionPolicy.SOURCE;
import android.annotation.IntDef;
import android.annotation.Nullable;
+import android.annotation.SuppressLint;
import android.app.Flags;
import android.app.INotificationManager;
import android.app.Notification;
@@ -70,8 +73,9 @@ import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.systemui.Dependency;
import com.android.systemui.res.R;
import com.android.systemui.statusbar.notification.AssistantFeedbackController;
-import com.android.systemui.statusbar.notification.NmSummarizationUiFlag;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.row.icon.AppIconProvider;
+import com.android.systemui.statusbar.notification.row.icon.NotificationIconStyleProvider;
import java.lang.annotation.Retention;
import java.util.List;
@@ -88,6 +92,8 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G
private TextView mAutomaticDescriptionView;
private INotificationManager mINotificationManager;
+ private AppIconProvider mAppIconProvider;
+ private NotificationIconStyleProvider mIconStyleProvider;
private OnUserInteractionCallback mOnUserInteractionCallback;
private PackageManager mPm;
private MetricsLogger mMetricsLogger;
@@ -183,6 +189,8 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G
public void bindNotification(
PackageManager pm,
INotificationManager iNotificationManager,
+ AppIconProvider appIconProvider,
+ NotificationIconStyleProvider iconStyleProvider,
OnUserInteractionCallback onUserInteractionCallback,
ChannelEditorDialogController channelEditorDialogController,
String pkg,
@@ -200,6 +208,8 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G
OnClickListener onCloseClick)
throws RemoteException {
mINotificationManager = iNotificationManager;
+ mAppIconProvider = appIconProvider;
+ mIconStyleProvider = iconStyleProvider;
mMetricsLogger = metricsLogger;
mOnUserInteractionCallback = onUserInteractionCallback;
mChannelEditorDialogController = channelEditorDialogController;
@@ -290,23 +300,39 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G
applyAlertingBehavior(behavior, false /* userTriggered */);
}
+ @SuppressLint("WrongThread")
private void bindHeader() {
- // Package name
mPkgIcon = null;
// filled in if missing during notification inflation, which must have happened if
// we have a notification to long press on
ApplicationInfo info =
mSbn.getNotification().extras.getParcelable(EXTRA_BUILDER_APPLICATION_INFO,
ApplicationInfo.class);
- if (info != null) {
- try {
- mAppName = String.valueOf(mPm.getApplicationLabel(info));
- mPkgIcon = mPm.getApplicationIcon(info);
- } catch (Exception ignored) {}
- }
- if (mPkgIcon == null) {
- // app is gone, just show package name and generic icon
- mPkgIcon = mPm.getDefaultActivityIcon();
+ if (notificationsRedesignGuts()) {
+ if (info != null) {
+ try {
+ mAppName = String.valueOf(mPm.getApplicationLabel(info));
+ // The app icon is likely already in the cache, so let's use it
+ boolean withWorkProfileBadge =
+ mIconStyleProvider.shouldShowWorkProfileBadge(mSbn, getContext());
+ mPkgIcon = mAppIconProvider.getOrFetchAppIcon(info.packageName, getContext(),
+ withWorkProfileBadge,
+ /* themed = */ notificationsRedesignThemedAppIcons());
+ } catch (Exception ignored) {
+ }
+ }
+ } else {
+ if (info != null) {
+ try {
+ mAppName = String.valueOf(mPm.getApplicationLabel(info));
+ mPkgIcon = mPm.getApplicationIcon(info);
+ } catch (Exception ignored) {
+ }
+ }
+ if (mPkgIcon == null) {
+ // app is gone, just show package name and generic icon
+ mPkgIcon = mPm.getDefaultActivityIcon();
+ }
}
((ImageView) findViewById(R.id.pkg_icon)).setImageDrawable(mPkgIcon);
((TextView) findViewById(R.id.pkg_name)).setText(mAppName);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinder.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinder.java
index 0be1d5d9d79d..05934e7edfba 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinder.java
@@ -24,6 +24,7 @@ import android.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
+import com.android.systemui.statusbar.notification.collection.EntryAdapter;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import java.lang.annotation.Retention;
@@ -170,13 +171,29 @@ public interface NotificationRowContentBinder {
* @param entry notification which failed to inflate content
* @param e exception
*/
- void handleInflationException(NotificationEntry entry, Exception e);
+ default void handleInflationException(NotificationEntry entry, Exception e) {
+ handleInflationException(e);
+ }
+
+ /**
+ * Callback for when there is an inflation exception
+ *
+ * @param e exception
+ */
+ void handleInflationException(Exception e);
/**
* Callback for after the content views finish inflating.
*
* @param entry the entry with the content views set
*/
- void onAsyncInflationFinished(NotificationEntry entry);
+ default void onAsyncInflationFinished(NotificationEntry entry) {
+ onAsyncInflationFinished();
+ }
+
+ /**
+ * Callback for after the content views finish inflating.
+ */
+ void onAsyncInflationFinished();
}
}
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 517fc3a06d84..761d3fe91cd0 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
@@ -49,6 +49,7 @@ import com.android.systemui.statusbar.NotificationRemoteInputManager
import com.android.systemui.statusbar.notification.ConversationNotificationProcessor
import com.android.systemui.statusbar.notification.InflationException
import com.android.systemui.statusbar.notification.NmSummarizationUiFlag
+import com.android.systemui.statusbar.notification.collection.EntryAdapter
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.promoted.PromotedNotificationContentExtractor
import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel
@@ -76,6 +77,7 @@ import com.android.systemui.statusbar.notification.row.shared.NotificationConten
import com.android.systemui.statusbar.notification.row.shared.NotificationRowContentBinderRefactor
import com.android.systemui.statusbar.notification.row.ui.viewbinder.SingleLineViewBinder
import com.android.systemui.statusbar.notification.row.wrapper.NotificationViewWrapper
+import com.android.systemui.statusbar.notification.shared.NotificationBundleUi
import com.android.systemui.statusbar.notification.stack.NotificationChildrenContainer
import com.android.systemui.statusbar.policy.InflatedSmartReplyState
import com.android.systemui.statusbar.policy.InflatedSmartReplyViewHolder
@@ -536,7 +538,7 @@ constructor(
val ident: String = (sbn.packageName + "/0x" + Integer.toHexString(sbn.id))
Log.e(TAG, "couldn't inflate view for notification $ident", e)
callback?.handleInflationException(
- row.entry,
+ if (NotificationBundleUi.isEnabled) entry else row.entry,
InflationException("Couldn't inflate contentViews$e"),
)
@@ -554,11 +556,11 @@ constructor(
logger.logAsyncTaskProgress(entry, "aborted")
}
- override fun handleInflationException(entry: NotificationEntry, e: Exception) {
+ override fun handleInflationException(e: Exception) {
handleError(e)
}
- override fun onAsyncInflationFinished(entry: NotificationEntry) {
+ override fun onAsyncInflationFinished() {
this.entry.onInflationTaskFinished()
row.onNotificationUpdated()
callback?.onAsyncInflationFinished(this.entry)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PromotedNotificationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PromotedNotificationInfo.java
index db25e0889298..6ff711deeb01 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PromotedNotificationInfo.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PromotedNotificationInfo.java
@@ -31,6 +31,8 @@ import com.android.internal.logging.UiEventLogger;
import com.android.systemui.res.R;
import com.android.systemui.statusbar.notification.AssistantFeedbackController;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.row.icon.AppIconProvider;
+import com.android.systemui.statusbar.notification.row.icon.NotificationIconStyleProvider;
/**
* The guts of a notification revealed when performing a long press, specifically
@@ -50,6 +52,8 @@ public class PromotedNotificationInfo extends NotificationInfo {
public void bindNotification(
PackageManager pm,
INotificationManager iNotificationManager,
+ AppIconProvider appIconProvider,
+ NotificationIconStyleProvider iconStyleProvider,
OnUserInteractionCallback onUserInteractionCallback,
ChannelEditorDialogController channelEditorDialogController,
String pkg,
@@ -64,11 +68,11 @@ public class PromotedNotificationInfo extends NotificationInfo {
boolean wasShownHighPriority,
AssistantFeedbackController assistantFeedbackController,
MetricsLogger metricsLogger, OnClickListener onCloseClick) throws RemoteException {
- super.bindNotification(pm, iNotificationManager, onUserInteractionCallback,
- channelEditorDialogController, pkg, notificationChannel, entry, onSettingsClick,
- onAppSettingsClick, feedbackClickListener, uiEventLogger, isDeviceProvisioned,
- isNonblockable, wasShownHighPriority, assistantFeedbackController, metricsLogger,
- onCloseClick);
+ super.bindNotification(pm, iNotificationManager, appIconProvider, iconStyleProvider,
+ onUserInteractionCallback, channelEditorDialogController, pkg, notificationChannel,
+ entry, onSettingsClick, onAppSettingsClick, feedbackClickListener, uiEventLogger,
+ isDeviceProvisioned, isNonblockable, wasShownHighPriority,
+ assistantFeedbackController, metricsLogger, onCloseClick);
mNotificationManager = iNotificationManager;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStage.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStage.java
index 6883ec575d7e..da361406fa2a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStage.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStage.java
@@ -21,10 +21,12 @@ import static com.android.systemui.statusbar.notification.row.NotificationRowCon
import androidx.annotation.NonNull;
import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.statusbar.notification.collection.EntryAdapter;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.BindParams;
import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationCallback;
import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag;
+import com.android.systemui.statusbar.notification.shared.NotificationBundleUi;
import javax.inject.Inject;
@@ -52,7 +54,7 @@ public class RowContentBindStage extends BindStage<RowContentBindParams> {
@Override
protected void executeStage(
- @NonNull NotificationEntry entry,
+ final @NonNull NotificationEntry entry,
@NonNull ExpandableNotificationRow row,
@NonNull StageCallback callback) {
RowContentBindParams params = getStageParams(entry);
@@ -77,15 +79,35 @@ public class RowContentBindStage extends BindStage<RowContentBindParams> {
InflationCallback inflationCallback = new InflationCallback() {
@Override
- public void handleInflationException(NotificationEntry entry, Exception e) {
- mNotifInflationErrorManager.setInflationError(entry, e);
+ public void handleInflationException(NotificationEntry errorEntry, Exception e) {
+ if (NotificationBundleUi.isEnabled()) {
+ mNotifInflationErrorManager.setInflationError(entry, e);
+ } else {
+ mNotifInflationErrorManager.setInflationError(errorEntry, e);
+ }
+ }
+
+ @Override
+ public void handleInflationException(Exception e) {
+
}
@Override
- public void onAsyncInflationFinished(NotificationEntry entry) {
- mNotifInflationErrorManager.clearInflationError(entry);
- getStageParams(entry).clearDirtyContentViews();
- callback.onStageFinished(entry);
+ public void onAsyncInflationFinished(NotificationEntry finishedEntry) {
+ if (NotificationBundleUi.isEnabled()) {
+ mNotifInflationErrorManager.clearInflationError(entry);
+ getStageParams(entry).clearDirtyContentViews();
+ callback.onStageFinished(entry);
+ } else {
+ mNotifInflationErrorManager.clearInflationError(finishedEntry);
+ getStageParams(finishedEntry).clearDirtyContentViews();
+ callback.onStageFinished(finishedEntry);
+ }
+ }
+
+ @Override
+ public void onAsyncInflationFinished() {
+
}
};
mBinder.cancelBind(entry, row);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowInflaterTask.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowInflaterTask.java
index 9f634bef4c5e..3971661fa787 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowInflaterTask.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowInflaterTask.java
@@ -17,6 +17,7 @@
package com.android.systemui.statusbar.notification.row;
import android.content.Context;
+import android.os.UserHandle;
import android.util.AttributeSet;
import android.util.Log;
import android.view.LayoutInflater;
@@ -29,9 +30,12 @@ import androidx.annotation.VisibleForTesting;
import androidx.asynclayoutinflater.view.AsyncLayoutFactory;
import androidx.asynclayoutinflater.view.AsyncLayoutInflater;
+import com.android.systemui.Flags;
import com.android.systemui.res.R;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.InflationTask;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.shared.NotificationBundleUi;
import com.android.systemui.util.time.SystemClock;
import java.util.concurrent.Executor;
@@ -41,7 +45,8 @@ import javax.inject.Inject;
/**
* An inflater task that asynchronously inflates a ExpandableNotificationRow
*/
-public class RowInflaterTask implements InflationTask, AsyncLayoutInflater.OnInflateFinishedListener {
+public class RowInflaterTask implements InflationTask,
+ AsyncLayoutInflater.OnInflateFinishedListener, AsyncRowInflater.OnInflateFinishedListener {
private static final String TAG = "RowInflaterTask";
private static final boolean TRACE_ORIGIN = true;
@@ -52,12 +57,17 @@ public class RowInflaterTask implements InflationTask, AsyncLayoutInflater.OnInf
private Throwable mInflateOrigin;
private final SystemClock mSystemClock;
private final RowInflaterTaskLogger mLogger;
+ private final AsyncRowInflater mAsyncRowInflater;
private long mInflateStartTimeMs;
+ private UserTracker mUserTracker;
@Inject
- public RowInflaterTask(SystemClock systemClock, RowInflaterTaskLogger logger) {
+ public RowInflaterTask(SystemClock systemClock, RowInflaterTaskLogger logger,
+ UserTracker userTracker, AsyncRowInflater asyncRowInflater) {
mSystemClock = systemClock;
mLogger = logger;
+ mUserTracker = userTracker;
+ mAsyncRowInflater = asyncRowInflater;
}
/**
@@ -81,13 +91,19 @@ public class RowInflaterTask implements InflationTask, AsyncLayoutInflater.OnInf
mInflateOrigin = new Throwable("inflate requested here");
}
mListener = listener;
- AsyncLayoutInflater inflater = new AsyncLayoutInflater(context, makeRowInflater(entry));
+ RowAsyncLayoutInflater asyncLayoutFactory = makeRowInflater(entry);
mEntry = entry;
entry.setInflationTask(this);
mLogger.logInflateStart(entry);
mInflateStartTimeMs = mSystemClock.elapsedRealtime();
- inflater.inflate(R.layout.status_bar_notification_row, parent, listenerExecutor, this);
+ if (Flags.useNotifInflationThreadForRow()) {
+ mAsyncRowInflater.inflate(context, asyncLayoutFactory,
+ R.layout.status_bar_notification_row, parent, this);
+ } else {
+ AsyncLayoutInflater inflater = new AsyncLayoutInflater(context, asyncLayoutFactory);
+ inflater.inflate(R.layout.status_bar_notification_row, parent, listenerExecutor, this);
+ }
}
/**
@@ -107,40 +123,8 @@ public class RowInflaterTask implements InflationTask, AsyncLayoutInflater.OnInf
}
private RowAsyncLayoutInflater makeRowInflater(NotificationEntry entry) {
- return new RowAsyncLayoutInflater(entry, mSystemClock, mLogger);
- }
-
- /**
- * A {@link LayoutInflater} that is copy of BasicLayoutInflater.
- */
- private static class BasicRowInflater extends LayoutInflater {
- private static final String[] sClassPrefixList =
- {"android.widget.", "android.webkit.", "android.app."};
- BasicRowInflater(Context context) {
- super(context);
- }
-
- @Override
- public LayoutInflater cloneInContext(Context newContext) {
- return new BasicRowInflater(newContext);
- }
-
- @Override
- protected View onCreateView(String name, AttributeSet attrs) throws ClassNotFoundException {
- for (String prefix : sClassPrefixList) {
- try {
- View view = createView(name, prefix, attrs);
- if (view != null) {
- return view;
- }
- } catch (ClassNotFoundException e) {
- // In this case we want to let the base class take a crack
- // at it.
- }
- }
-
- return super.onCreateView(name, attrs);
- }
+ return new RowAsyncLayoutInflater(
+ entry, mSystemClock, mLogger, mUserTracker.getUserHandle());
}
@VisibleForTesting
@@ -148,12 +132,14 @@ public class RowInflaterTask implements InflationTask, AsyncLayoutInflater.OnInf
private final NotificationEntry mEntry;
private final SystemClock mSystemClock;
private final RowInflaterTaskLogger mLogger;
+ private final UserHandle mTargetUser;
public RowAsyncLayoutInflater(NotificationEntry entry, SystemClock systemClock,
- RowInflaterTaskLogger logger) {
+ RowInflaterTaskLogger logger, UserHandle targetUser) {
mEntry = entry;
mSystemClock = systemClock;
mLogger = logger;
+ mTargetUser = targetUser;
}
@Nullable
@@ -165,8 +151,12 @@ public class RowInflaterTask implements InflationTask, AsyncLayoutInflater.OnInf
}
final long startMs = mSystemClock.elapsedRealtime();
- final ExpandableNotificationRow row =
- new ExpandableNotificationRow(context, attrs, mEntry);
+ ExpandableNotificationRow row = null;
+ if (NotificationBundleUi.isEnabled()) {
+ row = new ExpandableNotificationRow(context, attrs, mTargetUser);
+ } else {
+ row = new ExpandableNotificationRow(context, attrs, mEntry);
+ }
final long elapsedMs = mSystemClock.elapsedRealtime() - startMs;
mLogger.logCreatedRow(mEntry, elapsedMs);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/icon/AppIconProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/icon/AppIconProvider.kt
index 52a0f6f92355..33d943348cb3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/icon/AppIconProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/icon/AppIconProvider.kt
@@ -58,7 +58,7 @@ interface AppIconProvider {
packageName: String,
context: Context,
withWorkProfileBadge: Boolean = false,
- themed: Boolean = true,
+ themed: Boolean = false,
): Drawable
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index b9352bf64be4..c694a19a46ae 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -122,6 +122,7 @@ import com.android.systemui.statusbar.notification.row.ActivatableNotificationVi
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.ExpandableView;
import com.android.systemui.statusbar.notification.row.StackScrollerDecorView;
+import com.android.systemui.statusbar.notification.shared.NotificationBundleUi;
import com.android.systemui.statusbar.notification.shared.NotificationContentAlphaOptimization;
import com.android.systemui.statusbar.notification.shared.NotificationHeadsUpCycling;
import com.android.systemui.statusbar.notification.shared.NotificationThrottleHun;
@@ -2958,9 +2959,13 @@ public class NotificationStackScrollLayout
}
private boolean isChildInGroup(View child) {
- return child instanceof ExpandableNotificationRow
- && mGroupMembershipManager.isChildInGroup(
- ((ExpandableNotificationRow) child).getEntry());
+ if (child instanceof ExpandableNotificationRow) {
+ ExpandableNotificationRow childRow = (ExpandableNotificationRow) child;
+ return NotificationBundleUi.isEnabled()
+ ? mGroupMembershipManager.isChildInGroup(childRow.getEntryAdapter())
+ : mGroupMembershipManager.isChildInGroup(childRow.getEntry());
+ }
+ return false;
}
/**
@@ -3639,6 +3644,9 @@ public class NotificationStackScrollLayout
mScrollViewFields.sendCurrentGestureInGuts(false);
mScrollViewFields.sendCurrentGestureOverscroll(false);
setIsBeingDragged(false);
+ // dispatch to touchHandlers, so they can still finalize a previously started
+ // motion, while the shade is being dragged
+ return super.dispatchTouchEvent(ev);
}
return false;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index 810d0b43b0dd..888c8cc59439 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -382,9 +382,15 @@ public class NotificationStackScrollLayoutController implements Dumpable {
// Only animate if in a non-sensitive state (not screen sharing)
boolean shouldAnimate = animate && !isSensitiveContentProtectionActive;
+ mLogger.logUpdateSensitivenessWithAnimation(shouldAnimate,
+ isSensitive,
+ isSensitiveContentProtectionActive,
+ isAnyProfilePublic);
mView.updateSensitiveness(shouldAnimate, isSensitive);
} else {
- mView.updateSensitiveness(animate, mLockscreenUserManager.isAnyProfilePublicMode());
+ boolean anyProfilePublicMode = mLockscreenUserManager.isAnyProfilePublicMode();
+ mLogger.logUpdateSensitivenessWithAnimation(animate, anyProfilePublicMode);
+ mView.updateSensitiveness(animate, anyProfilePublicMode);
}
Trace.endSection();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLogger.kt
index 3396306412bd..30658710c3c5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLogger.kt
@@ -156,6 +156,44 @@ class NotificationStackScrollLogger @Inject constructor(
{ "removeTransientRow from NSSL: childKey: $str1" }
)
}
+
+ fun logUpdateSensitivenessWithAnimation(
+ shouldAnimate: Boolean,
+ isSensitive: Boolean,
+ isSensitiveContentProtectionActive: Boolean,
+ isAnyProfilePublic: Boolean,
+ ) {
+ notificationRenderBuffer.log(
+ TAG,
+ INFO,
+ {
+ bool1 = shouldAnimate
+ bool2 = isSensitive
+ bool3 = isSensitiveContentProtectionActive
+ bool4 = isAnyProfilePublic
+ },
+ {
+ "updateSensitivenessWithAnimation from NSSL: shouldAnimate=$bool1 " +
+ "isSensitive(hideSensitive)=$bool2 isSensitiveContentProtectionActive=$bool3 " +
+ "isAnyProfilePublic=$bool4"
+ },
+ )
+ }
+
+ fun logUpdateSensitivenessWithAnimation(animate: Boolean, anyProfilePublicMode: Boolean) {
+ notificationRenderBuffer.log(
+ TAG,
+ INFO,
+ {
+ bool1 = animate
+ bool2 = anyProfilePublicMode
+ },
+ {
+ "updateSensitivenessWithAnimation from NSSL: animate=$bool1 " +
+ "anyProfilePublicMode(hideSensitive)=$bool2"
+ },
+ )
+ }
}
-private const val TAG = "NotificationStackScroll" \ No newline at end of file
+private const val TAG = "NotificationStackScroll"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/shared/model/ShadeScrimBounds.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/shared/model/ShadeScrimBounds.kt
index 832e69080f3c..78ece5336d04 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/shared/model/ShadeScrimBounds.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/shared/model/ShadeScrimBounds.kt
@@ -16,6 +16,8 @@
package com.android.systemui.statusbar.notification.stack.shared.model
+import androidx.compose.ui.geometry.Rect
+
/** Models the bounds of the notification stack. */
data class ShadeScrimBounds(
/** The position of the left of the stack in its window coordinate system, in pixels. */
@@ -27,6 +29,10 @@ data class ShadeScrimBounds(
/** The position of the bottom of the stack in its window coordinate system, in pixels. */
val bottom: Float = 0f,
) {
+ constructor(
+ bounds: Rect
+ ) : this(left = bounds.left, top = bounds.top, right = bounds.right, bottom = bounds.bottom)
+
/** The current height of the notification container. */
val height: Float = bottom - top
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt
index 6385d53dbc8b..10b665d8ef01 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt
@@ -25,7 +25,7 @@ import com.android.internal.logging.MetricsLogger
import com.android.internal.logging.nano.MetricsProto
import com.android.systemui.common.ui.ConfigurationState
import com.android.systemui.common.ui.view.setImportantForAccessibilityYesNo
-import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.NotifInflation
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.lifecycle.repeatWhenAttachedToWindow
import com.android.systemui.plugins.FalsingManager
@@ -76,7 +76,7 @@ import kotlinx.coroutines.flow.stateIn
class NotificationListViewBinder
@Inject
constructor(
- @Background private val backgroundDispatcher: CoroutineDispatcher,
+ @NotifInflation private val inflationDispatcher: CoroutineDispatcher,
private val hiderTracker: DisplaySwitchNotificationsHiderTracker,
@ShadeDisplayAware private val configuration: ConfigurationState,
private val falsingManager: FalsingManager,
@@ -155,7 +155,7 @@ constructor(
parentView,
attachToRoot = false,
)
- .flowOn(backgroundDispatcher)
+ .flowOn(inflationDispatcher)
.collectLatest { footerView: FooterView ->
traceAsync("bind FooterView") {
parentView.setFooterView(footerView)
@@ -240,7 +240,7 @@ constructor(
parentView,
attachToRoot = false,
)
- .flowOn(backgroundDispatcher)
+ .flowOn(inflationDispatcher)
.collectLatest { emptyShadeView: EmptyShadeView ->
traceAsync("bind EmptyShadeView") {
parentView.setEmptyShadeView(emptyShadeView)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
index 2c8c7a1bdd44..54efa4a2bcf2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
@@ -48,7 +48,6 @@ import com.android.systemui.keyguard.ui.viewmodel.AodToGoneTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.AodToLockscreenTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.AodToOccludedTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.AodToPrimaryBouncerTransitionViewModel
-import com.android.systemui.keyguard.ui.viewmodel.DozingToDreamingTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.DozingToGlanceableHubTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.DozingToLockscreenTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.DozingToOccludedTransitionViewModel
@@ -137,7 +136,6 @@ constructor(
private val aodToLockscreenTransitionViewModel: AodToLockscreenTransitionViewModel,
private val aodToOccludedTransitionViewModel: AodToOccludedTransitionViewModel,
private val aodToPrimaryBouncerTransitionViewModel: AodToPrimaryBouncerTransitionViewModel,
- private val dozingToDreamingTransitionViewModel: DozingToDreamingTransitionViewModel,
dozingToGlanceableHubTransitionViewModel: DozingToGlanceableHubTransitionViewModel,
private val dozingToLockscreenTransitionViewModel: DozingToLockscreenTransitionViewModel,
private val dozingToOccludedTransitionViewModel: DozingToOccludedTransitionViewModel,
@@ -574,7 +572,6 @@ constructor(
aodToLockscreenTransitionViewModel.notificationAlpha,
aodToOccludedTransitionViewModel.lockscreenAlpha(viewState),
aodToPrimaryBouncerTransitionViewModel.notificationAlpha,
- dozingToDreamingTransitionViewModel.notificationAlpha,
dozingToLockscreenTransitionViewModel.lockscreenAlpha,
dozingToOccludedTransitionViewModel.lockscreenAlpha(viewState),
dozingToPrimaryBouncerTransitionViewModel.notificationAlpha,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java
index 1dc9de489806..05a46cd9fa31 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java
@@ -54,6 +54,7 @@ import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
+import com.android.systemui.statusbar.notification.shared.NotificationBundleUi;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.util.kotlin.JavaAdapter;
@@ -215,7 +216,11 @@ public class StatusBarRemoteInputCallback implements Callback, Callbacks,
if (ExpandHeadsUpOnInlineReply.isEnabled()) {
if (row.isChildInGroup() && !row.areChildrenExpanded()) {
// The group isn't expanded, let's make sure it's visible!
- mGroupExpansionManager.toggleGroupExpansion(row.getEntry());
+ if (NotificationBundleUi.isEnabled()) {
+ mGroupExpansionManager.toggleGroupExpansion(row.getEntryAdapter());
+ } else {
+ mGroupExpansionManager.toggleGroupExpansion(row.getEntry());
+ }
} else if (!row.isChildInGroup()) {
final boolean expandNotification;
if (row.isPinned()) {
@@ -233,7 +238,11 @@ public class StatusBarRemoteInputCallback implements Callback, Callbacks,
} else {
if (row.isChildInGroup() && !row.areChildrenExpanded()) {
// The group isn't expanded, let's make sure it's visible!
- mGroupExpansionManager.toggleGroupExpansion(row.getEntry());
+ if (NotificationBundleUi.isEnabled()) {
+ mGroupExpansionManager.toggleGroupExpansion(row.getEntryAdapter());
+ } else {
+ mGroupExpansionManager.toggleGroupExpansion(row.getEntry());
+ }
}
if (android.app.Flags.compactHeadsUpNotificationReply()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
index a1f7a81e258a..0eabb4ecee84 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
@@ -37,10 +37,8 @@ import com.android.systemui.statusbar.pipeline.satellite.ui.model.SatelliteIconM
import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
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.asStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flatMapLatest
@@ -255,12 +253,7 @@ class MobileIconInteractorImpl(
}
.stateIn(scope, SharingStarted.WhileSubscribed(), false)
- override val isNonTerrestrial: StateFlow<Boolean> =
- if (Flags.carrierEnabledSatelliteFlag()) {
- connectionRepository.isNonTerrestrial
- } else {
- MutableStateFlow(false).asStateFlow()
- }
+ override val isNonTerrestrial: StateFlow<Boolean> = connectionRepository.isNonTerrestrial
override val isRoaming: StateFlow<Boolean> =
combine(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt
index 788f041b38c0..0eef2e1ca685 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt
@@ -45,6 +45,7 @@ import com.android.systemui.statusbar.pipeline.shared.ui.binder.ModernStatusBarV
import com.android.systemui.statusbar.pipeline.shared.ui.binder.ModernStatusBarViewVisibilityHelper
import com.android.systemui.statusbar.pipeline.shared.ui.binder.StatusBarViewBinderConstants.ALPHA_ACTIVE
import com.android.systemui.statusbar.pipeline.shared.ui.binder.StatusBarViewBinderConstants.ALPHA_INACTIVE
+import com.android.systemui.util.kotlin.pairwiseBy
import kotlinx.coroutines.awaitCancellation
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.distinctUntilChanged
@@ -131,19 +132,37 @@ object MobileIconBinder {
// Set the icon for the triangle
launch {
- viewModel.icon.distinctUntilChanged().collect { icon ->
- viewModel.verboseLogger?.logBinderReceivedSignalIcon(
- view,
- viewModel.subscriptionId,
- icon,
- )
- if (icon is SignalIconModel.Cellular) {
- iconView.setImageDrawable(mobileDrawable)
- mobileDrawable.level = icon.toSignalDrawableState()
- } else if (icon is SignalIconModel.Satellite) {
- IconViewBinder.bind(icon.icon, iconView)
+ viewModel.icon
+ .pairwiseBy(initialValue = null) { oldIcon, newIcon ->
+ // Make sure we requestLayout if the number of levels changes
+ val shouldRequestLayout =
+ when {
+ oldIcon == null -> true
+ oldIcon is SignalIconModel.Cellular &&
+ newIcon is SignalIconModel.Cellular -> {
+ oldIcon.numberOfLevels != newIcon.numberOfLevels
+ }
+ else -> false
+ }
+ Pair(shouldRequestLayout, newIcon)
+ }
+ .collect { (shouldRequestLayout, newIcon) ->
+ viewModel.verboseLogger?.logBinderReceivedSignalIcon(
+ view,
+ viewModel.subscriptionId,
+ newIcon,
+ )
+ if (newIcon is SignalIconModel.Cellular) {
+ iconView.setImageDrawable(mobileDrawable)
+ mobileDrawable.level = newIcon.toSignalDrawableState()
+ } else if (newIcon is SignalIconModel.Satellite) {
+ IconViewBinder.bind(newIcon.icon, iconView)
+ }
+
+ if (shouldRequestLayout) {
+ iconView.requestLayout()
+ }
}
- }
}
launch {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileView.kt
index fd5ab135a1ad..7eda87f8418d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileView.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileView.kt
@@ -19,19 +19,18 @@ package com.android.systemui.statusbar.pipeline.mobile.ui.view
import android.content.Context
import android.util.AttributeSet
import android.view.LayoutInflater
+import android.widget.FrameLayout
import android.widget.ImageView
-import com.android.settingslib.flags.Flags.newStatusBarIcons
import com.android.systemui.res.R
import com.android.systemui.statusbar.StatusBarIconView.getVisibleStateString
+import com.android.systemui.statusbar.core.NewStatusBarIcons
import com.android.systemui.statusbar.pipeline.mobile.ui.MobileViewLogger
import com.android.systemui.statusbar.pipeline.mobile.ui.binder.MobileIconBinder
import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.LocationBasedMobileViewModel
import com.android.systemui.statusbar.pipeline.shared.ui.view.ModernStatusBarView
-class ModernStatusBarMobileView(
- context: Context,
- attrs: AttributeSet?,
-) : ModernStatusBarView(context, attrs) {
+class ModernStatusBarMobileView(context: Context, attrs: AttributeSet?) :
+ ModernStatusBarView(context, attrs) {
var subId: Int = -1
@@ -62,15 +61,34 @@ class ModernStatusBarMobileView(
as ModernStatusBarMobileView)
.also {
// Flag-specific configuration
- if (newStatusBarIcons()) {
- // New icon (with no embedded whitespace) is slightly shorter
- // (but actually taller)
- val iconView = it.requireViewById<ImageView>(R.id.mobile_signal)
- val lp = iconView.layoutParams
- lp.height =
- context.resources.getDimensionPixelSize(
- R.dimen.status_bar_mobile_signal_size_updated
- )
+ if (NewStatusBarIcons.isEnabled) {
+ // triangle
+ it.requireViewById<ImageView>(R.id.mobile_signal).apply {
+ layoutParams.height =
+ context.resources.getDimensionPixelSize(
+ R.dimen.status_bar_mobile_signal_size_updated
+ )
+ }
+
+ // RAT indicator container
+ it.requireViewById<FrameLayout>(R.id.mobile_type_container).apply {
+ (layoutParams as MarginLayoutParams).marginEnd =
+ context.resources.getDimensionPixelSize(
+ R.dimen.status_bar_mobile_container_margin_end
+ )
+ layoutParams.height =
+ context.resources.getDimensionPixelSize(
+ R.dimen.status_bar_mobile_container_height_updated
+ )
+ }
+
+ // RAT indicator
+ it.requireViewById<ImageView>(R.id.mobile_type).apply {
+ layoutParams.height =
+ context.resources.getDimensionPixelSize(
+ R.dimen.status_bar_mobile_type_size_updated
+ )
+ }
}
it.subId = viewModel.subscriptionId
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt
index 171e4f59b0e5..e37c3f10cfcb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt
@@ -23,6 +23,7 @@ import com.android.systemui.flags.FeatureFlagsClassic
import com.android.systemui.flags.Flags.NEW_NETWORK_SLICE_UI
import com.android.systemui.log.table.logDiffsForTable
import com.android.systemui.res.R
+import com.android.systemui.statusbar.core.NewStatusBarIcons
import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconInteractor
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor
@@ -278,10 +279,11 @@ private class CellularIconViewModel(
flowOf(null)
} else {
iconInteractor.showSliceAttribution.map {
- if (it) {
- Icon.Resource(R.drawable.mobile_network_type_background, null)
- } else {
- null
+ when {
+ it && NewStatusBarIcons.isEnabled ->
+ Icon.Resource(R.drawable.mobile_network_type_background_updated, null)
+ it -> Icon.Resource(R.drawable.mobile_network_type_background, null)
+ else -> null
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/composable/StatusBarRoot.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/composable/StatusBarRoot.kt
index a961713230c8..39a1b46292b0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/composable/StatusBarRoot.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/composable/StatusBarRoot.kt
@@ -255,10 +255,9 @@ fun StatusBarRoot(
)
setContent {
- val chips =
- statusBarViewModel.statusBarPopupChips
- .collectAsStateWithLifecycle()
- StatusBarPopupChipsContainer(chips = chips.value)
+ StatusBarPopupChipsContainer(
+ chips = statusBarViewModel.popupChips
+ )
}
}
endSideContent.addView(composeView, 0)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModel.kt
index f396cbfc8000..9ae2cb203d91 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModel.kt
@@ -20,6 +20,7 @@ import android.annotation.ColorInt
import android.graphics.Rect
import android.view.View
import androidx.compose.runtime.getValue
+import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
@@ -29,6 +30,7 @@ import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
import com.android.systemui.keyguard.shared.model.KeyguardState.OCCLUDED
import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.lifecycle.Activatable
import com.android.systemui.lifecycle.ExclusiveActivatable
import com.android.systemui.lifecycle.Hydrator
import com.android.systemui.log.table.TableLogBufferFactory
@@ -49,6 +51,7 @@ import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipsViewModel
import com.android.systemui.statusbar.events.domain.interactor.SystemStatusEventAnimationInteractor
import com.android.systemui.statusbar.events.shared.model.SystemEventAnimationState.Idle
+import com.android.systemui.statusbar.featurepods.popups.StatusBarPopupChips
import com.android.systemui.statusbar.featurepods.popups.shared.model.PopupChipModel
import com.android.systemui.statusbar.featurepods.popups.ui.viewmodel.StatusBarPopupChipsViewModel
import com.android.systemui.statusbar.headsup.shared.StatusBarNoHunBehavior
@@ -71,6 +74,8 @@ import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.awaitCancellation
+import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
@@ -94,7 +99,7 @@ import kotlinx.coroutines.flow.stateIn
* [StatusBarHideIconsForBouncerManager]. We should move those pieces of logic to this class instead
* so that it's all in one place and easily testable outside of the fragment.
*/
-interface HomeStatusBarViewModel {
+interface HomeStatusBarViewModel : Activatable {
/** Factory to create the view model for the battery icon */
val batteryViewModelFactory: BatteryViewModel.Factory
@@ -133,7 +138,7 @@ interface HomeStatusBarViewModel {
val operatorNameViewModel: StatusBarOperatorNameViewModel
/** The popup chips that should be shown on the right-hand side of the status bar. */
- val statusBarPopupChips: StateFlow<List<PopupChipModel.Shown>>
+ val popupChips: List<PopupChipModel.Shown>
/**
* True if the current scene can show the home status bar (aka this status bar), and false if
@@ -208,7 +213,7 @@ constructor(
shadeInteractor: ShadeInteractor,
shareToAppChipViewModel: ShareToAppChipViewModel,
ongoingActivityChipsViewModel: OngoingActivityChipsViewModel,
- statusBarPopupChipsViewModel: StatusBarPopupChipsViewModel,
+ statusBarPopupChipsViewModelFactory: StatusBarPopupChipsViewModel.Factory,
animations: SystemStatusEventAnimationInteractor,
statusBarContentInsetsViewModelStore: StatusBarContentInsetsViewModelStore,
@Background bgScope: CoroutineScope,
@@ -219,6 +224,8 @@ constructor(
val tableLogger = tableLoggerFactory.getOrCreate(tableLogBufferName(thisDisplayId), 200)
+ private val statusBarPopupChips by lazy { statusBarPopupChipsViewModelFactory.create() }
+
override val isTransitioningFromLockscreenToOccluded: StateFlow<Boolean> =
keyguardTransitionInteractor
.isInTransition(Edge.create(from = LOCKSCREEN, to = OCCLUDED))
@@ -246,7 +253,8 @@ constructor(
override val ongoingActivityChipsLegacy = ongoingActivityChipsViewModel.chipsLegacy
- override val statusBarPopupChips = statusBarPopupChipsViewModel.shownPopupChips
+ override val popupChips
+ get() = statusBarPopupChips.shownPopupChips
override val isHomeStatusBarAllowedByScene: StateFlow<Boolean> =
combine(
@@ -495,7 +503,13 @@ constructor(
private fun Boolean.toVisibleOrInvisible(): Int = if (this) View.VISIBLE else View.INVISIBLE
override suspend fun onActivated(): Nothing {
- hydrator.activate()
+ coroutineScope {
+ launch { hydrator.activate() }
+ if (StatusBarPopupChips.isEnabled) {
+ launch { statusBarPopupChips.activate() }
+ }
+ awaitCancellation()
+ }
}
/** Inject this to create the display-dependent view model */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java
index 9ad8619faacc..1d1826d532b7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java
@@ -17,7 +17,6 @@
package com.android.systemui.statusbar.policy;
import android.app.AlarmManager;
-import android.app.Flags;
import android.app.NotificationManager;
import android.content.BroadcastReceiver;
import android.content.ContentResolver;
@@ -175,11 +174,7 @@ public class ZenModeControllerImpl implements ZenModeController, Dumpable {
@Override
public void setZen(int zen, Uri conditionId, String reason) {
- if (Flags.modesApi()) {
- mNoMan.setZenMode(zen, conditionId, reason, /* fromUser= */ true);
- } else {
- mNoMan.setZenMode(zen, conditionId, reason);
- }
+ mNoMan.setZenMode(zen, conditionId, reason, /* fromUser= */ true);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
index 28cf78f6777e..9f60fe212567 100644
--- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
@@ -18,8 +18,10 @@ package com.android.systemui.theme;
import static android.util.TypedValue.TYPE_INT_COLOR_ARGB8;
+import static com.android.systemui.Flags.hardwareColorStyles;
import static com.android.systemui.Flags.themeOverlayControllerWakefulnessDeprecation;
import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_ASLEEP;
+import static com.android.systemui.monet.ColorScheme.GOOGLE_BLUE;
import static com.android.systemui.theme.ThemeOverlayApplier.COLOR_SOURCE_HOME;
import static com.android.systemui.theme.ThemeOverlayApplier.COLOR_SOURCE_LOCK;
import static com.android.systemui.theme.ThemeOverlayApplier.COLOR_SOURCE_PRESET;
@@ -73,6 +75,7 @@ import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
+import com.android.systemui.flags.SystemPropertiesHelper;
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
import com.android.systemui.keyguard.shared.model.KeyguardState;
@@ -99,6 +102,7 @@ import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
+import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
@@ -136,9 +140,11 @@ public class ThemeOverlayController implements CoreStartable, Dumpable {
private final DeviceProvisionedController mDeviceProvisionedController;
private final Resources mResources;
// Current wallpaper colors associated to a user.
- private final SparseArray<WallpaperColors> mCurrentColors = new SparseArray<>();
+ @VisibleForTesting
+ protected final SparseArray<WallpaperColors> mCurrentColors = new SparseArray<>();
private final WallpaperManager mWallpaperManager;
private final ActivityManager mActivityManager;
+ protected final SystemPropertiesHelper mSystemPropertiesHelper;
@VisibleForTesting
protected ColorScheme mColorScheme;
// If fabricated overlays were already created for the current theme.
@@ -423,7 +429,9 @@ public class ThemeOverlayController implements CoreStartable, Dumpable {
JavaAdapter javaAdapter,
KeyguardTransitionInteractor keyguardTransitionInteractor,
UiModeManager uiModeManager,
- ActivityManager activityManager) {
+ ActivityManager activityManager,
+ SystemPropertiesHelper systemPropertiesHelper
+ ) {
mContext = context;
mIsMonetEnabled = featureFlags.isEnabled(Flags.MONET);
mIsFidelityEnabled = featureFlags.isEnabled(Flags.COLOR_FIDELITY);
@@ -443,6 +451,7 @@ public class ThemeOverlayController implements CoreStartable, Dumpable {
mKeyguardTransitionInteractor = keyguardTransitionInteractor;
mUiModeManager = uiModeManager;
mActivityManager = activityManager;
+ mSystemPropertiesHelper = systemPropertiesHelper;
dumpManager.registerDumpable(TAG, this);
Flow<Boolean> isFinishedInAsleepStateFlow = mKeyguardTransitionInteractor
@@ -498,29 +507,38 @@ public class ThemeOverlayController implements CoreStartable, Dumpable {
mUserTracker.addCallback(mUserTrackerCallback, mMainExecutor);
mDeviceProvisionedController.addCallback(mDeviceProvisionedListener);
+ WallpaperColors systemColor;
+ if (hardwareColorStyles() && !mDeviceProvisionedController.isCurrentUserSetup()) {
+ Pair<Integer, Color> defaultSettings = getThemeSettingsDefaults();
+ mThemeStyle = defaultSettings.first;
+ Color seedColor = defaultSettings.second;
+
+ // we only use the first color anyway, so we can pass only the single color we have
+ systemColor = new WallpaperColors(
+ /*primaryColor*/ seedColor,
+ /*secondaryColor*/ seedColor,
+ /*tertiaryColor*/ seedColor
+ );
+ } else {
+ systemColor = mWallpaperManager.getWallpaperColors(
+ getDefaultWallpaperColorsSource(mUserTracker.getUserId()));
+ }
+
// Upon boot, make sure we have the most up to date colors
Runnable updateColors = () -> {
- WallpaperColors systemColor = mWallpaperManager.getWallpaperColors(
- getDefaultWallpaperColorsSource(mUserTracker.getUserId()));
- Runnable applyColors = () -> {
- if (DEBUG) Log.d(TAG, "Boot colors: " + systemColor);
- mCurrentColors.put(mUserTracker.getUserId(), systemColor);
- reevaluateSystemTheme(false /* forceReload */);
- };
- if (mDeviceProvisionedController.isCurrentUserSetup()) {
- mMainExecutor.execute(applyColors);
- } else {
- applyColors.run();
- }
+ if (DEBUG) Log.d(TAG, "Boot colors: " + systemColor);
+ mCurrentColors.put(mUserTracker.getUserId(), systemColor);
+ reevaluateSystemTheme(false /* forceReload */);
};
// Whenever we're going directly to setup wizard, we need to process colors synchronously,
// otherwise we'll see some jank when the activity is recreated.
if (!mDeviceProvisionedController.isCurrentUserSetup()) {
- updateColors.run();
+ mMainExecutor.execute(updateColors);
} else {
mBgExecutor.execute(updateColors);
}
+
mWallpaperManager.addOnColorsChangedListener(mOnColorsChangedListener, null,
UserHandle.USER_ALL);
@@ -604,7 +622,7 @@ public class ThemeOverlayController implements CoreStartable, Dumpable {
@VisibleForTesting
protected boolean isPrivateProfile(UserHandle userHandle) {
- Context usercontext = mContext.createContextAsUser(userHandle,0);
+ Context usercontext = mContext.createContextAsUser(userHandle, 0);
return usercontext.getSystemService(UserManager.class).isPrivateProfile();
}
@@ -720,6 +738,7 @@ public class ThemeOverlayController implements CoreStartable, Dumpable {
return true;
}
+ @SuppressWarnings("StringCaseLocaleUsage") // Package name is not localized
private void updateThemeOverlays() {
final int currentUser = mUserTracker.getUserId();
final String overlayPackageJson = mSecureSettings.getStringForUser(
@@ -746,7 +765,7 @@ public class ThemeOverlayController implements CoreStartable, Dumpable {
OverlayIdentifier systemPalette = categoryToPackage.get(OVERLAY_CATEGORY_SYSTEM_PALETTE);
if (mIsMonetEnabled && systemPalette != null && systemPalette.getPackageName() != null) {
try {
- String colorString = systemPalette.getPackageName().toLowerCase();
+ String colorString = systemPalette.getPackageName().toLowerCase();
if (!colorString.startsWith("#")) {
colorString = "#" + colorString;
}
@@ -856,6 +875,75 @@ public class ThemeOverlayController implements CoreStartable, Dumpable {
return style;
}
+ protected Pair<Integer, String> getHardwareColorSetting() {
+ String deviceColorProperty = "ro.boot.hardware.color";
+
+ String[] themeData = mResources.getStringArray(
+ com.android.internal.R.array.theming_defaults);
+
+ // Color can be hex (`#FF0000`) or `home_wallpaper`
+ Map<String, Pair<Integer, String>> themeMap = new HashMap<>();
+
+ // extract all theme settings
+ for (String themeEntry : themeData) {
+ String[] themeComponents = themeEntry.split("\\|");
+ if (themeComponents.length != 3) continue;
+ themeMap.put(themeComponents[0],
+ new Pair<>(Style.valueOf(themeComponents[1]), themeComponents[2]));
+ }
+
+ Pair<Integer, String> fallbackTheme = themeMap.get("*");
+ if (fallbackTheme == null) {
+ Log.d(TAG, "Theming wildcard not found. Fallback to TONAL_SPOT|" + COLOR_SOURCE_HOME);
+ fallbackTheme = new Pair<>(Style.TONAL_SPOT, COLOR_SOURCE_HOME);
+ }
+
+ String deviceColorPropertyValue = mSystemPropertiesHelper.get(deviceColorProperty);
+ Pair<Integer, String> selectedTheme = themeMap.get(deviceColorPropertyValue);
+ if (selectedTheme == null) {
+ Log.d(TAG, "Sysprop `" + deviceColorProperty + "` of value '" + deviceColorPropertyValue
+ + "' not found in theming_defaults: " + Arrays.toString(themeData));
+ selectedTheme = fallbackTheme;
+ }
+
+ return selectedTheme;
+ }
+
+ @VisibleForTesting
+ protected Pair<Integer, Color> getThemeSettingsDefaults() {
+
+ Pair<Integer, String> selectedTheme = getHardwareColorSetting();
+
+ // Last fallback color
+ Color defaultSeedColor = Color.valueOf(GOOGLE_BLUE);
+
+ // defaultColor will come from wallpaper or be parsed from a string
+ boolean isWallpaper = selectedTheme.second.equals(COLOR_SOURCE_HOME);
+
+ if (isWallpaper) {
+ WallpaperColors wallpaperColors = mWallpaperManager.getWallpaperColors(
+ getDefaultWallpaperColorsSource(mUserTracker.getUserId()));
+
+ if (wallpaperColors != null) {
+ defaultSeedColor = wallpaperColors.getPrimaryColor();
+ }
+
+ Log.d(TAG, "Default seed color read from home wallpaper: " + Integer.toHexString(
+ defaultSeedColor.toArgb()));
+ } else {
+ try {
+ defaultSeedColor = Color.valueOf(Color.parseColor(selectedTheme.second));
+ Log.d(TAG, "Default seed color read from resource: " + Integer.toHexString(
+ defaultSeedColor.toArgb()));
+ } catch (IllegalArgumentException e) {
+ Log.e(TAG, "Error parsing color: " + selectedTheme.second, e);
+ // defaultSeedColor remains unchanged in this case
+ }
+ }
+
+ return new Pair<>(selectedTheme.first, defaultSeedColor);
+ }
+
@Override
public void dump(@NonNull PrintWriter pw, @NonNull String[] args) {
pw.println("mSystemColors=" + mCurrentColors);
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/SysUICoroutinesModule.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/SysUICoroutinesModule.kt
index e5c1e7daa25a..79ff38eabc08 100644
--- a/packages/SystemUI/src/com/android/systemui/util/kotlin/SysUICoroutinesModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/SysUICoroutinesModule.kt
@@ -21,6 +21,7 @@ import com.android.systemui.coroutines.newTracingContext
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.NotifInflation
import com.android.systemui.dagger.qualifiers.UiBackground
import com.android.systemui.util.settings.SettingsSingleThreadBackground
import dagger.Module
@@ -123,4 +124,19 @@ class SysUICoroutinesModule {
): CoroutineContext {
return uiBgCoroutineDispatcher
}
+
+ /** Coroutine dispatcher for background notification inflation. */
+ @Provides
+ @NotifInflation
+ @SysUISingleton
+ fun notifInflationCoroutineDispatcher(
+ @NotifInflation notifInflationExecutor: Executor,
+ @Background bgCoroutineDispatcher: CoroutineDispatcher,
+ ): CoroutineDispatcher {
+ if (com.android.systemui.Flags.useNotifInflationThreadForFooter()) {
+ return notifInflationExecutor.asCoroutineDispatcher()
+ } else {
+ return bgCoroutineDispatcher
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/repository/SecureSettingsForUserRepository.kt b/packages/SystemUI/src/com/android/systemui/util/settings/repository/SecureSettingsForUserRepository.kt
new file mode 100644
index 000000000000..4d6eb4d8f391
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/settings/repository/SecureSettingsForUserRepository.kt
@@ -0,0 +1,35 @@
+/*
+ * 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.util.settings.repository
+
+import android.provider.Settings
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.util.settings.SecureSettings
+import javax.inject.Inject
+import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.CoroutineDispatcher
+
+/** Repository observing values of a [Settings.Secure] for the specified user. */
+@SysUISingleton
+class SecureSettingsForUserRepository
+@Inject
+constructor(
+ secureSettings: SecureSettings,
+ @Background backgroundDispatcher: CoroutineDispatcher,
+ @Background backgroundContext: CoroutineContext,
+) : SettingsForUserRepository(secureSettings, backgroundDispatcher, backgroundContext)
diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/repository/SettingsForUserRepository.kt b/packages/SystemUI/src/com/android/systemui/util/settings/repository/SettingsForUserRepository.kt
new file mode 100644
index 000000000000..94b3fd244a92
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/settings/repository/SettingsForUserRepository.kt
@@ -0,0 +1,66 @@
+/*
+ * 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.util.settings.repository
+
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
+import com.android.systemui.util.settings.UserSettingsProxy
+import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.withContext
+
+/**
+ * Repository observing values of a [UserSettingsProxy] for the specified user. This repository
+ * should be used for any system that tracks the desired user internally (e.g. the Quick Settings
+ * tiles system). In other cases, use a [UserAwareSettingsRepository] instead.
+ */
+abstract class SettingsForUserRepository(
+ private val userSettings: UserSettingsProxy,
+ @Background private val backgroundDispatcher: CoroutineDispatcher,
+ @Background private val backgroundContext: CoroutineContext,
+) {
+ fun boolSettingForUser(
+ userId: Int,
+ name: String,
+ defaultValue: Boolean = false,
+ ): Flow<Boolean> =
+ settingObserver(name, userId) { userSettings.getBoolForUser(name, defaultValue, userId) }
+ .distinctUntilChanged()
+ .flowOn(backgroundDispatcher)
+
+ fun <T> settingObserver(name: String, userId: Int, settingsReader: () -> T): Flow<T> {
+ return userSettings
+ .observerFlow(userId, name)
+ .onStart { emit(Unit) }
+ .map { settingsReader.invoke() }
+ }
+
+ suspend fun setBoolForUser(userId: Int, name: String, value: Boolean) {
+ withContext(backgroundContext) { userSettings.putBoolForUser(name, value, userId) }
+ }
+
+ suspend fun getBoolForUser(userId: Int, name: String, defaultValue: Boolean = false): Boolean {
+ return withContext(backgroundContext) {
+ userSettings.getBoolForUser(name, defaultValue, userId)
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/repository/UserAwareSettingsRepository.kt b/packages/SystemUI/src/com/android/systemui/util/settings/repository/UserAwareSettingsRepository.kt
index 73329b467c04..a8068cda685b 100644
--- a/packages/SystemUI/src/com/android/systemui/util/settings/repository/UserAwareSettingsRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/settings/repository/UserAwareSettingsRepository.kt
@@ -33,7 +33,8 @@ import kotlinx.coroutines.withContext
/**
* Repository for observing values of a [UserSettingsProxy], for the currently active user. That
* means that when the user is switched and the new user has a different value, the flow will emit
- * the new value.
+ * the new value. For any system that tracks the desired user internally (e.g. the Quick Settings
+ * tiles system), use a [SettingsForUserRepository] instead.
*/
// TODO: b/377244768 - Make internal when UserAwareSecureSettingsRepository can be made internal.
abstract class UserAwareSettingsRepository(
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index ae3756d390af..6fc95610159a 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -308,9 +308,9 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable,
private int mWindowGravity;
@VisibleForTesting
- final int mVolumeRingerIconDrawableId = R.drawable.ic_speaker_on;
+ final int mVolumeRingerIconDrawableId = R.drawable.ic_legacy_speaker_on;
@VisibleForTesting
- final int mVolumeRingerMuteIconDrawableId = R.drawable.ic_speaker_mute;
+ final int mVolumeRingerMuteIconDrawableId = R.drawable.ic_legacy_speaker_mute;
private int mOriginalGravity;
private final DevicePostureController.Callback mDevicePostureControllerCallback;
@@ -1791,8 +1791,9 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable,
enableRingerViewsH(!isZenMuted);
switch (mState.ringerModeInternal) {
case AudioManager.RINGER_MODE_VIBRATE:
- mRingerIcon.setImageResource(R.drawable.ic_volume_ringer_vibrate);
- mSelectedRingerIcon.setImageResource(R.drawable.ic_volume_ringer_vibrate);
+ mRingerIcon.setImageResource(R.drawable.ic_legacy_volume_ringer_vibrate);
+ mSelectedRingerIcon.setImageResource(
+ R.drawable.ic_legacy_volume_ringer_vibrate);
addAccessibilityDescription(mRingerIcon, RINGER_MODE_VIBRATE,
mContext.getString(R.string.volume_ringer_hint_mute));
mRingerIcon.setTag(Events.ICON_STATE_VIBRATE);
@@ -1990,7 +1991,7 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable,
if (zenMuted) {
iconRes = com.android.internal.R.drawable.ic_qs_dnd;
} else if (isRingVibrate) {
- iconRes = R.drawable.ic_volume_ringer_vibrate;
+ iconRes = R.drawable.ic_legacy_volume_ringer_vibrate;
} else if (isRingSilent) {
iconRes = row.iconMuteRes;
} else if (ss.routedToBluetooth) {
@@ -2009,7 +2010,7 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable,
row.setIcon(iconRes, mContext.getTheme());
row.iconState =
- iconRes == R.drawable.ic_volume_ringer_vibrate ? Events.ICON_STATE_VIBRATE
+ iconRes == R.drawable.ic_legacy_volume_ringer_vibrate ? Events.ICON_STATE_VIBRATE
: (iconRes == R.drawable.ic_volume_media_bt_mute || iconRes == row.iconMuteRes)
? Events.ICON_STATE_MUTE
: (iconRes == R.drawable.ic_volume_media_bt || iconRes == row.iconRes
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/binder/VolumeDialogRingerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/binder/VolumeDialogRingerViewBinder.kt
index ef750574830a..5ef03193820d 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/binder/VolumeDialogRingerViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/binder/VolumeDialogRingerViewBinder.kt
@@ -107,13 +107,6 @@ constructor(
fullRadius = volumeDialogBgFullRadius,
diff = volumeDialogBgFullRadius - volumeDialogBgSmallRadius,
progress,
- isBottom = false,
- )
- volumeDialogBackgroundView.applyCorners(
- fullRadius = volumeDialogBgFullRadius,
- diff = volumeDialogBgFullRadius - volumeDialogBgSmallRadius,
- progress,
- isBottom = true,
)
}
val ringerDrawerTransitionListener = VolumeDialogRingerDrawerTransitionListener {
@@ -132,10 +125,10 @@ constructor(
// Set up view background and visibility
drawerContainer.visibility = View.VISIBLE
+ (volumeDialogBackgroundView.background as GradientDrawable).cornerRadii =
+ bottomCornerRadii
when (uiModel.drawerState) {
is RingerDrawerState.Initial -> {
- (volumeDialogBackgroundView.background as GradientDrawable)
- .cornerRadii = bottomCornerRadii
drawerContainer.animateAndBindDrawerButtons(
viewModel,
uiModel,
@@ -216,8 +209,6 @@ constructor(
drawerContainer.transitionToState(
R.id.volume_dialog_ringer_drawer_open
)
- volumeDialogBackgroundView.background =
- volumeDialogBackgroundView.background.mutate()
ringerBackgroundView.background =
ringerBackgroundView.background.mutate()
}
@@ -423,14 +414,9 @@ constructor(
}
}
- private fun View.applyCorners(fullRadius: Int, diff: Int, progress: Float, isBottom: Boolean) {
+ private fun View.applyCorners(fullRadius: Int, diff: Int, progress: Float) {
val radius = fullRadius - progress * diff
- (background as GradientDrawable).cornerRadii =
- if (isBottom) {
- floatArrayOf(0F, 0F, 0F, 0F, radius, radius, radius, radius)
- } else {
- FloatArray(8) { radius }
- }
+ (background as GradientDrawable).cornerRadius = radius
background.invalidateSelf()
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioSharingStreamSliderViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioSharingStreamSliderViewModel.kt
index e2d2f3f68c6b..3efb2b464a1d 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioSharingStreamSliderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioSharingStreamSliderViewModel.kt
@@ -16,7 +16,6 @@
package com.android.systemui.volume.panel.component.volume.slider.ui.viewmodel
-import android.content.Context
import com.android.internal.logging.UiEventLogger
import com.android.systemui.Flags
import com.android.systemui.common.shared.model.Icon
@@ -24,6 +23,7 @@ import com.android.systemui.haptics.slider.SliderHapticFeedbackFilter
import com.android.systemui.haptics.slider.compose.ui.SliderHapticsViewModel
import com.android.systemui.res.R
import com.android.systemui.volume.domain.interactor.AudioSharingInteractor
+import com.android.systemui.volume.panel.shared.VolumePanelLogger
import com.android.systemui.volume.panel.ui.VolumePanelUiEvent
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
@@ -34,6 +34,7 @@ import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
@@ -43,21 +44,25 @@ class AudioSharingStreamSliderViewModel
@AssistedInject
constructor(
@Assisted private val coroutineScope: CoroutineScope,
- private val context: Context,
private val audioSharingInteractor: AudioSharingInteractor,
private val uiEventLogger: UiEventLogger,
private val hapticsViewModelFactory: SliderHapticsViewModel.Factory,
+ private val volumePanelLogger: VolumePanelLogger,
) : SliderViewModel {
private val volumeChanges = MutableStateFlow<Int?>(null)
override val slider: StateFlow<SliderState> =
- combine(audioSharingInteractor.volume, audioSharingInteractor.secondaryDevice) {
- volume,
- device ->
+ combine(
+ audioSharingInteractor.volume.distinctUntilChanged().onEach {
+ it?.let(volumePanelLogger::onAudioSharingVolumeUpdateReceived)
+ },
+ audioSharingInteractor.secondaryDevice,
+ ) { volume, device ->
val deviceName = device?.name ?: return@combine SliderState.Empty
if (volume == null) {
SliderState.Empty
} else {
+
State(
value = volume.toFloat(),
valueRange =
@@ -74,13 +79,15 @@ constructor(
init {
volumeChanges
.filterNotNull()
- .onEach { audioSharingInteractor.setStreamVolume(it) }
+ .onEach {
+ volumePanelLogger.onSetAudioSharingVolumeRequested(it)
+ audioSharingInteractor.setStreamVolume(it)
+ }
.launchIn(coroutineScope)
}
override fun onValueChanged(state: SliderState, newValue: Float) {
- val audioViewModel = state as? State
- audioViewModel ?: return
+ if (state !is State) return
volumeChanges.tryEmit(newValue.roundToInt())
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/CastVolumeSliderViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/CastVolumeSliderViewModel.kt
index 533276413ade..d74a433ad86c 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/CastVolumeSliderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/CastVolumeSliderViewModel.kt
@@ -26,6 +26,7 @@ import com.android.systemui.haptics.slider.compose.ui.SliderHapticsViewModel
import com.android.systemui.res.R
import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaDeviceSessionInteractor
import com.android.systemui.volume.panel.component.mediaoutput.shared.model.MediaDeviceSession
+import com.android.systemui.volume.panel.shared.VolumePanelLogger
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
@@ -44,17 +45,23 @@ constructor(
private val context: Context,
private val mediaDeviceSessionInteractor: MediaDeviceSessionInteractor,
private val hapticsViewModelFactory: SliderHapticsViewModel.Factory,
+ private val volumePanelLogger: VolumePanelLogger,
) : SliderViewModel {
override val slider: StateFlow<SliderState> =
mediaDeviceSessionInteractor
.playbackInfo(session)
- .mapNotNull { it?.getCurrentState() }
+ .mapNotNull {
+ volumePanelLogger.onVolumeUpdateReceived(session.sessionToken, it.currentVolume)
+ it.getCurrentState()
+ }
.stateIn(coroutineScope, SharingStarted.Eagerly, SliderState.Empty)
override fun onValueChanged(state: SliderState, newValue: Float) {
coroutineScope.launch {
- mediaDeviceSessionInteractor.setSessionVolume(session, newValue.roundToInt())
+ val volume = newValue.roundToInt()
+ volumePanelLogger.onSetVolumeRequested(session.sessionToken, volume)
+ mediaDeviceSessionInteractor.setSessionVolume(session, volume)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/shared/VolumePanelLogger.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/shared/VolumePanelLogger.kt
index 276326cbf430..930199a03a56 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/shared/VolumePanelLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/shared/VolumePanelLogger.kt
@@ -16,6 +16,8 @@
package com.android.systemui.volume.panel.shared
+import android.media.session.MediaSession
+import android.media.session.MediaSession.Token
import com.android.settingslib.volume.shared.model.AudioStream
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.core.LogLevel
@@ -42,7 +44,7 @@ class VolumePanelLogger @Inject constructor(@VolumeLog private val logBuffer: Lo
str1 = key
bool1 = isAvailable
},
- { "$str1 isAvailable=$bool1" }
+ { "$str1 isAvailable=$bool1" },
)
}
@@ -51,7 +53,7 @@ class VolumePanelLogger @Inject constructor(@VolumeLog private val logBuffer: Lo
TAG,
LogLevel.DEBUG,
{ bool1 = globalState.isVisible },
- { "Global state changed: isVisible=$bool1" }
+ { "Global state changed: isVisible=$bool1" },
)
}
@@ -63,7 +65,7 @@ class VolumePanelLogger @Inject constructor(@VolumeLog private val logBuffer: Lo
str1 = audioStream.toString()
int1 = volume
},
- { "Set volume: stream=$str1 volume=$int1" }
+ { "Set volume: stream=$str1 volume=$int1" },
)
}
@@ -75,7 +77,49 @@ class VolumePanelLogger @Inject constructor(@VolumeLog private val logBuffer: Lo
str1 = audioStream.toString()
int1 = volume
},
- { "Volume update received: stream=$str1 volume=$int1" }
+ { "Volume update received: stream=$str1 volume=$int1" },
+ )
+ }
+
+ fun onSetVolumeRequested(sessionToken: MediaSession.Token, volume: Int) {
+ logBuffer.log(
+ TAG,
+ LogLevel.DEBUG,
+ {
+ str1 = sessionToken.toString()
+ int1 = volume
+ },
+ { "Set volume: token=$str1 volume=$int1" },
+ )
+ }
+
+ fun onVolumeUpdateReceived(sessionToken: Token, volume: Int) {
+ logBuffer.log(
+ TAG,
+ LogLevel.DEBUG,
+ {
+ str1 = sessionToken.toString()
+ int1 = volume
+ },
+ { "Volume update received: token=$str1 volume=$int1" },
+ )
+ }
+
+ fun onSetAudioSharingVolumeRequested(volume: Int) {
+ logBuffer.log(
+ TAG,
+ LogLevel.DEBUG,
+ { int1 = volume },
+ { "Set volume: audio-sharing volume=$int1" },
+ )
+ }
+
+ fun onAudioSharingVolumeUpdateReceived(volume: Int) {
+ logBuffer.log(
+ TAG,
+ LogLevel.DEBUG,
+ { int1 = volume },
+ { "Volume update received: audio-sharing volume=$int1" },
)
}
}
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 ec74f4f47bc9..300a7e070b6c 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
@@ -17,6 +17,8 @@
package com.android.systemui.wallpapers.data.repository
import android.app.WallpaperInfo
+import android.graphics.PointF
+import android.graphics.RectF
import android.view.View
import com.android.systemui.dagger.SysUISingleton
import javax.inject.Inject
@@ -37,4 +39,8 @@ class NoopWallpaperRepository @Inject constructor() : WallpaperRepository {
override val wallpaperSupportsAmbientMode = flowOf(false)
override var rootView: View? = null
override val shouldSendFocalArea: StateFlow<Boolean> = MutableStateFlow(false).asStateFlow()
+
+ override fun sendLockScreenLayoutChangeCommand(wallpaperFocalAreaBounds: RectF) {}
+
+ override fun sendTapCommand(tapPosition: PointF) {}
}
diff --git a/packages/SystemUI/src/com/android/systemui/wallpapers/data/repository/WallpaperFocalAreaRepository.kt b/packages/SystemUI/src/com/android/systemui/wallpapers/data/repository/WallpaperFocalAreaRepository.kt
index 2c3491b06a90..974468c16578 100644
--- a/packages/SystemUI/src/com/android/systemui/wallpapers/data/repository/WallpaperFocalAreaRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/wallpapers/data/repository/WallpaperFocalAreaRepository.kt
@@ -33,7 +33,8 @@ interface WallpaperFocalAreaRepository {
val wallpaperFocalAreaBounds: StateFlow<RectF>
- val wallpaperFocalAreaTapPosition: StateFlow<PointF>
+ /** It will be true when wallpaper requires focal area info. */
+ val hasFocalArea: StateFlow<Boolean>
/** top of notifications without bcsmartspace in small clock settings */
val notificationDefaultTop: StateFlow<Float>
@@ -51,7 +52,9 @@ interface WallpaperFocalAreaRepository {
}
@SysUISingleton
-class WallpaperFocalAreaRepositoryImpl @Inject constructor() : WallpaperFocalAreaRepository {
+class WallpaperFocalAreaRepositoryImpl
+@Inject
+constructor(val wallpaperRepository: WallpaperRepository) : WallpaperFocalAreaRepository {
private val _shortcutAbsoluteTop = MutableStateFlow(0F)
override val shortcutAbsoluteTop = _shortcutAbsoluteTop.asStateFlow()
@@ -63,13 +66,11 @@ class WallpaperFocalAreaRepositoryImpl @Inject constructor() : WallpaperFocalAre
override val wallpaperFocalAreaBounds: StateFlow<RectF> =
_wallpaperFocalAreaBounds.asStateFlow()
- private val _wallpaperFocalAreaTapPosition = MutableStateFlow(PointF(0F, 0F))
- override val wallpaperFocalAreaTapPosition: StateFlow<PointF> =
- _wallpaperFocalAreaTapPosition.asStateFlow()
-
private val _notificationDefaultTop = MutableStateFlow(0F)
override val notificationDefaultTop: StateFlow<Float> = _notificationDefaultTop.asStateFlow()
+ override val hasFocalArea = wallpaperRepository.shouldSendFocalArea
+
override fun setShortcutAbsoluteTop(top: Float) {
_shortcutAbsoluteTop.value = top
}
@@ -84,9 +85,10 @@ class WallpaperFocalAreaRepositoryImpl @Inject constructor() : WallpaperFocalAre
override fun setWallpaperFocalAreaBounds(bounds: RectF) {
_wallpaperFocalAreaBounds.value = bounds
+ wallpaperRepository.sendLockScreenLayoutChangeCommand(bounds)
}
- override fun setTapPosition(point: PointF) {
- _wallpaperFocalAreaTapPosition.value = point
+ override fun setTapPosition(tapPosition: PointF) {
+ wallpaperRepository.sendTapCommand(tapPosition)
}
}
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 a55f76b333d9..b07342c4c76d 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
@@ -21,22 +21,18 @@ import android.app.WallpaperManager
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
+import android.graphics.PointF
+import android.graphics.RectF
import android.os.Bundle
import android.os.UserHandle
import android.provider.Settings
+import android.util.Log
import android.view.View
-import androidx.annotation.VisibleForTesting
-import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.internal.R
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
-import com.android.systemui.keyguard.shared.model.Edge
-import com.android.systemui.keyguard.shared.model.KeyguardState
-import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.res.R as SysUIR
-import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.shared.Flags.ambientAod
import com.android.systemui.shared.Flags.extendedWallpaperEffects
import com.android.systemui.user.data.model.SelectedUserModel
@@ -48,7 +44,6 @@ import com.android.systemui.utils.coroutines.flow.mapLatestConflated
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
@@ -76,6 +71,10 @@ interface WallpaperRepository {
/** some wallpapers require bounds to be sent from keyguard */
val shouldSendFocalArea: StateFlow<Boolean>
+
+ fun sendLockScreenLayoutChangeCommand(wallpaperFocalAreaBounds: RectF)
+
+ fun sendTapCommand(tapPosition: PointF)
}
@SysUISingleton
@@ -86,10 +85,8 @@ constructor(
@Background private val bgDispatcher: CoroutineDispatcher,
broadcastDispatcher: BroadcastDispatcher,
userRepository: UserRepository,
- wallpaperFocalAreaRepository: WallpaperFocalAreaRepository,
private val wallpaperManager: WallpaperManager,
private val context: Context,
- keyguardTransitionInteractor: KeyguardTransitionInteractor,
private val secureSettings: SecureSettings,
) : WallpaperRepository {
private val wallpaperChanged: Flow<Unit> =
@@ -109,9 +106,6 @@ constructor(
// Only update the wallpaper status once the user selection has finished.
.filter { it.selectionStatus == SelectionStatus.SELECTION_COMPLETE }
- @VisibleForTesting var sendLockscreenLayoutJob: Job? = null
- @VisibleForTesting var sendTapInShapeEffectsJob: Job? = null
-
override val wallpaperInfo: StateFlow<WallpaperInfo?> =
if (!wallpaperManager.isWallpaperSupported) {
MutableStateFlow(null).asStateFlow()
@@ -143,77 +137,45 @@ constructor(
override var rootView: View? = null
+ override fun sendLockScreenLayoutChangeCommand(wallpaperFocalAreaBounds: RectF) {
+ if (DEBUG) {
+ Log.d(TAG, "sendLockScreenLayoutChangeCommand $wallpaperFocalAreaBounds")
+ }
+ wallpaperManager.sendWallpaperCommand(
+ /* windowToken = */ rootView?.windowToken,
+ /* action = */ WallpaperManager.COMMAND_LOCKSCREEN_LAYOUT_CHANGED,
+ /* x = */ 0,
+ /* y = */ 0,
+ /* z = */ 0,
+ /* extras = */ Bundle().apply {
+ putFloat("wallpaperFocalAreaLeft", wallpaperFocalAreaBounds.left)
+ putFloat("wallpaperFocalAreaRight", wallpaperFocalAreaBounds.right)
+ putFloat("wallpaperFocalAreaTop", wallpaperFocalAreaBounds.top)
+ putFloat("wallpaperFocalAreaBottom", wallpaperFocalAreaBounds.bottom)
+ },
+ )
+ }
+
+ override fun sendTapCommand(tapPosition: PointF) {
+ if (DEBUG) {
+ Log.d(TAG, "sendTapCommand $tapPosition")
+ }
+
+ wallpaperManager.sendWallpaperCommand(
+ /* windowToken = */ rootView?.windowToken,
+ /* action = */ WallpaperManager.COMMAND_LOCKSCREEN_TAP,
+ /* x = */ tapPosition.x.toInt(),
+ /* y = */ tapPosition.y.toInt(),
+ /* z = */ 0,
+ /* extras = */ Bundle(),
+ )
+ }
+
override val shouldSendFocalArea =
wallpaperInfo
.map {
val focalAreaTarget = context.resources.getString(SysUIR.string.focal_area_target)
val shouldSendNotificationLayout = it?.component?.className == focalAreaTarget
- if (shouldSendNotificationLayout) {
- sendLockscreenLayoutJob =
- scope.launch {
- combine(
- wallpaperFocalAreaRepository.wallpaperFocalAreaBounds,
- keyguardTransitionInteractor
- .transition(
- edge = Edge.create(to = Scenes.Lockscreen),
- edgeWithoutSceneContainer =
- Edge.create(to = KeyguardState.LOCKSCREEN),
- )
- .filter { transitionStep ->
- transitionStep.transitionState ==
- TransitionState.STARTED
- },
- ::Pair,
- )
- .map { (bounds, _) -> bounds }
- .collect { wallpaperFocalAreaBounds ->
- wallpaperManager.sendWallpaperCommand(
- /* windowToken = */ rootView?.windowToken,
- /* action = */ WallpaperManager
- .COMMAND_LOCKSCREEN_LAYOUT_CHANGED,
- /* x = */ 0,
- /* y = */ 0,
- /* z = */ 0,
- /* extras = */ Bundle().apply {
- putFloat(
- "wallpaperFocalAreaLeft",
- wallpaperFocalAreaBounds.left,
- )
- putFloat(
- "wallpaperFocalAreaRight",
- wallpaperFocalAreaBounds.right,
- )
- putFloat(
- "wallpaperFocalAreaTop",
- wallpaperFocalAreaBounds.top,
- )
- putFloat(
- "wallpaperFocalAreaBottom",
- wallpaperFocalAreaBounds.bottom,
- )
- },
- )
- }
- }
-
- sendTapInShapeEffectsJob =
- scope.launch {
- wallpaperFocalAreaRepository.wallpaperFocalAreaTapPosition.collect {
- wallpaperFocalAreaTapPosition ->
- wallpaperManager.sendWallpaperCommand(
- /* windowToken = */ rootView?.windowToken,
- /* action = */ WallpaperManager.COMMAND_LOCKSCREEN_TAP,
- /* x = */ wallpaperFocalAreaTapPosition.x.toInt(),
- /* y = */ wallpaperFocalAreaTapPosition.y.toInt(),
- /* z = */ 0,
- /* extras = */ null,
- )
- }
- }
- } else {
- sendLockscreenLayoutJob?.cancel()
- sendTapInShapeEffectsJob?.cancel()
- }
shouldSendNotificationLayout
}
.stateIn(
@@ -227,4 +189,9 @@ constructor(
wallpaperManager.getWallpaperInfoForUser(selectedUser.userInfo.id)
}
}
+
+ companion object {
+ private val TAG = WallpaperRepositoryImpl::class.simpleName
+ private val DEBUG = true
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/wallpapers/domain/interactor/WallpaperFocalAreaInteractor.kt b/packages/SystemUI/src/com/android/systemui/wallpapers/domain/interactor/WallpaperFocalAreaInteractor.kt
index 187d6c7801c0..09c6cdf0ce22 100644
--- a/packages/SystemUI/src/com/android/systemui/wallpapers/domain/interactor/WallpaperFocalAreaInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/wallpapers/domain/interactor/WallpaperFocalAreaInteractor.kt
@@ -20,60 +20,38 @@ import android.content.Context
import android.content.res.Resources
import android.graphics.PointF
import android.graphics.RectF
+import android.util.Log
import android.util.TypedValue
-import androidx.annotation.VisibleForTesting
import com.android.app.animation.MathUtils
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
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.WallpaperFocalAreaRepository
-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.combine
import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.filter
@SysUISingleton
class WallpaperFocalAreaInteractor
@Inject
constructor(
- @Application private val applicationScope: CoroutineScope,
private val context: Context,
private val wallpaperFocalAreaRepository: WallpaperFocalAreaRepository,
shadeRepository: ShadeRepository,
- activeNotificationsInteractor: ActiveNotificationsInteractor,
- val wallpaperRepository: WallpaperRepository,
) {
- // When there's notifications in splitshade, the focal area should be left aligned
- @VisibleForTesting
- val notificationInShadeWideLayout: Flow<Boolean> =
- combine(
- shadeRepository.isShadeLayoutWide,
- activeNotificationsInteractor.areAnyNotificationsPresent,
- ) { isShadeLayoutWide, areAnyNotificationsPresent: Boolean ->
- when {
- !isShadeLayoutWide -> false
- !areAnyNotificationsPresent -> false
- else -> true
- }
- }
-
- val hasFocalArea = wallpaperRepository.shouldSendFocalArea
+ val hasFocalArea = wallpaperFocalAreaRepository.hasFocalArea
val wallpaperFocalAreaBounds: Flow<RectF> =
combine(
shadeRepository.isShadeLayoutWide,
- notificationInShadeWideLayout,
wallpaperFocalAreaRepository.notificationStackAbsoluteBottom,
wallpaperFocalAreaRepository.shortcutAbsoluteTop,
wallpaperFocalAreaRepository.notificationDefaultTop,
) {
isShadeLayoutWide,
- notificationInShadeWideLayout,
notificationStackAbsoluteBottom,
shortcutAbsoluteTop,
notificationDefaultTop ->
@@ -97,28 +75,21 @@ constructor(
screenBounds.centerY() + screenBounds.height() / 2F / wallpaperZoomedInScale,
)
+ val focalAreaMaxWidthDp = getFocalAreaMaxWidthDp(context)
val maxFocalAreaWidth =
TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP,
- FOCAL_AREA_MAX_WIDTH_DP.toFloat(),
+ focalAreaMaxWidthDp.toFloat(),
context.resources.displayMetrics,
)
val (left, right) =
- // tablet landscape
- if (context.resources.getBoolean(R.bool.center_align_focal_area_shape)) {
+ // Tablet & unfold foldable landscape
+ if (isShadeLayoutWide) {
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(
@@ -147,8 +118,10 @@ constructor(
wallpaperZoomedInScale
}
val bottom = scaledBounds.bottom - scaledBottomMargin
- RectF(left, top, right, bottom)
+ RectF(left, top, right, bottom).also { Log.d(TAG, "Focal area changes to $it") }
}
+ // Make sure a valid rec
+ .filter { it.width() >= 0 && it.height() >= 0 }
.distinctUntilChanged()
fun setFocalAreaBounds(bounds: RectF) {
@@ -187,8 +160,17 @@ constructor(
return if (scale == 0f) 1f else scale
}
- // A max width for focal area shape effects bounds, to avoid
- // it becoming too large in large screen portrait mode
- const val FOCAL_AREA_MAX_WIDTH_DP = 500
+ // A max width for focal area shape effects bounds, to avoid it becoming too large,
+ // especially in portrait mode
+ const val FOCAL_AREA_MAX_WIDTH_DP_TABLET = 500
+ const val FOCAL_AREA_MAX_WIDTH_DP_FOLDABLE = 400
+
+ fun getFocalAreaMaxWidthDp(context: Context): Int {
+ return if (context.resources.getBoolean(R.bool.center_align_focal_area_shape))
+ FOCAL_AREA_MAX_WIDTH_DP_TABLET
+ else FOCAL_AREA_MAX_WIDTH_DP_FOLDABLE
+ }
+
+ private val TAG = WallpaperFocalAreaInteractor::class.simpleName
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/wallpapers/ui/viewmodel/WallpaperFocalAreaViewModel.kt b/packages/SystemUI/src/com/android/systemui/wallpapers/ui/viewmodel/WallpaperFocalAreaViewModel.kt
index 70a97d473c49..4cd49d03ad36 100644
--- a/packages/SystemUI/src/com/android/systemui/wallpapers/ui/viewmodel/WallpaperFocalAreaViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/wallpapers/ui/viewmodel/WallpaperFocalAreaViewModel.kt
@@ -17,15 +17,41 @@
package com.android.systemui.wallpapers.ui.viewmodel
import android.graphics.RectF
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.Edge
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.wallpapers.domain.interactor.WallpaperFocalAreaInteractor
import javax.inject.Inject
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.map
class WallpaperFocalAreaViewModel
@Inject
-constructor(private val wallpaperFocalAreaInteractor: WallpaperFocalAreaInteractor) {
+constructor(
+ private val wallpaperFocalAreaInteractor: WallpaperFocalAreaInteractor,
+ val keyguardTransitionInteractor: KeyguardTransitionInteractor,
+) {
val hasFocalArea = wallpaperFocalAreaInteractor.hasFocalArea
- val wallpaperFocalAreaBounds = wallpaperFocalAreaInteractor.wallpaperFocalAreaBounds
+ val wallpaperFocalAreaBounds =
+ combine(
+ wallpaperFocalAreaInteractor.wallpaperFocalAreaBounds,
+ keyguardTransitionInteractor
+ .transition(
+ edge = Edge.create(to = Scenes.Lockscreen),
+ edgeWithoutSceneContainer = Edge.create(to = KeyguardState.LOCKSCREEN),
+ )
+ .filter { transitionStep ->
+ // Should not filter by TransitionState.STARTED, it may race with
+ // wakingup command, causing layout change command not be received.
+ transitionStep.transitionState == TransitionState.FINISHED
+ },
+ ::Pair,
+ )
+ .map { (bounds, _) -> bounds }
fun setFocalAreaBounds(bounds: RectF) {
wallpaperFocalAreaInteractor.setFocalAreaBounds(bounds)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/OnTeardownRuleTest.kt b/packages/SystemUI/tests/src/com/android/systemui/OnTeardownRuleTest.kt
index 8635bb0e8ab2..8635bb0e8ab2 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/OnTeardownRuleTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/OnTeardownRuleTest.kt
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/IMagnificationConnectionTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/IMagnificationConnectionTest.java
index 6d75c4ca3a38..6d75c4ca3a38 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/IMagnificationConnectionTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/IMagnificationConnectionTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/TextAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/TextAnimatorTest.kt
index dcf38800bb01..14a81b3f8bfb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/TextAnimatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/TextAnimatorTest.kt
@@ -30,7 +30,6 @@ import kotlin.math.ceil
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
-import org.mockito.Mockito.eq
import org.mockito.Mockito.inOrder
import org.mockito.Mockito.mock
import org.mockito.Mockito.never
@@ -60,10 +59,11 @@ class TextAnimatorTest : SysuiTestCase() {
val textAnimator =
TextAnimator(layout, TypefaceVariantCacheImpl(typeface, 20)).apply {
this.textInterpolator = textInterpolator
+ this.createAnimator = { valueAnimator }
this.animator = valueAnimator
}
- textAnimator.setTextStyle(weight = 400, animate = true)
+ textAnimator.setTextStyle(TextAnimator.Style("'wght' 400"), TextAnimator.Animation())
// If animation is requested, the base state should be rebased and the target state should
// be updated.
@@ -90,10 +90,11 @@ class TextAnimatorTest : SysuiTestCase() {
val textAnimator =
TextAnimator(layout, TypefaceVariantCacheImpl(typeface, 20)).apply {
this.textInterpolator = textInterpolator
+ this.createAnimator = { valueAnimator }
this.animator = valueAnimator
}
- textAnimator.setTextStyle(weight = 400, animate = false)
+ textAnimator.setTextStyle(TextAnimator.Style("'wght' 400"))
// If animation is not requested, the progress should be 1 which is end of animation and the
// base state is rebased to target state by calling rebase.
@@ -118,23 +119,24 @@ class TextAnimatorTest : SysuiTestCase() {
val textAnimator =
TextAnimator(layout, TypefaceVariantCacheImpl(typeface, 20)).apply {
this.textInterpolator = textInterpolator
+ this.createAnimator = { valueAnimator }
this.animator = valueAnimator
}
textAnimator.setTextStyle(
- weight = 400,
- animate = true,
- onAnimationEnd = animationEndCallback,
+ TextAnimator.Style("'wght' 400"),
+ TextAnimator.Animation(animate = true, onAnimationEnd = animationEndCallback),
)
// Verify animationEnd callback has been added.
val captor = ArgumentCaptor.forClass(AnimatorListenerAdapter::class.java)
- verify(valueAnimator).addListener(captor.capture())
- captor.value.onAnimationEnd(valueAnimator)
+ verify(valueAnimator, times(2)).addListener(captor.capture())
+ for (callback in captor.allValues) {
+ callback.onAnimationEnd(valueAnimator)
+ }
// Verify animationEnd callback has been invoked and removed.
verify(animationEndCallback).run()
- verify(valueAnimator).removeListener(eq(captor.value))
}
@Test
@@ -148,18 +150,20 @@ class TextAnimatorTest : SysuiTestCase() {
val textAnimator =
TextAnimator(layout, TypefaceVariantCacheImpl(typeface, 20)).apply {
this.textInterpolator = textInterpolator
+ this.createAnimator = { valueAnimator }
this.animator = valueAnimator
}
- textAnimator.setTextStyle(weight = 400, animate = true)
+ val animation = TextAnimator.Animation(animate = true)
+ textAnimator.setTextStyle(TextAnimator.Style("'wght' 400"), animation)
val prevTypeface = paint.typeface
- textAnimator.setTextStyle(weight = 700, animate = true)
+ textAnimator.setTextStyle(TextAnimator.Style("'wght' 700"), animation)
assertThat(paint.typeface).isNotSameInstanceAs(prevTypeface)
- textAnimator.setTextStyle(weight = 400, animate = true)
+ textAnimator.setTextStyle(TextAnimator.Style("'wght' 400"), animation)
assertThat(paint.typeface).isSameInstanceAs(prevTypeface)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
index b6c63479990e..b6c63479990e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingDialogDelegateTest.kt
index b4200b6850c8..b4200b6850c8 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingDialogDelegateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingDialogDelegateTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothDetailsContentViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothDetailsContentViewModelTest.kt
index bfc5361b6129..f04fc86d7230 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothDetailsContentViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothDetailsContentViewModelTest.kt
@@ -43,6 +43,7 @@ import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.nullable
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.time.FakeSystemClock
+import com.android.systemui.volume.domain.interactor.audioModeInteractor
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.MutableSharedFlow
@@ -146,6 +147,7 @@ class BluetoothDetailsContentViewModelTest : SysuiTestCase() {
)
),
kosmos.audioSharingInteractor,
+ kosmos.audioModeInteractor,
kosmos.audioSharingButtonViewModelFactory,
bluetoothDeviceMetadataInteractor,
mDialogTransitionAnimator,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactoryTest.kt
index 0aa5199cb20e..7c8822bc11ec 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactoryTest.kt
@@ -18,7 +18,6 @@ package com.android.systemui.bluetooth.qsdialog
import android.bluetooth.BluetoothDevice
import android.graphics.drawable.Drawable
-import android.media.AudioManager
import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
import android.testing.TestableLooper
@@ -61,8 +60,6 @@ class DeviceItemFactoryTest : SysuiTestCase() {
private val connectedDeviceItemFactory = ConnectedDeviceItemFactory()
private val savedDeviceItemFactory = SavedDeviceItemFactory()
- private val audioManager = context.getSystemService(AudioManager::class.java)!!
-
@Before
fun setup() {
mockitoSession =
@@ -132,7 +129,12 @@ class DeviceItemFactoryTest : SysuiTestCase() {
fun testAvailableAudioSharingMediaDeviceItemFactory_isFilterMatched_flagOff_returnsFalse() {
assertThat(
AvailableAudioSharingMediaDeviceItemFactory(localBluetoothManager)
- .isFilterMatched(context, cachedDevice, audioManager, false)
+ .isFilterMatched(
+ context,
+ cachedDevice,
+ isOngoingCall = false,
+ audioSharingAvailable = false,
+ )
)
.isFalse()
}
@@ -143,7 +145,12 @@ class DeviceItemFactoryTest : SysuiTestCase() {
assertThat(
AvailableAudioSharingMediaDeviceItemFactory(localBluetoothManager)
- .isFilterMatched(context, cachedDevice, audioManager, true)
+ .isFilterMatched(
+ context,
+ cachedDevice,
+ isOngoingCall = false,
+ audioSharingAvailable = true,
+ )
)
.isFalse()
}
@@ -157,7 +164,26 @@ class DeviceItemFactoryTest : SysuiTestCase() {
assertThat(
AvailableAudioSharingMediaDeviceItemFactory(localBluetoothManager)
- .isFilterMatched(context, cachedDevice, audioManager, true)
+ .isFilterMatched(
+ context,
+ cachedDevice,
+ isOngoingCall = false,
+ audioSharingAvailable = true,
+ )
+ )
+ .isFalse()
+ }
+
+ @Test
+ fun testAvailableAudioSharingMediaDeviceItemFactory_isFilterMatched_inCall_false() {
+ assertThat(
+ AvailableAudioSharingMediaDeviceItemFactory(localBluetoothManager)
+ .isFilterMatched(
+ context,
+ cachedDevice,
+ isOngoingCall = true,
+ audioSharingAvailable = true,
+ )
)
.isFalse()
}
@@ -171,7 +197,12 @@ class DeviceItemFactoryTest : SysuiTestCase() {
assertThat(
AvailableAudioSharingMediaDeviceItemFactory(localBluetoothManager)
- .isFilterMatched(context, cachedDevice, audioManager, true)
+ .isFilterMatched(
+ context,
+ cachedDevice,
+ isOngoingCall = false,
+ audioSharingAvailable = true,
+ )
)
.isTrue()
}
@@ -182,7 +213,9 @@ class DeviceItemFactoryTest : SysuiTestCase() {
`when`(cachedDevice.bondState).thenReturn(BluetoothDevice.BOND_BONDED)
`when`(cachedDevice.isConnected).thenReturn(false)
- assertThat(savedDeviceItemFactory.isFilterMatched(context, cachedDevice, audioManager))
+ assertThat(
+ savedDeviceItemFactory.isFilterMatched(context, cachedDevice, isOngoingCall = false)
+ )
.isTrue()
}
@@ -192,7 +225,9 @@ class DeviceItemFactoryTest : SysuiTestCase() {
`when`(cachedDevice.bondState).thenReturn(BluetoothDevice.BOND_BONDED)
`when`(cachedDevice.isConnected).thenReturn(true)
- assertThat(savedDeviceItemFactory.isFilterMatched(context, cachedDevice, audioManager))
+ assertThat(
+ savedDeviceItemFactory.isFilterMatched(context, cachedDevice, isOngoingCall = false)
+ )
.isFalse()
}
@@ -201,7 +236,9 @@ class DeviceItemFactoryTest : SysuiTestCase() {
fun testSavedFactory_isFilterMatched_notBonded_returnsFalse() {
`when`(cachedDevice.bondState).thenReturn(BluetoothDevice.BOND_NONE)
- assertThat(savedDeviceItemFactory.isFilterMatched(context, cachedDevice, audioManager))
+ assertThat(
+ savedDeviceItemFactory.isFilterMatched(context, cachedDevice, isOngoingCall = false)
+ )
.isFalse()
}
@@ -211,7 +248,9 @@ class DeviceItemFactoryTest : SysuiTestCase() {
`when`(cachedDevice.device).thenReturn(bluetoothDevice)
`when`(BluetoothUtils.isExclusivelyManagedBluetoothDevice(any(), any())).thenReturn(true)
- assertThat(savedDeviceItemFactory.isFilterMatched(context, cachedDevice, audioManager))
+ assertThat(
+ savedDeviceItemFactory.isFilterMatched(context, cachedDevice, isOngoingCall = false)
+ )
.isFalse()
}
@@ -223,7 +262,9 @@ class DeviceItemFactoryTest : SysuiTestCase() {
`when`(cachedDevice.bondState).thenReturn(BluetoothDevice.BOND_BONDED)
`when`(cachedDevice.isConnected).thenReturn(false)
- assertThat(savedDeviceItemFactory.isFilterMatched(context, cachedDevice, audioManager))
+ assertThat(
+ savedDeviceItemFactory.isFilterMatched(context, cachedDevice, isOngoingCall = false)
+ )
.isTrue()
}
@@ -235,7 +276,9 @@ class DeviceItemFactoryTest : SysuiTestCase() {
`when`(cachedDevice.bondState).thenReturn(BluetoothDevice.BOND_BONDED)
`when`(cachedDevice.isConnected).thenReturn(true)
- assertThat(savedDeviceItemFactory.isFilterMatched(context, cachedDevice, audioManager))
+ assertThat(
+ savedDeviceItemFactory.isFilterMatched(context, cachedDevice, isOngoingCall = false)
+ )
.isFalse()
}
@@ -244,14 +287,26 @@ class DeviceItemFactoryTest : SysuiTestCase() {
fun testConnectedFactory_isFilterMatched_bondedAndConnected_returnsTrue() {
`when`(BluetoothUtils.isConnectedBluetoothDevice(any(), any())).thenReturn(true)
- assertThat(connectedDeviceItemFactory.isFilterMatched(context, cachedDevice, audioManager))
+ assertThat(
+ connectedDeviceItemFactory.isFilterMatched(
+ context,
+ cachedDevice,
+ isOngoingCall = false,
+ )
+ )
.isTrue()
}
@Test
@DisableFlags(Flags.FLAG_ENABLE_HIDE_EXCLUSIVELY_MANAGED_BLUETOOTH_DEVICE)
fun testConnectedFactory_isFilterMatched_notConnected_returnsFalse() {
- assertThat(connectedDeviceItemFactory.isFilterMatched(context, cachedDevice, audioManager))
+ assertThat(
+ connectedDeviceItemFactory.isFilterMatched(
+ context,
+ cachedDevice,
+ isOngoingCall = false,
+ )
+ )
.isFalse()
}
@@ -261,7 +316,13 @@ class DeviceItemFactoryTest : SysuiTestCase() {
`when`(cachedDevice.device).thenReturn(bluetoothDevice)
`when`(BluetoothUtils.isExclusivelyManagedBluetoothDevice(any(), any())).thenReturn(true)
- assertThat(connectedDeviceItemFactory.isFilterMatched(context, cachedDevice, audioManager))
+ assertThat(
+ connectedDeviceItemFactory.isFilterMatched(
+ context,
+ cachedDevice,
+ isOngoingCall = false,
+ )
+ )
.isFalse()
}
@@ -272,7 +333,13 @@ class DeviceItemFactoryTest : SysuiTestCase() {
`when`(BluetoothUtils.isExclusivelyManagedBluetoothDevice(any(), any())).thenReturn(false)
`when`(BluetoothUtils.isConnectedBluetoothDevice(any(), any())).thenReturn(true)
- assertThat(connectedDeviceItemFactory.isFilterMatched(context, cachedDevice, audioManager))
+ assertThat(
+ connectedDeviceItemFactory.isFilterMatched(
+ context,
+ cachedDevice,
+ isOngoingCall = false,
+ )
+ )
.isTrue()
}
@@ -283,7 +350,13 @@ class DeviceItemFactoryTest : SysuiTestCase() {
`when`(BluetoothUtils.isExclusivelyManagedBluetoothDevice(any(), any())).thenReturn(false)
`when`(BluetoothUtils.isConnectedBluetoothDevice(any(), any())).thenReturn(false)
- assertThat(connectedDeviceItemFactory.isFilterMatched(context, cachedDevice, audioManager))
+ assertThat(
+ connectedDeviceItemFactory.isFilterMatched(
+ context,
+ cachedDevice,
+ isOngoingCall = false,
+ )
+ )
.isFalse()
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemInteractorTest.kt
index 42dc50d77d05..943b89aa10f3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemInteractorTest.kt
@@ -29,6 +29,7 @@ import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.testKosmos
import com.android.systemui.util.time.FakeSystemClock
+import com.android.systemui.volume.domain.interactor.audioModeInteractor
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.test.TestScope
@@ -102,7 +103,6 @@ class DeviceItemInteractorTest : SysuiTestCase() {
DeviceItemInteractor(
bluetoothTileDialogRepository,
kosmos.audioSharingInteractor,
- audioManager,
adapter,
localBluetoothManager,
fakeSystemClock,
@@ -111,6 +111,7 @@ class DeviceItemInteractorTest : SysuiTestCase() {
emptyList(),
testScope.backgroundScope,
dispatcher,
+ kosmos.audioModeInteractor,
)
val latest by collectLastValue(interactor.deviceItemUpdate)
@@ -130,7 +131,6 @@ class DeviceItemInteractorTest : SysuiTestCase() {
DeviceItemInteractor(
bluetoothTileDialogRepository,
kosmos.audioSharingInteractor,
- audioManager,
adapter,
localBluetoothManager,
fakeSystemClock,
@@ -139,6 +139,7 @@ class DeviceItemInteractorTest : SysuiTestCase() {
emptyList(),
testScope.backgroundScope,
dispatcher,
+ kosmos.audioModeInteractor,
)
val latest by collectLastValue(interactor.deviceItemUpdate)
@@ -158,7 +159,6 @@ class DeviceItemInteractorTest : SysuiTestCase() {
DeviceItemInteractor(
bluetoothTileDialogRepository,
kosmos.audioSharingInteractor,
- audioManager,
adapter,
localBluetoothManager,
fakeSystemClock,
@@ -167,6 +167,7 @@ class DeviceItemInteractorTest : SysuiTestCase() {
emptyList(),
testScope.backgroundScope,
dispatcher,
+ kosmos.audioModeInteractor,
)
val latest by collectLastValue(interactor.deviceItemUpdate)
@@ -186,7 +187,6 @@ class DeviceItemInteractorTest : SysuiTestCase() {
DeviceItemInteractor(
bluetoothTileDialogRepository,
kosmos.audioSharingInteractor,
- audioManager,
adapter,
localBluetoothManager,
fakeSystemClock,
@@ -198,6 +198,7 @@ class DeviceItemInteractorTest : SysuiTestCase() {
emptyList(),
testScope.backgroundScope,
dispatcher,
+ kosmos.audioModeInteractor,
)
val latest by collectLastValue(interactor.deviceItemUpdate)
@@ -217,7 +218,6 @@ class DeviceItemInteractorTest : SysuiTestCase() {
DeviceItemInteractor(
bluetoothTileDialogRepository,
kosmos.audioSharingInteractor,
- audioManager,
adapter,
localBluetoothManager,
fakeSystemClock,
@@ -238,6 +238,7 @@ class DeviceItemInteractorTest : SysuiTestCase() {
),
testScope.backgroundScope,
dispatcher,
+ kosmos.audioModeInteractor,
)
`when`(deviceItem1.type).thenReturn(DeviceItemType.CONNECTED_BLUETOOTH_DEVICE)
`when`(deviceItem2.type).thenReturn(DeviceItemType.SAVED_BLUETOOTH_DEVICE)
@@ -259,7 +260,6 @@ class DeviceItemInteractorTest : SysuiTestCase() {
DeviceItemInteractor(
bluetoothTileDialogRepository,
kosmos.audioSharingInteractor,
- audioManager,
adapter,
localBluetoothManager,
fakeSystemClock,
@@ -277,6 +277,7 @@ class DeviceItemInteractorTest : SysuiTestCase() {
listOf(DeviceItemType.CONNECTED_BLUETOOTH_DEVICE),
testScope.backgroundScope,
dispatcher,
+ kosmos.audioModeInteractor,
)
`when`(deviceItem1.type).thenReturn(DeviceItemType.CONNECTED_BLUETOOTH_DEVICE)
`when`(deviceItem2.type).thenReturn(DeviceItemType.CONNECTED_BLUETOOTH_DEVICE)
@@ -300,7 +301,6 @@ class DeviceItemInteractorTest : SysuiTestCase() {
DeviceItemInteractor(
bluetoothTileDialogRepository,
kosmos.audioSharingInteractor,
- audioManager,
adapter,
localBluetoothManager,
fakeSystemClock,
@@ -309,6 +309,7 @@ class DeviceItemInteractorTest : SysuiTestCase() {
emptyList(),
testScope.backgroundScope,
dispatcher,
+ kosmos.audioModeInteractor,
)
val latest by collectLastValue(interactor.deviceItemUpdate)
val latestShowSeeAll by collectLastValue(interactor.showSeeAllUpdate)
@@ -327,7 +328,7 @@ class DeviceItemInteractorTest : SysuiTestCase() {
override fun isFilterMatched(
context: Context,
cachedDevice: CachedBluetoothDevice,
- audioManager: AudioManager,
+ isOngoingCall: Boolean,
audioSharingAvailable: Boolean,
) = isFilterMatchFunc(cachedDevice)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt
index 296a0fc2eb40..296a0fc2eb40 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dump/LogEulogizerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/dump/LogEulogizerTest.kt
index ae6b337a3fa0..ae6b337a3fa0 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dump/LogEulogizerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/dump/LogEulogizerTest.kt
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/ui/view/ContextualEduUiCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/education/domain/ui/view/ContextualEduUiCoordinatorTest.kt
index 6cb6fed978b8..6cb6fed978b8 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/ui/view/ContextualEduUiCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/education/domain/ui/view/ContextualEduUiCoordinatorTest.kt
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModelTest.kt
index 9c7f01495b58..9c7f01495b58 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModelTest.kt
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/lifecycle/HydratorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/lifecycle/HydratorTest.kt
index b0e93fbecbb9..b0e93fbecbb9 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/lifecycle/HydratorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/lifecycle/HydratorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerTest.kt
index a2bd5ec28f08..aaf5559290df 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerTest.kt
@@ -66,6 +66,7 @@ import com.android.systemui.scene.data.repository.Idle
import com.android.systemui.scene.data.repository.setSceneTransition
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.statusbar.featurepods.media.domain.interactor.mediaControlChipInteractor
import com.android.systemui.statusbar.notification.collection.provider.OnReorderingAllowedListener
import com.android.systemui.statusbar.notification.collection.provider.VisualStabilityProvider
import com.android.systemui.statusbar.policy.ConfigurationController
@@ -203,6 +204,7 @@ class MediaCarouselControllerTest(flags: FlagsParameterization) : SysuiTestCase(
mediaCarouselViewModel = kosmos.mediaCarouselViewModel,
mediaViewControllerFactory = mediaViewControllerFactory,
deviceEntryInteractor = kosmos.deviceEntryInteractor,
+ mediaControlChipInteractor = kosmos.mediaControlChipInteractor,
)
verify(configurationController).addCallback(capture(configListener))
verify(visualStabilityProvider)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java
index 23282b16d8a8..205ccea657df 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java
@@ -84,7 +84,7 @@ public class MediaOutputBaseDialogTest extends SysuiTestCase {
private final Kosmos mKosmos = SysuiTestCaseExtKt.testKosmos(this);
// Mock
- private MediaOutputBaseAdapter mMediaOutputBaseAdapter = mock(MediaOutputBaseAdapter.class);
+ private MediaOutputAdapterBase mMediaOutputBaseAdapter = mock(MediaOutputAdapterBase.class);
private MediaController mMediaController = mock(MediaController.class);
private PlaybackState mPlaybackState = mock(PlaybackState.class);
private MediaSessionManager mMediaSessionManager = mock(MediaSessionManager.class);
@@ -219,7 +219,6 @@ public class MediaOutputBaseDialogTest extends SysuiTestCase {
public void refresh_withIconCompat_iconIsVisible() {
mIconCompat = IconCompat.createWithBitmap(
Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888));
- when(mMediaOutputBaseAdapter.getController()).thenReturn(mMediaSwitchingController);
mMediaOutputBaseDialogImpl.refresh();
final ImageView view = mMediaOutputBaseDialogImpl.mDialogView.requireViewById(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/QSFragmentComposeTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/QSFragmentComposeTest.kt
index ab78029684d4..ab78029684d4 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/QSFragmentComposeTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/QSFragmentComposeTest.kt
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/sensorprivacy/SensorUseStartedActivityTest.kt b/packages/SystemUI/tests/src/com/android/systemui/sensorprivacy/SensorUseStartedActivityTest.kt
index 1a4749c3196c..1a4749c3196c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/sensorprivacy/SensorUseStartedActivityTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/sensorprivacy/SensorUseStartedActivityTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
index 944604f94ce4..ae8b52aa6553 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
@@ -18,7 +18,7 @@ package com.android.systemui.shade
import android.graphics.Insets
import android.graphics.Rect
-import android.os.PowerManager
+import android.os.powerManager
import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
import android.testing.AndroidTestingRunner
@@ -32,7 +32,7 @@ import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
import androidx.test.filters.SmallTest
import com.android.compose.animation.scene.SceneKey
-import com.android.systemui.Flags
+import com.android.systemui.Flags.FLAG_COMMUNAL_HUB
import com.android.systemui.Flags.FLAG_GLANCEABLE_HUB_V2
import com.android.systemui.Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE_FIX
import com.android.systemui.SysuiTestCase
@@ -40,7 +40,6 @@ import com.android.systemui.ambient.touch.TouchHandler
import com.android.systemui.ambient.touch.TouchMonitor
import com.android.systemui.ambient.touch.dagger.AmbientTouchComponent
import com.android.systemui.bouncer.data.repository.fakeKeyguardBouncerRepository
-import com.android.systemui.communal.data.repository.FakeCommunalSceneRepository
import com.android.systemui.communal.data.repository.fakeCommunalSceneRepository
import com.android.systemui.communal.domain.interactor.communalInteractor
import com.android.systemui.communal.domain.interactor.communalSettingsInteractor
@@ -58,8 +57,10 @@ import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.collectLastValue
+import com.android.systemui.kosmos.runTest
import com.android.systemui.kosmos.testScope
+import com.android.systemui.kosmos.useUnconfinedTestDispatcher
import com.android.systemui.log.logcatLogBuffer
import com.android.systemui.media.controls.controller.keyguardMediaController
import com.android.systemui.res.R
@@ -69,8 +70,8 @@ import com.android.systemui.statusbar.lockscreen.lockscreenSmartspaceController
import com.android.systemui.statusbar.notification.stack.notificationStackScrollLayoutController
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.test.runTest
import org.junit.After
import org.junit.Assert.assertThrows
@@ -89,33 +90,23 @@ import org.mockito.kotlin.whenever
@RunWith(AndroidTestingRunner::class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
@SmallTest
+@EnableFlags(FLAG_COMMUNAL_HUB)
class GlanceableHubContainerControllerTest : SysuiTestCase() {
- private val kosmos: Kosmos =
- testKosmos().apply {
- // UnconfinedTestDispatcher makes testing simpler due to CommunalInteractor flows using
- // SharedFlow
- testDispatcher = UnconfinedTestDispatcher()
- }
+ private val kosmos: Kosmos = testKosmos().useUnconfinedTestDispatcher()
- private var communalViewModel = mock<CommunalViewModel>()
- private var powerManager = mock<PowerManager>()
- private var touchMonitor = mock<TouchMonitor>()
- private var communalColors = mock<CommunalColors>()
- private var communalContent = mock<CommunalContent>()
- private lateinit var ambientTouchComponentFactory: AmbientTouchComponent.Factory
+ private val swipeToHubEnabled = MutableStateFlow(false)
+ private val mockCommunalViewModel =
+ mock<CommunalViewModel> { on { swipeToHubEnabled } doReturn swipeToHubEnabled }
+ private val touchMonitor = mock<TouchMonitor>()
private lateinit var parentView: FrameLayout
private lateinit var containerView: View
- private lateinit var testableLooper: TestableLooper
-
- private lateinit var communalRepository: FakeCommunalSceneRepository
- private lateinit var underTest: GlanceableHubContainerController
- @Before
- fun setUp() {
- communalRepository = kosmos.fakeCommunalSceneRepository
+ private val Kosmos.testableLooper by
+ Kosmos.Fixture { TestableLooper.get(this@GlanceableHubContainerControllerTest) }
- ambientTouchComponentFactory =
+ private val Kosmos.ambientTouchComponentFactory by
+ Kosmos.Fixture {
object : AmbientTouchComponent.Factory {
override fun create(
lifecycleOwner: LifecycleOwner,
@@ -126,50 +117,39 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() {
override fun getTouchMonitor(): TouchMonitor = touchMonitor
}
}
+ }
- with(kosmos) {
- underTest =
- GlanceableHubContainerController(
- communalInteractor,
- communalSettingsInteractor,
- communalViewModel,
- keyguardInteractor,
- keyguardTransitionInteractor,
- shadeInteractor,
- powerManager,
- communalColors,
- ambientTouchComponentFactory,
- communalContent,
- sceneDataSourceDelegator,
- notificationStackScrollLayoutController,
- keyguardMediaController,
- lockscreenSmartspaceController,
- logcatLogBuffer("GlanceableHubContainerControllerTest"),
- )
-
- // Make below last notification true by default or else touches will be ignored by
- // default when the hub is not showing.
- whenever(notificationStackScrollLayoutController.isBelowLastNotification(any(), any()))
- .thenReturn(true)
+ private val Kosmos.underTest by
+ Kosmos.Fixture {
+ GlanceableHubContainerController(
+ communalInteractor,
+ communalSettingsInteractor,
+ mockCommunalViewModel,
+ keyguardInteractor,
+ keyguardTransitionInteractor,
+ shadeInteractor,
+ powerManager,
+ mock<CommunalColors>(),
+ ambientTouchComponentFactory,
+ mock<CommunalContent>(),
+ sceneDataSourceDelegator,
+ notificationStackScrollLayoutController,
+ keyguardMediaController,
+ lockscreenSmartspaceController,
+ logcatLogBuffer("GlanceableHubContainerControllerTest"),
+ )
}
- testableLooper = TestableLooper.get(this)
+ @Before
+ fun setUp() {
overrideResource(R.dimen.communal_right_edge_swipe_region_width, RIGHT_SWIPE_REGION_WIDTH)
overrideResource(R.dimen.communal_top_edge_swipe_region_height, TOP_SWIPE_REGION_WIDTH)
overrideResource(
R.dimen.communal_bottom_edge_swipe_region_height,
BOTTOM_SWIPE_REGION_WIDTH,
)
-
- // Make communal available so that communalInteractor.desiredScene accurately reflects
- // scene changes instead of just returning Blank.
- mSetFlagsRule.enableFlags(Flags.FLAG_COMMUNAL_HUB)
- with(kosmos.testScope) {
- launch { kosmos.setCommunalAvailable(true) }
- testScheduler.runCurrent()
- }
-
- initAndAttachContainerView()
+ runBlocking { kosmos.setCommunalAvailable(true) }
+ kosmos.initAndAttachContainerView()
}
@After
@@ -179,606 +159,531 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() {
@Test
fun initView_calledTwice_throwsException() =
- with(kosmos) {
- testScope.runTest {
- underTest =
- GlanceableHubContainerController(
- communalInteractor,
- kosmos.communalSettingsInteractor,
- communalViewModel,
- keyguardInteractor,
- kosmos.keyguardTransitionInteractor,
- shadeInteractor,
- powerManager,
- communalColors,
- ambientTouchComponentFactory,
- communalContent,
- kosmos.sceneDataSourceDelegator,
- kosmos.notificationStackScrollLayoutController,
- kosmos.keyguardMediaController,
- kosmos.lockscreenSmartspaceController,
- logcatLogBuffer("GlanceableHubContainerControllerTest"),
- )
+ kosmos.runTest {
+ val controller =
+ GlanceableHubContainerController(
+ communalInteractor,
+ communalSettingsInteractor,
+ mockCommunalViewModel,
+ keyguardInteractor,
+ keyguardTransitionInteractor,
+ shadeInteractor,
+ powerManager,
+ mock<CommunalColors>(),
+ ambientTouchComponentFactory,
+ mock<CommunalContent>(),
+ sceneDataSourceDelegator,
+ notificationStackScrollLayoutController,
+ keyguardMediaController,
+ lockscreenSmartspaceController,
+ logcatLogBuffer("GlanceableHubContainerControllerTest"),
+ )
- // First call succeeds.
- underTest.initView(context)
+ // First call succeeds.
+ controller.initView(context)
- // Second call throws.
- assertThrows(RuntimeException::class.java) { underTest.initView(context) }
- }
+ // Second call throws.
+ assertThrows(RuntimeException::class.java) { controller.initView(context) }
}
@Test
fun lifecycle_initializedAfterConstruction() =
- with(kosmos) {
- val underTest =
+ kosmos.runTest {
+ val controller =
GlanceableHubContainerController(
communalInteractor,
- kosmos.communalSettingsInteractor,
- communalViewModel,
+ communalSettingsInteractor,
+ mockCommunalViewModel,
keyguardInteractor,
- kosmos.keyguardTransitionInteractor,
+ keyguardTransitionInteractor,
shadeInteractor,
powerManager,
- communalColors,
+ mock<CommunalColors>(),
ambientTouchComponentFactory,
- communalContent,
- kosmos.sceneDataSourceDelegator,
- kosmos.notificationStackScrollLayoutController,
- kosmos.keyguardMediaController,
- kosmos.lockscreenSmartspaceController,
+ mock<CommunalContent>(),
+ sceneDataSourceDelegator,
+ notificationStackScrollLayoutController,
+ keyguardMediaController,
+ lockscreenSmartspaceController,
logcatLogBuffer("GlanceableHubContainerControllerTest"),
)
- assertThat(underTest.lifecycle.currentState).isEqualTo(Lifecycle.State.INITIALIZED)
+ assertThat(controller.lifecycle.currentState).isEqualTo(Lifecycle.State.INITIALIZED)
}
@Test
fun lifecycle_createdAfterViewCreated() =
- with(kosmos) {
- val underTest =
+ kosmos.runTest {
+ val controller =
GlanceableHubContainerController(
communalInteractor,
- kosmos.communalSettingsInteractor,
- communalViewModel,
+ communalSettingsInteractor,
+ mockCommunalViewModel,
keyguardInteractor,
- kosmos.keyguardTransitionInteractor,
+ keyguardTransitionInteractor,
shadeInteractor,
powerManager,
- communalColors,
+ mock<CommunalColors>(),
ambientTouchComponentFactory,
- communalContent,
- kosmos.sceneDataSourceDelegator,
- kosmos.notificationStackScrollLayoutController,
- kosmos.keyguardMediaController,
- kosmos.lockscreenSmartspaceController,
+ mock<CommunalContent>(),
+ sceneDataSourceDelegator,
+ notificationStackScrollLayoutController,
+ keyguardMediaController,
+ lockscreenSmartspaceController,
logcatLogBuffer("GlanceableHubContainerControllerTest"),
)
// Only initView without attaching a view as we don't want the flows to start collecting
// yet.
- underTest.initView(View(context))
+ controller.initView(View(context))
- assertThat(underTest.lifecycle.currentState).isEqualTo(Lifecycle.State.CREATED)
+ assertThat(controller.lifecycle.currentState).isEqualTo(Lifecycle.State.CREATED)
}
@Test
- fun lifecycle_startedAfterFlowsUpdate() {
- // Flows start collecting due to test setup, causing the state to advance to STARTED.
- assertThat(underTest.lifecycle.currentState).isEqualTo(Lifecycle.State.STARTED)
- }
+ fun lifecycle_startedAfterFlowsUpdate() =
+ kosmos.runTest {
+ // Flows start collecting due to test setup, causing the state to advance to STARTED.
+ assertThat(underTest.lifecycle.currentState).isEqualTo(Lifecycle.State.STARTED)
+ }
@Test
fun lifecycle_resumedAfterCommunalShows() =
- with(kosmos) {
- testScope.runTest {
- // Communal is open.
- goToScene(CommunalScenes.Communal)
+ kosmos.runTest {
+ // Communal is open.
+ goToScene(CommunalScenes.Communal)
- assertThat(underTest.lifecycle.currentState).isEqualTo(Lifecycle.State.RESUMED)
- }
+ assertThat(underTest.lifecycle.currentState).isEqualTo(Lifecycle.State.RESUMED)
}
@Test
fun lifecycle_startedAfterCommunalCloses() =
- with(kosmos) {
- testScope.runTest {
- // Communal is open.
- goToScene(CommunalScenes.Communal)
+ kosmos.runTest {
+ // Communal is open.
+ goToScene(CommunalScenes.Communal)
- assertThat(underTest.lifecycle.currentState).isEqualTo(Lifecycle.State.RESUMED)
+ assertThat(underTest.lifecycle.currentState).isEqualTo(Lifecycle.State.RESUMED)
- // Communal closes.
- goToScene(CommunalScenes.Blank)
+ // Communal closes.
+ goToScene(CommunalScenes.Blank)
- assertThat(underTest.lifecycle.currentState).isEqualTo(Lifecycle.State.STARTED)
- }
+ assertThat(underTest.lifecycle.currentState).isEqualTo(Lifecycle.State.STARTED)
}
@Test
fun lifecycle_startedAfterPrimaryBouncerShows() =
- with(kosmos) {
- testScope.runTest {
- // Communal is open.
- goToScene(CommunalScenes.Communal)
+ kosmos.runTest {
+ // Communal is open.
+ goToScene(CommunalScenes.Communal)
- // Bouncer is visible.
- fakeKeyguardBouncerRepository.setPrimaryShow(true)
- testableLooper.processAllMessages()
+ // Bouncer is visible.
+ fakeKeyguardBouncerRepository.setPrimaryShow(true)
+ testableLooper.processAllMessages()
- assertThat(underTest.lifecycle.currentState).isEqualTo(Lifecycle.State.STARTED)
- }
+ assertThat(underTest.lifecycle.currentState).isEqualTo(Lifecycle.State.STARTED)
}
@Test
fun lifecycle_startedAfterAlternateBouncerShows() =
- with(kosmos) {
- testScope.runTest {
- // Communal is open.
- goToScene(CommunalScenes.Communal)
+ kosmos.runTest {
+ // Communal is open.
+ goToScene(CommunalScenes.Communal)
- // Bouncer is visible.
- fakeKeyguardBouncerRepository.setAlternateVisible(true)
- testableLooper.processAllMessages()
+ // Bouncer is visible.
+ fakeKeyguardBouncerRepository.setAlternateVisible(true)
+ testableLooper.processAllMessages()
- assertThat(underTest.lifecycle.currentState).isEqualTo(Lifecycle.State.STARTED)
- }
+ assertThat(underTest.lifecycle.currentState).isEqualTo(Lifecycle.State.STARTED)
}
@Test
fun lifecycle_startedWhenEditActivityShowing() =
- with(kosmos) {
- testScope.runTest {
- // Communal is open.
- goToScene(CommunalScenes.Communal)
+ kosmos.runTest {
+ // Communal is open.
+ goToScene(CommunalScenes.Communal)
- // Edit activity is showing.
- communalInteractor.setEditActivityShowing(true)
- testableLooper.processAllMessages()
+ // Edit activity is showing.
+ communalInteractor.setEditActivityShowing(true)
+ testableLooper.processAllMessages()
- assertThat(underTest.lifecycle.currentState).isEqualTo(Lifecycle.State.STARTED)
- }
+ assertThat(underTest.lifecycle.currentState).isEqualTo(Lifecycle.State.STARTED)
}
@Test
fun lifecycle_startedWhenEditModeTransitionStarted() =
- with(kosmos) {
- testScope.runTest {
- // Communal is open.
- goToScene(CommunalScenes.Communal)
-
- // Leaving edit mode to return to the hub.
- fakeKeyguardTransitionRepository.sendTransitionStep(
- TransitionStep(
- from = KeyguardState.GONE,
- to = KeyguardState.GLANCEABLE_HUB,
- value = 1.0f,
- transitionState = TransitionState.RUNNING,
- )
+ kosmos.runTest {
+ // Communal is open.
+ goToScene(CommunalScenes.Communal)
+
+ // Leaving edit mode to return to the hub.
+ fakeKeyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(
+ from = KeyguardState.GONE,
+ to = KeyguardState.GLANCEABLE_HUB,
+ value = 1.0f,
+ transitionState = TransitionState.RUNNING,
)
- testableLooper.processAllMessages()
+ )
+ testableLooper.processAllMessages()
- assertThat(underTest.lifecycle.currentState).isEqualTo(Lifecycle.State.STARTED)
- }
+ assertThat(underTest.lifecycle.currentState).isEqualTo(Lifecycle.State.STARTED)
}
@Test
- fun lifecycle_createdAfterDisposeView() {
- // Container view disposed.
- underTest.disposeView()
+ fun lifecycle_createdAfterDisposeView() =
+ kosmos.runTest {
+ // Container view disposed.
+ underTest.disposeView()
- assertThat(underTest.lifecycle.currentState).isEqualTo(Lifecycle.State.CREATED)
- }
+ assertThat(underTest.lifecycle.currentState).isEqualTo(Lifecycle.State.CREATED)
+ }
@Test
fun lifecycle_startedAfterShadeShows() =
- with(kosmos) {
- testScope.runTest {
- // Communal is open.
- goToScene(CommunalScenes.Communal)
+ kosmos.runTest {
+ // Communal is open.
+ goToScene(CommunalScenes.Communal)
- // Shade shows up.
- shadeTestUtil.setQsExpansion(1.0f)
- testableLooper.processAllMessages()
+ // Shade shows up.
+ shadeTestUtil.setQsExpansion(1.0f)
+ testableLooper.processAllMessages()
- assertThat(underTest.lifecycle.currentState).isEqualTo(Lifecycle.State.STARTED)
- }
+ assertThat(underTest.lifecycle.currentState).isEqualTo(Lifecycle.State.STARTED)
}
@Test
fun lifecycle_doesNotResumeOnUserInteractivityOnceExpanded() =
- with(kosmos) {
- testScope.runTest {
- // Communal is open.
- goToScene(CommunalScenes.Communal)
-
- // Shade shows up.
- shadeTestUtil.setShadeExpansion(1.0f)
- testableLooper.processAllMessages()
- underTest.onTouchEvent(DOWN_EVENT)
- testableLooper.processAllMessages()
-
- assertThat(underTest.lifecycle.currentState).isEqualTo(Lifecycle.State.STARTED)
-
- // Shade starts collapsing.
- shadeTestUtil.setShadeExpansion(.5f)
- testableLooper.processAllMessages()
- underTest.onTouchEvent(DOWN_EVENT)
- testableLooper.processAllMessages()
-
- assertThat(underTest.lifecycle.currentState).isEqualTo(Lifecycle.State.STARTED)
-
- // Shade fully collpase, and then expand should with touch interaction should now
- // be resumed.
- shadeTestUtil.setShadeExpansion(0f)
- testableLooper.processAllMessages()
- shadeTestUtil.setShadeExpansion(.5f)
- testableLooper.processAllMessages()
- underTest.onTouchEvent(DOWN_EVENT)
- testableLooper.processAllMessages()
-
- assertThat(underTest.lifecycle.currentState).isEqualTo(Lifecycle.State.RESUMED)
- }
+ kosmos.runTest {
+ // Communal is open.
+ goToScene(CommunalScenes.Communal)
+
+ // Shade shows up.
+ shadeTestUtil.setShadeExpansion(1.0f)
+ testableLooper.processAllMessages()
+ underTest.onTouchEvent(DOWN_EVENT)
+ testableLooper.processAllMessages()
+
+ assertThat(underTest.lifecycle.currentState).isEqualTo(Lifecycle.State.STARTED)
+
+ // Shade starts collapsing.
+ shadeTestUtil.setShadeExpansion(.5f)
+ testableLooper.processAllMessages()
+ underTest.onTouchEvent(DOWN_EVENT)
+ testableLooper.processAllMessages()
+
+ assertThat(underTest.lifecycle.currentState).isEqualTo(Lifecycle.State.STARTED)
+
+ // Shade fully collpase, and then expand should with touch interaction should now
+ // be resumed.
+ shadeTestUtil.setShadeExpansion(0f)
+ testableLooper.processAllMessages()
+ shadeTestUtil.setShadeExpansion(.5f)
+ testableLooper.processAllMessages()
+ underTest.onTouchEvent(DOWN_EVENT)
+ testableLooper.processAllMessages()
+
+ assertThat(underTest.lifecycle.currentState).isEqualTo(Lifecycle.State.RESUMED)
}
@Test
fun touchHandling_moveEventProcessedAfterCancel() =
- with(kosmos) {
- testScope.runTest {
- // Communal is open.
- goToScene(CommunalScenes.Communal)
-
- // Touch starts and ends.
- assertThat(underTest.onTouchEvent(DOWN_EVENT)).isTrue()
- assertThat(underTest.onTouchEvent(CANCEL_EVENT)).isTrue()
-
- // Up event is no longer processed
- assertThat(underTest.onTouchEvent(UP_EVENT)).isFalse()
-
- // Move event can still be processed
- assertThat(underTest.onTouchEvent(MOVE_EVENT)).isTrue()
- assertThat(underTest.onTouchEvent(MOVE_EVENT)).isTrue()
- assertThat(underTest.onTouchEvent(UP_EVENT)).isTrue()
- }
+ kosmos.runTest {
+ // Communal is open.
+ goToScene(CommunalScenes.Communal)
+
+ // Touch starts and ends.
+ assertThat(underTest.onTouchEvent(DOWN_EVENT)).isTrue()
+ assertThat(underTest.onTouchEvent(CANCEL_EVENT)).isTrue()
+
+ // Up event is no longer processed
+ assertThat(underTest.onTouchEvent(UP_EVENT)).isFalse()
+
+ // Move event can still be processed
+ assertThat(underTest.onTouchEvent(MOVE_EVENT)).isTrue()
+ assertThat(underTest.onTouchEvent(MOVE_EVENT)).isTrue()
+ assertThat(underTest.onTouchEvent(UP_EVENT)).isTrue()
}
@Test
fun editMode_communalAvailable() =
- with(kosmos) {
- testScope.runTest {
- val available by collectLastValue(underTest.communalAvailable())
- setCommunalAvailable(false)
-
- assertThat(available).isFalse()
- communalInteractor.setEditModeOpen(true)
- assertThat(available).isTrue()
- }
+ kosmos.runTest {
+ val available by collectLastValue(underTest.communalAvailable())
+ setCommunalAvailable(false)
+
+ assertThat(available).isFalse()
+ communalInteractor.setEditModeOpen(true)
+ assertThat(available).isTrue()
}
@Test
@DisableFlags(FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE_FIX)
fun gestureExclusionZone_setAfterInit() =
- with(kosmos) {
- testScope.runTest {
- goToScene(CommunalScenes.Communal)
-
- assertThat(containerView.systemGestureExclusionRects)
- .containsExactly(
- Rect(
- /* left= */ 0,
- /* top= */ TOP_SWIPE_REGION_WIDTH,
- /* right= */ CONTAINER_WIDTH,
- /* bottom= */ CONTAINER_HEIGHT - BOTTOM_SWIPE_REGION_WIDTH,
- )
+ kosmos.runTest {
+ goToScene(CommunalScenes.Communal)
+
+ assertThat(containerView.systemGestureExclusionRects)
+ .containsExactly(
+ Rect(
+ /* left= */ 0,
+ /* top= */ TOP_SWIPE_REGION_WIDTH,
+ /* right= */ CONTAINER_WIDTH,
+ /* bottom= */ CONTAINER_HEIGHT - BOTTOM_SWIPE_REGION_WIDTH,
)
- }
+ )
}
@Test
@EnableFlags(FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE_FIX)
fun gestureExclusionZone_setAfterInit_fullSwipe() =
- with(kosmos) {
- testScope.runTest {
- goToScene(CommunalScenes.Communal)
+ kosmos.runTest {
+ goToScene(CommunalScenes.Communal)
- assertThat(containerView.systemGestureExclusionRects).isEmpty()
- }
+ assertThat(containerView.systemGestureExclusionRects).isEmpty()
}
@Test
@DisableFlags(FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE_FIX)
fun gestureExclusionZone_unsetWhenShadeOpen() =
- with(kosmos) {
- testScope.runTest {
- goToScene(CommunalScenes.Communal)
+ kosmos.runTest {
+ goToScene(CommunalScenes.Communal)
- // Exclusion rect is set.
- assertThat(containerView.systemGestureExclusionRects).isNotEmpty()
+ // Exclusion rect is set.
+ assertThat(containerView.systemGestureExclusionRects).isNotEmpty()
- // Shade shows up.
- shadeTestUtil.setQsExpansion(1.0f)
- testableLooper.processAllMessages()
+ // Shade shows up.
+ shadeTestUtil.setQsExpansion(1.0f)
+ testableLooper.processAllMessages()
- // Exclusion rects are unset.
- assertThat(containerView.systemGestureExclusionRects).isEmpty()
- }
+ // Exclusion rects are unset.
+ assertThat(containerView.systemGestureExclusionRects).isEmpty()
}
@Test
@DisableFlags(FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE_FIX)
fun gestureExclusionZone_unsetWhenBouncerOpen() =
- with(kosmos) {
- testScope.runTest {
- goToScene(CommunalScenes.Communal)
+ kosmos.runTest {
+ goToScene(CommunalScenes.Communal)
- // Exclusion rect is set.
- assertThat(containerView.systemGestureExclusionRects).isNotEmpty()
+ // Exclusion rect is set.
+ assertThat(containerView.systemGestureExclusionRects).isNotEmpty()
- // Bouncer is visible.
- fakeKeyguardBouncerRepository.setPrimaryShow(true)
- testableLooper.processAllMessages()
+ // Bouncer is visible.
+ fakeKeyguardBouncerRepository.setPrimaryShow(true)
+ testableLooper.processAllMessages()
- // Exclusion rects are unset.
- assertThat(containerView.systemGestureExclusionRects).isEmpty()
- }
+ // Exclusion rects are unset.
+ assertThat(containerView.systemGestureExclusionRects).isEmpty()
}
@Test
@DisableFlags(FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE_FIX)
fun gestureExclusionZone_unsetWhenHubClosed() =
- with(kosmos) {
- testScope.runTest {
- goToScene(CommunalScenes.Communal)
+ kosmos.runTest {
+ goToScene(CommunalScenes.Communal)
- // Exclusion rect is set.
- assertThat(containerView.systemGestureExclusionRects).isNotEmpty()
+ // Exclusion rect is set.
+ assertThat(containerView.systemGestureExclusionRects).isNotEmpty()
- // Leave the hub.
- goToScene(CommunalScenes.Blank)
+ // Leave the hub.
+ goToScene(CommunalScenes.Blank)
- // Exclusion rect is unset.
- assertThat(containerView.systemGestureExclusionRects).isEmpty()
- }
+ // Exclusion rect is unset.
+ assertThat(containerView.systemGestureExclusionRects).isEmpty()
}
@Test
fun fullScreenSwipeGesture_doNotProcessTouchesInNotificationStack() =
- with(kosmos) {
- testScope.runTest {
- // Communal is closed.
- goToScene(CommunalScenes.Blank)
- whenever(
- notificationStackScrollLayoutController.isBelowLastNotification(
- any(),
- any(),
- )
- )
- .thenReturn(false)
- assertThat(underTest.onTouchEvent(DOWN_EVENT)).isFalse()
- }
+ kosmos.runTest {
+ // Communal is closed.
+ goToScene(CommunalScenes.Blank)
+ whenever(notificationStackScrollLayoutController.isBelowLastNotification(any(), any()))
+ .thenReturn(false)
+ assertThat(underTest.onTouchEvent(DOWN_EVENT)).isFalse()
}
@Test
fun fullScreenSwipeGesture_doNotProcessTouchesInUmo() =
- with(kosmos) {
- testScope.runTest {
- // Communal is closed.
- goToScene(CommunalScenes.Blank)
- whenever(keyguardMediaController.isWithinMediaViewBounds(any(), any()))
- .thenReturn(true)
- assertThat(underTest.onTouchEvent(DOWN_EVENT)).isFalse()
- }
+ kosmos.runTest {
+ // Communal is closed.
+ goToScene(CommunalScenes.Blank)
+ whenever(keyguardMediaController.isWithinMediaViewBounds(any(), any())).thenReturn(true)
+ assertThat(underTest.onTouchEvent(DOWN_EVENT)).isFalse()
}
@Test
fun fullScreenSwipeGesture_doNotProcessTouchesInSmartspace() =
- with(kosmos) {
- testScope.runTest {
- // Communal is closed.
- goToScene(CommunalScenes.Blank)
- whenever(lockscreenSmartspaceController.isWithinSmartspaceBounds(any(), any()))
- .thenReturn(true)
- assertThat(underTest.onTouchEvent(DOWN_EVENT)).isFalse()
- }
+ kosmos.runTest {
+ // Communal is closed.
+ goToScene(CommunalScenes.Blank)
+ whenever(lockscreenSmartspaceController.isWithinSmartspaceBounds(any(), any()))
+ .thenReturn(true)
+ assertThat(underTest.onTouchEvent(DOWN_EVENT)).isFalse()
}
@Test
fun onTouchEvent_hubOpen_touchesDispatched() =
- with(kosmos) {
- testScope.runTest {
- // Communal is open.
- goToScene(CommunalScenes.Communal)
-
- // Touch event is sent to the container view.
- assertThat(underTest.onTouchEvent(DOWN_EVENT)).isTrue()
- verify(containerView).onTouchEvent(DOWN_EVENT)
- assertThat(underTest.onTouchEvent(UP_EVENT)).isTrue()
- verify(containerView).onTouchEvent(UP_EVENT)
- }
+ kosmos.runTest {
+ // Communal is open.
+ goToScene(CommunalScenes.Communal)
+
+ // Touch event is sent to the container view.
+ assertThat(underTest.onTouchEvent(DOWN_EVENT)).isTrue()
+ verify(containerView).onTouchEvent(DOWN_EVENT)
+ assertThat(underTest.onTouchEvent(UP_EVENT)).isTrue()
+ verify(containerView).onTouchEvent(UP_EVENT)
}
@Test
fun onTouchEvent_editActivityShowing_touchesConsumedButNotDispatched() =
- with(kosmos) {
- testScope.runTest {
- // Communal is open.
- goToScene(CommunalScenes.Communal)
-
- // Transitioning to or from edit mode.
- communalInteractor.setEditActivityShowing(true)
- testableLooper.processAllMessages()
-
- // onTouchEvent returns true to consume the touch, but it is not sent to the
- // container view.
- assertThat(underTest.onTouchEvent(DOWN_EVENT)).isTrue()
- verify(containerView, never()).onTouchEvent(any())
- }
+ kosmos.runTest {
+ // Communal is open.
+ goToScene(CommunalScenes.Communal)
+
+ // Transitioning to or from edit mode.
+ communalInteractor.setEditActivityShowing(true)
+ testableLooper.processAllMessages()
+
+ // onTouchEvent returns true to consume the touch, but it is not sent to the
+ // container view.
+ assertThat(underTest.onTouchEvent(DOWN_EVENT)).isTrue()
+ verify(containerView, never()).onTouchEvent(any())
}
@Test
fun onTouchEvent_editModeTransitionStarted_touchesConsumedButNotDispatched() =
- with(kosmos) {
- testScope.runTest {
- // Communal is open.
- goToScene(CommunalScenes.Communal)
-
- // Leaving edit mode to return to the hub.
- fakeKeyguardTransitionRepository.sendTransitionStep(
- TransitionStep(
- from = KeyguardState.GONE,
- to = KeyguardState.GLANCEABLE_HUB,
- value = 1.0f,
- transitionState = TransitionState.RUNNING,
- )
+ kosmos.runTest {
+ // Communal is open.
+ goToScene(CommunalScenes.Communal)
+
+ // Leaving edit mode to return to the hub.
+ fakeKeyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(
+ from = KeyguardState.GONE,
+ to = KeyguardState.GLANCEABLE_HUB,
+ value = 1.0f,
+ transitionState = TransitionState.RUNNING,
)
- testableLooper.processAllMessages()
+ )
+ testableLooper.processAllMessages()
- // onTouchEvent returns true to consume the touch, but it is not sent to the
- // container view.
- assertThat(underTest.onTouchEvent(DOWN_EVENT)).isTrue()
- verify(containerView, never()).onTouchEvent(any())
- }
+ // onTouchEvent returns true to consume the touch, but it is not sent to the
+ // container view.
+ assertThat(underTest.onTouchEvent(DOWN_EVENT)).isTrue()
+ verify(containerView, never()).onTouchEvent(any())
}
@Test
fun onTouchEvent_shadeInteracting_movesNotDispatched() =
- with(kosmos) {
- testScope.runTest {
- `whenever`(communalViewModel.swipeToHubEnabled()).thenReturn(true)
- // On lockscreen.
- goToScene(CommunalScenes.Blank)
- whenever(
- notificationStackScrollLayoutController.isBelowLastNotification(
- any(),
- any(),
- )
- )
- .thenReturn(true)
+ kosmos.runTest {
+ swipeToHubEnabled.value = true
- // Touches not consumed by default but are received by containerView.
- assertThat(underTest.onTouchEvent(DOWN_EVENT)).isFalse()
- verify(containerView).onTouchEvent(DOWN_EVENT)
+ // On lockscreen.
+ goToScene(CommunalScenes.Blank)
+ whenever(notificationStackScrollLayoutController.isBelowLastNotification(any(), any()))
+ .thenReturn(true)
- // User is interacting with shade on lockscreen.
- shadeTestUtil.setLockscreenShadeTracking(true)
- testableLooper.processAllMessages()
+ // Touches not consumed by default but are received by containerView.
+ assertThat(underTest.onTouchEvent(DOWN_EVENT)).isFalse()
+ verify(containerView).onTouchEvent(DOWN_EVENT)
- // A move event is ignored while the user is already interacting.
- assertThat(underTest.onTouchEvent(MOVE_EVENT)).isFalse()
- verify(containerView, never()).onTouchEvent(MOVE_EVENT)
+ // User is interacting with shade on lockscreen.
+ shadeTestUtil.setLockscreenShadeTracking(true)
+ testableLooper.processAllMessages()
- // An up event is still delivered.
- assertThat(underTest.onTouchEvent(UP_EVENT)).isFalse()
- verify(containerView).onTouchEvent(UP_EVENT)
- }
+ // A move event is ignored while the user is already interacting.
+ assertThat(underTest.onTouchEvent(MOVE_EVENT)).isFalse()
+ verify(containerView, never()).onTouchEvent(MOVE_EVENT)
+
+ // An up event is still delivered.
+ assertThat(underTest.onTouchEvent(UP_EVENT)).isFalse()
+ verify(containerView).onTouchEvent(UP_EVENT)
}
@Test
fun onTouchEvent_shadeExpanding_touchesNotDispatched() =
- with(kosmos) {
- testScope.runTest {
- // On lockscreen.
- goToScene(CommunalScenes.Blank)
- whenever(
- notificationStackScrollLayoutController.isBelowLastNotification(
- any(),
- any(),
- )
- )
- .thenReturn(true)
+ kosmos.runTest {
+ // On lockscreen.
+ goToScene(CommunalScenes.Blank)
+ whenever(notificationStackScrollLayoutController.isBelowLastNotification(any(), any()))
+ .thenReturn(true)
- // Shade is open slightly.
- shadeTestUtil.setShadeExpansion(0.01f)
- testableLooper.processAllMessages()
+ // Shade is open slightly.
+ shadeTestUtil.setShadeExpansion(0.01f)
+ testableLooper.processAllMessages()
- // Touches are not consumed.
- assertThat(underTest.onTouchEvent(DOWN_EVENT)).isFalse()
- verify(containerView, never()).onTouchEvent(DOWN_EVENT)
- }
+ // Touches are not consumed.
+ assertThat(underTest.onTouchEvent(DOWN_EVENT)).isFalse()
+ verify(containerView, never()).onTouchEvent(DOWN_EVENT)
}
@Test
fun onTouchEvent_qsExpanding_touchesNotDispatched() =
- with(kosmos) {
- testScope.runTest {
- // On lockscreen.
- goToScene(CommunalScenes.Blank)
- whenever(
- notificationStackScrollLayoutController.isBelowLastNotification(
- any(),
- any(),
- )
- )
- .thenReturn(true)
+ kosmos.runTest {
+ // On lockscreen.
+ goToScene(CommunalScenes.Blank)
+ whenever(notificationStackScrollLayoutController.isBelowLastNotification(any(), any()))
+ .thenReturn(true)
- // Shade is open slightly.
- shadeTestUtil.setQsExpansion(0.01f)
- testableLooper.processAllMessages()
+ // Shade is open slightly.
+ shadeTestUtil.setQsExpansion(0.01f)
+ testableLooper.processAllMessages()
- // Touches are not consumed.
- assertThat(underTest.onTouchEvent(DOWN_EVENT)).isFalse()
- verify(containerView, never()).onTouchEvent(DOWN_EVENT)
- }
+ // Touches are not consumed.
+ assertThat(underTest.onTouchEvent(DOWN_EVENT)).isFalse()
+ verify(containerView, never()).onTouchEvent(DOWN_EVENT)
}
@Test
fun onTouchEvent_bouncerInteracting_movesNotDispatched() =
- with(kosmos) {
- testScope.runTest {
- `whenever`(communalViewModel.swipeToHubEnabled()).thenReturn(true)
- // On lockscreen.
- goToScene(CommunalScenes.Blank)
- whenever(
- notificationStackScrollLayoutController.isBelowLastNotification(
- any(),
- any(),
- )
- )
- .thenReturn(true)
+ kosmos.runTest {
+ swipeToHubEnabled.value = true
+ // On lockscreen.
+ goToScene(CommunalScenes.Blank)
+ whenever(notificationStackScrollLayoutController.isBelowLastNotification(any(), any()))
+ .thenReturn(true)
- // Touches not consumed by default but are received by containerView.
- assertThat(underTest.onTouchEvent(DOWN_EVENT)).isFalse()
- verify(containerView).onTouchEvent(DOWN_EVENT)
+ // Touches not consumed by default but are received by containerView.
+ assertThat(underTest.onTouchEvent(DOWN_EVENT)).isFalse()
+ verify(containerView).onTouchEvent(DOWN_EVENT)
- // User is interacting with bouncer on lockscreen.
- fakeKeyguardBouncerRepository.setPrimaryShow(true)
- testableLooper.processAllMessages()
+ // User is interacting with bouncer on lockscreen.
+ fakeKeyguardBouncerRepository.setPrimaryShow(true)
+ testableLooper.processAllMessages()
- // A move event is ignored while the user is already interacting.
- assertThat(underTest.onTouchEvent(MOVE_EVENT)).isFalse()
- verify(containerView, never()).onTouchEvent(MOVE_EVENT)
+ // A move event is ignored while the user is already interacting.
+ assertThat(underTest.onTouchEvent(MOVE_EVENT)).isFalse()
+ verify(containerView, never()).onTouchEvent(MOVE_EVENT)
- // An up event is still delivered.
- assertThat(underTest.onTouchEvent(UP_EVENT)).isFalse()
- verify(containerView).onTouchEvent(UP_EVENT)
- }
+ // An up event is still delivered.
+ assertThat(underTest.onTouchEvent(UP_EVENT)).isFalse()
+ verify(containerView).onTouchEvent(UP_EVENT)
}
@EnableFlags(FLAG_GLANCEABLE_HUB_V2)
@Test
fun onTouchEvent_onLockscreenAndGlanceableHubV2_touchIgnored() =
- with(kosmos) {
- testScope.runTest {
- kosmos.setCommunalV2ConfigEnabled(true)
+ kosmos.runTest {
+ swipeToHubEnabled.value = false
+ setCommunalV2ConfigEnabled(true)
- // On lockscreen.
- goToScene(CommunalScenes.Blank)
+ // On lockscreen.
+ goToScene(CommunalScenes.Blank)
- assertThat(underTest.onTouchEvent(DOWN_EVENT)).isFalse()
- verify(containerView, never()).onTouchEvent(DOWN_EVENT)
- }
+ assertThat(underTest.onTouchEvent(DOWN_EVENT)).isFalse()
+ verify(containerView, never()).onTouchEvent(DOWN_EVENT)
}
@Test
- fun disposeView_destroysTouchMonitor() {
- clearInvocations(touchMonitor)
+ fun disposeView_destroysTouchMonitor() =
+ kosmos.runTest {
+ clearInvocations(touchMonitor)
- underTest.disposeView()
+ underTest.disposeView()
- verify(touchMonitor).destroy()
- }
+ verify(touchMonitor).destroy()
+ }
- private fun initAndAttachContainerView() {
+ private fun Kosmos.initAndAttachContainerView() {
val mockInsets =
mock<WindowInsets> {
on { getInsets(WindowInsets.Type.systemGestures()) } doReturn FAKE_INSETS
@@ -802,8 +707,8 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() {
testableLooper.processAllMessages()
}
- private suspend fun goToScene(scene: SceneKey) {
- communalRepository.changeScene(scene)
+ private suspend fun Kosmos.goToScene(scene: SceneKey) {
+ fakeCommunalSceneRepository.changeScene(scene)
if (scene == CommunalScenes.Communal) {
kosmos.fakeKeyguardTransitionRepository.sendTransitionSteps(
from = KeyguardState.LOCKSCREEN,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/QuickSettingsControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplTest.java
index 49cbb5a924f1..49cbb5a924f1 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/QuickSettingsControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplTest.java
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shadow/DoubleShadowTextClockTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shadow/DoubleShadowTextClockTest.kt
index 89a3d5b5cf0b..89a3d5b5cf0b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shadow/DoubleShadowTextClockTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shadow/DoubleShadowTextClockTest.kt
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/clocks/AnimatableClockViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/AnimatableClockViewTest.kt
index e076418f2630..79e78c9532c6 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/clocks/AnimatableClockViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/AnimatableClockViewTest.kt
@@ -20,9 +20,10 @@ import android.view.LayoutInflater
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.app.animation.Interpolators
-import com.android.systemui.customization.R
import com.android.systemui.SysuiTestCase
+import com.android.systemui.animation.FontVariationUtils
import com.android.systemui.animation.TextAnimator
+import com.android.systemui.customization.R
import com.android.systemui.util.mockito.any
import org.junit.Before
import org.junit.Rule
@@ -32,7 +33,9 @@ import org.mockito.Mock
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
import org.mockito.Mockito.verifyNoMoreInteractions
+import org.mockito.Mockito.`when` as whenever
import org.mockito.junit.MockitoJUnit
+import org.mockito.kotlin.eq
@RunWith(AndroidJUnit4::class)
@SmallTest
@@ -46,6 +49,7 @@ class AnimatableClockViewTest : SysuiTestCase() {
@Before
fun setUp() {
val layoutInflater = LayoutInflater.from(context)
+ whenever(mockTextAnimator.fontVariationUtils).thenReturn(FontVariationUtils())
clockView =
layoutInflater.inflate(R.layout.clock_default_small, null) as AnimatableClockView
clockView.textAnimatorFactory = { _, _ -> mockTextAnimator }
@@ -57,18 +61,19 @@ class AnimatableClockViewTest : SysuiTestCase() {
clockView.animateAppearOnLockscreen()
clockView.measure(50, 50)
+ verify(mockTextAnimator).fontVariationUtils
verify(mockTextAnimator).glyphFilter = any()
verify(mockTextAnimator)
.setTextStyle(
- weight = 300,
- textSize = -1.0f,
- color = 200,
- strokeWidth = -1F,
- animate = false,
- duration = 833L,
- interpolator = Interpolators.EMPHASIZED_DECELERATE,
- delay = 0L,
- onAnimationEnd = null
+ eq(TextAnimator.Style(fVar = "'wght' 300", color = 200)),
+ eq(
+ TextAnimator.Animation(
+ animate = false,
+ duration = 833L,
+ interpolator = Interpolators.EMPHASIZED_DECELERATE,
+ onAnimationEnd = null,
+ )
+ ),
)
verifyNoMoreInteractions(mockTextAnimator)
}
@@ -79,30 +84,24 @@ class AnimatableClockViewTest : SysuiTestCase() {
clockView.measure(50, 50)
clockView.animateAppearOnLockscreen()
+ verify(mockTextAnimator, times(2)).fontVariationUtils
verify(mockTextAnimator, times(2)).glyphFilter = any()
verify(mockTextAnimator)
.setTextStyle(
- weight = 100,
- textSize = -1.0f,
- color = 200,
- strokeWidth = -1F,
- animate = false,
- duration = 0L,
- interpolator = null,
- delay = 0L,
- onAnimationEnd = null
+ eq(TextAnimator.Style(fVar = "'wght' 100", color = 200)),
+ eq(TextAnimator.Animation(animate = false, duration = 0)),
)
+
verify(mockTextAnimator)
.setTextStyle(
- weight = 300,
- textSize = -1.0f,
- color = 200,
- strokeWidth = -1F,
- animate = true,
- duration = 833L,
- interpolator = Interpolators.EMPHASIZED_DECELERATE,
- delay = 0L,
- onAnimationEnd = null
+ eq(TextAnimator.Style(fVar = "'wght' 300", color = 200)),
+ eq(
+ TextAnimator.Animation(
+ animate = true,
+ duration = 833L,
+ interpolator = Interpolators.EMPHASIZED_DECELERATE,
+ )
+ ),
)
verifyNoMoreInteractions(mockTextAnimator)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/condition/CombinedConditionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/condition/CombinedConditionTest.kt
index 8418598c256b..8418598c256b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/condition/CombinedConditionTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/condition/CombinedConditionTest.kt
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/system/UncaughtExceptionPreHandlerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/system/UncaughtExceptionPreHandlerTest.kt
index d67ce303c451..d67ce303c451 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/system/UncaughtExceptionPreHandlerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/system/UncaughtExceptionPreHandlerTest.kt
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/featurepods/popups/ui/viewmodel/StatusBarPopupChipsViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/featurepods/popups/ui/viewmodel/StatusBarPopupChipsViewModelTest.kt
index fcbf0fe9a37a..510d6fe2b776 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/featurepods/popups/ui/viewmodel/StatusBarPopupChipsViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/featurepods/popups/ui/viewmodel/StatusBarPopupChipsViewModelTest.kt
@@ -17,19 +17,23 @@
package com.android.systemui.statusbar.featurepods.popups.ui.viewmodel
import android.platform.test.annotations.EnableFlags
+import androidx.compose.runtime.snapshots.Snapshot
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.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.media.controls.shared.model.MediaDataLoadingModel
-import com.android.systemui.statusbar.featurepods.popups.shared.model.PopupChipId
import com.android.systemui.statusbar.featurepods.popups.StatusBarPopupChips
+import com.android.systemui.statusbar.featurepods.popups.shared.model.PopupChipId
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.test.runTest
+import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -37,22 +41,41 @@ import org.junit.runner.RunWith
@EnableFlags(StatusBarPopupChips.FLAG_NAME)
@RunWith(AndroidJUnit4::class)
class StatusBarPopupChipsViewModelTest : SysuiTestCase() {
- private val kosmos = testKosmos()
- private val testScope = kosmos.testScope
- private val mediaFilterRepository = kosmos.mediaFilterRepository
- private val underTest = kosmos.statusBarPopupChipsViewModel
+ private val kosmos = testKosmos().useUnconfinedTestDispatcher()
+ private val underTest = kosmos.statusBarPopupChipsViewModelFactory.create()
+
+ @Before
+ fun setUp() {
+ underTest.activateIn(kosmos.testScope)
+ }
@Test
fun shownPopupChips_allHidden_empty() =
- testScope.runTest {
- val shownPopupChips by collectLastValue(underTest.shownPopupChips)
+ kosmos.runTest {
+ val shownPopupChips = underTest.shownPopupChips
assertThat(shownPopupChips).isEmpty()
}
@Test
fun shownPopupChips_activeMedia_restHidden_mediaControlChipShown() =
- testScope.runTest {
- val shownPopupChips by collectLastValue(underTest.shownPopupChips)
+ kosmos.runTest {
+ val shownPopupChips = underTest.shownPopupChips
+ val userMedia = MediaData(active = true, song = "test")
+ val instanceId = userMedia.instanceId
+
+ mediaFilterRepository.addSelectedUserMediaEntry(userMedia)
+ mediaFilterRepository.addMediaDataLoadingState(MediaDataLoadingModel.Loaded(instanceId))
+
+ Snapshot.takeSnapshot {
+ assertThat(shownPopupChips).hasSize(1)
+ assertThat(shownPopupChips.first().chipId).isEqualTo(PopupChipId.MediaControl)
+ }
+ }
+
+ @Test
+ fun shownPopupChips_mediaChipToggled_popupShown() =
+ kosmos.runTest {
+ val shownPopupChips = underTest.shownPopupChips
val userMedia = MediaData(active = true, song = "test")
val instanceId = userMedia.instanceId
@@ -60,7 +83,13 @@ class StatusBarPopupChipsViewModelTest : SysuiTestCase() {
mediaFilterRepository.addSelectedUserMediaEntry(userMedia)
mediaFilterRepository.addMediaDataLoadingState(MediaDataLoadingModel.Loaded(instanceId))
- assertThat(shownPopupChips).hasSize(1)
- assertThat(shownPopupChips!!.first().chipId).isEqualTo(PopupChipId.MediaControl)
+ Snapshot.takeSnapshot {
+ assertThat(shownPopupChips).hasSize(1)
+ val mediaChip = shownPopupChips.first()
+ assertThat(mediaChip.isPopupShown).isFalse()
+
+ mediaChip.showPopup.invoke()
+ assertThat(shownPopupChips.first().isPopupShown).isTrue()
+ }
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/layout/StatusBarBoundsProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/layout/StatusBarBoundsProviderTest.kt
index 04319f05f6f9..04319f05f6f9 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/layout/StatusBarBoundsProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/layout/StatusBarBoundsProviderTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java
index 281ce16b539f..19d1224a9bf3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java
@@ -28,6 +28,8 @@ import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_AMBIENT;
import static com.android.systemui.statusbar.NotificationEntryHelper.modifyRanking;
import static com.android.systemui.statusbar.NotificationEntryHelper.modifySbn;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
@@ -46,6 +48,7 @@ import android.os.Bundle;
import android.os.UserHandle;
import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.service.notification.NotificationListenerService.Ranking;
import android.service.notification.SnoozeCriterion;
import android.service.notification.StatusBarNotification;
@@ -59,9 +62,12 @@ import com.android.systemui.statusbar.RankingBuilder;
import com.android.systemui.statusbar.SbnBuilder;
import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips;
import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUi;
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
+import com.android.systemui.statusbar.notification.shared.NotificationBundleUi;
import com.android.systemui.util.time.FakeSystemClock;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
@@ -83,6 +89,9 @@ public class NotificationEntryTest extends SysuiTestCase {
private NotificationChannel mChannel = Mockito.mock(NotificationChannel.class);
private final FakeSystemClock mClock = new FakeSystemClock();
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
@Before
public void setup() {
Notification.Builder n = new Notification.Builder(mContext, "")
@@ -444,6 +453,145 @@ public class NotificationEntryTest extends SysuiTestCase {
// no crash, good
}
+ @Test
+ @EnableFlags(NotificationBundleUi.FLAG_NAME)
+ public void getParent_adapter() {
+ GroupEntry ge = new GroupEntryBuilder()
+ .build();
+ Notification notification = new Notification.Builder(mContext, "")
+ .setSmallIcon(R.drawable.ic_person)
+ .build();
+
+ NotificationEntry entry = new NotificationEntryBuilder()
+ .setPkg(TEST_PACKAGE_NAME)
+ .setOpPkg(TEST_PACKAGE_NAME)
+ .setUid(TEST_UID)
+ .setChannel(mChannel)
+ .setId(mId++)
+ .setNotification(notification)
+ .setUser(new UserHandle(ActivityManager.getCurrentUser()))
+ .setParent(ge)
+ .build();
+
+ assertThat(entry.getEntryAdapter().getParent()).isEqualTo(entry.getParent());
+ }
+
+ @Test
+ @EnableFlags(NotificationBundleUi.FLAG_NAME)
+ public void isTopLevelEntry_adapter() {
+ Notification notification = new Notification.Builder(mContext, "")
+ .setSmallIcon(R.drawable.ic_person)
+ .build();
+
+ NotificationEntry entry = new NotificationEntryBuilder()
+ .setPkg(TEST_PACKAGE_NAME)
+ .setOpPkg(TEST_PACKAGE_NAME)
+ .setUid(TEST_UID)
+ .setChannel(mChannel)
+ .setId(mId++)
+ .setNotification(notification)
+ .setUser(new UserHandle(ActivityManager.getCurrentUser()))
+ .setParent(GroupEntry.ROOT_ENTRY)
+ .build();
+
+ assertThat(entry.getEntryAdapter().isTopLevelEntry()).isTrue();
+ }
+
+ @Test
+ @EnableFlags(NotificationBundleUi.FLAG_NAME)
+ public void getKey_adapter() {
+ Notification notification = new Notification.Builder(mContext, "")
+ .setSmallIcon(R.drawable.ic_person)
+ .build();
+
+ NotificationEntry entry = new NotificationEntryBuilder()
+ .setPkg(TEST_PACKAGE_NAME)
+ .setOpPkg(TEST_PACKAGE_NAME)
+ .setUid(TEST_UID)
+ .setChannel(mChannel)
+ .setId(mId++)
+ .setNotification(notification)
+ .setUser(new UserHandle(ActivityManager.getCurrentUser()))
+ .build();
+
+ assertThat(entry.getEntryAdapter().getKey()).isEqualTo(entry.getKey());
+ }
+
+ @Test
+ @EnableFlags(NotificationBundleUi.FLAG_NAME)
+ public void getRow_adapter() {
+ ExpandableNotificationRow row = mock(ExpandableNotificationRow.class);
+ Notification notification = new Notification.Builder(mContext, "")
+ .setSmallIcon(R.drawable.ic_person)
+ .build();
+
+ NotificationEntry entry = new NotificationEntryBuilder()
+ .setPkg(TEST_PACKAGE_NAME)
+ .setOpPkg(TEST_PACKAGE_NAME)
+ .setUid(TEST_UID)
+ .setChannel(mChannel)
+ .setId(mId++)
+ .setNotification(notification)
+ .setUser(new UserHandle(ActivityManager.getCurrentUser()))
+ .build();
+ entry.setRow(row);
+
+ assertThat(entry.getEntryAdapter().getRow()).isEqualTo(entry.getRow());
+ }
+
+ @Test
+ @EnableFlags(NotificationBundleUi.FLAG_NAME)
+ public void getGroupRoot_adapter_groupSummary() {
+ ExpandableNotificationRow row = mock(ExpandableNotificationRow.class);
+ Notification notification = new Notification.Builder(mContext, "")
+ .setSmallIcon(R.drawable.ic_person)
+ .setGroupSummary(true)
+ .setGroup("key")
+ .build();
+
+ NotificationEntry entry = new NotificationEntryBuilder()
+ .setPkg(TEST_PACKAGE_NAME)
+ .setOpPkg(TEST_PACKAGE_NAME)
+ .setUid(TEST_UID)
+ .setChannel(mChannel)
+ .setId(mId++)
+ .setNotification(notification)
+ .setUser(new UserHandle(ActivityManager.getCurrentUser()))
+ .setParent(GroupEntry.ROOT_ENTRY)
+ .build();
+ entry.setRow(row);
+
+ assertThat(entry.getEntryAdapter().getGroupRoot()).isNull();
+ }
+
+ @Test
+ @EnableFlags(NotificationBundleUi.FLAG_NAME)
+ public void getGroupRoot_adapter_groupChild() {
+ Notification notification = new Notification.Builder(mContext, "")
+ .setSmallIcon(R.drawable.ic_person)
+ .setGroupSummary(true)
+ .setGroup("key")
+ .build();
+
+ NotificationEntry parent = new NotificationEntryBuilder()
+ .setParent(GroupEntry.ROOT_ENTRY)
+ .build();
+ GroupEntryBuilder groupEntry = new GroupEntryBuilder()
+ .setSummary(parent);
+
+ NotificationEntry entry = new NotificationEntryBuilder()
+ .setPkg(TEST_PACKAGE_NAME)
+ .setOpPkg(TEST_PACKAGE_NAME)
+ .setUid(TEST_UID)
+ .setChannel(mChannel)
+ .setId(mId++)
+ .setNotification(notification)
+ .setUser(new UserHandle(ActivityManager.getCurrentUser()))
+ .setParent(groupEntry.build())
+ .build();
+
+ assertThat(entry.getEntryAdapter().getGroupRoot()).isEqualTo(parent.getEntryAdapter());
+ }
private Notification.Action createContextualAction(String title) {
return new Notification.Action.Builder(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java
index f1edb417a314..f1edb417a314 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/BigPictureIconManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/BigPictureIconManagerTest.kt
index 99dcd6c9a798..99dcd6c9a798 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/BigPictureIconManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/BigPictureIconManagerTest.kt
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 77b116e2e465..a6722c5f4c22 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
@@ -16,6 +16,8 @@
package com.android.systemui.statusbar.notification.row;
+import static android.app.Flags.FLAG_NOTIFICATIONS_REDESIGN_TEMPLATES;
+
import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_ALL;
import static com.android.systemui.statusbar.notification.row.NotificationTestHelper.PKG;
import static com.android.systemui.statusbar.notification.row.NotificationTestHelper.USER_HANDLE;
@@ -29,6 +31,7 @@ import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
@@ -189,6 +192,54 @@ public class ExpandableNotificationRowTest extends SysuiTestCase {
}
@Test
+ @EnableFlags(FLAG_NOTIFICATIONS_REDESIGN_TEMPLATES)
+ public void setSensitive_doesNothingIfCalledAgain() throws Exception {
+ ExpandableNotificationRow row = mNotificationTestHelper.createRow();
+ measureAndLayout(row);
+
+ // GIVEN a mocked public layout
+ NotificationContentView mockPublicLayout = mock(NotificationContentView.class);
+ row.setPublicLayout(mockPublicLayout);
+
+ // GIVEN a sensitive notification row that's currently redacted
+ row.setHideSensitiveForIntrinsicHeight(true);
+ row.setSensitive(true, true);
+ assertThat(row.getShowingLayout()).isSameInstanceAs(row.getPublicLayout());
+ verify(mockPublicLayout).requestSelectLayout(eq(true));
+ clearInvocations(mockPublicLayout);
+
+ // WHEN the row is set to the same sensitive settings
+ row.setSensitive(true, true);
+
+ // VERIFY that the layout is not updated again
+ assertThat(row.getShowingLayout()).isSameInstanceAs(row.getPublicLayout());
+ verify(mockPublicLayout, never()).requestSelectLayout(anyBoolean());
+ }
+
+ @Test
+ @EnableFlags(FLAG_NOTIFICATIONS_REDESIGN_TEMPLATES)
+ public void testSetSensitiveOnNotifRowUpdatesLayout() throws Exception {
+ // GIVEN a sensitive notification row that's currently redacted
+ ExpandableNotificationRow row = mNotificationTestHelper.createRow();
+ measureAndLayout(row);
+ row.setHideSensitiveForIntrinsicHeight(true);
+ row.setSensitive(true, true);
+ assertThat(row.getShowingLayout()).isSameInstanceAs(row.getPublicLayout());
+
+ // GIVEN a mocked private layout
+ NotificationContentView mockPrivateLayout = mock(NotificationContentView.class);
+ row.setPrivateLayout(mockPrivateLayout);
+
+ // WHEN the row is set to no longer be sensitive
+ row.setSensitive(false, true);
+
+ // VERIFY that the layout is updated
+ assertThat(row.getShowingLayout()).isSameInstanceAs(row.getPrivateLayout());
+ verify(mockPrivateLayout).requestSelectLayout(eq(true));
+ }
+
+ @Test
+ @DisableFlags(FLAG_NOTIFICATIONS_REDESIGN_TEMPLATES)
public void testSetSensitiveOnNotifRowNotifiesOfHeightChange() throws Exception {
// GIVEN a sensitive notification row that's currently redacted
ExpandableNotificationRow row = mNotificationTestHelper.createRow();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.kt
index 699e8c30afde..47238fedee4d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.kt
@@ -23,6 +23,7 @@ import android.service.notification.StatusBarNotification
import android.testing.TestableLooper
import android.testing.ViewUtils
import android.view.NotificationHeaderView
+import android.view.NotificationTopLineView
import android.view.View
import android.view.ViewGroup
import android.widget.FrameLayout
@@ -37,6 +38,7 @@ import com.android.systemui.SysuiTestCase
import com.android.systemui.statusbar.notification.FeedbackIcon
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier
+import com.android.systemui.statusbar.notification.shared.NotificationBundleUi
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
@@ -82,8 +84,21 @@ class NotificationContentViewTest : SysuiTestCase() {
val mockEntry = createMockNotificationEntry()
row =
spy(
- ExpandableNotificationRow(mContext, /* attrs= */ null, mockEntry).apply {
- entry = mockEntry
+ when (NotificationBundleUi.isEnabled) {
+ true -> {
+ ExpandableNotificationRow(
+ mContext,
+ /* attrs= */ null,
+ UserHandle.CURRENT
+ ).apply {
+ entry = mockEntry
+ }
+ }
+ false -> {
+ ExpandableNotificationRow(mContext, /* attrs= */ null, mockEntry).apply {
+ entry = mockEntry
+ }
+ }
}
)
ViewUtils.attachView(fakeParent)
@@ -270,7 +285,7 @@ class NotificationContentViewTest : SysuiTestCase() {
val icon =
FeedbackIcon(
R.drawable.ic_feedback_alerted,
- R.string.notification_feedback_indicator_alerted
+ R.string.notification_feedback_indicator_alerted,
)
view.setFeedbackIcon(icon)
@@ -291,10 +306,7 @@ class NotificationContentViewTest : SysuiTestCase() {
val mockHeadsUpEB = mock<NotificationExpandButton>()
val mockHeadsUp = createMockNotificationHeaderView(contractedHeight, mockHeadsUpEB)
- val view =
- createContentView(
- isSystemExpanded = false,
- )
+ val view = createContentView(isSystemExpanded = false)
// Update all 3 child forms
view.apply {
@@ -319,12 +331,14 @@ class NotificationContentViewTest : SysuiTestCase() {
private fun createMockNotificationHeaderView(
height: Int,
- mockExpandedEB: NotificationExpandButton
+ mockExpandedEB: NotificationExpandButton,
) =
spy(NotificationHeaderView(mContext, /* attrs= */ null).apply { minimumHeight = height })
.apply {
whenever(this.animate()).thenReturn(mock())
whenever(this.findViewById<View>(R.id.expand_button)).thenReturn(mockExpandedEB)
+ whenever(this.findViewById<View>(R.id.notification_top_line))
+ .thenReturn(mock<NotificationTopLineView>())
}
@Test
@@ -344,7 +358,7 @@ class NotificationContentViewTest : SysuiTestCase() {
isSystemExpanded = false,
contractedView = mockContracted,
expandedView = mockExpanded,
- headsUpView = mockHeadsUp
+ headsUpView = mockHeadsUp,
)
view.setRemoteInputVisible(true)
@@ -373,7 +387,7 @@ class NotificationContentViewTest : SysuiTestCase() {
isSystemExpanded = false,
contractedView = mockContracted,
expandedView = mockExpanded,
- headsUpView = mockHeadsUp
+ headsUpView = mockHeadsUp,
)
view.setRemoteInputVisible(false)
@@ -635,7 +649,7 @@ class NotificationContentViewTest : SysuiTestCase() {
contractedView: View = createViewWithHeight(contractedHeight),
expandedView: View = createViewWithHeight(expandedHeight),
headsUpView: View = createViewWithHeight(contractedHeight),
- row: ExpandableNotificationRow = this.row
+ row: ExpandableNotificationRow = this.row,
): NotificationContentView {
val height = if (isSystemExpanded) expandedHeight else contractedHeight
doReturn(height).whenever(row).intrinsicHeight
@@ -647,7 +661,7 @@ class NotificationContentViewTest : SysuiTestCase() {
setHeights(
/* smallHeight= */ contractedHeight,
/* headsUpMaxHeight= */ contractedHeight,
- /* maxHeight= */ expandedHeight
+ /* maxHeight= */ expandedHeight,
)
contractedChild = contractedView
expandedChild = expandedView
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 1b447525bbf5..3d4c90140adb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerWithScenesTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerWithScenesTest.kt
@@ -67,6 +67,8 @@ import com.android.systemui.statusbar.notification.collection.provider.HighPrior
import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor
import com.android.systemui.statusbar.notification.headsup.mockHeadsUpManager
import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier
+import com.android.systemui.statusbar.notification.row.icon.appIconProvider
+import com.android.systemui.statusbar.notification.row.icon.notificationIconStyleProvider
import com.android.systemui.statusbar.notification.stack.NotificationListContainer
import com.android.systemui.statusbar.notificationLockscreenUserManager
import com.android.systemui.statusbar.policy.deviceProvisionedController
@@ -128,6 +130,8 @@ class NotificationGutsManagerWithScenesTest : SysuiTestCase() {
private val statusBarStateController = kosmos.statusBarStateController
private val headsUpManager = kosmos.mockHeadsUpManager
private val activityStarter = kosmos.activityStarter
+ private val appIconProvider = kosmos.appIconProvider
+ private val iconStyleProvider = kosmos.notificationIconStyleProvider
private val userManager = kosmos.userManager
private val activeNotificationsInteractor = kosmos.activeNotificationsInteractor
private val sceneInteractor = kosmos.sceneInteractor
@@ -174,6 +178,8 @@ class NotificationGutsManagerWithScenesTest : SysuiTestCase() {
accessibilityManager,
highPriorityProvider,
notificationManager,
+ appIconProvider,
+ iconStyleProvider,
userManager,
peopleSpaceWidgetManager,
launcherApps,
@@ -429,6 +435,8 @@ class NotificationGutsManagerWithScenesTest : SysuiTestCase() {
.bindNotification(
any<PackageManager>(),
any<INotificationManager>(),
+ eq(appIconProvider),
+ eq(iconStyleProvider),
eq(onUserInteractionCallback),
eq(channelEditorDialogController),
eq(statusBarNotification.packageName),
@@ -463,6 +471,8 @@ class NotificationGutsManagerWithScenesTest : SysuiTestCase() {
.bindNotification(
any<PackageManager>(),
any<INotificationManager>(),
+ eq(appIconProvider),
+ eq(iconStyleProvider),
eq(onUserInteractionCallback),
eq(channelEditorDialogController),
eq(statusBarNotification.packageName),
@@ -497,6 +507,8 @@ class NotificationGutsManagerWithScenesTest : SysuiTestCase() {
.bindNotification(
any<PackageManager>(),
any<INotificationManager>(),
+ eq(appIconProvider),
+ eq(iconStyleProvider),
eq(onUserInteractionCallback),
eq(channelEditorDialogController),
eq(statusBarNotification.packageName),
@@ -529,8 +541,8 @@ class NotificationGutsManagerWithScenesTest : SysuiTestCase() {
.setChannel(testNotificationChannel)
.build()
row
- } catch (e: Exception) {
- org.junit.Assert.fail()
+ } catch (_: Exception) {
+ Assert.fail()
null
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/RowImageInflaterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/RowImageInflaterTest.kt
index 86689cb88569..86689cb88569 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/RowImageInflaterTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/RowImageInflaterTest.kt
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.kt
index 31f8590c0378..31f8590c0378 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.kt
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 14a1233045bb..10886760b521 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
@@ -63,6 +63,7 @@ import com.android.systemui.SysuiTestCase;
import com.android.systemui.animation.ShadeInterpolation;
import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants;
import com.android.systemui.dock.DockManager;
+import com.android.systemui.flags.DisableSceneContainer;
import com.android.systemui.flags.EnableSceneContainer;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository;
@@ -118,10 +119,8 @@ public class ScrimControllerTest extends SysuiTestCase {
@Rule public Expect mExpect = Expect.create();
private final KosmosJavaAdapter mKosmos = new KosmosJavaAdapter(this);
- private final FakeConfigurationController mConfigurationController =
- new FakeConfigurationController();
- private final LargeScreenShadeInterpolator
- mLinearLargeScreenShadeInterpolator = new LinearLargeScreenShadeInterpolator();
+ private FakeConfigurationController mConfigurationController;
+ private LargeScreenShadeInterpolator mLinearLargeScreenShadeInterpolator;
private final TestScope mTestScope = mKosmos.getTestScope();
private final JavaAdapter mJavaAdapter = new JavaAdapter(mTestScope.getBackgroundScope());
@@ -137,6 +136,7 @@ public class ScrimControllerTest extends SysuiTestCase {
private boolean mAlwaysOnEnabled;
private TestableLooper mLooper;
private Context mContext;
+
@Mock private DozeParameters mDozeParameters;
@Mock private LightBarController mLightBarController;
@Mock private DelayedWakeLock.Factory mDelayedWakeLockFactory;
@@ -149,12 +149,11 @@ public class ScrimControllerTest extends SysuiTestCase {
@Mock private PrimaryBouncerToGoneTransitionViewModel mPrimaryBouncerToGoneTransitionViewModel;
@Mock private AlternateBouncerToGoneTransitionViewModel
mAlternateBouncerToGoneTransitionViewModel;
- private final KeyguardTransitionInteractor mKeyguardTransitionInteractor =
- mKosmos.getKeyguardTransitionInteractor();
- private final FakeKeyguardTransitionRepository mKeyguardTransitionRepository =
- mKosmos.getKeyguardTransitionRepository();
@Mock private KeyguardInteractor mKeyguardInteractor;
+ private KeyguardTransitionInteractor mKeyguardTransitionInteractor;
+ private FakeKeyguardTransitionRepository mKeyguardTransitionRepository;
+
// TODO(b/204991468): Use a real PanelExpansionStateManager object once this bug is fixed. (The
// event-dispatch-on-registration pattern caused some of these unit tests to fail.)
@Mock private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
@@ -238,6 +237,9 @@ public class ScrimControllerTest extends SysuiTestCase {
when(mContext.getColor(com.android.internal.R.color.materialColorSurface))
.thenAnswer(invocation -> mSurfaceColor);
+ mConfigurationController = new FakeConfigurationController();
+ mLinearLargeScreenShadeInterpolator = new LinearLargeScreenShadeInterpolator();
+
mScrimBehind = spy(new ScrimView(mContext));
mScrimInFront = new ScrimView(mContext);
mNotificationsScrim = new ScrimView(mContext);
@@ -270,6 +272,9 @@ public class ScrimControllerTest extends SysuiTestCase {
when(mAlternateBouncerToGoneTransitionViewModel.getScrimAlpha())
.thenReturn(emptyFlow());
+ mKeyguardTransitionRepository = mKosmos.getKeyguardTransitionRepository();
+ mKeyguardTransitionInteractor = mKosmos.getKeyguardTransitionInteractor();
+
mScrimController = new ScrimController(
mLightBarController,
mDozeParameters,
@@ -322,6 +327,7 @@ public class ScrimControllerTest extends SysuiTestCase {
}
@Test
+ @DisableSceneContainer
public void transitionToKeyguard() {
mScrimController.legacyTransitionTo(ScrimState.KEYGUARD);
finishAnimationsImmediately();
@@ -337,6 +343,7 @@ public class ScrimControllerTest extends SysuiTestCase {
}
@Test
+ @DisableSceneContainer
public void transitionToShadeLocked() {
mScrimController.legacyTransitionTo(SHADE_LOCKED);
mScrimController.setQsPosition(1f, 0);
@@ -373,6 +380,7 @@ public class ScrimControllerTest extends SysuiTestCase {
}
@Test
+ @DisableSceneContainer
public void transitionToShadeLocked_clippingQs() {
mScrimController.setClipsQsScrim(true);
mScrimController.legacyTransitionTo(SHADE_LOCKED);
@@ -391,6 +399,7 @@ public class ScrimControllerTest extends SysuiTestCase {
}
@Test
+ @DisableSceneContainer
public void transitionToOff() {
mScrimController.legacyTransitionTo(ScrimState.OFF);
finishAnimationsImmediately();
@@ -406,6 +415,7 @@ public class ScrimControllerTest extends SysuiTestCase {
}
@Test
+ @DisableSceneContainer
public void transitionToAod_withRegularWallpaper() {
mScrimController.legacyTransitionTo(ScrimState.AOD);
finishAnimationsImmediately();
@@ -421,6 +431,7 @@ public class ScrimControllerTest extends SysuiTestCase {
}
@Test
+ @DisableSceneContainer
public void transitionToAod_withFrontAlphaUpdates() {
// Assert that setting the AOD front scrim alpha doesn't take effect in a non-AOD state.
mScrimController.legacyTransitionTo(ScrimState.KEYGUARD);
@@ -465,6 +476,7 @@ public class ScrimControllerTest extends SysuiTestCase {
}
@Test
+ @DisableSceneContainer
public void transitionToAod_afterDocked_ignoresAlwaysOnAndUpdatesFrontAlpha() {
// Assert that setting the AOD front scrim alpha doesn't take effect in a non-AOD state.
mScrimController.legacyTransitionTo(ScrimState.KEYGUARD);
@@ -506,6 +518,7 @@ public class ScrimControllerTest extends SysuiTestCase {
}
@Test
+ @DisableSceneContainer
public void transitionToPulsing_withFrontAlphaUpdates() {
// Pre-condition
// Need to go to AoD first because PULSING doesn't change
@@ -551,6 +564,7 @@ public class ScrimControllerTest extends SysuiTestCase {
}
@Test
+ @DisableSceneContainer
public void transitionToKeyguardBouncer() {
mScrimController.legacyTransitionTo(BOUNCER);
finishAnimationsImmediately();
@@ -571,6 +585,7 @@ public class ScrimControllerTest extends SysuiTestCase {
}
@Test
+ @DisableSceneContainer
public void lockscreenToHubTransition_setsBehindScrimAlpha() {
// Start on lockscreen.
mScrimController.legacyTransitionTo(ScrimState.KEYGUARD);
@@ -617,6 +632,7 @@ public class ScrimControllerTest extends SysuiTestCase {
}
@Test
+ @DisableSceneContainer
public void hubToLockscreenTransition_setsViewAlpha() {
// Start on glanceable hub.
mScrimController.legacyTransitionTo(ScrimState.GLANCEABLE_HUB);
@@ -663,6 +679,7 @@ public class ScrimControllerTest extends SysuiTestCase {
}
@Test
+ @DisableSceneContainer
public void transitionToHub() {
mScrimController.setRawPanelExpansionFraction(0f);
mScrimController.setBouncerHiddenFraction(KeyguardBouncerConstants.EXPANSION_HIDDEN);
@@ -677,6 +694,7 @@ public class ScrimControllerTest extends SysuiTestCase {
}
@Test
+ @DisableSceneContainer
public void openBouncerOnHub() {
mScrimController.legacyTransitionTo(ScrimState.GLANCEABLE_HUB);
@@ -706,6 +724,7 @@ public class ScrimControllerTest extends SysuiTestCase {
}
@Test
+ @DisableSceneContainer
public void openShadeOnHub() {
mScrimController.legacyTransitionTo(ScrimState.GLANCEABLE_HUB);
@@ -734,6 +753,7 @@ public class ScrimControllerTest extends SysuiTestCase {
}
@Test
+ @DisableSceneContainer
public void transitionToHubOverDream() {
mScrimController.setRawPanelExpansionFraction(0f);
mScrimController.setBouncerHiddenFraction(KeyguardBouncerConstants.EXPANSION_HIDDEN);
@@ -748,6 +768,7 @@ public class ScrimControllerTest extends SysuiTestCase {
}
@Test
+ @DisableSceneContainer
public void openBouncerOnHubOverDream() {
mScrimController.legacyTransitionTo(ScrimState.GLANCEABLE_HUB_OVER_DREAM);
@@ -777,6 +798,7 @@ public class ScrimControllerTest extends SysuiTestCase {
}
@Test
+ @DisableSceneContainer
public void openShadeOnHubOverDream() {
mScrimController.legacyTransitionTo(ScrimState.GLANCEABLE_HUB_OVER_DREAM);
@@ -805,6 +827,7 @@ public class ScrimControllerTest extends SysuiTestCase {
}
@Test
+ @DisableSceneContainer
public void onThemeChange_bouncerBehindTint_isUpdatedToSurfaceColor() {
assertEquals(BOUNCER.getBehindTint(), 0x112233);
mSurfaceColor = 0x223344;
@@ -813,6 +836,7 @@ public class ScrimControllerTest extends SysuiTestCase {
}
@Test
+ @DisableSceneContainer
public void onThemeChangeWhileClipQsScrim_bouncerBehindTint_remainsBlack() {
mScrimController.setClipsQsScrim(true);
mScrimController.legacyTransitionTo(BOUNCER);
@@ -825,6 +849,7 @@ public class ScrimControllerTest extends SysuiTestCase {
}
@Test
+ @DisableSceneContainer
public void transitionToKeyguardBouncer_clippingQs() {
mScrimController.setClipsQsScrim(true);
mScrimController.legacyTransitionTo(BOUNCER);
@@ -845,6 +870,7 @@ public class ScrimControllerTest extends SysuiTestCase {
}
@Test
+ @DisableSceneContainer
public void disableClipQsScrimWithoutStateTransition_updatesTintAndAlpha() {
mScrimController.setClipsQsScrim(true);
mScrimController.legacyTransitionTo(BOUNCER);
@@ -867,6 +893,7 @@ public class ScrimControllerTest extends SysuiTestCase {
}
@Test
+ @DisableSceneContainer
public void enableClipQsScrimWithoutStateTransition_updatesTintAndAlpha() {
mScrimController.setClipsQsScrim(false);
mScrimController.legacyTransitionTo(BOUNCER);
@@ -889,6 +916,7 @@ public class ScrimControllerTest extends SysuiTestCase {
}
@Test
+ @DisableSceneContainer
public void transitionToBouncer() {
mScrimController.legacyTransitionTo(ScrimState.BOUNCER_SCRIMMED);
finishAnimationsImmediately();
@@ -902,6 +930,7 @@ public class ScrimControllerTest extends SysuiTestCase {
}
@Test
+ @DisableSceneContainer
public void transitionToUnlocked_clippedQs() {
mScrimController.setClipsQsScrim(true);
mScrimController.setRawPanelExpansionFraction(0f);
@@ -960,6 +989,7 @@ public class ScrimControllerTest extends SysuiTestCase {
}
@Test
+ @DisableSceneContainer
public void transitionToUnlocked_nonClippedQs_followsLargeScreensInterpolator() {
mScrimController.setClipsQsScrim(false);
mScrimController.setRawPanelExpansionFraction(0f);
@@ -999,6 +1029,7 @@ public class ScrimControllerTest extends SysuiTestCase {
}
@Test
+ @DisableSceneContainer
public void scrimStateCallback() {
mScrimController.legacyTransitionTo(ScrimState.UNLOCKED);
finishAnimationsImmediately();
@@ -1014,6 +1045,7 @@ public class ScrimControllerTest extends SysuiTestCase {
}
@Test
+ @DisableSceneContainer
public void panelExpansion() {
mScrimController.setRawPanelExpansionFraction(0f);
mScrimController.setRawPanelExpansionFraction(0.5f);
@@ -1036,6 +1068,7 @@ public class ScrimControllerTest extends SysuiTestCase {
}
@Test
+ @DisableSceneContainer
public void qsExpansion() {
reset(mScrimBehind);
mScrimController.setQsPosition(1f, 999 /* value doesn't matter */);
@@ -1048,6 +1081,7 @@ public class ScrimControllerTest extends SysuiTestCase {
}
@Test
+ @DisableSceneContainer
public void qsExpansion_clippingQs() {
reset(mScrimBehind);
mScrimController.setClipsQsScrim(true);
@@ -1061,6 +1095,7 @@ public class ScrimControllerTest extends SysuiTestCase {
}
@Test
+ @DisableSceneContainer
public void qsExpansion_half_clippingQs() {
reset(mScrimBehind);
mScrimController.setClipsQsScrim(true);
@@ -1074,6 +1109,7 @@ public class ScrimControllerTest extends SysuiTestCase {
}
@Test
+ @DisableSceneContainer
public void panelExpansionAffectsAlpha() {
mScrimController.setRawPanelExpansionFraction(0f);
mScrimController.setRawPanelExpansionFraction(0.5f);
@@ -1096,6 +1132,7 @@ public class ScrimControllerTest extends SysuiTestCase {
}
@Test
+ @DisableSceneContainer
public void transitionToUnlockedFromOff() {
// Simulate unlock with fingerprint without AOD
mScrimController.legacyTransitionTo(ScrimState.OFF);
@@ -1118,6 +1155,7 @@ public class ScrimControllerTest extends SysuiTestCase {
}
@Test
+ @DisableSceneContainer
public void transitionToUnlockedFromAod() {
// Simulate unlock with fingerprint
mScrimController.legacyTransitionTo(ScrimState.AOD);
@@ -1140,6 +1178,7 @@ public class ScrimControllerTest extends SysuiTestCase {
}
@Test
+ @DisableSceneContainer
public void scrimBlanksBeforeLeavingAod() {
// Simulate unlock with fingerprint
mScrimController.legacyTransitionTo(ScrimState.AOD);
@@ -1163,6 +1202,7 @@ public class ScrimControllerTest extends SysuiTestCase {
}
@Test
+ @DisableSceneContainer
public void scrimBlankCallbackWhenUnlockingFromPulse() {
boolean[] blanked = {false};
// Simulate unlock with fingerprint
@@ -1181,6 +1221,7 @@ public class ScrimControllerTest extends SysuiTestCase {
}
@Test
+ @DisableSceneContainer
public void blankingNotRequired_leavingAoD() {
// GIVEN display does NOT need blanking
when(mDozeParameters.getDisplayNeedsBlanking()).thenReturn(false);
@@ -1236,6 +1277,7 @@ public class ScrimControllerTest extends SysuiTestCase {
}
@Test
+ @DisableSceneContainer
public void testScrimCallback() {
int[] callOrder = {0, 0, 0};
int[] currentCall = {0};
@@ -1262,12 +1304,14 @@ public class ScrimControllerTest extends SysuiTestCase {
}
@Test
+ @DisableSceneContainer
public void testScrimCallbacksWithoutAmbientDisplay() {
mAlwaysOnEnabled = false;
testScrimCallback();
}
@Test
+ @DisableSceneContainer
public void testScrimCallbackCancelled() {
boolean[] cancelledCalled = {false};
mScrimController.legacyTransitionTo(ScrimState.AOD, new ScrimController.Callback() {
@@ -1281,6 +1325,7 @@ public class ScrimControllerTest extends SysuiTestCase {
}
@Test
+ @DisableSceneContainer
public void testHoldsWakeLock_whenAOD() {
mScrimController.legacyTransitionTo(ScrimState.AOD);
verify(mWakeLock).acquire(anyString());
@@ -1290,6 +1335,7 @@ public class ScrimControllerTest extends SysuiTestCase {
}
@Test
+ @DisableSceneContainer
public void testDoesNotHoldWakeLock_whenUnlocking() {
mScrimController.legacyTransitionTo(ScrimState.UNLOCKED);
finishAnimationsImmediately();
@@ -1297,6 +1343,7 @@ public class ScrimControllerTest extends SysuiTestCase {
}
@Test
+ @DisableSceneContainer
public void testCallbackInvokedOnSameStateTransition() {
mScrimController.legacyTransitionTo(ScrimState.UNLOCKED);
finishAnimationsImmediately();
@@ -1306,6 +1353,7 @@ public class ScrimControllerTest extends SysuiTestCase {
}
@Test
+ @DisableSceneContainer
public void testConservesExpansionOpacityAfterTransition() {
mScrimController.legacyTransitionTo(ScrimState.UNLOCKED);
mScrimController.setRawPanelExpansionFraction(0.5f);
@@ -1323,6 +1371,7 @@ public class ScrimControllerTest extends SysuiTestCase {
}
@Test
+ @DisableSceneContainer
public void testCancelsOldAnimationBeforeBlanking() {
mScrimController.legacyTransitionTo(ScrimState.AOD);
finishAnimationsImmediately();
@@ -1335,6 +1384,7 @@ public class ScrimControllerTest extends SysuiTestCase {
}
@Test
+ @DisableSceneContainer
public void testScrimsAreNotFocusable() {
assertFalse("Behind scrim should not be focusable", mScrimBehind.isFocusable());
assertFalse("Front scrim should not be focusable", mScrimInFront.isFocusable());
@@ -1343,6 +1393,7 @@ public class ScrimControllerTest extends SysuiTestCase {
}
@Test
+ @DisableSceneContainer
public void testEatsTouchEvent() {
HashSet<ScrimState> eatsTouches =
new HashSet<>(Collections.singletonList(ScrimState.AOD));
@@ -1359,6 +1410,7 @@ public class ScrimControllerTest extends SysuiTestCase {
}
@Test
+ @DisableSceneContainer
public void testAnimatesTransitionToAod() {
when(mDozeParameters.shouldControlScreenOff()).thenReturn(false);
ScrimState.AOD.prepare(ScrimState.KEYGUARD);
@@ -1373,6 +1425,7 @@ public class ScrimControllerTest extends SysuiTestCase {
}
@Test
+ @DisableSceneContainer
public void testIsLowPowerMode() {
HashSet<ScrimState> lowPowerModeStates = new HashSet<>(Arrays.asList(
ScrimState.OFF, ScrimState.AOD, ScrimState.PULSING));
@@ -1390,6 +1443,7 @@ public class ScrimControllerTest extends SysuiTestCase {
}
@Test
+ @DisableSceneContainer
public void testScrimsOpaque_whenShadeFullyExpanded() {
mScrimController.legacyTransitionTo(ScrimState.UNLOCKED);
mScrimController.setRawPanelExpansionFraction(1);
@@ -1404,6 +1458,7 @@ public class ScrimControllerTest extends SysuiTestCase {
}
@Test
+ @DisableSceneContainer
public void testScrimsVisible_whenShadeVisible() {
mScrimController.setClipsQsScrim(true);
mScrimController.legacyTransitionTo(ScrimState.UNLOCKED);
@@ -1419,6 +1474,7 @@ public class ScrimControllerTest extends SysuiTestCase {
}
@Test
+ @DisableSceneContainer
public void testDoesntAnimate_whenUnlocking() {
// LightRevealScrim will animate the transition, we should only hide the keyguard scrims.
ScrimState.UNLOCKED.prepare(ScrimState.KEYGUARD);
@@ -1439,6 +1495,7 @@ public class ScrimControllerTest extends SysuiTestCase {
}
@Test
+ @DisableSceneContainer
public void testScrimsVisible_whenShadeVisible_clippingQs() {
mScrimController.setClipsQsScrim(true);
mScrimController.legacyTransitionTo(ScrimState.UNLOCKED);
@@ -1454,6 +1511,7 @@ public class ScrimControllerTest extends SysuiTestCase {
}
@Test
+ @DisableSceneContainer
public void testScrimsVisible_whenShadeVisibleOnLockscreen() {
mScrimController.legacyTransitionTo(ScrimState.KEYGUARD);
mScrimController.setQsPosition(0.25f, 300);
@@ -1465,6 +1523,7 @@ public class ScrimControllerTest extends SysuiTestCase {
}
@Test
+ @DisableSceneContainer
public void testNotificationScrimTransparent_whenOnLockscreen() {
mScrimController.legacyTransitionTo(ScrimState.KEYGUARD);
// even if shade is not pulled down, panel has expansion of 1 on the lockscreen
@@ -1477,6 +1536,7 @@ public class ScrimControllerTest extends SysuiTestCase {
}
@Test
+ @DisableSceneContainer
public void testNotificationScrimVisible_afterOpeningShadeFromLockscreen() {
mScrimController.setRawPanelExpansionFraction(1);
mScrimController.legacyTransitionTo(SHADE_LOCKED);
@@ -1488,6 +1548,7 @@ public class ScrimControllerTest extends SysuiTestCase {
}
@Test
+ @DisableSceneContainer
public void qsExpansion_BehindTint_shadeLocked_bouncerActive_usesBouncerProgress() {
when(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit()).thenReturn(true);
// clipping doesn't change tested logic but allows to assert scrims more in line with
@@ -1504,6 +1565,7 @@ public class ScrimControllerTest extends SysuiTestCase {
}
@Test
+ @DisableSceneContainer
public void expansionNotificationAlpha_shadeLocked_bouncerActive_usesBouncerInterpolator() {
when(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit()).thenReturn(true);
@@ -1520,6 +1582,7 @@ public class ScrimControllerTest extends SysuiTestCase {
}
@Test
+ @DisableSceneContainer
public void expansionNotificationAlpha_shadeLocked_bouncerNotActive_usesShadeInterpolator() {
when(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit()).thenReturn(false);
@@ -1535,6 +1598,7 @@ public class ScrimControllerTest extends SysuiTestCase {
}
@Test
+ @DisableSceneContainer
public void notificationAlpha_unnocclusionAnimating_bouncerNotActive_usesKeyguardNotifAlpha() {
when(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit()).thenReturn(false);
@@ -1554,6 +1618,7 @@ public class ScrimControllerTest extends SysuiTestCase {
}
@Test
+ @DisableSceneContainer
public void notificationAlpha_inKeyguardState_bouncerActive_usesInvertedBouncerInterpolator() {
when(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit()).thenReturn(true);
mScrimController.setClipsQsScrim(true);
@@ -1574,6 +1639,7 @@ public class ScrimControllerTest extends SysuiTestCase {
}
@Test
+ @DisableSceneContainer
public void notificationAlpha_inKeyguardState_bouncerNotActive_usesInvertedShadeInterpolator() {
when(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit()).thenReturn(false);
mScrimController.setClipsQsScrim(true);
@@ -1594,6 +1660,7 @@ public class ScrimControllerTest extends SysuiTestCase {
}
@Test
+ @DisableSceneContainer
public void behindTint_inKeyguardState_bouncerNotActive_usesKeyguardBehindTint() {
when(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit()).thenReturn(false);
mScrimController.setClipsQsScrim(false);
@@ -1605,6 +1672,7 @@ public class ScrimControllerTest extends SysuiTestCase {
}
@Test
+ @DisableSceneContainer
public void testNotificationTransparency_followsTransitionToFullShade() {
mScrimController.setClipsQsScrim(true);
@@ -1646,6 +1714,7 @@ public class ScrimControllerTest extends SysuiTestCase {
}
@Test
+ @DisableSceneContainer
public void notificationTransparency_followsNotificationScrimProgress() {
mScrimController.legacyTransitionTo(SHADE_LOCKED);
mScrimController.setRawPanelExpansionFraction(1.0f);
@@ -1662,6 +1731,7 @@ public class ScrimControllerTest extends SysuiTestCase {
}
@Test
+ @DisableSceneContainer
public void notificationAlpha_qsNotClipped_alphaMatchesNotificationExpansionProgress() {
mScrimController.setClipsQsScrim(false);
mScrimController.legacyTransitionTo(ScrimState.KEYGUARD);
@@ -1697,6 +1767,7 @@ public class ScrimControllerTest extends SysuiTestCase {
}
@Test
+ @DisableSceneContainer
public void setNotificationsOverScrollAmount_setsTranslationYOnNotificationsScrim() {
int overScrollAmount = 10;
@@ -1706,6 +1777,7 @@ public class ScrimControllerTest extends SysuiTestCase {
}
@Test
+ @DisableSceneContainer
public void setNotificationsOverScrollAmount_doesNotSetTranslationYOnBehindScrim() {
int overScrollAmount = 10;
@@ -1715,6 +1787,7 @@ public class ScrimControllerTest extends SysuiTestCase {
}
@Test
+ @DisableSceneContainer
public void setNotificationsOverScrollAmount_doesNotSetTranslationYOnFrontScrim() {
int overScrollAmount = 10;
@@ -1724,6 +1797,7 @@ public class ScrimControllerTest extends SysuiTestCase {
}
@Test
+ @DisableSceneContainer
public void notificationBoundsTopGetsPassedToKeyguard() {
mScrimController.legacyTransitionTo(SHADE_LOCKED);
mScrimController.setQsPosition(1f, 0);
@@ -1734,6 +1808,7 @@ public class ScrimControllerTest extends SysuiTestCase {
}
@Test
+ @DisableSceneContainer
public void notificationBoundsTopDoesNotGetPassedToKeyguardWhenNotifScrimIsNotVisible() {
mScrimController.setKeyguardOccluded(true);
mScrimController.legacyTransitionTo(ScrimState.KEYGUARD);
@@ -1744,6 +1819,7 @@ public class ScrimControllerTest extends SysuiTestCase {
}
@Test
+ @DisableSceneContainer
public void transitionToDreaming() {
mScrimController.setRawPanelExpansionFraction(0f);
mScrimController.setBouncerHiddenFraction(KeyguardBouncerConstants.EXPANSION_HIDDEN);
@@ -1763,6 +1839,7 @@ public class ScrimControllerTest extends SysuiTestCase {
}
@Test
+ @DisableSceneContainer
public void keyguardGoingAwayUpdateScrims() {
when(mKeyguardStateController.isKeyguardGoingAway()).thenReturn(true);
mScrimController.updateScrims();
@@ -1772,6 +1849,7 @@ public class ScrimControllerTest extends SysuiTestCase {
@Test
+ @DisableSceneContainer
public void setUnOccludingAnimationKeyguard() {
mScrimController.legacyTransitionTo(ScrimState.KEYGUARD);
finishAnimationsImmediately();
@@ -1786,6 +1864,7 @@ public class ScrimControllerTest extends SysuiTestCase {
}
@Test
+ @DisableSceneContainer
public void testHidesScrimFlickerInActivity() {
mScrimController.setKeyguardOccluded(true);
mScrimController.legacyTransitionTo(ScrimState.KEYGUARD);
@@ -1804,6 +1883,7 @@ public class ScrimControllerTest extends SysuiTestCase {
}
@Test
+ @DisableSceneContainer
public void notificationAlpha_inKeyguardState_bouncerNotActive_clipsQsScrimFalse() {
mScrimController.setClipsQsScrim(false);
mScrimController.legacyTransitionTo(ScrimState.KEYGUARD);
@@ -1813,6 +1893,7 @@ public class ScrimControllerTest extends SysuiTestCase {
}
@Test
+ @DisableSceneContainer
public void aodStateSetsFrontScrimToNotBlend() {
mScrimController.legacyTransitionTo(ScrimState.AOD);
assertFalse("Front scrim should not blend with main color",
@@ -1820,6 +1901,7 @@ public class ScrimControllerTest extends SysuiTestCase {
}
@Test
+ @DisableSceneContainer
public void applyState_unlocked_bouncerShowing() {
mScrimController.legacyTransitionTo(ScrimState.UNLOCKED);
mScrimController.setBouncerHiddenFraction(0.99f);
@@ -1829,6 +1911,7 @@ public class ScrimControllerTest extends SysuiTestCase {
}
@Test
+ @DisableSceneContainer
public void ignoreTransitionRequestWhileKeyguardTransitionRunning() {
mScrimController.legacyTransitionTo(ScrimState.UNLOCKED);
mScrimController.mBouncerToGoneTransition.accept(
@@ -1841,6 +1924,7 @@ public class ScrimControllerTest extends SysuiTestCase {
}
@Test
+ @DisableSceneContainer
public void primaryBouncerToGoneOnFinishCallsKeyguardFadedAway() {
when(mKeyguardStateController.isKeyguardFadingAway()).thenReturn(true);
mScrimController.mBouncerToGoneTransition.accept(
@@ -1851,6 +1935,7 @@ public class ScrimControllerTest extends SysuiTestCase {
}
@Test
+ @DisableSceneContainer
public void primaryBouncerToGoneOnFinishCallsLightBarController() {
reset(mLightBarController);
mScrimController.mBouncerToGoneTransition.accept(
@@ -1862,6 +1947,7 @@ public class ScrimControllerTest extends SysuiTestCase {
}
@Test
+ @DisableSceneContainer
public void testDoNotAnimateChangeIfOccludeAnimationPlaying() {
mScrimController.setOccludeAnimationPlaying(true);
mScrimController.legacyTransitionTo(ScrimState.UNLOCKED);
@@ -1870,6 +1956,7 @@ public class ScrimControllerTest extends SysuiTestCase {
}
@Test
+ @DisableSceneContainer
public void testNotifScrimAlpha_1f_afterUnlockFinishedAndExpanded() {
mScrimController.legacyTransitionTo(ScrimState.KEYGUARD);
when(mKeyguardUnlockAnimationController.isPlayingCannedUnlockAnimation()).thenReturn(true);
@@ -1942,9 +2029,9 @@ public class ScrimControllerTest extends SysuiTestCase {
// Check combined scrim visibility.
final int visibility;
- if (scrimToAlpha.values().contains(OPAQUE)) {
+ if (scrimToAlpha.containsValue(OPAQUE)) {
visibility = OPAQUE;
- } else if (scrimToAlpha.values().contains(SEMI_TRANSPARENT)) {
+ } else if (scrimToAlpha.containsValue(SEMI_TRANSPARENT)) {
visibility = SEMI_TRANSPARENT;
} else {
visibility = TRANSPARENT;
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
index dde6e2ee1866..dde6e2ee1866 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
index df5c6e916931..a51e919636ee 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
@@ -129,11 +129,7 @@ class MobileConnectionRepositoryTest : SysuiTestCase() {
@Mock private lateinit var context: Context
private val mobileMappings = FakeMobileMappingsProxy()
- private val systemUiCarrierConfig =
- SystemUiCarrierConfig(
- SUB_1_ID,
- createTestConfig(),
- )
+ private val systemUiCarrierConfig = SystemUiCarrierConfig(SUB_1_ID, createTestConfig())
private val testDispatcher = UnconfinedTestDispatcher()
private val testScope = TestScope(testDispatcher)
@@ -902,11 +898,7 @@ class MobileConnectionRepositoryTest : SysuiTestCase() {
assertThat(latest).isEqualTo(NetworkNameModel.IntentDerived("$PLMN$SEP$DATA_SPN"))
- val intentWithoutInfo =
- spnIntent(
- showSpn = false,
- showPlmn = false,
- )
+ val intentWithoutInfo = spnIntent(showSpn = false, showPlmn = false)
captor.lastValue.onReceive(context, intentWithoutInfo)
@@ -929,11 +921,7 @@ class MobileConnectionRepositoryTest : SysuiTestCase() {
assertThat(latest).isEqualTo(NetworkNameModel.IntentDerived("$PLMN$SEP$DATA_SPN"))
- val intentWithoutInfo =
- spnIntent(
- showSpn = false,
- showPlmn = false,
- )
+ val intentWithoutInfo = spnIntent(showSpn = false, showPlmn = false)
captor.lastValue.onReceive(context, intentWithoutInfo)
@@ -1301,7 +1289,6 @@ class MobileConnectionRepositoryTest : SysuiTestCase() {
}
@Test
- @EnableFlags(com.android.internal.telephony.flags.Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG)
fun isNonTerrestrial_updatesFromCallback0() =
testScope.runTest {
val latest by collectLastValue(underTest.isNonTerrestrial)
@@ -1430,10 +1417,7 @@ class MobileConnectionRepositoryTest : SysuiTestCase() {
return MobileTelephonyHelpers.getTelephonyCallbackForType(telephonyManager)
}
- private fun carrierIdIntent(
- subId: Int = SUB_1_ID,
- carrierId: Int,
- ): Intent =
+ private fun carrierIdIntent(subId: Int = SUB_1_ID, carrierId: Int): Intent =
Intent(TelephonyManager.ACTION_SUBSCRIPTION_CARRIER_IDENTITY_CHANGED).apply {
putExtra(EXTRA_SUBSCRIPTION_ID, subId)
putExtra(EXTRA_CARRIER_ID, carrierId)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/temporarydisplay/TouchableRegionViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TouchableRegionViewControllerTest.kt
index a230f0630d6e..a230f0630d6e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/temporarydisplay/TouchableRegionViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TouchableRegionViewControllerTest.kt
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/FoldAodAnimationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/FoldAodAnimationControllerTest.kt
index 1135a5f86952..1135a5f86952 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/FoldAodAnimationControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/FoldAodAnimationControllerTest.kt
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/UnfoldLatencyTrackerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldLatencyTrackerTest.kt
index a1122c3cbcd2..a1122c3cbcd2 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/UnfoldLatencyTrackerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldLatencyTrackerTest.kt
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProviderTest.kt
index c7b685fba455..c7b685fba455 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProviderTest.kt
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/progress/UnfoldRemoteFilterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/UnfoldRemoteFilterTest.kt
index b93c161a7039..b93c161a7039 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/progress/UnfoldRemoteFilterTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/UnfoldRemoteFilterTest.kt
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/usb/UsbPermissionActivityTest.kt b/packages/SystemUI/tests/src/com/android/systemui/usb/UsbPermissionActivityTest.kt
index 32c598612aa6..32c598612aa6 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/usb/UsbPermissionActivityTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/usb/UsbPermissionActivityTest.kt
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/user/CreateUserActivityTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/CreateUserActivityTest.kt
index 25ceea951d3c..25ceea951d3c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/user/CreateUserActivityTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/CreateUserActivityTest.kt
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/util/kotlin/FlowUtilTests.kt b/packages/SystemUI/tests/src/com/android/systemui/util/kotlin/FlowUtilTests.kt
index 9440280649dd..9440280649dd 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/util/kotlin/FlowUtilTests.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/kotlin/FlowUtilTests.kt
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/util/kotlin/PackageManagerExtComponentEnabledTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/kotlin/PackageManagerExtComponentEnabledTest.kt
index b3f2113f86ec..b3f2113f86ec 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/util/kotlin/PackageManagerExtComponentEnabledTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/kotlin/PackageManagerExtComponentEnabledTest.kt
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/util/sensors/AsyncSensorManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/AsyncSensorManagerTest.java
index c896fc0bfb8a..c896fc0bfb8a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/util/sensors/AsyncSensorManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/AsyncSensorManagerTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
index 8d05ea16cfa6..44392420da49 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
@@ -465,9 +465,9 @@ public class VolumeDialogImplTest extends SysuiTestCase {
public void notificationVolumeSeparated_theRingerIconChangesToSpeakerIcon() {
// already separated. assert icon is new based on res id
assertEquals(mDialog.mVolumeRingerIconDrawableId,
- R.drawable.ic_speaker_on);
+ R.drawable.ic_legacy_speaker_on);
assertEquals(mDialog.mVolumeRingerMuteIconDrawableId,
- R.drawable.ic_speaker_mute);
+ R.drawable.ic_legacy_speaker_mute);
}
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/wallpapers/GradientColorWallpaperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/wallpapers/GradientColorWallpaperTest.kt
index b4fbaad6ab37..b4fbaad6ab37 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/wallpapers/GradientColorWallpaperTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/wallpapers/GradientColorWallpaperTest.kt
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/activity/data/repository/ActivityManagerRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/activity/data/repository/ActivityManagerRepositoryKosmos.kt
index a6e71333c816..5dc28bea9bc4 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/activity/data/repository/ActivityManagerRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/activity/data/repository/ActivityManagerRepositoryKosmos.kt
@@ -17,33 +17,77 @@
package com.android.systemui.activity.data.repository
import android.app.activityManager
+import com.android.systemui.activity.data.model.AppVisibilityModel
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.log.core.Logger
+import com.android.systemui.util.time.SystemClock
+import com.android.systemui.util.time.fakeSystemClock
+import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
-val Kosmos.activityManagerRepository by Kosmos.Fixture { FakeActivityManagerRepository() }
+val Kosmos.activityManagerRepository by
+ Kosmos.Fixture { FakeActivityManagerRepository(fakeSystemClock) }
val Kosmos.realActivityManagerRepository by
- Kosmos.Fixture { ActivityManagerRepositoryImpl(testDispatcher, activityManager) }
+ Kosmos.Fixture {
+ ActivityManagerRepositoryImpl(testDispatcher, fakeSystemClock, activityManager)
+ }
-class FakeActivityManagerRepository : ActivityManagerRepository {
- private val uidFlows = mutableMapOf<Int, MutableList<MutableStateFlow<Boolean>>>()
+class FakeActivityManagerRepository(private val systemClock: SystemClock) :
+ ActivityManagerRepository {
+ private val isVisibleFlows = mutableMapOf<Int, MutableList<MutableStateFlow<Boolean>>>()
+ private val appVisibilityFlows =
+ mutableMapOf<Int, MutableList<MutableStateFlow<AppVisibilityModel>>>()
var startingIsAppVisibleValue = false
+ override fun createAppVisibilityFlow(
+ creationUid: Int,
+ logger: Logger,
+ identifyingLogTag: String,
+ ): Flow<AppVisibilityModel> {
+ val newFlow =
+ MutableStateFlow(
+ if (startingIsAppVisibleValue) {
+ AppVisibilityModel(
+ isAppCurrentlyVisible = true,
+ lastAppVisibleTime = systemClock.currentTimeMillis(),
+ )
+ } else {
+ AppVisibilityModel(isAppCurrentlyVisible = false, lastAppVisibleTime = null)
+ }
+ )
+ appVisibilityFlows.computeIfAbsent(creationUid) { mutableListOf() }.add(newFlow)
+ return newFlow
+ }
+
override fun createIsAppVisibleFlow(
creationUid: Int,
logger: Logger,
identifyingLogTag: String,
): MutableStateFlow<Boolean> {
val newFlow = MutableStateFlow(startingIsAppVisibleValue)
- uidFlows.computeIfAbsent(creationUid) { mutableListOf() }.add(newFlow)
+ isVisibleFlows.computeIfAbsent(creationUid) { mutableListOf() }.add(newFlow)
return newFlow
}
fun setIsAppVisible(uid: Int, isAppVisible: Boolean) {
- uidFlows[uid]?.forEach { stateFlow -> stateFlow.value = isAppVisible }
+ isVisibleFlows[uid]?.forEach { stateFlow -> stateFlow.value = isAppVisible }
+ appVisibilityFlows[uid]?.forEach { stateFlow ->
+ stateFlow.value =
+ if (isAppVisible) {
+ AppVisibilityModel(
+ isAppCurrentlyVisible = true,
+ lastAppVisibleTime = systemClock.currentTimeMillis(),
+ )
+ } else {
+ AppVisibilityModel(
+ isAppCurrentlyVisible = false,
+ stateFlow.value.lastAppVisibleTime,
+ )
+ }
+ }
}
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractorKosmos.kt
index 4068a2290559..b781f61723f2 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractorKosmos.kt
@@ -20,7 +20,7 @@ import com.android.systemui.keyguard.data.repository.keyguardClockRepository
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.media.controls.domain.pipeline.interactor.mediaCarouselInteractor
-import com.android.systemui.shade.domain.interactor.shadeInteractor
+import com.android.systemui.shade.domain.interactor.shadeModeInteractor
import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor
import com.android.systemui.statusbar.notification.promoted.domain.interactor.aodPromotedNotificationInteractor
import com.android.systemui.statusbar.notification.stack.domain.interactor.headsUpNotificationInteractor
@@ -32,7 +32,7 @@ val Kosmos.keyguardClockInteractor by
mediaCarouselInteractor = mediaCarouselInteractor,
activeNotificationsInteractor = activeNotificationsInteractor,
aodPromotedNotificationInteractor = aodPromotedNotificationInteractor,
- shadeInteractor = shadeInteractor,
+ shadeModeInteractor = shadeModeInteractor,
keyguardInteractor = keyguardInteractor,
keyguardTransitionInteractor = keyguardTransitionInteractor,
headsUpNotificationInteractor = headsUpNotificationInteractor,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelKosmos.kt
index c0b39b1df7d5..5dc19a340dd0 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelKosmos.kt
@@ -22,7 +22,7 @@ import com.android.systemui.common.ui.domain.interactor.configurationInteractor
import com.android.systemui.keyguard.domain.interactor.keyguardClockInteractor
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
-import com.android.systemui.shade.domain.interactor.shadeInteractor
+import com.android.systemui.shade.domain.interactor.shadeModeInteractor
import com.android.systemui.statusbar.notification.icon.ui.viewmodel.notificationIconContainerAlwaysOnDisplayViewModel
import com.android.systemui.statusbar.ui.systemBarUtilsProxy
@@ -33,7 +33,7 @@ val Kosmos.keyguardClockViewModel by
keyguardClockInteractor = keyguardClockInteractor,
applicationScope = applicationCoroutineScope,
aodNotificationIconViewModel = notificationIconContainerAlwaysOnDisplayViewModel,
- shadeInteractor = shadeInteractor,
+ shadeModeInteractor = shadeModeInteractor,
systemBarUtils = systemBarUtilsProxy,
configurationInteractor = configurationInteractor,
resources = mainResources,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/model/FakeSceneDataSource.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/model/FakeSceneDataSource.kt
index 60c0f342b874..f9917ac680e0 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/model/FakeSceneDataSource.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/model/FakeSceneDataSource.kt
@@ -44,6 +44,8 @@ class FakeSceneDataSource(initialSceneKey: SceneKey, val testScope: TestScope) :
var pendingOverlays: Set<OverlayKey>? = null
private set
+ var freezeAndAnimateToCurrentStateCallCount = 0
+
override fun changeScene(toScene: SceneKey, transitionKey: TransitionKey?) {
if (_isPaused) {
_pendingScene = toScene
@@ -85,6 +87,10 @@ class FakeSceneDataSource(initialSceneKey: SceneKey, val testScope: TestScope) :
hideOverlay(overlay)
}
+ override fun freezeAndAnimateToCurrentState() {
+ freezeAndAnimateToCurrentStateCallCount++
+ }
+
/**
* Pauses scene and overlay changes.
*
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeDisplayChangeLatencyTrackerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeDisplayChangeLatencyTrackerKosmos.kt
index 67dd0ad896d5..0892e66b8b86 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeDisplayChangeLatencyTrackerKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeDisplayChangeLatencyTrackerKosmos.kt
@@ -27,7 +27,7 @@ import java.util.Optional
val Kosmos.shadeDisplayChangeLatencyTracker by Fixture {
ShadeDisplayChangeLatencyTracker(
- Optional.of(mockShadeRootView),
+ mockShadeRootView,
configurationRepository,
latencyTracker,
testScope.backgroundScope,
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 ba7557ef7f71..26a441bc8ca3 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
@@ -30,7 +30,6 @@ import com.android.systemui.shade.display.ShadeExpansionIntent
import com.android.systemui.shade.display.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.util.settings.fakeGlobalSettings
val Kosmos.defaultShadeDisplayPolicy: DefaultDisplayShadePolicy by
@@ -49,9 +48,8 @@ val Kosmos.statusBarTouchShadeDisplayPolicy: StatusBarTouchShadeDisplayPolicy by
StatusBarTouchShadeDisplayPolicy(
displayRepository = displayRepository,
backgroundScope = testScope.backgroundScope,
- shadeInteractor = { shadeInteractor },
- notificationElement = { notificationElement },
qsShadeElement = { qsElement },
+ notificationElement = { notificationElement },
)
}
val Kosmos.shadeExpansionIntent: ShadeExpansionIntent by
@@ -65,6 +63,7 @@ val Kosmos.shadeDisplaysRepository: ShadeDisplaysRepository by
defaultPolicy = defaultShadeDisplayPolicy,
shadeOnDefaultDisplayWhenLocked = true,
keyguardRepository = keyguardRepository,
+ displayRepository = displayRepository,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractorKosmos.kt
index 46314135c574..1397d974cbc5 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractorKosmos.kt
@@ -29,7 +29,6 @@ import com.android.systemui.statusbar.notification.domain.interactor.activeNotif
import com.android.systemui.statusbar.notification.row.notificationRebindingTracker
import com.android.systemui.statusbar.notification.stack.notificationStackRebindingHider
import com.android.systemui.statusbar.policy.configurationController
-import java.util.Optional
import org.mockito.kotlin.any
import org.mockito.kotlin.mock
import org.mockito.kotlin.whenever
@@ -55,11 +54,11 @@ val Kosmos.shadeDisplaysInteractor by
testScope.backgroundScope,
testScope.backgroundScope.coroutineContext,
mockedShadeDisplayChangeLatencyTracker,
- Optional.of(shadeExpandedStateInteractor),
+ shadeExpandedStateInteractor,
shadeExpansionIntent,
activeNotificationsInteractor,
notificationRebindingTracker,
- Optional.of(notificationStackRebindingHider),
+ notificationStackRebindingHider,
configurationController,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/NotificationsShadeOverlayContentViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/NotificationsShadeOverlayContentViewModelKosmos.kt
index 20e4523fda0f..55e35f2b2703 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/NotificationsShadeOverlayContentViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/NotificationsShadeOverlayContentViewModelKosmos.kt
@@ -18,9 +18,11 @@ package com.android.systemui.shade.ui.viewmodel
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.media.controls.domain.pipeline.interactor.mediaCarouselInteractor
import com.android.systemui.notifications.ui.viewmodel.NotificationsShadeOverlayContentViewModel
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.shade.domain.interactor.shadeInteractor
+import com.android.systemui.statusbar.disableflags.domain.interactor.disableFlagsInteractor
import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.notificationsPlaceholderViewModelFactory
@@ -31,6 +33,8 @@ val Kosmos.notificationsShadeOverlayContentViewModel:
notificationsPlaceholderViewModelFactory = notificationsPlaceholderViewModelFactory,
sceneInteractor = sceneInteractor,
shadeInteractor = shadeInteractor,
+ disableFlagsInteractor = disableFlagsInteractor,
+ mediaCarouselInteractor = mediaCarouselInteractor,
activeNotificationsInteractor = activeNotificationsInteractor,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelKosmos.kt
index 878c2deb43b2..d8e0cfe4fbf8 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelKosmos.kt
@@ -21,6 +21,7 @@ import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.statusbar.chips.notification.domain.interactor.statusBarNotificationChipsInteractor
import com.android.systemui.statusbar.notification.stack.domain.interactor.headsUpNotificationInteractor
+import com.android.systemui.util.time.fakeSystemClock
val Kosmos.notifChipsViewModel: NotifChipsViewModel by
Kosmos.Fixture {
@@ -29,5 +30,6 @@ val Kosmos.notifChipsViewModel: NotifChipsViewModel by
applicationCoroutineScope,
statusBarNotificationChipsInteractor,
headsUpNotificationInteractor,
+ fakeSystemClock,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/featurepods/popups/ui/viewmodel/StatusBarPopupChipsViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/featurepods/popups/ui/viewmodel/StatusBarPopupChipsViewModelKosmos.kt
index 93502f365202..b876095fefe5 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/featurepods/popups/ui/viewmodel/StatusBarPopupChipsViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/featurepods/popups/ui/viewmodel/StatusBarPopupChipsViewModelKosmos.kt
@@ -17,13 +17,14 @@
package com.android.systemui.statusbar.featurepods.popups.ui.viewmodel
import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.kosmos.testScope
import com.android.systemui.statusbar.featurepods.media.ui.viewmodel.mediaControlChipViewModel
-val Kosmos.statusBarPopupChipsViewModel: StatusBarPopupChipsViewModel by
+private val Kosmos.statusBarPopupChipsViewModel: StatusBarPopupChipsViewModel by
+ Kosmos.Fixture { StatusBarPopupChipsViewModel(mediaControlChip = mediaControlChipViewModel) }
+
+val Kosmos.statusBarPopupChipsViewModelFactory by
Kosmos.Fixture {
- StatusBarPopupChipsViewModel(
- testScope.backgroundScope,
- mediaControlChipViewModel = mediaControlChipViewModel,
- )
+ object : StatusBarPopupChipsViewModel.Factory {
+ override fun create(): StatusBarPopupChipsViewModel = statusBarPopupChipsViewModel
+ }
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/NotificationEntryBuilderKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/NotificationEntryBuilderKosmos.kt
new file mode 100644
index 000000000000..59f5ecd2563f
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/NotificationEntryBuilderKosmos.kt
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification
+
+import android.app.Notification
+import android.app.PendingIntent
+import android.app.Person
+import android.content.Intent
+import android.content.applicationContext
+import android.graphics.drawable.Icon
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
+import com.android.systemui.statusbar.notification.icon.IconPack
+import com.android.systemui.statusbar.notification.promoted.setPromotedContent
+import org.mockito.kotlin.mock
+
+fun Kosmos.setIconPackWithMockIconViews(entry: NotificationEntry) {
+ entry.icons =
+ IconPack.buildPack(
+ /* statusBarIcon = */ mock(),
+ /* statusBarChipIcon = */ mock(),
+ /* shelfIcon = */ mock(),
+ /* aodIcon = */ mock(),
+ /* source = */ null,
+ )
+}
+
+fun Kosmos.buildOngoingCallEntry(
+ promoted: Boolean = false,
+ block: NotificationEntryBuilder.() -> Unit = {},
+): NotificationEntry =
+ buildNotificationEntry(
+ tag = "call",
+ promoted = promoted,
+ style = makeOngoingCallStyle(),
+ block = block,
+ )
+
+fun Kosmos.buildPromotedOngoingEntry(
+ block: NotificationEntryBuilder.() -> Unit = {}
+): NotificationEntry =
+ buildNotificationEntry(tag = "ron", promoted = true, style = null, block = block)
+
+fun Kosmos.buildNotificationEntry(
+ tag: String? = null,
+ promoted: Boolean = false,
+ style: Notification.Style? = null,
+ block: NotificationEntryBuilder.() -> Unit = {},
+): NotificationEntry =
+ NotificationEntryBuilder()
+ .apply {
+ setTag(tag)
+ setFlag(applicationContext, Notification.FLAG_PROMOTED_ONGOING, promoted)
+ modifyNotification(applicationContext)
+ .setSmallIcon(Icon.createWithContentUri("content://null"))
+ .setStyle(style)
+ }
+ .apply(block)
+ .build()
+ .also {
+ setIconPackWithMockIconViews(it)
+ if (promoted) setPromotedContent(it)
+ }
+
+private fun Kosmos.makeOngoingCallStyle(): Notification.CallStyle {
+ val pendingIntent =
+ PendingIntent.getBroadcast(
+ applicationContext,
+ 0,
+ Intent("action"),
+ PendingIntent.FLAG_IMMUTABLE,
+ )
+ val person = Person.Builder().setName("person").build()
+ return Notification.CallStyle.forOngoingCall(person, pendingIntent)
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/NotifPipelineKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/NotifPipelineKosmos.kt
index a48b27015c02..fa3702cea5ee 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/NotifPipelineKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/NotifPipelineKosmos.kt
@@ -17,6 +17,7 @@
package com.android.systemui.statusbar.notification.collection
import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.util.mockito.mock
+import org.mockito.kotlin.mock
-var Kosmos.notifPipeline by Kosmos.Fixture { mock<NotifPipeline>() }
+var Kosmos.notifPipeline by Kosmos.Fixture { mockNotifPipeline }
+var Kosmos.mockNotifPipeline by Kosmos.Fixture { mock<NotifPipeline>() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractorKosmos.kt
index 63521de096c9..e55cd0dc16f4 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractorKosmos.kt
@@ -16,8 +16,11 @@
package com.android.systemui.statusbar.notification.promoted
+import android.app.Notification
import android.content.applicationContext
import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.row.RowImageInflater
import com.android.systemui.statusbar.notification.row.shared.skeletonImageTransform
var Kosmos.promotedNotificationContentExtractor by
@@ -28,3 +31,14 @@ var Kosmos.promotedNotificationContentExtractor by
promotedNotificationLogger,
)
}
+
+fun Kosmos.setPromotedContent(entry: NotificationEntry) {
+ val extractedContent =
+ promotedNotificationContentExtractor.extractContent(
+ entry,
+ Notification.Builder.recoverBuilder(applicationContext, entry.sbn.notification),
+ RowImageInflater.newInstance(null).useForContentModel(),
+ )
+ entry.promotedNotificationContentModel =
+ requireNotNull(extractedContent) { "extractContent returned null" }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/promoted/domain/interactor/AODPromotedNotificationInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/promoted/domain/interactor/AODPromotedNotificationInteractorKosmos.kt
index df1c82278bc2..fcd484353011 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/promoted/domain/interactor/AODPromotedNotificationInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/promoted/domain/interactor/AODPromotedNotificationInteractorKosmos.kt
@@ -18,12 +18,11 @@ package com.android.systemui.statusbar.notification.promoted.domain.interactor
import com.android.systemui.dump.dumpManager
import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor
val Kosmos.aodPromotedNotificationInteractor by
Kosmos.Fixture {
AODPromotedNotificationInteractor(
- activeNotificationsInteractor = activeNotificationsInteractor,
+ promotedNotificationsInteractor = promotedNotificationsInteractor,
dumpManager = dumpManager,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/promoted/domain/interactor/PromotedNotificationsInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/promoted/domain/interactor/PromotedNotificationsInteractorKosmos.kt
new file mode 100644
index 000000000000..093ec10e2642
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/promoted/domain/interactor/PromotedNotificationsInteractorKosmos.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.promoted.domain.interactor
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.statusbar.chips.call.domain.interactor.callChipInteractor
+import com.android.systemui.statusbar.chips.notification.domain.interactor.statusBarNotificationChipsInteractor
+import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor
+
+val Kosmos.promotedNotificationsInteractor by
+ Kosmos.Fixture {
+ PromotedNotificationsInteractor(
+ activeNotificationsInteractor = activeNotificationsInteractor,
+ callChipInteractor = callChipInteractor,
+ notifChipsInteractor = statusBarNotificationChipsInteractor,
+ backgroundDispatcher = testDispatcher,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowBuilder.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowBuilder.kt
index e445a73b06d0..8b19491bfdf8 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowBuilder.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowBuilder.kt
@@ -41,6 +41,7 @@ import com.android.systemui.media.controls.util.MediaFeatureFlag
import com.android.systemui.media.dialog.MediaOutputDialogManager
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.settings.UserTracker
import com.android.systemui.shared.system.ActivityManagerWrapper
import com.android.systemui.shared.system.DevicePolicyManagerWrapper
import com.android.systemui.shared.system.PackageManagerWrapper
@@ -346,10 +347,15 @@ class ExpandableNotificationRowBuilder(
// NOTE: This flag is read when the ExpandableNotificationRow is inflated, so it needs to be
// set, but we do not want to override an existing value that is needed by a specific test.
+ val userTracker = Mockito.mock(UserTracker::class.java, STUB_ONLY)
+ whenever(userTracker.userHandle).thenReturn(context.user)
+
val rowInflaterTask =
RowInflaterTask(
mFakeSystemClock,
Mockito.mock(RowInflaterTaskLogger::class.java, STUB_ONLY),
+ userTracker,
+ Mockito.mock(AsyncRowInflater::class.java, STUB_ONLY),
)
val row = rowInflaterTask.inflateSynchronously(context, null, entry)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinderKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinderKosmos.kt
index bc1363ac3d5c..970b87cd368a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinderKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinderKosmos.kt
@@ -33,7 +33,7 @@ import java.util.Optional
val Kosmos.notificationListViewBinder by Fixture {
NotificationListViewBinder(
- backgroundDispatcher = testDispatcher,
+ inflationDispatcher = testDispatcher,
hiderTracker = displaySwitchNotificationsHiderTracker,
configuration = configurationState,
falsingManager = falsingManager,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt
index 047bd13f0c27..7a2b7c24252b 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt
@@ -29,7 +29,6 @@ import com.android.systemui.keyguard.ui.viewmodel.aodToGoneTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.aodToLockscreenTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.aodToOccludedTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.aodToPrimaryBouncerTransitionViewModel
-import com.android.systemui.keyguard.ui.viewmodel.dozingToDreamingTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.dozingToGlanceableHubTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.dozingToLockscreenTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.dozingToOccludedTransitionViewModel
@@ -82,7 +81,6 @@ val Kosmos.sharedNotificationContainerViewModel by Fixture {
aodToLockscreenTransitionViewModel = aodToLockscreenTransitionViewModel,
aodToOccludedTransitionViewModel = aodToOccludedTransitionViewModel,
aodToPrimaryBouncerTransitionViewModel = aodToPrimaryBouncerTransitionViewModel,
- dozingToDreamingTransitionViewModel = dozingToDreamingTransitionViewModel,
dozingToGlanceableHubTransitionViewModel = dozingToGlanceableHubTransitionViewModel,
dozingToLockscreenTransitionViewModel = dozingToLockscreenTransitionViewModel,
dozingToOccludedTransitionViewModel = dozingToOccludedTransitionViewModel,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelKosmos.kt
index fbada934c9d4..a97c651ba426 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelKosmos.kt
@@ -29,7 +29,7 @@ import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.statusbar.chips.sharetoapp.ui.viewmodel.shareToAppChipViewModel
import com.android.systemui.statusbar.chips.ui.viewmodel.ongoingActivityChipsViewModel
import com.android.systemui.statusbar.events.domain.interactor.systemStatusEventAnimationInteractor
-import com.android.systemui.statusbar.featurepods.popups.ui.viewmodel.statusBarPopupChipsViewModel
+import com.android.systemui.statusbar.featurepods.popups.ui.viewmodel.statusBarPopupChipsViewModelFactory
import com.android.systemui.statusbar.layout.ui.viewmodel.multiDisplayStatusBarContentInsetsViewModelStore
import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor
import com.android.systemui.statusbar.notification.stack.domain.interactor.headsUpNotificationInteractor
@@ -59,7 +59,7 @@ var Kosmos.homeStatusBarViewModel: HomeStatusBarViewModel by
shadeInteractor,
shareToAppChipViewModel,
ongoingActivityChipsViewModel,
- statusBarPopupChipsViewModel,
+ statusBarPopupChipsViewModelFactory,
systemStatusEventAnimationInteractor,
multiDisplayStatusBarContentInsetsViewModelStore,
backgroundScope,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/data/repository/SecureSettingsForUserRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/data/repository/SecureSettingsForUserRepositoryKosmos.kt
new file mode 100644
index 000000000000..81f71e9f7b2f
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/data/repository/SecureSettingsForUserRepositoryKosmos.kt
@@ -0,0 +1,28 @@
+/*
+ * 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.util.settings.data.repository
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.backgroundCoroutineContext
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.util.settings.fakeSettings
+import com.android.systemui.util.settings.repository.SecureSettingsForUserRepository
+
+val Kosmos.secureSettingsForUserRepository by
+ Kosmos.Fixture {
+ SecureSettingsForUserRepository(fakeSettings, testDispatcher, backgroundCoroutineContext)
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/time/FakeSystemClockKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/util/time/FakeSystemClockKosmos.kt
index 703d6ad83eac..a209ec9d0c9c 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/util/time/FakeSystemClockKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/time/FakeSystemClockKosmos.kt
@@ -31,3 +31,6 @@ val Kosmos.systemClock by
}
val Kosmos.fakeSystemClock by Kosmos.Fixture { FakeSystemClock() }
+
+val SystemClock.fake
+ get() = this as FakeSystemClock
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioSharingStreamSliderViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioSharingStreamSliderViewModelKosmos.kt
index 96bc9722635a..8c8d0240f572 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioSharingStreamSliderViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioSharingStreamSliderViewModelKosmos.kt
@@ -16,11 +16,11 @@
package com.android.systemui.volume.panel.component.volume.slider.ui.viewmodel
-import android.content.applicationContext
import com.android.internal.logging.uiEventLogger
import com.android.systemui.haptics.slider.sliderHapticsViewModelFactory
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.volume.domain.interactor.audioSharingInteractor
+import com.android.systemui.volume.shared.volumePanelLogger
import kotlinx.coroutines.CoroutineScope
val Kosmos.audioSharingStreamSliderViewModelFactory by
@@ -29,10 +29,10 @@ val Kosmos.audioSharingStreamSliderViewModelFactory by
override fun create(coroutineScope: CoroutineScope): AudioSharingStreamSliderViewModel {
return AudioSharingStreamSliderViewModel(
coroutineScope,
- applicationContext,
audioSharingInteractor,
uiEventLogger,
sliderHapticsViewModelFactory,
+ volumePanelLogger,
)
}
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/CastVolumeSliderViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/CastVolumeSliderViewModelKosmos.kt
index abd4235143f1..6875619d45fc 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/CastVolumeSliderViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/CastVolumeSliderViewModelKosmos.kt
@@ -21,6 +21,7 @@ import com.android.systemui.haptics.slider.sliderHapticsViewModelFactory
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.volume.mediaDeviceSessionInteractor
import com.android.systemui.volume.panel.component.mediaoutput.shared.model.MediaDeviceSession
+import com.android.systemui.volume.shared.volumePanelLogger
import kotlinx.coroutines.CoroutineScope
val Kosmos.castVolumeSliderViewModelFactory by
@@ -36,6 +37,7 @@ val Kosmos.castVolumeSliderViewModelFactory by
applicationContext,
mediaDeviceSessionInteractor,
sliderHapticsViewModelFactory,
+ volumePanelLogger,
)
}
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/wallpapers/data/repository/FakeWallpaperFocalAreaRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/wallpapers/data/repository/FakeWallpaperFocalAreaRepository.kt
index aeff86ed89bb..24d2f1f0d901 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/wallpapers/data/repository/FakeWallpaperFocalAreaRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/wallpapers/data/repository/FakeWallpaperFocalAreaRepository.kt
@@ -34,12 +34,15 @@ class FakeWallpaperFocalAreaRepository : WallpaperFocalAreaRepository {
_wallpaperFocalAreaBounds.asStateFlow()
private val _wallpaperFocalAreaTapPosition = MutableStateFlow(PointF(0F, 0F))
- override val wallpaperFocalAreaTapPosition: StateFlow<PointF> =
+ val wallpaperFocalAreaTapPosition: StateFlow<PointF> =
_wallpaperFocalAreaTapPosition.asStateFlow()
private val _notificationDefaultTop = MutableStateFlow(0F)
override val notificationDefaultTop: StateFlow<Float> = _notificationDefaultTop.asStateFlow()
+ private val _hasFocalArea = MutableStateFlow(false)
+ override val hasFocalArea: StateFlow<Boolean> = _hasFocalArea.asStateFlow()
+
override fun setShortcutAbsoluteTop(top: Float) {
_shortcutAbsoluteTop.value = top
}
@@ -56,7 +59,7 @@ class FakeWallpaperFocalAreaRepository : WallpaperFocalAreaRepository {
_wallpaperFocalAreaBounds.value = bounds
}
- override fun setTapPosition(point: PointF) {
- _wallpaperFocalAreaTapPosition.value = point
+ override fun setTapPosition(tapPosition: PointF) {
+ _wallpaperFocalAreaTapPosition.value = tapPosition
}
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/wallpapers/data/repository/FakeWallpaperRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/wallpapers/data/repository/FakeWallpaperRepository.kt
index 8689e04e62dd..66bb803c182d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/wallpapers/data/repository/FakeWallpaperRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/wallpapers/data/repository/FakeWallpaperRepository.kt
@@ -17,6 +17,8 @@
package com.android.systemui.wallpapers.data.repository
import android.app.WallpaperInfo
+import android.graphics.PointF
+import android.graphics.RectF
import android.view.View
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
@@ -34,9 +36,9 @@ class FakeWallpaperRepository : WallpaperRepository {
private val _shouldSendFocalArea = MutableStateFlow(false)
override val shouldSendFocalArea: StateFlow<Boolean> = _shouldSendFocalArea.asStateFlow()
- fun setShouldSendFocalArea(shouldSendFocalArea: Boolean) {
- _shouldSendFocalArea.value = shouldSendFocalArea
- }
+ override fun sendLockScreenLayoutChangeCommand(wallpaperFocalAreaBounds: RectF) {}
+
+ override fun sendTapCommand(tapPosition: PointF) {}
fun setWallpaperInfo(wallpaperInfo: WallpaperInfo?) {
_wallpaperInfo.value = wallpaperInfo
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 7ebec6c3a7b9..1761503b2cc9 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.domain.interactor.keyguardTransitionInteractor
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
import com.android.systemui.kosmos.testDispatcher
@@ -34,8 +33,6 @@ val Kosmos.wallpaperRepository by Fixture {
bgDispatcher = testDispatcher,
broadcastDispatcher = broadcastDispatcher,
userRepository = userRepository,
- keyguardTransitionInteractor = keyguardTransitionInteractor,
- wallpaperFocalAreaRepository = wallpaperFocalAreaRepository,
wallpaperManager = wallpaperManager,
secureSettings = fakeSettings,
)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/wallpapers/domain/interactor/WallpaperFocalAreaInteractor.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/wallpapers/domain/interactor/WallpaperFocalAreaInteractor.kt
index 88eb5511160b..eaf55a72be93 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/wallpapers/domain/interactor/WallpaperFocalAreaInteractor.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/wallpapers/domain/interactor/WallpaperFocalAreaInteractor.kt
@@ -18,20 +18,14 @@ package com.android.systemui.wallpapers.domain.interactor
import android.content.applicationContext
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.wallpaperFocalAreaRepository
-import com.android.systemui.wallpapers.data.repository.wallpaperRepository
-val Kosmos.wallpaperFocalAreaInteractor by
+var Kosmos.wallpaperFocalAreaInteractor by
Kosmos.Fixture {
WallpaperFocalAreaInteractor(
- applicationScope = applicationCoroutineScope,
context = applicationContext,
wallpaperFocalAreaRepository = wallpaperFocalAreaRepository,
shadeRepository = shadeRepository,
- activeNotificationsInteractor = activeNotificationsInteractor,
- wallpaperRepository = wallpaperRepository,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/wallpapers/ui/viewmodel/WallpaperFocalAreaViewModel.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/wallpapers/ui/viewmodel/WallpaperFocalAreaViewModel.kt
index 7e232c526732..4032503d04c1 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/wallpapers/ui/viewmodel/WallpaperFocalAreaViewModel.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/wallpapers/ui/viewmodel/WallpaperFocalAreaViewModel.kt
@@ -16,10 +16,14 @@
package com.android.systemui.wallpapers.ui.viewmodel
+import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.wallpapers.domain.interactor.wallpaperFocalAreaInteractor
var Kosmos.wallpaperFocalAreaViewModel by
Kosmos.Fixture {
- WallpaperFocalAreaViewModel(wallpaperFocalAreaInteractor = wallpaperFocalAreaInteractor)
+ WallpaperFocalAreaViewModel(
+ wallpaperFocalAreaInteractor = wallpaperFocalAreaInteractor,
+ keyguardTransitionInteractor = keyguardTransitionInteractor,
+ )
}
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodMethodCallLogger.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodMethodCallLogger.java
new file mode 100644
index 000000000000..7ee9d7a8a5c6
--- /dev/null
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodMethodCallLogger.java
@@ -0,0 +1,229 @@
+/*
+ * 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.platform.test.ravenwood;
+
+import com.android.ravenwood.RavenwoodRuntimeNative;
+
+import java.io.PrintStream;
+import java.util.HashSet;
+import java.util.Objects;
+
+/**
+ * Provides a method call hook that prints almost all (see below) the framework methods being
+ * called with indentation.
+ *
+ * We don't log methods that are trivial, uninteresting, or would be too noisy.
+ * e.g. we don't want to log any logging related methods, or collection APIs.
+ *
+ */
+public class RavenwoodMethodCallLogger {
+ private RavenwoodMethodCallLogger() {
+ }
+
+ /** We don't want to log anything before ravenwood is initialized. This flag controls it.*/
+ private static volatile boolean sEnabled = false;
+
+ private static volatile PrintStream sOut = System.out;
+
+ /** Return the current thread's call nest level. */
+ private static int getNestLevel() {
+ return Thread.currentThread().getStackTrace().length;
+ }
+
+ private static class ThreadInfo {
+ /**
+ * We save the current thread's nest call level here and use that as the initial level.
+ * We do it because otherwise the nest level would be too deep by the time test
+ * starts.
+ */
+ public final int mInitialNestLevel = getNestLevel();
+
+ /**
+ * A nest level where shouldLog() returned false.
+ * Once it's set, we ignore all calls deeper than this.
+ */
+ public int mDisabledNestLevel = Integer.MAX_VALUE;
+ }
+
+ private static final ThreadLocal<ThreadInfo> sThreadInfo = new ThreadLocal<>() {
+ @Override
+ protected ThreadInfo initialValue() {
+ return new ThreadInfo();
+ }
+ };
+
+ /** Classes that should be logged. Uses a map for fast lookup. */
+ private static final HashSet<Class> sIgnoreClasses = new HashSet<>();
+ static {
+ // The following classes are not interesting...
+ sIgnoreClasses.add(android.util.Log.class);
+ sIgnoreClasses.add(android.util.Slog.class);
+ sIgnoreClasses.add(android.util.EventLog.class);
+ sIgnoreClasses.add(android.util.TimingsTraceLog.class);
+
+ sIgnoreClasses.add(android.util.SparseArray.class);
+ sIgnoreClasses.add(android.util.SparseIntArray.class);
+ sIgnoreClasses.add(android.util.SparseLongArray.class);
+ sIgnoreClasses.add(android.util.SparseBooleanArray.class);
+ sIgnoreClasses.add(android.util.SparseDoubleArray.class);
+ sIgnoreClasses.add(android.util.SparseSetArray.class);
+ sIgnoreClasses.add(android.util.SparseArrayMap.class);
+ sIgnoreClasses.add(android.util.LongSparseArray.class);
+ sIgnoreClasses.add(android.util.LongSparseLongArray.class);
+ sIgnoreClasses.add(android.util.LongArray.class);
+
+ sIgnoreClasses.add(android.text.FontConfig.class);
+
+ sIgnoreClasses.add(android.os.SystemClock.class);
+ sIgnoreClasses.add(android.os.Trace.class);
+ sIgnoreClasses.add(android.os.LocaleList.class);
+ sIgnoreClasses.add(android.os.Build.class);
+ sIgnoreClasses.add(android.os.SystemProperties.class);
+
+ sIgnoreClasses.add(com.android.internal.util.Preconditions.class);
+
+ sIgnoreClasses.add(android.graphics.FontListParser.class);
+ sIgnoreClasses.add(android.graphics.ColorSpace.class);
+
+ sIgnoreClasses.add(android.graphics.fonts.FontStyle.class);
+ sIgnoreClasses.add(android.graphics.fonts.FontVariationAxis.class);
+
+ sIgnoreClasses.add(com.android.internal.compat.CompatibilityChangeInfo.class);
+ sIgnoreClasses.add(com.android.internal.os.LoggingPrintStream.class);
+
+ sIgnoreClasses.add(android.os.ThreadLocalWorkSource.class);
+
+ // Following classes *may* be interesting for some purposes, but the initialization is
+ // too noisy...
+ sIgnoreClasses.add(android.graphics.fonts.SystemFonts.class);
+
+ }
+
+ /**
+ * Return if a class should be ignored. Uses {link #sIgnoreCladsses}, but
+ * we ignore more classes.
+ */
+ private static boolean shouldIgnoreClass(Class<?> clazz) {
+ if (sIgnoreClasses.contains(clazz)) {
+ return true;
+ }
+ // Let's also ignore collection-ish classes in android.util.
+ if (java.util.Collection.class.isAssignableFrom(clazz)
+ || java.util.Map.class.isAssignableFrom(clazz)
+ ) {
+ if ("android.util".equals(clazz.getPackageName())) {
+ return true;
+ }
+ return false;
+ }
+
+ switch (clazz.getSimpleName()) {
+ case "EventLogTags":
+ return false;
+ }
+
+ // Following are classes that can't be referred to here directly.
+ // e.g. AndroidPrintStream is package-private, so we can't use its "class" here.
+ switch (clazz.getName()) {
+ case "com.android.internal.os.AndroidPrintStream":
+ return false;
+ }
+ return false;
+ }
+
+ private static boolean shouldLog(
+ Class<?> methodClass,
+ String methodName,
+ @SuppressWarnings("UnusedVariable") String methodDescriptor
+ ) {
+ // Should we ignore this class?
+ if (shouldIgnoreClass(methodClass)) {
+ return false;
+ }
+ // Is it a nested class in a class that should be ignored?
+ var host = methodClass.getNestHost();
+ if (host != methodClass && shouldIgnoreClass(host)) {
+ return false;
+ }
+
+ var className = methodClass.getName();
+
+ // Ad-hoc ignore list. They'd be too noisy.
+ if ("create".equals(methodName)
+ // We may apply jarjar, so use endsWith().
+ && className.endsWith("com.android.server.compat.CompatConfig")) {
+ return false;
+ }
+
+ var pkg = methodClass.getPackageName();
+ if (pkg.startsWith("android.icu")) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Call this to enable logging.
+ */
+ public static void enable(PrintStream out) {
+ sEnabled = true;
+ sOut = Objects.requireNonNull(out);
+
+ // It's called from the test thread (Java's main thread). Because we're already
+ // in deep nest calls, we initialize the initial nest level here.
+ sThreadInfo.get();
+ }
+
+ /** Actual method hook entry point.*/
+ public static void logMethodCall(
+ Class<?> methodClass,
+ String methodName,
+ String methodDescriptor
+ ) {
+ if (!sEnabled) {
+ return;
+ }
+ final var ti = sThreadInfo.get();
+ final int nestLevel = getNestLevel() - ti.mInitialNestLevel;
+
+ // Once shouldLog() returns false, we just ignore all deeper calls.
+ if (ti.mDisabledNestLevel < nestLevel) {
+ return; // Still ignore.
+ }
+ final boolean shouldLog = shouldLog(methodClass, methodName, methodDescriptor);
+
+ if (!shouldLog) {
+ ti.mDisabledNestLevel = nestLevel;
+ return;
+ }
+ ti.mDisabledNestLevel = Integer.MAX_VALUE;
+
+ var out = sOut;
+ out.print("# [");
+ out.print(RavenwoodRuntimeNative.gettid());
+ out.print(": ");
+ out.print(Thread.currentThread().getName());
+ out.print("]: ");
+ out.print("[@");
+ out.printf("%2d", nestLevel);
+ out.print("] ");
+ for (int i = 0; i < nestLevel; i++) {
+ out.print(" ");
+ }
+ out.println(methodClass.getName() + "." + methodName + methodDescriptor);
+ }
+}
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java
index 7af03ed2e6c8..ae88bb234e9d 100644
--- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java
@@ -23,7 +23,6 @@ import static android.platform.test.ravenwood.RavenwoodSystemServer.ANDROID_PACK
import static com.android.ravenwood.common.RavenwoodCommonUtils.RAVENWOOD_EMPTY_RESOURCES_APK;
import static com.android.ravenwood.common.RavenwoodCommonUtils.RAVENWOOD_INST_RESOURCE_APK;
import static com.android.ravenwood.common.RavenwoodCommonUtils.RAVENWOOD_RESOURCE_APK;
-import static com.android.ravenwood.common.RavenwoodCommonUtils.RAVENWOOD_VERBOSE_LOGGING;
import static com.android.ravenwood.common.RavenwoodCommonUtils.RAVENWOOD_VERSION_JAVA_SYSPROP;
import static com.android.ravenwood.common.RavenwoodCommonUtils.parseNullableInt;
import static com.android.ravenwood.common.RavenwoodCommonUtils.withDefault;
@@ -103,6 +102,10 @@ public class RavenwoodRuntimeEnvironmentController {
private RavenwoodRuntimeEnvironmentController() {
}
+ private static final PrintStream sStdOut = System.out;
+ @SuppressWarnings("UnusedVariable")
+ private static final PrintStream sStdErr = System.err;
+
private static final String MAIN_THREAD_NAME = "RavenwoodMain";
private static final String LIBRAVENWOOD_INITIALIZER_NAME = "ravenwood_initializer";
private static final String RAVENWOOD_NATIVE_RUNTIME_NAME = "ravenwood_runtime";
@@ -212,9 +215,9 @@ public class RavenwoodRuntimeEnvironmentController {
}
private static void globalInitInner() throws IOException {
- if (RAVENWOOD_VERBOSE_LOGGING) {
- Log.v(TAG, "globalInit() called here...", new RuntimeException("NOT A CRASH"));
- }
+ // We haven't initialized liblog yet, so directly write to System.out here.
+ RavenwoodCommonUtils.log(TAG, "globalInitInner()");
+
if (ENABLE_UNCAUGHT_EXCEPTION_DETECTION) {
Thread.setDefaultUncaughtExceptionHandler(sUncaughtExceptionHandler);
}
@@ -234,9 +237,6 @@ public class RavenwoodRuntimeEnvironmentController {
dumpJavaProperties();
dumpOtherInfo();
- // We haven't initialized liblog yet, so directly write to System.out here.
- RavenwoodCommonUtils.log(TAG, "globalInitInner()");
-
// Make sure libravenwood_runtime is loaded.
System.load(RavenwoodCommonUtils.getJniLibraryPath(RAVENWOOD_NATIVE_RUNTIME_NAME));
@@ -261,6 +261,9 @@ public class RavenwoodRuntimeEnvironmentController {
// Make sure libandroid_runtime is loaded.
RavenwoodNativeLoader.loadFrameworkNativeCode();
+ // Start method logging.
+ RavenwoodMethodCallLogger.enable(sStdOut);
+
// Touch some references early to ensure they're <clinit>'ed
Objects.requireNonNull(Build.TYPE);
Objects.requireNonNull(Build.VERSION.SDK);
diff --git a/ravenwood/runtime-jni/ravenwood_initializer.cpp b/ravenwood/runtime-jni/ravenwood_initializer.cpp
index 391c5d56b212..8a35ade649b2 100644
--- a/ravenwood/runtime-jni/ravenwood_initializer.cpp
+++ b/ravenwood/runtime-jni/ravenwood_initializer.cpp
@@ -26,6 +26,10 @@
#include <fcntl.h>
#include <set>
+#include <fstream>
+#include <iostream>
+#include <string>
+#include <cstdlib>
#include "jni_helper.h"
@@ -182,17 +186,82 @@ static jboolean removeSystemProperty(JNIEnv* env, jclass, jstring javaKey) {
}
}
+// Find the PPID of child_pid using /proc/N/stat. The 4th field is the PPID.
+// Also returns child_pid's process name (2nd field).
+static pid_t getppid_of(pid_t child_pid, std::string& out_process_name) {
+ if (child_pid < 0) {
+ return -1;
+ }
+ std::string stat_file = "/proc/" + std::to_string(child_pid) + "/stat";
+ std::ifstream stat_stream(stat_file);
+ if (!stat_stream.is_open()) {
+ ALOGW("Unable to open '%s': %s", stat_file.c_str(), strerror(errno));
+ return -1;
+ }
+
+ std::string field;
+ int field_count = 0;
+ while (std::getline(stat_stream, field, ' ')) {
+ if (++field_count == 4) {
+ return atoi(field.c_str());
+ }
+ if (field_count == 2) {
+ out_process_name = field;
+ }
+ }
+ ALOGW("Unexpected format in '%s'", stat_file.c_str());
+ return -1;
+}
+
+// Find atest's PID. Climb up the process tree, and find "atest-py3".
+static pid_t find_atest_pid() {
+ auto ret = getpid(); // self (isolation runner process)
+
+ while (ret != -1) {
+ std::string proc;
+ ret = getppid_of(ret, proc);
+ if (proc == "(atest-py3)") {
+ return ret;
+ }
+ }
+
+ return ret;
+}
+
+// If $RAVENWOOD_LOG_OUT is set, redirect stdout/err to this file.
+// Originally it was added to allow to monitor log in realtime, with
+// RAVENWOOD_LOG_OUT=$(tty) atest...
+//
+// As a special case, if $RAVENWOOD_LOG_OUT is set to "-", we try to find
+// atest's process and send the output to its stdout. It's sort of hacky, but
+// this allows shell redirection to work on Ravenwood output too,
+// so e.g. `atest ... |tee atest.log` would work on Ravenwood's output.
+// (which wouldn't work with `RAVENWOOD_LOG_OUT=$(tty)`).
+//
+// Otherwise -- if $RAVENWOOD_LOG_OUT isn't set -- atest/tradefed just writes
+// the test's output to its own log file.
static void maybeRedirectLog() {
auto ravenwoodLogOut = getenv("RAVENWOOD_LOG_OUT");
- if (ravenwoodLogOut == NULL) {
+ if (ravenwoodLogOut == NULL || *ravenwoodLogOut == '\0') {
return;
}
- ALOGI("RAVENWOOD_LOG_OUT set. Redirecting output to %s", ravenwoodLogOut);
+ std::string path;
+ if (strcmp("-", ravenwoodLogOut) == 0) {
+ pid_t ppid = find_atest_pid();
+ if (ppid < 0) {
+ ALOGI("RAVENWOOD_LOG_OUT set to '-', but unable to find atest's PID");
+ return;
+ }
+ path = std::format("/proc/{}/fd/1", ppid);
+ } else {
+ path = ravenwoodLogOut;
+ }
+ ALOGI("RAVENWOOD_LOG_OUT set. Redirecting output to '%s'", path.c_str());
// Redirect stdin / stdout to /dev/tty.
- int ttyFd = open(ravenwoodLogOut, O_WRONLY | O_APPEND);
+ int ttyFd = open(path.c_str(), O_WRONLY | O_APPEND);
if (ttyFd == -1) {
- ALOGW("$RAVENWOOD_LOG_OUT is set to %s, but failed to open: %s ", ravenwoodLogOut,
+ ALOGW("$RAVENWOOD_LOG_OUT is set, but failed to open '%s': %s ", path.c_str(),
strerror(errno));
return;
}
diff --git a/ravenwood/texts/ravenwood-standard-options.txt b/ravenwood/texts/ravenwood-standard-options.txt
index 91fd9283aff2..0edc348fc7f2 100644
--- a/ravenwood/texts/ravenwood-standard-options.txt
+++ b/ravenwood/texts/ravenwood-standard-options.txt
@@ -9,8 +9,10 @@
# Uncomment below lines to enable each feature.
+# Enable method call hook.
#--default-method-call-hook
-# com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
+# android.platform.test.ravenwood.RavenwoodMethodCallLogger.logMethodCall
+
#--default-class-load-hook
# com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded
diff --git a/ravenwood/tools/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostTestUtils.java b/ravenwood/tools/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostTestUtils.java
index 78fd8f7f960a..145325ccc809 100644
--- a/ravenwood/tools/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostTestUtils.java
+++ b/ravenwood/tools/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostTestUtils.java
@@ -18,6 +18,7 @@ package com.android.hoststubgen.hosthelper;
import java.io.PrintStream;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
+import java.util.Arrays;
/**
* Utilities used in the host side test environment.
@@ -36,9 +37,14 @@ public class HostTestUtils {
public static final String CLASS_INTERNAL_NAME = getInternalName(HostTestUtils.class);
+ /** If true, we skip all method call hooks */
+ private static final boolean SKIP_METHOD_CALL_HOOK = "1".equals(System.getenv(
+ "HOSTTEST_SKIP_METHOD_CALL_HOOK"));
+
/** If true, we won't print method call log. */
- private static final boolean SKIP_METHOD_LOG = "1".equals(System.getenv(
- "HOSTTEST_SKIP_METHOD_LOG"));
+ private static final boolean SKIP_METHOD_LOG =
+ "1".equals(System.getenv("HOSTTEST_SKIP_METHOD_LOG"))
+ || "1".equals(System.getenv("RAVENWOOD_NO_METHOD_LOG"));
/** If true, we won't print class load log. */
private static final boolean SKIP_CLASS_LOG = "1".equals(System.getenv(
@@ -65,6 +71,9 @@ public class HostTestUtils {
+ "consider using Mockito; more details at go/ravenwood-docs");
}
+ private static final Class<?>[] sMethodHookArgTypes =
+ { Class.class, String.class, String.class};
+
/**
* Trampoline method for method-call-hook.
*/
@@ -74,16 +83,22 @@ public class HostTestUtils {
String methodDescriptor,
String callbackMethod
) {
- callStaticMethodByName(callbackMethod, "method call hook", methodClass,
- methodName, methodDescriptor);
+ if (SKIP_METHOD_CALL_HOOK) {
+ return;
+ }
+ callStaticMethodByName(callbackMethod, "method call hook", sMethodHookArgTypes,
+ methodClass, methodName, methodDescriptor);
}
/**
+ * Simple implementation of method call hook, which just prints the information of the
+ * method. This is just for basic testing. We don't use it in Ravenwood, because this would
+ * be way too noisy as it prints every single method, even trivial ones. (iterator methods,
+ * etc..)
+ *
* I can be used as
* {@code --default-method-call-hook
* com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall}.
- *
- * It logs every single methods called.
*/
public static void logMethodCall(
Class<?> methodClass,
@@ -97,6 +112,8 @@ public class HostTestUtils {
+ methodName + methodDescriptor);
}
+ private static final Class<?>[] sClassLoadHookArgTypes = { Class.class };
+
/**
* Called when any top level class (not nested classes) in the impl jar is loaded.
*
@@ -111,11 +128,12 @@ public class HostTestUtils {
logPrintStream.println("! Class loaded: " + loadedClass.getCanonicalName()
+ " calling hook " + callbackMethod);
- callStaticMethodByName(callbackMethod, "class load hook", loadedClass);
+ callStaticMethodByName(
+ callbackMethod, "class load hook", sClassLoadHookArgTypes, loadedClass);
}
private static void callStaticMethodByName(String classAndMethodName,
- String description, Object... args) {
+ String description, Class<?>[] argTypes, Object... args) {
// Forward the call to callbackMethod.
final int lastPeriod = classAndMethodName.lastIndexOf(".");
@@ -145,19 +163,14 @@ public class HostTestUtils {
className));
}
- Class<?>[] argTypes = new Class[args.length];
- for (int i = 0; i < args.length; i++) {
- argTypes[i] = args[i].getClass();
- }
-
Method method = null;
try {
method = clazz.getMethod(methodName, argTypes);
} catch (Exception e) {
throw new HostTestException(String.format(
"Unable to find %s: class %s doesn't have method %s"
- + " (method must take exactly one parameter of type Class,"
- + " and public static)",
+ + " Method must be public static, and arg types must be: "
+ + Arrays.toString(argTypes),
description, className, methodName), e);
}
if (!(Modifier.isPublic(method.getModifiers())
diff --git a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt
index 985947575a86..3340990f4765 100644
--- a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt
+++ b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt
@@ -145,6 +145,7 @@ class HostStubGen(val options: HostStubGenOptions) {
// Inject default hooks from options.
filter = DefaultHookInjectingFilter(
+ allClasses,
options.defaultClassLoadHook.get,
options.defaultMethodCallHook.get,
filter
diff --git a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/DefaultHookInjectingFilter.kt b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/DefaultHookInjectingFilter.kt
index d771003a955d..aaf49c154a17 100644
--- a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/DefaultHookInjectingFilter.kt
+++ b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/DefaultHookInjectingFilter.kt
@@ -16,8 +16,11 @@
package com.android.hoststubgen.filters
import com.android.hoststubgen.addLists
+import com.android.hoststubgen.asm.ClassNodes
+import com.android.hoststubgen.asm.isAnnotation
class DefaultHookInjectingFilter(
+ val classes: ClassNodes,
defaultClassLoadHook: String?,
defaultMethodCallHook: String?,
fallback: OutputFilter
@@ -36,8 +39,30 @@ class DefaultHookInjectingFilter(
private val defaultClassLoadHookAsList: List<String> = toSingleList(defaultClassLoadHook)
private val defaultMethodCallHookAsList: List<String> = toSingleList(defaultMethodCallHook)
+ private fun shouldInject(className: String): Boolean {
+ // Let's not inject default hooks to annotation classes or inner classes of an annotation
+ // class, because these methods could be called at the class load time, which
+ // is very confusing, and usually not useful.
+
+ val cn = classes.findClass(className) ?: return false
+ if (cn.isAnnotation()) {
+ return false
+ }
+ cn.nestHostClass?.let { nestHostClass ->
+ val nestHost = classes.findClass(nestHostClass) ?: return false
+ if (nestHost.isAnnotation()) {
+ return false
+ }
+ }
+ return true
+ }
+
override fun getClassLoadHooks(className: String): List<String> {
- return addLists(super.getClassLoadHooks(className), defaultClassLoadHookAsList)
+ val s = super.getClassLoadHooks(className)
+ if (!shouldInject(className)) {
+ return s
+ }
+ return addLists(s, defaultClassLoadHookAsList)
}
override fun getMethodCallHooks(
@@ -45,9 +70,23 @@ class DefaultHookInjectingFilter(
methodName: String,
descriptor: String
): List<String> {
- return addLists(
- super.getMethodCallHooks(className, methodName, descriptor),
- defaultMethodCallHookAsList,
- )
+ val s = super.getMethodCallHooks(className, methodName, descriptor)
+ if (!shouldInject(className)) {
+ return s
+ }
+ // Don't hook Object methods.
+ if (methodName == "finalize" && descriptor == "()V") {
+ return s
+ }
+ if (methodName == "toString" && descriptor == "()Ljava/lang/String;") {
+ return s
+ }
+ if (methodName == "equals" && descriptor == "(Ljava/lang/Object;)Z") {
+ return s
+ }
+ if (methodName == "hashCode" && descriptor == "()I") {
+ return s
+ }
+ return addLists(s, defaultMethodCallHookAsList)
}
-} \ No newline at end of file
+}
diff --git a/ravenwood/tools/hoststubgen/test-tiny-framework/golden-output.RELEASE_TARGET_JAVA_21/13-hoststubgen-test-tiny-framework-host-ext-dump.txt b/ravenwood/tools/hoststubgen/test-tiny-framework/golden-output.RELEASE_TARGET_JAVA_21/13-hoststubgen-test-tiny-framework-host-ext-dump.txt
index 49769e648bbf..fb225ff1aa21 100644
--- a/ravenwood/tools/hoststubgen/test-tiny-framework/golden-output.RELEASE_TARGET_JAVA_21/13-hoststubgen-test-tiny-framework-host-ext-dump.txt
+++ b/ravenwood/tools/hoststubgen/test-tiny-framework/golden-output.RELEASE_TARGET_JAVA_21/13-hoststubgen-test-tiny-framework-host-ext-dump.txt
@@ -6,17 +6,9 @@ public interface android.hosttest.annotation.HostSideTestClassLoadHook extends j
flags: (0x2601) ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT, ACC_ANNOTATION
this_class: #x // android/hosttest/annotation/HostSideTestClassLoadHook
super_class: #x // java/lang/Object
- interfaces: 1, fields: 0, methods: 2, attributes: 2
- private static {};
- descriptor: ()V
- flags: (0x000a) ACC_PRIVATE, ACC_STATIC
- Code:
- stack=2, locals=0, args_size=0
- x: ldc #x // class android/hosttest/annotation/HostSideTestClassLoadHook
- x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded
- x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onClassLoaded:(Ljava/lang/Class;Ljava/lang/String;)V
- x: return
-
+ interfaces: 1, fields: 0, methods: 1, attributes: 2
+Constant pool:
+{
public abstract java.lang.String value();
descriptor: ()Ljava/lang/String;
flags: (0x0401) ACC_PUBLIC, ACC_ABSTRACT
@@ -44,16 +36,9 @@ public interface android.hosttest.annotation.HostSideTestKeep extends java.lang.
flags: (0x2601) ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT, ACC_ANNOTATION
this_class: #x // android/hosttest/annotation/HostSideTestKeep
super_class: #x // java/lang/Object
- interfaces: 1, fields: 0, methods: 1, attributes: 2
- private static {};
- descriptor: ()V
- flags: (0x000a) ACC_PRIVATE, ACC_STATIC
- Code:
- stack=2, locals=0, args_size=0
- x: ldc #x // class android/hosttest/annotation/HostSideTestKeep
- x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded
- x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onClassLoaded:(Ljava/lang/Class;Ljava/lang/String;)V
- x: return
+ interfaces: 1, fields: 0, methods: 0, attributes: 2
+Constant pool:
+{
}
SourceFile: "HostSideTestKeep.java"
RuntimeVisibleAnnotations:
@@ -75,16 +60,9 @@ public interface android.hosttest.annotation.HostSideTestRedirect extends java.l
flags: (0x2601) ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT, ACC_ANNOTATION
this_class: #x // android/hosttest/annotation/HostSideTestRedirect
super_class: #x // java/lang/Object
- interfaces: 1, fields: 0, methods: 1, attributes: 2
- private static {};
- descriptor: ()V
- flags: (0x000a) ACC_PRIVATE, ACC_STATIC
- Code:
- stack=2, locals=0, args_size=0
- x: ldc #x // class android/hosttest/annotation/HostSideTestRedirect
- x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded
- x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onClassLoaded:(Ljava/lang/Class;Ljava/lang/String;)V
- x: return
+ interfaces: 1, fields: 0, methods: 0, attributes: 2
+Constant pool:
+{
}
SourceFile: "HostSideTestRedirect.java"
RuntimeVisibleAnnotations:
@@ -106,17 +84,9 @@ public interface android.hosttest.annotation.HostSideTestRedirectionClass extend
flags: (0x2601) ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT, ACC_ANNOTATION
this_class: #x // android/hosttest/annotation/HostSideTestRedirectionClass
super_class: #x // java/lang/Object
- interfaces: 1, fields: 0, methods: 2, attributes: 2
- private static {};
- descriptor: ()V
- flags: (0x000a) ACC_PRIVATE, ACC_STATIC
- Code:
- stack=2, locals=0, args_size=0
- x: ldc #x // class android/hosttest/annotation/HostSideTestRedirectionClass
- x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded
- x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onClassLoaded:(Ljava/lang/Class;Ljava/lang/String;)V
- x: return
-
+ interfaces: 1, fields: 0, methods: 1, attributes: 2
+Constant pool:
+{
public abstract java.lang.String value();
descriptor: ()Ljava/lang/String;
flags: (0x0401) ACC_PUBLIC, ACC_ABSTRACT
@@ -144,16 +114,9 @@ public interface android.hosttest.annotation.HostSideTestRemove extends java.lan
flags: (0x2601) ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT, ACC_ANNOTATION
this_class: #x // android/hosttest/annotation/HostSideTestRemove
super_class: #x // java/lang/Object
- interfaces: 1, fields: 0, methods: 1, attributes: 2
- private static {};
- descriptor: ()V
- flags: (0x000a) ACC_PRIVATE, ACC_STATIC
- Code:
- stack=2, locals=0, args_size=0
- x: ldc #x // class android/hosttest/annotation/HostSideTestRemove
- x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded
- x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onClassLoaded:(Ljava/lang/Class;Ljava/lang/String;)V
- x: return
+ interfaces: 1, fields: 0, methods: 0, attributes: 2
+Constant pool:
+{
}
SourceFile: "HostSideTestRemove.java"
RuntimeVisibleAnnotations:
@@ -175,16 +138,9 @@ public interface android.hosttest.annotation.HostSideTestStaticInitializerKeep e
flags: (0x2601) ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT, ACC_ANNOTATION
this_class: #x // android/hosttest/annotation/HostSideTestStaticInitializerKeep
super_class: #x // java/lang/Object
- interfaces: 1, fields: 0, methods: 1, attributes: 2
- private static {};
- descriptor: ()V
- flags: (0x000a) ACC_PRIVATE, ACC_STATIC
- Code:
- stack=2, locals=0, args_size=0
- x: ldc #x // class android/hosttest/annotation/HostSideTestStaticInitializerKeep
- x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded
- x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onClassLoaded:(Ljava/lang/Class;Ljava/lang/String;)V
- x: return
+ interfaces: 1, fields: 0, methods: 0, attributes: 2
+Constant pool:
+{
}
SourceFile: "HostSideTestStaticInitializerKeep.java"
RuntimeVisibleAnnotations:
@@ -206,17 +162,9 @@ public interface android.hosttest.annotation.HostSideTestSubstitute extends java
flags: (0x2601) ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT, ACC_ANNOTATION
this_class: #x // android/hosttest/annotation/HostSideTestSubstitute
super_class: #x // java/lang/Object
- interfaces: 1, fields: 0, methods: 2, attributes: 2
- private static {};
- descriptor: ()V
- flags: (0x000a) ACC_PRIVATE, ACC_STATIC
- Code:
- stack=2, locals=0, args_size=0
- x: ldc #x // class android/hosttest/annotation/HostSideTestSubstitute
- x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded
- x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onClassLoaded:(Ljava/lang/Class;Ljava/lang/String;)V
- x: return
-
+ interfaces: 1, fields: 0, methods: 1, attributes: 2
+Constant pool:
+{
public abstract java.lang.String suffix();
descriptor: ()Ljava/lang/String;
flags: (0x0401) ACC_PUBLIC, ACC_ABSTRACT
@@ -244,16 +192,9 @@ public interface android.hosttest.annotation.HostSideTestThrow extends java.lang
flags: (0x2601) ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT, ACC_ANNOTATION
this_class: #x // android/hosttest/annotation/HostSideTestThrow
super_class: #x // java/lang/Object
- interfaces: 1, fields: 0, methods: 1, attributes: 2
- private static {};
- descriptor: ()V
- flags: (0x000a) ACC_PRIVATE, ACC_STATIC
- Code:
- stack=2, locals=0, args_size=0
- x: ldc #x // class android/hosttest/annotation/HostSideTestThrow
- x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded
- x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onClassLoaded:(Ljava/lang/Class;Ljava/lang/String;)V
- x: return
+ interfaces: 1, fields: 0, methods: 0, attributes: 2
+Constant pool:
+{
}
SourceFile: "HostSideTestThrow.java"
RuntimeVisibleAnnotations:
@@ -275,16 +216,9 @@ public interface android.hosttest.annotation.HostSideTestWholeClassKeep extends
flags: (0x2601) ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT, ACC_ANNOTATION
this_class: #x // android/hosttest/annotation/HostSideTestWholeClassKeep
super_class: #x // java/lang/Object
- interfaces: 1, fields: 0, methods: 1, attributes: 2
- private static {};
- descriptor: ()V
- flags: (0x000a) ACC_PRIVATE, ACC_STATIC
- Code:
- stack=2, locals=0, args_size=0
- x: ldc #x // class android/hosttest/annotation/HostSideTestWholeClassKeep
- x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded
- x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onClassLoaded:(Ljava/lang/Class;Ljava/lang/String;)V
- x: return
+ interfaces: 1, fields: 0, methods: 0, attributes: 2
+Constant pool:
+{
}
SourceFile: "HostSideTestWholeClassKeep.java"
RuntimeVisibleAnnotations:
diff --git a/ravenwood/tools/hoststubgen/test-tiny-framework/golden-output/13-hoststubgen-test-tiny-framework-host-ext-dump.txt b/ravenwood/tools/hoststubgen/test-tiny-framework/golden-output/13-hoststubgen-test-tiny-framework-host-ext-dump.txt
index 0f8af92dc486..e4b9db2ed6d8 100644
--- a/ravenwood/tools/hoststubgen/test-tiny-framework/golden-output/13-hoststubgen-test-tiny-framework-host-ext-dump.txt
+++ b/ravenwood/tools/hoststubgen/test-tiny-framework/golden-output/13-hoststubgen-test-tiny-framework-host-ext-dump.txt
@@ -6,17 +6,9 @@ public interface android.hosttest.annotation.HostSideTestClassLoadHook extends j
flags: (0x2601) ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT, ACC_ANNOTATION
this_class: #x // android/hosttest/annotation/HostSideTestClassLoadHook
super_class: #x // java/lang/Object
- interfaces: 1, fields: 0, methods: 2, attributes: 2
- private static {};
- descriptor: ()V
- flags: (0x000a) ACC_PRIVATE, ACC_STATIC
- Code:
- stack=2, locals=0, args_size=0
- x: ldc #x // class android/hosttest/annotation/HostSideTestClassLoadHook
- x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded
- x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onClassLoaded:(Ljava/lang/Class;Ljava/lang/String;)V
- x: return
-
+ interfaces: 1, fields: 0, methods: 1, attributes: 2
+Constant pool:
+{
public abstract java.lang.String value();
descriptor: ()Ljava/lang/String;
flags: (0x0401) ACC_PUBLIC, ACC_ABSTRACT
@@ -44,16 +36,9 @@ public interface android.hosttest.annotation.HostSideTestKeep extends java.lang.
flags: (0x2601) ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT, ACC_ANNOTATION
this_class: #x // android/hosttest/annotation/HostSideTestKeep
super_class: #x // java/lang/Object
- interfaces: 1, fields: 0, methods: 1, attributes: 2
- private static {};
- descriptor: ()V
- flags: (0x000a) ACC_PRIVATE, ACC_STATIC
- Code:
- stack=2, locals=0, args_size=0
- x: ldc #x // class android/hosttest/annotation/HostSideTestKeep
- x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded
- x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onClassLoaded:(Ljava/lang/Class;Ljava/lang/String;)V
- x: return
+ interfaces: 1, fields: 0, methods: 0, attributes: 2
+Constant pool:
+{
}
SourceFile: "HostSideTestKeep.java"
RuntimeVisibleAnnotations:
@@ -75,16 +60,9 @@ public interface android.hosttest.annotation.HostSideTestRedirect extends java.l
flags: (0x2601) ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT, ACC_ANNOTATION
this_class: #x // android/hosttest/annotation/HostSideTestRedirect
super_class: #x // java/lang/Object
- interfaces: 1, fields: 0, methods: 1, attributes: 2
- private static {};
- descriptor: ()V
- flags: (0x000a) ACC_PRIVATE, ACC_STATIC
- Code:
- stack=2, locals=0, args_size=0
- x: ldc #x // class android/hosttest/annotation/HostSideTestRedirect
- x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded
- x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onClassLoaded:(Ljava/lang/Class;Ljava/lang/String;)V
- x: return
+ interfaces: 1, fields: 0, methods: 0, attributes: 2
+Constant pool:
+{
}
SourceFile: "HostSideTestRedirect.java"
RuntimeVisibleAnnotations:
@@ -106,17 +84,9 @@ public interface android.hosttest.annotation.HostSideTestRedirectionClass extend
flags: (0x2601) ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT, ACC_ANNOTATION
this_class: #x // android/hosttest/annotation/HostSideTestRedirectionClass
super_class: #x // java/lang/Object
- interfaces: 1, fields: 0, methods: 2, attributes: 2
- private static {};
- descriptor: ()V
- flags: (0x000a) ACC_PRIVATE, ACC_STATIC
- Code:
- stack=2, locals=0, args_size=0
- x: ldc #x // class android/hosttest/annotation/HostSideTestRedirectionClass
- x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded
- x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onClassLoaded:(Ljava/lang/Class;Ljava/lang/String;)V
- x: return
-
+ interfaces: 1, fields: 0, methods: 1, attributes: 2
+Constant pool:
+{
public abstract java.lang.String value();
descriptor: ()Ljava/lang/String;
flags: (0x0401) ACC_PUBLIC, ACC_ABSTRACT
@@ -144,16 +114,9 @@ public interface android.hosttest.annotation.HostSideTestRemove extends java.lan
flags: (0x2601) ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT, ACC_ANNOTATION
this_class: #x // android/hosttest/annotation/HostSideTestRemove
super_class: #x // java/lang/Object
- interfaces: 1, fields: 0, methods: 1, attributes: 2
- private static {};
- descriptor: ()V
- flags: (0x000a) ACC_PRIVATE, ACC_STATIC
- Code:
- stack=2, locals=0, args_size=0
- x: ldc #x // class android/hosttest/annotation/HostSideTestRemove
- x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded
- x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onClassLoaded:(Ljava/lang/Class;Ljava/lang/String;)V
- x: return
+ interfaces: 1, fields: 0, methods: 0, attributes: 2
+Constant pool:
+{
}
SourceFile: "HostSideTestRemove.java"
RuntimeVisibleAnnotations:
@@ -175,16 +138,9 @@ public interface android.hosttest.annotation.HostSideTestStaticInitializerKeep e
flags: (0x2601) ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT, ACC_ANNOTATION
this_class: #x // android/hosttest/annotation/HostSideTestStaticInitializerKeep
super_class: #x // java/lang/Object
- interfaces: 1, fields: 0, methods: 1, attributes: 2
- private static {};
- descriptor: ()V
- flags: (0x000a) ACC_PRIVATE, ACC_STATIC
- Code:
- stack=2, locals=0, args_size=0
- x: ldc #x // class android/hosttest/annotation/HostSideTestStaticInitializerKeep
- x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded
- x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onClassLoaded:(Ljava/lang/Class;Ljava/lang/String;)V
- x: return
+ interfaces: 1, fields: 0, methods: 0, attributes: 2
+Constant pool:
+{
}
SourceFile: "HostSideTestStaticInitializerKeep.java"
RuntimeVisibleAnnotations:
@@ -206,17 +162,9 @@ public interface android.hosttest.annotation.HostSideTestSubstitute extends java
flags: (0x2601) ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT, ACC_ANNOTATION
this_class: #x // android/hosttest/annotation/HostSideTestSubstitute
super_class: #x // java/lang/Object
- interfaces: 1, fields: 0, methods: 2, attributes: 2
- private static {};
- descriptor: ()V
- flags: (0x000a) ACC_PRIVATE, ACC_STATIC
- Code:
- stack=2, locals=0, args_size=0
- x: ldc #x // class android/hosttest/annotation/HostSideTestSubstitute
- x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded
- x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onClassLoaded:(Ljava/lang/Class;Ljava/lang/String;)V
- x: return
-
+ interfaces: 1, fields: 0, methods: 1, attributes: 2
+Constant pool:
+{
public abstract java.lang.String suffix();
descriptor: ()Ljava/lang/String;
flags: (0x0401) ACC_PUBLIC, ACC_ABSTRACT
@@ -244,16 +192,9 @@ public interface android.hosttest.annotation.HostSideTestThrow extends java.lang
flags: (0x2601) ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT, ACC_ANNOTATION
this_class: #x // android/hosttest/annotation/HostSideTestThrow
super_class: #x // java/lang/Object
- interfaces: 1, fields: 0, methods: 1, attributes: 2
- private static {};
- descriptor: ()V
- flags: (0x000a) ACC_PRIVATE, ACC_STATIC
- Code:
- stack=2, locals=0, args_size=0
- x: ldc #x // class android/hosttest/annotation/HostSideTestThrow
- x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded
- x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onClassLoaded:(Ljava/lang/Class;Ljava/lang/String;)V
- x: return
+ interfaces: 1, fields: 0, methods: 0, attributes: 2
+Constant pool:
+{
}
SourceFile: "HostSideTestThrow.java"
RuntimeVisibleAnnotations:
@@ -275,16 +216,9 @@ public interface android.hosttest.annotation.HostSideTestWholeClassKeep extends
flags: (0x2601) ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT, ACC_ANNOTATION
this_class: #x // android/hosttest/annotation/HostSideTestWholeClassKeep
super_class: #x // java/lang/Object
- interfaces: 1, fields: 0, methods: 1, attributes: 2
- private static {};
- descriptor: ()V
- flags: (0x000a) ACC_PRIVATE, ACC_STATIC
- Code:
- stack=2, locals=0, args_size=0
- x: ldc #x // class android/hosttest/annotation/HostSideTestWholeClassKeep
- x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded
- x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onClassLoaded:(Ljava/lang/Class;Ljava/lang/String;)V
- x: return
+ interfaces: 1, fields: 0, methods: 0, attributes: 2
+Constant pool:
+{
}
SourceFile: "HostSideTestWholeClassKeep.java"
RuntimeVisibleAnnotations:
@@ -307,6 +241,8 @@ public class com.android.hoststubgen.test.tinyframework.IPretendingAidl$Stub$Pro
this_class: #x // com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub$Proxy
super_class: #x // java/lang/Object
interfaces: 0, fields: 0, methods: 3, attributes: 4
+Constant pool:
+{
private static {};
descriptor: ()V
flags: (0x000a) ACC_PRIVATE, ACC_STATIC
@@ -377,6 +313,8 @@ public class com.android.hoststubgen.test.tinyframework.IPretendingAidl$Stub
this_class: #x // com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub
super_class: #x // java/lang/Object
interfaces: 0, fields: 0, methods: 3, attributes: 4
+Constant pool:
+{
private static {};
descriptor: ()V
flags: (0x000a) ACC_PRIVATE, ACC_STATIC
@@ -447,6 +385,8 @@ public interface com.android.hoststubgen.test.tinyframework.IPretendingAidl
this_class: #x // com/android/hoststubgen/test/tinyframework/IPretendingAidl
super_class: #x // java/lang/Object
interfaces: 0, fields: 0, methods: 1, attributes: 4
+Constant pool:
+{
private static {};
descriptor: ()V
flags: (0x000a) ACC_PRIVATE, ACC_STATIC
@@ -476,6 +416,8 @@ public class com.android.hoststubgen.test.tinyframework.R$Nested
this_class: #x // com/android/hoststubgen/test/tinyframework/R$Nested
super_class: #x // java/lang/Object
interfaces: 0, fields: 1, methods: 2, attributes: 4
+Constant pool:
+{
public static int[] ARRAY;
descriptor: [I
flags: (0x0009) ACC_PUBLIC, ACC_STATIC
@@ -546,6 +488,8 @@ public class com.android.hoststubgen.test.tinyframework.R
this_class: #x // com/android/hoststubgen/test/tinyframework/R
super_class: #x // java/lang/Object
interfaces: 0, fields: 0, methods: 2, attributes: 4
+Constant pool:
+{
private static {};
descriptor: ()V
flags: (0x000a) ACC_PRIVATE, ACC_STATIC
@@ -594,6 +538,8 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkAnnotations
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotations
super_class: #x // java/lang/Object
interfaces: 0, fields: 1, methods: 7, attributes: 3
+Constant pool:
+{
public int keep;
descriptor: I
flags: (0x0001) ACC_PUBLIC
@@ -785,6 +731,8 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHo
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkClassLoadHook
super_class: #x // java/lang/Object
interfaces: 0, fields: 1, methods: 3, attributes: 3
+Constant pool:
+{
public static final java.util.Set<java.lang.Class<?>> sLoadedClasses;
descriptor: Ljava/util/Set;
flags: (0x0019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL
@@ -880,6 +828,8 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassWideAn
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWideAnnotations
super_class: #x // java/lang/Object
interfaces: 0, fields: 1, methods: 5, attributes: 3
+Constant pool:
+{
public int keep;
descriptor: I
flags: (0x0001) ACC_PUBLIC
@@ -1010,6 +960,8 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassWithIn
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWithInitializerDefault
super_class: #x // java/lang/Object
interfaces: 0, fields: 2, methods: 0, attributes: 3
+Constant pool:
+{
public static boolean sInitialized;
descriptor: Z
flags: (0x0009) ACC_PUBLIC, ACC_STATIC
@@ -1047,6 +999,8 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassWithIn
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWithInitializerStub
super_class: #x // java/lang/Object
interfaces: 0, fields: 2, methods: 1, attributes: 3
+Constant pool:
+{
public static boolean sInitialized;
descriptor: Z
flags: (0x0009) ACC_PUBLIC, ACC_STATIC
@@ -1117,6 +1071,8 @@ public final class com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumC
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex
super_class: #x // java/lang/Enum
interfaces: 0, fields: 6, methods: 7, attributes: 4
+Constant pool:
+{
public static final com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumComplex RED;
descriptor: Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
flags: (0x4019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM
@@ -1400,6 +1356,8 @@ public final class com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumS
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple
super_class: #x // java/lang/Enum
interfaces: 0, fields: 3, methods: 5, attributes: 4
+Constant pool:
+{
public static final com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumSimple CAT;
descriptor: Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;
flags: (0x4019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM
@@ -1576,6 +1534,8 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkExceptionTe
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkExceptionTester
super_class: #x // java/lang/Object
interfaces: 0, fields: 0, methods: 3, attributes: 3
+Constant pool:
+{
private static {};
descriptor: ()V
flags: (0x000a) ACC_PRIVATE, ACC_STATIC
@@ -1659,6 +1619,8 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkForTextPoli
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy
super_class: #x // java/lang/Object
interfaces: 0, fields: 1, methods: 15, attributes: 2
+Constant pool:
+{
public int stub;
descriptor: I
flags: (0x0001) ACC_PUBLIC
@@ -1971,6 +1933,8 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkLambdas$Nes
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkLambdas$Nested
super_class: #x // java/lang/Object
interfaces: 0, fields: 2, methods: 8, attributes: 6
+Constant pool:
+{
public final java.util.function.Supplier<java.lang.Integer> mSupplier;
descriptor: Ljava/util/function/Supplier;
flags: (0x0011) ACC_PUBLIC, ACC_FINAL
@@ -2201,6 +2165,8 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkLambdas
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkLambdas
super_class: #x // java/lang/Object
interfaces: 0, fields: 2, methods: 8, attributes: 6
+Constant pool:
+{
public final java.util.function.Supplier<java.lang.Integer> mSupplier;
descriptor: Ljava/util/function/Supplier;
flags: (0x0011) ACC_PUBLIC, ACC_FINAL
@@ -2432,6 +2398,8 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkMethodCallR
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ReplaceTo
super_class: #x // java/lang/Object
interfaces: 0, fields: 0, methods: 4, attributes: 4
+Constant pool:
+{
private static {};
descriptor: ()V
flags: (0x000a) ACC_PRIVATE, ACC_STATIC
@@ -2526,6 +2494,8 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkMethodCallR
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace
super_class: #x // java/lang/Object
interfaces: 0, fields: 0, methods: 5, attributes: 6
+Constant pool:
+{
private static {};
descriptor: ()V
flags: (0x000a) ACC_PRIVATE, ACC_STATIC
@@ -2665,6 +2635,8 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNative
super_class: #x // java/lang/Object
interfaces: 0, fields: 1, methods: 15, attributes: 3
+Constant pool:
+{
int value;
descriptor: I
flags: (0x0000)
@@ -2998,6 +2970,8 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative_host
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host
super_class: #x // java/lang/Object
interfaces: 0, fields: 0, methods: 8, attributes: 3
+Constant pool:
+{
private static {};
descriptor: ()V
flags: (0x000a) ACC_PRIVATE, ACC_STATIC
@@ -3173,6 +3147,8 @@ class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$1 ex
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$1
super_class: #x // java/lang/Object
interfaces: 1, fields: 1, methods: 4, attributes: 6
+Constant pool:
+{
final com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses this$0;
descriptor: Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses;
flags: (0x1010) ACC_FINAL, ACC_SYNTHETIC
@@ -3278,6 +3254,8 @@ class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$2 ex
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$2
super_class: #x // java/lang/Object
interfaces: 1, fields: 0, methods: 4, attributes: 6
+Constant pool:
+{
private static {};
descriptor: ()V
flags: (0x000a) ACC_PRIVATE, ACC_STATIC
@@ -3369,6 +3347,8 @@ class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$3 ex
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$3
super_class: #x // java/lang/Object
interfaces: 1, fields: 1, methods: 4, attributes: 6
+Constant pool:
+{
final com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses this$0;
descriptor: Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses;
flags: (0x1010) ACC_FINAL, ACC_SYNTHETIC
@@ -3474,6 +3454,8 @@ class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$4 ex
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$4
super_class: #x // java/lang/Object
interfaces: 1, fields: 0, methods: 4, attributes: 6
+Constant pool:
+{
private static {};
descriptor: ()V
flags: (0x000a) ACC_PRIVATE, ACC_STATIC
@@ -3565,6 +3547,8 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClass
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$BaseClass
super_class: #x // java/lang/Object
interfaces: 0, fields: 1, methods: 2, attributes: 4
+Constant pool:
+{
public int value;
descriptor: I
flags: (0x0001) ACC_PUBLIC
@@ -3623,6 +3607,8 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClass
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$InnerClass
super_class: #x // java/lang/Object
interfaces: 0, fields: 2, methods: 2, attributes: 4
+Constant pool:
+{
public int value;
descriptor: I
flags: (0x0001) ACC_PUBLIC
@@ -3694,6 +3680,8 @@ class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$Stat
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1
super_class: #x // java/lang/Object
interfaces: 1, fields: 0, methods: 4, attributes: 6
+Constant pool:
+{
private static {};
descriptor: ()V
flags: (0x000a) ACC_PRIVATE, ACC_STATIC
@@ -3786,6 +3774,8 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClass
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$Double$NestedClass
super_class: #x // java/lang/Object
interfaces: 0, fields: 1, methods: 2, attributes: 4
+Constant pool:
+{
public int value;
descriptor: I
flags: (0x0001) ACC_PUBLIC
@@ -3844,6 +3834,8 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClass
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass
super_class: #x // java/lang/Object
interfaces: 0, fields: 1, methods: 3, attributes: 4
+Constant pool:
+{
public int value;
descriptor: I
flags: (0x0001) ACC_PUBLIC
@@ -3923,6 +3915,8 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClass
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$SubClass
super_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$BaseClass
interfaces: 0, fields: 0, methods: 2, attributes: 4
+Constant pool:
+{
private static {};
descriptor: ()V
flags: (0x000a) ACC_PRIVATE, ACC_STATIC
@@ -3973,6 +3967,8 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClass
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
super_class: #x // java/lang/Object
interfaces: 0, fields: 2, methods: 4, attributes: 5
+Constant pool:
+{
public final java.util.function.Supplier<java.lang.Integer> mSupplier;
descriptor: Ljava/util/function/Supplier;
flags: (0x0011) ACC_PUBLIC, ACC_FINAL
@@ -4121,6 +4117,8 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkPackageRedi
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkPackageRedirect
super_class: #x // java/lang/Object
interfaces: 0, fields: 0, methods: 3, attributes: 3
+Constant pool:
+{
private static {};
descriptor: ()V
flags: (0x000a) ACC_PRIVATE, ACC_STATIC
@@ -4192,6 +4190,8 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkRenamedClas
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkRenamedClassCaller
super_class: #x // java/lang/Object
interfaces: 0, fields: 0, methods: 3, attributes: 3
+Constant pool:
+{
private static {};
descriptor: ()V
flags: (0x000a) ACC_PRIVATE, ACC_STATIC
@@ -4263,6 +4263,8 @@ public class com.android.hoststubgen.test.tinyframework.packagetest.A
this_class: #x // com/android/hoststubgen/test/tinyframework/packagetest/A
super_class: #x // java/lang/Object
interfaces: 0, fields: 0, methods: 1, attributes: 2
+Constant pool:
+{
private static {};
descriptor: ()V
flags: (0x000a) ACC_PRIVATE, ACC_STATIC
@@ -4286,6 +4288,8 @@ public class com.android.hoststubgen.test.tinyframework.packagetest.sub.A
this_class: #x // com/android/hoststubgen/test/tinyframework/packagetest/sub/A
super_class: #x // java/lang/Object
interfaces: 0, fields: 0, methods: 1, attributes: 2
+Constant pool:
+{
private static {};
descriptor: ()V
flags: (0x000a) ACC_PRIVATE, ACC_STATIC
@@ -4309,6 +4313,8 @@ public class com.android.hoststubgen.test.tinyframework.subclasstest.C1
this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/C1
super_class: #x // java/lang/Object
interfaces: 0, fields: 0, methods: 1, attributes: 2
+Constant pool:
+{
private static {};
descriptor: ()V
flags: (0x000a) ACC_PRIVATE, ACC_STATIC
@@ -4332,6 +4338,8 @@ public class com.android.hoststubgen.test.tinyframework.subclasstest.C2 extends
this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/C2
super_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/C1
interfaces: 0, fields: 0, methods: 1, attributes: 2
+Constant pool:
+{
private static {};
descriptor: ()V
flags: (0x000a) ACC_PRIVATE, ACC_STATIC
@@ -4355,6 +4363,8 @@ public class com.android.hoststubgen.test.tinyframework.subclasstest.C3 extends
this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/C3
super_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/C2
interfaces: 0, fields: 0, methods: 1, attributes: 2
+Constant pool:
+{
private static {};
descriptor: ()V
flags: (0x000a) ACC_PRIVATE, ACC_STATIC
@@ -4378,6 +4388,8 @@ public class com.android.hoststubgen.test.tinyframework.subclasstest.CA
this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/CA
super_class: #x // java/lang/Object
interfaces: 0, fields: 0, methods: 1, attributes: 2
+Constant pool:
+{
private static {};
descriptor: ()V
flags: (0x000a) ACC_PRIVATE, ACC_STATIC
@@ -4401,6 +4413,8 @@ public class com.android.hoststubgen.test.tinyframework.subclasstest.CB
this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/CB
super_class: #x // java/lang/Object
interfaces: 0, fields: 0, methods: 1, attributes: 2
+Constant pool:
+{
private static {};
descriptor: ()V
flags: (0x000a) ACC_PRIVATE, ACC_STATIC
@@ -4424,6 +4438,8 @@ public class com.android.hoststubgen.test.tinyframework.subclasstest.Class_C1 ex
this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/Class_C1
super_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/C1
interfaces: 0, fields: 0, methods: 1, attributes: 2
+Constant pool:
+{
private static {};
descriptor: ()V
flags: (0x000a) ACC_PRIVATE, ACC_STATIC
@@ -4447,6 +4463,8 @@ public class com.android.hoststubgen.test.tinyframework.subclasstest.Class_C2 ex
this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/Class_C2
super_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/C2
interfaces: 0, fields: 0, methods: 1, attributes: 2
+Constant pool:
+{
private static {};
descriptor: ()V
flags: (0x000a) ACC_PRIVATE, ACC_STATIC
@@ -4470,6 +4488,8 @@ public class com.android.hoststubgen.test.tinyframework.subclasstest.Class_C3 ex
this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/Class_C3
super_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/C3
interfaces: 0, fields: 0, methods: 1, attributes: 2
+Constant pool:
+{
private static {};
descriptor: ()V
flags: (0x000a) ACC_PRIVATE, ACC_STATIC
@@ -4493,6 +4513,8 @@ public class com.android.hoststubgen.test.tinyframework.subclasstest.Class_I1 im
this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/Class_I1
super_class: #x // java/lang/Object
interfaces: 1, fields: 0, methods: 1, attributes: 2
+Constant pool:
+{
private static {};
descriptor: ()V
flags: (0x000a) ACC_PRIVATE, ACC_STATIC
@@ -4516,6 +4538,8 @@ public class com.android.hoststubgen.test.tinyframework.subclasstest.Class_I1_IA
this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/Class_I1_IA
super_class: #x // java/lang/Object
interfaces: 2, fields: 0, methods: 1, attributes: 2
+Constant pool:
+{
private static {};
descriptor: ()V
flags: (0x000a) ACC_PRIVATE, ACC_STATIC
@@ -4539,6 +4563,8 @@ public class com.android.hoststubgen.test.tinyframework.subclasstest.Class_I2 im
this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/Class_I2
super_class: #x // java/lang/Object
interfaces: 1, fields: 0, methods: 1, attributes: 2
+Constant pool:
+{
private static {};
descriptor: ()V
flags: (0x000a) ACC_PRIVATE, ACC_STATIC
@@ -4562,6 +4588,8 @@ public class com.android.hoststubgen.test.tinyframework.subclasstest.Class_I3 im
this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/Class_I3
super_class: #x // java/lang/Object
interfaces: 1, fields: 0, methods: 1, attributes: 2
+Constant pool:
+{
private static {};
descriptor: ()V
flags: (0x000a) ACC_PRIVATE, ACC_STATIC
@@ -4585,6 +4613,8 @@ public interface com.android.hoststubgen.test.tinyframework.subclasstest.I1
this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/I1
super_class: #x // java/lang/Object
interfaces: 0, fields: 0, methods: 1, attributes: 2
+Constant pool:
+{
private static {};
descriptor: ()V
flags: (0x000a) ACC_PRIVATE, ACC_STATIC
@@ -4608,6 +4638,8 @@ public interface com.android.hoststubgen.test.tinyframework.subclasstest.I2 exte
this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/I2
super_class: #x // java/lang/Object
interfaces: 1, fields: 0, methods: 1, attributes: 2
+Constant pool:
+{
private static {};
descriptor: ()V
flags: (0x000a) ACC_PRIVATE, ACC_STATIC
@@ -4631,6 +4663,8 @@ public interface com.android.hoststubgen.test.tinyframework.subclasstest.I3 exte
this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/I3
super_class: #x // java/lang/Object
interfaces: 1, fields: 0, methods: 1, attributes: 2
+Constant pool:
+{
private static {};
descriptor: ()V
flags: (0x000a) ACC_PRIVATE, ACC_STATIC
@@ -4654,6 +4688,8 @@ public interface com.android.hoststubgen.test.tinyframework.subclasstest.IA
this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/IA
super_class: #x // java/lang/Object
interfaces: 0, fields: 0, methods: 1, attributes: 2
+Constant pool:
+{
private static {};
descriptor: ()V
flags: (0x000a) ACC_PRIVATE, ACC_STATIC
@@ -4677,6 +4713,8 @@ public interface com.android.hoststubgen.test.tinyframework.subclasstest.IB
this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/IB
super_class: #x // java/lang/Object
interfaces: 0, fields: 0, methods: 1, attributes: 2
+Constant pool:
+{
private static {};
descriptor: ()V
flags: (0x000a) ACC_PRIVATE, ACC_STATIC
@@ -4700,6 +4738,8 @@ public class com.supported.UnsupportedClass
this_class: #x // com/supported/UnsupportedClass
super_class: #x // java/lang/Object
interfaces: 0, fields: 1, methods: 3, attributes: 3
+Constant pool:
+{
private final int mValue;
descriptor: I
flags: (0x0012) ACC_PRIVATE, ACC_FINAL
@@ -4779,6 +4819,8 @@ public class com.unsupported.UnsupportedClass
this_class: #x // com/unsupported/UnsupportedClass
super_class: #x // java/lang/Object
interfaces: 0, fields: 0, methods: 3, attributes: 3
+Constant pool:
+{
private static {};
descriptor: ()V
flags: (0x000a) ACC_PRIVATE, ACC_STATIC
@@ -4854,6 +4896,8 @@ public class rename_prefix.com.android.hoststubgen.test.tinyframework.TinyFramew
this_class: #x // rename_prefix/com/android/hoststubgen/test/tinyframework/TinyFrameworkToBeRenamed
super_class: #x // java/lang/Object
interfaces: 0, fields: 1, methods: 3, attributes: 3
+Constant pool:
+{
private final int mValue;
descriptor: I
flags: (0x0012) ACC_PRIVATE, ACC_FINAL
diff --git a/services/accessibility/accessibility.aconfig b/services/accessibility/accessibility.aconfig
index 529a564ea607..bb0eacb5afa7 100644
--- a/services/accessibility/accessibility.aconfig
+++ b/services/accessibility/accessibility.aconfig
@@ -145,6 +145,13 @@ flag {
}
flag {
+ name: "enable_magnification_follows_mouse_with_pointer_motion_filter"
+ namespace: "accessibility"
+ description: "Whether to enable mouse following using pointer motion filter"
+ bug: "361817142"
+}
+
+flag {
name: "enable_magnification_keyboard_control"
namespace: "accessibility"
description: "Whether to enable keyboard control for magnification"
diff --git a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java
index d0ee7af1bbfb..5191fb5f51cb 100644
--- a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java
+++ b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java
@@ -20,6 +20,8 @@ import static android.app.appfunctions.AppFunctionRuntimeMetadata.APP_FUNCTION_R
import static android.app.appfunctions.AppFunctionRuntimeMetadata.APP_FUNCTION_RUNTIME_NAMESPACE;
import static com.android.server.appfunctions.AppFunctionExecutors.THREAD_POOL_EXECUTOR;
+import static com.android.server.appfunctions.CallerValidator.CAN_EXECUTE_APP_FUNCTIONS_ALLOWED_HAS_PERMISSION;
+import static com.android.server.appfunctions.CallerValidator.CAN_EXECUTE_APP_FUNCTIONS_DENIED;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -236,30 +238,42 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub {
requestInternal.getCallingPackage(),
targetPackageName,
requestInternal.getClientRequest().getFunctionIdentifier())
- .thenAccept(
- canExecute -> {
- if (!canExecute) {
- throw new SecurityException(
+ .thenCompose(
+ canExecuteResult -> {
+ if (canExecuteResult == CAN_EXECUTE_APP_FUNCTIONS_DENIED) {
+ return AndroidFuture.failedFuture(new SecurityException(
"Caller does not have permission to execute the"
- + " appfunction");
+ + " appfunction"));
}
+ return isAppFunctionEnabled(
+ requestInternal
+ .getClientRequest()
+ .getFunctionIdentifier(),
+ requestInternal
+ .getClientRequest()
+ .getTargetPackageName(),
+ getAppSearchManagerAsUser(
+ requestInternal.getUserHandle()),
+ THREAD_POOL_EXECUTOR)
+ .thenApply(
+ isEnabled -> {
+ if (!isEnabled) {
+ throw new DisabledAppFunctionException(
+ "The app function is disabled");
+ }
+ return canExecuteResult;
+ });
})
- .thenCompose(
- isEnabled ->
- isAppFunctionEnabled(
- requestInternal.getClientRequest().getFunctionIdentifier(),
- requestInternal.getClientRequest().getTargetPackageName(),
- getAppSearchManagerAsUser(requestInternal.getUserHandle()),
- THREAD_POOL_EXECUTOR))
.thenAccept(
- isEnabled -> {
- if (!isEnabled) {
- throw new DisabledAppFunctionException(
- "The app function is disabled");
+ canExecuteResult -> {
+ int bindFlags = Context.BIND_AUTO_CREATE;
+ if (canExecuteResult
+ == CAN_EXECUTE_APP_FUNCTIONS_ALLOWED_HAS_PERMISSION) {
+ // If the caller doesn't have the permission, do not use
+ // BIND_FOREGROUND_SERVICE to avoid it raising its process state by
+ // calling its own AppFunctions.
+ bindFlags |= Context.BIND_FOREGROUND_SERVICE;
}
- })
- .thenAccept(
- unused -> {
Intent serviceIntent =
mInternalServiceHelper.resolveAppFunctionService(
targetPackageName, targetUser);
@@ -294,8 +308,7 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub {
targetUser,
localCancelTransport,
safeExecuteAppFunctionCallback,
- /* bindFlags= */ Context.BIND_AUTO_CREATE
- | Context.BIND_FOREGROUND_SERVICE,
+ bindFlags,
callerBinder,
callingUid);
})
diff --git a/services/appfunctions/java/com/android/server/appfunctions/CallerValidator.java b/services/appfunctions/java/com/android/server/appfunctions/CallerValidator.java
index 98ef974b9443..c8038a4e56df 100644
--- a/services/appfunctions/java/com/android/server/appfunctions/CallerValidator.java
+++ b/services/appfunctions/java/com/android/server/appfunctions/CallerValidator.java
@@ -17,12 +17,16 @@
package com.android.server.appfunctions;
import android.Manifest;
+import android.annotation.IntDef;
import android.annotation.NonNull;
import android.os.UserHandle;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.infra.AndroidFuture;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
/**
* Interface for validating that the caller has the correct privilege to call an AppFunctionManager
* API.
@@ -70,7 +74,8 @@ public interface CallerValidator {
* @param functionId The id of the app function to execute.
* @return Whether the caller can execute the specified app function.
*/
- AndroidFuture<Boolean> verifyCallerCanExecuteAppFunction(
+ @CanExecuteAppFunctionResult
+ AndroidFuture<Integer> verifyCallerCanExecuteAppFunction(
int callingUid,
int callingPid,
@NonNull UserHandle targetUser,
@@ -78,6 +83,31 @@ public interface CallerValidator {
@NonNull String targetPackageName,
@NonNull String functionId);
+ @IntDef(
+ prefix = {"CAN_EXECUTE_APP_FUNCTIONS_"},
+ value = {
+ CAN_EXECUTE_APP_FUNCTIONS_DENIED,
+ CAN_EXECUTE_APP_FUNCTIONS_ALLOWED_SAME_PACKAGE,
+ CAN_EXECUTE_APP_FUNCTIONS_ALLOWED_HAS_PERMISSION,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @interface CanExecuteAppFunctionResult {}
+
+ /** Callers are not allowed to execute app functions. */
+ int CAN_EXECUTE_APP_FUNCTIONS_DENIED = 0;
+
+ /**
+ * Callers can execute app functions because they are calling app functions from the same
+ * package.
+ */
+ int CAN_EXECUTE_APP_FUNCTIONS_ALLOWED_SAME_PACKAGE = 1;
+
+ /**
+ * Callers can execute app functions because they have the necessary permission.
+ * This case also applies when a caller with the permission invokes their own app functions.
+ */
+ int CAN_EXECUTE_APP_FUNCTIONS_ALLOWED_HAS_PERMISSION = 2;
+
/**
* Checks if the app function policy is allowed.
*
diff --git a/services/appfunctions/java/com/android/server/appfunctions/CallerValidatorImpl.java b/services/appfunctions/java/com/android/server/appfunctions/CallerValidatorImpl.java
index fe163d77c4fc..3f8b2e3316dc 100644
--- a/services/appfunctions/java/com/android/server/appfunctions/CallerValidatorImpl.java
+++ b/services/appfunctions/java/com/android/server/appfunctions/CallerValidatorImpl.java
@@ -16,24 +16,12 @@
package com.android.server.appfunctions;
-import static android.app.appfunctions.AppFunctionStaticMetadataHelper.APP_FUNCTION_STATIC_METADATA_DB;
-import static android.app.appfunctions.AppFunctionStaticMetadataHelper.APP_FUNCTION_STATIC_NAMESPACE;
-import static android.app.appfunctions.AppFunctionStaticMetadataHelper.getDocumentIdForAppFunction;
-
-import static com.android.server.appfunctions.AppFunctionExecutors.THREAD_POOL_EXECUTOR;
-
import android.Manifest;
import android.annotation.BinderThread;
import android.annotation.NonNull;
import android.annotation.RequiresPermission;
import android.app.admin.DevicePolicyManager;
import android.app.admin.DevicePolicyManager.AppFunctionsPolicy;
-import android.app.appsearch.AppSearchBatchResult;
-import android.app.appsearch.AppSearchManager;
-import android.app.appsearch.AppSearchManager.SearchContext;
-import android.app.appsearch.AppSearchResult;
-import android.app.appsearch.GenericDocument;
-import android.app.appsearch.GetByDocumentIdRequest;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Binder;
@@ -84,64 +72,25 @@ class CallerValidatorImpl implements CallerValidator {
@Override
@RequiresPermission(Manifest.permission.EXECUTE_APP_FUNCTIONS)
- public AndroidFuture<Boolean> verifyCallerCanExecuteAppFunction(
+ @CanExecuteAppFunctionResult
+ public AndroidFuture<Integer> verifyCallerCanExecuteAppFunction(
int callingUid,
int callingPid,
@NonNull UserHandle targetUser,
@NonNull String callerPackageName,
@NonNull String targetPackageName,
@NonNull String functionId) {
- if (callerPackageName.equals(targetPackageName)) {
- return AndroidFuture.completedFuture(true);
- }
-
boolean hasExecutionPermission =
mContext.checkPermission(
- Manifest.permission.EXECUTE_APP_FUNCTIONS, callingPid, callingUid)
+ Manifest.permission.EXECUTE_APP_FUNCTIONS, callingPid, callingUid)
== PackageManager.PERMISSION_GRANTED;
-
- if (!hasExecutionPermission) {
- return AndroidFuture.completedFuture(false);
+ if (hasExecutionPermission) {
+ return AndroidFuture.completedFuture(CAN_EXECUTE_APP_FUNCTIONS_ALLOWED_HAS_PERMISSION);
}
-
- FutureAppSearchSession futureAppSearchSession =
- new FutureAppSearchSessionImpl(
- Objects.requireNonNull(
- mContext.createContextAsUser(targetUser, 0)
- .getSystemService(AppSearchManager.class)),
- THREAD_POOL_EXECUTOR,
- new SearchContext.Builder(APP_FUNCTION_STATIC_METADATA_DB).build());
-
- String documentId = getDocumentIdForAppFunction(targetPackageName, functionId);
-
- return futureAppSearchSession
- .getByDocumentId(
- new GetByDocumentIdRequest.Builder(APP_FUNCTION_STATIC_NAMESPACE)
- .addIds(documentId)
- .build())
- .thenApply(
- batchResult -> getGenericDocumentFromBatchResult(batchResult, documentId))
- // At this point, already checked the app has the permission.
- .thenApply(document -> true)
- .whenComplete(
- (result, throwable) -> {
- futureAppSearchSession.close();
- });
- }
-
- private static GenericDocument getGenericDocumentFromBatchResult(
- AppSearchBatchResult<String, GenericDocument> result, String documentId) {
- if (result.isSuccess()) {
- return result.getSuccesses().get(documentId);
+ if (callerPackageName.equals(targetPackageName)) {
+ return AndroidFuture.completedFuture(CAN_EXECUTE_APP_FUNCTIONS_ALLOWED_SAME_PACKAGE);
}
-
- AppSearchResult<GenericDocument> failedResult = result.getFailures().get(documentId);
- throw new AppSearchException(
- failedResult.getResultCode(),
- "Unable to retrieve document with id: "
- + documentId
- + " due to "
- + failedResult.getErrorMessage());
+ return AndroidFuture.completedFuture(CAN_EXECUTE_APP_FUNCTIONS_DENIED);
}
@Override
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
index f03e8c713228..28efdfc01ee2 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
@@ -163,6 +163,16 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
*/
private static final long PENDING_TRAMPOLINE_TIMEOUT_MS = 5000;
+ /**
+ * Global lock for this Virtual Device.
+ *
+ * Never call outside this class while holding this lock. A number of other system services like
+ * WindowManager, DisplayManager, etc. call into this device to get device-specific information,
+ * while holding their own global locks.
+ *
+ * Making a call to another service while holding this lock creates lock order inversion and
+ * will potentially cause a deadlock.
+ */
private final Object mVirtualDeviceLock = new Object();
private final int mBaseVirtualDisplayFlags;
@@ -179,14 +189,6 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
private final int mDeviceId;
@Nullable
private final String mPersistentDeviceId;
- // Thou shall not hold the mVirtualDeviceLock over the mInputController calls.
- // Holding the lock can lead to lock inversion with GlobalWindowManagerLock.
- // 1. After display is created the window manager calls into VDM during construction
- // of display specific context to fetch device id corresponding to the display.
- // mVirtualDeviceLock will be held while this is done.
- // 2. InputController interactions result in calls to DisplayManager (to set IME,
- // possibly more indirect calls), and those attempt to lock GlobalWindowManagerLock which
- // creates lock inversion.
private final InputController mInputController;
private final SensorController mSensorController;
private final CameraAccessController mCameraAccessController;
@@ -205,19 +207,23 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
private final DisplayManagerGlobal mDisplayManager;
private final DisplayManagerInternal mDisplayManagerInternal;
private final PowerManager mPowerManager;
- @GuardedBy("mVirtualDeviceLock")
+ @GuardedBy("mIntentInterceptors")
private final Map<IBinder, IntentFilter> mIntentInterceptors = new ArrayMap<>();
@NonNull
private final Consumer<ArraySet<Integer>> mRunningAppsChangedCallback;
+
// The default setting for showing the pointer on new displays.
@GuardedBy("mVirtualDeviceLock")
private boolean mDefaultShowPointerIcon = true;
@GuardedBy("mVirtualDeviceLock")
@Nullable
private LocaleList mLocaleList = null;
- @GuardedBy("mVirtualDeviceLock")
+
+ // Lock for power operations for this virtual device that allow calling PowerManager.
+ private final Object mPowerLock = new Object();
+ @GuardedBy("mPowerLock")
private boolean mLockdownActive = false;
- @GuardedBy("mVirtualDeviceLock")
+ @GuardedBy("mPowerLock")
private boolean mRequestedToBeAwake = true;
@NonNull
@@ -334,7 +340,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
*/
@Override
public boolean shouldInterceptIntent(@NonNull Intent intent) {
- synchronized (mVirtualDeviceLock) {
+ synchronized (mIntentInterceptors) {
boolean hasInterceptedIntent = false;
for (Map.Entry<IBinder, IntentFilter> interceptor
: mIntentInterceptors.entrySet()) {
@@ -500,7 +506,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
}
void onLockdownChanged(boolean lockdownActive) {
- synchronized (mVirtualDeviceLock) {
+ synchronized (mPowerLock) {
if (lockdownActive != mLockdownActive) {
mLockdownActive = lockdownActive;
if (mLockdownActive) {
@@ -610,7 +616,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
@Override // Binder call
public void goToSleep() {
checkCallerIsDeviceOwner();
- synchronized (mVirtualDeviceLock) {
+ synchronized (mPowerLock) {
mRequestedToBeAwake = false;
}
final long ident = Binder.clearCallingIdentity();
@@ -624,7 +630,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
@Override // Binder call
public void wakeUp() {
checkCallerIsDeviceOwner();
- synchronized (mVirtualDeviceLock) {
+ synchronized (mPowerLock) {
mRequestedToBeAwake = true;
if (mLockdownActive) {
Slog.w(TAG, "Cannot wake up device during lockdown.");
@@ -850,8 +856,8 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
checkDisplayOwnedByVirtualDeviceLocked(displayId);
if (mVirtualAudioController == null) {
mVirtualAudioController = new VirtualAudioController(mContext, mAttributionSource);
- GenericWindowPolicyController gwpc = mVirtualDisplays.get(
- displayId).getWindowPolicyController();
+ GenericWindowPolicyController gwpc =
+ mVirtualDisplays.get(displayId).getWindowPolicyController();
mVirtualAudioController.startListening(gwpc, routingCallback,
configChangedCallback);
}
@@ -1293,7 +1299,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
checkCallerIsDeviceOwner();
Objects.requireNonNull(intentInterceptor);
Objects.requireNonNull(filter);
- synchronized (mVirtualDeviceLock) {
+ synchronized (mIntentInterceptors) {
mIntentInterceptors.put(intentInterceptor.asBinder(), filter);
}
}
@@ -1303,7 +1309,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
@NonNull IVirtualDeviceIntentInterceptor intentInterceptor) {
checkCallerIsDeviceOwner();
Objects.requireNonNull(intentInterceptor);
- synchronized (mVirtualDeviceLock) {
+ synchronized (mIntentInterceptors) {
mIntentInterceptors.remove(intentInterceptor.asBinder());
}
}
@@ -1653,6 +1659,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
void goToSleepInternal(@PowerManager.GoToSleepReason int reason) {
final long now = SystemClock.uptimeMillis();
+ IntArray displayIds = new IntArray();
synchronized (mVirtualDeviceLock) {
for (int i = 0; i < mVirtualDisplays.size(); i++) {
VirtualDisplayWrapper wrapper = mVirtualDisplays.valueAt(i);
@@ -1660,13 +1667,17 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
continue;
}
int displayId = mVirtualDisplays.keyAt(i);
- mPowerManager.goToSleep(displayId, now, reason, /* flags= */ 0);
+ displayIds.add(displayId);
}
}
+ for (int i = 0; i < displayIds.size(); ++i) {
+ mPowerManager.goToSleep(displayIds.get(i), now, reason, /* flags= */ 0);
+ }
}
void wakeUpInternal(@PowerManager.WakeReason int reason, String details) {
final long now = SystemClock.uptimeMillis();
+ IntArray displayIds = new IntArray();
synchronized (mVirtualDeviceLock) {
for (int i = 0; i < mVirtualDisplays.size(); i++) {
VirtualDisplayWrapper wrapper = mVirtualDisplays.valueAt(i);
@@ -1674,9 +1685,12 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
continue;
}
int displayId = mVirtualDisplays.keyAt(i);
- mPowerManager.wakeUp(now, reason, details, displayId);
+ displayIds.add(displayId);
}
}
+ for (int i = 0; i < displayIds.size(); ++i) {
+ mPowerManager.wakeUp(now, reason, details, displayIds.get(i));
+ }
}
/**
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
index ff82ca00b840..139bbae26289 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
@@ -117,7 +117,18 @@ public class VirtualDeviceManagerService extends SystemService {
*/
static final int CDM_ASSOCIATION_ID_NONE = 0;
+ /**
+ * Global VDM lock.
+ *
+ * Never call outside this class while holding this lock. A number of other system services like
+ * WindowManager, DisplayManager, etc. call into VDM to get device-specific information, while
+ * holding their own global locks.
+ *
+ * Making a call to another service while holding this lock creates lock order inversion and
+ * will potentially cause a deadlock.
+ */
private final Object mVirtualDeviceManagerLock = new Object();
+
private final VirtualDeviceManagerImpl mImpl;
private final VirtualDeviceManagerNativeImpl mNativeImpl;
private final VirtualDeviceManagerInternal mLocalService;
@@ -235,10 +246,9 @@ public class VirtualDeviceManagerService extends SystemService {
// Called when the global lockdown state changes, i.e. lockdown is considered active if any user
// is in lockdown mode, and inactive if no users are in lockdown mode.
void onLockdownChanged(boolean lockdownActive) {
- synchronized (mVirtualDeviceManagerLock) {
- for (int i = 0; i < mVirtualDevices.size(); i++) {
- mVirtualDevices.valueAt(i).onLockdownChanged(lockdownActive);
- }
+ ArrayList<VirtualDeviceImpl> virtualDevicesSnapshot = getVirtualDevicesSnapshot();
+ for (int i = 0; i < virtualDevicesSnapshot.size(); i++) {
+ virtualDevicesSnapshot.get(i).onLockdownChanged(lockdownActive);
}
}
@@ -264,16 +274,14 @@ public class VirtualDeviceManagerService extends SystemService {
return null;
}
int userId = userHandle.getIdentifier();
- synchronized (mVirtualDeviceManagerLock) {
- for (int i = 0; i < mVirtualDevices.size(); i++) {
- final CameraAccessController cameraAccessController =
- mVirtualDevices.valueAt(i).getCameraAccessController();
- if (cameraAccessController.getUserId() == userId) {
- return cameraAccessController;
- }
+ ArrayList<VirtualDeviceImpl> virtualDevicesSnapshot = getVirtualDevicesSnapshot();
+ for (int i = 0; i < virtualDevicesSnapshot.size(); i++) {
+ final CameraAccessController cameraAccessController =
+ virtualDevicesSnapshot.get(i).getCameraAccessController();
+ if (cameraAccessController.getUserId() == userId) {
+ return cameraAccessController;
}
- }
- Context userContext = getContext().createContextAsUser(userHandle, 0);
+ } Context userContext = getContext().createContextAsUser(userHandle, 0);
return new CameraAccessController(userContext, mLocalService, this::onCameraAccessBlocked);
}
@@ -520,11 +528,10 @@ public class VirtualDeviceManagerService extends SystemService {
@Override // Binder call
public List<VirtualDevice> getVirtualDevices() {
List<VirtualDevice> virtualDevices = new ArrayList<>();
- synchronized (mVirtualDeviceManagerLock) {
- for (int i = 0; i < mVirtualDevices.size(); i++) {
- final VirtualDeviceImpl device = mVirtualDevices.valueAt(i);
- virtualDevices.add(device.getPublicVirtualDeviceObject());
- }
+ ArrayList<VirtualDeviceImpl> virtualDevicesSnapshot = getVirtualDevicesSnapshot();
+ for (int i = 0; i < virtualDevicesSnapshot.size(); i++) {
+ VirtualDeviceImpl device = virtualDevicesSnapshot.get(i);
+ virtualDevices.add(device.getPublicVirtualDeviceObject());
}
return virtualDevices;
}
@@ -834,15 +841,17 @@ public class VirtualDeviceManagerService extends SystemService {
@Nullable
public LocaleList getPreferredLocaleListForUid(int uid) {
// TODO: b/263188984 support the case where an app is running on multiple VDs
+ VirtualDeviceImpl virtualDevice = null;
synchronized (mVirtualDeviceManagerLock) {
for (int i = 0; i < mAppsOnVirtualDevices.size(); i++) {
if (mAppsOnVirtualDevices.valueAt(i).contains(uid)) {
int deviceId = mAppsOnVirtualDevices.keyAt(i);
- return mVirtualDevices.get(deviceId).getDeviceLocaleList();
+ virtualDevice = mVirtualDevices.get(deviceId);
+ break;
}
}
}
- return null;
+ return virtualDevice == null ? null : virtualDevice.getDeviceLocaleList();
}
@Override
diff --git a/services/contextualsearch/java/com/android/server/contextualsearch/ContextualSearchManagerService.java b/services/contextualsearch/java/com/android/server/contextualsearch/ContextualSearchManagerService.java
index 700a1624f7d4..d8e10f842665 100644
--- a/services/contextualsearch/java/com/android/server/contextualsearch/ContextualSearchManagerService.java
+++ b/services/contextualsearch/java/com/android/server/contextualsearch/ContextualSearchManagerService.java
@@ -38,7 +38,6 @@ import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
import android.app.ActivityOptions;
import android.app.AppOpsManager;
-import android.app.admin.DevicePolicyManagerInternal;
import android.app.assist.AssistContent;
import android.app.assist.AssistStructure;
import android.app.contextualsearch.CallbackToken;
@@ -67,6 +66,7 @@ import android.os.ResultReceiver;
import android.os.ServiceManager;
import android.os.ShellCallback;
import android.os.SystemClock;
+import android.os.UserManager;
import android.provider.Settings;
import android.util.Log;
import android.util.Slog;
@@ -112,8 +112,8 @@ public class ContextualSearchManagerService extends SystemService {
private final ActivityTaskManagerInternal mAtmInternal;
private final PackageManagerInternal mPackageManager;
private final WindowManagerInternal mWmInternal;
- private final DevicePolicyManagerInternal mDpmInternal;
private final AudioManager mAudioManager;
+ private final UserManager mUserManager;
private final Object mLock = new Object();
private final AssistDataRequester mAssistDataRequester;
@@ -179,9 +179,9 @@ public class ContextualSearchManagerService extends SystemService {
LocalServices.getService(ActivityTaskManagerInternal.class));
mPackageManager = LocalServices.getService(PackageManagerInternal.class);
mAudioManager = context.getSystemService(AudioManager.class);
+ mUserManager = context.getSystemService(UserManager.class);
mWmInternal = Objects.requireNonNull(LocalServices.getService(WindowManagerInternal.class));
- mDpmInternal = LocalServices.getService(DevicePolicyManagerInternal.class);
mAssistDataRequester = new AssistDataRequester(
mContext,
IWindowManager.Stub.asInterface(ServiceManager.getService(Context.WINDOW_SERVICE)),
@@ -308,6 +308,11 @@ public class ContextualSearchManagerService extends SystemService {
}
}
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.MANAGE_USERS,
+ android.Manifest.permission.CREATE_USERS,
+ android.Manifest.permission.QUERY_USERS
+ })
private Intent getContextualSearchIntent(int entrypoint, int userId, CallbackToken mToken) {
final Intent launchIntent = getResolvedLaunchIntent(userId);
if (launchIntent == null) {
@@ -338,8 +343,7 @@ public class ContextualSearchManagerService extends SystemService {
visiblePackageNames.add(record.getComponentName().getPackageName());
activityTokens.add(record.getActivityToken());
}
- if (mDpmInternal != null
- && mDpmInternal.isUserOrganizationManaged(record.getUserId())) {
+ if (mUserManager.isManagedProfile(record.getUserId())) {
isManagedProfileVisible = true;
}
}
@@ -507,7 +511,10 @@ public class ContextualSearchManagerService extends SystemService {
Intent launchIntent = getContextualSearchIntent(entrypoint, callingUserId, mToken);
if (launchIntent != null) {
int result = invokeContextualSearchIntent(launchIntent, callingUserId);
- if (DEBUG) Log.d(TAG, "Launch result: " + result);
+ if (DEBUG) {
+ Log.d(TAG, "Launch intent: " + launchIntent);
+ Log.d(TAG, "Launch result: " + result);
+ }
}
});
}
diff --git a/services/core/java/com/android/server/UiModeManagerService.java b/services/core/java/com/android/server/UiModeManagerService.java
index 4976a63b016b..8eda17698b9b 100644
--- a/services/core/java/com/android/server/UiModeManagerService.java
+++ b/services/core/java/com/android/server/UiModeManagerService.java
@@ -18,7 +18,6 @@ package com.android.server;
import static android.app.Flags.enableCurrentModeTypeBinderCache;
import static android.app.Flags.enableNightModeBinderCache;
-import static android.app.Flags.modesApi;
import static android.app.UiModeManager.ContrastUtils.CONTRAST_DEFAULT_VALUE;
import static android.app.UiModeManager.DEFAULT_PRIORITY;
import static android.app.UiModeManager.FORCE_INVERT_TYPE_DARK;
@@ -2208,14 +2207,12 @@ final class UiModeManagerService extends SystemService {
appliedOverrides = true;
}
- if (modesApi()) {
- // Computes final night mode values based on Attention Mode.
- mComputedNightMode = switch (mAttentionModeThemeOverlay) {
- case (UiModeManager.MODE_ATTENTION_THEME_OVERLAY_NIGHT) -> true;
- case (UiModeManager.MODE_ATTENTION_THEME_OVERLAY_DAY) -> false;
- default -> newComputedValue; // case OFF
- };
- }
+ // Computes final night mode values based on Attention Mode.
+ mComputedNightMode = switch (mAttentionModeThemeOverlay) {
+ case (UiModeManager.MODE_ATTENTION_THEME_OVERLAY_NIGHT) -> true;
+ case (UiModeManager.MODE_ATTENTION_THEME_OVERLAY_DAY) -> false;
+ default -> newComputedValue; // case OFF
+ };
if (appliedOverrides) {
return;
diff --git a/services/core/java/com/android/server/am/AppStartInfoTracker.java b/services/core/java/com/android/server/am/AppStartInfoTracker.java
index 961022b7231b..517279bd7527 100644
--- a/services/core/java/com/android/server/am/AppStartInfoTracker.java
+++ b/services/core/java/com/android/server/am/AppStartInfoTracker.java
@@ -54,15 +54,21 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.ProcessMap;
import com.android.internal.os.Clock;
import com.android.internal.os.MonotonicClock;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
import com.android.server.IoThread;
import com.android.server.ServiceThread;
import com.android.server.SystemServiceManager;
import com.android.server.wm.WindowProcessController;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collections;
@@ -1006,6 +1012,12 @@ public final class AppStartInfoTracker {
throws IOException, WireTypeMismatchException, ClassNotFoundException {
long token = proto.start(fieldId);
String pkgName = "";
+
+ // Create objects for reuse.
+ ByteArrayInputStream byteArrayInputStream = null;
+ ObjectInputStream objectInputStream = null;
+ TypedXmlPullParser typedXmlPullParser = null;
+
for (int next = proto.nextField();
next != ProtoInputStream.NO_MORE_FIELDS;
next = proto.nextField()) {
@@ -1017,7 +1029,7 @@ public final class AppStartInfoTracker {
AppStartInfoContainer container =
new AppStartInfoContainer(mAppStartInfoHistoryListSize);
int uid = container.readFromProto(proto, AppsStartInfoProto.Package.USERS,
- pkgName);
+ pkgName, byteArrayInputStream, objectInputStream, typedXmlPullParser);
// If the isolated process flag is enabled and the uid is that of an isolated
// process, then break early so that the container will not be added to mData.
@@ -1052,6 +1064,12 @@ public final class AppStartInfoTracker {
out = af.startWrite();
ProtoOutputStream proto = new ProtoOutputStream(out);
proto.write(AppsStartInfoProto.LAST_UPDATE_TIMESTAMP, now);
+
+ // Create objects for reuse.
+ ByteArrayOutputStream byteArrayOutputStream = null;
+ ObjectOutputStream objectOutputStream = null;
+ TypedXmlSerializer typedXmlSerializer = null;
+
synchronized (mLock) {
succeeded = forEachPackageLocked(
(packageName, records) -> {
@@ -1060,8 +1078,9 @@ public final class AppStartInfoTracker {
int uidArraySize = records.size();
for (int j = 0; j < uidArraySize; j++) {
try {
- records.valueAt(j)
- .writeToProto(proto, AppsStartInfoProto.Package.USERS);
+ records.valueAt(j).writeToProto(proto,
+ AppsStartInfoProto.Package.USERS, byteArrayOutputStream,
+ objectOutputStream, typedXmlSerializer);
} catch (IOException e) {
Slog.w(TAG, "Unable to write app start info into persistent"
+ "storage: " + e);
@@ -1414,19 +1433,23 @@ public final class AppStartInfoTracker {
}
@GuardedBy("mLock")
- void writeToProto(ProtoOutputStream proto, long fieldId) throws IOException {
+ void writeToProto(ProtoOutputStream proto, long fieldId,
+ ByteArrayOutputStream byteArrayOutputStream, ObjectOutputStream objectOutputStream,
+ TypedXmlSerializer typedXmlSerializer) throws IOException {
long token = proto.start(fieldId);
proto.write(AppsStartInfoProto.Package.User.UID, mUid);
int size = mInfos.size();
for (int i = 0; i < size; i++) {
- mInfos.get(i)
- .writeToProto(proto, AppsStartInfoProto.Package.User.APP_START_INFO);
+ mInfos.get(i).writeToProto(proto, AppsStartInfoProto.Package.User.APP_START_INFO,
+ byteArrayOutputStream, objectOutputStream, typedXmlSerializer);
}
proto.write(AppsStartInfoProto.Package.User.MONITORING_ENABLED, mMonitoringModeEnabled);
proto.end(token);
}
- int readFromProto(ProtoInputStream proto, long fieldId, String packageName)
+ int readFromProto(ProtoInputStream proto, long fieldId, String packageName,
+ ByteArrayInputStream byteArrayInputStream, ObjectInputStream objectInputStream,
+ TypedXmlPullParser typedXmlPullParser)
throws IOException, WireTypeMismatchException, ClassNotFoundException {
long token = proto.start(fieldId);
for (int next = proto.nextField();
@@ -1440,7 +1463,8 @@ public final class AppStartInfoTracker {
// Create record with monotonic time 0 in case the persisted record does not
// have a create time.
ApplicationStartInfo info = new ApplicationStartInfo(0);
- info.readFromProto(proto, AppsStartInfoProto.Package.User.APP_START_INFO);
+ info.readFromProto(proto, AppsStartInfoProto.Package.User.APP_START_INFO,
+ byteArrayInputStream, objectInputStream, typedXmlPullParser);
info.setPackageName(packageName);
mInfos.add(info);
break;
diff --git a/services/core/java/com/android/server/appop/DiscreteOpsSqlRegistry.java b/services/core/java/com/android/server/appop/DiscreteOpsSqlRegistry.java
index 30c2a82296ca..604cb30294a9 100644
--- a/services/core/java/com/android/server/appop/DiscreteOpsSqlRegistry.java
+++ b/services/core/java/com/android/server/appop/DiscreteOpsSqlRegistry.java
@@ -418,7 +418,9 @@ public class DiscreteOpsSqlRegistry extends DiscreteOpsRegistry {
evictedEvents.addAll(mCache);
mCache.clear();
}
- mSqliteWriteHandler.obtainMessage(WRITE_CACHE_EVICTED_OP_EVENTS, evictedEvents);
+ Message msg = mSqliteWriteHandler.obtainMessage(
+ WRITE_CACHE_EVICTED_OP_EVENTS, evictedEvents);
+ mSqliteWriteHandler.sendMessage(msg);
}
}
}
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index 6d6e1fb6bfb3..ef80d59993e9 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -18,6 +18,7 @@ package com.android.server.audio;
import static android.media.audio.Flags.scoManagedByAudio;
import static com.android.media.audio.Flags.equalScoLeaVcIndexRange;
+import static com.android.media.audio.Flags.optimizeBtDeviceSwitch;
import static com.android.server.audio.AudioService.BT_COMM_DEVICE_ACTIVE_BLE_HEADSET;
import static com.android.server.audio.AudioService.BT_COMM_DEVICE_ACTIVE_BLE_SPEAKER;
import static com.android.server.audio.AudioService.BT_COMM_DEVICE_ACTIVE_SCO;
@@ -290,8 +291,8 @@ public class AudioDeviceBroker {
}
@GuardedBy("mDeviceStateLock")
- /*package*/ void onSetBtScoActiveDevice(BluetoothDevice btDevice) {
- mBtHelper.onSetBtScoActiveDevice(btDevice);
+ /*package*/ void onSetBtScoActiveDevice(BluetoothDevice btDevice, boolean deviceSwitch) {
+ mBtHelper.onSetBtScoActiveDevice(btDevice, deviceSwitch);
}
/*package*/ void setBluetoothA2dpOn_Async(boolean on, String source) {
@@ -941,6 +942,7 @@ public class AudioDeviceBroker {
final @NonNull String mEventSource;
final int mAudioSystemDevice;
final int mMusicDevice;
+ final boolean mIsDeviceSwitch;
BtDeviceInfo(@NonNull BtDeviceChangedData d, @NonNull BluetoothDevice device, int state,
int audioDevice, @AudioSystem.AudioFormatNativeEnumForBtCodec int codec) {
@@ -953,6 +955,8 @@ public class AudioDeviceBroker {
mEventSource = d.mEventSource;
mAudioSystemDevice = audioDevice;
mMusicDevice = AudioSystem.DEVICE_NONE;
+ mIsDeviceSwitch = optimizeBtDeviceSwitch()
+ && d.mNewDevice != null && d.mPreviousDevice != null;
}
// constructor used by AudioDeviceBroker to search similar message
@@ -966,6 +970,7 @@ public class AudioDeviceBroker {
mSupprNoisy = false;
mVolume = -1;
mIsLeOutput = false;
+ mIsDeviceSwitch = false;
}
// constructor used by AudioDeviceInventory when config change failed
@@ -980,6 +985,7 @@ public class AudioDeviceBroker {
mSupprNoisy = false;
mVolume = -1;
mIsLeOutput = false;
+ mIsDeviceSwitch = false;
}
BtDeviceInfo(@NonNull BtDeviceInfo src, int state) {
@@ -992,6 +998,7 @@ public class AudioDeviceBroker {
mEventSource = src.mEventSource;
mAudioSystemDevice = src.mAudioSystemDevice;
mMusicDevice = src.mMusicDevice;
+ mIsDeviceSwitch = false;
}
// redefine equality op so we can match messages intended for this device
@@ -1026,7 +1033,8 @@ public class AudioDeviceBroker {
+ " isLeOutput=" + mIsLeOutput
+ " eventSource=" + mEventSource
+ " audioSystemDevice=" + mAudioSystemDevice
- + " musicDevice=" + mMusicDevice;
+ + " musicDevice=" + mMusicDevice
+ + " isDeviceSwitch=" + mIsDeviceSwitch;
}
}
@@ -1196,6 +1204,8 @@ public class AudioDeviceBroker {
AudioSystem.setParameters("A2dpSuspended=true");
AudioSystem.setParameters("LeAudioSuspended=true");
AudioSystem.setParameters("BT_SCO=on");
+ mBluetoothA2dpSuspendedApplied = true;
+ mBluetoothLeSuspendedApplied = true;
} else {
AudioSystem.setParameters("BT_SCO=off");
if (mBluetoothA2dpSuspendedApplied) {
@@ -1680,10 +1690,11 @@ public class AudioDeviceBroker {
}
/*package*/ boolean handleDeviceConnection(@NonNull AudioDeviceAttributes attributes,
- boolean connect, @Nullable BluetoothDevice btDevice) {
+ boolean connect, @Nullable BluetoothDevice btDevice,
+ boolean deviceSwitch) {
synchronized (mDeviceStateLock) {
return mDeviceInventory.handleDeviceConnection(
- attributes, connect, false /*for test*/, btDevice);
+ attributes, connect, false /*for test*/, btDevice, deviceSwitch);
}
}
@@ -1776,6 +1787,18 @@ public class AudioDeviceBroker {
pw.println("\n" + prefix + "mScoManagedByAudio: " + mScoManagedByAudio);
+ pw.println("\n" + prefix + "Bluetooth SCO on"
+ + ", requested: " + mBluetoothScoOn
+ + ", applied: " + mBluetoothScoOnApplied);
+ pw.println("\n" + prefix + "Bluetooth A2DP suspended"
+ + ", requested ext: " + mBluetoothA2dpSuspendedExt
+ + ", requested int: " + mBluetoothA2dpSuspendedInt
+ + ", applied " + mBluetoothA2dpSuspendedApplied);
+ pw.println("\n" + prefix + "Bluetooth LE Audio suspended"
+ + ", requested ext: " + mBluetoothLeSuspendedExt
+ + ", requested int: " + mBluetoothLeSuspendedInt
+ + ", applied " + mBluetoothLeSuspendedApplied);
+
mBtHelper.dump(pw, prefix);
}
@@ -1930,10 +1953,12 @@ public class AudioDeviceBroker {
|| btInfo.mIsLeOutput)
? mAudioService.getBluetoothContextualVolumeStream()
: AudioSystem.STREAM_DEFAULT);
- if (btInfo.mProfile == BluetoothProfile.LE_AUDIO
+ if ((btInfo.mProfile == BluetoothProfile.LE_AUDIO
|| btInfo.mProfile == BluetoothProfile.HEARING_AID
|| (mScoManagedByAudio
- && btInfo.mProfile == BluetoothProfile.HEADSET)) {
+ && btInfo.mProfile == BluetoothProfile.HEADSET))
+ && (btInfo.mState == BluetoothProfile.STATE_CONNECTED
+ || !btInfo.mIsDeviceSwitch)) {
onUpdateCommunicationRouteClient(
bluetoothScoRequestOwnerAttributionSource(),
"setBluetoothActiveDevice");
diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
index ef10793fd955..ae91934e7498 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
@@ -799,7 +799,7 @@ public class AudioDeviceInventory {
di.mDeviceAddress,
di.mDeviceName),
AudioSystem.DEVICE_STATE_AVAILABLE,
- di.mDeviceCodecFormat);
+ di.mDeviceCodecFormat, false /*deviceSwitch*/);
if (asDeviceConnectionFailure() && res != AudioSystem.AUDIO_STATUS_OK) {
failedReconnectionDeviceList.add(di);
}
@@ -811,7 +811,7 @@ public class AudioDeviceInventory {
EventLogger.Event.ALOGE, TAG);
mConnectedDevices.remove(di.getKey(), di);
if (AudioSystem.isBluetoothScoDevice(di.mDeviceType)) {
- mDeviceBroker.onSetBtScoActiveDevice(null);
+ mDeviceBroker.onSetBtScoActiveDevice(null, false /*deviceSwitch*/);
}
}
}
@@ -851,7 +851,8 @@ public class AudioDeviceInventory {
Log.d(TAG, "onSetBtActiveDevice"
+ " btDevice=" + btInfo.mDevice
+ " profile=" + BluetoothProfile.getProfileName(btInfo.mProfile)
- + " state=" + BluetoothProfile.getConnectionStateName(btInfo.mState));
+ + " state=" + BluetoothProfile.getConnectionStateName(btInfo.mState)
+ + " isDeviceSwitch=" + btInfo.mIsDeviceSwitch);
}
String address = btInfo.mDevice.getAddress();
if (!BluetoothAdapter.checkBluetoothAddress(address)) {
@@ -897,7 +898,8 @@ public class AudioDeviceInventory {
break;
case BluetoothProfile.A2DP:
if (switchToUnavailable) {
- makeA2dpDeviceUnavailableNow(address, di.mDeviceCodecFormat);
+ makeA2dpDeviceUnavailableNow(address, di.mDeviceCodecFormat,
+ btInfo.mIsDeviceSwitch);
} else if (switchToAvailable) {
// device is not already connected
if (btInfo.mVolume != -1) {
@@ -911,7 +913,7 @@ public class AudioDeviceInventory {
break;
case BluetoothProfile.HEARING_AID:
if (switchToUnavailable) {
- makeHearingAidDeviceUnavailable(address);
+ makeHearingAidDeviceUnavailable(address, btInfo.mIsDeviceSwitch);
} else if (switchToAvailable) {
makeHearingAidDeviceAvailable(address, BtHelper.getName(btInfo.mDevice),
streamType, "onSetBtActiveDevice");
@@ -921,7 +923,8 @@ public class AudioDeviceInventory {
case BluetoothProfile.LE_AUDIO_BROADCAST:
if (switchToUnavailable) {
makeLeAudioDeviceUnavailableNow(address,
- btInfo.mAudioSystemDevice, di.mDeviceCodecFormat);
+ btInfo.mAudioSystemDevice, di.mDeviceCodecFormat,
+ btInfo.mIsDeviceSwitch);
} else if (switchToAvailable) {
makeLeAudioDeviceAvailable(
btInfo, streamType, codec, "onSetBtActiveDevice");
@@ -930,9 +933,10 @@ public class AudioDeviceInventory {
case BluetoothProfile.HEADSET:
if (mDeviceBroker.isScoManagedByAudio()) {
if (switchToUnavailable) {
- mDeviceBroker.onSetBtScoActiveDevice(null);
+ mDeviceBroker.onSetBtScoActiveDevice(null, btInfo.mIsDeviceSwitch);
} else if (switchToAvailable) {
- mDeviceBroker.onSetBtScoActiveDevice(btInfo.mDevice);
+ mDeviceBroker.onSetBtScoActiveDevice(
+ btInfo.mDevice, false /*deviceSwitch*/);
}
}
break;
@@ -1053,19 +1057,19 @@ public class AudioDeviceInventory {
/*package*/ void onMakeA2dpDeviceUnavailableNow(String address, int a2dpCodec) {
synchronized (mDevicesLock) {
- makeA2dpDeviceUnavailableNow(address, a2dpCodec);
+ makeA2dpDeviceUnavailableNow(address, a2dpCodec, false /*deviceSwitch*/);
}
}
/*package*/ void onMakeLeAudioDeviceUnavailableNow(String address, int device, int codec) {
synchronized (mDevicesLock) {
- makeLeAudioDeviceUnavailableNow(address, device, codec);
+ makeLeAudioDeviceUnavailableNow(address, device, codec, false /*deviceSwitch*/);
}
}
/*package*/ void onMakeHearingAidDeviceUnavailableNow(String address) {
synchronized (mDevicesLock) {
- makeHearingAidDeviceUnavailable(address);
+ makeHearingAidDeviceUnavailable(address, false /*deviceSwitch*/);
}
}
@@ -1180,7 +1184,8 @@ public class AudioDeviceInventory {
}
if (!handleDeviceConnection(wdcs.mAttributes,
- wdcs.mState == AudioService.CONNECTION_STATE_CONNECTED, wdcs.mForTest, null)) {
+ wdcs.mState == AudioService.CONNECTION_STATE_CONNECTED, wdcs.mForTest,
+ null, false /*deviceSwitch*/)) {
// change of connection state failed, bailout
mmi.set(MediaMetrics.Property.EARLY_RETURN, "change of connection state failed")
.record();
@@ -1788,14 +1793,15 @@ public class AudioDeviceInventory {
*/
/*package*/ boolean handleDeviceConnection(@NonNull AudioDeviceAttributes attributes,
boolean connect, boolean isForTesting,
- @Nullable BluetoothDevice btDevice) {
+ @Nullable BluetoothDevice btDevice,
+ boolean deviceSwitch) {
int device = attributes.getInternalType();
String address = attributes.getAddress();
String deviceName = attributes.getName();
if (AudioService.DEBUG_DEVICES) {
Slog.i(TAG, "handleDeviceConnection(" + connect + " dev:"
+ Integer.toHexString(device) + " address:" + address
- + " name:" + deviceName + ")");
+ + " name:" + deviceName + ", deviceSwitch: " + deviceSwitch + ")");
}
MediaMetrics.Item mmi = new MediaMetrics.Item(mMetricsId + "handleDeviceConnection")
.set(MediaMetrics.Property.ADDRESS, address)
@@ -1829,7 +1835,8 @@ public class AudioDeviceInventory {
res = AudioSystem.AUDIO_STATUS_OK;
} else {
res = mAudioSystem.setDeviceConnectionState(attributes,
- AudioSystem.DEVICE_STATE_AVAILABLE, AudioSystem.AUDIO_FORMAT_DEFAULT);
+ AudioSystem.DEVICE_STATE_AVAILABLE, AudioSystem.AUDIO_FORMAT_DEFAULT,
+ false /*deviceSwitch*/);
}
if (res != AudioSystem.AUDIO_STATUS_OK) {
final String reason = "not connecting device 0x" + Integer.toHexString(device)
@@ -1856,7 +1863,8 @@ public class AudioDeviceInventory {
status = true;
} else if (!connect && isConnected) {
mAudioSystem.setDeviceConnectionState(attributes,
- AudioSystem.DEVICE_STATE_UNAVAILABLE, AudioSystem.AUDIO_FORMAT_DEFAULT);
+ AudioSystem.DEVICE_STATE_UNAVAILABLE, AudioSystem.AUDIO_FORMAT_DEFAULT,
+ deviceSwitch);
// always remove even if disconnection failed
mConnectedDevices.remove(deviceKey);
mDeviceBroker.postCheckCommunicationDeviceRemoval(attributes);
@@ -2030,7 +2038,7 @@ public class AudioDeviceInventory {
}
}
if (disconnect) {
- mDeviceBroker.onSetBtScoActiveDevice(null);
+ mDeviceBroker.onSetBtScoActiveDevice(null, false /*deviceSwitch*/);
}
}
@@ -2068,7 +2076,8 @@ public class AudioDeviceInventory {
|| info.mProfile == BluetoothProfile.LE_AUDIO_BROADCAST)
&& info.mIsLeOutput)
|| info.mProfile == BluetoothProfile.HEARING_AID
- || info.mProfile == BluetoothProfile.A2DP)) {
+ || info.mProfile == BluetoothProfile.A2DP)
+ && !info.mIsDeviceSwitch) {
@AudioService.ConnectionState int asState =
(info.mState == BluetoothProfile.STATE_CONNECTED)
? AudioService.CONNECTION_STATE_CONNECTED
@@ -2124,7 +2133,7 @@ public class AudioDeviceInventory {
AudioDeviceAttributes ada = new AudioDeviceAttributes(
AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address, name);
final int res = mAudioSystem.setDeviceConnectionState(ada,
- AudioSystem.DEVICE_STATE_AVAILABLE, codec);
+ AudioSystem.DEVICE_STATE_AVAILABLE, codec, false);
// TODO: log in MediaMetrics once distinction between connection failure and
// double connection is made.
@@ -2362,7 +2371,7 @@ public class AudioDeviceInventory {
}
@GuardedBy("mDevicesLock")
- private void makeA2dpDeviceUnavailableNow(String address, int codec) {
+ private void makeA2dpDeviceUnavailableNow(String address, int codec, boolean deviceSwitch) {
MediaMetrics.Item mmi = new MediaMetrics.Item(mMetricsId + "a2dp." + address)
.set(MediaMetrics.Property.ENCODING, AudioSystem.audioFormatToString(codec))
.set(MediaMetrics.Property.EVENT, "makeA2dpDeviceUnavailableNow");
@@ -2393,7 +2402,7 @@ public class AudioDeviceInventory {
AudioDeviceAttributes ada = new AudioDeviceAttributes(
AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address);
final int res = mAudioSystem.setDeviceConnectionState(ada,
- AudioSystem.DEVICE_STATE_UNAVAILABLE, codec);
+ AudioSystem.DEVICE_STATE_UNAVAILABLE, codec, deviceSwitch);
if (res != AudioSystem.AUDIO_STATUS_OK) {
AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
@@ -2404,7 +2413,8 @@ public class AudioDeviceInventory {
} else {
AudioService.sDeviceLogger.enqueue((new EventLogger.StringEvent(
"A2DP device addr=" + Utils.anonymizeBluetoothAddress(address)
- + " made unavailable")).printSlog(EventLogger.Event.ALOGI, TAG));
+ + " made unavailable, deviceSwitch" + deviceSwitch))
+ .printSlog(EventLogger.Event.ALOGI, TAG));
}
mApmConnectedDevices.remove(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP);
@@ -2440,7 +2450,7 @@ public class AudioDeviceInventory {
final int res = mAudioSystem.setDeviceConnectionState(new AudioDeviceAttributes(
AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, address),
AudioSystem.DEVICE_STATE_AVAILABLE,
- AudioSystem.AUDIO_FORMAT_DEFAULT);
+ AudioSystem.AUDIO_FORMAT_DEFAULT, false);
if (res != AudioSystem.AUDIO_STATUS_OK) {
AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
"APM failed to make available A2DP source device addr="
@@ -2465,7 +2475,7 @@ public class AudioDeviceInventory {
AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, address);
mAudioSystem.setDeviceConnectionState(ada,
AudioSystem.DEVICE_STATE_UNAVAILABLE,
- AudioSystem.AUDIO_FORMAT_DEFAULT);
+ AudioSystem.AUDIO_FORMAT_DEFAULT, false);
// always remove regardless of the result
mConnectedDevices.remove(
DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, address));
@@ -2485,7 +2495,7 @@ public class AudioDeviceInventory {
DEVICE_OUT_HEARING_AID, address, name);
final int res = mAudioSystem.setDeviceConnectionState(ada,
AudioSystem.DEVICE_STATE_AVAILABLE,
- AudioSystem.AUDIO_FORMAT_DEFAULT);
+ AudioSystem.AUDIO_FORMAT_DEFAULT, false);
if (asDeviceConnectionFailure() && res != AudioSystem.AUDIO_STATUS_OK) {
AudioService.sDeviceLogger.enqueueAndSlog(
"APM failed to make available HearingAid addr=" + address
@@ -2515,12 +2525,12 @@ public class AudioDeviceInventory {
}
@GuardedBy("mDevicesLock")
- private void makeHearingAidDeviceUnavailable(String address) {
+ private void makeHearingAidDeviceUnavailable(String address, boolean deviceSwitch) {
AudioDeviceAttributes ada = new AudioDeviceAttributes(
DEVICE_OUT_HEARING_AID, address);
mAudioSystem.setDeviceConnectionState(ada,
AudioSystem.DEVICE_STATE_UNAVAILABLE,
- AudioSystem.AUDIO_FORMAT_DEFAULT);
+ AudioSystem.AUDIO_FORMAT_DEFAULT, deviceSwitch);
// always remove regardless of return code
mConnectedDevices.remove(
DeviceInfo.makeDeviceListKey(DEVICE_OUT_HEARING_AID, address));
@@ -2622,7 +2632,7 @@ public class AudioDeviceInventory {
AudioDeviceAttributes ada = new AudioDeviceAttributes(device, address, name);
final int res = mAudioSystem.setDeviceConnectionState(ada,
- AudioSystem.DEVICE_STATE_AVAILABLE, codec);
+ AudioSystem.DEVICE_STATE_AVAILABLE, codec, false /*deviceSwitch*/);
if (res != AudioSystem.AUDIO_STATUS_OK) {
AudioService.sDeviceLogger.enqueueAndSlog(
"APM failed to make available LE Audio device addr=" + address
@@ -2669,13 +2679,13 @@ public class AudioDeviceInventory {
@GuardedBy("mDevicesLock")
private void makeLeAudioDeviceUnavailableNow(String address, int device,
- @AudioSystem.AudioFormatNativeEnumForBtCodec int codec) {
+ @AudioSystem.AudioFormatNativeEnumForBtCodec int codec, boolean deviceSwitch) {
AudioDeviceAttributes ada = null;
if (device != AudioSystem.DEVICE_NONE) {
ada = new AudioDeviceAttributes(device, address);
final int res = mAudioSystem.setDeviceConnectionState(ada,
AudioSystem.DEVICE_STATE_UNAVAILABLE,
- codec);
+ codec, deviceSwitch);
if (res != AudioSystem.AUDIO_STATUS_OK) {
AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
@@ -2685,7 +2695,8 @@ public class AudioDeviceInventory {
} else {
AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
"LE Audio device addr=" + Utils.anonymizeBluetoothAddress(address)
- + " made unavailable").printSlog(EventLogger.Event.ALOGI, TAG));
+ + " made unavailable, deviceSwitch" + deviceSwitch)
+ .printSlog(EventLogger.Event.ALOGI, TAG));
}
mConnectedDevices.remove(DeviceInfo.makeDeviceListKey(device, address));
}
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 86871ea45d13..813e661d6970 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -63,6 +63,7 @@ import static com.android.media.audio.Flags.audioserverPermissions;
import static com.android.media.audio.Flags.disablePrescaleAbsoluteVolume;
import static com.android.media.audio.Flags.deferWearPermissionUpdates;
import static com.android.media.audio.Flags.equalScoLeaVcIndexRange;
+import static com.android.media.audio.Flags.optimizeBtDeviceSwitch;
import static com.android.media.audio.Flags.replaceStreamBtSco;
import static com.android.media.audio.Flags.ringMyCar;
import static com.android.media.audio.Flags.ringerModeAffectsAlarm;
@@ -585,6 +586,9 @@ public class AudioService extends IAudioService.Stub
// protects mRingerMode
private final Object mSettingsLock = new Object();
+ // protects VolumeStreamState / VolumeGroupState operations
+ private final Object mVolumeStateLock = new Object();
+
/** Maximum volume index values for audio streams */
protected static int[] MAX_STREAM_VOLUME = new int[] {
5, // STREAM_VOICE_CALL
@@ -1611,7 +1615,7 @@ public class AudioService extends IAudioService.Stub
private void initVolumeStreamStates() {
int numStreamTypes = AudioSystem.getNumStreamTypes();
- synchronized (VolumeStreamState.class) {
+ synchronized (mVolumeStateLock) {
for (int streamType = numStreamTypes - 1; streamType >= 0; streamType--) {
final VolumeStreamState streamState = getVssForStream(streamType);
if (streamState == null) {
@@ -2419,7 +2423,7 @@ public class AudioService extends IAudioService.Stub
private void checkAllAliasStreamVolumes() {
synchronized (mSettingsLock) {
- synchronized (VolumeStreamState.class) {
+ synchronized (mVolumeStateLock) {
int numStreamTypes = AudioSystem.getNumStreamTypes();
for (int streamType = 0; streamType < numStreamTypes; streamType++) {
int streamAlias = sStreamVolumeAlias.get(streamType, /*valueIfKeyNotFound=*/-1);
@@ -2501,7 +2505,7 @@ public class AudioService extends IAudioService.Stub
private void onUpdateVolumeStatesForAudioDevice(int device, String caller) {
final int numStreamTypes = AudioSystem.getNumStreamTypes();
synchronized (mSettingsLock) {
- synchronized (VolumeStreamState.class) {
+ synchronized (mVolumeStateLock) {
for (int streamType = 0; streamType < numStreamTypes; streamType++) {
updateVolumeStates(device, streamType, caller);
}
@@ -2770,7 +2774,7 @@ public class AudioService extends IAudioService.Stub
updateDefaultVolumes();
synchronized (mSettingsLock) {
- synchronized (VolumeStreamState.class) {
+ synchronized (mVolumeStateLock) {
getVssForStreamOrDefault(AudioSystem.STREAM_DTMF)
.setAllIndexes(getVssForStreamOrDefault(dtmfStreamAlias), caller);
getVssForStreamOrDefault(AudioSystem.STREAM_ACCESSIBILITY).setSettingName(
@@ -3232,8 +3236,10 @@ public class AudioService extends IAudioService.Stub
// Each stream will read its own persisted settings
// Broadcast the sticky intents
- broadcastRingerMode(AudioManager.RINGER_MODE_CHANGED_ACTION, mRingerModeExternal);
- broadcastRingerMode(AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION, mRingerMode);
+ synchronized (mSettingsLock) {
+ broadcastRingerMode(AudioManager.RINGER_MODE_CHANGED_ACTION, mRingerModeExternal);
+ broadcastRingerMode(AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION, mRingerMode);
+ }
// Broadcast vibrate settings
broadcastVibrateSetting(AudioManager.VIBRATE_TYPE_RINGER);
@@ -4235,7 +4241,7 @@ public class AudioService extends IAudioService.Stub
private void muteAliasStreams(int streamAlias, boolean state) {
// Locking mSettingsLock to avoid inversion when calling doMute -> updateVolumeGroupIndex
synchronized (mSettingsLock) {
- synchronized (VolumeStreamState.class) {
+ synchronized (mVolumeStateLock) {
List<Integer> streamsToMute = new ArrayList<>();
for (int streamIdx = 0; streamIdx < mStreamStates.size(); streamIdx++) {
final VolumeStreamState vss = mStreamStates.valueAt(streamIdx);
@@ -4279,7 +4285,7 @@ public class AudioService extends IAudioService.Stub
// Locking mSettingsLock to avoid inversion when calling vss.mute -> vss.doMute ->
// vss.updateVolumeGroupIndex
synchronized (mSettingsLock) {
- synchronized (VolumeStreamState.class) {
+ synchronized (mVolumeStateLock) {
final VolumeStreamState streamState = getVssForStreamOrDefault(streamAlias);
// if unmuting causes a change, it was muted
wasMuted = streamState.mute(false, "onUnmuteStreamOnSingleVolDevice");
@@ -4455,7 +4461,7 @@ public class AudioService extends IAudioService.Stub
/** @see AudioManager#getVolumeGroupVolumeIndex(int) */
public int getVolumeGroupVolumeIndex(int groupId) {
super.getVolumeGroupVolumeIndex_enforcePermission();
- synchronized (VolumeStreamState.class) {
+ synchronized (mVolumeStateLock) {
if (sVolumeGroupStates.indexOfKey(groupId) < 0) {
Log.e(TAG, "No volume group for id " + groupId);
return 0;
@@ -4472,7 +4478,7 @@ public class AudioService extends IAudioService.Stub
MODIFY_AUDIO_SETTINGS_PRIVILEGED, MODIFY_AUDIO_ROUTING })
public int getVolumeGroupMaxVolumeIndex(int groupId) {
super.getVolumeGroupMaxVolumeIndex_enforcePermission();
- synchronized (VolumeStreamState.class) {
+ synchronized (mVolumeStateLock) {
if (sVolumeGroupStates.indexOfKey(groupId) < 0) {
Log.e(TAG, "No volume group for id " + groupId);
return 0;
@@ -4487,7 +4493,7 @@ public class AudioService extends IAudioService.Stub
MODIFY_AUDIO_SETTINGS_PRIVILEGED, MODIFY_AUDIO_ROUTING })
public int getVolumeGroupMinVolumeIndex(int groupId) {
super.getVolumeGroupMinVolumeIndex_enforcePermission();
- synchronized (VolumeStreamState.class) {
+ synchronized (mVolumeStateLock) {
if (sVolumeGroupStates.indexOfKey(groupId) < 0) {
Log.e(TAG, "No volume group for id " + groupId);
return 0;
@@ -4632,7 +4638,7 @@ public class AudioService extends IAudioService.Stub
@android.annotation.EnforcePermission(QUERY_AUDIO_STATE)
public int getLastAudibleVolumeForVolumeGroup(int groupId) {
super.getLastAudibleVolumeForVolumeGroup_enforcePermission();
- synchronized (VolumeStreamState.class) {
+ synchronized (mVolumeStateLock) {
if (sVolumeGroupStates.indexOfKey(groupId) < 0) {
Log.e(TAG, ": no volume group found for id " + groupId);
return 0;
@@ -4644,7 +4650,7 @@ public class AudioService extends IAudioService.Stub
/** @see AudioManager#isVolumeGroupMuted(int) */
public boolean isVolumeGroupMuted(int groupId) {
- synchronized (VolumeStreamState.class) {
+ synchronized (mVolumeStateLock) {
if (sVolumeGroupStates.indexOfKey(groupId) < 0) {
Log.e(TAG, ": no volume group found for id " + groupId);
return false;
@@ -4990,6 +4996,8 @@ public class AudioService extends IAudioService.Stub
+ cacheGetStreamMinMaxVolume());
pw.println("\tandroid.media.audio.Flags.cacheGetStreamVolume:"
+ cacheGetStreamVolume());
+ pw.println("\tcom.android.media.audio.optimizeBtDeviceSwitch:"
+ + optimizeBtDeviceSwitch());
}
private void dumpAudioMode(PrintWriter pw) {
@@ -5470,7 +5478,7 @@ public class AudioService extends IAudioService.Stub
}
streamType = replaceBtScoStreamWithVoiceCall(streamType, "isStreamMute");
- synchronized (VolumeStreamState.class) {
+ synchronized (mVolumeStateLock) {
ensureValidStreamType(streamType);
return getVssForStreamOrDefault(streamType).mIsMuted;
}
@@ -5651,7 +5659,7 @@ public class AudioService extends IAudioService.Stub
}
private int getStreamVolume(int streamType, int device) {
- synchronized (VolumeStreamState.class) {
+ synchronized (mVolumeStateLock) {
final VolumeStreamState vss = getVssForStreamOrDefault(streamType);
int index = vss.getIndex(device);
@@ -5695,7 +5703,7 @@ public class AudioService extends IAudioService.Stub
vib.setMinVolumeIndex((vss.mIndexMin + 5) / 10);
vib.setMaxVolumeIndex((vss.mIndexMax + 5) / 10);
- synchronized (VolumeStreamState.class) {
+ synchronized (mVolumeStateLock) {
final int index;
if (isFixedVolumeDevice(ada.getInternalType())) {
index = (vss.mIndexMax + 5) / 10;
@@ -6263,7 +6271,7 @@ public class AudioService extends IAudioService.Stub
// ring and notifications volume should never be 0 when not silenced
if (sStreamVolumeAlias.get(streamType) == AudioSystem.STREAM_RING
|| sStreamVolumeAlias.get(streamType) == AudioSystem.STREAM_NOTIFICATION) {
- synchronized (VolumeStreamState.class) {
+ synchronized (mVolumeStateLock) {
for (int i = 0; i < vss.mIndexMap.size(); i++) {
int device = vss.mIndexMap.keyAt(i);
int value = vss.mIndexMap.valueAt(i);
@@ -7023,7 +7031,7 @@ public class AudioService extends IAudioService.Stub
}
streamState.readSettings();
- synchronized (VolumeStreamState.class) {
+ synchronized (mVolumeStateLock) {
// unmute stream that was muted but is not affect by mute anymore
if (streamState.mIsMuted && ((!isStreamAffectedByMute(streamType) &&
!isStreamMutedByRingerOrZenMode(streamType)) || mUseFixedVolume)) {
@@ -8056,14 +8064,14 @@ public class AudioService extends IAudioService.Stub
public Set<Integer> getDeviceSetForStream(int stream) {
stream = replaceBtScoStreamWithVoiceCall(stream, "getDeviceSetForStream");
ensureValidStreamType(stream);
- synchronized (VolumeStreamState.class) {
+ synchronized (mVolumeStateLock) {
return getVssForStreamOrDefault(stream).observeDevicesForStream_syncVSS(true);
}
}
private void onObserveDevicesForAllStreams(int skipStream) {
synchronized (mSettingsLock) {
- synchronized (VolumeStreamState.class) {
+ synchronized (mVolumeStateLock) {
for (int stream = 0; stream < mStreamStates.size(); stream++) {
final VolumeStreamState vss = mStreamStates.valueAt(stream);
if (vss != null && vss.getStreamType() != skipStream) {
@@ -8645,7 +8653,7 @@ public class AudioService extends IAudioService.Stub
private void readVolumeGroupsSettings(boolean userSwitch) {
synchronized (mSettingsLock) {
- synchronized (VolumeStreamState.class) {
+ synchronized (mVolumeStateLock) {
if (DEBUG_VOL) {
Log.d(TAG, "readVolumeGroupsSettings userSwitch=" + userSwitch);
}
@@ -8703,7 +8711,7 @@ public class AudioService extends IAudioService.Stub
// NOTE: Locking order for synchronized objects related to volume management:
// 1 mSettingsLock
- // 2 VolumeStreamState.class
+ // 2 mVolumeStateLock
private class VolumeGroupState {
private final AudioVolumeGroup mAudioVolumeGroup;
private final SparseIntArray mIndexMap = new SparseIntArray(8);
@@ -8797,7 +8805,7 @@ public class AudioService extends IAudioService.Stub
* Mute/unmute the volume group
* @param muted the new mute state
*/
- @GuardedBy("AudioService.VolumeStreamState.class")
+ @GuardedBy("AudioService.this.mVolumeStateLock")
public boolean mute(boolean muted) {
if (!isMutable()) {
// Non mutable volume group
@@ -8821,7 +8829,7 @@ public class AudioService extends IAudioService.Stub
public void adjustVolume(int direction, int flags) {
synchronized (mSettingsLock) {
- synchronized (AudioService.VolumeStreamState.class) {
+ synchronized (mVolumeStateLock) {
int device = getDeviceForVolume();
int previousIndex = getIndex(device);
if (isMuteAdjust(direction) && !isMutable()) {
@@ -8875,14 +8883,14 @@ public class AudioService extends IAudioService.Stub
}
public int getVolumeIndex() {
- synchronized (AudioService.VolumeStreamState.class) {
+ synchronized (mVolumeStateLock) {
return getIndex(getDeviceForVolume());
}
}
public void setVolumeIndex(int index, int flags) {
synchronized (mSettingsLock) {
- synchronized (AudioService.VolumeStreamState.class) {
+ synchronized (mVolumeStateLock) {
if (mUseFixedVolume) {
return;
}
@@ -8891,7 +8899,7 @@ public class AudioService extends IAudioService.Stub
}
}
- @GuardedBy("AudioService.VolumeStreamState.class")
+ @GuardedBy("AudioService.this.mVolumeStateLock")
private void setVolumeIndex(int index, int device, int flags) {
// Update cache & persist (muted by volume 0 shall be persisted)
updateVolumeIndex(index, device);
@@ -8904,7 +8912,7 @@ public class AudioService extends IAudioService.Stub
}
}
- @GuardedBy("AudioService.VolumeStreamState.class")
+ @GuardedBy("AudioService.this.mVolumeStateLock")
public void updateVolumeIndex(int index, int device) {
// Filter persistency if already exist and the index has not changed
if (mIndexMap.indexOfKey(device) < 0 || mIndexMap.get(device) != index) {
@@ -8922,7 +8930,7 @@ public class AudioService extends IAudioService.Stub
}
}
- @GuardedBy("AudioService.VolumeStreamState.class")
+ @GuardedBy("AudioService.this.mVolumeStateLock")
private void setVolumeIndexInt(int index, int device, int flags) {
// Reflect mute state of corresponding stream by forcing index to 0 if muted
// Only set audio policy BT SCO stream volume to 0 when the stream is actually muted.
@@ -8955,14 +8963,14 @@ public class AudioService extends IAudioService.Stub
mAudioSystem.setVolumeIndexForAttributes(mAudioAttributes, index, muted, device);
}
- @GuardedBy("AudioService.VolumeStreamState.class")
+ @GuardedBy("AudioService.this.mVolumeStateLock")
private int getIndex(int device) {
int index = mIndexMap.get(device, -1);
// there is always an entry for AudioSystem.DEVICE_OUT_DEFAULT
return (index != -1) ? index : mIndexMap.get(AudioSystem.DEVICE_OUT_DEFAULT);
}
- @GuardedBy("AudioService.VolumeStreamState.class")
+ @GuardedBy("AudioService.this.mVolumeStateLock")
private boolean hasIndexForDevice(int device) {
return (mIndexMap.get(device, -1) != -1);
}
@@ -8989,7 +8997,7 @@ public class AudioService extends IAudioService.Stub
public void applyAllVolumes(boolean userSwitch) {
String caller = "from vgs";
- synchronized (AudioService.VolumeStreamState.class) {
+ synchronized (mVolumeStateLock) {
// apply device specific volumes first
for (int i = 0; i < mIndexMap.size(); i++) {
int device = mIndexMap.keyAt(i);
@@ -9092,25 +9100,27 @@ public class AudioService extends IAudioService.Stub
if (mUseFixedVolume || mHasValidStreamType) {
return;
}
- if (DEBUG_VOL) {
- Log.v(TAG, "persistVolumeGroup: storing index " + getIndex(device) + " for group "
- + mAudioVolumeGroup.name()
- + ", device " + AudioSystem.getOutputDeviceName(device)
- + " and User=" + getCurrentUserId()
- + " mSettingName: " + mSettingName);
- }
+ synchronized (mVolumeStateLock) {
+ if (DEBUG_VOL) {
+ Log.v(TAG, "persistVolumeGroup: storing index " + getIndex(device)
+ + " for group " + mAudioVolumeGroup.name()
+ + ", device " + AudioSystem.getOutputDeviceName(device)
+ + " and User=" + getCurrentUserId()
+ + " mSettingName: " + mSettingName);
+ }
- boolean success = mSettings.putSystemIntForUser(mContentResolver,
- getSettingNameForDevice(device),
- getIndex(device),
- getVolumePersistenceUserId());
- if (!success) {
- Log.e(TAG, "persistVolumeGroup failed for group " + mAudioVolumeGroup.name());
+ boolean success = mSettings.putSystemIntForUser(mContentResolver,
+ getSettingNameForDevice(device),
+ getIndex(device),
+ getVolumePersistenceUserId());
+ if (!success) {
+ Log.e(TAG, "persistVolumeGroup failed for group " + mAudioVolumeGroup.name());
+ }
}
}
public void readSettings() {
- synchronized (AudioService.VolumeStreamState.class) {
+ synchronized (mVolumeStateLock) {
// force maximum volume on all streams if fixed volume property is set
if (mUseFixedVolume) {
mIndexMap.put(AudioSystem.DEVICE_OUT_DEFAULT, mIndexMax);
@@ -9144,7 +9154,7 @@ public class AudioService extends IAudioService.Stub
}
}
- @GuardedBy("AudioService.VolumeStreamState.class")
+ @GuardedBy("AudioService.this.mVolumeStateLock")
private int getValidIndex(int index) {
if (index < mIndexMin) {
return mIndexMin;
@@ -9218,7 +9228,7 @@ public class AudioService extends IAudioService.Stub
// 1 mScoclient OR mSafeMediaVolumeState
// 2 mSetModeLock
// 3 mSettingsLock
- // 4 VolumeStreamState.class
+ // 4 mVolumeStateLock
/*package*/ class VolumeStreamState {
private final int mStreamType;
private VolumeGroupState mVolumeGroupState = null;
@@ -9427,7 +9437,7 @@ public class AudioService extends IAudioService.Stub
*
* This is a reference to the local list, do not modify.
*/
- @GuardedBy("VolumeStreamState.class")
+ @GuardedBy("mVolumeStateLock")
@NonNull
public Set<Integer> observeDevicesForStream_syncVSS(
boolean checkOthers) {
@@ -9495,7 +9505,7 @@ public class AudioService extends IAudioService.Stub
public void readSettings() {
synchronized (mSettingsLock) {
- synchronized (VolumeStreamState.class) {
+ synchronized (mVolumeStateLock) {
// force maximum volume on all streams if fixed volume property is set
if (mUseFixedVolume) {
mIndexMap.put(AudioSystem.DEVICE_OUT_DEFAULT, mIndexMax);
@@ -9515,7 +9525,7 @@ public class AudioService extends IAudioService.Stub
}
}
}
- synchronized (VolumeStreamState.class) {
+ synchronized (mVolumeStateLock) {
for (int device : AudioSystem.DEVICE_OUT_ALL_SET) {
// retrieve current volume for device
@@ -9548,7 +9558,7 @@ public class AudioService extends IAudioService.Stub
* will send the non-zero index together with muted state. Otherwise, index 0 will be sent
* to native for signalising a muted stream.
**/
- @GuardedBy("VolumeStreamState.class")
+ @GuardedBy("mVolumeStateLock")
private void setStreamVolumeIndex(int index, int device) {
// Only set audio policy BT SCO stream volume to 0 when the stream is actually muted.
// This allows RX path muting by the audio HAL only when explicitly muted but not when
@@ -9570,8 +9580,8 @@ public class AudioService extends IAudioService.Stub
mAudioSystem.setStreamVolumeIndexAS(mStreamType, index, muted, device);
}
- // must be called while synchronized VolumeStreamState.class
- @GuardedBy("VolumeStreamState.class")
+ // must be called while synchronized mVolumeStateLock
+ @GuardedBy("mVolumeStateLock")
/*package*/ void applyDeviceVolume_syncVSS(int device) {
int index;
if (isFullyMuted() && !ringMyCar()) {
@@ -9597,7 +9607,7 @@ public class AudioService extends IAudioService.Stub
}
public void applyAllVolumes() {
- synchronized (VolumeStreamState.class) {
+ synchronized (mVolumeStateLock) {
// apply device specific volumes first
int index;
boolean isAbsoluteVolume = false;
@@ -9659,7 +9669,7 @@ public class AudioService extends IAudioService.Stub
final boolean isCurrentDevice;
final StringBuilder aliasStreamIndexes = new StringBuilder();
synchronized (mSettingsLock) {
- synchronized (VolumeStreamState.class) {
+ synchronized (mVolumeStateLock) {
oldIndex = getIndex(device);
index = getValidIndex(index, hasModifyAudioSettings);
// for STREAM_SYSTEM_ENFORCED, do not sync aliased streams on the enforced index
@@ -9777,7 +9787,7 @@ public class AudioService extends IAudioService.Stub
}
public int getIndex(int device) {
- synchronized (VolumeStreamState.class) {
+ synchronized (mVolumeStateLock) {
int index = mIndexMap.get(device, -1);
if (index == -1) {
// there is always an entry for AudioSystem.DEVICE_OUT_DEFAULT
@@ -9788,7 +9798,7 @@ public class AudioService extends IAudioService.Stub
}
public @NonNull VolumeInfo getVolumeInfo(int device) {
- synchronized (VolumeStreamState.class) {
+ synchronized (mVolumeStateLock) {
int index = mIndexMap.get(device, -1);
if (index == -1) {
// there is always an entry for AudioSystem.DEVICE_OUT_DEFAULT
@@ -9805,7 +9815,7 @@ public class AudioService extends IAudioService.Stub
}
public boolean hasIndexForDevice(int device) {
- synchronized (VolumeStreamState.class) {
+ synchronized (mVolumeStateLock) {
return (mIndexMap.get(device, -1) != -1);
}
}
@@ -9837,8 +9847,8 @@ public class AudioService extends IAudioService.Stub
* @param srcStream
* @param caller
*/
- // must be sync'd on mSettingsLock before VolumeStreamState.class
- @GuardedBy("VolumeStreamState.class")
+ // must be sync'd on mSettingsLock before mVolumeStateLock
+ @GuardedBy("mVolumeStateLock")
public void setAllIndexes(VolumeStreamState srcStream, String caller) {
if (srcStream == null || mStreamType == srcStream.mStreamType) {
return;
@@ -9862,8 +9872,8 @@ public class AudioService extends IAudioService.Stub
}
}
- // must be sync'd on mSettingsLock before VolumeStreamState.class
- @GuardedBy("VolumeStreamState.class")
+ // must be sync'd on mSettingsLock before mVolumeStateLock
+ @GuardedBy("mVolumeStateLock")
public void setAllIndexesToMax() {
for (int i = 0; i < mIndexMap.size(); i++) {
mIndexMap.put(mIndexMap.keyAt(i), mIndexMax);
@@ -9876,7 +9886,7 @@ public class AudioService extends IAudioService.Stub
// vss.setIndex which grabs this lock after VSS.class. Locking order needs to be
// preserved
synchronized (mSettingsLock) {
- synchronized (VolumeStreamState.class) {
+ synchronized (mVolumeStateLock) {
if (mVolumeGroupState != null) {
int groupIndex = (getIndex(device) + 5) / 10;
if (DEBUG_VOL) {
@@ -9908,7 +9918,7 @@ public class AudioService extends IAudioService.Stub
*/
public boolean mute(boolean state, String source) {
boolean changed = false;
- synchronized (VolumeStreamState.class) {
+ synchronized (mVolumeStateLock) {
changed = mute(state, true, source);
}
if (changed) {
@@ -9924,7 +9934,7 @@ public class AudioService extends IAudioService.Stub
*/
public boolean muteInternally(boolean state) {
boolean changed = false;
- synchronized (VolumeStreamState.class) {
+ synchronized (mVolumeStateLock) {
if (state != mIsMutedInternally) {
changed = true;
mIsMutedInternally = state;
@@ -9939,7 +9949,7 @@ public class AudioService extends IAudioService.Stub
return changed;
}
- @GuardedBy("VolumeStreamState.class")
+ @GuardedBy("mVolumeStateLock")
public boolean isFullyMuted() {
return mIsMuted || mIsMutedInternally;
}
@@ -9960,7 +9970,7 @@ public class AudioService extends IAudioService.Stub
*/
public boolean mute(boolean state, boolean apply, String src) {
boolean changed;
- synchronized (VolumeStreamState.class) {
+ synchronized (mVolumeStateLock) {
changed = state != mIsMuted;
if (changed) {
sMuteLogger.enqueue(
@@ -9994,7 +10004,7 @@ public class AudioService extends IAudioService.Stub
}
public void doMute() {
- synchronized (VolumeStreamState.class) {
+ synchronized (mVolumeStateLock) {
// If associated to volume group, update group cache
updateVolumeGroupIndex(getDeviceForStream(mStreamType), /* forceMuteState= */true);
@@ -10015,7 +10025,7 @@ public class AudioService extends IAudioService.Stub
}
public void checkFixedVolumeDevices() {
- synchronized (VolumeStreamState.class) {
+ synchronized (mVolumeStateLock) {
// ignore settings for fixed volume devices: volume should always be at max or 0
if (sStreamVolumeAlias.get(mStreamType) == AudioSystem.STREAM_MUSIC) {
for (int i = 0; i < mIndexMap.size(); i++) {
@@ -10186,8 +10196,7 @@ public class AudioService extends IAudioService.Stub
}
/*package*/ void setDeviceVolume(VolumeStreamState streamState, int device) {
-
- synchronized (VolumeStreamState.class) {
+ synchronized (mVolumeStateLock) {
sendMsg(mAudioHandler, SoundDoseHelper.MSG_CSD_UPDATE_ATTENUATION, SENDMSG_QUEUE,
device, (isAbsoluteVolumeDevice(device) || isA2dpAbsoluteVolumeDevice(device)
|| AudioSystem.isLeAudioDeviceType(device) ? 1 : 0),
@@ -12191,7 +12200,7 @@ public class AudioService extends IAudioService.Stub
mCameraSoundForced = cameraSoundForced;
if (cameraSoundForcedChanged) {
if (!mIsSingleVolume) {
- synchronized (VolumeStreamState.class) {
+ synchronized (mVolumeStateLock) {
final VolumeStreamState s = getVssForStreamOrDefault(
AudioSystem.STREAM_SYSTEM_ENFORCED);
if (cameraSoundForced) {
diff --git a/services/core/java/com/android/server/audio/AudioSystemAdapter.java b/services/core/java/com/android/server/audio/AudioSystemAdapter.java
index e86c34cab88a..a6267c156fb3 100644
--- a/services/core/java/com/android/server/audio/AudioSystemAdapter.java
+++ b/services/core/java/com/android/server/audio/AudioSystemAdapter.java
@@ -367,9 +367,9 @@ public class AudioSystemAdapter implements AudioSystem.RoutingUpdateCallback,
* @return
*/
public int setDeviceConnectionState(AudioDeviceAttributes attributes, int state,
- int codecFormat) {
+ int codecFormat, boolean deviceSwitch) {
invalidateRoutingCache();
- return AudioSystem.setDeviceConnectionState(attributes, state, codecFormat);
+ return AudioSystem.setDeviceConnectionState(attributes, state, codecFormat, deviceSwitch);
}
/**
diff --git a/services/core/java/com/android/server/audio/BtHelper.java b/services/core/java/com/android/server/audio/BtHelper.java
index 922116999bc7..844e3524384d 100644
--- a/services/core/java/com/android/server/audio/BtHelper.java
+++ b/services/core/java/com/android/server/audio/BtHelper.java
@@ -26,6 +26,8 @@ import static android.media.AudioManager.AUDIO_DEVICE_CATEGORY_SPEAKER;
import static android.media.AudioManager.AUDIO_DEVICE_CATEGORY_UNKNOWN;
import static android.media.AudioManager.AUDIO_DEVICE_CATEGORY_WATCH;
+import static com.android.media.audio.Flags.optimizeBtDeviceSwitch;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.bluetooth.BluetoothA2dp;
@@ -393,8 +395,11 @@ public class BtHelper {
+ "received with null profile proxy for device: "
+ btDevice)).printLog(TAG));
return;
+
}
- onSetBtScoActiveDevice(btDevice);
+ boolean deviceSwitch = optimizeBtDeviceSwitch()
+ && btDevice != null && mBluetoothHeadsetDevice != null;
+ onSetBtScoActiveDevice(btDevice, deviceSwitch);
} else if (action.equals(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED)) {
int btState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1);
onScoAudioStateChanged(btState);
@@ -814,7 +819,7 @@ public class BtHelper {
if (device == null) {
continue;
}
- onSetBtScoActiveDevice(device);
+ onSetBtScoActiveDevice(device, false /*deviceSwitch*/);
}
} else {
Log.e(TAG, "onHeadsetProfileConnected: Null BluetoothAdapter");
@@ -907,7 +912,8 @@ public class BtHelper {
}
@GuardedBy("mDeviceBroker.mDeviceStateLock")
- private boolean handleBtScoActiveDeviceChange(BluetoothDevice btDevice, boolean isActive) {
+ private boolean handleBtScoActiveDeviceChange(BluetoothDevice btDevice, boolean isActive,
+ boolean deviceSwitch) {
if (btDevice == null) {
return true;
}
@@ -919,12 +925,12 @@ public class BtHelper {
if (isActive) {
audioDevice = btHeadsetDeviceToAudioDevice(btDevice);
result = mDeviceBroker.handleDeviceConnection(
- audioDevice, true /*connect*/, btDevice);
+ audioDevice, true /*connect*/, btDevice, false /*deviceSwitch*/);
} else {
AudioDeviceAttributes ada = mResolvedScoAudioDevices.get(btDevice);
if (ada != null) {
result = mDeviceBroker.handleDeviceConnection(
- ada, false /*connect*/, btDevice);
+ ada, false /*connect*/, btDevice, deviceSwitch);
} else {
// Disconnect all possible audio device types if the disconnected device type is
// unknown
@@ -935,7 +941,8 @@ public class BtHelper {
};
for (int outDeviceType : outDeviceTypes) {
result |= mDeviceBroker.handleDeviceConnection(new AudioDeviceAttributes(
- outDeviceType, address, name), false /*connect*/, btDevice);
+ outDeviceType, address, name), false /*connect*/, btDevice,
+ deviceSwitch);
}
}
}
@@ -944,7 +951,7 @@ public class BtHelper {
// handleDeviceConnection() && result to make sure the method get executed
result = mDeviceBroker.handleDeviceConnection(new AudioDeviceAttributes(
inDevice, address, name),
- isActive, btDevice) && result;
+ isActive, btDevice, deviceSwitch) && result;
if (result) {
if (isActive) {
mResolvedScoAudioDevices.put(btDevice, audioDevice);
@@ -961,18 +968,18 @@ public class BtHelper {
}
@GuardedBy("mDeviceBroker.mDeviceStateLock")
- /*package */ void onSetBtScoActiveDevice(BluetoothDevice btDevice) {
+ /*package */ void onSetBtScoActiveDevice(BluetoothDevice btDevice, boolean deviceSwitch) {
Log.i(TAG, "onSetBtScoActiveDevice: " + getAnonymizedAddress(mBluetoothHeadsetDevice)
- + " -> " + getAnonymizedAddress(btDevice));
+ + " -> " + getAnonymizedAddress(btDevice) + ", deviceSwitch: " + deviceSwitch);
final BluetoothDevice previousActiveDevice = mBluetoothHeadsetDevice;
if (Objects.equals(btDevice, previousActiveDevice)) {
return;
}
- if (!handleBtScoActiveDeviceChange(previousActiveDevice, false)) {
+ if (!handleBtScoActiveDeviceChange(previousActiveDevice, false, deviceSwitch)) {
Log.w(TAG, "onSetBtScoActiveDevice() failed to remove previous device "
+ getAnonymizedAddress(previousActiveDevice));
}
- if (!handleBtScoActiveDeviceChange(btDevice, true)) {
+ if (!handleBtScoActiveDeviceChange(btDevice, true, false /*deviceSwitch*/)) {
Log.e(TAG, "onSetBtScoActiveDevice() failed to add new device "
+ getAnonymizedAddress(btDevice));
// set mBluetoothHeadsetDevice to null when failing to add new device
diff --git a/services/core/java/com/android/server/biometrics/BiometricCameraManagerImpl.java b/services/core/java/com/android/server/biometrics/BiometricCameraManagerImpl.java
index 000ee5446962..9f364677705e 100644
--- a/services/core/java/com/android/server/biometrics/BiometricCameraManagerImpl.java
+++ b/services/core/java/com/android/server/biometrics/BiometricCameraManagerImpl.java
@@ -20,12 +20,16 @@ import static android.hardware.SensorPrivacyManager.Sensors.CAMERA;
import android.annotation.NonNull;
import android.hardware.SensorPrivacyManager;
+import android.hardware.camera2.CameraAccessException;
import android.hardware.camera2.CameraManager;
+import android.util.Log;
import java.util.concurrent.ConcurrentHashMap;
public class BiometricCameraManagerImpl implements BiometricCameraManager {
+ private static final String TAG = "BiometricCameraManager";
+
private final CameraManager mCameraManager;
private final SensorPrivacyManager mSensorPrivacyManager;
private final ConcurrentHashMap<String, Boolean> mIsCameraAvailable = new ConcurrentHashMap<>();
@@ -52,12 +56,18 @@ public class BiometricCameraManagerImpl implements BiometricCameraManager {
@Override
public boolean isAnyCameraUnavailable() {
- for (String cameraId : mIsCameraAvailable.keySet()) {
- if (!mIsCameraAvailable.get(cameraId)) {
- return true;
+ try {
+ for (String cameraId : mCameraManager.getCameraIdList()) {
+ if (!mIsCameraAvailable.getOrDefault(cameraId, true)) {
+ return true;
+ }
}
+ return false;
+ } catch (CameraAccessException e) {
+ Log.e(TAG, "Camera exception thrown when trying to determine availability: ", e);
+ //If face HAL is unable to get access to a camera, it will return an error.
+ return false;
}
- return false;
}
@Override
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index b6a3f4041b13..258c95582e3a 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -812,7 +812,7 @@ public final class DisplayManagerService extends SystemService {
handleMinimalPostProcessingAllowedSettingChange();
if (mFlags.isDisplayContentModeManagementEnabled()) {
- updateMirrorBuiltInDisplaySettingLocked();
+ updateMirrorBuiltInDisplaySettingLocked(/*shouldSendDisplayChangeEvent=*/ true);
}
final UserManager userManager = getUserManager();
@@ -868,7 +868,7 @@ public final class DisplayManagerService extends SystemService {
updateHdrConversionModeSettingsLocked();
}
if (mFlags.isDisplayContentModeManagementEnabled()) {
- updateMirrorBuiltInDisplaySettingLocked();
+ updateMirrorBuiltInDisplaySettingLocked(/*shouldSendDisplayChangeEvent=*/ false);
}
}
@@ -1237,8 +1237,11 @@ public final class DisplayManagerService extends SystemService {
}
if (Settings.Secure.getUriFor(MIRROR_BUILT_IN_DISPLAY).equals(uri)) {
- if (mFlags.isDisplayContentModeManagementEnabled()) {
- updateMirrorBuiltInDisplaySettingLocked();
+ synchronized (mSyncRoot) {
+ if (mFlags.isDisplayContentModeManagementEnabled()) {
+ updateMirrorBuiltInDisplaySettingLocked(/*shouldSendDisplayChangeEvent=*/
+ true);
+ }
}
return;
}
@@ -1258,18 +1261,19 @@ public final class DisplayManagerService extends SystemService {
1, UserHandle.USER_CURRENT) != 0);
}
- private void updateMirrorBuiltInDisplaySettingLocked() {
- synchronized (mSyncRoot) {
- ContentResolver resolver = mContext.getContentResolver();
- final boolean mirrorBuiltInDisplay = Settings.Secure.getIntForUser(resolver,
- MIRROR_BUILT_IN_DISPLAY, 0, UserHandle.USER_CURRENT) != 0;
- if (mMirrorBuiltInDisplay == mirrorBuiltInDisplay) {
- return;
- }
- mMirrorBuiltInDisplay = mirrorBuiltInDisplay;
- if (mFlags.isDisplayContentModeManagementEnabled()) {
- mLogicalDisplayMapper.forEachLocked(this::updateCanHostTasksIfNeededLocked);
- }
+ private void updateMirrorBuiltInDisplaySettingLocked(boolean shouldSendDisplayChangeEvent) {
+ ContentResolver resolver = mContext.getContentResolver();
+ final boolean mirrorBuiltInDisplay = Settings.Secure.getIntForUser(resolver,
+ MIRROR_BUILT_IN_DISPLAY, 0, UserHandle.USER_CURRENT) != 0;
+ if (mMirrorBuiltInDisplay == mirrorBuiltInDisplay) {
+ return;
+ }
+ mMirrorBuiltInDisplay = mirrorBuiltInDisplay;
+ if (mFlags.isDisplayContentModeManagementEnabled()) {
+ mLogicalDisplayMapper.forEachLocked(logicalDisplay -> {
+ updateCanHostTasksIfNeededLocked(logicalDisplay,
+ shouldSendDisplayChangeEvent);
+ });
}
}
@@ -2380,7 +2384,7 @@ public final class DisplayManagerService extends SystemService {
new BrightnessPair(brightnessDefault, brightnessDefault));
if (mFlags.isDisplayContentModeManagementEnabled()) {
- updateCanHostTasksIfNeededLocked(display);
+ updateCanHostTasksIfNeededLocked(display, /*shouldSendDisplayChangeEvent=*/ false);
}
DisplayManagerGlobal.invalidateLocalDisplayInfoCaches();
@@ -2587,6 +2591,11 @@ public final class DisplayManagerService extends SystemService {
sendDisplayEventIfEnabledLocked(display, DisplayManagerGlobal.EVENT_DISPLAY_STATE_CHANGED);
}
+ private void handleLogicalDisplayCommittedStateChangedLocked(@NonNull LogicalDisplay display) {
+ sendDisplayEventIfEnabledLocked(display,
+ DisplayManagerGlobal.EVENT_DISPLAY_COMMITTED_STATE_CHANGED);
+ }
+
private void notifyDefaultDisplayDeviceUpdated(LogicalDisplay display) {
mDisplayModeDirector.defaultDisplayDeviceUpdated(display.getPrimaryDisplayDeviceLocked()
.mDisplayDeviceConfig);
@@ -2609,7 +2618,8 @@ public final class DisplayManagerService extends SystemService {
// Blank or unblank the display immediately to match the state requested
// by the display power controller (if known).
DisplayDeviceInfo info = device.getDisplayDeviceInfoLocked();
- if ((info.flags & DisplayDeviceInfo.FLAG_NEVER_BLANK) == 0) {
+ if ((info.flags & DisplayDeviceInfo.FLAG_NEVER_BLANK) == 0
+ || android.companion.virtualdevice.flags.Flags.correctVirtualDisplayPowerState()) {
final LogicalDisplay display = mLogicalDisplayMapper.getDisplayLocked(device);
if (display == null) {
return null;
@@ -2697,8 +2707,9 @@ public final class DisplayManagerService extends SystemService {
}
}
- private void updateCanHostTasksIfNeededLocked(LogicalDisplay display) {
- if (display.setCanHostTasksLocked(!mMirrorBuiltInDisplay)) {
+ private void updateCanHostTasksIfNeededLocked(LogicalDisplay display,
+ boolean shouldSendDisplayChangeEvent) {
+ if (display.setCanHostTasksLocked(!mMirrorBuiltInDisplay) && shouldSendDisplayChangeEvent) {
sendDisplayEventIfEnabledLocked(display,
DisplayManagerGlobal.EVENT_DISPLAY_BASIC_CHANGED);
}
@@ -4165,6 +4176,9 @@ public final class DisplayManagerService extends SystemService {
case LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_STATE_CHANGED:
handleLogicalDisplayStateChangedLocked(display);
break;
+ case LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_COMMITTED_STATE_CHANGED:
+ handleLogicalDisplayCommittedStateChangedLocked(display);
+ break;
}
}
@@ -4419,6 +4433,9 @@ public final class DisplayManagerService extends SystemService {
case DisplayManagerGlobal.EVENT_DISPLAY_STATE_CHANGED:
return (mask & DisplayManagerGlobal
.INTERNAL_EVENT_FLAG_DISPLAY_STATE) != 0;
+ case DisplayManagerGlobal.EVENT_DISPLAY_COMMITTED_STATE_CHANGED:
+ return (mask & DisplayManagerGlobal
+ .INTERNAL_EVENT_FLAG_DISPLAY_COMMITTED_STATE_CHANGED) != 0;
default:
// This should never happen.
Slog.e(TAG, "Unknown display event " + event);
@@ -5563,7 +5580,9 @@ public final class DisplayManagerService extends SystemService {
final DisplayDevice displayDevice = mLogicalDisplayMapper.getDisplayLocked(
id).getPrimaryDisplayDeviceLocked();
final int flags = displayDevice.getDisplayDeviceInfoLocked().flags;
- if ((flags & DisplayDeviceInfo.FLAG_NEVER_BLANK) == 0) {
+ if ((flags & DisplayDeviceInfo.FLAG_NEVER_BLANK) == 0
+ || android.companion.virtualdevice.flags.Flags
+ .correctVirtualDisplayPowerState()) {
final DisplayPowerController displayPowerController =
mDisplayPowerControllers.get(id);
if (displayPowerController != null) {
diff --git a/services/core/java/com/android/server/display/LogicalDisplayMapper.java b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
index 02db051dff57..872f33484951 100644
--- a/services/core/java/com/android/server/display/LogicalDisplayMapper.java
+++ b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
@@ -91,6 +91,8 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
public static final int LOGICAL_DISPLAY_EVENT_DISCONNECTED = 1 << 8;
public static final int LOGICAL_DISPLAY_EVENT_REFRESH_RATE_CHANGED = 1 << 9;
public static final int LOGICAL_DISPLAY_EVENT_STATE_CHANGED = 1 << 10;
+ public static final int LOGICAL_DISPLAY_EVENT_COMMITTED_STATE_CHANGED = 1 << 11;
+
public static final int DISPLAY_GROUP_EVENT_ADDED = 1;
public static final int DISPLAY_GROUP_EVENT_CHANGED = 2;
@@ -810,7 +812,7 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
int logicalDisplayEventMask = mLogicalDisplaysToUpdate
.get(displayId, LOGICAL_DISPLAY_EVENT_BASE);
boolean hasBasicInfoChanged =
- !mTempDisplayInfo.equals(newDisplayInfo, /* compareRefreshRate */ false);
+ !mTempDisplayInfo.equals(newDisplayInfo, /* compareOnlyBasicChanges */ true);
// The display is no longer valid and needs to be removed.
if (!display.isValidLocked()) {
// Remove from group
@@ -930,6 +932,7 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
sendUpdatesForDisplaysLocked(LOGICAL_DISPLAY_EVENT_BASIC_CHANGED);
sendUpdatesForDisplaysLocked(LOGICAL_DISPLAY_EVENT_REFRESH_RATE_CHANGED);
sendUpdatesForDisplaysLocked(LOGICAL_DISPLAY_EVENT_STATE_CHANGED);
+ sendUpdatesForDisplaysLocked(LOGICAL_DISPLAY_EVENT_COMMITTED_STATE_CHANGED);
sendUpdatesForDisplaysLocked(LOGICAL_DISPLAY_EVENT_FRAME_RATE_OVERRIDES_CHANGED);
sendUpdatesForDisplaysLocked(LOGICAL_DISPLAY_EVENT_SWAPPED);
sendUpdatesForDisplaysLocked(LOGICAL_DISPLAY_EVENT_CONNECTED);
@@ -961,6 +964,11 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
&& mTempDisplayInfo.state != newDisplayInfo.state) {
mask |= LOGICAL_DISPLAY_EVENT_STATE_CHANGED;
}
+
+ if (mFlags.isCommittedStateSeparateEventEnabled()
+ && mTempDisplayInfo.committedState != newDisplayInfo.committedState) {
+ mask |= LOGICAL_DISPLAY_EVENT_COMMITTED_STATE_CHANGED;
+ }
return mask;
}
/**
@@ -1360,6 +1368,8 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
return "disconnected";
case LOGICAL_DISPLAY_EVENT_STATE_CHANGED:
return "state_changed";
+ case LOGICAL_DISPLAY_EVENT_COMMITTED_STATE_CHANGED:
+ return "committed_state_changed";
case LOGICAL_DISPLAY_EVENT_REFRESH_RATE_CHANGED:
return "refresh_rate_changed";
case LOGICAL_DISPLAY_EVENT_BASIC_CHANGED:
diff --git a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
index 4779b690adfb..e7939bb50ece 100644
--- a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
@@ -371,7 +371,15 @@ public class VirtualDisplayAdapter extends DisplayAdapter {
mCallback = callback;
mProjection = projection;
mMediaProjectionCallback = mediaProjectionCallback;
- mDisplayState = Display.STATE_ON;
+ if (android.companion.virtualdevice.flags.Flags.correctVirtualDisplayPowerState()) {
+ // The display's power state depends on the power state of the state of its
+ // display / power group, which we don't know here. Initializing to UNKNOWN allows
+ // the first call to requestDisplayStateLocked() to set the correct state.
+ // This also triggers VirtualDisplay.Callback to tell the owner the initial state.
+ mDisplayState = Display.STATE_UNKNOWN;
+ } else {
+ mDisplayState = Display.STATE_ON;
+ }
mPendingChanges |= PENDING_SURFACE_CHANGE;
mDisplayIdToMirror = virtualDisplayConfig.getDisplayIdToMirror();
mIsWindowManagerMirroring = virtualDisplayConfig.isWindowManagerMirroringEnabled();
@@ -564,14 +572,23 @@ public class VirtualDisplayAdapter extends DisplayAdapter {
mInfo.yDpi = mDensityDpi;
mInfo.presentationDeadlineNanos = 1000000000L / (int) getRefreshRate(); // 1 frame
mInfo.flags = 0;
- if ((mFlags & VIRTUAL_DISPLAY_FLAG_PUBLIC) == 0) {
- mInfo.flags |= DisplayDeviceInfo.FLAG_PRIVATE
- | DisplayDeviceInfo.FLAG_NEVER_BLANK;
- }
- if ((mFlags & VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR) != 0) {
- mInfo.flags &= ~DisplayDeviceInfo.FLAG_NEVER_BLANK;
+ if (android.companion.virtualdevice.flags.Flags.correctVirtualDisplayPowerState()) {
+ if ((mFlags & VIRTUAL_DISPLAY_FLAG_PUBLIC) == 0) {
+ mInfo.flags |= DisplayDeviceInfo.FLAG_PRIVATE;
+ }
+ if ((mFlags & VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR) == 0) {
+ mInfo.flags |= DisplayDeviceInfo.FLAG_OWN_CONTENT_ONLY;
+ }
} else {
- mInfo.flags |= DisplayDeviceInfo.FLAG_OWN_CONTENT_ONLY;
+ if ((mFlags & VIRTUAL_DISPLAY_FLAG_PUBLIC) == 0) {
+ mInfo.flags |= DisplayDeviceInfo.FLAG_PRIVATE
+ | DisplayDeviceInfo.FLAG_NEVER_BLANK;
+ }
+ if ((mFlags & VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR) != 0) {
+ mInfo.flags &= ~DisplayDeviceInfo.FLAG_NEVER_BLANK;
+ } else {
+ mInfo.flags |= DisplayDeviceInfo.FLAG_OWN_CONTENT_ONLY;
+ }
}
if ((mFlags & VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP) != 0) {
mInfo.flags |= DisplayDeviceInfo.FLAG_OWN_DISPLAY_GROUP;
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 bc5d90599b41..e4b595ab7c55 100644
--- a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
+++ b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
@@ -280,6 +280,11 @@ public class DisplayManagerFlags {
Flags::refreshRateEventForForegroundApps
);
+ private final FlagState mCommittedStateSeparateEvent = new FlagState(
+ Flags.FLAG_COMMITTED_STATE_SEPARATE_EVENT,
+ Flags::committedStateSeparateEvent
+ );
+
/**
* @return {@code true} if 'port' is allowed in display layout configuration file.
*/
@@ -603,6 +608,14 @@ public class DisplayManagerFlags {
}
/**
+ * @return {@code true} if the flag for having a separate event for display's committed state
+ * is enabled
+ */
+ public boolean isCommittedStateSeparateEventEnabled() {
+ return mCommittedStateSeparateEvent.isEnabled();
+ }
+
+ /**
* dumps all flagstates
* @param pw printWriter
*/
@@ -659,6 +672,7 @@ public class DisplayManagerFlags {
pw.println(" " + mBaseDensityForExternalDisplays);
pw.println(" " + mFramerateOverrideTriggersRrCallbacks);
pw.println(" " + mRefreshRateEventForForegroundApps);
+ pw.println(" " + mCommittedStateSeparateEvent);
}
private static class FlagState {
diff --git a/services/core/java/com/android/server/display/feature/display_flags.aconfig b/services/core/java/com/android/server/display/feature/display_flags.aconfig
index 8211febade60..acdc0e0cf891 100644
--- a/services/core/java/com/android/server/display/feature/display_flags.aconfig
+++ b/services/core/java/com/android/server/display/feature/display_flags.aconfig
@@ -456,9 +456,8 @@ flag {
flag {
name: "enable_display_content_mode_management"
namespace: "lse_desktop_experience"
- description: "Enable switching the content mode of connected displays between mirroring and extened. Also change the default content mode to extended mode."
+ description: "Enable switching the content mode of connected displays between mirroring and extended. Also change the default content mode to extended mode."
bug: "378385869"
- is_fixed_read_only: true
}
flag {
@@ -509,3 +508,14 @@ flag {
bug: "293651324"
is_fixed_read_only: false
}
+
+flag {
+ name: "committed_state_separate_event"
+ namespace: "display_manager"
+ description: "Move Display committed state into a separate event"
+ bug: "342192387"
+ is_fixed_read_only: true
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/services/core/java/com/android/server/dreams/DreamManagerService.java b/services/core/java/com/android/server/dreams/DreamManagerService.java
index 2af74f620c95..7e8bb28b6a37 100644
--- a/services/core/java/com/android/server/dreams/DreamManagerService.java
+++ b/services/core/java/com/android/server/dreams/DreamManagerService.java
@@ -569,8 +569,7 @@ public final class DreamManagerService extends SystemService {
}
private void requestDreamInternal() {
- if (isDreamingInternal() && !dreamIsFrontmost() && mController.bringDreamToFront()
- && !isDozingInternal()) {
+ if (isDreamingInternal() && !dreamIsFrontmost() && mController.bringDreamToFront()) {
return;
}
diff --git a/services/core/java/com/android/server/hdmi/AudioDeviceVolumeManagerWrapper.java b/services/core/java/com/android/server/hdmi/AudioDeviceVolumeManagerWrapper.java
index 94842041af82..ab86433ca50d 100644
--- a/services/core/java/com/android/server/hdmi/AudioDeviceVolumeManagerWrapper.java
+++ b/services/core/java/com/android/server/hdmi/AudioDeviceVolumeManagerWrapper.java
@@ -58,9 +58,9 @@ public interface AudioDeviceVolumeManagerWrapper {
void setDeviceAbsoluteVolumeBehavior(
@NonNull AudioDeviceAttributes device,
@NonNull VolumeInfo volume,
+ boolean handlesVolumeAdjustment,
@NonNull @CallbackExecutor Executor executor,
- @NonNull AudioDeviceVolumeManager.OnAudioDeviceVolumeChangedListener vclistener,
- boolean handlesVolumeAdjustment);
+ @NonNull AudioDeviceVolumeManager.OnAudioDeviceVolumeChangedListener vclistener);
/**
* Wrapper for {@link AudioDeviceVolumeManager#setDeviceAbsoluteVolumeAdjustOnlyBehavior(
@@ -69,7 +69,7 @@ public interface AudioDeviceVolumeManagerWrapper {
void setDeviceAbsoluteVolumeAdjustOnlyBehavior(
@NonNull AudioDeviceAttributes device,
@NonNull VolumeInfo volume,
+ boolean handlesVolumeAdjustment,
@NonNull @CallbackExecutor Executor executor,
- @NonNull AudioDeviceVolumeManager.OnAudioDeviceVolumeChangedListener vclistener,
- boolean handlesVolumeAdjustment);
+ @NonNull AudioDeviceVolumeManager.OnAudioDeviceVolumeChangedListener vclistener);
}
diff --git a/services/core/java/com/android/server/hdmi/DefaultAudioDeviceVolumeManagerWrapper.java b/services/core/java/com/android/server/hdmi/DefaultAudioDeviceVolumeManagerWrapper.java
index ff99ace38ef0..10cbb00d2398 100644
--- a/services/core/java/com/android/server/hdmi/DefaultAudioDeviceVolumeManagerWrapper.java
+++ b/services/core/java/com/android/server/hdmi/DefaultAudioDeviceVolumeManagerWrapper.java
@@ -61,21 +61,21 @@ public class DefaultAudioDeviceVolumeManagerWrapper
public void setDeviceAbsoluteVolumeBehavior(
@NonNull AudioDeviceAttributes device,
@NonNull VolumeInfo volume,
+ boolean handlesVolumeAdjustment,
@NonNull @CallbackExecutor Executor executor,
- @NonNull AudioDeviceVolumeManager.OnAudioDeviceVolumeChangedListener vclistener,
- boolean handlesVolumeAdjustment) {
- mAudioDeviceVolumeManager.setDeviceAbsoluteVolumeBehavior(device, volume, executor,
- vclistener, handlesVolumeAdjustment);
+ @NonNull AudioDeviceVolumeManager.OnAudioDeviceVolumeChangedListener vclistener) {
+ mAudioDeviceVolumeManager.setDeviceAbsoluteVolumeBehavior(device, volume,
+ handlesVolumeAdjustment, executor, vclistener);
}
@Override
public void setDeviceAbsoluteVolumeAdjustOnlyBehavior(
@NonNull AudioDeviceAttributes device,
@NonNull VolumeInfo volume,
+ boolean handlesVolumeAdjustment,
@NonNull @CallbackExecutor Executor executor,
- @NonNull AudioDeviceVolumeManager.OnAudioDeviceVolumeChangedListener vclistener,
- boolean handlesVolumeAdjustment) {
+ @NonNull AudioDeviceVolumeManager.OnAudioDeviceVolumeChangedListener vclistener) {
mAudioDeviceVolumeManager.setDeviceAbsoluteVolumeAdjustOnlyBehavior(device, volume,
- executor, vclistener, handlesVolumeAdjustment);
+ handlesVolumeAdjustment, executor, vclistener);
}
}
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
index 3d6d34bf9911..3cb21c3e2697 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
@@ -1404,6 +1404,9 @@ public class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice {
if (connected) {
if (mArcEstablished) {
enableAudioReturnChannel(true);
+ } else {
+ HdmiLogger.debug("Restart ARC again");
+ onNewAvrAdded(getAvrDeviceInfo());
}
} else {
enableAudioReturnChannel(false);
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index 89f0d0edbf2b..6d973ac8d1b5 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -4798,15 +4798,15 @@ public class HdmiControlService extends SystemService {
Slog.d(TAG, "Enabling absolute volume behavior");
for (AudioDeviceAttributes device : getAvbCapableAudioOutputDevices()) {
getAudioDeviceVolumeManager().setDeviceAbsoluteVolumeBehavior(
- device, volumeInfo, mServiceThreadExecutor,
- mAbsoluteVolumeChangedListener, true);
+ device, volumeInfo, true, mServiceThreadExecutor,
+ mAbsoluteVolumeChangedListener);
}
} else if (tv() != null) {
Slog.d(TAG, "Enabling adjust-only absolute volume behavior");
for (AudioDeviceAttributes device : getAvbCapableAudioOutputDevices()) {
getAudioDeviceVolumeManager().setDeviceAbsoluteVolumeAdjustOnlyBehavior(
- device, volumeInfo, mServiceThreadExecutor,
- mAbsoluteVolumeChangedListener, true);
+ device, volumeInfo, true, mServiceThreadExecutor,
+ mAbsoluteVolumeChangedListener);
}
}
diff --git a/services/core/java/com/android/server/input/InputManagerInternal.java b/services/core/java/com/android/server/input/InputManagerInternal.java
index 87f693cc7291..1ace41cba364 100644
--- a/services/core/java/com/android/server/input/InputManagerInternal.java
+++ b/services/core/java/com/android/server/input/InputManagerInternal.java
@@ -344,4 +344,42 @@ public abstract class InputManagerInternal {
*/
public abstract void applyBackupPayload(Map<Integer, byte[]> payload, int userId)
throws XmlPullParserException, IOException;
+
+ /**
+ * An interface for filtering pointer motion event before cursor position is determined.
+ * <p>
+ * Different from {@code android.view.InputFilter}, this filter can filter motion events at
+ * an early stage of the input pipeline, but only called for pointer's relative motion events.
+ * Unless the user really needs to filter events before the cursor position in the display is
+ * determined, use {@code android.view.InputFilter} instead.
+ */
+ public interface AccessibilityPointerMotionFilter {
+ /**
+ * Called everytime pointer's relative motion event happens.
+ * The returned dx and dy will be used to move the cursor in the display.
+ * <p>
+ * This call happens on the input hot path and it is extremely performance sensitive. It
+ * also must not call back into native code.
+ *
+ * @param dx delta x of the event in pixels.
+ * @param dy delta y of the event in pixels.
+ * @param currentX the cursor x coordinate on the screen before the motion event.
+ * @param currentY the cursor y coordinate on the screen before the motion event.
+ * @param displayId the display ID of the current cursor.
+ * @return an array of length 2, delta x and delta y after filtering the motion. The delta
+ * values are in pixels and must be between 0 and original delta.
+ */
+ @NonNull
+ float[] filterPointerMotionEvent(float dx, float dy, float currentX, float currentY,
+ int displayId);
+ }
+
+ /**
+ * Registers an {@code AccessibilityCursorFilter}.
+ *
+ * @param filter The filter to register. If a filter is already registered, the old filter is
+ * unregistered. {@code null} unregisters the filter that is already registered.
+ */
+ public abstract void registerAccessibilityPointerMotionFilter(
+ @Nullable AccessibilityPointerMotionFilter filter);
}
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index 8624f4230e9c..0e37238bcb84 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -25,8 +25,8 @@ import static android.view.KeyEvent.KEYCODE_UNKNOWN;
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
import static com.android.hardware.input.Flags.enableCustomizableInputGestures;
-import static com.android.hardware.input.Flags.touchpadVisualizer;
import static com.android.hardware.input.Flags.keyEventActivityDetection;
+import static com.android.hardware.input.Flags.touchpadVisualizer;
import static com.android.hardware.input.Flags.useKeyGestureEventHandler;
import static com.android.server.policy.WindowManagerPolicy.ACTION_PASS_TO_USER;
@@ -193,15 +193,11 @@ public class InputManagerService extends IInputManager.Stub
private static final int MSG_SYSTEM_READY = 5;
private static final int DEFAULT_VIBRATION_MAGNITUDE = 192;
- private static final AdditionalDisplayInputProperties
- DEFAULT_ADDITIONAL_DISPLAY_INPUT_PROPERTIES = new AdditionalDisplayInputProperties();
private final NativeInputManagerService mNative;
private final Context mContext;
private final InputManagerHandler mHandler;
- @UserIdInt
- private int mCurrentUserId = UserHandle.USER_SYSTEM;
private DisplayManagerInternal mDisplayManagerInternal;
private WindowManagerInternal mWindowManagerInternal;
@@ -289,7 +285,7 @@ public class InputManagerService extends IInputManager.Stub
final Object mKeyEventActivityLock = new Object();
@GuardedBy("mKeyEventActivityLock")
- private List<IKeyEventActivityListener> mKeyEventActivityListenersToNotify =
+ private final List<IKeyEventActivityListener> mKeyEventActivityListenersToNotify =
new ArrayList<>();
// Rate limit for key event activity detection. Prevent the listener from being notified
@@ -460,6 +456,14 @@ public class InputManagerService extends IInputManager.Stub
private boolean mShowKeyPresses = false;
private boolean mShowRotaryInput = false;
+ /**
+ * A lock for the accessibility pointer motion filter. Don't call native methods while holding
+ * this lock.
+ */
+ private final Object mAccessibilityPointerMotionFilterLock = new Object();
+ private InputManagerInternal.AccessibilityPointerMotionFilter
+ mAccessibilityPointerMotionFilter = null;
+
/** Point of injection for test dependencies. */
@VisibleForTesting
static class Injector {
@@ -2593,6 +2597,23 @@ public class InputManagerService extends IInputManager.Stub
// Native callback.
@SuppressWarnings("unused")
+ final float[] filterPointerMotion(float dx, float dy, float currentX, float currentY,
+ int displayId) {
+ // This call happens on the input hot path and it is extremely performance sensitive.
+ // This must not call back into native code. This is called while the
+ // PointerChoreographer's lock is held.
+ synchronized (mAccessibilityPointerMotionFilterLock) {
+ if (mAccessibilityPointerMotionFilter == null) {
+ throw new IllegalStateException(
+ "filterCursor is invoked but no callback is registered.");
+ }
+ return mAccessibilityPointerMotionFilter.filterPointerMotionEvent(dx, dy, currentX,
+ currentY, displayId);
+ }
+ }
+
+ // Native callback.
+ @SuppressWarnings("unused")
@VisibleForTesting
public int interceptKeyBeforeQueueing(KeyEvent event, int policyFlags) {
notifyKeyActivityListeners(event);
@@ -3215,7 +3236,6 @@ public class InputManagerService extends IInputManager.Stub
}
private void handleCurrentUserChanged(@UserIdInt int userId) {
- mCurrentUserId = userId;
mKeyGestureController.setCurrentUserId(userId);
}
@@ -3828,6 +3848,12 @@ public class InputManagerService extends IInputManager.Stub
payload.get(BACKUP_CATEGORY_INPUT_GESTURES), userId);
}
}
+
+ @Override
+ public void registerAccessibilityPointerMotionFilter(
+ AccessibilityPointerMotionFilter filter) {
+ InputManagerService.this.registerAccessibilityPointerMotionFilter(filter);
+ }
}
@Override
@@ -4014,6 +4040,26 @@ public class InputManagerService extends IInputManager.Stub
mPointerIconCache.setAccessibilityScaleFactor(displayId, scaleFactor);
}
+ void registerAccessibilityPointerMotionFilter(
+ InputManagerInternal.AccessibilityPointerMotionFilter filter) {
+ // `#filterPointerMotion` expects that when it's called, `mAccessibilityPointerMotionFilter`
+ // is not null.
+ // Also, to avoid potential lock contention, we shouldn't call native method while holding
+ // the lock here. Native code calls `#filterPointerMotion` while PointerChoreographer's
+ // lock is held.
+ // Thus, we must set filter before we enable the filter in native, and reset the filter
+ // after we disable the filter.
+ // This also ensures the previously installed filter isn't called after the filter is
+ // updated.
+ mNative.setAccessibilityPointerMotionFilterEnabled(false);
+ synchronized (mAccessibilityPointerMotionFilterLock) {
+ mAccessibilityPointerMotionFilter = filter;
+ }
+ if (filter != null) {
+ mNative.setAccessibilityPointerMotionFilterEnabled(true);
+ }
+ }
+
interface KeyboardBacklightControllerInterface {
default void incrementKeyboardBacklight(int deviceId) {}
default void decrementKeyboardBacklight(int deviceId) {}
diff --git a/services/core/java/com/android/server/input/NativeInputManagerService.java b/services/core/java/com/android/server/input/NativeInputManagerService.java
index f34338a397db..32409d39db3b 100644
--- a/services/core/java/com/android/server/input/NativeInputManagerService.java
+++ b/services/core/java/com/android/server/input/NativeInputManagerService.java
@@ -315,6 +315,16 @@ interface NativeInputManagerService {
*/
boolean setKernelWakeEnabled(int deviceId, boolean enabled);
+ /**
+ * Set whether the accessibility pointer motion filter is enabled.
+ * <p>
+ * Once enabled, {@link InputManagerService#filterPointerMotion} is called for evety motion
+ * event from pointer devices.
+ *
+ * @param enabled {@code true} if the filter is enabled, {@code false} otherwise.
+ */
+ void setAccessibilityPointerMotionFilterEnabled(boolean enabled);
+
/** The native implementation of InputManagerService methods. */
class NativeImpl implements NativeInputManagerService {
/** Pointer to native input manager service object, used by native code. */
@@ -628,5 +638,8 @@ interface NativeInputManagerService {
@Override
public native boolean setKernelWakeEnabled(int deviceId, boolean enabled);
+
+ @Override
+ public native void setAccessibilityPointerMotionFilterEnabled(boolean enabled);
}
}
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 484b47022f04..508bc2f811e0 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -365,7 +365,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl.
return mCurrentImeUserId;
}
- /**
+ /**
* Figures out the target IME user ID associated with the given {@code displayId}.
*
* @param displayId the display ID to be queried about
@@ -649,12 +649,25 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl.
visibilityStateComputer.getImePolicy().setA11yRequestNoSoftKeyboard(
accessibilitySoftKeyboardSetting);
if (visibilityStateComputer.getImePolicy().isA11yRequestNoSoftKeyboard()) {
- hideCurrentInputLocked(userData.mImeBindingState.mFocusedWindow,
- 0 /* flags */, SoftInputShowHideReason.HIDE_SETTINGS_ON_CHANGE, userId);
+ if (Flags.refactorInsetsController()) {
+ final var statsToken = createStatsTokenForFocusedClient(false /* show */,
+ SoftInputShowHideReason.HIDE_SETTINGS_ON_CHANGE, userId);
+ setImeVisibilityOnFocusedWindowClient(false, userData, statsToken);
+ } else {
+ hideCurrentInputLocked(userData.mImeBindingState.mFocusedWindow,
+ 0 /* flags */, SoftInputShowHideReason.HIDE_SETTINGS_ON_CHANGE,
+ userId);
+ }
} else if (isShowRequestedForCurrentWindow(userId)) {
- showCurrentInputLocked(userData.mImeBindingState.mFocusedWindow,
- InputMethodManager.SHOW_IMPLICIT,
- SoftInputShowHideReason.SHOW_SETTINGS_ON_CHANGE, userId);
+ if (Flags.refactorInsetsController()) {
+ final var statsToken = createStatsTokenForFocusedClient(true /* show */,
+ SoftInputShowHideReason.SHOW_SETTINGS_ON_CHANGE, userId);
+ setImeVisibilityOnFocusedWindowClient(true, userData, statsToken);
+ } else {
+ showCurrentInputLocked(userData.mImeBindingState.mFocusedWindow,
+ InputMethodManager.SHOW_IMPLICIT,
+ SoftInputShowHideReason.SHOW_SETTINGS_ON_CHANGE, userId);
+ }
}
break;
}
@@ -1319,8 +1332,8 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl.
// Do not reset the default (current) IME when it is a 3rd-party IME
String selectedMethodId = bindingController.getSelectedMethodId();
final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
- if (selectedMethodId != null && settings.getMethodMap().get(selectedMethodId) != null
- && !settings.getMethodMap().get(selectedMethodId).isSystem()) {
+ final InputMethodInfo selectedImi = settings.getMethodMap().get(selectedMethodId);
+ if (selectedImi != null && !selectedImi.isSystem()) {
return;
}
final List<InputMethodInfo> suitableImes = InputMethodInfoUtils.getDefaultEnabledImes(
diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
index 6c0d8ad7264d..debac9436bb3 100644
--- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
+++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
@@ -1635,11 +1635,11 @@ class MediaRouter2ServiceImpl {
manager));
}
- List<MediaRoute2Info> routes =
- userRecord.mHandler.mLastNotifiedRoutesToPrivilegedRouters.values().stream()
- .toList();
userRecord.mHandler.sendMessage(
- obtainMessage(ManagerRecord::notifyRoutesUpdated, managerRecord, routes));
+ obtainMessage(
+ UserHandler::dispatchRoutesToManagerOnHandler,
+ userRecord.mHandler,
+ managerRecord));
}
@GuardedBy("mLock")
@@ -2119,6 +2119,9 @@ class MediaRouter2ServiceImpl {
mHasBluetoothRoutingPermission.set(checkCallerHasBluetoothPermissions(mPid, mUid));
boolean newSystemRoutingPermissionValue = hasSystemRoutingPermission();
if (oldSystemRoutingPermissionValue != newSystemRoutingPermissionValue) {
+ // TODO: b/379788233 - Ensure access to fields like
+ // mLastNotifiedRoutesToPrivilegedRouters happens on the right thread. We might need
+ // to run this on the handler.
Map<String, MediaRoute2Info> routesToReport =
newSystemRoutingPermissionValue
? mUserRecord.mHandler.mLastNotifiedRoutesToPrivilegedRouters
@@ -2543,6 +2546,8 @@ class MediaRouter2ServiceImpl {
* both system route providers and user route providers.
*
* <p>See {@link #getRouterRecords(boolean hasModifyAudioRoutingPermission)}.
+ *
+ * <p>Must be accessed on this handler's thread.
*/
private final Map<String, MediaRoute2Info> mLastNotifiedRoutesToPrivilegedRouters =
new ArrayMap<>();
@@ -2558,6 +2563,8 @@ class MediaRouter2ServiceImpl {
* (e.g. volume changes) to non-privileged routers.
*
* <p>See {@link SystemMediaRoute2Provider#mDefaultRoute}.
+ *
+ * <p>Must be accessed on this handler's thread.
*/
private final Map<String, MediaRoute2Info> mLastNotifiedRoutesToNonPrivilegedRouters =
new ArrayMap<>();
@@ -2800,7 +2807,7 @@ class MediaRouter2ServiceImpl {
removedRoutes));
}
- dispatchUpdates(
+ dispatchUpdatesOnHandler(
hasAddedOrModifiedRoutes,
hasRemovedRoutes,
provider.mIsSystemRouteProvider,
@@ -2822,6 +2829,13 @@ class MediaRouter2ServiceImpl {
source, providerId, routesString);
}
+ /** Notifies the given manager of the current routes. */
+ public void dispatchRoutesToManagerOnHandler(ManagerRecord managerRecord) {
+ List<MediaRoute2Info> routes =
+ mLastNotifiedRoutesToPrivilegedRouters.values().stream().toList();
+ managerRecord.notifyRoutesUpdated(routes);
+ }
+
/**
* Dispatches the latest route updates in {@link #mLastNotifiedRoutesToPrivilegedRouters}
* and {@link #mLastNotifiedRoutesToNonPrivilegedRouters} to registered {@link
@@ -2834,7 +2848,7 @@ class MediaRouter2ServiceImpl {
* @param isSystemProvider whether the latest update was caused by a system provider.
* @param defaultRoute the current default route in {@link #mSystemProvider}.
*/
- private void dispatchUpdates(
+ private void dispatchUpdatesOnHandler(
boolean hasAddedOrModifiedRoutes,
boolean hasRemovedRoutes,
boolean isSystemProvider,
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 4cf439611852..91a2843ccaf7 100644
--- a/services/core/java/com/android/server/media/quality/MediaQualityService.java
+++ b/services/core/java/com/android/server/media/quality/MediaQualityService.java
@@ -120,6 +120,8 @@ public class MediaQualityService extends SystemService {
private final Object mPictureProfileLock = new Object();
// A global lock for sound profile objects.
private final Object mSoundProfileLock = new Object();
+ // A global lock for user state objects.
+ private final Object mUserStateLock = new Object();
// A global lock for ambient backlight objects.
private final Object mAmbientBacklightLock = new Object();
@@ -181,7 +183,6 @@ public class MediaQualityService extends SystemService {
publishBinderService(Context.MEDIA_QUALITY_SERVICE, new BinderService());
}
- // TODO: Add additional APIs. b/373951081
private final class BinderService extends IMediaQualityManager.Stub {
@GuardedBy("mPictureProfileLock")
@@ -269,12 +270,13 @@ public class MediaQualityService extends SystemService {
mMqManagerNotifier.notifyOnPictureProfileError(id,
PictureProfile.ERROR_INVALID_ARGUMENT,
Binder.getCallingUid(), Binder.getCallingPid());
+ } else {
+ mMqManagerNotifier.notifyOnPictureProfileRemoved(
+ mPictureProfileTempIdMap.getValue(dbId), toDelete,
+ Binder.getCallingUid(), Binder.getCallingPid());
+ mPictureProfileTempIdMap.remove(dbId);
+ mHalNotifier.notifyHalOnPictureProfileChange(dbId, null);
}
- mMqManagerNotifier.notifyOnPictureProfileRemoved(
- mPictureProfileTempIdMap.getValue(dbId), toDelete,
- Binder.getCallingUid(), Binder.getCallingPid());
- mPictureProfileTempIdMap.remove(dbId);
- mHalNotifier.notifyHalOnPictureProfileChange(dbId, null);
}
}
}
@@ -520,12 +522,13 @@ public class MediaQualityService extends SystemService {
mMqManagerNotifier.notifyOnSoundProfileError(id,
SoundProfile.ERROR_INVALID_ARGUMENT,
Binder.getCallingUid(), Binder.getCallingPid());
+ } else {
+ mMqManagerNotifier.notifyOnSoundProfileRemoved(
+ mSoundProfileTempIdMap.getValue(dbId), toDelete,
+ Binder.getCallingUid(), Binder.getCallingPid());
+ mSoundProfileTempIdMap.remove(dbId);
+ mHalNotifier.notifyHalOnSoundProfileChange(dbId, null);
}
- mMqManagerNotifier.notifyOnSoundProfileRemoved(
- mSoundProfileTempIdMap.getValue(dbId), toDelete,
- Binder.getCallingUid(), Binder.getCallingPid());
- mSoundProfileTempIdMap.remove(dbId);
- mHalNotifier.notifyHalOnSoundProfileChange(dbId, null);
}
}
}
@@ -684,24 +687,22 @@ public class MediaQualityService extends SystemService {
mContext.getPackageName()) == mPackageManager.PERMISSION_GRANTED;
}
- //TODO: need lock here?
@Override
public void registerPictureProfileCallback(final IPictureProfileCallback callback) {
int callingPid = Binder.getCallingPid();
int callingUid = Binder.getCallingUid();
- UserState userState = getOrCreateUserStateLocked(Binder.getCallingUid());
+ UserState userState = getOrCreateUserState(Binder.getCallingUid());
userState.mPictureProfileCallbackPidUidMap.put(callback,
Pair.create(callingPid, callingUid));
}
- //TODO: need lock here?
@Override
public void registerSoundProfileCallback(final ISoundProfileCallback callback) {
int callingPid = Binder.getCallingPid();
int callingUid = Binder.getCallingUid();
- UserState userState = getOrCreateUserStateLocked(Binder.getCallingUid());
+ UserState userState = getOrCreateUserState(Binder.getCallingUid());
userState.mSoundProfileCallbackPidUidMap.put(callback,
Pair.create(callingPid, callingUid));
}
@@ -1060,7 +1061,7 @@ public class MediaQualityService extends SystemService {
synchronized (mPictureProfileLock) {
for (int i = 0; i < mUserStates.size(); i++) {
int userId = mUserStates.keyAt(i);
- UserState userState = getOrCreateUserStateLocked(userId);
+ UserState userState = getOrCreateUserState(userId);
userState.mPictureProfileCallbackPidUidMap.remove(callback);
}
}
@@ -1074,7 +1075,7 @@ public class MediaQualityService extends SystemService {
synchronized (mSoundProfileLock) {
for (int i = 0; i < mUserStates.size(); i++) {
int userId = mUserStates.keyAt(i);
- UserState userState = getOrCreateUserStateLocked(userId);
+ UserState userState = getOrCreateUserState(userId);
userState.mSoundProfileCallbackPidUidMap.remove(callback);
}
}
@@ -1100,19 +1101,23 @@ public class MediaQualityService extends SystemService {
}
}
- //TODO: used by both picture and sound. can i add both locks?
- private UserState getOrCreateUserStateLocked(int userId) {
- UserState userState = getUserStateLocked(userId);
+ @GuardedBy("mUserStateLock")
+ private UserState getOrCreateUserState(int userId) {
+ UserState userState = getUserState(userId);
if (userState == null) {
userState = new UserState(mContext, userId);
- mUserStates.put(userId, userState);
+ synchronized (mUserStateLock) {
+ mUserStates.put(userId, userState);
+ }
}
return userState;
}
- //TODO: used by both picture and sound. can i add both locks?
- private UserState getUserStateLocked(int userId) {
- return mUserStates.get(userId);
+ @GuardedBy("mUserStateLock")
+ private UserState getUserState(int userId) {
+ synchronized (mUserStateLock) {
+ return mUserStates.get(userId);
+ }
}
private final class MqDatabaseUtils {
@@ -1266,7 +1271,7 @@ public class MediaQualityService extends SystemService {
private void notifyPictureProfileHelper(int mode, String profileId,
PictureProfile profile, Integer errorCode,
List<ParameterCapability> paramCaps, int uid, int pid) {
- UserState userState = getOrCreateUserStateLocked(UserHandle.USER_SYSTEM);
+ UserState userState = getOrCreateUserState(UserHandle.USER_SYSTEM);
int n = userState.mPictureProfileCallbacks.beginBroadcast();
for (int i = 0; i < n; ++i) {
@@ -1351,7 +1356,7 @@ public class MediaQualityService extends SystemService {
private void notifySoundProfileHelper(int mode, String profileId,
SoundProfile profile, Integer errorCode,
List<ParameterCapability> paramCaps, int uid, int pid) {
- UserState userState = getOrCreateUserStateLocked(UserHandle.USER_SYSTEM);
+ UserState userState = getOrCreateUserState(UserHandle.USER_SYSTEM);
int n = userState.mSoundProfileCallbacks.beginBroadcast();
for (int i = 0; i < n; ++i) {
diff --git a/services/core/java/com/android/server/notification/ManagedServices.java b/services/core/java/com/android/server/notification/ManagedServices.java
index b0ef80793cd7..62e26e189a35 100644
--- a/services/core/java/com/android/server/notification/ManagedServices.java
+++ b/services/core/java/com/android/server/notification/ManagedServices.java
@@ -25,6 +25,8 @@ import static android.os.UserHandle.USER_ALL;
import static android.os.UserHandle.USER_SYSTEM;
import static android.service.notification.NotificationListenerService.META_DATA_DEFAULT_AUTOBIND;
+import static com.android.server.notification.Flags.FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER;
+import static com.android.server.notification.Flags.managedServicesConcurrentMultiuser;
import static com.android.server.notification.NotificationManagerService.privateSpaceFlagsEnabled;
import android.annotation.FlaggedApi;
@@ -75,7 +77,9 @@ import com.android.internal.util.XmlUtils;
import com.android.internal.util.function.TriPredicate;
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;
+import com.android.server.LocalServices;
import com.android.server.notification.NotificationManagerService.DumpFilter;
+import com.android.server.pm.UserManagerInternal;
import com.android.server.utils.TimingsTraceAndSlog;
import org.xmlpull.v1.XmlPullParser;
@@ -134,6 +138,7 @@ abstract public class ManagedServices {
private final UserProfiles mUserProfiles;
protected final IPackageManager mPm;
protected final UserManager mUm;
+ protected final UserManagerInternal mUmInternal;
private final Config mConfig;
private final Handler mHandler = new Handler(Looper.getMainLooper());
@@ -157,12 +162,17 @@ abstract public class ManagedServices {
protected final ArraySet<String> mDefaultPackages = new ArraySet<>();
// lists the component names of all enabled (and therefore potentially connected)
- // app services for current profiles.
+ // app services for each user. This is intended to support a concurrent multi-user environment.
+ // key value is the resolved userId.
@GuardedBy("mMutex")
- private final ArraySet<ComponentName> mEnabledServicesForCurrentProfiles = new ArraySet<>();
- // Just the packages from mEnabledServicesForCurrentProfiles
+ private final SparseArray<ArraySet<ComponentName>> mEnabledServicesByUser =
+ new SparseArray<>();
+ // Just the packages from mEnabledServicesByUser
+ // This is intended to support a concurrent multi-user environment.
+ // key value is the resolved userId.
@GuardedBy("mMutex")
- private final ArraySet<String> mEnabledServicesPackageNames = new ArraySet<>();
+ private final SparseArray<ArraySet<String>> mEnabledServicesPackageNamesByUser =
+ new SparseArray<>();
// Per user id, list of enabled packages that have nevertheless asked not to be run
@GuardedBy("mSnoozing")
private final SparseSetArray<ComponentName> mSnoozing = new SparseSetArray<>();
@@ -195,6 +205,10 @@ abstract public class ManagedServices {
mConfig = getConfig();
mApprovalLevel = APPROVAL_BY_COMPONENT;
mUm = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
+ mUmInternal = LocalServices.getService(UserManagerInternal.class);
+ // Initialize for the current user.
+ mEnabledServicesByUser.put(UserHandle.USER_CURRENT, new ArraySet<>());
+ mEnabledServicesPackageNamesByUser.put(UserHandle.USER_CURRENT, new ArraySet<>());
}
abstract protected Config getConfig();
@@ -383,11 +397,30 @@ abstract public class ManagedServices {
}
synchronized (mMutex) {
- pw.println(" All " + getCaption() + "s (" + mEnabledServicesForCurrentProfiles.size()
- + ") enabled for current profiles:");
- for (ComponentName cmpt : mEnabledServicesForCurrentProfiles) {
- if (filter != null && !filter.matches(cmpt)) continue;
- pw.println(" " + cmpt);
+ if (managedServicesConcurrentMultiuser()) {
+ for (int i = 0; i < mEnabledServicesByUser.size(); i++) {
+ final int userId = mEnabledServicesByUser.keyAt(i);
+ final ArraySet<ComponentName> componentNames =
+ mEnabledServicesByUser.get(userId);
+ String userString = userId == UserHandle.USER_CURRENT
+ ? "current profiles" : "user " + Integer.toString(userId);
+ pw.println(" All " + getCaption() + "s (" + componentNames.size()
+ + ") enabled for " + userString + ":");
+ for (ComponentName cmpt : componentNames) {
+ if (filter != null && !filter.matches(cmpt)) continue;
+ pw.println(" " + cmpt);
+ }
+ }
+ } else {
+ final ArraySet<ComponentName> enabledServicesForCurrentProfiles =
+ mEnabledServicesByUser.get(UserHandle.USER_CURRENT);
+ pw.println(" All " + getCaption() + "s ("
+ + enabledServicesForCurrentProfiles.size()
+ + ") enabled for current profiles:");
+ for (ComponentName cmpt : enabledServicesForCurrentProfiles) {
+ if (filter != null && !filter.matches(cmpt)) continue;
+ pw.println(" " + cmpt);
+ }
}
pw.println(" Live " + getCaption() + "s (" + mServices.size() + "):");
@@ -442,11 +475,24 @@ abstract public class ManagedServices {
}
}
-
synchronized (mMutex) {
- for (ComponentName cmpt : mEnabledServicesForCurrentProfiles) {
- if (filter != null && !filter.matches(cmpt)) continue;
- cmpt.dumpDebug(proto, ManagedServicesProto.ENABLED);
+ if (managedServicesConcurrentMultiuser()) {
+ for (int i = 0; i < mEnabledServicesByUser.size(); i++) {
+ final int userId = mEnabledServicesByUser.keyAt(i);
+ final ArraySet<ComponentName> componentNames =
+ mEnabledServicesByUser.get(userId);
+ for (ComponentName cmpt : componentNames) {
+ if (filter != null && !filter.matches(cmpt)) continue;
+ cmpt.dumpDebug(proto, ManagedServicesProto.ENABLED);
+ }
+ }
+ } else {
+ final ArraySet<ComponentName> enabledServicesForCurrentProfiles =
+ mEnabledServicesByUser.get(UserHandle.USER_CURRENT);
+ for (ComponentName cmpt : enabledServicesForCurrentProfiles) {
+ if (filter != null && !filter.matches(cmpt)) continue;
+ cmpt.dumpDebug(proto, ManagedServicesProto.ENABLED);
+ }
}
for (ManagedServiceInfo info : mServices) {
if (filter != null && !filter.matches(info.component)) continue;
@@ -841,9 +887,31 @@ abstract public class ManagedServices {
}
}
+ /** convenience method for looking in mEnabledServicesPackageNamesByUser
+ * for UserHandle.USER_CURRENT.
+ * This is a legacy API. When FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER becomes
+ * trunk stable, this API should be deprecated. Additionally, when this method
+ * is deprecated, the unit tests written using this method should also be revised.
+ *
+ * @param pkg target package name
+ * @return boolean value that indicates whether it is enabled for the current profiles
+ */
protected boolean isComponentEnabledForPackage(String pkg) {
+ return isComponentEnabledForPackage(pkg, UserHandle.USER_CURRENT);
+ }
+
+ /** convenience method for looking in mEnabledServicesPackageNamesByUser
+ *
+ * @param pkg target package name
+ * @param userId the id of the target user
+ * @return boolean value that indicates whether it is enabled for the target user
+ */
+ @FlaggedApi(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER)
+ protected boolean isComponentEnabledForPackage(String pkg, int userId) {
synchronized (mMutex) {
- return mEnabledServicesPackageNames.contains(pkg);
+ ArraySet<String> enabledServicesPackageNames =
+ mEnabledServicesPackageNamesByUser.get(resolveUserId(userId));
+ return enabledServicesPackageNames != null && enabledServicesPackageNames.contains(pkg);
}
}
@@ -1016,9 +1084,14 @@ abstract public class ManagedServices {
public void onPackagesChanged(boolean removingPackage, String[] pkgList, int[] uidList) {
if (DEBUG) {
synchronized (mMutex) {
+ int resolvedUserId = (managedServicesConcurrentMultiuser()
+ && (uidList != null && uidList.length > 0))
+ ? resolveUserId(UserHandle.getUserId(uidList[0]))
+ : UserHandle.USER_CURRENT;
Slog.d(TAG, "onPackagesChanged removingPackage=" + removingPackage
+ " pkgList=" + (pkgList == null ? null : Arrays.asList(pkgList))
- + " mEnabledServicesPackageNames=" + mEnabledServicesPackageNames);
+ + " mEnabledServicesPackageNames="
+ + mEnabledServicesPackageNamesByUser.get(resolvedUserId));
}
}
@@ -1034,11 +1107,18 @@ abstract public class ManagedServices {
}
}
for (String pkgName : pkgList) {
- if (isComponentEnabledForPackage(pkgName)) {
- anyServicesInvolved = true;
+ if (!managedServicesConcurrentMultiuser()) {
+ if (isComponentEnabledForPackage(pkgName)) {
+ anyServicesInvolved = true;
+ }
}
if (uidList != null && uidList.length > 0) {
for (int uid : uidList) {
+ if (managedServicesConcurrentMultiuser()) {
+ if (isComponentEnabledForPackage(pkgName, UserHandle.getUserId(uid))) {
+ anyServicesInvolved = true;
+ }
+ }
if (isPackageAllowed(pkgName, UserHandle.getUserId(uid))) {
anyServicesInvolved = true;
trimApprovedListsForInvalidServices(pkgName, UserHandle.getUserId(uid));
@@ -1065,6 +1145,36 @@ abstract public class ManagedServices {
unbindUserServices(user);
}
+ /**
+ * Call this method when a user is stopped
+ *
+ * @param user the id of the stopped user
+ */
+ public void onUserStopped(int user) {
+ if (!managedServicesConcurrentMultiuser()) {
+ return;
+ }
+ boolean hasAny = false;
+ synchronized (mMutex) {
+ if (mEnabledServicesByUser.contains(user)
+ && mEnabledServicesPackageNamesByUser.contains(user)) {
+ // Through the ManagedServices.resolveUserId,
+ // we resolve UserHandle.USER_CURRENT as the key for users
+ // other than the visible background user.
+ // Therefore, the user IDs that exist as keys for each member variable
+ // correspond to the visible background user.
+ // We need to unbind services of the stopped visible background user.
+ mEnabledServicesByUser.remove(user);
+ mEnabledServicesPackageNamesByUser.remove(user);
+ hasAny = true;
+ }
+ }
+ if (hasAny) {
+ Slog.i(TAG, "Removing approved services for stopped user " + user);
+ unbindUserServices(user);
+ }
+ }
+
public void onUserSwitched(int user) {
if (DEBUG) Slog.d(TAG, "onUserSwitched u=" + user);
unbindOtherUserServices(user);
@@ -1386,19 +1496,42 @@ abstract public class ManagedServices {
protected void populateComponentsToBind(SparseArray<Set<ComponentName>> componentsToBind,
final IntArray activeUsers,
SparseArray<ArraySet<ComponentName>> approvedComponentsByUser) {
- mEnabledServicesForCurrentProfiles.clear();
- mEnabledServicesPackageNames.clear();
final int nUserIds = activeUsers.size();
-
+ if (managedServicesConcurrentMultiuser()) {
+ for (int i = 0; i < nUserIds; ++i) {
+ final int resolvedUserId = resolveUserId(activeUsers.get(i));
+ if (mEnabledServicesByUser.get(resolvedUserId) != null) {
+ mEnabledServicesByUser.get(resolvedUserId).clear();
+ }
+ if (mEnabledServicesPackageNamesByUser.get(resolvedUserId) != null) {
+ mEnabledServicesPackageNamesByUser.get(resolvedUserId).clear();
+ }
+ }
+ } else {
+ mEnabledServicesByUser.get(UserHandle.USER_CURRENT).clear();
+ mEnabledServicesPackageNamesByUser.get(UserHandle.USER_CURRENT).clear();
+ }
for (int i = 0; i < nUserIds; ++i) {
- // decode the list of components
final int userId = activeUsers.get(i);
+ // decode the list of components
final ArraySet<ComponentName> userComponents = approvedComponentsByUser.get(userId);
if (null == userComponents) {
componentsToBind.put(userId, new ArraySet<>());
continue;
}
+ final int resolvedUserId = managedServicesConcurrentMultiuser()
+ ? resolveUserId(userId)
+ : UserHandle.USER_CURRENT;
+ ArraySet<ComponentName> enabledServices =
+ mEnabledServicesByUser.contains(resolvedUserId)
+ ? mEnabledServicesByUser.get(resolvedUserId)
+ : new ArraySet<>();
+ ArraySet<String> enabledServicesPackageName =
+ mEnabledServicesPackageNamesByUser.contains(resolvedUserId)
+ ? mEnabledServicesPackageNamesByUser.get(resolvedUserId)
+ : new ArraySet<>();
+
final Set<ComponentName> add = new HashSet<>(userComponents);
synchronized (mSnoozing) {
ArraySet<ComponentName> snoozed = mSnoozing.get(userId);
@@ -1409,12 +1542,12 @@ abstract public class ManagedServices {
componentsToBind.put(userId, add);
- mEnabledServicesForCurrentProfiles.addAll(userComponents);
-
+ enabledServices.addAll(userComponents);
for (int j = 0; j < userComponents.size(); j++) {
- final ComponentName component = userComponents.valueAt(j);
- mEnabledServicesPackageNames.add(component.getPackageName());
+ enabledServicesPackageName.add(userComponents.valueAt(j).getPackageName());
}
+ mEnabledServicesByUser.put(resolvedUserId, enabledServices);
+ mEnabledServicesPackageNamesByUser.put(resolvedUserId, enabledServicesPackageName);
}
}
@@ -1453,13 +1586,9 @@ abstract public class ManagedServices {
*/
protected void rebindServices(boolean forceRebind, int userToRebind) {
if (DEBUG) Slog.d(TAG, "rebindServices " + forceRebind + " " + userToRebind);
- IntArray userIds = mUserProfiles.getCurrentProfileIds();
boolean rebindAllCurrentUsers = mUserProfiles.isProfileUser(userToRebind, mContext)
&& allowRebindForParentUser();
- if (userToRebind != USER_ALL && !rebindAllCurrentUsers) {
- userIds = new IntArray(1);
- userIds.add(userToRebind);
- }
+ IntArray userIds = getUserIdsForRebindServices(userToRebind, rebindAllCurrentUsers);
final SparseArray<Set<ComponentName>> componentsToBind = new SparseArray<>();
final SparseArray<Set<ComponentName>> componentsToUnbind = new SparseArray<>();
@@ -1483,6 +1612,23 @@ abstract public class ManagedServices {
bindToServices(componentsToBind);
}
+ private IntArray getUserIdsForRebindServices(int userToRebind, boolean rebindAllCurrentUsers) {
+ IntArray userIds = mUserProfiles.getCurrentProfileIds();
+ if (userToRebind != USER_ALL && !rebindAllCurrentUsers) {
+ userIds = new IntArray(1);
+ userIds.add(userToRebind);
+ } else if (managedServicesConcurrentMultiuser()
+ && userToRebind == USER_ALL) {
+ for (UserInfo user : mUm.getUsers()) {
+ if (mUmInternal.isVisibleBackgroundFullUser(user.id)
+ && !userIds.contains(user.id)) {
+ userIds.add(user.id);
+ }
+ }
+ }
+ return userIds;
+ }
+
/**
* Called when user switched to unbind all services from other users.
*/
@@ -1506,7 +1652,11 @@ abstract public class ManagedServices {
synchronized (mMutex) {
final Set<ManagedServiceInfo> removableBoundServices = getRemovableConnectedServices();
for (ManagedServiceInfo info : removableBoundServices) {
- if ((allExceptUser && (info.userid != user))
+ // User switching is the event for the forground user.
+ // It should not affect the service of the visible background user.
+ if ((allExceptUser && (info.userid != user)
+ && !(managedServicesConcurrentMultiuser()
+ && info.isVisibleBackgroundUserService))
|| (!allExceptUser && (info.userid == user))) {
Set<ComponentName> toUnbind =
componentsToUnbind.get(info.userid, new ArraySet<>());
@@ -1861,6 +2011,29 @@ abstract public class ManagedServices {
}
/**
+ * This method returns the mapped id for the incoming user id
+ * If the incoming id was not the id of the visible background user, it returns USER_CURRENT.
+ * In the other cases, it returns the same value as the input.
+ *
+ * @param userId the id of the user
+ * @return the user id if it is a visible background user, otherwise
+ * {@link UserHandle#USER_CURRENT}
+ */
+ @FlaggedApi(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER)
+ @VisibleForTesting
+ public int resolveUserId(int userId) {
+ if (managedServicesConcurrentMultiuser()) {
+ if (mUmInternal.isVisibleBackgroundFullUser(userId)) {
+ // The dataset of the visible background user should be managed independently.
+ return userId;
+ }
+ }
+ // The data of current user and its profile users need to be managed
+ // in a dataset as before.
+ return UserHandle.USER_CURRENT;
+ }
+
+ /**
* Returns true if services in the parent user should be rebound
* when rebindServices is called with a profile userId.
* Must be false for NotificationAssistants.
@@ -1878,6 +2051,8 @@ abstract public class ManagedServices {
public int targetSdkVersion;
public Pair<ComponentName, Integer> mKey;
public int uid;
+ @FlaggedApi(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER)
+ public boolean isVisibleBackgroundUserService;
public ManagedServiceInfo(IInterface service, ComponentName component,
int userid, boolean isSystem, ServiceConnection connection, int targetSdkVersion,
@@ -1889,6 +2064,10 @@ abstract public class ManagedServices {
this.connection = connection;
this.targetSdkVersion = targetSdkVersion;
this.uid = uid;
+ if (managedServicesConcurrentMultiuser()) {
+ this.isVisibleBackgroundUserService = LocalServices
+ .getService(UserManagerInternal.class).isVisibleBackgroundFullUser(userid);
+ }
mKey = Pair.create(component, userid);
}
@@ -1937,19 +2116,28 @@ abstract public class ManagedServices {
}
public boolean isSameUser(int userId) {
- if (!isEnabledForCurrentProfiles()) {
+ if (!isEnabledForUser()) {
return false;
}
return userId == USER_ALL || userId == this.userid;
}
public boolean enabledAndUserMatches(int nid) {
- if (!isEnabledForCurrentProfiles()) {
+ if (!isEnabledForUser()) {
return false;
}
if (this.userid == USER_ALL) return true;
if (this.isSystem) return true;
if (nid == USER_ALL || nid == this.userid) return true;
+ if (managedServicesConcurrentMultiuser()
+ && mUmInternal.getProfileParentId(nid)
+ != mUmInternal.getProfileParentId(this.userid)) {
+ // If the profile parent IDs do not match each other,
+ // it is determined that the users do not match.
+ // This situation may occur when comparing the current user's ID
+ // with the visible background user's ID.
+ return false;
+ }
return supportsProfiles()
&& mUserProfiles.isCurrentProfile(nid)
&& isPermittedForProfile(nid);
@@ -1969,12 +2157,21 @@ abstract public class ManagedServices {
removeServiceImpl(this.service, this.userid);
}
- /** convenience method for looking in mEnabledServicesForCurrentProfiles */
- public boolean isEnabledForCurrentProfiles() {
+ /**
+ * convenience method for looking in mEnabledServicesByUser.
+ * If FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER is disabled, this manages the data using
+ * only UserHandle.USER_CURRENT as the key, in order to behave the same as the legacy logic.
+ */
+ public boolean isEnabledForUser() {
if (this.isSystem) return true;
if (this.connection == null) return false;
synchronized (mMutex) {
- return mEnabledServicesForCurrentProfiles.contains(this.component);
+ int resolvedUserId = managedServicesConcurrentMultiuser()
+ ? resolveUserId(this.userid)
+ : UserHandle.USER_CURRENT;
+ ArraySet<ComponentName> enabledServices =
+ mEnabledServicesByUser.get(resolvedUserId);
+ return enabledServices != null && enabledServices.contains(this.component);
}
}
@@ -2017,10 +2214,30 @@ abstract public class ManagedServices {
}
}
- /** convenience method for looking in mEnabledServicesForCurrentProfiles */
+ /** convenience method for looking in mEnabledServicesByUser for UserHandle.USER_CURRENT.
+ * This is a legacy API. When FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER becomes
+ * trunk stable, this API should be deprecated. Additionally, when this method
+ * is deprecated, the unit tests written using this method should also be revised.
+ *
+ * @param component target component name
+ * @return boolean value that indicates whether it is enabled for the current profiles
+ */
public boolean isComponentEnabledForCurrentProfiles(ComponentName component) {
+ return isComponentEnabledForUser(component, UserHandle.USER_CURRENT);
+ }
+
+ /** convenience method for looking in mEnabledServicesForUser
+ *
+ * @param component target component name
+ * @param userId the id of the target user
+ * @return boolean value that indicates whether it is enabled for the target user
+ */
+ @FlaggedApi(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER)
+ public boolean isComponentEnabledForUser(ComponentName component, int userId) {
synchronized (mMutex) {
- return mEnabledServicesForCurrentProfiles.contains(component);
+ ArraySet<ComponentName> enabledServicesForUser =
+ mEnabledServicesByUser.get(resolveUserId(userId));
+ return enabledServicesForUser != null && enabledServicesForUser.contains(component);
}
}
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 340afb776405..6fddfb5f90a7 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -173,6 +173,7 @@ import static com.android.server.am.PendingIntentRecord.FLAG_ACTIVITY_SENDER;
import static com.android.server.am.PendingIntentRecord.FLAG_BROADCAST_SENDER;
import static com.android.server.am.PendingIntentRecord.FLAG_SERVICE_SENDER;
import static com.android.server.notification.Flags.expireBitmaps;
+import static com.android.server.notification.Flags.managedServicesConcurrentMultiuser;
import static com.android.server.policy.PhoneWindowManager.TOAST_WINDOW_ANIM_BUFFER;
import static com.android.server.policy.PhoneWindowManager.TOAST_WINDOW_TIMEOUT;
import static com.android.server.utils.PriorityDump.PRIORITY_ARG;
@@ -1207,7 +1208,7 @@ public class NotificationManagerService extends SystemService {
}
mAssistants.resetDefaultAssistantsIfNecessary();
- mPreferencesHelper.syncChannelsBypassingDnd();
+ mPreferencesHelper.syncHasPriorityChannels();
}
@VisibleForTesting
@@ -2323,6 +2324,9 @@ public class NotificationManagerService extends SystemService {
if (userHandle >= 0) {
cancelAllNotificationsInt(MY_UID, MY_PID, null, null, 0, 0, userHandle,
REASON_USER_STOPPED);
+ mConditionProviders.onUserStopped(userHandle);
+ mListeners.onUserStopped(userHandle);
+ mAssistants.onUserStopped(userHandle);
}
} else if (
isProfileUnavailable(action)) {
@@ -2343,7 +2347,7 @@ public class NotificationManagerService extends SystemService {
mConditionProviders.onUserSwitched(userId);
mListeners.onUserSwitched(userId);
mZenModeHelper.onUserSwitched(userId);
- mPreferencesHelper.syncChannelsBypassingDnd();
+ mPreferencesHelper.syncHasPriorityChannels();
}
// assistant is the only thing that cares about managed profiles specifically
mAssistants.onUserSwitched(userId);
@@ -2367,7 +2371,7 @@ public class NotificationManagerService extends SystemService {
mConditionProviders.onUserRemoved(userId);
mAssistants.onUserRemoved(userId);
mHistoryManager.onUserRemoved(userId);
- mPreferencesHelper.syncChannelsBypassingDnd();
+ mPreferencesHelper.syncHasPriorityChannels();
handleSavePolicyFile();
} else if (action.equals(Intent.ACTION_USER_UNLOCKED)) {
final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, USER_NULL);
@@ -2376,9 +2380,6 @@ public class NotificationManagerService extends SystemService {
if (!mUserProfiles.isProfileUser(userId, context)) {
mConditionProviders.onUserUnlocked(userId);
mListeners.onUserUnlocked(userId);
- if (!android.app.Flags.modesApi()) {
- mZenModeHelper.onUserUnlocked(userId);
- }
}
}
}
@@ -2767,9 +2768,7 @@ public class NotificationManagerService extends SystemService {
void onPolicyChanged(Policy newPolicy) {
Binder.withCleanCallingIdentity(() -> {
Intent intent = new Intent(ACTION_NOTIFICATION_POLICY_CHANGED);
- if (android.app.Flags.modesApi()) {
- intent.putExtra(EXTRA_NOTIFICATION_POLICY, newPolicy);
- }
+ intent.putExtra(EXTRA_NOTIFICATION_POLICY, newPolicy);
sendRegisteredOnlyBroadcast(intent);
mRankingHandler.requestSort();
});
@@ -2778,11 +2777,10 @@ public class NotificationManagerService extends SystemService {
@Override
void onConsolidatedPolicyChanged(Policy newConsolidatedPolicy) {
Binder.withCleanCallingIdentity(() -> {
- if (android.app.Flags.modesApi()) {
- Intent intent = new Intent(ACTION_CONSOLIDATED_NOTIFICATION_POLICY_CHANGED);
- intent.putExtra(EXTRA_NOTIFICATION_POLICY, newConsolidatedPolicy);
- sendRegisteredOnlyBroadcast(intent);
- }
+ Intent intent = new Intent(ACTION_CONSOLIDATED_NOTIFICATION_POLICY_CHANGED);
+ intent.putExtra(EXTRA_NOTIFICATION_POLICY, newConsolidatedPolicy);
+ sendRegisteredOnlyBroadcast(intent);
+
mRankingHandler.requestSort();
});
}
@@ -3368,7 +3366,7 @@ public class NotificationManagerService extends SystemService {
migrateDefaultNAS();
maybeShowInitialReviewPermissionsNotification();
- if (android.app.Flags.modesApi() && !mZenModeHelper.hasDeviceEffectsApplier()) {
+ if (!mZenModeHelper.hasDeviceEffectsApplier()) {
// Cannot be done earlier, as some services aren't ready until this point.
mZenModeHelper.setDeviceEffectsApplier(
new DefaultDeviceEffectsApplier(getContext()));
@@ -3446,7 +3444,7 @@ public class NotificationManagerService extends SystemService {
mConditionProviders.onUserSwitched(userId);
mListeners.onUserSwitched(userId);
mZenModeHelper.onUserSwitched(userId);
- mPreferencesHelper.syncChannelsBypassingDnd();
+ mPreferencesHelper.syncHasPriorityChannels();
}
// assistant is the only thing that cares about managed profiles specifically
mAssistants.onUserSwitched(userId);
@@ -5236,11 +5234,8 @@ public class NotificationManagerService extends SystemService {
@Override
public boolean areChannelsBypassingDnd() {
- if (android.app.Flags.modesApi()) {
- return mZenModeHelper.getConsolidatedNotificationPolicy().allowPriorityChannels()
- && mPreferencesHelper.areChannelsBypassingDnd();
- }
- return mPreferencesHelper.areChannelsBypassingDnd();
+ return mZenModeHelper.getConsolidatedNotificationPolicy().allowPriorityChannels()
+ && mPreferencesHelper.hasPriorityChannels();
}
@Override
@@ -5730,12 +5725,13 @@ public class NotificationManagerService extends SystemService {
public void requestBindListener(ComponentName component) {
checkCallerIsSystemOrSameApp(component.getPackageName());
int uid = Binder.getCallingUid();
+ int userId = UserHandle.getUserId(uid);
final long identity = Binder.clearCallingIdentity();
try {
- ManagedServices manager =
- mAssistants.isComponentEnabledForCurrentProfiles(component)
- ? mAssistants
- : mListeners;
+ boolean isAssistantEnabled = managedServicesConcurrentMultiuser()
+ ? mAssistants.isComponentEnabledForUser(component, userId)
+ : mAssistants.isComponentEnabledForCurrentProfiles(component);
+ ManagedServices manager = isAssistantEnabled ? mAssistants : mListeners;
manager.setComponentState(component, UserHandle.getUserId(uid), true);
} finally {
Binder.restoreCallingIdentity(identity);
@@ -5762,16 +5758,16 @@ public class NotificationManagerService extends SystemService {
public void requestUnbindListenerComponent(ComponentName component) {
checkCallerIsSameApp(component.getPackageName());
int uid = Binder.getCallingUid();
+ int userId = UserHandle.getUserId(uid);
final long identity = Binder.clearCallingIdentity();
try {
synchronized (mNotificationLock) {
- ManagedServices manager =
- mAssistants.isComponentEnabledForCurrentProfiles(component)
- ? mAssistants
- : mListeners;
- if (manager.isPackageOrComponentAllowed(component.flattenToString(),
- UserHandle.getUserId(uid))) {
- manager.setComponentState(component, UserHandle.getUserId(uid), false);
+ boolean isAssistantEnabled = managedServicesConcurrentMultiuser()
+ ? mAssistants.isComponentEnabledForUser(component, userId)
+ : mAssistants.isComponentEnabledForCurrentProfiles(component);
+ ManagedServices manager = isAssistantEnabled ? mAssistants : mListeners;
+ if (manager.isPackageOrComponentAllowed(component.flattenToString(), userId)) {
+ manager.setComponentState(component, userId, false);
}
}
} finally {
@@ -6092,43 +6088,27 @@ public class NotificationManagerService extends SystemService {
@Override
public void requestInterruptionFilterFromListener(INotificationListener token,
int interruptionFilter) throws RemoteException {
- if (android.app.Flags.modesApi()) {
- final int callingUid = Binder.getCallingUid();
- ManagedServiceInfo info;
- synchronized (mNotificationLock) {
- info = mListeners.checkServiceTokenLocked(token);
- }
+ final int callingUid = Binder.getCallingUid();
+ ManagedServiceInfo info;
+ synchronized (mNotificationLock) {
+ info = mListeners.checkServiceTokenLocked(token);
+ }
- final int zenMode = zenModeFromInterruptionFilter(interruptionFilter, -1);
- if (zenMode == -1) return;
+ final int zenMode = zenModeFromInterruptionFilter(interruptionFilter, -1);
+ if (zenMode == -1) return;
- UserHandle zenUser = getCallingZenUser();
- if (!canManageGlobalZenPolicy(info.component.getPackageName(), callingUid)) {
- mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(
- zenUser, info.component.getPackageName(), callingUid, zenMode);
- } else {
- int origin = computeZenOrigin(/* fromUser= */ false);
- Binder.withCleanCallingIdentity(() -> {
- mZenModeHelper.setManualZenMode(zenUser, zenMode, /* conditionId= */ null,
- origin, "listener:" + info.component.flattenToShortString(),
- /* caller= */ info.component.getPackageName(),
- callingUid);
- });
- }
+ UserHandle zenUser = getCallingZenUser();
+ if (!canManageGlobalZenPolicy(info.component.getPackageName(), callingUid)) {
+ mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(
+ zenUser, info.component.getPackageName(), callingUid, zenMode);
} else {
- final int callingUid = Binder.getCallingUid();
- final boolean isSystemOrSystemUi = isCallerSystemOrSystemUi();
- final long identity = Binder.clearCallingIdentity();
- try {
- synchronized (mNotificationLock) {
- final ManagedServiceInfo info = mListeners.checkServiceTokenLocked(token);
- mZenModeHelper.requestFromListener(info.component, interruptionFilter,
- callingUid, isSystemOrSystemUi);
- updateInterruptionFilterLocked();
- }
- } finally {
- Binder.restoreCallingIdentity(identity);
- }
+ int origin = computeZenOrigin(/* fromUser= */ false);
+ Binder.withCleanCallingIdentity(() -> {
+ mZenModeHelper.setManualZenMode(zenUser, zenMode, /* conditionId= */ null,
+ origin, "listener:" + info.component.flattenToShortString(),
+ /* caller= */ info.component.getPackageName(),
+ callingUid);
+ });
}
}
@@ -6177,19 +6157,8 @@ public class NotificationManagerService extends SystemService {
}
}
- // TODO: b/310620812 - Remove getZenRules() when MODES_API is inlined.
- @Override
- public List<ZenModeConfig.ZenRule> getZenRules() throws RemoteException {
- int callingUid = Binder.getCallingUid();
- enforcePolicyAccess(callingUid, "getZenRules");
- return mZenModeHelper.getZenRules(getCallingZenUser(), callingUid);
- }
-
@Override
public Map<String, AutomaticZenRule> getAutomaticZenRules() {
- if (!android.app.Flags.modesApi()) {
- throw new IllegalStateException("getAutomaticZenRules called with flag off!");
- }
int callingUid = Binder.getCallingUid();
enforcePolicyAccess(callingUid, "getAutomaticZenRules");
return mZenModeHelper.getAutomaticZenRules(getCallingZenUser(), callingUid);
@@ -6260,50 +6229,40 @@ public class NotificationManagerService extends SystemService {
// Implicit rules have no ConditionProvider or Activity. We allow the user to customize
// them (via Settings), but not the owner app. Should the app want to start using it as
// a "normal" rule, it must provide a CP/ConfigActivity too.
- if (android.app.Flags.modesApi()) {
- boolean isImplicitRuleUpdateFromSystem = updateId != null
- && ZenModeConfig.isImplicitRuleId(updateId)
- && isCallerSystemOrSystemUi();
- if (!isImplicitRuleUpdateFromSystem
- && rule.getOwner() == null
- && rule.getConfigurationActivity() == null) {
- throw new NullPointerException(
- "Rule must have a ConditionProviderService and/or configuration "
- + "activity");
- }
- } else {
- if (rule.getOwner() == null && rule.getConfigurationActivity() == null) {
- throw new NullPointerException(
- "Rule must have a ConditionProviderService and/or configuration "
- + "activity");
- }
+ boolean isImplicitRuleUpdateFromSystem = updateId != null
+ && ZenModeConfig.isImplicitRuleId(updateId)
+ && isCallerSystemOrSystemUi();
+ if (!isImplicitRuleUpdateFromSystem
+ && rule.getOwner() == null
+ && rule.getConfigurationActivity() == null) {
+ throw new NullPointerException(
+ "Rule must have a ConditionProviderService and/or configuration "
+ + "activity");
}
Objects.requireNonNull(rule.getConditionId(), "ConditionId is null");
- if (android.app.Flags.modesApi()) {
- if (isCallerSystemOrSystemUi()) {
- return; // System callers can use any type.
- }
- int uid = Binder.getCallingUid();
- int userId = UserHandle.getUserId(uid);
+ if (isCallerSystemOrSystemUi()) {
+ return; // System callers can use any type.
+ }
+ int uid = Binder.getCallingUid();
+ int userId = UserHandle.getUserId(uid);
- if (rule.getType() == AutomaticZenRule.TYPE_MANAGED) {
- boolean isDeviceOwner = Binder.withCleanCallingIdentity(
- () -> mDpm.isActiveDeviceOwner(uid));
- if (!isDeviceOwner) {
- throw new IllegalArgumentException(
- "Only Device Owners can use AutomaticZenRules with TYPE_MANAGED");
- }
- } else if (rule.getType() == AutomaticZenRule.TYPE_BEDTIME) {
- String wellbeingPackage = getContext().getResources().getString(
- com.android.internal.R.string.config_systemWellbeing);
- boolean isCallerWellbeing = !TextUtils.isEmpty(wellbeingPackage)
- && mPackageManagerInternal.isSameApp(wellbeingPackage, uid, userId);
- if (!isCallerWellbeing) {
- throw new IllegalArgumentException(
- "Only the 'Wellbeing' package can use AutomaticZenRules with "
- + "TYPE_BEDTIME");
- }
+ if (rule.getType() == AutomaticZenRule.TYPE_MANAGED) {
+ boolean isDeviceOwner = Binder.withCleanCallingIdentity(
+ () -> mDpm.isActiveDeviceOwner(uid));
+ if (!isDeviceOwner) {
+ throw new IllegalArgumentException(
+ "Only Device Owners can use AutomaticZenRules with TYPE_MANAGED");
+ }
+ } else if (rule.getType() == AutomaticZenRule.TYPE_BEDTIME) {
+ String wellbeingPackage = getContext().getResources().getString(
+ com.android.internal.R.string.config_systemWellbeing);
+ boolean isCallerWellbeing = !TextUtils.isEmpty(wellbeingPackage)
+ && mPackageManagerInternal.isSameApp(wellbeingPackage, uid, userId);
+ if (!isCallerWellbeing) {
+ throw new IllegalArgumentException(
+ "Only the 'Wellbeing' package can use AutomaticZenRules with "
+ + "TYPE_BEDTIME");
}
}
}
@@ -6386,9 +6345,7 @@ public class NotificationManagerService extends SystemService {
@ZenModeConfig.ConfigOrigin
private int computeZenOrigin(boolean fromUser) {
- // "fromUser" is introduced with MODES_API, so only consider it in that case.
- // (Non-MODES_API behavior should also not depend at all on ORIGIN_USER_IN_X).
- if (android.app.Flags.modesApi() && fromUser) {
+ if (fromUser) {
if (isCallerSystemOrSystemUi()) {
return ZenModeConfig.ORIGIN_USER_IN_SYSTEMUI;
} else {
@@ -6402,9 +6359,7 @@ public class NotificationManagerService extends SystemService {
}
private void enforceUserOriginOnlyFromSystem(boolean fromUser, String method) {
- if (android.app.Flags.modesApi()
- && fromUser
- && !isCallerSystemOrSystemUiOrShell()) {
+ if (fromUser && !isCallerSystemOrSystemUiOrShell()) {
throw new SecurityException(TextUtils.formatSimple(
"Calling %s with fromUser == true is only allowed for system", method));
}
@@ -6419,7 +6374,7 @@ public class NotificationManagerService extends SystemService {
enforceUserOriginOnlyFromSystem(fromUser, "setInterruptionFilter");
UserHandle zenUser = getCallingZenUser();
- if (android.app.Flags.modesApi() && !canManageGlobalZenPolicy(pkg, callingUid)) {
+ if (!canManageGlobalZenPolicy(pkg, callingUid)) {
mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(zenUser, pkg, callingUid, zen);
return;
}
@@ -6549,6 +6504,13 @@ public class NotificationManagerService extends SystemService {
} catch (NameNotFoundException e) {
return false;
}
+ if (managedServicesConcurrentMultiuser()) {
+ return checkPackagePolicyAccess(pkg)
+ || mListeners.isComponentEnabledForPackage(pkg,
+ UserHandle.getCallingUserId())
+ || (mDpm != null
+ && (mDpm.isActiveProfileOwner(uid) || mDpm.isActiveDeviceOwner(uid)));
+ }
//TODO(b/169395065) Figure out if this flow makes sense in Device Owner mode.
return checkPackagePolicyAccess(pkg)
|| mListeners.isComponentEnabledForPackage(pkg)
@@ -6723,7 +6685,7 @@ public class NotificationManagerService extends SystemService {
public Policy getNotificationPolicy(String pkg) {
final int callingUid = Binder.getCallingUid();
UserHandle zenUser = getCallingZenUser();
- if (android.app.Flags.modesApi() && !canManageGlobalZenPolicy(pkg, callingUid)) {
+ if (!canManageGlobalZenPolicy(pkg, callingUid)) {
return mZenModeHelper.getNotificationPolicyFromImplicitZenRule(zenUser, pkg);
}
final long identity = Binder.clearCallingIdentity();
@@ -6760,8 +6722,7 @@ public class NotificationManagerService extends SystemService {
UserHandle zenUser = getCallingZenUser();
boolean isSystemCaller = isCallerSystemOrSystemUiOrShell();
- boolean shouldApplyAsImplicitRule = android.app.Flags.modesApi()
- && !canManageGlobalZenPolicy(pkg, callingUid);
+ boolean shouldApplyAsImplicitRule = !canManageGlobalZenPolicy(pkg, callingUid);
final long identity = Binder.clearCallingIdentity();
try {
@@ -6953,7 +6914,8 @@ public class NotificationManagerService extends SystemService {
android.Manifest.permission.INTERACT_ACROSS_USERS,
"setNotificationListenerAccessGrantedForUser for user " + userId);
}
- if (mUmInternal.isVisibleBackgroundFullUser(userId)) {
+ if (!managedServicesConcurrentMultiuser()
+ && mUmInternal.isVisibleBackgroundFullUser(userId)) {
// The main use case for visible background users is the Automotive multi-display
// configuration where a passenger can use a secondary display while the driver is
// using the main display. NotificationListeners is designed only for the current
@@ -8219,9 +8181,6 @@ public class NotificationManagerService extends SystemService {
@Override
public void setDeviceEffectsApplier(DeviceEffectsApplier applier) {
- if (!android.app.Flags.modesApi()) {
- return;
- }
if (mZenModeHelper == null) {
throw new IllegalStateException("ZenModeHelper is not yet ready!");
}
@@ -13165,7 +13124,8 @@ public class NotificationManagerService extends SystemService {
@Override
public void onUserUnlocked(int user) {
- if (mUmInternal.isVisibleBackgroundFullUser(user)) {
+ if (!managedServicesConcurrentMultiuser()
+ && mUmInternal.isVisibleBackgroundFullUser(user)) {
// The main use case for visible background users is the Automotive
// multi-display configuration where a passenger can use a secondary
// display while the driver is using the main display.
@@ -13805,7 +13765,7 @@ public class NotificationManagerService extends SystemService {
// TODO (b/73052211): if the ranking update changed the notification type,
// cancel notifications for NLSes that can't see them anymore
for (final ManagedServiceInfo serviceInfo : getServices()) {
- if (!serviceInfo.isEnabledForCurrentProfiles() || !isInteractionVisibleToListener(
+ if (!serviceInfo.isEnabledForUser() || !isInteractionVisibleToListener(
serviceInfo, ActivityManager.getCurrentUser())) {
continue;
}
@@ -13833,7 +13793,7 @@ public class NotificationManagerService extends SystemService {
@GuardedBy("mNotificationLock")
public void notifyListenerHintsChangedLocked(final int hints) {
for (final ManagedServiceInfo serviceInfo : getServices()) {
- if (!serviceInfo.isEnabledForCurrentProfiles() || !isInteractionVisibleToListener(
+ if (!serviceInfo.isEnabledForUser() || !isInteractionVisibleToListener(
serviceInfo, ActivityManager.getCurrentUser())) {
continue;
}
@@ -13889,7 +13849,7 @@ public class NotificationManagerService extends SystemService {
public void notifyInterruptionFilterChanged(final int interruptionFilter) {
for (final ManagedServiceInfo serviceInfo : getServices()) {
- if (!serviceInfo.isEnabledForCurrentProfiles() || !isInteractionVisibleToListener(
+ if (!serviceInfo.isEnabledForUser() || !isInteractionVisibleToListener(
serviceInfo, ActivityManager.getCurrentUser())) {
continue;
}
diff --git a/services/core/java/com/android/server/notification/NotificationRecordLogger.java b/services/core/java/com/android/server/notification/NotificationRecordLogger.java
index 6c0035b82a86..f680ce722e81 100644
--- a/services/core/java/com/android/server/notification/NotificationRecordLogger.java
+++ b/services/core/java/com/android/server/notification/NotificationRecordLogger.java
@@ -514,11 +514,14 @@ interface NotificationRecordLogger {
final int fsi_state;
final boolean is_locked;
final int age_in_minutes;
+ final boolean is_promoted_ongoing;
+ final boolean has_promotable_characteristics;
@DurationMillisLong long post_duration_millis; // Not final; calculated at the end.
NotificationReported(NotificationRecordPair p,
NotificationReportedEvent eventType, int position, int buzzBeepBlink,
InstanceId groupId) {
+ final Notification notification = p.r.getSbn().getNotification();
this.event_id = eventType.getId();
this.uid = p.r.getUid();
this.package_name = p.r.getSbn().getPackageName();
@@ -527,8 +530,8 @@ interface NotificationRecordLogger {
this.channel_id_hash = p.getChannelIdHash();
this.group_id_hash = p.getGroupIdHash();
this.group_instance_id = (groupId == null) ? 0 : groupId.getId();
- this.is_group_summary = p.r.getSbn().getNotification().isGroupSummary();
- this.category = p.r.getSbn().getNotification().category;
+ this.is_group_summary = notification.isGroupSummary();
+ this.category = notification.category;
this.style = p.getStyle();
this.num_people = p.getNumPeople();
this.position = position;
@@ -542,22 +545,18 @@ interface NotificationRecordLogger {
this.assistant_ranking_score = p.r.getRankingScore();
this.is_ongoing = p.r.getSbn().isOngoing();
this.is_foreground_service = NotificationRecordLogger.isForegroundService(p.r);
- this.timeout_millis = p.r.getSbn().getNotification().getTimeoutAfter();
+ this.timeout_millis = notification.getTimeoutAfter();
this.is_non_dismissible = NotificationRecordLogger.isNonDismissible(p.r);
-
- final boolean hasFullScreenIntent =
- p.r.getSbn().getNotification().fullScreenIntent != null;
-
- final boolean hasFsiRequestedButDeniedFlag = (p.r.getSbn().getNotification().flags
- & Notification.FLAG_FSI_REQUESTED_BUT_DENIED) != 0;
-
+ final boolean hasFullScreenIntent = notification.fullScreenIntent != null;
+ final boolean hasFsiRequestedButDeniedFlag =
+ (notification.flags & Notification.FLAG_FSI_REQUESTED_BUT_DENIED) != 0;
this.fsi_state = NotificationRecordLogger.getFsiState(
hasFullScreenIntent, hasFsiRequestedButDeniedFlag, eventType);
-
this.is_locked = p.r.isLocked();
-
this.age_in_minutes = NotificationRecordLogger.getAgeInMinutes(
- p.r.getSbn().getPostTime(), p.r.getSbn().getNotification().getWhen());
+ p.r.getSbn().getPostTime(), notification.getWhen());
+ this.is_promoted_ongoing = notification.isPromotedOngoing();
+ this.has_promotable_characteristics = notification.hasPromotableCharacteristics();
}
}
diff --git a/services/core/java/com/android/server/notification/NotificationRecordLoggerImpl.java b/services/core/java/com/android/server/notification/NotificationRecordLoggerImpl.java
index fc0a7764963e..e0e3fbaf49f1 100644
--- a/services/core/java/com/android/server/notification/NotificationRecordLoggerImpl.java
+++ b/services/core/java/com/android/server/notification/NotificationRecordLoggerImpl.java
@@ -78,7 +78,9 @@ class NotificationRecordLoggerImpl implements NotificationRecordLogger {
notificationReported.post_duration_millis,
notificationReported.fsi_state,
notificationReported.is_locked,
- notificationReported.age_in_minutes);
+ notificationReported.age_in_minutes,
+ notificationReported.is_promoted_ongoing,
+ notificationReported.has_promotable_characteristics);
}
@Override
diff --git a/services/core/java/com/android/server/notification/NotificationShellCmd.java b/services/core/java/com/android/server/notification/NotificationShellCmd.java
index c305d66c24c1..bc987ed21251 100644
--- a/services/core/java/com/android/server/notification/NotificationShellCmd.java
+++ b/services/core/java/com/android/server/notification/NotificationShellCmd.java
@@ -183,13 +183,8 @@ public class NotificationShellCmd extends ShellCommand {
interruptionFilter = INTERRUPTION_FILTER_ALL;
}
final int filter = interruptionFilter;
- if (android.app.Flags.modesApi()) {
- mBinderService.setInterruptionFilter(callingPackage, filter,
- /* fromUser= */ true);
- } else {
- mBinderService.setInterruptionFilter(callingPackage, filter,
- /* fromUser= */ false);
- }
+ mBinderService.setInterruptionFilter(callingPackage, filter,
+ /* fromUser= */ true);
}
break;
case "allow_dnd": {
diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
index 3974c839fd38..0fc182f3f1bb 100644
--- a/services/core/java/com/android/server/notification/PreferencesHelper.java
+++ b/services/core/java/com/android/server/notification/PreferencesHelper.java
@@ -233,11 +233,9 @@ public class PreferencesHelper implements RankingConfig {
private SparseBooleanArray mLockScreenShowNotifications;
private SparseBooleanArray mLockScreenPrivateNotifications;
private boolean mIsMediaNotificationFilteringEnabled;
- // When modes_api flag is enabled, this value only tracks whether the current user has any
- // channels marked as "priority channels", but not necessarily whether they are permitted
- // to bypass DND by current zen policy.
- // TODO: b/310620812 - Rename to be more accurate when modes_api flag is inlined.
- private boolean mCurrentUserHasChannelsBypassingDnd;
+ // Whether the current user has any channels marked as "priority channels" -- but not
+ // necessarily whether they are permitted to bypass DND by current zen policy.
+ private boolean mCurrentUserHasPriorityChannels;
private boolean mHideSilentStatusBarIcons = DEFAULT_HIDE_SILENT_STATUS_BAR_ICONS;
private final boolean mShowReviewPermissionsNotification;
@@ -1063,7 +1061,7 @@ public class PreferencesHelper implements RankingConfig {
r.groups.put(group.getId(), group);
}
if (needsDndChange) {
- updateCurrentUserHasChannelsBypassingDnd(callingUid, fromSystemOrSystemUi);
+ updateCurrentUserHasPriorityChannels(callingUid, fromSystemOrSystemUi);
}
if (android.app.Flags.nmBinderPerfCacheChannels() && changed) {
invalidateNotificationChannelGroupCache();
@@ -1150,7 +1148,7 @@ public class PreferencesHelper implements RankingConfig {
existing.setBypassDnd(bypassDnd);
needsPolicyFileChange = true;
- if (bypassDnd != mCurrentUserHasChannelsBypassingDnd
+ if (bypassDnd != mCurrentUserHasPriorityChannels
|| previousExistingImportance != existing.getImportance()) {
needsDndChange = true;
}
@@ -1214,7 +1212,7 @@ public class PreferencesHelper implements RankingConfig {
}
r.channels.put(channel.getId(), channel);
- if (channel.canBypassDnd() != mCurrentUserHasChannelsBypassingDnd) {
+ if (channel.canBypassDnd() != mCurrentUserHasPriorityChannels) {
needsDndChange = true;
}
MetricsLogger.action(getChannelLog(channel, pkg).setType(
@@ -1224,7 +1222,7 @@ public class PreferencesHelper implements RankingConfig {
}
if (needsDndChange) {
- updateCurrentUserHasChannelsBypassingDnd(callingUid, fromSystemOrSystemUi);
+ updateCurrentUserHasPriorityChannels(callingUid, fromSystemOrSystemUi);
}
if (android.app.Flags.nmBinderPerfCacheChannels() && needsPolicyFileChange) {
@@ -1317,14 +1315,14 @@ public class PreferencesHelper implements RankingConfig {
// relevantly affected without the parent channel already having been.
}
- if (updatedChannel.canBypassDnd() != mCurrentUserHasChannelsBypassingDnd
+ if (updatedChannel.canBypassDnd() != mCurrentUserHasPriorityChannels
|| channel.getImportance() != updatedChannel.getImportance()) {
needsDndChange = true;
changed = true;
}
}
if (needsDndChange) {
- updateCurrentUserHasChannelsBypassingDnd(callingUid, fromSystemOrSystemUi);
+ updateCurrentUserHasPriorityChannels(callingUid, fromSystemOrSystemUi);
}
if (changed) {
if (android.app.Flags.nmBinderPerfCacheChannels()) {
@@ -1550,7 +1548,7 @@ public class PreferencesHelper implements RankingConfig {
}
}
if (channelBypassedDnd) {
- updateCurrentUserHasChannelsBypassingDnd(callingUid, fromSystemOrSystemUi);
+ updateCurrentUserHasPriorityChannels(callingUid, fromSystemOrSystemUi);
}
if (android.app.Flags.nmBinderPerfCacheChannels() && deletedChannel) {
@@ -1745,7 +1743,7 @@ public class PreferencesHelper implements RankingConfig {
}
}
if (groupBypassedDnd) {
- updateCurrentUserHasChannelsBypassingDnd(callingUid, fromSystemOrSystemUi);
+ updateCurrentUserHasPriorityChannels(callingUid, fromSystemOrSystemUi);
}
if (android.app.Flags.nmBinderPerfCacheChannels()) {
if (deletedChannels.size() > 0) {
@@ -1906,8 +1904,8 @@ public class PreferencesHelper implements RankingConfig {
}
}
if (!deletedChannelIds.isEmpty()) {
- if (mCurrentUserHasChannelsBypassingDnd) {
- updateCurrentUserHasChannelsBypassingDnd(callingUid, fromSystemOrSystemUi);
+ if (mCurrentUserHasPriorityChannels) {
+ updateCurrentUserHasPriorityChannels(callingUid, fromSystemOrSystemUi);
}
if (android.app.Flags.nmBinderPerfCacheChannels()) {
invalidateNotificationChannelCache();
@@ -2098,7 +2096,7 @@ public class PreferencesHelper implements RankingConfig {
}
/**
- * Syncs {@link #mCurrentUserHasChannelsBypassingDnd} with the current user's notification
+ * Syncs {@link #mCurrentUserHasPriorityChannels} with the current user's notification
* policy before updating. Must be called:
* <ul>
* <li>On system init, after channels and DND configurations are loaded.
@@ -2106,22 +2104,23 @@ public class PreferencesHelper implements RankingConfig {
* <li>If users are removed (the removed user could've been a profile of the current one).
* </ul>
*/
- void syncChannelsBypassingDnd() {
- mCurrentUserHasChannelsBypassingDnd =
+ void syncHasPriorityChannels() {
+ mCurrentUserHasPriorityChannels =
(mZenModeHelper.getNotificationPolicy(UserHandle.CURRENT).state
- & NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND) != 0;
+ & NotificationManager.Policy.STATE_HAS_PRIORITY_CHANNELS) != 0;
- updateCurrentUserHasChannelsBypassingDnd(/* callingUid= */ Process.SYSTEM_UID,
+ updateCurrentUserHasPriorityChannels(/* callingUid= */ Process.SYSTEM_UID,
/* fromSystemOrSystemUi= */ true);
}
/**
* Updates the user's NotificationPolicy based on whether the current userId has channels
- * bypassing DND. It should be called whenever a channel is created, updated, or deleted, or
- * when the current user (or its profiles) change.
+ * marked as "priority" (which might bypass DND, depending on the zen rule details). It should
+ * be called whenever a channel is created, updated, or deleted, or when the current user (or
+ * its profiles) change.
*/
// TODO: b/368247671 - remove fromSystemOrSystemUi argument when modes_ui is inlined.
- private void updateCurrentUserHasChannelsBypassingDnd(int callingUid,
+ private void updateCurrentUserHasPriorityChannels(int callingUid,
boolean fromSystemOrSystemUi) {
ArraySet<Pair<String, Integer>> candidatePkgs = new ArraySet<>();
@@ -2149,13 +2148,13 @@ public class PreferencesHelper implements RankingConfig {
}
}
boolean haveBypassingApps = candidatePkgs.size() > 0;
- if (mCurrentUserHasChannelsBypassingDnd != haveBypassingApps) {
- mCurrentUserHasChannelsBypassingDnd = haveBypassingApps;
+ if (mCurrentUserHasPriorityChannels != haveBypassingApps) {
+ mCurrentUserHasPriorityChannels = haveBypassingApps;
if (android.app.Flags.modesUi()) {
mZenModeHelper.updateHasPriorityChannels(UserHandle.CURRENT,
- mCurrentUserHasChannelsBypassingDnd);
+ mCurrentUserHasPriorityChannels);
} else {
- updateZenPolicy(mCurrentUserHasChannelsBypassingDnd, callingUid,
+ updateZenPolicy(mCurrentUserHasPriorityChannels, callingUid,
fromSystemOrSystemUi);
}
}
@@ -2188,16 +2187,20 @@ public class PreferencesHelper implements RankingConfig {
policy.priorityCategories, policy.priorityCallSenders,
policy.priorityMessageSenders, policy.suppressedVisualEffects,
(areChannelsBypassingDnd
- ? NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND : 0),
+ ? NotificationManager.Policy.STATE_HAS_PRIORITY_CHANNELS : 0),
policy.priorityConversationSenders),
fromSystemOrSystemUi ? ZenModeConfig.ORIGIN_SYSTEM
: ZenModeConfig.ORIGIN_APP,
callingUid);
}
- // TODO: b/310620812 - rename to hasPriorityChannels() when modes_api is inlined.
- public boolean areChannelsBypassingDnd() {
- return mCurrentUserHasChannelsBypassingDnd;
+ /**
+ * Whether the current user has any channels marked as "priority channels"
+ * ({@link NotificationChannel#canBypassDnd}), but not necessarily whether they are permitted
+ * to bypass the filters set by the current zen policy.
+ */
+ public boolean hasPriorityChannels() {
+ return mCurrentUserHasPriorityChannels;
}
/**
diff --git a/services/core/java/com/android/server/notification/ZenModeEventLogger.java b/services/core/java/com/android/server/notification/ZenModeEventLogger.java
index fcc5e9771f94..ec9a2db52de9 100644
--- a/services/core/java/com/android/server/notification/ZenModeEventLogger.java
+++ b/services/core/java/com/android/server/notification/ZenModeEventLogger.java
@@ -16,7 +16,7 @@
package com.android.server.notification;
-import static android.app.NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND;
+import static android.app.NotificationManager.Policy.STATE_HAS_PRIORITY_CHANNELS;
import static android.provider.Settings.Global.ZEN_MODE_OFF;
import static android.service.notification.NotificationServiceProto.CHANNEL_POLICY_NONE;
import static android.service.notification.NotificationServiceProto.CHANNEL_POLICY_PRIORITY;
@@ -285,11 +285,10 @@ class ZenModeEventLogger {
return true;
}
- if (Flags.modesApi() && hasActiveRuleCountDiff()) {
- // Rules with INTERRUPTION_FILTER_ALL were always possible but before MODES_API
- // they were completely useless; now they can apply effects, so we want to log
- // when they become active/inactive, even though DND itself (as in "notification
- // blocking") is off.
+ if (hasActiveRuleCountDiff()) {
+ // Rules with INTERRUPTION_FILTER_ALL can apply effects, so we want to log when they
+ // become active/inactive, even though DND itself (as in "notification blocking")
+ // is off.
return true;
}
@@ -331,7 +330,7 @@ class ZenModeEventLogger {
}
}
- if (Flags.modesApi() && mNewZenMode == ZEN_MODE_OFF) {
+ if (mNewZenMode == ZEN_MODE_OFF) {
// If the mode is OFF -> OFF then there cannot be any *effective* change to policy.
// (Note that, in theory, a policy diff is impossible since we don't merge the
// policies of INTERRUPTION_FILTER_ALL rules; this is a "just in case" check).
@@ -439,24 +438,14 @@ class ZenModeEventLogger {
// Determine the number of (automatic & manual) rules active after the change takes place.
int getNumRulesActive() {
- if (!Flags.modesApi()) {
- // If the zen mode has turned off, that means nothing can be active.
- if (mNewZenMode == ZEN_MODE_OFF) {
- return 0;
- }
- }
return numActiveRulesInConfig(mNewConfig);
}
/**
- * Return a list of the types of each of the active rules in the configuration.
- * Only available when {@code MODES_API} is active; otherwise returns an empty list.
+ * Return a list of the types of each of the active rules in the configuration (sorted by
+ * the numerical value of the type, and including duplicates).
*/
int[] getActiveRuleTypes() {
- if (!Flags.modesApi()) {
- return new int[0];
- }
-
ArrayList<Integer> activeTypes = new ArrayList<>();
List<ZenRule> activeRules = activeRulesList(mNewConfig);
if (activeRules.size() == 0) {
@@ -476,77 +465,10 @@ class ZenModeEventLogger {
return out;
}
- /**
- * Return our best guess as to whether the changes observed are due to a user action.
- * Note that this (before {@code MODES_API}) won't be 100% accurate as we can't necessarily
- * distinguish between a system uid call indicating "user interacted with Settings" vs "a
- * system app changed something automatically".
- */
+ /** Return whether the changes observed are due to a user action. */
boolean getIsUserAction() {
- if (Flags.modesApi()) {
- return mOrigin == ZenModeConfig.ORIGIN_USER_IN_SYSTEMUI
- || mOrigin == ZenModeConfig.ORIGIN_USER_IN_APP;
- }
-
- // Approach for pre-MODES_API:
- // - if manual rule turned on or off, the calling UID is system, and the new manual
- // rule does not have an enabler set, guess that this is likely to be a user action.
- // This may represent a system app turning on DND automatically, but we guess "user"
- // in this case.
- // - note that this has a known failure mode of "manual rule turning off
- // automatically after the default time runs out". We currently have no way
- // of distinguishing this case from a user manually turning off the rule.
- // - the reason for checking the enabler field is that a call may look like it's
- // coming from a system UID, but if an enabler is set then the request came
- // from an external source. "enabler" will be blank when manual rule is turned
- // on from Quick Settings or Settings.
- // - if an automatic rule's state changes in whether it is "enabled", then
- // that is probably a user action.
- // - if an automatic rule goes from "not snoozing" to "snoozing", that is probably
- // a user action; that means that the user temporarily turned off DND associated
- // with that rule.
- // - if an automatic rule becomes active but does *not* change in its enabled state
- // (covered by a previous case anyway), we guess that this is an automatic change.
- // - if a rule is added or removed and the call comes from the system, we guess that
- // this is a user action (as system rules can't be added or removed without a user
- // action).
- switch (getChangedRuleType()) {
- case RULE_TYPE_MANUAL:
- // TODO(b/278888961): Distinguish the automatically-turned-off state
- return isFromSystemOrSystemUi() && (getNewManualRuleEnabler() == null);
- case RULE_TYPE_AUTOMATIC:
- for (ZenModeDiff.RuleDiff d : getChangedAutomaticRules().values()) {
- if (d.wasAdded() || d.wasRemoved()) {
- // If the change comes from system, a rule being added/removed indicates
- // a likely user action. From an app, it's harder to know for sure.
- return isFromSystemOrSystemUi();
- }
- ZenModeDiff.FieldDiff enabled = d.getDiffForField(
- ZenModeDiff.RuleDiff.FIELD_ENABLED);
- if (enabled != null && enabled.hasDiff()) {
- return true;
- }
- ZenModeDiff.FieldDiff snoozing = d.getDiffForField(
- ZenModeDiff.RuleDiff.FIELD_SNOOZING);
- if (snoozing != null && snoozing.hasDiff() && (boolean) snoozing.to()) {
- return true;
- }
- }
- // If the change was in an automatic rule and none of the "probably triggered
- // by a user" cases apply, then it's probably an automatic change.
- return false;
- case RULE_TYPE_UNKNOWN:
- default:
- }
-
- // If the change wasn't in a rule, but was in the zen policy: consider to be user action
- // if the calling uid is system
- if (hasPolicyDiff() || hasChannelsBypassingDiff()) {
- return mCallingUid == Process.SYSTEM_UID;
- }
-
- // don't know, or none of the other things triggered; assume not a user action
- return false;
+ return mOrigin == ZenModeConfig.ORIGIN_USER_IN_SYSTEMUI
+ || mOrigin == ZenModeConfig.ORIGIN_USER_IN_APP;
}
boolean isFromSystemOrSystemUi() {
@@ -587,7 +509,7 @@ class ZenModeEventLogger {
*/
@Nullable
byte[] getDNDPolicyProto() {
- if (Flags.modesApi() && mNewZenMode == ZEN_MODE_OFF) {
+ if (mNewZenMode == ZEN_MODE_OFF) {
return null;
}
@@ -628,13 +550,10 @@ class ZenModeEventLogger {
mNewPolicy.allowMessagesFrom()));
proto.write(DNDPolicyProto.ALLOW_CONVERSATIONS_FROM,
mNewPolicy.allowConversationsFrom());
-
- if (Flags.modesApi()) {
- proto.write(DNDPolicyProto.ALLOW_CHANNELS,
- mNewPolicy.allowPriorityChannels()
- ? CHANNEL_POLICY_PRIORITY
- : CHANNEL_POLICY_NONE);
- }
+ proto.write(DNDPolicyProto.ALLOW_CHANNELS,
+ mNewPolicy.allowPriorityChannels()
+ ? CHANNEL_POLICY_PRIORITY
+ : CHANNEL_POLICY_NONE);
} else {
Log.wtf(TAG, "attempted to write zen mode log event with null policy");
}
@@ -648,14 +567,14 @@ class ZenModeEventLogger {
*/
boolean getAreChannelsBypassing() {
if (mNewPolicy != null) {
- return (mNewPolicy.state & STATE_CHANNELS_BYPASSING_DND) != 0;
+ return (mNewPolicy.state & STATE_HAS_PRIORITY_CHANNELS) != 0;
}
return false;
}
private boolean hasChannelsBypassingDiff() {
boolean prevChannelsBypassing = mPrevPolicy != null
- ? (mPrevPolicy.state & STATE_CHANNELS_BYPASSING_DND) != 0 : false;
+ ? (mPrevPolicy.state & STATE_HAS_PRIORITY_CHANNELS) != 0 : false;
return prevChannelsBypassing != getAreChannelsBypassing();
}
diff --git a/services/core/java/com/android/server/notification/ZenModeFiltering.java b/services/core/java/com/android/server/notification/ZenModeFiltering.java
index bdca555707e3..87ae78195ff5 100644
--- a/services/core/java/com/android/server/notification/ZenModeFiltering.java
+++ b/services/core/java/com/android/server/notification/ZenModeFiltering.java
@@ -19,7 +19,6 @@ package com.android.server.notification;
import static android.provider.Settings.Global.ZEN_MODE_OFF;
import static android.service.notification.ZenPolicy.CONVERSATION_SENDERS_ANYONE;
-import android.app.Flags;
import android.app.Notification;
import android.app.NotificationManager;
import android.content.ComponentName;
@@ -146,16 +145,12 @@ public class ZenModeFiltering {
// Returns whether the record is permitted to bypass DND when the zen mode is
// ZEN_MODE_IMPORTANT_INTERRUPTIONS. This depends on whether the record's package priority is
- // marked as PRIORITY_MAX (an indication of it belonging to a priority channel), and, if
- // the modes_api flag is on, whether the given policy permits priority channels to bypass.
- // TODO: b/310620812 - simplify when modes_api is inlined.
+ // marked as PRIORITY_MAX (an indication of it belonging to a priority channel), and whether the
+ // given policy permits priority channels to bypass.
private boolean canRecordBypassDnd(NotificationRecord record,
NotificationManager.Policy policy) {
boolean inPriorityChannel = record.getPackagePriority() == Notification.PRIORITY_MAX;
- if (Flags.modesApi()) {
- return inPriorityChannel && policy.allowPriorityChannels();
- }
- return inPriorityChannel;
+ return inPriorityChannel && policy.allowPriorityChannels();
}
/**
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index b39b6fde6258..889df512dd60 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -157,6 +157,12 @@ public class ZenModeHelper {
static final int RULE_LIMIT_PER_PACKAGE = 100;
private static final Duration DELETED_RULE_KEPT_FOR = Duration.ofDays(30);
+ /**
+ * Amount of time since last activation after which implicit rules that have never been
+ * customized by the user are automatically cleaned up.
+ */
+ private static final Duration IMPLICIT_RULE_KEPT_FOR = Duration.ofDays(30);
+
private static final int MAX_ICON_RESOURCE_NAME_LENGTH = 1000;
/**
@@ -326,9 +332,6 @@ public class ZenModeHelper {
* applied immediately.
*/
void setDeviceEffectsApplier(@NonNull DeviceEffectsApplier deviceEffectsApplier) {
- if (!Flags.modesApi()) {
- return;
- }
synchronized (mConfigLock) {
if (mDeviceEffectsApplier != null) {
throw new IllegalStateException("Already set up a DeviceEffectsApplier!");
@@ -350,11 +353,6 @@ public class ZenModeHelper {
}
}
- // TODO: b/310620812 - Remove when MODES_API is inlined (no more callers).
- public void onUserUnlocked(int user) {
- loadConfigForUser(user, "onUserUnlocked");
- }
-
void setPriorityOnlyDndExemptPackages(String[] packages) {
mPriorityOnlyDndExemptPackages = packages;
}
@@ -385,21 +383,6 @@ public class ZenModeHelper {
return NotificationManager.zenModeToInterruptionFilter(mZenMode);
}
- // TODO: b/310620812 - Remove when MODES_API is inlined (no more callers).
- public void requestFromListener(ComponentName name, int filter, int callingUid,
- boolean fromSystemOrSystemUi) {
- final int newZen = NotificationManager.zenModeFromInterruptionFilter(filter, -1);
- if (newZen != -1) {
- // This change is known to be for UserHandle.CURRENT because NLSes for
- // background users are unbound.
- setManualZenMode(UserHandle.CURRENT, newZen, null,
- fromSystemOrSystemUi ? ORIGIN_SYSTEM : ORIGIN_APP,
- /* reason= */ "listener:" + (name != null ? name.flattenToShortString() : null),
- /* caller= */ name != null ? name.getPackageName() : null,
- callingUid);
- }
- }
-
public void setSuppressedEffects(long suppressedEffects) {
if (mSuppressedEffects == suppressedEffects) return;
mSuppressedEffects = suppressedEffects;
@@ -414,33 +397,24 @@ public class ZenModeHelper {
return mZenMode;
}
- // TODO: b/310620812 - Make private (or inline) when MODES_API is inlined.
- public List<ZenRule> getZenRules(UserHandle user, int callingUid) {
- List<ZenRule> rules = new ArrayList<>();
+ /**
+ * Get the list of {@link AutomaticZenRule} instances that the calling package can manage
+ * (which means the owned rules for a regular app, and every rule for system callers) together
+ * with their ids.
+ */
+ Map<String, AutomaticZenRule> getAutomaticZenRules(UserHandle user, int callingUid) {
+ HashMap<String, AutomaticZenRule> rules = new HashMap<>();
synchronized (mConfigLock) {
ZenModeConfig config = getConfigLocked(user);
if (config == null) return rules;
+
for (ZenRule rule : config.automaticRules.values()) {
if (canManageAutomaticZenRule(rule, callingUid)) {
- rules.add(rule);
+ rules.put(rule.id, zenRuleToAutomaticZenRule(rule));
}
}
+ return rules;
}
- return rules;
- }
-
- /**
- * Get the list of {@link AutomaticZenRule} instances that the calling package can manage
- * (which means the owned rules for a regular app, and every rule for system callers) together
- * with their ids.
- */
- Map<String, AutomaticZenRule> getAutomaticZenRules(UserHandle user, int callingUid) {
- List<ZenRule> ruleList = getZenRules(user, callingUid);
- HashMap<String, AutomaticZenRule> rules = new HashMap<>(ruleList.size());
- for (ZenRule rule : ruleList) {
- rules.put(rule.id, zenRuleToAutomaticZenRule(rule));
- }
- return rules;
}
public AutomaticZenRule getAutomaticZenRule(UserHandle user, String id, int callingUid) {
@@ -511,9 +485,6 @@ public class ZenModeHelper {
@GuardedBy("mConfigLock")
private ZenRule maybeRestoreRemovedRule(ZenModeConfig config, String pkg, ZenRule ruleToAdd,
AutomaticZenRule azrToAdd, @ConfigOrigin int origin) {
- if (!Flags.modesApi()) {
- return ruleToAdd;
- }
String deletedKey = ZenModeConfig.deletedRuleKey(ruleToAdd);
if (deletedKey == null) {
// Couldn't calculate the deletedRuleKey (condition or pkg null?). This should
@@ -561,9 +532,6 @@ public class ZenModeHelper {
*/
private static void maybeReplaceDefaultRule(ZenModeConfig config, @Nullable ZenRule oldRule,
AutomaticZenRule rule) {
- if (!Flags.modesApi()) {
- return;
- }
if (rule.getType() == AutomaticZenRule.TYPE_BEDTIME
&& (oldRule == null || oldRule.type != rule.getType())) {
// Note: we must not verify canManageAutomaticZenRule here, since most likely they
@@ -572,7 +540,7 @@ public class ZenModeHelper {
ZenModeConfig.EVERY_NIGHT_DEFAULT_RULE_ID);
if (sleepingRule != null
&& !sleepingRule.enabled
- && sleepingRule.canBeUpdatedByApp() /* meaning it's not user-customized */) {
+ && !sleepingRule.isUserModified()) {
config.automaticRules.remove(ZenModeConfig.EVERY_NIGHT_DEFAULT_RULE_ID);
}
}
@@ -599,18 +567,10 @@ public class ZenModeHelper {
}
ZenModeConfig newConfig = config.copy();
ZenModeConfig.ZenRule newRule = requireNonNull(newConfig.automaticRules.get(ruleId));
- if (!Flags.modesApi()) {
- if (newRule.enabled != automaticZenRule.isEnabled()) {
- dispatchOnAutomaticRuleStatusChanged(config.user, newRule.getPkg(), ruleId,
- automaticZenRule.isEnabled()
- ? AUTOMATIC_RULE_STATUS_ENABLED
- : AUTOMATIC_RULE_STATUS_DISABLED);
- }
- }
boolean updated = populateZenRule(newRule.pkg, automaticZenRule, newConfig, newRule,
origin, /* isNew= */ false);
- if (Flags.modesApi() && !updated) {
+ if (!updated) {
// Bail out so we don't have the side effects of updating a rule (i.e. dropping
// condition) when no changes happen.
return true;
@@ -643,10 +603,6 @@ public class ZenModeHelper {
*/
void applyGlobalZenModeAsImplicitZenRule(UserHandle user, String callingPkg, int callingUid,
int zenMode) {
- if (!android.app.Flags.modesApi()) {
- Log.wtf(TAG, "applyGlobalZenModeAsImplicitZenRule called with flag off!");
- return;
- }
synchronized (mConfigLock) {
ZenModeConfig config = getConfigLocked(user);
if (config == null) {
@@ -712,10 +668,6 @@ public class ZenModeHelper {
*/
void applyGlobalPolicyAsImplicitZenRule(UserHandle user, String callingPkg, int callingUid,
NotificationManager.Policy policy) {
- if (!android.app.Flags.modesApi()) {
- Log.wtf(TAG, "applyGlobalPolicyAsImplicitZenRule called with flag off!");
- return;
- }
synchronized (mConfigLock) {
ZenModeConfig config = getConfigLocked(user);
if (config == null) {
@@ -772,10 +724,6 @@ public class ZenModeHelper {
*/
@Nullable
Policy getNotificationPolicyFromImplicitZenRule(UserHandle user, String callingPkg) {
- if (!android.app.Flags.modesApi()) {
- Log.wtf(TAG, "getNotificationPolicyFromImplicitZenRule called with flag off!");
- return getNotificationPolicy(user);
- }
synchronized (mConfigLock) {
ZenModeConfig config = getConfigLocked(user);
if (config == null) {
@@ -814,7 +762,6 @@ public class ZenModeHelper {
.appendPath(pkg)
.build();
rule.enabled = true;
- rule.modified = false;
rule.component = null;
rule.configurationActivity = null;
return rule;
@@ -918,15 +865,12 @@ public class ZenModeHelper {
private void maybePreserveRemovedRule(ZenModeConfig config, ZenRule ruleToRemove,
@ConfigOrigin int origin) {
- if (!Flags.modesApi()) {
- return;
- }
// If an app deletes a previously customized rule, keep it around to preserve
// the user's customization when/if it's recreated later.
// We don't try to preserve system-owned rules because their conditionIds (used as
// deletedRuleKey) are not stable. This is almost moot anyway because an app cannot
// delete a system-owned rule.
- if (origin == ORIGIN_APP && !ruleToRemove.canBeUpdatedByApp()
+ if (origin == ORIGIN_APP && ruleToRemove.isUserModified()
&& !PACKAGE_ANDROID.equals(ruleToRemove.pkg)) {
String deletedKey = ZenModeConfig.deletedRuleKey(ruleToRemove);
if (deletedKey != null) {
@@ -952,7 +896,7 @@ public class ZenModeHelper {
if (rule == null || !canManageAutomaticZenRule(rule, callingUid)) {
return Condition.STATE_UNKNOWN;
}
- if (Flags.modesApi() && Flags.modesUi()) {
+ if (Flags.modesUi()) {
return rule.isActive() ? STATE_TRUE : STATE_FALSE;
} else {
// Buggy, does not consider snoozing!
@@ -971,16 +915,9 @@ public class ZenModeHelper {
newConfig = config.copy();
ZenRule rule = newConfig.automaticRules.get(id);
- if (Flags.modesApi()) {
- if (rule != null && canManageAutomaticZenRule(rule, callingUid)) {
- setAutomaticZenRuleStateLocked(newConfig, Collections.singletonList(rule),
- condition, origin, "setAzrState: " + rule.id, callingUid);
- }
- } else {
- ArrayList<ZenRule> rules = new ArrayList<>();
- rules.add(rule); // rule may be null and throw NPE in the next method.
- setAutomaticZenRuleStateLocked(newConfig, rules, condition, origin,
- "setAzrState: " + (rule != null ? rule.id : "null!"), callingUid);
+ if (rule != null && canManageAutomaticZenRule(rule, callingUid)) {
+ setAutomaticZenRuleStateLocked(newConfig, Collections.singletonList(rule),
+ condition, origin, "setAzrState: " + rule.id, callingUid);
}
}
}
@@ -995,13 +932,12 @@ public class ZenModeHelper {
newConfig = config.copy();
List<ZenRule> matchingRules = findMatchingRules(newConfig, ruleConditionId, condition);
- if (Flags.modesApi()) {
- for (int i = matchingRules.size() - 1; i >= 0; i--) {
- if (!canManageAutomaticZenRule(matchingRules.get(i), callingUid)) {
- matchingRules.remove(i);
- }
+ for (int i = matchingRules.size() - 1; i >= 0; i--) {
+ if (!canManageAutomaticZenRule(matchingRules.get(i), callingUid)) {
+ matchingRules.remove(i);
}
}
+
setAutomaticZenRuleStateLocked(newConfig, matchingRules, condition, origin,
"setAzrStateFromCps: " + ruleConditionId, callingUid);
}
@@ -1013,7 +949,7 @@ public class ZenModeHelper {
if (rules == null || rules.isEmpty()) return;
if (!Flags.modesUi()) {
- if (Flags.modesApi() && condition.source == SOURCE_USER_ACTION) {
+ if (condition.source == SOURCE_USER_ACTION) {
origin = ORIGIN_USER_IN_APP; // Although coming from app, it's actually from user.
}
}
@@ -1026,7 +962,7 @@ public class ZenModeHelper {
private static void applyConditionAndReconsiderOverride(ZenRule rule, Condition condition,
int origin) {
- if (Flags.modesApi() && Flags.modesUi()) {
+ if (Flags.modesUi()) {
if (isImplicitRuleId(rule.id)) {
// Implicit rules do not use overrides, and always apply conditions directly.
// This is compatible with the previous behavior (where the package set the
@@ -1173,8 +1109,7 @@ public class ZenModeHelper {
// if default rule wasn't user-modified use localized name
// instead of previous system name
if (currRule != null
- && !currRule.modified
- && (currRule.zenPolicyUserModifiedFields & AutomaticZenRule.FIELD_NAME) == 0
+ && (currRule.userModifiedFields & AutomaticZenRule.FIELD_NAME) == 0
&& !defaultRule.name.equals(currRule.name)) {
if (DEBUG) {
Slog.d(TAG, "Locale change - updating default zen rule name "
@@ -1184,7 +1119,7 @@ public class ZenModeHelper {
updated = true;
}
}
- if (Flags.modesApi() && Flags.modesUi()) {
+ if (Flags.modesUi()) {
for (ZenRule rule : newConfig.automaticRules.values()) {
if (SystemZenRules.isSystemOwnedRule(rule)) {
updated |= SystemZenRules.updateTriggerDescription(mContext, rule);
@@ -1256,172 +1191,145 @@ public class ZenModeHelper {
@GuardedBy("mConfigLock")
private boolean populateZenRule(String pkg, AutomaticZenRule azr, ZenModeConfig config,
ZenRule rule, @ConfigOrigin int origin, boolean isNew) {
- if (Flags.modesApi()) {
- boolean modified = false;
- // These values can always be edited by the app, so we apply changes immediately.
- if (isNew) {
- rule.id = ZenModeConfig.newRuleId();
- rule.creationTime = mClock.millis();
- rule.component = azr.getOwner();
- rule.pkg = pkg;
- modified = true;
- }
- // Allow updating the CPS backing system rules (e.g. for custom manual -> schedule)
- if (Flags.modesUi()
- && (origin == ORIGIN_SYSTEM || origin == ORIGIN_USER_IN_SYSTEMUI)
- && Objects.equals(rule.pkg, SystemZenRules.PACKAGE_ANDROID)
- && !Objects.equals(rule.component, azr.getOwner())) {
- rule.component = azr.getOwner();
- modified = true;
- }
+ boolean modified = false;
+ // These values can always be edited by the app, so we apply changes immediately.
+ if (isNew) {
+ rule.id = ZenModeConfig.newRuleId();
+ rule.creationTime = mClock.millis();
+ rule.component = azr.getOwner();
+ rule.pkg = pkg;
+ modified = true;
+ }
- if (Flags.modesUi()) {
- if (!azr.isEnabled() && (isNew || rule.enabled)) {
- // Creating a rule as disabled, or disabling a previously enabled rule.
- // Record whodunit.
- rule.disabledOrigin = origin;
- } else if (azr.isEnabled()) {
- // Enabling or previously enabled. Clear disabler.
- rule.disabledOrigin = ORIGIN_UNKNOWN;
- }
- }
+ // Allow updating the CPS backing system rules (e.g. for custom manual -> schedule)
+ if (Flags.modesUi()
+ && (origin == ORIGIN_SYSTEM || origin == ORIGIN_USER_IN_SYSTEMUI)
+ && Objects.equals(rule.pkg, SystemZenRules.PACKAGE_ANDROID)
+ && !Objects.equals(rule.component, azr.getOwner())) {
+ rule.component = azr.getOwner();
+ modified = true;
+ }
- if (!Objects.equals(rule.conditionId, azr.getConditionId())) {
- rule.conditionId = azr.getConditionId();
- modified = true;
- }
- // This can be removed when {@link Flags#modesUi} is fully ramped up
- final boolean isWatch =
- mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH);
- boolean shouldPreserveCondition =
- Flags.modesApi()
- && (Flags.modesUi() || isWatch)
- && !isNew
- && origin == ORIGIN_USER_IN_SYSTEMUI
- && rule.enabled == azr.isEnabled()
- && rule.conditionId != null
- && rule.condition != null
- && rule.conditionId.equals(rule.condition.id);
- if (!shouldPreserveCondition) {
- // Do not update 'modified'. If only this changes we treat it as a no-op updateAZR.
- rule.condition = null;
- }
-
- if (rule.enabled != azr.isEnabled()) {
- rule.enabled = azr.isEnabled();
- rule.resetConditionOverride();
- modified = true;
- }
- if (!Objects.equals(rule.configurationActivity, azr.getConfigurationActivity())) {
- rule.configurationActivity = azr.getConfigurationActivity();
- modified = true;
- }
- if (rule.allowManualInvocation != azr.isManualInvocationAllowed()) {
- rule.allowManualInvocation = azr.isManualInvocationAllowed();
- modified = true;
- }
- if (!Flags.modesUi()) {
- String iconResName = drawableResIdToResName(rule.pkg, azr.getIconResId());
- if (!Objects.equals(rule.iconResName, iconResName)) {
- rule.iconResName = iconResName;
- modified = true;
- }
- }
- if (!Objects.equals(rule.triggerDescription, azr.getTriggerDescription())) {
- rule.triggerDescription = azr.getTriggerDescription();
- modified = true;
+ if (Flags.modesUi()) {
+ if (!azr.isEnabled() && (isNew || rule.enabled)) {
+ // Creating a rule as disabled, or disabling a previously enabled rule.
+ // Record whodunit.
+ rule.disabledOrigin = origin;
+ } else if (azr.isEnabled()) {
+ // Enabling or previously enabled. Clear disabler.
+ rule.disabledOrigin = ORIGIN_UNKNOWN;
}
- if (rule.type != azr.getType()) {
- rule.type = azr.getType();
+ }
+
+ if (!Objects.equals(rule.conditionId, azr.getConditionId())) {
+ rule.conditionId = azr.getConditionId();
+ modified = true;
+ }
+ // This can be removed when {@link Flags#modesUi} is fully ramped up
+ final boolean isWatch =
+ mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH);
+ boolean shouldPreserveCondition =
+ (Flags.modesUi() || isWatch)
+ && !isNew
+ && origin == ORIGIN_USER_IN_SYSTEMUI
+ && rule.enabled == azr.isEnabled()
+ && rule.conditionId != null
+ && rule.condition != null
+ && rule.conditionId.equals(rule.condition.id);
+ if (!shouldPreserveCondition) {
+ // Do not update 'modified'. If only this changes we treat it as a no-op updateAZR.
+ rule.condition = null;
+ }
+
+ if (rule.enabled != azr.isEnabled()) {
+ rule.enabled = azr.isEnabled();
+ rule.resetConditionOverride();
+ modified = true;
+ }
+ if (!Objects.equals(rule.configurationActivity, azr.getConfigurationActivity())) {
+ rule.configurationActivity = azr.getConfigurationActivity();
+ modified = true;
+ }
+ if (rule.allowManualInvocation != azr.isManualInvocationAllowed()) {
+ rule.allowManualInvocation = azr.isManualInvocationAllowed();
+ modified = true;
+ }
+ if (!Flags.modesUi()) {
+ String iconResName = drawableResIdToResName(rule.pkg, azr.getIconResId());
+ if (!Objects.equals(rule.iconResName, iconResName)) {
+ rule.iconResName = iconResName;
modified = true;
}
- // TODO: b/310620812 - Remove this once FLAG_MODES_API is inlined.
- rule.modified = azr.isModified();
+ }
+ if (!Objects.equals(rule.triggerDescription, azr.getTriggerDescription())) {
+ rule.triggerDescription = azr.getTriggerDescription();
+ modified = true;
+ }
+ if (rule.type != azr.getType()) {
+ rule.type = azr.getType();
+ modified = true;
+ }
- // Name is treated differently than other values:
- // App is allowed to update name if the name was not modified by the user (even if
- // other values have been modified). In this way, if the locale of an app changes,
- // i18n of the rule name can still occur even if the user has customized the rule
- // contents.
- String previousName = rule.name;
- if (isNew || doesOriginAlwaysUpdateValues(origin)
- || (rule.userModifiedFields & AutomaticZenRule.FIELD_NAME) == 0) {
- rule.name = azr.getName();
- modified |= !Objects.equals(rule.name, previousName);
- }
+ // Name is treated differently than other values:
+ // App is allowed to update name if the name was not modified by the user (even if
+ // other values have been modified). In this way, if the locale of an app changes,
+ // i18n of the rule name can still occur even if the user has customized the rule
+ // contents.
+ String previousName = rule.name;
+ if (isNew || doesOriginAlwaysUpdateValues(origin)
+ || (rule.userModifiedFields & AutomaticZenRule.FIELD_NAME) == 0) {
+ rule.name = azr.getName();
+ modified |= !Objects.equals(rule.name, previousName);
+ }
- // For the remaining values, rules can always have all values updated if:
- // * the rule is newly added, or
- // * the request comes from an origin that can always update values, like the user, or
- // * the rule has not yet been user modified, and thus can be updated by the app.
- boolean updateValues = isNew || doesOriginAlwaysUpdateValues(origin)
- || rule.canBeUpdatedByApp();
+ // For the remaining values, rules can always have all values updated if:
+ // * the rule is newly added, or
+ // * the request comes from an origin that can always update values, like the user, or
+ // * the rule has not yet been user modified, and thus can be updated by the app.
+ boolean updateValues = isNew || doesOriginAlwaysUpdateValues(origin)
+ || !rule.isUserModified();
- // For all other values, if updates are not allowed, we discard the update.
- if (!updateValues) {
- return modified;
- }
+ // For all other values, if updates are not allowed, we discard the update.
+ if (!updateValues) {
+ return modified;
+ }
- // Updates the bitmasks if the origin of the change is the user.
- boolean updateBitmask = (origin == ORIGIN_USER_IN_SYSTEMUI);
+ // Updates the bitmasks if the origin of the change is the user.
+ boolean updateBitmask = (origin == ORIGIN_USER_IN_SYSTEMUI);
- if (updateBitmask && !TextUtils.equals(previousName, azr.getName())) {
- rule.userModifiedFields |= AutomaticZenRule.FIELD_NAME;
+ if (updateBitmask && !TextUtils.equals(previousName, azr.getName())) {
+ rule.userModifiedFields |= AutomaticZenRule.FIELD_NAME;
+ }
+ int newZenMode = NotificationManager.zenModeFromInterruptionFilter(
+ azr.getInterruptionFilter(), Global.ZEN_MODE_OFF);
+ if (rule.zenMode != newZenMode) {
+ rule.zenMode = newZenMode;
+ if (updateBitmask) {
+ rule.userModifiedFields |= AutomaticZenRule.FIELD_INTERRUPTION_FILTER;
}
- int newZenMode = NotificationManager.zenModeFromInterruptionFilter(
- azr.getInterruptionFilter(), Global.ZEN_MODE_OFF);
- if (rule.zenMode != newZenMode) {
- rule.zenMode = newZenMode;
+ modified = true;
+ }
+
+ if (Flags.modesUi()) {
+ String iconResName = drawableResIdToResName(rule.pkg, azr.getIconResId());
+ if (!Objects.equals(rule.iconResName, iconResName)) {
+ rule.iconResName = iconResName;
if (updateBitmask) {
- rule.userModifiedFields |= AutomaticZenRule.FIELD_INTERRUPTION_FILTER;
+ rule.userModifiedFields |= AutomaticZenRule.FIELD_ICON;
}
modified = true;
}
+ }
- if (Flags.modesUi()) {
- String iconResName = drawableResIdToResName(rule.pkg, azr.getIconResId());
- if (!Objects.equals(rule.iconResName, iconResName)) {
- rule.iconResName = iconResName;
- if (updateBitmask) {
- rule.userModifiedFields |= AutomaticZenRule.FIELD_ICON;
- }
- modified = true;
- }
- }
-
- // Updates the bitmask and values for all policy fields, based on the origin.
- modified |= updatePolicy(config, rule, azr.getZenPolicy(), updateBitmask, isNew);
+ // Updates the bitmask and values for all policy fields, based on the origin.
+ modified |= updatePolicy(config, rule, azr.getZenPolicy(), updateBitmask, isNew);
- // Updates the bitmask and values for all device effect fields, based on the origin.
- modified |= updateZenDeviceEffects(rule, azr.getDeviceEffects(),
- origin == ORIGIN_APP, updateBitmask);
+ // Updates the bitmask and values for all device effect fields, based on the origin.
+ modified |= updateZenDeviceEffects(rule, azr.getDeviceEffects(),
+ origin == ORIGIN_APP, updateBitmask);
- return modified;
- } else {
- if (rule.enabled != azr.isEnabled()) {
- rule.resetConditionOverride();
- }
- rule.name = azr.getName();
- rule.condition = null;
- rule.conditionId = azr.getConditionId();
- rule.enabled = azr.isEnabled();
- rule.modified = azr.isModified();
- rule.zenPolicy = azr.getZenPolicy();
- rule.zenMode = NotificationManager.zenModeFromInterruptionFilter(
- azr.getInterruptionFilter(), Global.ZEN_MODE_OFF);
- rule.configurationActivity = azr.getConfigurationActivity();
-
- if (isNew) {
- rule.id = ZenModeConfig.newRuleId();
- rule.creationTime = System.currentTimeMillis();
- rule.component = azr.getOwner();
- rule.pkg = pkg;
- }
-
- // Only the MODES_API path cares about the result, so just return whatever here.
- return true;
- }
+ return modified;
}
/**
@@ -1629,32 +1537,21 @@ public class ZenModeHelper {
}
private AutomaticZenRule zenRuleToAutomaticZenRule(ZenRule rule) {
- AutomaticZenRule azr;
- if (Flags.modesApi()) {
- azr = new AutomaticZenRule.Builder(rule.name, rule.conditionId)
- .setManualInvocationAllowed(rule.allowManualInvocation)
- .setPackage(rule.pkg)
- .setCreationTime(rule.creationTime)
- .setIconResId(drawableResNameToResId(rule.pkg, rule.iconResName))
- .setType(rule.type)
- .setZenPolicy(rule.zenPolicy)
- .setDeviceEffects(rule.zenDeviceEffects)
- .setEnabled(rule.enabled)
- .setInterruptionFilter(
- NotificationManager.zenModeToInterruptionFilter(rule.zenMode))
- .setOwner(rule.component)
- .setConfigurationActivity(rule.configurationActivity)
- .setTriggerDescription(rule.triggerDescription)
- .build();
- } else {
- azr = new AutomaticZenRule(rule.name, rule.component,
- rule.configurationActivity,
- rule.conditionId, rule.zenPolicy,
- NotificationManager.zenModeToInterruptionFilter(rule.zenMode),
- rule.enabled, rule.creationTime);
- azr.setPackageName(rule.pkg);
- }
- return azr;
+ return new AutomaticZenRule.Builder(rule.name, rule.conditionId)
+ .setManualInvocationAllowed(rule.allowManualInvocation)
+ .setPackage(rule.pkg)
+ .setCreationTime(rule.creationTime)
+ .setIconResId(drawableResNameToResId(rule.pkg, rule.iconResName))
+ .setType(rule.type)
+ .setZenPolicy(rule.zenPolicy)
+ .setDeviceEffects(rule.zenDeviceEffects)
+ .setEnabled(rule.enabled)
+ .setInterruptionFilter(
+ NotificationManager.zenModeToInterruptionFilter(rule.zenMode))
+ .setOwner(rule.component)
+ .setConfigurationActivity(rule.configurationActivity)
+ .setTriggerDescription(rule.triggerDescription)
+ .build();
}
// Update only the hasPriorityChannels state (aka areChannelsBypassingDnd) without modifying
@@ -1669,12 +1566,12 @@ public class ZenModeHelper {
if (config == null) return;
// If it already matches, do nothing
- if (config.areChannelsBypassingDnd == hasPriorityChannels) {
+ if (config.hasPriorityChannels == hasPriorityChannels) {
return;
}
ZenModeConfig newConfig = config.copy();
- newConfig.areChannelsBypassingDnd = hasPriorityChannels;
+ newConfig.hasPriorityChannels = hasPriorityChannels;
// The updated calculation of whether there are priority channels is always done by
// the system, even if the event causing the calculation had a different origin.
setConfigLocked(newConfig, null, ORIGIN_SYSTEM, "updateHasPriorityChannels",
@@ -1754,9 +1651,7 @@ public class ZenModeHelper {
newRule.zenMode = zenMode;
newRule.conditionId = conditionId;
newRule.enabler = caller;
- if (Flags.modesApi()) {
- newRule.allowManualInvocation = true;
- }
+ newRule.allowManualInvocation = true;
newConfig.manualRule = newRule;
}
}
@@ -1849,7 +1744,7 @@ public class ZenModeHelper {
boolean hasDefaultRules = config.automaticRules.containsAll(
ZenModeConfig.getDefaultRuleIds());
- long time = Flags.modesApi() ? mClock.millis() : System.currentTimeMillis();
+ long time = mClock.millis();
if (config.automaticRules != null && config.automaticRules.size() > 0) {
for (ZenRule automaticRule : config.automaticRules.values()) {
if (forRestore) {
@@ -1863,7 +1758,7 @@ public class ZenModeHelper {
// Upon upgrading to a version with modes_api enabled, keep all behaviors of
// rules with null ZenPolicies explicitly as a copy of the global policy.
- if (Flags.modesApi() && config.version < ZenModeConfig.XML_VERSION_MODES_API) {
+ if (config.version < ZenModeConfig.XML_VERSION_MODES_API) {
// Keep the manual ("global") policy that from config.
ZenPolicy manualRulePolicy = config.getZenPolicy();
if (automaticRule.zenPolicy == null) {
@@ -1877,8 +1772,7 @@ public class ZenModeHelper {
}
}
- if (Flags.modesApi() && Flags.modesUi()
- && config.version < ZenModeConfig.XML_VERSION_MODES_UI) {
+ if (Flags.modesUi() && config.version < ZenModeConfig.XML_VERSION_MODES_UI) {
// Clear icons from implicit rules. App icons are not suitable for some
// surfaces, so juse use a default (the user can select a different one).
if (ZenModeConfig.isImplicitRuleId(automaticRule.id)) {
@@ -1904,11 +1798,11 @@ public class ZenModeHelper {
reason += ", reset to default rules";
}
- if (Flags.modesApi() && Flags.modesUi()) {
+ if (Flags.modesUi()) {
SystemZenRules.maybeUpgradeRules(mContext, config);
}
- if (Flags.modesApi() && forRestore) {
+ if (forRestore) {
// Note: forBackup doesn't write deletedRules, but just in case.
config.deletedRules.clear();
}
@@ -1995,7 +1889,7 @@ public class ZenModeHelper {
if (config == null) return;
final ZenModeConfig newConfig = config.copy();
- if (Flags.modesApi() && !Flags.modesUi()) {
+ if (!Flags.modesUi()) {
// Fix for b/337193321 -- propagate changes to notificationPolicy to rules where
// the user cannot edit zen policy to emulate the previous "inheritance".
ZenPolicy previousPolicy = ZenAdapters.notificationPolicyToZenPolicy(
@@ -2026,6 +1920,7 @@ public class ZenModeHelper {
* <ul>
* <li>Rule instances whose owner is not installed.
* <li>Deleted rules that were deleted more than 30 days ago.
+ * <li>Implicit rules that haven't been used in 30 days (and have not been customized).
* </ul>
*/
private void cleanUpZenRules() {
@@ -2034,17 +1929,20 @@ public class ZenModeHelper {
final ZenModeConfig newConfig = mConfig.copy();
deleteRulesWithoutOwner(newConfig.automaticRules);
- if (Flags.modesApi()) {
- deleteRulesWithoutOwner(newConfig.deletedRules);
- for (int i = newConfig.deletedRules.size() - 1; i >= 0; i--) {
- ZenRule deletedRule = newConfig.deletedRules.valueAt(i);
- if (deletedRule.deletionInstant == null
- || deletedRule.deletionInstant.isBefore(keptRuleThreshold)) {
- newConfig.deletedRules.removeAt(i);
- }
+ deleteRulesWithoutOwner(newConfig.deletedRules);
+
+ for (int i = newConfig.deletedRules.size() - 1; i >= 0; i--) {
+ ZenRule deletedRule = newConfig.deletedRules.valueAt(i);
+ if (deletedRule.deletionInstant == null
+ || deletedRule.deletionInstant.isBefore(keptRuleThreshold)) {
+ newConfig.deletedRules.removeAt(i);
}
}
+ if (Flags.modesUi() && Flags.modesCleanupImplicit()) {
+ deleteUnusedImplicitRules(newConfig.automaticRules);
+ }
+
if (!newConfig.equals(mConfig)) {
setConfigLocked(newConfig, null, ORIGIN_SYSTEM,
"cleanUpZenRules", Process.SYSTEM_UID);
@@ -2053,7 +1951,7 @@ public class ZenModeHelper {
}
private void deleteRulesWithoutOwner(ArrayMap<String, ZenRule> ruleList) {
- long currentTime = Flags.modesApi() ? mClock.millis() : System.currentTimeMillis();
+ long currentTime = mClock.millis();
if (ruleList != null) {
for (int i = ruleList.size() - 1; i >= 0; i--) {
ZenRule rule = ruleList.valueAt(i);
@@ -2070,6 +1968,29 @@ public class ZenModeHelper {
}
}
+ private void deleteUnusedImplicitRules(ArrayMap<String, ZenRule> ruleList) {
+ if (ruleList == null) {
+ return;
+ }
+ Instant deleteIfUnusedSince = mClock.instant().minus(IMPLICIT_RULE_KEPT_FOR);
+
+ for (int i = ruleList.size() - 1; i >= 0; i--) {
+ ZenRule rule = ruleList.valueAt(i);
+ if (isImplicitRuleId(rule.id) && !rule.isUserModified()) {
+ if (rule.lastActivation == null) {
+ // This rule existed before we started tracking activation time. It *might* be
+ // in use. Set lastActivation=now so it has some time (IMPLICIT_RULE_KEPT_FOR)
+ // before being removed if truly unused.
+ rule.lastActivation = mClock.instant();
+ }
+
+ if (rule.lastActivation.isBefore(deleteIfUnusedSince)) {
+ ruleList.removeAt(i);
+ }
+ }
+ }
+ }
+
/**
* @return a copy of the zen mode configuration
*/
@@ -2188,7 +2109,7 @@ public class ZenModeHelper {
mZenMode, mConfig, mConsolidatedPolicy);
if (!config.equals(mConfig)) {
// Schedule broadcasts. Cannot be sent during boot, though.
- if (Flags.modesApi() && origin != ORIGIN_INIT) {
+ if (origin != ORIGIN_INIT) {
for (ZenRule rule : config.automaticRules.values()) {
ZenRule original = mConfig.automaticRules.get(rule.id);
if (original != null) {
@@ -2204,6 +2125,20 @@ public class ZenModeHelper {
}
}
+ // Update last activation for rules that are being activated.
+ if (Flags.modesUi() && Flags.modesCleanupImplicit()) {
+ Instant now = mClock.instant();
+ if (!mConfig.isManualActive() && config.isManualActive()) {
+ config.manualRule.lastActivation = now;
+ }
+ for (ZenRule rule : config.automaticRules.values()) {
+ ZenRule previousRule = mConfig.automaticRules.get(rule.id);
+ if (rule.isActive() && (previousRule == null || !previousRule.isActive())) {
+ rule.lastActivation = now;
+ }
+ }
+ }
+
mConfig = config;
dispatchOnConfigChanged();
updateAndApplyConsolidatedPolicyAndDeviceEffects(origin, reason);
@@ -2295,7 +2230,7 @@ public class ZenModeHelper {
private void applyCustomPolicy(ZenModeConfig config, ZenPolicy policy, ZenRule rule,
boolean useManualConfig) {
if (rule.zenMode == Global.ZEN_MODE_NO_INTERRUPTIONS) {
- if (Flags.modesApi() && Flags.modesUi()) {
+ if (Flags.modesUi()) {
policy.apply(ZenPolicy.getBasePolicyInterruptionFilterNone());
} else {
policy.apply(new ZenPolicy.Builder()
@@ -2304,7 +2239,7 @@ public class ZenModeHelper {
.build());
}
} else if (rule.zenMode == Global.ZEN_MODE_ALARMS) {
- if (Flags.modesApi() && Flags.modesUi()) {
+ if (Flags.modesUi()) {
policy.apply(ZenPolicy.getBasePolicyInterruptionFilterAlarms());
} else {
policy.apply(new ZenPolicy.Builder()
@@ -2317,22 +2252,17 @@ public class ZenModeHelper {
} else if (rule.zenPolicy != null) {
policy.apply(rule.zenPolicy);
} else {
- if (Flags.modesApi()) {
- if (useManualConfig) {
- // manual rule is configured using the settings stored directly in ZenModeConfig
- policy.apply(config.getZenPolicy());
- } else {
- // under modes_api flag, an active automatic rule with no specified policy
- // inherits the device default settings as stored in mDefaultConfig. While the
- // rule's policy fields should be set upon creation, this is a fallback to
- // catch any that may have fallen through the cracks.
- Log.wtf(TAG, "active automatic rule found with no specified policy: " + rule);
- policy.apply(Flags.modesUi()
- ? mDefaultConfig.getZenPolicy() : config.getZenPolicy());
- }
- } else {
- // active rule with no specified policy inherits the manual rule config settings
+ if (useManualConfig) {
+ // manual rule is configured using the settings stored directly in ZenModeConfig
policy.apply(config.getZenPolicy());
+ } else {
+ // An active automatic rule with no specified policy inherits the device default
+ // settings as stored in mDefaultConfig. While the rule's policy fields should be
+ // set upon creation, this is a fallback to catch any that may have fallen through
+ // the cracks.
+ Log.wtf(TAG, "active automatic rule found with no specified policy: " + rule);
+ policy.apply(Flags.modesUi()
+ ? mDefaultConfig.getZenPolicy() : config.getZenPolicy());
}
}
}
@@ -2346,9 +2276,7 @@ public class ZenModeHelper {
ZenDeviceEffects.Builder deviceEffectsBuilder = new ZenDeviceEffects.Builder();
if (mConfig.isManualActive()) {
applyCustomPolicy(mConfig, policy, mConfig.manualRule, true);
- if (Flags.modesApi()) {
- deviceEffectsBuilder.add(mConfig.manualRule.zenDeviceEffects);
- }
+ deviceEffectsBuilder.add(mConfig.manualRule.zenDeviceEffects);
}
for (ZenRule automaticRule : mConfig.automaticRules.values()) {
@@ -2356,12 +2284,10 @@ public class ZenModeHelper {
// Active rules with INTERRUPTION_FILTER_ALL are not included in consolidated
// policy. This is relevant in case some other active rule has a more
// restrictive INTERRUPTION_FILTER but a more lenient ZenPolicy!
- if (!Flags.modesApi() || automaticRule.zenMode != Global.ZEN_MODE_OFF) {
+ if (automaticRule.zenMode != Global.ZEN_MODE_OFF) {
applyCustomPolicy(mConfig, policy, automaticRule, false);
}
- if (Flags.modesApi()) {
- deviceEffectsBuilder.add(automaticRule.zenDeviceEffects);
- }
+ deviceEffectsBuilder.add(automaticRule.zenDeviceEffects);
}
}
@@ -2380,40 +2306,35 @@ public class ZenModeHelper {
ZenLog.traceSetConsolidatedZenPolicy(mConsolidatedPolicy, reason);
}
- if (Flags.modesApi()) {
- // Prevent other rules from applying grayscale if Driving is active (but allow it
- // if _Driving itself_ wants grayscale).
- if (Flags.modesUi() && preventZenDeviceEffectsWhileDriving()) {
- boolean hasActiveDriving = false;
- boolean hasActiveDrivingWithGrayscale = false;
- for (ZenRule rule : mConfig.automaticRules.values()) {
- if (rule.isActive() && rule.type == TYPE_DRIVING) {
- hasActiveDriving = true;
- if (rule.zenDeviceEffects != null
- && rule.zenDeviceEffects.shouldDisplayGrayscale()) {
- hasActiveDrivingWithGrayscale = true;
- break; // Further rules won't affect decision.
- }
+ // Prevent other rules from applying grayscale if Driving is active (but allow it
+ // if _Driving itself_ wants grayscale).
+ if (Flags.modesUi() && preventZenDeviceEffectsWhileDriving()) {
+ boolean hasActiveDriving = false;
+ boolean hasActiveDrivingWithGrayscale = false;
+ for (ZenRule rule : mConfig.automaticRules.values()) {
+ if (rule.isActive() && rule.type == TYPE_DRIVING) {
+ hasActiveDriving = true;
+ if (rule.zenDeviceEffects != null
+ && rule.zenDeviceEffects.shouldDisplayGrayscale()) {
+ hasActiveDrivingWithGrayscale = true;
+ break; // Further rules won't affect decision.
}
}
- if (hasActiveDriving && !hasActiveDrivingWithGrayscale) {
- deviceEffectsBuilder.setShouldDisplayGrayscale(false);
- }
}
-
- ZenDeviceEffects deviceEffects = deviceEffectsBuilder.build();
- if (!deviceEffects.equals(mConsolidatedDeviceEffects)) {
- mConsolidatedDeviceEffects = deviceEffects;
- mHandler.postApplyDeviceEffects(origin);
+ if (hasActiveDriving && !hasActiveDrivingWithGrayscale) {
+ deviceEffectsBuilder.setShouldDisplayGrayscale(false);
}
}
+
+ ZenDeviceEffects deviceEffects = deviceEffectsBuilder.build();
+ if (!deviceEffects.equals(mConsolidatedDeviceEffects)) {
+ mConsolidatedDeviceEffects = deviceEffects;
+ mHandler.postApplyDeviceEffects(origin);
+ }
}
}
private void applyConsolidatedDeviceEffects(@ConfigOrigin int source) {
- if (!Flags.modesApi()) {
- return;
- }
DeviceEffectsApplier applier;
ZenDeviceEffects effects;
synchronized (mConfigLock) {
@@ -2434,10 +2355,8 @@ public class ZenModeHelper {
* to the current locale.
*/
private static void updateDefaultConfig(Context context, ZenModeConfig defaultConfig) {
- if (Flags.modesApi()) {
- updateDefaultAutomaticRulePolicies(defaultConfig);
- }
- if (Flags.modesApi() && Flags.modesUi()) {
+ updateDefaultAutomaticRulePolicies(defaultConfig);
+ if (Flags.modesUi()) {
SystemZenRules.maybeUpgradeRules(context, defaultConfig);
}
updateRuleStringsForCurrentLocale(context, defaultConfig);
@@ -2453,7 +2372,7 @@ public class ZenModeHelper {
rule.name = context.getResources()
.getString(R.string.zen_mode_default_every_night_name);
}
- if (Flags.modesApi() && Flags.modesUi()) {
+ if (Flags.modesUi()) {
SystemZenRules.updateTriggerDescription(context, rule);
}
}
@@ -2462,10 +2381,6 @@ public class ZenModeHelper {
// Updates the policies in the default automatic rules (provided via default XML config) to
// be fully filled in default values.
private static void updateDefaultAutomaticRulePolicies(ZenModeConfig defaultConfig) {
- if (!Flags.modesApi()) {
- // Should be checked before calling, but just in case.
- return;
- }
ZenPolicy defaultPolicy = defaultConfig.getZenPolicy();
for (ZenRule rule : defaultConfig.automaticRules.values()) {
if (ZenModeConfig.getDefaultRuleIds().contains(rule.id) && rule.zenPolicy == null) {
@@ -2611,6 +2526,7 @@ public class ZenModeHelper {
}
}
+ // TODO: b/368247671 - Delete this method AND default_zen_mode_config.xml when inlining modes_ui
private ZenModeConfig readDefaultConfig(Resources resources) {
XmlResourceParser parser = null;
try {
@@ -2649,7 +2565,7 @@ public class ZenModeHelper {
events.add(FrameworkStatsLog.buildStatsEvent(DND_MODE_RULE,
/* optional int32 user = 1 */ user,
/* optional bool enabled = 2 */ config.isManualActive(),
- /* optional bool channels_bypassing = 3 */ config.areChannelsBypassingDnd,
+ /* optional bool channels_bypassing = 3 */ config.hasPriorityChannels,
/* optional LoggedZenMode zen_mode = 4 */ ROOT_CONFIG,
/* optional string id = 5 */ "", // empty for root config
/* optional int32 uid = 6 */ Process.SYSTEM_UID, // system owns root config
@@ -2924,9 +2840,6 @@ public class ZenModeHelper {
* ({@link #addAutomaticZenRule}, {@link #removeAutomaticZenRule}, etc, makes sense.
*/
private static void checkManageRuleOrigin(String method, @ConfigOrigin int origin) {
- if (!Flags.modesApi()) {
- return;
- }
checkArgument(origin == ORIGIN_APP || origin == ORIGIN_SYSTEM
|| origin == ORIGIN_USER_IN_SYSTEMUI,
"Expected one of ORIGIN_APP, ORIGIN_SYSTEM, or "
@@ -2939,9 +2852,6 @@ public class ZenModeHelper {
* {@link #setAutomaticZenRuleStateFromConditionProvider} makes sense.
*/
private static void checkSetRuleStateOrigin(String method, @ConfigOrigin int origin) {
- if (!Flags.modesApi()) {
- return;
- }
checkArgument(origin == ORIGIN_APP || origin == ORIGIN_USER_IN_APP
|| origin == ORIGIN_SYSTEM || origin == ORIGIN_USER_IN_SYSTEMUI,
"Expected one of ORIGIN_APP, ORIGIN_USER_IN_APP, ORIGIN_SYSTEM, or "
diff --git a/services/core/java/com/android/server/notification/flags.aconfig b/services/core/java/com/android/server/notification/flags.aconfig
index 048f2b6b0cbc..76cd5c88b388 100644
--- a/services/core/java/com/android/server/notification/flags.aconfig
+++ b/services/core/java/com/android/server/notification/flags.aconfig
@@ -210,3 +210,10 @@ flag {
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ name: "managed_services_concurrent_multiuser"
+ namespace: "systemui"
+ description: "Enables ManagedServices to support Concurrent multi user environment"
+ bug: "380297485"
+}
diff --git a/services/core/java/com/android/server/pm/BackgroundInstallControlService.java b/services/core/java/com/android/server/pm/BackgroundInstallControlService.java
index d538bb876b64..c3af578de369 100644
--- a/services/core/java/com/android/server/pm/BackgroundInstallControlService.java
+++ b/services/core/java/com/android/server/pm/BackgroundInstallControlService.java
@@ -176,16 +176,13 @@ public class BackgroundInstallControlService extends SystemService {
if (Flags.bicClient()) {
mService.enforceCallerPermissions();
}
- if (!Build.IS_DEBUGGABLE) {
- return mService.getBackgroundInstalledPackages(flags, userId);
- }
// The debug.transparency.bg-install-apps (only works for debuggable builds)
// is used to set mock list of background installed apps for testing.
// The list of apps' names is delimited by ",".
// TODO: Remove after migrating test to new background install method using
// {@link BackgroundInstallControlCallbackHelperTest}.installPackage b/310983905
String propertyString = SystemProperties.get("debug.transparency.bg-install-apps");
- if (TextUtils.isEmpty(propertyString)) {
+ if (TextUtils.isEmpty(propertyString) || !Build.IS_DEBUGGABLE) {
return mService.getBackgroundInstalledPackages(flags, userId);
} else {
return mService.getMockBackgroundInstalledPackages(propertyString);
@@ -219,10 +216,27 @@ public class BackgroundInstallControlService extends SystemService {
PackageManager.PackageInfoFlags.of(flags), userId);
initBackgroundInstalledPackages();
+ if(Build.IS_DEBUGGABLE) {
+ StringBuilder sb = new StringBuilder();
+ sb.append("Tracked background installed package size: ")
+ .append(mBackgroundInstalledPackages.size())
+ .append("\n");
+ for (int i = 0; i < mBackgroundInstalledPackages.size(); ++i) {
+ int installingUserId = mBackgroundInstalledPackages.keyAt(i);
+ mBackgroundInstalledPackages.get(installingUserId).forEach(pkgName ->
+ sb.append("userId: ").append(installingUserId)
+ .append(", name: ").append(pkgName).append("\n"));
+ }
+ Slog.d(TAG, "Tracked background installed package: " + sb.toString());
+ }
+
ListIterator<PackageInfo> iter = packages.listIterator();
while (iter.hasNext()) {
String packageName = iter.next().packageName;
if (!mBackgroundInstalledPackages.contains(userId, packageName)) {
+ if(Build.IS_DEBUGGABLE) {
+ Slog.d(TAG, packageName + " is not tracked, removing");
+ }
iter.remove();
}
}
@@ -284,6 +298,9 @@ public class BackgroundInstallControlService extends SystemService {
}
void handlePackageAdd(String packageName, int userId) {
+ if(Build.IS_DEBUGGABLE) {
+ Slog.d(TAG, "handlePackageAdd: checking " + packageName);
+ }
ApplicationInfo appInfo = null;
try {
appInfo =
@@ -302,7 +319,7 @@ public class BackgroundInstallControlService extends SystemService {
installerPackageName = installInfo.getInstallingPackageName();
initiatingPackageName = installInfo.getInitiatingPackageName();
} catch (PackageManager.NameNotFoundException e) {
- Slog.w(TAG, "Package's installer not found " + packageName);
+ Slog.w(TAG, "Package's installer not found: " + packageName);
return;
}
@@ -314,6 +331,10 @@ public class BackgroundInstallControlService extends SystemService {
VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT,
userId)
!= PERMISSION_GRANTED) {
+ if(Build.IS_DEBUGGABLE) {
+ Slog.d(TAG, "handlePackageAdd " + packageName + ": installer doesn't "
+ + "have INSTALL_PACKAGES permission, skipping");
+ }
return;
}
@@ -324,6 +345,10 @@ public class BackgroundInstallControlService extends SystemService {
if (installedByAdb(initiatingPackageName)
|| wasForegroundInstallation(installerPackageName, userId, installTimestamp)) {
+ if(Build.IS_DEBUGGABLE) {
+ Slog.d(TAG, "handlePackageAdd " + packageName + ": is installed by ADB or was "
+ + "foreground installation, skipping");
+ }
return;
}
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 15688c0f7366..28117470e7a3 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -204,6 +204,7 @@ import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
@@ -1023,6 +1024,7 @@ final class InstallPackageHelper {
*/
void installPackagesTraced(List<InstallRequest> requests, MoveInfo moveInfo) {
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "installPackages");
+ boolean pendingForDexopt = false;
boolean success = false;
final Map<String, Boolean> createdAppId = new ArrayMap<>(requests.size());
final Map<String, Settings.VersionInfo> versionInfos = new ArrayMap<>(requests.size());
@@ -1036,17 +1038,41 @@ final class InstallPackageHelper {
if (reconciledPackages == null) {
return;
}
+
if (renameAndUpdatePaths(requests)) {
// rename before dexopt because art will encoded the path in the odex/vdex file
if (Flags.improveInstallFreeze()) {
- prepPerformDexoptIfNeeded(reconciledPackages);
- }
- if (commitInstallPackages(reconciledPackages)) {
- success = true;
+ pendingForDexopt = true;
+ final Runnable actionsAfterDexopt = () ->
+ doPostDexopt(reconciledPackages, requests,
+ createdAppId, moveInfo, acquireTime);
+ prepPerformDexoptIfNeeded(reconciledPackages, actionsAfterDexopt);
+ } else {
+ if (commitInstallPackages(reconciledPackages)) {
+ success = true;
+ }
}
}
}
} finally {
+ if (!pendingForDexopt) {
+ completeInstallProcess(requests, createdAppId, success);
+ Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
+ doPostInstall(requests, moveInfo);
+ releaseWakeLock(acquireTime, requests.size());
+ }
+ }
+ }
+
+ void doPostDexopt(List<ReconciledPackage> reconciledPackages,
+ List<InstallRequest> requests, Map<String, Boolean> createdAppId,
+ MoveInfo moveInfo, long acquireTime) {
+ boolean success = false;
+ try {
+ if (commitInstallPackages(reconciledPackages)) {
+ success = true;
+ }
+ } finally {
completeInstallProcess(requests, createdAppId, success);
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
doPostInstall(requests, moveInfo);
@@ -1123,7 +1149,7 @@ final class InstallPackageHelper {
throws PackageManagerException {
final int userId = installRequest.getUserId();
if (userId != UserHandle.USER_ALL && userId != UserHandle.USER_CURRENT
- && !mPm.mUserManager.exists(userId)) {
+ && !ArrayUtils.contains(allUsers, userId)) {
throw new PackageManagerException(PackageManagerException.INTERNAL_ERROR_MISSING_USER,
"User " + userId + " doesn't exist or has been removed");
}
@@ -1155,7 +1181,9 @@ final class InstallPackageHelper {
}
}
- private void prepPerformDexoptIfNeeded(List<ReconciledPackage> reconciledPackages) {
+ private void prepPerformDexoptIfNeeded(List<ReconciledPackage> reconciledPackages,
+ Runnable actionsAfterDexopt) {
+ List<CompletableFuture<Void>> completableFutures = new ArrayList<>();
for (ReconciledPackage reconciledPkg : reconciledPackages) {
final InstallRequest request = reconciledPkg.mInstallRequest;
// prepare profiles
@@ -1171,6 +1199,7 @@ final class InstallPackageHelper {
mSharedLibraries.executeSharedLibrariesUpdate(request.getParsedPackage(), ps,
null, null, reconciledPkg.mCollectedSharedLibraryInfos, allUsers);
}
+
try (PackageManagerTracedLock installLock = mPm.mInstallLock.acquireLock()) {
final int[] newUsers = getNewUsers(request, allUsers);
// Hardcode previousAppId to 0 to disable any data migration (http://b/221088088)
@@ -1182,11 +1211,22 @@ final class InstallPackageHelper {
}
} catch (PackageManagerException e) {
request.setError(e.error, e.getMessage());
- return;
+ break;
}
request.setKeepArtProfile(true);
- // TODO(b/388159696): Use performDexoptIfNeededAsync.
- DexOptHelper.performDexoptIfNeeded(request, mDexManager, null /* installLock */);
+
+ CompletableFuture<Void> future =
+ DexOptHelper.performDexoptIfNeededAsync(request, mDexManager);
+ completableFutures.add(future);
+ }
+
+ if (!completableFutures.isEmpty()) {
+ CompletableFuture<Void> allFutures =
+ CompletableFuture.allOf(
+ completableFutures.toArray(CompletableFuture[]::new));
+ var unused = allFutures.thenRun(() -> mPm.mHandler.post(actionsAfterDexopt));
+ } else {
+ actionsAfterDexopt.run();
}
}
@@ -2759,6 +2799,7 @@ final class InstallPackageHelper {
| Installer.FLAG_CLEAR_CODE_CACHE_ONLY);
}
+ // run synchronous dexopt if the freeze improvement is not supported
DexOptHelper.performDexoptIfNeeded(
installRequest, mDexManager, mPm.mInstallLock.getRawLock());
}
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index e1fcc6650650..2d0bb258e89f 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -805,22 +805,20 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements
}
}
- if (Flags.recoverabilityDetection()) {
- if (params.rollbackImpactLevel == PackageManager.ROLLBACK_USER_IMPACT_HIGH
- || params.rollbackImpactLevel
- == PackageManager.ROLLBACK_USER_IMPACT_ONLY_MANUAL) {
- if ((params.installFlags & PackageManager.INSTALL_ENABLE_ROLLBACK) == 0) {
- throw new IllegalArgumentException(
- "Can't set rollbackImpactLevel when rollback is not enabled");
- }
- if (mContext.checkCallingOrSelfPermission(Manifest.permission.MANAGE_ROLLBACKS)
- != PackageManager.PERMISSION_GRANTED) {
- throw new SecurityException(
- "Setting rollbackImpactLevel requires the MANAGE_ROLLBACKS permission");
- }
- } else if (params.rollbackImpactLevel < 0) {
- throw new IllegalArgumentException("rollbackImpactLevel can't be negative.");
+ if (params.rollbackImpactLevel == PackageManager.ROLLBACK_USER_IMPACT_HIGH
+ || params.rollbackImpactLevel
+ == PackageManager.ROLLBACK_USER_IMPACT_ONLY_MANUAL) {
+ if ((params.installFlags & PackageManager.INSTALL_ENABLE_ROLLBACK) == 0) {
+ throw new IllegalArgumentException(
+ "Can't set rollbackImpactLevel when rollback is not enabled");
+ }
+ if (mContext.checkCallingOrSelfPermission(Manifest.permission.MANAGE_ROLLBACKS)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException(
+ "Setting rollbackImpactLevel requires the MANAGE_ROLLBACKS permission");
}
+ } else if (params.rollbackImpactLevel < 0) {
+ throw new IllegalArgumentException("rollbackImpactLevel can't be negative.");
}
boolean isApex = (params.installFlags & PackageManager.INSTALL_APEX) != 0;
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index aa235c2258ac..cf598e89c988 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -3561,9 +3561,6 @@ class PackageManagerShellCommand extends ShellCommand {
sessionParams.setEnableRollback(true, rollbackStrategy);
break;
case "--rollback-impact-level":
- if (!Flags.recoverabilityDetection()) {
- throw new IllegalArgumentException("Unknown option " + opt);
- }
int rollbackImpactLevel = Integer.parseInt(peekNextArg());
if (rollbackImpactLevel < PackageManager.ROLLBACK_USER_IMPACT_LOW
|| rollbackImpactLevel
@@ -4775,11 +4772,9 @@ class PackageManagerShellCommand extends ShellCommand {
pw.println(" --full: cause the app to be installed as a non-ephemeral full app");
pw.println(" --enable-rollback: enable rollbacks for the upgrade.");
pw.println(" 0=restore (default), 1=wipe, 2=retain");
- if (Flags.recoverabilityDetection()) {
- pw.println(
- " --rollback-impact-level: set device impact required for rollback.");
- pw.println(" 0=low (default), 1=high, 2=manual only");
- }
+ pw.println(
+ " --rollback-impact-level: set device impact required for rollback.");
+ pw.println(" 0=low (default), 1=high, 2=manual only");
pw.println(" --install-location: force the install location:");
pw.println(" 0=auto, 1=internal only, 2=prefer external");
pw.println(" --install-reason: indicates why the app is being installed:");
diff --git a/services/core/java/com/android/server/pm/VerifyingSession.java b/services/core/java/com/android/server/pm/VerifyingSession.java
index dd60a155f2fb..8510ee70cc56 100644
--- a/services/core/java/com/android/server/pm/VerifyingSession.java
+++ b/services/core/java/com/android/server/pm/VerifyingSession.java
@@ -179,8 +179,7 @@ final class VerifyingSession {
// Perform package verification and enable rollback (unless we are simply moving the
// package).
if (!mOriginInfo.mExisting) {
- final boolean verifyForRollback = Flags.recoverabilityDetection()
- ? !isARollback() : true;
+ final boolean verifyForRollback = !isARollback();
if (!isApex() && !isArchivedInstallation() && verifyForRollback) {
// TODO(b/182426975): treat APEX as APK when APK verification is concerned
sendApkVerificationRequest(pkgLite);
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 4153cd1be0a6..76c5240ab623 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -1163,15 +1163,6 @@ public class PhoneWindowManager implements WindowManagerPolicy {
}
}
- private boolean shouldShowHub() {
- final boolean hubEnabled = Settings.Secure.getIntForUser(
- mContext.getContentResolver(), Settings.Secure.GLANCEABLE_HUB_ENABLED,
- 1, mCurrentUserId) == 1;
-
- return mUserManagerInternal.isUserUnlocked(mCurrentUserId) && hubEnabled
- && mDreamManagerInternal.dreamConditionActive();
- }
-
@VisibleForTesting
void powerPress(long eventTime, int count, int displayId) {
// SideFPS still needs to know about suppressed power buttons, in case it needs to block
@@ -1270,10 +1261,9 @@ public class PhoneWindowManager implements WindowManagerPolicy {
// show hub.
boolean keyguardAvailable = !mLockPatternUtils.isLockScreenDisabled(
mCurrentUserId);
- if (shouldShowHub() && keyguardAvailable) {
- // If the hub can be launched, send a message to keyguard. We do not know if
- // the hub is already running or not, keyguard handles turning screen off if
- // it is.
+ if (mUserManagerInternal.isUserUnlocked(mCurrentUserId) && hubEnabled
+ && keyguardAvailable && mDreamManagerInternal.dreamConditionActive()) {
+ // If the hub can be launched, send a message to keyguard.
Bundle options = new Bundle();
options.putBoolean(EXTRA_TRIGGER_HUB, true);
lockNow(options);
@@ -1334,14 +1324,14 @@ public class PhoneWindowManager implements WindowManagerPolicy {
* @param isScreenOn Whether the screen is currently on.
* @param noDreamAction The action to perform if dreaming is not possible.
*/
- private boolean attemptToDreamFromShortPowerButtonPress(
+ private void attemptToDreamFromShortPowerButtonPress(
boolean isScreenOn, Runnable noDreamAction) {
if (mShortPressOnPowerBehavior != SHORT_PRESS_POWER_DREAM_OR_SLEEP
&& mShortPressOnPowerBehavior != SHORT_PRESS_POWER_HUB_OR_DREAM_OR_SLEEP) {
// If the power button behavior isn't one that should be able to trigger the dream, give
// up.
noDreamAction.run();
- return false;
+ return;
}
final DreamManagerInternal dreamManagerInternal = getDreamManagerInternal();
@@ -1349,7 +1339,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
Slog.d(TAG, "Can't start dreaming when attempting to dream from short power"
+ " press (isScreenOn=" + isScreenOn + ")");
noDreamAction.run();
- return false;
+ return;
}
synchronized (mLock) {
@@ -1360,8 +1350,6 @@ public class PhoneWindowManager implements WindowManagerPolicy {
}
dreamManagerInternal.requestDream();
-
- return true;
}
/**
@@ -6410,17 +6398,6 @@ public class PhoneWindowManager implements WindowManagerPolicy {
event.getDisplayId(), event.getKeyCode(), "wakeUpFromWakeKey")) {
return;
}
-
- if (!shouldShowHub()
- && mShortPressOnPowerBehavior == SHORT_PRESS_POWER_HUB_OR_DREAM_OR_SLEEP
- && event.getKeyCode() == KEYCODE_POWER
- && attemptToDreamFromShortPowerButtonPress(false, () -> {})) {
- // In the case that we should wake to dream and successfully initiate dreaming, do not
- // continue waking up. Doing so will exit the dream state and cause UI to react
- // accordingly.
- return;
- }
-
wakeUpFromWakeKey(
event.getEventTime(),
event.getKeyCode(),
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index 8fae875eb29b..e3eced252d1f 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -3379,7 +3379,7 @@ public final class PowerManagerService extends SystemService
}
changed = sleepPowerGroupLocked(powerGroup, time,
PowerManager.GO_TO_SLEEP_REASON_INATTENTIVE, Process.SYSTEM_UID);
- } else if (shouldNapAtBedTimeLocked()) {
+ } else if (shouldNapAtBedTimeLocked(powerGroup)) {
changed = dreamPowerGroupLocked(powerGroup, time,
Process.SYSTEM_UID, /* allowWake= */ false);
} else {
@@ -3395,7 +3395,10 @@ public final class PowerManagerService extends SystemService
* activity timeout has expired and it's bedtime.
*/
@GuardedBy("mLock")
- private boolean shouldNapAtBedTimeLocked() {
+ private boolean shouldNapAtBedTimeLocked(PowerGroup powerGroup) {
+ if (!powerGroup.supportsSandmanLocked()) {
+ return false;
+ }
return mDreamsActivateOnSleepSetting
|| (mDreamsActivateOnDockSetting
&& mDockState != Intent.EXTRA_DOCK_STATE_UNDOCKED)
@@ -3617,9 +3620,10 @@ public final class PowerManagerService extends SystemService
if (!mDreamsDisabledByAmbientModeSuppressionConfig) {
return;
}
+ final PowerGroup defaultPowerGroup = mPowerGroups.get(Display.DEFAULT_DISPLAY_GROUP);
if (!isSuppressed && mIsPowered && mDreamsSupportedConfig && mDreamsEnabledSetting
- && shouldNapAtBedTimeLocked() && isItBedTimeYetLocked(
- mPowerGroups.get(Display.DEFAULT_DISPLAY_GROUP))) {
+ && shouldNapAtBedTimeLocked(defaultPowerGroup)
+ && isItBedTimeYetLocked(defaultPowerGroup)) {
napInternal(SystemClock.uptimeMillis(), Process.SYSTEM_UID, /* allowWake= */ true);
} else if (isSuppressed) {
mDirty |= DIRTY_SETTINGS;
diff --git a/services/core/java/com/android/server/resources/ResourcesManagerShellCommand.java b/services/core/java/com/android/server/resources/ResourcesManagerShellCommand.java
index a75d110e3cd1..17739712d65a 100644
--- a/services/core/java/com/android/server/resources/ResourcesManagerShellCommand.java
+++ b/services/core/java/com/android/server/resources/ResourcesManagerShellCommand.java
@@ -88,6 +88,5 @@ public class ResourcesManagerShellCommand extends ShellCommand {
out.println(" Print this help text.");
out.println(" dump <PROCESS>");
out.println(" Dump the Resources objects in use as well as the history of Resources");
-
}
}
diff --git a/services/core/java/com/android/server/rollback/Rollback.java b/services/core/java/com/android/server/rollback/Rollback.java
index ab756f2a755b..5347ca46ab31 100644
--- a/services/core/java/com/android/server/rollback/Rollback.java
+++ b/services/core/java/com/android/server/rollback/Rollback.java
@@ -29,7 +29,6 @@ import android.annotation.WorkerThread;
import android.content.Context;
import android.content.Intent;
import android.content.IntentSender;
-import android.content.pm.Flags;
import android.content.pm.PackageInstaller;
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
@@ -965,9 +964,7 @@ class Rollback {
ipw.println("-stateDescription: " + mStateDescription);
ipw.println("-timestamp: " + getTimestamp());
ipw.println("-rollbackLifetimeMillis: " + getRollbackLifetimeMillis());
- if (Flags.recoverabilityDetection()) {
- ipw.println("-rollbackImpactLevel: " + info.getRollbackImpactLevel());
- }
+ ipw.println("-rollbackImpactLevel: " + info.getRollbackImpactLevel());
ipw.println("-isStaged: " + isStaged());
ipw.println("-originalSessionId: " + getOriginalSessionId());
ipw.println("-packages:");
diff --git a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
index 2e6be5bb56a8..9ed52d8b3504 100644
--- a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
+++ b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
@@ -1244,17 +1244,12 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub implements Rollba
rollback.makeAvailable();
mPackageHealthObserver.notifyRollbackAvailable(rollback.info);
- if (Flags.recoverabilityDetection()) {
- if (rollback.info.getRollbackImpactLevel() == PackageManager.ROLLBACK_USER_IMPACT_LOW) {
- // TODO(zezeozue): Provide API to explicitly start observing instead
- // of doing this for all rollbacks. If we do this for all rollbacks,
- // should document in PackageInstaller.SessionParams#setEnableRollback
- // After enabling and committing any rollback, observe packages and
- // prepare to rollback if packages crashes too frequently.
- mPackageWatchdog.startExplicitHealthCheck(rollback.getPackageNames(),
- mRollbackLifetimeDurationInMillis, mPackageHealthObserver);
- }
- } else {
+ if (rollback.info.getRollbackImpactLevel() == PackageManager.ROLLBACK_USER_IMPACT_LOW) {
+ // TODO(zezeozue): Provide API to explicitly start observing instead
+ // of doing this for all rollbacks. If we do this for all rollbacks,
+ // should document in PackageInstaller.SessionParams#setEnableRollback
+ // After enabling and committing any rollback, observe packages and
+ // prepare to rollback if packages crashes too frequently.
mPackageWatchdog.startExplicitHealthCheck(rollback.getPackageNames(),
mRollbackLifetimeDurationInMillis, mPackageHealthObserver);
}
diff --git a/services/core/java/com/android/server/rollback/RollbackStore.java b/services/core/java/com/android/server/rollback/RollbackStore.java
index 50db1e4ac30e..6dc40323f2ee 100644
--- a/services/core/java/com/android/server/rollback/RollbackStore.java
+++ b/services/core/java/com/android/server/rollback/RollbackStore.java
@@ -197,9 +197,7 @@ class RollbackStore {
json.put("isStaged", rollback.isStaged());
json.put("causePackages", versionedPackagesToJson(rollback.getCausePackages()));
json.put("committedSessionId", rollback.getCommittedSessionId());
- if (Flags.recoverabilityDetection()) {
- json.put("rollbackImpactLevel", rollback.getRollbackImpactLevel());
- }
+ json.put("rollbackImpactLevel", rollback.getRollbackImpactLevel());
return json;
}
@@ -211,11 +209,9 @@ class RollbackStore {
versionedPackagesFromJson(json.getJSONArray("causePackages")),
json.getInt("committedSessionId"));
- if (Flags.recoverabilityDetection()) {
- // to make it backward compatible.
- rollbackInfo.setRollbackImpactLevel(json.optInt("rollbackImpactLevel",
- PackageManager.ROLLBACK_USER_IMPACT_LOW));
- }
+ // to make it backward compatible.
+ rollbackInfo.setRollbackImpactLevel(json.optInt("rollbackImpactLevel",
+ PackageManager.ROLLBACK_USER_IMPACT_LOW));
return rollbackInfo;
}
diff --git a/services/core/java/com/android/server/security/AttestationVerificationPeerDeviceVerifier.java b/services/core/java/com/android/server/security/AttestationVerificationPeerDeviceVerifier.java
index f060e4d11e82..82df310db9a4 100644
--- a/services/core/java/com/android/server/security/AttestationVerificationPeerDeviceVerifier.java
+++ b/services/core/java/com/android/server/security/AttestationVerificationPeerDeviceVerifier.java
@@ -303,7 +303,11 @@ class AttestationVerificationPeerDeviceVerifier {
if (mRevocationEnabled) {
// Checks Revocation Status List based on
// https://developer.android.com/training/articles/security-key-attestation#certificate_status
- mCertificateRevocationStatusManager.checkRevocationStatus(certificates);
+ // The first certificate is the leaf, which is generated at runtime with the attestation
+ // attributes such as the challenge. It is specific to this attestation instance and
+ // does not need to be checked for revocation.
+ mCertificateRevocationStatusManager.checkRevocationStatus(
+ new ArrayList<>(certificates.subList(1, certificates.size())));
}
}
diff --git a/services/core/java/com/android/server/security/CertificateRevocationStatusManager.java b/services/core/java/com/android/server/security/CertificateRevocationStatusManager.java
index d36d9f5f6636..4cd4b3b84910 100644
--- a/services/core/java/com/android/server/security/CertificateRevocationStatusManager.java
+++ b/services/core/java/com/android/server/security/CertificateRevocationStatusManager.java
@@ -42,6 +42,7 @@ import java.net.MalformedURLException;
import java.net.URL;
import java.security.cert.CertPathValidatorException;
import java.security.cert.X509Certificate;
+import java.time.Duration;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneId;
@@ -67,6 +68,8 @@ class CertificateRevocationStatusManager {
*/
@VisibleForTesting static final int MAX_DAYS_SINCE_LAST_CHECK = 30;
+ @VisibleForTesting static final int NUM_HOURS_BEFORE_NEXT_CHECK = 24;
+
/**
* The number of days since issue date for an intermediary certificate to be considered fresh
* and not require a revocation list check.
@@ -127,6 +130,17 @@ class CertificateRevocationStatusManager {
serialNumbers.add(serialNumber);
}
try {
+ if (isLastCheckedWithin(Duration.ofHours(NUM_HOURS_BEFORE_NEXT_CHECK), serialNumbers)) {
+ Slog.d(
+ TAG,
+ "All certificates have been checked for revocation recently. No need to"
+ + " check this time.");
+ return;
+ }
+ } catch (IOException ignored) {
+ // Proceed to check the revocation status
+ }
+ try {
JSONObject revocationList = fetchRemoteRevocationList();
Map<String, Boolean> areCertificatesRevoked = new HashMap<>();
for (String serialNumber : serialNumbers) {
@@ -151,25 +165,32 @@ class CertificateRevocationStatusManager {
serialNumbers.remove(serialNumber);
}
}
- Map<String, LocalDateTime> lastRevocationCheckData;
try {
- lastRevocationCheckData = getLastRevocationCheckData();
+ if (!isLastCheckedWithin(
+ Duration.ofDays(MAX_DAYS_SINCE_LAST_CHECK), serialNumbers)) {
+ throw new CertPathValidatorException(
+ "Unable to verify the revocation status of one of the certificates "
+ + serialNumbers);
+ }
} catch (IOException ex2) {
throw new CertPathValidatorException(
"Unable to load stored revocation status", ex2);
}
- for (String serialNumber : serialNumbers) {
- if (!lastRevocationCheckData.containsKey(serialNumber)
- || lastRevocationCheckData
- .get(serialNumber)
- .isBefore(
- LocalDateTime.now().minusDays(MAX_DAYS_SINCE_LAST_CHECK))) {
- throw new CertPathValidatorException(
- "Unable to verify the revocation status of certificate "
- + serialNumber);
- }
+ }
+ }
+
+ private boolean isLastCheckedWithin(Duration lastCheckedWithin, List<String> serialNumbers)
+ throws IOException {
+ Map<String, LocalDateTime> lastRevocationCheckData = getLastRevocationCheckData();
+ for (String serialNumber : serialNumbers) {
+ if (!lastRevocationCheckData.containsKey(serialNumber)
+ || lastRevocationCheckData
+ .get(serialNumber)
+ .isBefore(LocalDateTime.now().minus(lastCheckedWithin))) {
+ return false;
}
}
+ return true;
}
private static boolean needToCheckRevocationStatus(
diff --git a/services/core/java/com/android/server/security/FileIntegrityService.java b/services/core/java/com/android/server/security/FileIntegrityService.java
index bfd86d724583..9f9a9807d973 100644
--- a/services/core/java/com/android/server/security/FileIntegrityService.java
+++ b/services/core/java/com/android/server/security/FileIntegrityService.java
@@ -54,11 +54,6 @@ public class FileIntegrityService extends SystemService {
super(PermissionEnforcer.fromContext(context));
}
- @Override
- public boolean isApkVeritySupported() {
- return VerityUtils.isFsVeritySupported();
- }
-
private void checkCallerPackageName(String packageName) {
final int callingUid = Binder.getCallingUid();
final int callingUserId = UserHandle.getUserId(callingUid);
diff --git a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
index 6cc17d4fa009..a94183849bc5 100644
--- a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
+++ b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
@@ -857,8 +857,7 @@ class ActivityMetricsLogger {
info.mWindowsDrawnDelayMs = info.calculateDelay(timestampNs);
info.mIsDrawn = true;
final TransitionInfoSnapshot infoSnapshot = new TransitionInfoSnapshot(info);
- if (info.mLoggedTransitionStarting || (!r.mDisplayContent.mOpeningApps.contains(r)
- && !r.mTransitionController.isCollecting(r))) {
+ if (info.mLoggedTransitionStarting || !r.mTransitionController.isCollecting(r)) {
done(false /* abort */, info, "notifyWindowsDrawn", timestampNs);
}
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 03fe7775edb0..333d91a1b08f 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -159,7 +159,6 @@ import static com.android.server.wm.ActivityRecordProto.FRONT_OF_TASK;
import static com.android.server.wm.ActivityRecordProto.IN_SIZE_COMPAT_MODE;
import static com.android.server.wm.ActivityRecordProto.IS_ANIMATING;
import static com.android.server.wm.ActivityRecordProto.IS_USER_FULLSCREEN_OVERRIDE_ENABLED;
-import static com.android.server.wm.ActivityRecordProto.IS_WAITING_FOR_TRANSITION_START;
import static com.android.server.wm.ActivityRecordProto.LAST_ALL_DRAWN;
import static com.android.server.wm.ActivityRecordProto.LAST_DROP_INPUT_MODE;
import static com.android.server.wm.ActivityRecordProto.LAST_SURFACE_SHOWING;
@@ -330,7 +329,6 @@ import android.view.RemoteAnimationDefinition;
import android.view.SurfaceControl;
import android.view.SurfaceControl.Transaction;
import android.view.WindowInsets;
-import android.view.WindowInsets.Type;
import android.view.WindowManager;
import android.view.WindowManager.LayoutParams;
import android.view.WindowManager.TransitionOldType;
@@ -1519,17 +1517,7 @@ final class ActivityRecord extends WindowToken {
this.task = newTask;
if (shouldStartChangeTransition(newParent, oldParent)) {
- if (mTransitionController.isShellTransitionsEnabled()) {
- // For Shell transition, call #initializeChangeTransition directly to take the
- // screenshot at the Activity level. And Shell will be in charge of handling the
- // surface reparent and crop.
- initializeChangeTransition(getBounds());
- } else {
- // For legacy app transition, we want to take a screenshot of the Activity surface,
- // but animate the change transition on TaskFragment level to get the correct window
- // crop.
- newParent.initializeChangeTransition(getBounds(), getSurfaceControl());
- }
+ mTransitionController.collectVisibleChange(this);
}
super.onParentChanged(newParent, oldParent);
@@ -1557,16 +1545,6 @@ final class ActivityRecord extends WindowToken {
mLastReportedPictureInPictureMode = inPinnedWindowingMode();
}
- // When the associated task is {@code null}, the {@link ActivityRecord} can no longer
- // access visual elements like the {@link DisplayContent}. We must remove any associations
- // such as animations.
- if (task == null) {
- // It is possible we have been marked as a closing app earlier. We must remove ourselves
- // from this list so we do not participate in any future animations.
- if (getDisplayContent() != null) {
- getDisplayContent().mClosingApps.remove(this);
- }
- }
final Task rootTask = getRootTask();
if (task == mLastParentBeforePip && task != null) {
// Notify the TaskFragmentOrganizer that the activity is reparented back from pip.
@@ -1749,14 +1727,6 @@ final class ActivityRecord extends WindowToken {
return;
}
prevDc.onRunningActivityChanged();
-
- if (prevDc.mOpeningApps.remove(this)) {
- // Transfer opening transition to new display.
- mDisplayContent.mOpeningApps.add(this);
- mDisplayContent.executeAppTransition();
- }
-
- prevDc.mClosingApps.remove(this);
prevDc.getDisplayPolicy().removeRelaunchingApp(this);
if (prevDc.mFocusedApp == this) {
@@ -4392,7 +4362,6 @@ final class ActivityRecord extends WindowToken {
ProtoLog.v(WM_DEBUG_APP_TRANSITIONS, "Removing app token: %s", this);
- getDisplayContent().mOpeningApps.remove(this);
getDisplayContent().mUnknownAppVisibilityController.appRemovedOrHidden(this);
mWmService.mSnapshotController.onAppRemoved(this);
mAtmService.mStartingProcessActivities.remove(this);
@@ -4404,20 +4373,9 @@ final class ActivityRecord extends WindowToken {
mAppCompatController.getTransparentPolicy().stop();
// Defer removal of this activity when either a child is animating, or app transition is on
- // going. App transition animation might be applied on the parent task not on the activity,
- // but the actual frame buffer is associated with the activity, so we have to keep the
- // activity while a parent is animating.
- boolean delayed = isAnimating(TRANSITION | PARENTS | CHILDREN,
- ANIMATION_TYPE_APP_TRANSITION | ANIMATION_TYPE_WINDOW_ANIMATION);
- if (getDisplayContent().mClosingApps.contains(this)) {
- delayed = true;
- } else if (getDisplayContent().mAppTransition.isTransitionSet()) {
- getDisplayContent().mClosingApps.add(this);
- delayed = true;
- } else if (mTransitionController.inTransition()) {
- delayed = true;
- }
-
+ // going. The handleCompleteDeferredRemoval will continue the removal.
+ final boolean delayed = isAnimating(CHILDREN, ANIMATION_TYPE_WINDOW_ANIMATION)
+ || mTransitionController.inTransition();
// Don't commit visibility if it is waiting to animate. It will be set post animation.
if (!delayed) {
commitVisibility(false /* visible */, true /* performLayout */);
@@ -5552,9 +5510,6 @@ final class ActivityRecord extends WindowToken {
mAtmService.mBackNavigationController.onAppVisibilityChanged(this, visible);
- final DisplayContent displayContent = getDisplayContent();
- displayContent.mOpeningApps.remove(this);
- displayContent.mClosingApps.remove(this);
setVisibleRequested(visible);
mLastDeferHidingClient = deferHidingClient;
@@ -5567,13 +5522,6 @@ final class ActivityRecord extends WindowToken {
setClientVisible(false);
}
} else {
- if (!appTransition.isTransitionSet()
- && appTransition.isReady()) {
- // Add the app mOpeningApps if transition is unset but ready. This means
- // we're doing a screen freeze, and the unfreeze will wait for all opening
- // apps to be ready.
- displayContent.mOpeningApps.add(this);
- }
startingMoved = false;
// If the token is currently hidden (should be the common case), or has been
// stopped, then we need to set up to wait for its windows to be ready.
@@ -6775,7 +6723,7 @@ final class ActivityRecord extends WindowToken {
setAppLayoutChanges(FINISH_LAYOUT_REDO_ANIM, "checkAppWindowsReadyToShow");
// We can now show all of the drawn windows!
- if (!getDisplayContent().mOpeningApps.contains(this) && canShowWindows()) {
+ if (canShowWindows()) {
showAllWindowsLocked();
}
}
@@ -7449,15 +7397,6 @@ final class ActivityRecord extends WindowToken {
return boundsLayer;
}
- @Override
- boolean isWaitingForTransitionStart() {
- final DisplayContent dc = getDisplayContent();
- return dc != null && dc.mAppTransition.isTransitionSet()
- && (dc.mOpeningApps.contains(this)
- || dc.mClosingApps.contains(this)
- || dc.mChangingContainers.contains(this));
- }
-
boolean isTransitionForward() {
return (mStartingData != null && mStartingData.mIsTransitionForward)
|| mDisplayContent.isNextTransitionForward();
@@ -8051,6 +7990,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.
@@ -8079,6 +8019,9 @@ final class ActivityRecord extends WindowToken {
resolvedConfig.windowConfiguration.setMaxBounds(mTmpBounds);
}
+ mAppCompatController.getSandboxingPolicy().sandboxBoundsIfNeeded(resolvedConfig,
+ parentWindowingMode);
+
applySizeOverrideIfNeeded(
mDisplayContent,
info.applicationInfo,
@@ -9557,7 +9500,6 @@ final class ActivityRecord extends WindowToken {
writeNameToProto(proto, NAME);
super.dumpDebug(proto, WINDOW_TOKEN, logLevel);
proto.write(LAST_SURFACE_SHOWING, mLastSurfaceShowing);
- proto.write(IS_WAITING_FOR_TRANSITION_START, isWaitingForTransitionStart());
proto.write(IS_ANIMATING, isAnimating(TRANSITION | PARENTS | CHILDREN,
ANIMATION_TYPE_APP_TRANSITION | ANIMATION_TYPE_WINDOW_ANIMATION));
proto.write(FILLS_PARENT, fillsParent());
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index 71d04f854908..463a92fb55bb 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -145,6 +145,7 @@ import android.util.SparseIntArray;
import android.view.Display;
import android.webkit.URLUtil;
import android.window.ActivityWindowInfo;
+import android.window.DesktopExperienceFlags;
import android.window.DesktopModeFlags;
import com.android.internal.R;
@@ -2933,6 +2934,8 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks {
/** The helper to calculate whether a container is opaque. */
static class OpaqueContainerHelper implements Predicate<ActivityRecord> {
+ private final boolean mEnableMultipleDesktopsBackend =
+ DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue();
private ActivityRecord mStarting;
private boolean mIgnoringInvisibleActivity;
private boolean mIgnoringKeyguard;
@@ -2955,7 +2958,7 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks {
mIgnoringKeyguard = ignoringKeyguard;
final boolean isOpaque;
- if (!Flags.enableMultipleDesktopsBackend()) {
+ if (!mEnableMultipleDesktopsBackend) {
isOpaque = container.getActivity(this,
true /* traverseTopToBottom */, null /* boundary */) != null;
} else {
@@ -2966,13 +2969,16 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks {
}
private boolean isOpaqueInner(@NonNull WindowContainer<?> container) {
- // If it's a leaf task fragment, then opacity is calculated based on its activities.
- if (container.asTaskFragment() != null
- && ((TaskFragment) container).isLeafTaskFragment()) {
+ final boolean isActivity = container.asActivityRecord() != null;
+ final boolean isLeafTaskFragment = container.asTaskFragment() != null
+ && ((TaskFragment) container).isLeafTaskFragment();
+ if (isActivity || isLeafTaskFragment) {
+ // When it is an activity or leaf task fragment, then opacity is calculated based
+ // on itself or its activities.
return container.getActivity(this,
true /* traverseTopToBottom */, null /* boundary */) != null;
}
- // When not a leaf, it's considered opaque if any of its opaque children fill this
+ // Otherwise, it's considered opaque if any of its opaque children fill this
// container, unless the children are adjacent fragments, in which case as long as they
// are all opaque then |container| is also considered opaque, even if the adjacent
// task fragment aren't filling.
diff --git a/services/core/java/com/android/server/wm/AppCompatController.java b/services/core/java/com/android/server/wm/AppCompatController.java
index bed95face1c9..fc504796b0ac 100644
--- a/services/core/java/com/android/server/wm/AppCompatController.java
+++ b/services/core/java/com/android/server/wm/AppCompatController.java
@@ -44,6 +44,8 @@ class AppCompatController {
private final AppCompatLetterboxPolicy mLetterboxPolicy;
@NonNull
private final AppCompatSizeCompatModePolicy mSizeCompatModePolicy;
+ @NonNull
+ private final AppCompatSandboxingPolicy mSandboxingPolicy;
AppCompatController(@NonNull WindowManagerService wmService,
@NonNull ActivityRecord activityRecord) {
@@ -66,6 +68,7 @@ class AppCompatController {
mAppCompatOverrides, mTransparentPolicy, wmService.mAppCompatConfiguration);
mSizeCompatModePolicy = new AppCompatSizeCompatModePolicy(activityRecord,
mAppCompatOverrides);
+ mSandboxingPolicy = new AppCompatSandboxingPolicy(activityRecord);
}
@NonNull
@@ -143,6 +146,11 @@ class AppCompatController {
return mSizeCompatModePolicy;
}
+ @NonNull
+ AppCompatSandboxingPolicy getSandboxingPolicy() {
+ return mSandboxingPolicy;
+ }
+
void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
getTransparentPolicy().dump(pw, prefix);
getLetterboxPolicy().dump(pw, prefix);
diff --git a/services/core/java/com/android/server/wm/AppCompatLetterboxPolicy.java b/services/core/java/com/android/server/wm/AppCompatLetterboxPolicy.java
index dff072e2dcf8..57811e24351f 100644
--- a/services/core/java/com/android/server/wm/AppCompatLetterboxPolicy.java
+++ b/services/core/java/com/android/server/wm/AppCompatLetterboxPolicy.java
@@ -16,12 +16,14 @@
package com.android.server.wm;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
import static android.content.res.Configuration.ORIENTATION_UNDEFINED;
import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
+import static android.window.DesktopModeFlags.EXCLUDE_CAPTION_FROM_APP_BOUNDS;
import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_BACKGROUND_WALLPAPER;
import static com.android.server.wm.AppCompatConfiguration.letterboxBackgroundTypeToString;
@@ -48,6 +50,8 @@ import java.io.PrintWriter;
*/
class AppCompatLetterboxPolicy {
+ private static final int DIFF_TOLERANCE_PX = 1;
+
@NonNull
private final ActivityRecord mActivityRecord;
@NonNull
@@ -56,6 +60,9 @@ class AppCompatLetterboxPolicy {
private final AppCompatRoundedCorners mAppCompatRoundedCorners;
@NonNull
private final AppCompatConfiguration mAppCompatConfiguration;
+ // Convenience temporary object to save allocation when calculating Rect.
+ @NonNull
+ private final Rect mTmpRect = new Rect();
private boolean mLastShouldShowLetterboxUi;
@@ -71,7 +78,7 @@ class AppCompatLetterboxPolicy {
: new LegacyLetterboxPolicyState();
// TODO (b/358334569) Improve cutout logic dependency on app compat.
mAppCompatRoundedCorners = new AppCompatRoundedCorners(mActivityRecord,
- this::isLetterboxedNotForDisplayCutout);
+ this::ieEligibleForRoundedCorners);
mAppCompatConfiguration = appCompatConfiguration;
}
@@ -84,7 +91,7 @@ class AppCompatLetterboxPolicy {
mLetterboxPolicyState.stop();
}
- /** @return {@value true} if the letterbox policy is running and the activity letterboxed. */
+ /** @return {@code true} if the letterbox policy is running and the activity letterboxed. */
boolean isRunning() {
return mLetterboxPolicyState.isRunning();
}
@@ -130,7 +137,7 @@ class AppCompatLetterboxPolicy {
* <li>The activity is in fullscreen.
* <li>The activity is portrait-only.
* <li>The activity doesn't have a starting window (education should only be displayed
- * once the starting window is removed in {@link #removeStartingWindow}).
+ * once the starting window is removed in {@link ActivityRecord#removeStartingWindow}).
* </ul>
*/
boolean isEligibleForLetterboxEducation() {
@@ -294,16 +301,40 @@ class AppCompatLetterboxPolicy {
}
}
+ private boolean ieEligibleForRoundedCorners(@NonNull WindowState mainWindow) {
+ return isLetterboxedNotForDisplayCutout(mainWindow)
+ && !isFreeformActivityMatchParentAppBoundsHeight();
+ }
+
private boolean isLetterboxedNotForDisplayCutout(@NonNull WindowState mainWindow) {
return shouldShowLetterboxUi(mainWindow)
&& !mainWindow.isLetterboxedForDisplayCutout();
}
+ private boolean isFreeformActivityMatchParentAppBoundsHeight() {
+ if (!EXCLUDE_CAPTION_FROM_APP_BOUNDS.isTrue()) {
+ return false;
+ }
+ final Task task = mActivityRecord.getTask();
+ if (task == null) {
+ return false;
+ }
+ final Rect parentAppBounds = task.getWindowConfiguration().getAppBounds();
+ if (parentAppBounds == null) {
+ return false;
+ }
+
+ mLetterboxPolicyState.getLetterboxInnerBounds(mTmpRect);
+ final int diff = parentAppBounds.height() - mTmpRect.height();
+ // Compare bounds with tolerance of 1 px to account for rounding error calculations.
+ return task.getWindowingMode() == WINDOWING_MODE_FREEFORM && diff <= DIFF_TOLERANCE_PX;
+ }
+
private static boolean shouldNotLayoutLetterbox(@Nullable WindowState w) {
if (w == null) {
return true;
}
- final int type = w.mAttrs.type;
+ final int type = w.getAttrs().type;
// Allow letterbox to be displayed early for base application or application starting
// windows even if it is not on the top z order to prevent flickering when the
// letterboxed window is brought to the top
diff --git a/services/core/java/com/android/server/wm/AppCompatRoundedCorners.java b/services/core/java/com/android/server/wm/AppCompatRoundedCorners.java
index 92d76e5616d8..8165638d4bda 100644
--- a/services/core/java/com/android/server/wm/AppCompatRoundedCorners.java
+++ b/services/core/java/com/android/server/wm/AppCompatRoundedCorners.java
@@ -35,12 +35,12 @@ class AppCompatRoundedCorners {
@NonNull
private final ActivityRecord mActivityRecord;
@NonNull
- private final Predicate<WindowState> mIsLetterboxedNotForDisplayCutout;
+ private final Predicate<WindowState> mRoundedCornersWindowCondition;
AppCompatRoundedCorners(@NonNull ActivityRecord activityRecord,
- @NonNull Predicate<WindowState> isLetterboxedNotForDisplayCutout) {
+ @NonNull Predicate<WindowState> roundedCornersWindowCondition) {
mActivityRecord = activityRecord;
- mIsLetterboxedNotForDisplayCutout = isLetterboxedNotForDisplayCutout;
+ mRoundedCornersWindowCondition = roundedCornersWindowCondition;
}
void updateRoundedCornersIfNeeded(@NonNull final WindowState mainWindow) {
@@ -136,7 +136,7 @@ class AppCompatRoundedCorners {
private boolean requiresRoundedCorners(@NonNull final WindowState mainWindow) {
final AppCompatLetterboxOverrides letterboxOverrides = mActivityRecord
.mAppCompatController.getLetterboxOverrides();
- return mIsLetterboxedNotForDisplayCutout.test(mainWindow)
+ return mRoundedCornersWindowCondition.test(mainWindow)
&& letterboxOverrides.isLetterboxActivityCornersRounded();
}
diff --git a/services/core/java/com/android/server/wm/AppCompatSandboxingPolicy.java b/services/core/java/com/android/server/wm/AppCompatSandboxingPolicy.java
new file mode 100644
index 000000000000..6a46f57e2640
--- /dev/null
+++ b/services/core/java/com/android/server/wm/AppCompatSandboxingPolicy.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.wm;
+
+import static android.window.DesktopModeFlags.EXCLUDE_CAPTION_FROM_APP_BOUNDS;
+
+import static com.android.server.wm.AppCompatUtils.isInDesktopMode;
+
+import android.annotation.NonNull;
+import android.app.WindowConfiguration.WindowingMode;
+import android.content.res.Configuration;
+import android.graphics.Rect;
+
+/**
+ * Encapsulate logic related to sandboxing for app compatibility.
+ */
+class AppCompatSandboxingPolicy {
+
+ @NonNull
+ private final ActivityRecord mActivityRecord;
+
+ AppCompatSandboxingPolicy(@NonNull ActivityRecord activityRecord) {
+ mActivityRecord = activityRecord;
+ }
+
+ /**
+ * In freeform, the container bounds are scaled with app bounds. Activity bounds can be
+ * outside of its container bounds if insets are coupled with configuration outside of
+ * freeform and maintained in freeform for size compat mode.
+ *
+ * <p>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).
+ */
+ void sandboxBoundsIfNeeded(@NonNull Configuration resolvedConfig,
+ @WindowingMode int windowingMode) {
+ if (!EXCLUDE_CAPTION_FROM_APP_BOUNDS.isTrue()) {
+ return;
+ }
+
+ if (isInDesktopMode(mActivityRecord.mAtmService.mContext, windowingMode)) {
+ 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 = mActivityRecord.mResolveConfigHint.mParentAppBoundsOverride;
+ }
+ resolvedConfig.windowConfiguration.setBounds(appBounds);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/wm/AppCompatSizeCompatModePolicy.java b/services/core/java/com/android/server/wm/AppCompatSizeCompatModePolicy.java
index bbc33004ee54..2cfa242bc5fe 100644
--- a/services/core/java/com/android/server/wm/AppCompatSizeCompatModePolicy.java
+++ b/services/core/java/com/android/server/wm/AppCompatSizeCompatModePolicy.java
@@ -17,14 +17,13 @@
package com.android.server.wm;
import static android.app.WindowConfiguration.ROTATION_UNDEFINED;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.content.pm.ActivityInfo.SIZE_CHANGES_SUPPORTED_METADATA;
import static android.content.pm.ActivityInfo.SIZE_CHANGES_SUPPORTED_OVERRIDE;
import static android.content.pm.ActivityInfo.SIZE_CHANGES_UNSUPPORTED_METADATA;
import static android.content.pm.ActivityInfo.SIZE_CHANGES_UNSUPPORTED_OVERRIDE;
import static android.content.res.Configuration.ORIENTATION_UNDEFINED;
-import static com.android.server.wm.DesktopModeHelper.canEnterDesktopMode;
+import static com.android.server.wm.AppCompatUtils.isInDesktopMode;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -545,9 +544,8 @@ class AppCompatSizeCompatModePolicy {
// Allow an application to be up-scaled if its window is smaller than its
// original container or if it's a freeform window in desktop mode.
boolean shouldAllowUpscaling = !(contentW <= viewportW && contentH <= viewportH)
- || (canEnterDesktopMode(mActivityRecord.mAtmService.mContext)
- && newParentConfig.windowConfiguration.getWindowingMode()
- == WINDOWING_MODE_FREEFORM);
+ || isInDesktopMode(mActivityRecord.mAtmService.mContext,
+ newParentConfig.windowConfiguration.getWindowingMode());
return shouldAllowUpscaling ? Math.min(
(float) viewportW / contentW, (float) viewportH / contentH) : 1f;
}
diff --git a/services/core/java/com/android/server/wm/AppCompatUtils.java b/services/core/java/com/android/server/wm/AppCompatUtils.java
index 3e054fc40540..146044008b3f 100644
--- a/services/core/java/com/android/server/wm/AppCompatUtils.java
+++ b/services/core/java/com/android/server/wm/AppCompatUtils.java
@@ -16,16 +16,20 @@
package com.android.server.wm;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.content.res.Configuration.UI_MODE_TYPE_MASK;
import static android.content.res.Configuration.UI_MODE_TYPE_VR_HEADSET;
import static com.android.server.wm.ActivityRecord.State.RESUMED;
+import static com.android.server.wm.DesktopModeHelper.canEnterDesktopMode;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.AppCompatTaskInfo;
import android.app.CameraCompatTaskInfo;
import android.app.TaskInfo;
+import android.app.WindowConfiguration.WindowingMode;
+import android.content.Context;
import android.content.res.Configuration;
import android.graphics.Rect;
import android.view.InsetsSource;
@@ -276,6 +280,14 @@ final class AppCompatUtils {
inOutConfig.windowConfiguration.getAppBounds().offset(offsetX, offsetY);
}
+ /**
+ * Return {@code true} if window is currently in desktop mode.
+ */
+ static boolean isInDesktopMode(@NonNull Context context,
+ @WindowingMode int parentWindowingMode) {
+ return parentWindowingMode == WINDOWING_MODE_FREEFORM && canEnterDesktopMode(context);
+ }
+
private static void clearAppCompatTaskInfo(@NonNull AppCompatTaskInfo info) {
info.topActivityLetterboxVerticalPosition = TaskInfo.PROPERTY_VALUE_UNSET;
info.topActivityLetterboxHorizontalPosition = TaskInfo.PROPERTY_VALUE_UNSET;
diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java
index d98ad8bb9e05..12d4a210400c 100644
--- a/services/core/java/com/android/server/wm/AppTransition.java
+++ b/services/core/java/com/android/server/wm/AppTransition.java
@@ -89,7 +89,6 @@ import static com.android.internal.R.styleable.WindowAnimation_wallpaperIntraOpe
import static com.android.internal.R.styleable.WindowAnimation_wallpaperOpenEnterAnimation;
import static com.android.internal.R.styleable.WindowAnimation_wallpaperOpenExitAnimation;
import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_ANIM;
-import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_APP_TRANSITIONS;
import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_APP_TRANSITIONS_ANIM;
import static com.android.server.wm.AppTransitionProto.APP_TRANSITION_STATE;
import static com.android.server.wm.AppTransitionProto.LAST_USED_APP_TRANSITION;
@@ -1554,26 +1553,6 @@ public class AppTransition implements Dump {
}
private void handleAppTransitionTimeout() {
- synchronized (mService.mGlobalLock) {
- final DisplayContent dc = mDisplayContent;
- if (dc == null) {
- return;
- }
- notifyAppTransitionTimeoutLocked();
- if (isTransitionSet() || !dc.mOpeningApps.isEmpty() || !dc.mClosingApps.isEmpty()
- || !dc.mChangingContainers.isEmpty()) {
- ProtoLog.v(WM_DEBUG_APP_TRANSITIONS,
- "*** APP TRANSITION TIMEOUT. displayId=%d isTransitionSet()=%b "
- + "mOpeningApps.size()=%d mClosingApps.size()=%d "
- + "mChangingApps.size()=%d",
- dc.getDisplayId(), dc.mAppTransition.isTransitionSet(),
- dc.mOpeningApps.size(), dc.mClosingApps.size(),
- dc.mChangingContainers.size());
-
- setTimeout();
- mService.mWindowPlacerLocked.performSurfacePlacement();
- }
- }
}
private static void doAnimationCallback(@NonNull IRemoteCallback callback) {
diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java
index e76a83453a9d..d652ea1e26a4 100644
--- a/services/core/java/com/android/server/wm/BackNavigationController.java
+++ b/services/core/java/com/android/server/wm/BackNavigationController.java
@@ -190,7 +190,9 @@ class BackNavigationController {
currentActivity = window.mActivityRecord;
currentTask = window.getTask();
if ((currentTask != null && !currentTask.isVisibleRequested())
- || (currentActivity != null && !currentActivity.isVisibleRequested())) {
+ || (currentActivity != null && !currentActivity.isVisibleRequested())
+ || (currentActivity != null && currentTask != null
+ && currentTask.getTopNonFinishingActivity() != currentActivity)) {
// Closing transition is happening on focus window and should be update soon,
// don't drive back navigation with it.
ProtoLog.d(WM_DEBUG_BACK_PREVIEW, "Focus window is closing.");
@@ -279,6 +281,10 @@ class BackNavigationController {
} else if (hasTranslucentActivity(currentActivity, prevActivities)) {
// skip if one of participant activity is translucent
backType = BackNavigationInfo.TYPE_CALLBACK;
+ } else if (!allActivitiesHaveProcesses(prevActivities)) {
+ // Skip if one of previous activity has no process. Restart process can be slow, and
+ // the final hierarchy could be different.
+ backType = BackNavigationInfo.TYPE_CALLBACK;
} else if (prevActivities.size() > 0
&& requestOverride == SystemOverrideOnBackInvokedCallback.OVERRIDE_UNDEFINED) {
if ((!isOccluded || isAllActivitiesCanShowWhenLocked(prevActivities))
@@ -601,6 +607,17 @@ class BackNavigationController {
return false;
}
+ private static boolean allActivitiesHaveProcesses(
+ @NonNull ArrayList<ActivityRecord> prevActivities) {
+ for (int i = prevActivities.size() - 1; i >= 0; --i) {
+ final ActivityRecord test = prevActivities.get(i);
+ if (!test.hasProcess()) {
+ return false;
+ }
+ }
+ return true;
+ }
+
private static boolean isAllActivitiesCanShowWhenLocked(
@NonNull ArrayList<ActivityRecord> prevActivities) {
for (int i = prevActivities.size() - 1; i >= 0; --i) {
diff --git a/services/core/java/com/android/server/wm/ConfigurationContainer.java b/services/core/java/com/android/server/wm/ConfigurationContainer.java
index 05dcbb7f9af4..aaa18ad6acc9 100644
--- a/services/core/java/com/android/server/wm/ConfigurationContainer.java
+++ b/services/core/java/com/android/server/wm/ConfigurationContainer.java
@@ -257,10 +257,12 @@ public abstract class ConfigurationContainer<E extends ConfigurationContainer> {
// This should be the only place override the configuration for ActivityRecord. Override
// the value if not calculated yet.
Rect outAppBounds = inOutConfig.windowConfiguration.getAppBounds();
+ Rect outConfigBounds = new Rect(outAppBounds);
if (outAppBounds == null || outAppBounds.isEmpty()) {
inOutConfig.windowConfiguration.setAppBounds(
newParentConfiguration.windowConfiguration.getBounds());
outAppBounds = inOutConfig.windowConfiguration.getAppBounds();
+ outConfigBounds.set(outAppBounds);
if (task != null) {
task = task.getCreatedByOrganizerTask();
if (task != null && (task.mOffsetYForInsets != 0 || task.mOffsetXForInsets != 0)) {
@@ -279,6 +281,12 @@ public abstract class ConfigurationContainer<E extends ConfigurationContainer> {
outAppBounds.inset(decor.mOverrideNonDecorInsets);
}
}
+ if (!outConfigBounds.intersect(decor.mOverrideConfigFrame)) {
+ if (inOutConfig.windowConfiguration.getWindowingMode()
+ == WINDOWING_MODE_MULTI_WINDOW) {
+ outAppBounds.inset(decor.mOverrideConfigInsets);
+ }
+ }
if (task != null && (task.mOffsetYForInsets != 0 || task.mOffsetXForInsets != 0)) {
outAppBounds.offset(-task.mOffsetXForInsets, -task.mOffsetYForInsets);
}
@@ -289,10 +297,10 @@ public abstract class ConfigurationContainer<E extends ConfigurationContainer> {
}
density *= DisplayMetrics.DENSITY_DEFAULT_SCALE;
if (inOutConfig.screenWidthDp == Configuration.SCREEN_WIDTH_DP_UNDEFINED) {
- inOutConfig.screenWidthDp = (int) (outAppBounds.width() / density + 0.5f);
+ inOutConfig.screenWidthDp = (int) (outConfigBounds.width() / density + 0.5f);
}
if (inOutConfig.screenHeightDp == Configuration.SCREEN_HEIGHT_DP_UNDEFINED) {
- inOutConfig.screenHeightDp = (int) (outAppBounds.height() / density + 0.5f);
+ inOutConfig.screenHeightDp = (int) (outConfigBounds.height() / density + 0.5f);
}
if (inOutConfig.smallestScreenWidthDp == Configuration.SMALLEST_SCREEN_WIDTH_DP_UNDEFINED
&& parentWindowingMode == WINDOWING_MODE_FULLSCREEN) {
diff --git a/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java b/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java
index 4eaa11bac016..f473b7b7e4fb 100644
--- a/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java
+++ b/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java
@@ -60,10 +60,11 @@ class DeferredDisplayUpdater {
*/
@VisibleForTesting
static final DisplayInfoFieldsUpdater DEFERRABLE_FIELDS = (out, override) -> {
- // Treat unique id and address change as WM-specific display change as we re-query display
- // settings and parameters based on it which could cause window changes
+ // Treat unique id, address, and canHostTasks change as WM-specific display change as we
+ // re-query display settings and parameters based on it which could cause window changes.
out.uniqueId = override.uniqueId;
out.address = override.address;
+ out.canHostTasks = override.canHostTasks;
// Also apply WM-override fields, since they might produce differences in window hierarchy
WM_OVERRIDE_FIELDS.setFields(out, override);
@@ -433,7 +434,7 @@ class DeferredDisplayUpdater {
second.thermalRefreshRateThrottling)
|| !Objects.equals(first.thermalBrightnessThrottlingDataId,
second.thermalBrightnessThrottlingDataId)
- || first.canHostTasks != second.canHostTasks) {
+ ) {
diff |= DIFF_NOT_WM_DEFERRABLE;
}
@@ -454,6 +455,7 @@ class DeferredDisplayUpdater {
|| !Objects.equals(first.displayShape, second.displayShape)
|| !Objects.equals(first.uniqueId, second.uniqueId)
|| !Objects.equals(first.address, second.address)
+ || first.canHostTasks != second.canHostTasks
) {
diff |= DIFF_WM_DEFERRABLE;
}
diff --git a/services/core/java/com/android/server/wm/DesktopModeHelper.java b/services/core/java/com/android/server/wm/DesktopModeHelper.java
index f35930700653..c2255d8d011a 100644
--- a/services/core/java/com/android/server/wm/DesktopModeHelper.java
+++ b/services/core/java/com/android/server/wm/DesktopModeHelper.java
@@ -51,13 +51,8 @@ public final class DesktopModeHelper {
}
/**
- * Return {@code true} if the current device can hosts desktop sessions on its internal display.
+ * Return {@code true} if the current device supports desktop mode.
*/
- @VisibleForTesting
- static boolean canInternalDisplayHostDesktops(@NonNull Context context) {
- return context.getResources().getBoolean(R.bool.config_canInternalDisplayHostDesktops);
- }
-
// TODO(b/337819319): use a companion object instead.
private static boolean isDesktopModeSupported(@NonNull Context context) {
return context.getResources().getBoolean(R.bool.config_isDesktopModeSupported);
@@ -68,32 +63,45 @@ public final class DesktopModeHelper {
}
/**
+ * Return {@code true} if the current device can hosts desktop sessions on its internal display.
+ */
+ @VisibleForTesting
+ static boolean canInternalDisplayHostDesktops(@NonNull Context context) {
+ return context.getResources().getBoolean(R.bool.config_canInternalDisplayHostDesktops);
+ }
+
+ /**
* Check if Desktop mode should be enabled because the dev option is shown and enabled.
*/
private static boolean isDesktopModeEnabledByDevOption(@NonNull Context context) {
return DesktopModeFlags.isDesktopModeForcedEnabled() && (isDesktopModeDevOptionsSupported(
- context) || isInternalDisplayEligibleToHostDesktops(context));
+ context) || isDeviceEligibleForDesktopMode(context));
}
@VisibleForTesting
- static boolean isInternalDisplayEligibleToHostDesktops(@NonNull Context context) {
- return !shouldEnforceDeviceRestrictions() || canInternalDisplayHostDesktops(context) || (
- Flags.enableDesktopModeThroughDevOption() && isDesktopModeDevOptionsSupported(
- context));
+ static boolean isDeviceEligibleForDesktopMode(@NonNull Context context) {
+ if (!shouldEnforceDeviceRestrictions()) {
+ return true;
+ }
+ final boolean desktopModeSupported = isDesktopModeSupported(context)
+ && canInternalDisplayHostDesktops(context);
+ final boolean desktopModeSupportedByDevOptions =
+ Flags.enableDesktopModeThroughDevOption()
+ && isDesktopModeDevOptionsSupported(context);
+ return desktopModeSupported || desktopModeSupportedByDevOptions;
}
/**
* Return {@code true} if desktop mode can be entered on the current device.
*/
static boolean canEnterDesktopMode(@NonNull Context context) {
- return (isInternalDisplayEligibleToHostDesktops(context)
- && DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_MODE.isTrue()
- && (isDesktopModeSupported(context) || !shouldEnforceDeviceRestrictions()))
+ return (isDeviceEligibleForDesktopMode(context)
+ && DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_MODE.isTrue())
|| isDesktopModeEnabledByDevOption(context);
}
/** Returns {@code true} if desktop experience wallpaper is supported on this device. */
public static boolean isDeviceEligibleForDesktopExperienceWallpaper(@NonNull Context context) {
- return enableConnectedDisplaysWallpaper() && canEnterDesktopMode(context);
+ return enableConnectedDisplaysWallpaper() && isDeviceEligibleForDesktopMode(context);
}
}
diff --git a/services/core/java/com/android/server/wm/DesktopModeLaunchParamsModifier.java b/services/core/java/com/android/server/wm/DesktopModeLaunchParamsModifier.java
index ac987929a142..b6f74a08631e 100644
--- a/services/core/java/com/android/server/wm/DesktopModeLaunchParamsModifier.java
+++ b/services/core/java/com/android/server/wm/DesktopModeLaunchParamsModifier.java
@@ -75,8 +75,8 @@ class DesktopModeLaunchParamsModifier implements LaunchParamsModifier {
return RESULT_SKIP;
}
if (com.android.window.flags.Flags.fixLayoutExistingTask()
- && task.getOrganizedTask() != null) {
- appendLog("task is organized, skipping");
+ && task.getCreatedByOrganizerTask() != null) {
+ appendLog("has created-by-organizer-task, skipping");
return RESULT_SKIP;
}
@@ -111,6 +111,11 @@ class DesktopModeLaunchParamsModifier implements LaunchParamsModifier {
return RESULT_SKIP;
}
+ if ((options == null || options.getLaunchBounds() == null) && task.hasOverrideBounds()) {
+ appendLog("current task has bounds set, not overriding");
+ return RESULT_SKIP;
+ }
+
DesktopModeBoundsCalculator.updateInitialBounds(task, layout, activity, options,
outParams.mBounds, this::appendLog);
appendLog("final desktop mode task bounds set to %s", outParams.mBounds);
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 682f3d8cf1e5..fd322a5c6345 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -108,7 +108,6 @@ import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_W
import static com.android.server.wm.ActivityRecord.State.RESUMED;
import static com.android.server.wm.ActivityTaskManagerService.POWER_MODE_REASON_CHANGE_DISPLAY;
import static com.android.server.wm.DisplayContentProto.APP_TRANSITION;
-import static com.android.server.wm.DisplayContentProto.CLOSING_APPS;
import static com.android.server.wm.DisplayContentProto.CURRENT_FOCUS;
import static com.android.server.wm.DisplayContentProto.DISPLAY_FRAMES;
import static com.android.server.wm.DisplayContentProto.DISPLAY_INFO;
@@ -125,7 +124,6 @@ import static com.android.server.wm.DisplayContentProto.INPUT_METHOD_TARGET;
import static com.android.server.wm.DisplayContentProto.IS_SLEEPING;
import static com.android.server.wm.DisplayContentProto.KEEP_CLEAR_AREAS;
import static com.android.server.wm.DisplayContentProto.MIN_SIZE_OF_RESIZEABLE_TASK_DP;
-import static com.android.server.wm.DisplayContentProto.OPENING_APPS;
import static com.android.server.wm.DisplayContentProto.RESUMED_ACTIVITY;
import static com.android.server.wm.DisplayContentProto.ROOT_DISPLAY_AREA;
import static com.android.server.wm.DisplayContentProto.SLEEP_TOKENS;
@@ -196,7 +194,6 @@ import android.os.UserHandle;
import android.os.UserManager;
import android.os.WorkSource;
import android.provider.Settings;
-import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.DisplayMetrics;
import android.util.DisplayUtils;
@@ -367,15 +364,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
final AppTransition mAppTransition;
- final ArraySet<ActivityRecord> mOpeningApps = new ArraySet<>();
- final ArraySet<ActivityRecord> mClosingApps = new ArraySet<>();
- final ArraySet<WindowContainer> mChangingContainers = new ArraySet<>();
final UnknownAppVisibilityController mUnknownAppVisibilityController;
- /**
- * If a container is closing when resizing, keeps track of its starting bounds when it is
- * removed from {@link #mChangingContainers}.
- */
- final ArrayMap<WindowContainer, Rect> mClosingChangingContainers = new ArrayMap<>();
private MetricsLogger mMetricsLogger;
@@ -1910,17 +1899,10 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
return false;
}
if (checkOpening) {
- if (mTransitionController.isShellTransitionsEnabled()) {
- if (!mTransitionController.isCollecting(r)) {
- return false;
- }
- } else {
- if (!mAppTransition.isTransitionSet() || !mOpeningApps.contains(r)) {
- // Apply normal rotation animation in case of the activity set different
- // requested orientation without activity switch, or the transition is unset due
- // to starting window was transferred ({@link #mSkipAppTransitionAnimation}).
- return false;
- }
+ if (!mTransitionController.isCollecting(r)) {
+ // Apply normal rotation animation in case the activity changes requested
+ // orientation without activity switch.
+ return false;
}
if (r.isState(RESUMED) && !r.getTask().mInResumeTopActivity) {
// If the activity is executing or has done the lifecycle callback, use normal
@@ -3239,25 +3221,48 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
Slog.e(TAG, "ShouldShowSystemDecors shouldn't be updated when the flag is off.");
}
- final boolean shouldShow;
- if (isDefaultDisplay) {
- shouldShow = true;
- } else if (isPrivate()) {
- shouldShow = false;
- } else {
- shouldShow = mDisplay.canHostTasks();
+ final boolean shouldShowContent;
+ if (!allowContentModeSwitch()) {
+ return;
}
+ shouldShowContent = mDisplay.canHostTasks();
- if (shouldShow == mWmService.mDisplayWindowSettings.shouldShowSystemDecorsLocked(this)) {
+ if (shouldShowContent == mWmService.mDisplayWindowSettings
+ .shouldShowSystemDecorsLocked(this)) {
return;
}
- mWmService.mDisplayWindowSettings.setShouldShowSystemDecorsLocked(this, shouldShow);
+ mWmService.mDisplayWindowSettings.setShouldShowSystemDecorsLocked(this, shouldShowContent);
- if (!shouldShow) {
+ if (!shouldShowContent) {
clearAllTasksOnDisplay(null /* clearTasksCallback */, false /* isRemovingDisplay */);
}
}
+ /**
+ * Note that we only allow displays that are able to show system decorations to use the content
+ * mode switch; however, not all displays that are able to show system decorations are allowed
+ * to use the content mode switch.
+ */
+ boolean allowContentModeSwitch() {
+ // The default display should always show system decorations.
+ if (isDefaultDisplay) {
+ return false;
+ }
+
+ // Private display should never show system decorations.
+ if (isPrivate()) {
+ return false;
+ }
+
+ // TODO(b/391965805): Remove this after introducing FLAG_ALLOW_SYSTEM_DECORATIONS_CHANGE.
+ // Virtual displays cannot add or remove system decorations during their lifecycle.
+ if (mDisplay.getType() == Display.TYPE_VIRTUAL) {
+ return false;
+ }
+
+ return true;
+ }
+
DisplayCutout loadDisplayCutout(int displayWidth, int displayHeight) {
if (mDisplayPolicy == null || mInitialDisplayCutout == null) {
return null;
@@ -3354,10 +3359,6 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
void removeImmediately() {
mDeferredRemoval = false;
try {
- // Clear all transitions & screen frozen states when removing display.
- mOpeningApps.clear();
- mClosingApps.clear();
- mChangingContainers.clear();
mUnknownAppVisibilityController.clear();
mAppTransition.removeAppTransitionTimeoutCallbacks();
mTransitionController.unregisterLegacyListener(mFixedRotationTransitionListener);
@@ -3549,12 +3550,6 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
if (mFocusedApp != null) {
mFocusedApp.writeNameToProto(proto, FOCUSED_APP);
}
- for (int i = mOpeningApps.size() - 1; i >= 0; i--) {
- mOpeningApps.valueAt(i).writeIdentifierToProto(proto, OPENING_APPS);
- }
- for (int i = mClosingApps.size() - 1; i >= 0; i--) {
- mClosingApps.valueAt(i).writeIdentifierToProto(proto, CLOSING_APPS);
- }
final Task focusedRootTask = getFocusedRootTask();
if (focusedRootTask != null) {
@@ -4813,19 +4808,6 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
}
}
- if (!mOpeningApps.isEmpty() || !mClosingApps.isEmpty() || !mChangingContainers.isEmpty()) {
- pw.println();
- if (mOpeningApps.size() > 0) {
- pw.print(" mOpeningApps="); pw.println(mOpeningApps);
- }
- if (mClosingApps.size() > 0) {
- pw.print(" mClosingApps="); pw.println(mClosingApps);
- }
- if (mChangingContainers.size() > 0) {
- pw.print(" mChangingApps="); pw.println(mChangingContainers);
- }
- }
-
mUnknownAppVisibilityController.dump(pw, " ");
}
@@ -6578,22 +6560,6 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
.getKeyguardController().isKeyguardLocked(mDisplayId);
}
- boolean isKeyguardLockedOrAodShowing() {
- return isKeyguardLocked() || isAodShowing();
- }
-
- /**
- * @return whether aod is showing for this display
- */
- boolean isAodShowing() {
- final boolean isAodShowing = mRootWindowContainer.mTaskSupervisor
- .getKeyguardController().isAodShowing(mDisplayId);
- if (mDisplayId == DEFAULT_DISPLAY && isAodShowing) {
- return !isKeyguardGoingAway();
- }
- return isAodShowing;
- }
-
/**
* @return whether keyguard is going away on this display
*/
diff --git a/services/core/java/com/android/server/wm/DisplayWindowSettings.java b/services/core/java/com/android/server/wm/DisplayWindowSettings.java
index 539fc123720e..117387553f30 100644
--- a/services/core/java/com/android/server/wm/DisplayWindowSettings.java
+++ b/services/core/java/com/android/server/wm/DisplayWindowSettings.java
@@ -247,12 +247,7 @@ class DisplayWindowSettings {
void setShouldShowSystemDecorsLocked(@NonNull DisplayContent dc, boolean shouldShow) {
final boolean changed = (shouldShow != shouldShowSystemDecorsLocked(dc));
-
- final DisplayInfo displayInfo = dc.getDisplayInfo();
- final SettingsProvider.SettingsEntry overrideSettings =
- mSettingsProvider.getOverrideSettings(displayInfo);
- overrideSettings.mShouldShowSystemDecors = shouldShow;
- mSettingsProvider.updateOverrideSettings(displayInfo, overrideSettings);
+ setShouldShowSystemDecorsInternalLocked(dc, shouldShow);
if (enableDisplayContentModeManagement()) {
if (dc.isDefaultDisplay || dc.isPrivate() || !changed) {
@@ -269,6 +264,15 @@ class DisplayWindowSettings {
}
}
+ void setShouldShowSystemDecorsInternalLocked(@NonNull DisplayContent dc,
+ boolean shouldShow) {
+ final DisplayInfo displayInfo = dc.getDisplayInfo();
+ final SettingsProvider.SettingsEntry overrideSettings =
+ mSettingsProvider.getOverrideSettings(displayInfo);
+ overrideSettings.mShouldShowSystemDecors = shouldShow;
+ mSettingsProvider.updateOverrideSettings(displayInfo, overrideSettings);
+ }
+
boolean isHomeSupportedLocked(@NonNull DisplayContent dc) {
if (dc.getDisplayId() == Display.DEFAULT_DISPLAY) {
// Default display should show home.
diff --git a/services/core/java/com/android/server/wm/KeyguardController.java b/services/core/java/com/android/server/wm/KeyguardController.java
index dd2f49e171a8..6091b8334438 100644
--- a/services/core/java/com/android/server/wm/KeyguardController.java
+++ b/services/core/java/com/android/server/wm/KeyguardController.java
@@ -18,7 +18,6 @@ package com.android.server.wm;
import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
import static android.view.Display.DEFAULT_DISPLAY;
-import static android.view.WindowManager.TRANSIT_FLAG_AOD_APPEARING;
import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_APPEARING;
import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY;
import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_NO_ANIMATION;
@@ -217,9 +216,6 @@ class KeyguardController {
} else if (keyguardShowing && !state.mKeyguardShowing) {
transition.addFlag(TRANSIT_FLAG_KEYGUARD_APPEARING);
}
- if (mWindowManager.mFlags.mAodTransition && aodShowing && !state.mAodShowing) {
- transition.addFlag(TRANSIT_FLAG_AOD_APPEARING);
- }
}
}
// Update the task snapshot if the screen will not be turned off. To make sure that the
@@ -242,27 +238,19 @@ class KeyguardController {
state.mAodShowing = aodShowing;
state.writeEventLog("setKeyguardShown");
- if (keyguardChanged || aodChanged) {
- if (keyguardChanged) {
- // Irrelevant to AOD.
- state.mKeyguardGoingAway = false;
- if (keyguardShowing) {
- state.mDismissalRequested = false;
- }
+ if (keyguardChanged) {
+ // Irrelevant to AOD.
+ state.mKeyguardGoingAway = false;
+ if (keyguardShowing) {
+ state.mDismissalRequested = false;
}
if (goingAwayRemoved
- || (keyguardShowing && !Display.isOffState(dc.getDisplayInfo().state))
- || (mWindowManager.mFlags.mAodTransition && aodShowing)) {
+ || (keyguardShowing && !Display.isOffState(dc.getDisplayInfo().state))) {
// Keyguard decided to show or stopped going away. Send a transition to animate back
// to the locked state before holding the sleep token again
if (!ENABLE_NEW_KEYGUARD_SHELL_TRANSITIONS) {
dc.requestTransitionAndLegacyPrepare(
TRANSIT_TO_FRONT, TRANSIT_FLAG_KEYGUARD_APPEARING);
- if (mWindowManager.mFlags.mAodTransition && aodShowing
- && dc.mTransitionController.isCollecting()) {
- dc.mTransitionController.getCollectingTransition().addFlag(
- TRANSIT_FLAG_AOD_APPEARING);
- }
}
dc.mWallpaperController.adjustWallpaperWindows();
dc.executeAppTransition();
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index c93efd327096..40f16c187f20 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -2775,6 +2775,12 @@ class RootWindowContainer extends WindowContainer<DisplayContent>
return;
}
+ if (enableDisplayContentModeManagement() && display.allowContentModeSwitch()) {
+ mWindowManager.mDisplayWindowSettings
+ .setShouldShowSystemDecorsInternalLocked(display,
+ display.mDisplay.canHostTasks());
+ }
+
startSystemDecorations(display, "displayAdded");
// Drop any cached DisplayInfos associated with this display id - the values are now
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 3abab8bf62c2..6b3499a5d68c 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -166,6 +166,7 @@ import android.view.InsetsState;
import android.view.SurfaceControl;
import android.view.WindowInsets;
import android.view.WindowManager;
+import android.window.DesktopExperienceFlags;
import android.window.DesktopModeFlags;
import android.window.ITaskOrganizer;
import android.window.PictureInPictureSurfaceTransaction;
@@ -2027,7 +2028,7 @@ class Task extends TaskFragment {
}
if (shouldStartChangeTransition(prevWinMode, mTmpPrevBounds)) {
- initializeChangeTransition(mTmpPrevBounds);
+ mTransitionController.collectVisibleChange(this);
}
// If the configuration supports persistent bounds (eg. Freeform), keep track of the
@@ -2378,7 +2379,7 @@ class Task extends TaskFragment {
// configurations and let its parent (organized task) to control it;
final Task rootTask = getRootTask();
boolean shouldInheritBounds = rootTask != this && rootTask.isOrganized();
- if (Flags.enableMultipleDesktopsBackend()) {
+ if (DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue()) {
// Only inherit from organized parent when this task is not organized.
shouldInheritBounds &= !isOrganized();
}
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 74059c1cc9b1..a698a9e82929 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -2779,9 +2779,7 @@ class TaskFragment extends WindowContainer<WindowContainer> {
}
// If this TaskFragment is closing while resizing, crop to the starting bounds instead.
- final Rect bounds = isClosingWhenResizing()
- ? mDisplayContent.mClosingChangingContainers.get(this)
- : getBounds();
+ final Rect bounds = getBounds();
final int width = bounds.width();
final int height = bounds.height();
if (!forceUpdate && width == mLastSurfaceSize.x && height == mLastSurfaceSize.y) {
diff --git a/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java b/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java
index e3a5b66b83fd..6c7d979dc43d 100644
--- a/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java
+++ b/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java
@@ -384,7 +384,7 @@ class TaskLaunchParamsModifier implements LaunchParamsModifier {
// an existing task.
adjustBoundsToAvoidConflictInDisplayArea(taskDisplayArea, outParams.mBounds);
}
- } else {
+ } else if (task == null || !task.hasOverrideBounds()) {
if (source != null && source.inFreeformWindowingMode()
&& resolvedMode == WINDOWING_MODE_FREEFORM
&& outParams.mBounds.isEmpty()
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index fe653e454d6c..5217a759c6ae 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -36,7 +36,6 @@ import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_CLOSE;
-import static android.view.WindowManager.TRANSIT_FLAG_AOD_APPEARING;
import static android.view.WindowManager.TRANSIT_FLAG_IS_RECENTS;
import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_LOCKED;
import static android.view.WindowManager.TRANSIT_OPEN;
@@ -974,10 +973,6 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
return false;
}
- boolean isInAodAppearTransition() {
- return (mFlags & TRANSIT_FLAG_AOD_APPEARING) != 0;
- }
-
/**
* Specifies configuration change explicitly for the window container, so it can be chosen as
* transition target. This is usually used with transition mode
diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
index 25b513d85384..ba7f36419ac5 100644
--- a/services/core/java/com/android/server/wm/TransitionController.java
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -525,19 +525,6 @@ class TransitionController {
return false;
}
- boolean isInAodAppearTransition() {
- if (mCollectingTransition != null && mCollectingTransition.isInAodAppearTransition()) {
- return true;
- }
- for (int i = mWaitingTransitions.size() - 1; i >= 0; --i) {
- if (mWaitingTransitions.get(i).isInAodAppearTransition()) return true;
- }
- for (int i = mPlayingTransitions.size() - 1; i >= 0; --i) {
- if (mPlayingTransitions.get(i).isInAodAppearTransition()) return true;
- }
- return false;
- }
-
/**
* @return A pair of the transition and restore-behind target for the given {@param container}.
* @param container An ancestor of a transient-launch activity
diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java
index 70948e1264c4..a8b9fedcdc73 100644
--- a/services/core/java/com/android/server/wm/WallpaperController.java
+++ b/services/core/java/com/android/server/wm/WallpaperController.java
@@ -166,14 +166,6 @@ class WallpaperController {
mFindResults.setWallpaperTarget(w);
return false;
}
- } else if (mService.mFlags.mAodTransition
- && mDisplayContent.isKeyguardLockedOrAodShowing()) {
- if (mService.mPolicy.isKeyguardHostWindow(w.mAttrs)
- && w.mTransitionController.isInAodAppearTransition()) {
- if (DEBUG_WALLPAPER) Slog.v(TAG, "Found aod transition wallpaper target: " + w);
- mFindResults.setWallpaperTarget(w);
- return true;
- }
}
final boolean animationWallpaper = animatingContainer != null
@@ -300,12 +292,6 @@ class WallpaperController {
return false;
}
- boolean isWallpaperTargetAnimating() {
- return mWallpaperTarget != null && mWallpaperTarget.isAnimating(TRANSITION | PARENTS)
- && (mWallpaperTarget.mActivityRecord == null
- || !mWallpaperTarget.mActivityRecord.isWaitingForTransitionStart());
- }
-
void hideDeferredWallpapersIfNeededLegacy() {
for (int i = mWallpaperTokens.size() - 1; i >= 0; i--) {
final WallpaperWindowToken token = mWallpaperTokens.get(i);
@@ -692,8 +678,7 @@ class WallpaperController {
private WallpaperWindowToken getTokenForTarget(WindowState target) {
if (target == null) return null;
WindowState window = mFindResults.getTopWallpaper(
- (target.canShowWhenLocked() && mService.isKeyguardLocked())
- || (mService.mFlags.mAodTransition && mDisplayContent.isAodShowing()));
+ target.canShowWhenLocked() && mService.isKeyguardLocked());
return window == null ? null : window.mToken.asWallpaperToken();
}
@@ -736,9 +721,7 @@ class WallpaperController {
if (mFindResults.wallpaperTarget == null && mFindResults.useTopWallpaperAsTarget) {
mFindResults.setWallpaperTarget(
- mFindResults.getTopWallpaper(mService.mFlags.mAodTransition
- ? mDisplayContent.isKeyguardLockedOrAodShowing()
- : mDisplayContent.isKeyguardLocked()));
+ mFindResults.getTopWallpaper(mDisplayContent.isKeyguardLocked()));
}
}
@@ -848,14 +831,6 @@ class WallpaperController {
// Use the old target if new target is hidden but old target
// is not. If they're both hidden, still use the new target.
mWallpaperTarget = prevWallpaperTarget;
- } else if (newTargetHidden == oldTargetHidden
- && !mDisplayContent.mOpeningApps.contains(wallpaperTarget.mActivityRecord)
- && (mDisplayContent.mOpeningApps.contains(prevWallpaperTarget.mActivityRecord)
- || mDisplayContent.mClosingApps.contains(prevWallpaperTarget.mActivityRecord))) {
- // If they're both hidden (or both not hidden), prefer the one that's currently in
- // opening or closing app list, this allows transition selection logic to better
- // determine the wallpaper status of opening/closing apps.
- mWallpaperTarget = prevWallpaperTarget;
}
result.setWallpaperTarget(wallpaperTarget);
@@ -910,17 +885,11 @@ class WallpaperController {
if (mDisplayContent.mWmService.mFlags.mEnsureWallpaperInTransitions) {
visibleRequested = mWallpaperTarget != null && mWallpaperTarget.isVisibleRequested();
}
- updateWallpaperTokens(visibleRequested,
- mService.mFlags.mAodTransition
- ? mDisplayContent.isKeyguardLockedOrAodShowing()
- : mDisplayContent.isKeyguardLocked());
+ updateWallpaperTokens(visibleRequested, mDisplayContent.isKeyguardLocked());
ProtoLog.v(WM_DEBUG_WALLPAPER,
"Wallpaper at display %d - visibility: %b, keyguardLocked: %b",
- mDisplayContent.getDisplayId(), visible,
- mService.mFlags.mAodTransition
- ? mDisplayContent.isKeyguardLockedOrAodShowing()
- : mDisplayContent.isKeyguardLocked());
+ mDisplayContent.getDisplayId(), visible, mDisplayContent.isKeyguardLocked());
if (visible && mLastFrozen != mFindResults.isWallpaperTargetForLetterbox) {
mLastFrozen = mFindResults.isWallpaperTargetForLetterbox;
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 95cdf46ea488..45202a29ba97 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -34,7 +34,6 @@ import static android.os.UserHandle.USER_NULL;
import static android.view.SurfaceControl.Transaction;
import static android.view.WindowInsets.Type.InsetsType;
import static android.view.WindowManager.LayoutParams.INVALID_WINDOW_TYPE;
-import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.window.TaskFragmentAnimationParams.DEFAULT_ANIMATION_BACKGROUND_COLOR;
import static android.window.DesktopModeFlags.ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION;
@@ -901,10 +900,6 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
*/
@CallSuper
void removeImmediately() {
- final DisplayContent dc = getDisplayContent();
- if (dc != null) {
- dc.mClosingChangingContainers.remove(this);
- }
while (!mChildren.isEmpty()) {
final E child = mChildren.getLast();
child.removeImmediately();
@@ -1116,10 +1111,6 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
if (asWindowState() == null) {
mTransitionController.collect(this);
}
- // Cancel any change transition queued-up for this container on the old display when
- // this container is moved from the old display.
- mDisplayContent.mClosingChangingContainers.remove(this);
- mDisplayContent.mChangingContainers.remove(this);
}
mDisplayContent = dc;
if (dc != null && dc != this && mPendingTransaction != null) {
@@ -1268,14 +1259,6 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
}
/**
- * @return {@code true} when the container is waiting the app transition start, {@code false}
- * otherwise.
- */
- boolean isWaitingForTransitionStart() {
- return false;
- }
-
- /**
* @return {@code true} if in this subtree of the hierarchy we have an
* {@code ActivityRecord#isAnimating(TRANSITION)}, {@code false} otherwise.
*/
@@ -1302,13 +1285,6 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
return isAnimating(0 /* self only */);
}
- /**
- * @return {@code true} if the container is in changing app transition.
- */
- boolean isChangingAppTransition() {
- return mDisplayContent != null && mDisplayContent.mChangingContainers.contains(this);
- }
-
boolean inTransition() {
return mTransitionController.inTransition(this);
}
@@ -1427,12 +1403,6 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
return setVisibleRequested(newVisReq);
}
- /** Whether this window is closing while resizing. */
- boolean isClosingWhenResizing() {
- return mDisplayContent != null
- && mDisplayContent.mClosingChangingContainers.containsKey(this);
- }
-
void writeIdentifierToProto(ProtoOutputStream proto, long fieldId) {
final long token = proto.start(fieldId);
proto.write(HASH_CODE, System.identityHashCode(this));
@@ -3044,36 +3014,6 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
|| (getParent() != null && getParent().inPinnedWindowingMode());
}
- /**
- * Initializes a change transition.
- *
- * For now, this will only be called for the following cases:
- * 1. {@link Task} is changing windowing mode between fullscreen and freeform.
- * 2. {@link TaskFragment} is organized and is changing window bounds.
- * 3. {@link ActivityRecord} is reparented into an organized {@link TaskFragment}. (The
- * transition will happen on the {@link TaskFragment} for this case).
- *
- * This shouldn't be called on other {@link WindowContainer} unless there is a valid
- * use case.
- *
- * @param startBounds The original bounds (on screen) of the surface we are snapshotting.
- */
- void initializeChangeTransition(Rect startBounds, @Nullable SurfaceControl freezeTarget) {
- if (mDisplayContent.mTransitionController.isShellTransitionsEnabled()) {
- mDisplayContent.mTransitionController.collectVisibleChange(this);
- return;
- }
- mDisplayContent.prepareAppTransition(TRANSIT_CHANGE);
- mDisplayContent.mChangingContainers.add(this);
- // Calculate the relative position in parent container.
- final Rect parentBounds = getParent().getBounds();
- mTmpPoint.set(startBounds.left - parentBounds.left, startBounds.top - parentBounds.top);
- }
-
- void initializeChangeTransition(Rect startBounds) {
- initializeChangeTransition(startBounds, null /* freezeTarget */);
- }
-
ArraySet<WindowContainer> getAnimationSources() {
return mSurfaceAnimationSources;
}
@@ -3166,8 +3106,7 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
getAnimationPosition(mTmpPoint);
mTmpRect.offsetTo(0, 0);
- final boolean isChanging = AppTransition.isChangeTransitOld(transit) && enter
- && isChangingAppTransition();
+ final boolean isChanging = AppTransition.isChangeTransitOld(transit);
if (isChanging) {
final float durationScale = mWmService.getTransitionAnimationScaleLocked();
@@ -3519,9 +3458,6 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
&& (mSurfaceAnimator.getAnimationType() & typesToCheck) > 0) {
return true;
}
- if ((flags & TRANSITION) != 0 && isWaitingForTransitionStart()) {
- return true;
- }
return false;
}
@@ -3603,13 +3539,7 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
return;
}
- if (isClosingWhenResizing()) {
- // This container is closing while resizing, keep its surface at the starting position
- // to prevent animation flicker.
- getRelativePosition(mDisplayContent.mClosingChangingContainers.get(this), mTmpPos);
- } else {
- getRelativePosition(mTmpPos);
- }
+ getRelativePosition(mTmpPos);
final int deltaRotation = getRelativeDisplayRotation();
if (mTmpPos.equals(mLastSurfacePosition) && deltaRotation == mLastDeltaRotation) {
return;
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 4c53ba53a506..4b4736ec6c59 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -1060,7 +1060,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
effects |= applyChanges(taskFragment, c);
if (taskFragment.shouldStartChangeTransition(mTmpBounds0, mTmpBounds1)) {
- taskFragment.initializeChangeTransition(mTmpBounds0);
+ mTransitionController.collectVisibleChange(taskFragment);
}
taskFragment.continueOrganizedTaskFragmentSurfaceUpdate();
return effects;
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 589724182980..9f1289b2c12a 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -3272,13 +3272,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
mDestroying = false;
destroyedSomething = true;
}
-
- // Since mDestroying will affect ActivityRecord#allDrawn, we need to perform another
- // traversal in case we are waiting on this window to start the transition.
- if (getDisplayContent().mAppTransition.isTransitionSet()
- && getDisplayContent().mOpeningApps.contains(mActivityRecord)) {
- mWmService.mWindowPlacerLocked.requestTraversal();
- }
}
return destroyedSomething;
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index f07e6722d836..a8c49e11e4e9 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -47,6 +47,7 @@
#include <dispatcher/Entry.h>
#include <include/gestures.h>
#include <input/Input.h>
+#include <input/InputFlags.h>
#include <input/PointerController.h>
#include <input/PrintTools.h>
#include <input/SpriteController.h>
@@ -124,6 +125,7 @@ static struct {
jmethodID notifyStylusGestureStarted;
jmethodID notifyVibratorState;
jmethodID filterInputEvent;
+ jmethodID filterPointerMotion;
jmethodID interceptKeyBeforeQueueing;
jmethodID interceptMotionBeforeQueueingNonInteractive;
jmethodID interceptKeyBeforeDispatching;
@@ -451,6 +453,8 @@ public:
void notifyPointerDisplayIdChanged(ui::LogicalDisplayId displayId,
const vec2& position) override;
void notifyMouseCursorFadedOnTyping() override;
+ std::optional<vec2> filterPointerMotionForAccessibility(
+ const vec2& current, const vec2& delta, const ui::LogicalDisplayId& displayId) override;
/* --- InputFilterPolicyInterface implementation --- */
void notifyStickyModifierStateChanged(uint32_t modifierState,
@@ -663,7 +667,7 @@ void NativeInputManager::setDisplayViewports(JNIEnv* env, jobjectArray viewportO
}
void NativeInputManager::setDisplayTopology(JNIEnv* env, jobject topologyGraph) {
- if (!com::android::input::flags::connected_displays_cursor()) {
+ if (!InputFlags::connectedDisplaysCursorEnabled()) {
return;
}
@@ -938,6 +942,27 @@ void NativeInputManager::notifyStickyModifierStateChanged(uint32_t modifierState
checkAndClearExceptionFromCallback(env, "notifyStickyModifierStateChanged");
}
+std::optional<vec2> NativeInputManager::filterPointerMotionForAccessibility(
+ const vec2& current, const vec2& delta, const ui::LogicalDisplayId& displayId) {
+ JNIEnv* env = jniEnv();
+ ScopedFloatArrayRO filtered(env,
+ jfloatArray(
+ env->CallObjectMethod(mServiceObj,
+ gServiceClassInfo.filterPointerMotion,
+ delta.x, delta.y, current.x,
+ current.y, displayId.val())));
+ if (checkAndClearExceptionFromCallback(env, "filterPointerMotionForAccessibilityLocked")) {
+ ALOGE("Disabling accessibility pointer motion filter due to an error. "
+ "The filter state in Java and PointerChoreographer would no longer be in sync.");
+ return std::nullopt;
+ }
+ LOG_ALWAYS_FATAL_IF(filtered.size() != 2,
+ "Accessibility pointer motion filter is misbehaving. Returned array size "
+ "%zu should be 2.",
+ filtered.size());
+ return vec2{filtered[0], filtered[1]};
+}
+
sp<SurfaceControl> NativeInputManager::getParentSurfaceForPointers(ui::LogicalDisplayId displayId) {
JNIEnv* env = jniEnv();
jlong nativeSurfaceControlPtr =
@@ -3271,6 +3296,12 @@ static jboolean nativeSetKernelWakeEnabled(JNIEnv* env, jobject nativeImplObj, j
return im->getInputManager()->getReader().setKernelWakeEnabled(deviceId, enabled);
}
+static void nativeSetAccessibilityPointerMotionFilterEnabled(JNIEnv* env, jobject nativeImplObj,
+ jboolean enabled) {
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
+ im->getInputManager()->getChoreographer().setAccessibilityPointerMotionFilterEnabled(enabled);
+}
+
// ----------------------------------------------------------------------------
static const JNINativeMethod gInputManagerMethods[] = {
@@ -3398,6 +3429,8 @@ static const JNINativeMethod gInputManagerMethods[] = {
{"setInputMethodConnectionIsActive", "(Z)V", (void*)nativeSetInputMethodConnectionIsActive},
{"getLastUsedInputDeviceId", "()I", (void*)nativeGetLastUsedInputDeviceId},
{"setKernelWakeEnabled", "(IZ)Z", (void*)nativeSetKernelWakeEnabled},
+ {"setAccessibilityPointerMotionFilterEnabled", "(Z)V",
+ (void*)nativeSetAccessibilityPointerMotionFilterEnabled},
};
#define FIND_CLASS(var, className) \
@@ -3482,6 +3515,8 @@ int register_android_server_InputManager(JNIEnv* env) {
GET_METHOD_ID(gServiceClassInfo.filterInputEvent, clazz,
"filterInputEvent", "(Landroid/view/InputEvent;I)Z");
+ GET_METHOD_ID(gServiceClassInfo.filterPointerMotion, clazz, "filterPointerMotion", "(FFFFI)[F");
+
GET_METHOD_ID(gServiceClassInfo.interceptKeyBeforeQueueing, clazz,
"interceptKeyBeforeQueueing", "(Landroid/view/KeyEvent;I)I");
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 2bbd69c65eb8..e158310455ac 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -1279,12 +1279,6 @@ public final class SystemServer implements Dumpable {
if (!Flags.refactorCrashrecovery()) {
// Initialize RescueParty.
CrashRecoveryAdaptor.rescuePartyRegisterHealthObserver(mSystemContext);
- if (!Flags.recoverabilityDetection()) {
- // Now that we have the bare essentials of the OS up and running, take
- // note that we just booted, which might send out a rescue party if
- // we're stuck in a runtime restart loop.
- CrashRecoveryAdaptor.packageWatchdogNoteBoot(mSystemContext);
- }
}
@@ -1558,14 +1552,6 @@ public final class SystemServer implements Dumpable {
boolean enableVrService = context.getPackageManager().hasSystemFeature(
PackageManager.FEATURE_VR_MODE_HIGH_PERFORMANCE);
- if (!Flags.recoverabilityDetection()) {
- // For debugging RescueParty
- if (Build.IS_DEBUGGABLE
- && SystemProperties.getBoolean("debug.crash_system", false)) {
- throw new RuntimeException();
- }
- }
-
try {
final String SECONDARY_ZYGOTE_PRELOAD = "SecondaryZygotePreload";
// We start the preload ~1s before the webview factory preparation, to
@@ -3091,13 +3077,11 @@ public final class SystemServer implements Dumpable {
CrashRecoveryAdaptor.initializeCrashrecoveryModuleService(mSystemServiceManager);
t.traceEnd();
} else {
- if (Flags.recoverabilityDetection()) {
- // Now that we have the essential services needed for mitigations, register the boot
- // with package watchdog.
- // Note that we just booted, which might send out a rescue party if we're stuck in a
- // runtime restart loop.
- CrashRecoveryAdaptor.packageWatchdogNoteBoot(mSystemContext);
- }
+ // Now that we have the essential services needed for mitigations, register the boot
+ // with package watchdog.
+ // Note that we just booted, which might send out a rescue party if we're stuck in a
+ // runtime restart loop.
+ CrashRecoveryAdaptor.packageWatchdogNoteBoot(mSystemContext);
}
t.traceBegin("MakeDisplayManagerServiceReady");
@@ -3511,12 +3495,10 @@ public final class SystemServer implements Dumpable {
* are updated outside of OTA; and to avoid breaking dependencies from system into apexes.
*/
private void startApexServices(@NonNull TimingsTraceAndSlog t) {
- if (Flags.recoverabilityDetection()) {
- // For debugging RescueParty
- if (Build.IS_DEBUGGABLE
- && SystemProperties.getBoolean("debug.crash_system", false)) {
- throw new RuntimeException();
- }
+ // For debugging RescueParty
+ if (Build.IS_DEBUGGABLE
+ && SystemProperties.getBoolean("debug.crash_system", false)) {
+ throw new RuntimeException();
}
t.traceBegin("startApexServices");
diff --git a/services/supervision/java/com/android/server/supervision/SupervisionService.java b/services/supervision/java/com/android/server/supervision/SupervisionService.java
index a96c477c78d2..f731b50d81b4 100644
--- a/services/supervision/java/com/android/server/supervision/SupervisionService.java
+++ b/services/supervision/java/com/android/server/supervision/SupervisionService.java
@@ -17,6 +17,8 @@
package com.android.server.supervision;
import static android.Manifest.permission.INTERACT_ACROSS_USERS;
+import static android.Manifest.permission.MANAGE_USERS;
+import static android.Manifest.permission.QUERY_USERS;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static com.android.internal.util.Preconditions.checkCallAuthorization;
@@ -79,6 +81,25 @@ public class SupervisionService extends ISupervisionManager.Stub {
}
/**
+ * Creates an {@link Intent} that can be used with {@link Context#startActivity(Intent)} to
+ * launch the activity to verify supervision credentials.
+ *
+ * <p>A valid {@link Intent} is always returned if supervision is enabled at the time this
+ * method is called, the launched activity still need to perform validity checks as the
+ * supervision state can change when it's launched. A null intent is returned if supervision is
+ * disabled at the time of this method call.
+ *
+ * <p>A result code of {@link android.app.Activity#RESULT_OK} indicates successful verification
+ * of the supervision credentials.
+ */
+ @Override
+ @Nullable
+ public Intent createConfirmSupervisionCredentialsIntent() {
+ // TODO(b/392961554): Implement createAuthenticationIntent API
+ throw new UnsupportedOperationException();
+ }
+
+ /**
* Returns whether supervision is enabled for the given user.
*
* <p>Supervision is automatically enabled when the supervision app becomes the profile owner or
@@ -86,6 +107,7 @@ public class SupervisionService extends ISupervisionManager.Stub {
*/
@Override
public boolean isSupervisionEnabledForUser(@UserIdInt int userId) {
+ enforceAnyPermission(QUERY_USERS, MANAGE_USERS);
if (UserHandle.getUserId(Binder.getCallingUid()) != userId) {
enforcePermission(INTERACT_ACROSS_USERS);
}
@@ -96,6 +118,7 @@ public class SupervisionService extends ISupervisionManager.Stub {
@Override
public void setSupervisionEnabledForUser(@UserIdInt int userId, boolean enabled) {
+ // TODO(b/395630828): Ensure that this method can only be called by the system.
if (UserHandle.getUserId(Binder.getCallingUid()) != userId) {
enforcePermission(INTERACT_ACROSS_USERS);
}
@@ -181,8 +204,8 @@ public class SupervisionService extends ISupervisionManager.Stub {
* Ensures that supervision is enabled when the supervision app is the profile owner.
*
* <p>The state syncing with the DevicePolicyManager can only enable supervision and never
- * disable. Supervision can only be disabled explicitly via calls to the
- * {@link #setSupervisionEnabledForUser} method.
+ * disable. Supervision can only be disabled explicitly via calls to the {@link
+ * #setSupervisionEnabledForUser} method.
*/
private void syncStateWithDevicePolicyManager(@UserIdInt int userId) {
final DevicePolicyManagerInternal dpmInternal = mInjector.getDpmInternal();
@@ -221,6 +244,17 @@ public class SupervisionService extends ISupervisionManager.Stub {
mContext.checkCallingOrSelfPermission(permission) == PERMISSION_GRANTED);
}
+ /** Enforces that the caller has at least one of the given permission. */
+ private void enforceAnyPermission(String... permissions) {
+ boolean authorized = false;
+ for (String permission : permissions) {
+ if (mContext.checkCallingOrSelfPermission(permission) == PERMISSION_GRANTED) {
+ authorized = true;
+ }
+ }
+ checkCallAuthorization(authorized);
+ }
+
/** Provides local services in a lazy manner. */
static class Injector {
private final Context mContext;
@@ -280,7 +314,7 @@ public class SupervisionService extends ISupervisionManager.Stub {
}
@VisibleForTesting
- @SuppressLint("MissingPermission") // not needed for a system service
+ @SuppressLint("MissingPermission")
void registerProfileOwnerListener() {
IntentFilter poIntentFilter = new IntentFilter();
poIntentFilter.addAction(DevicePolicyManager.ACTION_PROFILE_OWNER_CHANGED);
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/InputMethodServiceTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/InputMethodServiceTest.java
index 5d64cb638702..2d3f7231cc5c 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/InputMethodServiceTest.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/InputMethodServiceTest.java
@@ -34,12 +34,12 @@ import static org.junit.Assert.fail;
import static org.junit.Assume.assumeFalse;
import static org.junit.Assume.assumeTrue;
+import android.app.ActivityManager;
import android.app.Instrumentation;
import android.content.res.Configuration;
import android.graphics.Insets;
+import android.os.Build;
import android.os.RemoteException;
-import android.platform.test.annotations.RequiresFlagsDisabled;
-import android.platform.test.flag.junit.CheckFlagsRule;
import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.provider.Settings;
import android.server.wm.WindowManagerStateHelper;
@@ -86,25 +86,36 @@ public class InputMethodServiceTest {
"android:id/input_method_nav_back";
private static final String INPUT_METHOD_NAV_IME_SWITCHER_ID =
"android:id/input_method_nav_ime_switcher";
- private static final long TIMEOUT_IN_SECONDS = 3;
- private static final String ENABLE_SHOW_IME_WITH_HARD_KEYBOARD_CMD =
- "settings put secure " + Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD + " 1";
- private static final String DISABLE_SHOW_IME_WITH_HARD_KEYBOARD_CMD =
- "settings put secure " + Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD + " 0";
+
+ /** Timeout until the uiObject should be found. */
+ private static final long TIMEOUT_MS = 5000L * Build.HW_TIMEOUT_MULTIPLIER;
+
+ /** Timeout until the event is expected. */
+ private static final long EXPECT_TIMEOUT_MS = 3000L * Build.HW_TIMEOUT_MULTIPLIER;
+
+ /** Timeout during which the event is not expected. */
+ private static final long NOT_EXCEPT_TIMEOUT_MS = 2000L * Build.HW_TIMEOUT_MULTIPLIER;
+
+ /** Command to set showing the IME when a hardware keyboard is connected. */
+ private static final String SET_SHOW_IME_WITH_HARD_KEYBOARD_CMD =
+ "settings put secure " + Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD;
+ /** Command to get verbose ImeTracker logging state. */
+ private static final String GET_VERBOSE_IME_TRACKER_LOGGING_CMD =
+ "getprop persist.debug.imetracker";
+ /** Command to set verbose ImeTracker logging state. */
+ private static final String SET_VERBOSE_IME_TRACKER_LOGGING_CMD =
+ "setprop persist.debug.imetracker";
/** The ids of the subtypes of SimpleIme. */
private static final int[] SUBTYPE_IDS = new int[]{1, 2};
- private final WindowManagerStateHelper mWmState = new WindowManagerStateHelper();
+ private final WindowManagerStateHelper mWmState = new WindowManagerStateHelper();
private final GestureNavSwitchHelper mGestureNavSwitchHelper = new GestureNavSwitchHelper();
private final DeviceFlagsValueProvider mFlagsValueProvider = new DeviceFlagsValueProvider();
@Rule
- public final CheckFlagsRule mCheckFlagsRule = new CheckFlagsRule(mFlagsValueProvider);
-
- @Rule
public final TestName mName = new TestName();
private Instrumentation mInstrumentation;
@@ -114,7 +125,8 @@ public class InputMethodServiceTest {
private String mInputMethodId;
private TestActivity mActivity;
private InputMethodServiceWrapper mInputMethodService;
- private boolean mShowImeWithHardKeyboardEnabled;
+ private boolean mOriginalVerboseImeTrackerLoggingEnabled;
+ private boolean mOriginalShowImeWithHardKeyboardEnabled;
@Before
public void setUp() throws Exception {
@@ -123,9 +135,12 @@ public class InputMethodServiceTest {
mImm = mInstrumentation.getContext().getSystemService(InputMethodManager.class);
mTargetPackageName = mInstrumentation.getTargetContext().getPackageName();
mInputMethodId = getInputMethodId();
+ mOriginalVerboseImeTrackerLoggingEnabled = getVerboseImeTrackerLogging();
+ if (!mOriginalVerboseImeTrackerLoggingEnabled) {
+ setVerboseImeTrackerLogging(true);
+ }
prepareIme();
prepareActivity();
- mInstrumentation.waitForIdleSync();
mUiDevice.freezeRotation();
mUiDevice.setOrientationNatural();
// Waits for input binding ready.
@@ -148,17 +163,18 @@ public class InputMethodServiceTest {
.that(mInputMethodService.getCurrentInputViewStarted()).isFalse();
});
// Save the original value of show_ime_with_hard_keyboard from Settings.
- mShowImeWithHardKeyboardEnabled =
+ mOriginalShowImeWithHardKeyboardEnabled =
mInputMethodService.getShouldShowImeWithHardKeyboardForTesting();
}
@After
public void tearDown() throws Exception {
mUiDevice.unfreezeRotation();
+ if (!mOriginalVerboseImeTrackerLoggingEnabled) {
+ setVerboseImeTrackerLogging(false);
+ }
// Change back the original value of show_ime_with_hard_keyboard in Settings.
- executeShellCommand(mShowImeWithHardKeyboardEnabled
- ? ENABLE_SHOW_IME_WITH_HARD_KEYBOARD_CMD
- : DISABLE_SHOW_IME_WITH_HARD_KEYBOARD_CMD);
+ setShowImeWithHardKeyboard(mOriginalShowImeWithHardKeyboardEnabled);
executeShellCommand("ime disable " + mInputMethodId);
}
@@ -170,7 +186,7 @@ public class InputMethodServiceTest {
public void testShowHideKeyboard_byUserAction() {
waitUntilActivityReadyForInputInjection(mActivity);
- setShowImeWithHardKeyboard(true /* enabled */);
+ setShowImeWithHardKeyboard(true /* enable */);
// Performs click on EditText to bring up the IME.
Log.i(TAG, "Click on EditText");
@@ -201,14 +217,12 @@ public class InputMethodServiceTest {
*/
@Test
public void testShowHideKeyboard_byInputMethodManager() {
- setShowImeWithHardKeyboard(true /* enabled */);
+ setShowImeWithHardKeyboard(true /* enable */);
- // Triggers to show IME via public API.
verifyInputViewStatusOnMainSync(
() -> assertThat(mActivity.showImeWithInputMethodManager(0 /* flags */)).isTrue(),
EVENT_SHOW, true /* eventExpected */, true /* shown */, "IME is shown");
- // Triggers to hide IME via public API.
verifyInputViewStatusOnMainSync(
() -> assertThat(mActivity.hideImeWithInputMethodManager(0 /* flags */)).isTrue(),
EVENT_HIDE, true /* eventExpected */, false /* shown */, "IME is not shown");
@@ -219,14 +233,12 @@ public class InputMethodServiceTest {
*/
@Test
public void testShowHideKeyboard_byInsetsController() {
- setShowImeWithHardKeyboard(true /* enabled */);
+ setShowImeWithHardKeyboard(true /* enable */);
- // Triggers to show IME via public API.
verifyInputViewStatusOnMainSync(
() -> mActivity.showImeWithWindowInsetsController(),
EVENT_SHOW, true /* eventExpected */, true /* shown */, "IME is shown");
- // Triggers to hide IME via public API.
verifyInputViewStatusOnMainSync(
() -> mActivity.hideImeWithWindowInsetsController(),
EVENT_HIDE, true /* eventExpected */, false /* shown */, "IME is not shown");
@@ -234,53 +246,18 @@ public class InputMethodServiceTest {
/**
* This checks the result of calling IMS#requestShowSelf and IMS#requestHideSelf.
- *
- * <p>With the refactor in b/298172246, all calls to IMMS#{show,hide}MySoftInputLocked
- * will be just apply the requested visibility (by using the callback). Therefore, we will
- * lose flags like HIDE_IMPLICIT_ONLY.
*/
@Test
public void testShowHideSelf() {
- setShowImeWithHardKeyboard(true /* enabled */);
+ setShowImeWithHardKeyboard(true /* enable */);
- // IME request to show itself without any flags, expect shown.
- Log.i(TAG, "Call IMS#requestShowSelf(0)");
verifyInputViewStatusOnMainSync(
() -> mInputMethodService.requestShowSelf(0 /* flags */),
EVENT_SHOW, true /* eventExpected */, true /* shown */, "IME is shown");
- if (!mFlagsValueProvider.getBoolean(Flags.FLAG_REFACTOR_INSETS_CONTROLLER)) {
- // IME request to hide itself with flag HIDE_IMPLICIT_ONLY, expect not hide (shown).
- Log.i(TAG, "Call IMS#requestHideSelf(InputMethodManager.HIDE_IMPLICIT_ONLY)");
- verifyInputViewStatusOnMainSync(
- () -> mInputMethodService.requestHideSelf(
- InputMethodManager.HIDE_IMPLICIT_ONLY),
- EVENT_HIDE, false /* eventExpected */, true /* shown */,
- "IME is still shown after HIDE_IMPLICIT_ONLY");
- }
-
- // IME request to hide itself without any flags, expect hidden.
- Log.i(TAG, "Call IMS#requestHideSelf(0)");
verifyInputViewStatusOnMainSync(
() -> mInputMethodService.requestHideSelf(0 /* flags */),
EVENT_HIDE, true /* eventExpected */, false /* shown */, "IME is not shown");
-
- if (!mFlagsValueProvider.getBoolean(Flags.FLAG_REFACTOR_INSETS_CONTROLLER)) {
- // IME request to show itself with flag SHOW_IMPLICIT, expect shown.
- Log.i(TAG, "Call IMS#requestShowSelf(InputMethodManager.SHOW_IMPLICIT)");
- verifyInputViewStatusOnMainSync(
- () -> mInputMethodService.requestShowSelf(InputMethodManager.SHOW_IMPLICIT),
- EVENT_SHOW, true /* eventExpected */, true /* shown */,
- "IME is shown with SHOW_IMPLICIT");
-
- // IME request to hide itself with flag HIDE_IMPLICIT_ONLY, expect hidden.
- Log.i(TAG, "Call IMS#requestHideSelf(InputMethodManager.HIDE_IMPLICIT_ONLY)");
- verifyInputViewStatusOnMainSync(
- () -> mInputMethodService.requestHideSelf(
- InputMethodManager.HIDE_IMPLICIT_ONLY),
- EVENT_HIDE, true /* eventExpected */, false /* shown */,
- "IME is not shown after HIDE_IMPLICIT_ONLY");
- }
}
/**
@@ -289,28 +266,25 @@ public class InputMethodServiceTest {
*/
@Test
public void testOnEvaluateInputViewShown_showImeWithHardKeyboard() {
- setShowImeWithHardKeyboard(true /* enabled */);
+ setShowImeWithHardKeyboard(true /* enable */);
final var config = mInputMethodService.getResources().getConfiguration();
final var initialConfig = new Configuration(config);
try {
config.keyboard = Configuration.KEYBOARD_QWERTY;
config.hardKeyboardHidden = Configuration.HARDKEYBOARDHIDDEN_NO;
- eventually(() ->
- assertWithMessage("InputView should show with visible hardware keyboard")
- .that(mInputMethodService.onEvaluateInputViewShown()).isTrue());
+ assertWithMessage("InputView should show with visible hardware keyboard")
+ .that(mInputMethodService.onEvaluateInputViewShown()).isTrue();
config.keyboard = Configuration.KEYBOARD_NOKEYS;
config.hardKeyboardHidden = Configuration.HARDKEYBOARDHIDDEN_NO;
- eventually(() ->
- assertWithMessage("InputView should show without hardware keyboard")
- .that(mInputMethodService.onEvaluateInputViewShown()).isTrue());
+ assertWithMessage("InputView should show without hardware keyboard")
+ .that(mInputMethodService.onEvaluateInputViewShown()).isTrue();
config.keyboard = Configuration.KEYBOARD_QWERTY;
config.hardKeyboardHidden = Configuration.HARDKEYBOARDHIDDEN_YES;
- eventually(() ->
- assertWithMessage("InputView should show with hidden hardware keyboard")
- .that(mInputMethodService.onEvaluateInputViewShown()).isTrue());
+ assertWithMessage("InputView should show with hidden hardware keyboard")
+ .that(mInputMethodService.onEvaluateInputViewShown()).isTrue();
} finally {
mInputMethodService.getResources()
.updateConfiguration(initialConfig, null /* metrics */, null /* compat */);
@@ -323,28 +297,25 @@ public class InputMethodServiceTest {
*/
@Test
public void testOnEvaluateInputViewShown_disableShowImeWithHardKeyboard() {
- setShowImeWithHardKeyboard(false /* enabled */);
+ setShowImeWithHardKeyboard(false /* enable */);
final var config = mInputMethodService.getResources().getConfiguration();
final var initialConfig = new Configuration(config);
try {
config.keyboard = Configuration.KEYBOARD_QWERTY;
config.hardKeyboardHidden = Configuration.HARDKEYBOARDHIDDEN_NO;
- eventually(() ->
- assertWithMessage("InputView should not show with visible hardware keyboard")
- .that(mInputMethodService.onEvaluateInputViewShown()).isFalse());
+ assertWithMessage("InputView should not show with visible hardware keyboard")
+ .that(mInputMethodService.onEvaluateInputViewShown()).isFalse();
config.keyboard = Configuration.KEYBOARD_NOKEYS;
config.hardKeyboardHidden = Configuration.HARDKEYBOARDHIDDEN_NO;
- eventually(() ->
- assertWithMessage("InputView should show without hardware keyboard")
- .that(mInputMethodService.onEvaluateInputViewShown()).isTrue());
+ assertWithMessage("InputView should show without hardware keyboard")
+ .that(mInputMethodService.onEvaluateInputViewShown()).isTrue();
config.keyboard = Configuration.KEYBOARD_QWERTY;
config.hardKeyboardHidden = Configuration.HARDKEYBOARDHIDDEN_YES;
- eventually(() ->
- assertWithMessage("InputView should show with hidden hardware keyboard")
- .that(mInputMethodService.onEvaluateInputViewShown()).isTrue());
+ assertWithMessage("InputView should show with hidden hardware keyboard")
+ .that(mInputMethodService.onEvaluateInputViewShown()).isTrue();
} finally {
mInputMethodService.getResources()
.updateConfiguration(initialConfig, null /* metrics */, null /* compat */);
@@ -357,7 +328,7 @@ public class InputMethodServiceTest {
*/
@Test
public void testShowSoftInput_disableShowImeWithHardKeyboard() {
- setShowImeWithHardKeyboard(false /* enabled */);
+ setShowImeWithHardKeyboard(false /* enable */);
final var config = mInputMethodService.getResources().getConfiguration();
final var initialConfig = new Configuration(config);
@@ -386,49 +357,17 @@ public class InputMethodServiceTest {
}
/**
- * This checks that an explicit show request results in the IME being shown.
- */
- @Test
- public void testShowSoftInputExplicitly() {
- setShowImeWithHardKeyboard(true /* enabled */);
-
- // When InputMethodService#onEvaluateInputViewShown() returns true and flag is EXPLICIT, the
- // IME should be shown.
- verifyInputViewStatusOnMainSync(
- () -> assertThat(mActivity.showImeWithInputMethodManager(0 /* flags */)).isTrue(),
- EVENT_SHOW, true /* eventExpected */, true /* shown */, "IME is shown");
- }
-
- /**
- * This checks that an implicit show request results in the IME being shown.
- */
- @Test
- public void testShowSoftInputImplicitly() {
- setShowImeWithHardKeyboard(true /* enabled */);
-
- // When InputMethodService#onEvaluateInputViewShown() returns true and flag is IMPLICIT,
- // the IME should be shown.
- verifyInputViewStatusOnMainSync(() -> assertThat(
- mActivity.showImeWithInputMethodManager(InputMethodManager.SHOW_IMPLICIT)).isTrue(),
- EVENT_SHOW, true /* eventExpected */, true /* shown */, "IME is shown");
- }
-
- /**
* This checks that an explicit show request when the IME is not previously shown,
* and it should be shown in fullscreen mode, results in the IME being shown.
*/
@Test
public void testShowSoftInputExplicitly_fullScreenMode() {
- setShowImeWithHardKeyboard(true /* enabled */);
+ setShowImeWithHardKeyboard(true /* enable */);
// Set orientation landscape to enable fullscreen mode.
setOrientation(2);
- eventually(() -> assertWithMessage("No longer in natural orientation")
- .that(mUiDevice.isNaturalOrientation()).isFalse());
- // Wait for the TestActivity to be recreated.
eventually(() -> assertWithMessage("Activity was re-created after rotation")
.that(TestActivity.getInstance()).isNotEqualTo(mActivity));
- // Get the new TestActivity.
mActivity = TestActivity.getInstance();
assertWithMessage("Re-created activity is not null").that(mActivity).isNotNull();
// Wait for the new EditText to be served by InputMethodManager.
@@ -442,34 +381,40 @@ public class InputMethodServiceTest {
/**
* This checks that an implicit show request when the IME is not previously shown,
- * and it should be shown in fullscreen mode, results in the IME not being shown.
+ * and it should be shown in fullscreen mode behaves like an explicit show request, resulting
+ * in the IME being shown. This is due to the refactor in b/298172246, causing us to lose flag
+ * information like {@link InputMethodManager#SHOW_IMPLICIT}.
*
- * <p>With the refactor in b/298172246, all calls from InputMethodManager#{show,hide}SoftInput
- * will be redirected to InsetsController#{show,hide}. Therefore, we will lose flags like
- * SHOW_IMPLICIT.
+ * <p>Previously, an implicit show request when the IME is not previously shown,
+ * and it should be shown in fullscreen mode, would result in the IME not being shown.
*/
@Test
- @RequiresFlagsDisabled(Flags.FLAG_REFACTOR_INSETS_CONTROLLER)
public void testShowSoftInputImplicitly_fullScreenMode() {
- setShowImeWithHardKeyboard(true /* enabled */);
+ setShowImeWithHardKeyboard(true /* enable */);
// Set orientation landscape to enable fullscreen mode.
setOrientation(2);
- eventually(() -> assertWithMessage("No longer in natural orientation")
- .that(mUiDevice.isNaturalOrientation()).isFalse());
- // Wait for the TestActivity to be recreated.
eventually(() -> assertWithMessage("Activity was re-created after rotation")
.that(TestActivity.getInstance()).isNotEqualTo(mActivity));
- // Get the new TestActivity.
mActivity = TestActivity.getInstance();
assertWithMessage("Re-created activity is not null").that(mActivity).isNotNull();
// Wait for the new EditText to be served by InputMethodManager.
eventually(() -> assertWithMessage("Has an input connection to the re-created Activity")
.that(mImm.hasActiveInputConnection(mActivity.getEditText())).isTrue());
- verifyInputViewStatusOnMainSync(() -> assertThat(
- mActivity.showImeWithInputMethodManager(InputMethodManager.SHOW_IMPLICIT)).isTrue(),
- EVENT_SHOW, false /* eventExpected */, false /* shown */, "IME is not shown");
+ if (mFlagsValueProvider.getBoolean(Flags.FLAG_REFACTOR_INSETS_CONTROLLER)) {
+ verifyInputViewStatusOnMainSync(() -> assertThat(
+ mActivity.showImeWithInputMethodManager(
+ InputMethodManager.SHOW_IMPLICIT))
+ .isTrue(),
+ EVENT_SHOW, true /* eventExpected */, true /* shown */, "IME is shown");
+ } else {
+ verifyInputViewStatusOnMainSync(() -> assertThat(
+ mActivity.showImeWithInputMethodManager(
+ InputMethodManager.SHOW_IMPLICIT))
+ .isTrue(),
+ EVENT_SHOW, false /* eventExpected */, false /* shown */, "IME is not shown");
+ }
}
/**
@@ -478,7 +423,7 @@ public class InputMethodServiceTest {
*/
@Test
public void testShowSoftInputExplicitly_withHardKeyboard() {
- setShowImeWithHardKeyboard(false /* enabled */);
+ setShowImeWithHardKeyboard(false /* enable */);
final var config = mInputMethodService.getResources().getConfiguration();
final var initialConfig = new Configuration(config);
@@ -497,17 +442,17 @@ public class InputMethodServiceTest {
}
/**
- * This checks that an implicit show request when a hardware keyboard is connected,
- * results in the IME not being shown.
+ * This checks that an implicit show request when a hardware keyboard is connected behaves
+ * like an explicit show request, resulting in the IME being shown. This is due to the
+ * refactor in b/298172246, causing us to lose flag information like
+ * {@link InputMethodManager#SHOW_IMPLICIT}.
*
- * <p>With the refactor in b/298172246, all calls from InputMethodManager#{show,hide}SoftInput
- * will be redirected to InsetsController#{show,hide}. Therefore, we will lose flags like
- * SHOW_IMPLICIT.
+ * <p>Previously, an implicit show request when a hardware keyboard is connected would
+ * result in the IME not being shown.
*/
@Test
- @RequiresFlagsDisabled(Flags.FLAG_REFACTOR_INSETS_CONTROLLER)
public void testShowSoftInputImplicitly_withHardKeyboard() {
- setShowImeWithHardKeyboard(false /* enabled */);
+ setShowImeWithHardKeyboard(false /* enable */);
final var config = mInputMethodService.getResources().getConfiguration();
final var initialConfig = new Configuration(config);
@@ -516,10 +461,20 @@ public class InputMethodServiceTest {
config.keyboard = Configuration.KEYBOARD_QWERTY;
config.hardKeyboardHidden = Configuration.HARDKEYBOARDHIDDEN_YES;
- verifyInputViewStatusOnMainSync(() ->assertThat(
- mActivity.showImeWithInputMethodManager(InputMethodManager.SHOW_IMPLICIT))
- .isTrue(),
- EVENT_SHOW, false /* eventExpected */, false /* shown */, "IME is not shown");
+ if (mFlagsValueProvider.getBoolean(Flags.FLAG_REFACTOR_INSETS_CONTROLLER)) {
+ verifyInputViewStatusOnMainSync(() -> assertThat(
+ mActivity.showImeWithInputMethodManager(
+ InputMethodManager.SHOW_IMPLICIT))
+ .isTrue(),
+ EVENT_SHOW, true /* eventExpected */, true /* shown */, "IME is shown");
+ } else {
+ verifyInputViewStatusOnMainSync(() -> assertThat(
+ mActivity.showImeWithInputMethodManager(
+ InputMethodManager.SHOW_IMPLICIT))
+ .isTrue(),
+ EVENT_SHOW, false /* eventExpected */, false /* shown */,
+ "IME is not shown");
+ }
} finally {
mInputMethodService.getResources()
.updateConfiguration(initialConfig, null /* metrics */, null /* compat */);
@@ -532,7 +487,7 @@ public class InputMethodServiceTest {
*/
@Test
public void testShowSoftInputExplicitly_thenConfigurationChanged() {
- setShowImeWithHardKeyboard(false /* enabled */);
+ setShowImeWithHardKeyboard(false /* enable */);
final var config = mInputMethodService.getResources().getConfiguration();
final var initialConfig = new Configuration(config);
@@ -565,17 +520,17 @@ public class InputMethodServiceTest {
/**
* This checks that an implicit show request followed by connecting a hardware keyboard
- * and a configuration change, does not trigger IMS#onFinishInputView,
- * but results in the IME being hidden.
+ * and a configuration change behaves like an explicit show request, resulting in the IME
+ * still being shown. This is due to the refactor in b/298172246, causing us to lose flag
+ * information like {@link InputMethodManager#SHOW_IMPLICIT}.
*
- * <p>With the refactor in b/298172246, all calls from InputMethodManager#{show,hide}SoftInput
- * will be redirected to InsetsController#{show,hide}. Therefore, we will lose flags like
- * SHOW_IMPLICIT.
+ * <p>Previously, an implicit show request followed by connecting a hardware keyboard
+ * and a configuration change, would not trigger IMS#onFinishInputView, but resulted in the
+ * IME being hidden.
*/
@Test
- @RequiresFlagsDisabled(Flags.FLAG_REFACTOR_INSETS_CONTROLLER)
public void testShowSoftInputImplicitly_thenConfigurationChanged() {
- setShowImeWithHardKeyboard(false /* enabled */);
+ setShowImeWithHardKeyboard(false /* enable */);
final var config = mInputMethodService.getResources().getConfiguration();
final var initialConfig = new Configuration(config);
@@ -596,16 +551,23 @@ public class InputMethodServiceTest {
// Simulate a fake configuration change to avoid the recreation of TestActivity.
config.orientation = Configuration.ORIENTATION_LANDSCAPE;
- // Normally, IMS#onFinishInputView will be called when finishing the input view by
- // the user. But if IMS#hideWindow is called when receiving a new configuration change,
- // we don't expect that it's user-driven to finish the lifecycle of input view with
- // IMS#onFinishInputView, because the input view will be re-initialized according
- // to the last #mShowInputRequested state. So in this case we treat the input view as
- // still alive.
- verifyInputViewStatusOnMainSync(
- () -> mInputMethodService.onConfigurationChanged(config),
- EVENT_CONFIG, true /* eventExpected */, true /* inputViewStarted */,
- false /* shown */, "IME is not shown after a configuration change");
+ if (mFlagsValueProvider.getBoolean(Flags.FLAG_REFACTOR_INSETS_CONTROLLER)) {
+ verifyInputViewStatusOnMainSync(
+ () -> mInputMethodService.onConfigurationChanged(config),
+ EVENT_CONFIG, true /* eventExpected */, true /* shown */,
+ "IME is still shown after a configuration change");
+ } else {
+ // Normally, IMS#onFinishInputView will be called when finishing the input view by
+ // the user. But if IMS#hideWindow is called when receiving a new configuration
+ // change, we don't expect that it's user-driven to finish the lifecycle of input
+ // view with IMS#onFinishInputView, because the input view will be re-initialized
+ // according to the last #mShowInputRequested state. So in this case we treat the
+ // input view as still alive.
+ verifyInputViewStatusOnMainSync(
+ () -> mInputMethodService.onConfigurationChanged(config),
+ EVENT_CONFIG, true /* eventExpected */, true /* inputViewStarted */,
+ false /* shown */, "IME is not shown after a configuration change");
+ }
} finally {
mInputMethodService.getResources()
.updateConfiguration(initialConfig, null /* metrics */, null /* compat */);
@@ -619,7 +581,7 @@ public class InputMethodServiceTest {
*/
@Test
public void testShowSoftInputExplicitly_thenShowSoftInputImplicitly_withHardKeyboard() {
- setShowImeWithHardKeyboard(false /* enabled */);
+ setShowImeWithHardKeyboard(false /* enable */);
final var config = mInputMethodService.getResources().getConfiguration();
final var initialConfig = new Configuration(config);
@@ -628,12 +590,10 @@ public class InputMethodServiceTest {
config.keyboard = Configuration.KEYBOARD_QWERTY;
config.hardKeyboardHidden = Configuration.HARDKEYBOARDHIDDEN_YES;
- // Explicit show request.
verifyInputViewStatusOnMainSync(() -> assertThat(
mActivity.showImeWithInputMethodManager(0 /* flags */)).isTrue(),
EVENT_SHOW, true /* eventExpected */, true /* shown */, "IME is shown");
- // Implicit show request.
verifyInputViewStatusOnMainSync(() -> assertThat(
mActivity.showImeWithInputMethodManager(
InputMethodManager.SHOW_IMPLICIT)).isTrue(),
@@ -654,17 +614,18 @@ public class InputMethodServiceTest {
/**
* This checks that a forced show request directly followed by an explicit show request,
- * and then a hide not always request, still results in the IME being shown
- * (i.e. the explicit show request retains the forced state).
+ * and then a not always hide request behaves like a normal hide request, resulting in the
+ * IME being hidden (i.e. the explicit show request does not retain the forced state). This is
+ * due to the refactor in b/298172246, causing us to lose flag information like
+ * {@link InputMethodManager#SHOW_FORCED}.
*
- * <p>With the refactor in b/298172246, all calls from InputMethodManager#{show,hide}SoftInput
- * will be redirected to InsetsController#{show,hide}. Therefore, we will lose flags like
- * HIDE_NOT_ALWAYS.
+ * <p>Previously, a forced show request directly followed by an explicit show request,
+ * and then a not always hide request, would result in the IME still being shown
+ * (i.e. the explicit show request would retain the forced state).
*/
@Test
- @RequiresFlagsDisabled(Flags.FLAG_REFACTOR_INSETS_CONTROLLER)
public void testShowSoftInputForced_testShowSoftInputExplicitly_thenHideSoftInputNotAlways() {
- setShowImeWithHardKeyboard(true /* enabled */);
+ setShowImeWithHardKeyboard(true /* enable */);
verifyInputViewStatusOnMainSync(() -> assertThat(
mActivity.showImeWithInputMethodManager(InputMethodManager.SHOW_FORCED)).isTrue(),
@@ -674,11 +635,123 @@ public class InputMethodServiceTest {
mActivity.showImeWithInputMethodManager(0 /* flags */)).isTrue(),
EVENT_SHOW, false /* eventExpected */, true /* shown */, "IME is still shown");
- verifyInputViewStatusOnMainSync(() -> assertThat(
- mActivity.hideImeWithInputMethodManager(InputMethodManager.HIDE_NOT_ALWAYS))
- .isTrue(),
- EVENT_HIDE, false /* eventExpected */, true /* shown */,
- "IME is still shown after HIDE_NOT_ALWAYS");
+ if (mFlagsValueProvider.getBoolean(Flags.FLAG_REFACTOR_INSETS_CONTROLLER)) {
+ verifyInputViewStatusOnMainSync(() -> assertThat(mActivity
+ .hideImeWithInputMethodManager(InputMethodManager.HIDE_NOT_ALWAYS))
+ .isTrue(),
+ EVENT_HIDE, true /* eventExpected */, false /* shown */,
+ "IME is not shown after HIDE_NOT_ALWAYS");
+ } else {
+ verifyInputViewStatusOnMainSync(() -> assertThat(mActivity
+ .hideImeWithInputMethodManager(InputMethodManager.HIDE_NOT_ALWAYS))
+ .isTrue(),
+ EVENT_HIDE, false /* eventExpected */, true /* shown */,
+ "IME is still shown after HIDE_NOT_ALWAYS");
+ }
+ }
+
+ /**
+ * This checks that an explicit show request followed by an implicit only hide request
+ * behaves like a normal hide request, resulting in the IME being hidden. This is due to
+ * the refactor in b/298172246, causing us to lose flag information like
+ * {@link InputMethodManager#SHOW_IMPLICIT} and {@link InputMethodManager#HIDE_IMPLICIT_ONLY}.
+ *
+ * <p>Previously, an explicit show request followed by an implicit only hide request
+ * would result in the IME still being shown.
+ */
+ @Test
+ public void testShowSoftInputExplicitly_thenHideSoftInputImplicitOnly() {
+ setShowImeWithHardKeyboard(true /* enable */);
+
+ verifyInputViewStatusOnMainSync(
+ () -> mActivity.showImeWithInputMethodManager(0 /* flags */),
+ EVENT_SHOW, true /* eventExpected */, true /* shown */, "IME is shown");
+
+ if (mFlagsValueProvider.getBoolean(Flags.FLAG_REFACTOR_INSETS_CONTROLLER)) {
+ verifyInputViewStatusOnMainSync(
+ () -> mActivity.hideImeWithInputMethodManager(
+ InputMethodManager.HIDE_IMPLICIT_ONLY),
+ EVENT_HIDE, true /* eventExpected */, false /* shown */,
+ "IME is not shown after HIDE_IMPLICIT_ONLY");
+ } else {
+ verifyInputViewStatusOnMainSync(
+ () -> mActivity.hideImeWithInputMethodManager(
+ InputMethodManager.HIDE_IMPLICIT_ONLY),
+ EVENT_HIDE, false /* eventExpected */, true /* shown */,
+ "IME is still shown after HIDE_IMPLICIT_ONLY");
+ }
+ }
+
+ /**
+ * This checks that an implicit show request followed by an implicit only hide request
+ * results in the IME being hidden.
+ */
+ @Test
+ public void testShowSoftInputImplicitly_thenHideSoftInputImplicitOnly() {
+ setShowImeWithHardKeyboard(true /* enable */);
+
+ verifyInputViewStatusOnMainSync(
+ () -> mActivity.showImeWithInputMethodManager(InputMethodManager.SHOW_IMPLICIT),
+ EVENT_SHOW, true /* eventExpected */, true /* shown */,
+ "IME is shown with SHOW_IMPLICIT");
+
+ verifyInputViewStatusOnMainSync(
+ () -> mActivity.hideImeWithInputMethodManager(
+ InputMethodManager.HIDE_IMPLICIT_ONLY),
+ EVENT_HIDE, true /* eventExpected */, false /* shown */,
+ "IME is not shown after HIDE_IMPLICIT_ONLY");
+ }
+
+ /**
+ * This checks that an explicit show self request followed by an implicit only hide self request
+ * behaves like a normal hide self request, resulting in the IME being hidden. This is due to
+ * the refactor in b/298172246, causing us to lose flag information like
+ * {@link InputMethodManager#SHOW_IMPLICIT} and {@link InputMethodManager#HIDE_IMPLICIT_ONLY}.
+ *
+ * <p>Previously, an explicit show self request followed by an implicit only hide self request
+ * would result in the IME still being shown.
+ */
+ @Test
+ public void testShowSelfExplicitly_thenHideSelfImplicitOnly() {
+ setShowImeWithHardKeyboard(true /* enable */);
+
+ verifyInputViewStatusOnMainSync(
+ () -> mInputMethodService.requestShowSelf(0 /* flags */),
+ EVENT_SHOW, true /* eventExpected */, true /* shown */, "IME is shown");
+
+ if (mFlagsValueProvider.getBoolean(Flags.FLAG_REFACTOR_INSETS_CONTROLLER)) {
+ verifyInputViewStatusOnMainSync(
+ () -> mInputMethodService.requestHideSelf(
+ InputMethodManager.HIDE_IMPLICIT_ONLY),
+ EVENT_HIDE, true /* eventExpected */, false /* shown */,
+ "IME is not shown after HIDE_IMPLICIT_ONLY");
+ } else {
+ verifyInputViewStatusOnMainSync(
+ () -> mInputMethodService.requestHideSelf(
+ InputMethodManager.HIDE_IMPLICIT_ONLY),
+ EVENT_HIDE, false /* eventExpected */, true /* shown */,
+ "IME is still shown after HIDE_IMPLICIT_ONLY");
+ }
+ }
+
+ /**
+ * This checks that an implicit show self request followed by an implicit only hide self request
+ * results in the IME being hidden.
+ */
+ @Test
+ public void testShowSelfImplicitly_thenHideSelfImplicitOnly() {
+ setShowImeWithHardKeyboard(true /* enable */);
+
+ verifyInputViewStatusOnMainSync(
+ () -> mInputMethodService.requestShowSelf(InputMethodManager.SHOW_IMPLICIT),
+ EVENT_SHOW, true /* eventExpected */, true /* shown */,
+ "IME is shown with SHOW_IMPLICIT");
+
+ verifyInputViewStatusOnMainSync(
+ () -> mInputMethodService.requestHideSelf(
+ InputMethodManager.HIDE_IMPLICIT_ONLY),
+ EVENT_HIDE, true /* eventExpected */, false /* shown */,
+ "IME is not shown after HIDE_IMPLICIT_ONLY");
}
/**
@@ -686,7 +759,7 @@ public class InputMethodServiceTest {
*/
@Test
public void testFullScreenMode() {
- setShowImeWithHardKeyboard(true /* enabled */);
+ setShowImeWithHardKeyboard(true /* enable */);
Log.i(TAG, "Set orientation natural");
verifyFullscreenMode(() -> setOrientation(0), false /* eventExpected */,
@@ -723,25 +796,22 @@ public class InputMethodServiceTest {
public void testShowHideImeNavigationBar_doesDrawImeNavBar() {
assumeTrue("Must have a navigation bar", hasNavigationBar());
- setShowImeWithHardKeyboard(true /* enabled */);
+ setShowImeWithHardKeyboard(true /* enable */);
- // Show IME
verifyInputViewStatusOnMainSync(
() -> {
- setDrawsImeNavBarAndSwitcherButton(true /* enabled */);
+ setDrawsImeNavBarAndSwitcherButton(true /* enable */);
mActivity.showImeWithWindowInsetsController();
},
EVENT_SHOW, true /* eventExpected */, true /* shown */, "IME is shown");
assertWithMessage("IME navigation bar is initially shown")
.that(mInputMethodService.isImeNavigationBarShownForTesting()).isTrue();
- // Try to hide IME nav bar
mInstrumentation.runOnMainSync(() -> setShowImeNavigationBar(false /* show */));
mInstrumentation.waitForIdleSync();
assertWithMessage("IME navigation bar is not shown after hide request")
.that(mInputMethodService.isImeNavigationBarShownForTesting()).isFalse();
- // Try to show IME nav bar
mInstrumentation.runOnMainSync(() -> setShowImeNavigationBar(true /* show */));
mInstrumentation.waitForIdleSync();
assertWithMessage("IME navigation bar is shown after show request")
@@ -758,25 +828,22 @@ public class InputMethodServiceTest {
public void testShowHideImeNavigationBar_doesNotDrawImeNavBar() {
assumeTrue("Must have a navigation bar", hasNavigationBar());
- setShowImeWithHardKeyboard(true /* enabled */);
+ setShowImeWithHardKeyboard(true /* enable */);
- // Show IME
verifyInputViewStatusOnMainSync(
() -> {
- setDrawsImeNavBarAndSwitcherButton(false /* enabled */);
+ setDrawsImeNavBarAndSwitcherButton(false /* enable */);
mActivity.showImeWithWindowInsetsController();
},
EVENT_SHOW, true /* eventExpected */, true /* shown */, "IME is shown");
assertWithMessage("IME navigation bar is initially not shown")
.that(mInputMethodService.isImeNavigationBarShownForTesting()).isFalse();
- // Try to hide IME nav bar
mInstrumentation.runOnMainSync(() -> setShowImeNavigationBar(false /* show */));
mInstrumentation.waitForIdleSync();
assertWithMessage("IME navigation bar is not shown after hide request")
.that(mInputMethodService.isImeNavigationBarShownForTesting()).isFalse();
- // Try to show IME nav bar
mInstrumentation.runOnMainSync(() -> setShowImeNavigationBar(true /* show */));
mInstrumentation.waitForIdleSync();
assertWithMessage("IME navigation bar is not shown after show request")
@@ -792,7 +859,7 @@ public class InputMethodServiceTest {
waitUntilActivityReadyForInputInjection(mActivity);
- setShowImeWithHardKeyboard(true /* enabled */);
+ setShowImeWithHardKeyboard(true /* enable */);
try (var ignored = mGestureNavSwitchHelper.withGestureNavigationMode()) {
verifyInputViewStatusOnMainSync(
@@ -818,7 +885,7 @@ public class InputMethodServiceTest {
waitUntilActivityReadyForInputInjection(mActivity);
- setShowImeWithHardKeyboard(true /* enabled */);
+ setShowImeWithHardKeyboard(true /* enable */);
try (var ignored = mGestureNavSwitchHelper.withGestureNavigationMode()) {
verifyInputViewStatusOnMainSync(
@@ -844,7 +911,7 @@ public class InputMethodServiceTest {
waitUntilActivityReadyForInputInjection(mActivity);
- setShowImeWithHardKeyboard(true /* enabled */);
+ setShowImeWithHardKeyboard(true /* enable */);
final var info = mImm.getCurrentInputMethodInfo();
assertWithMessage("InputMethodInfo is not null").that(info).isNotNull();
@@ -855,7 +922,7 @@ public class InputMethodServiceTest {
try (var ignored = mGestureNavSwitchHelper.withGestureNavigationMode()) {
verifyInputViewStatusOnMainSync(
() -> {
- setDrawsImeNavBarAndSwitcherButton(true /* enabled */);
+ setDrawsImeNavBarAndSwitcherButton(true /* enable */);
mActivity.showImeWithWindowInsetsController();
},
EVENT_SHOW, true /* eventExpected */, true /* shown */, "IME is shown");
@@ -884,7 +951,7 @@ public class InputMethodServiceTest {
waitUntilActivityReadyForInputInjection(mActivity);
- setShowImeWithHardKeyboard(true /* enabled */);
+ setShowImeWithHardKeyboard(true /* enable */);
final var info = mImm.getCurrentInputMethodInfo();
assertWithMessage("InputMethodInfo is not null").that(info).isNotNull();
@@ -893,7 +960,7 @@ public class InputMethodServiceTest {
try (var ignored = mGestureNavSwitchHelper.withGestureNavigationMode()) {
verifyInputViewStatusOnMainSync(
() -> {
- setDrawsImeNavBarAndSwitcherButton(true /* enabled */);
+ setDrawsImeNavBarAndSwitcherButton(true /* enable */);
mActivity.showImeWithWindowInsetsController();
},
EVENT_SHOW, true /* eventExpected */, true /* shown */, "IME is shown");
@@ -956,7 +1023,8 @@ public class InputMethodServiceTest {
runnable.run();
}
mInstrumentation.waitForIdleSync();
- eventCalled = latch.await(TIMEOUT_IN_SECONDS, TimeUnit.SECONDS);
+ eventCalled = latch.await(eventExpected ? EXPECT_TIMEOUT_MS : NOT_EXCEPT_TIMEOUT_MS,
+ TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
fail("Interrupted while waiting for latch: " + e.getMessage());
return;
@@ -1016,10 +1084,8 @@ public class InputMethodServiceTest {
verifyInputViewStatus(runnable, EVENT_CONFIG, eventExpected, false /* shown */,
"IME is not shown");
if (eventExpected) {
- // Wait for the TestActivity to be recreated.
eventually(() -> assertWithMessage("Activity was re-created after rotation")
.that(TestActivity.getInstance()).isNotEqualTo(mActivity));
- // Get the new TestActivity.
mActivity = TestActivity.getInstance();
assertWithMessage("Re-created activity is not null").that(mActivity).isNotNull();
// Wait for the new EditText to be served by InputMethodManager.
@@ -1062,6 +1128,7 @@ public class InputMethodServiceTest {
private void prepareActivity() {
mActivity = TestActivity.startSync(mInstrumentation);
+ mInstrumentation.waitForIdleSync();
Log.i(TAG, "Finish preparing activity with editor.");
}
@@ -1086,21 +1153,51 @@ public class InputMethodServiceTest {
* @param enable the value to be set.
*/
private void setShowImeWithHardKeyboard(boolean enable) {
+ if (mInputMethodService == null) {
+ // If the IME is no longer around, reset the setting unconditionally.
+ executeShellCommand(SET_SHOW_IME_WITH_HARD_KEYBOARD_CMD + " " + (enable ? "1" : "0"));
+ return;
+ }
+
final boolean currentEnabled =
mInputMethodService.getShouldShowImeWithHardKeyboardForTesting();
if (currentEnabled != enable) {
- executeShellCommand(enable
- ? ENABLE_SHOW_IME_WITH_HARD_KEYBOARD_CMD
- : DISABLE_SHOW_IME_WITH_HARD_KEYBOARD_CMD);
+ executeShellCommand(SET_SHOW_IME_WITH_HARD_KEYBOARD_CMD + " " + (enable ? "1" : "0"));
eventually(() -> assertWithMessage("showImeWithHardKeyboard updated")
.that(mInputMethodService.getShouldShowImeWithHardKeyboardForTesting())
.isEqualTo(enable));
}
}
- private static void executeShellCommand(@NonNull String cmd) {
+ /**
+ * Gets the verbose logging state in {@link android.view.inputmethod.ImeTracker}.
+ *
+ * @return {@code true} iff verbose logging is enabled.
+ */
+ private static boolean getVerboseImeTrackerLogging() {
+ return executeShellCommand(GET_VERBOSE_IME_TRACKER_LOGGING_CMD).trim().equals("1");
+ }
+
+ /**
+ * Sets verbose logging in {@link android.view.inputmethod.ImeTracker}.
+ *
+ * @param enabled whether to enable or disable verbose logging.
+ *
+ * @implNote This must use {@link ActivityManager#notifySystemPropertiesChanged()} to listen
+ * for changes to the system property for the verbose ImeTracker logging.
+ */
+ private void setVerboseImeTrackerLogging(boolean enabled) {
+ final var context = mInstrumentation.getContext();
+ final var am = context.getSystemService(ActivityManager.class);
+
+ executeShellCommand(SET_VERBOSE_IME_TRACKER_LOGGING_CMD + " " + (enabled ? "1" : "0"));
+ am.notifySystemPropertiesChanged();
+ }
+
+ @NonNull
+ private static String executeShellCommand(@NonNull String cmd) {
Log.i(TAG, "Run command: " + cmd);
- SystemUtil.runShellCommandOrThrow(cmd);
+ return SystemUtil.runShellCommandOrThrow(cmd);
}
/**
@@ -1113,8 +1210,7 @@ public class InputMethodServiceTest {
@NonNull
private UiObject2 getUiObject(@NonNull BySelector bySelector) {
- final var uiObject = mUiDevice.wait(Until.findObject(bySelector),
- TimeUnit.SECONDS.toMillis(TIMEOUT_IN_SECONDS));
+ final var uiObject = mUiDevice.wait(Until.findObject(bySelector), TIMEOUT_MS);
assertWithMessage("UiObject with " + bySelector + " was found").that(uiObject).isNotNull();
return uiObject;
}
@@ -1137,10 +1233,10 @@ public class InputMethodServiceTest {
*
* <p>Note, neither of these are normally drawn when in three button navigation mode.
*
- * @param enabled whether the IME nav bar and IME Switcher button are drawn.
+ * @param enable whether the IME nav bar and IME Switcher button are drawn.
*/
- private void setDrawsImeNavBarAndSwitcherButton(boolean enabled) {
- final int flags = enabled ? IME_DRAWS_IME_NAV_BAR | SHOW_IME_SWITCHER_WHEN_IME_IS_SHOWN : 0;
+ private void setDrawsImeNavBarAndSwitcherButton(boolean enable) {
+ final int flags = enable ? IME_DRAWS_IME_NAV_BAR | SHOW_IME_SWITCHER_WHEN_IME_IS_SHOWN : 0;
mInputMethodService.getInputMethodInternal().onNavButtonFlagsChanged(flags);
}
diff --git a/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/src/com/android/apps/inputmethod/simpleime/ims/InputMethodServiceWrapper.java b/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/src/com/android/apps/inputmethod/simpleime/ims/InputMethodServiceWrapper.java
index 558d1a7c4e8b..d4d4dcaa4f48 100644
--- a/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/src/com/android/apps/inputmethod/simpleime/ims/InputMethodServiceWrapper.java
+++ b/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/src/com/android/apps/inputmethod/simpleime/ims/InputMethodServiceWrapper.java
@@ -111,12 +111,6 @@ public class InputMethodServiceWrapper extends InputMethodService {
}
@Override
- public void requestHideSelf(int flags) {
- Log.i(TAG, "requestHideSelf() " + flags);
- super.requestHideSelf(flags);
- }
-
- @Override
public void onConfigurationChanged(Configuration newConfig) {
Log.i(TAG, "onConfigurationChanged() " + newConfig);
super.onConfigurationChanged(newConfig);
diff --git a/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java b/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java
index 7d3cd8a8a9ae..38de7ce013c2 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java
@@ -34,6 +34,7 @@ import static com.android.server.display.DisplayAdapter.DISPLAY_DEVICE_EVENT_REM
import static com.android.server.display.DisplayDeviceInfo.DIFF_EVERYTHING;
import static com.android.server.display.DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY;
import static com.android.server.display.LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_ADDED;
+import static com.android.server.display.LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_COMMITTED_STATE_CHANGED;
import static com.android.server.display.LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_CONNECTED;
import static com.android.server.display.LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_DISCONNECTED;
import static com.android.server.display.LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_REMOVED;
@@ -1180,13 +1181,21 @@ public class LogicalDisplayMapperTest {
assertEquals(LOGICAL_DISPLAY_EVENT_STATE_CHANGED,
mLogicalDisplayMapper.updateAndGetMaskForDisplayPropertyChanges(newDisplayInfo));
+ // Change the display committed state
+ when(mFlagsMock.isCommittedStateSeparateEventEnabled()).thenReturn(true);
+ newDisplayInfo = new DisplayInfo();
+ newDisplayInfo.committedState = STATE_OFF;
+ assertEquals(LOGICAL_DISPLAY_EVENT_COMMITTED_STATE_CHANGED,
+ mLogicalDisplayMapper.updateAndGetMaskForDisplayPropertyChanges(newDisplayInfo));
// Change multiple properties
newDisplayInfo = new DisplayInfo();
newDisplayInfo.refreshRateOverride = 30;
newDisplayInfo.state = STATE_OFF;
+ newDisplayInfo.committedState = STATE_OFF;
assertEquals(LOGICAL_DISPLAY_EVENT_REFRESH_RATE_CHANGED
- | LOGICAL_DISPLAY_EVENT_STATE_CHANGED,
+ | LOGICAL_DISPLAY_EVENT_STATE_CHANGED
+ | LOGICAL_DISPLAY_EVENT_COMMITTED_STATE_CHANGED,
mLogicalDisplayMapper.updateAndGetMaskForDisplayPropertyChanges(newDisplayInfo));
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java b/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java
index db04d39e772c..eda5e8613dba 100644
--- a/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java
@@ -23,7 +23,6 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyLong;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyString;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
import static com.android.server.PackageWatchdog.MITIGATION_RESULT_SKIPPED;
import static com.android.server.PackageWatchdog.MITIGATION_RESULT_SUCCESS;
@@ -34,8 +33,6 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.ArgumentMatchers.isNull;
-import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import android.content.ContentResolver;
@@ -43,13 +40,8 @@ import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.VersionedPackage;
-import android.crashrecovery.flags.Flags;
import android.os.RecoverySystem;
import android.os.SystemProperties;
-import android.os.UserHandle;
-import android.platform.test.annotations.EnableFlags;
-import android.platform.test.flag.junit.FlagsParameterization;
-import android.platform.test.flag.junit.SetFlagsRule;
import android.provider.DeviceConfig;
import android.provider.Settings;
@@ -60,14 +52,8 @@ import com.android.server.am.SettingsToPropertiesMapper;
import org.junit.After;
import org.junit.Before;
-import org.junit.Rule;
import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameters;
import org.mockito.Answers;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoSession;
import org.mockito.quality.Strictness;
@@ -76,34 +62,19 @@ import org.mockito.stubbing.Answer;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.HashSet;
-import java.util.List;
import java.util.concurrent.TimeUnit;
/**
* Test RescueParty.
*/
-@RunWith(Parameterized.class)
public class RescuePartyTest {
- @Rule
- public SetFlagsRule mSetFlagsRule;
private static final long CURRENT_NETWORK_TIME_MILLIS = 0L;
- private static final String FAKE_NATIVE_NAMESPACE1 = "native1";
- private static final String FAKE_NATIVE_NAMESPACE2 = "native2";
- private static final String[] FAKE_RESET_NATIVE_NAMESPACES =
- {FAKE_NATIVE_NAMESPACE1, FAKE_NATIVE_NAMESPACE2};
private static VersionedPackage sFailingPackage = new VersionedPackage("com.package.name", 1);
private static final String PROP_DISABLE_RESCUE = "persist.sys.disable_rescue";
- private static final String CALLING_PACKAGE1 = "com.package.name1";
- private static final String CALLING_PACKAGE2 = "com.package.name2";
- private static final String CALLING_PACKAGE3 = "com.package.name3";
private static final String PERSISTENT_PACKAGE = "com.persistent.package";
private static final String NON_PERSISTENT_PACKAGE = "com.nonpersistent.package";
- private static final String NAMESPACE1 = "namespace1";
- private static final String NAMESPACE2 = "namespace2";
- private static final String NAMESPACE3 = "namespace3";
- private static final String NAMESPACE4 = "namespace4";
private static final String PROP_DEVICE_CONFIG_DISABLE_FLAG =
"persist.device_config.configuration.disable_rescue_party";
private static final String PROP_DISABLE_FACTORY_RESET_FLAG =
@@ -127,22 +98,6 @@ public class RescuePartyTest {
// Mock only sysprop apis
private PackageWatchdog.BootThreshold mSpyBootThreshold;
- @Captor
- private ArgumentCaptor<DeviceConfig.MonitorCallback> mMonitorCallbackCaptor;
- @Captor
- private ArgumentCaptor<List<String>> mPackageListCaptor;
-
- @Parameters(name = "{0}")
- public static List<FlagsParameterization> getFlags() {
- return FlagsParameterization.allCombinationsOf(
- Flags.FLAG_RECOVERABILITY_DETECTION,
- Flags.FLAG_DEPRECATE_FLAGS_AND_SETTINGS_RESETS);
- }
-
- public RescuePartyTest(FlagsParameterization flags) {
- mSetFlagsRule = new SetFlagsRule(flags);
- }
-
@Before
public void setUp() throws Exception {
mSession =
@@ -248,7 +203,6 @@ public class RescuePartyTest {
}
@Test
- @EnableFlags(Flags.FLAG_DEPRECATE_FLAGS_AND_SETTINGS_RESETS)
public void testBootLoopNoFlags() {
// this is old test where the flag needs to be disabled
noteBoot(1);
@@ -260,7 +214,6 @@ public class RescuePartyTest {
}
@Test
- @EnableFlags(Flags.FLAG_DEPRECATE_FLAGS_AND_SETTINGS_RESETS)
public void testPersistentAppCrashNoFlags() {
// this is old test where the flag needs to be disabled
noteAppCrash(1, true);
@@ -396,7 +349,6 @@ public class RescuePartyTest {
}
@Test
- @EnableFlags(Flags.FLAG_DEPRECATE_FLAGS_AND_SETTINGS_RESETS)
public void testHealthCheckLevelsNoFlags() {
// this is old test where the flag needs to be disabled
RescuePartyObserver observer = RescuePartyObserver.getInstance(mMockContext);
@@ -416,7 +368,6 @@ public class RescuePartyTest {
}
@Test
- @EnableFlags(Flags.FLAG_DEPRECATE_FLAGS_AND_SETTINGS_RESETS)
public void testBootLoopLevelsNoFlags() {
RescuePartyObserver observer = RescuePartyObserver.getInstance(mMockContext);
@@ -425,25 +376,6 @@ public class RescuePartyTest {
}
- private void verifySettingsResets(int resetMode, String[] resetNamespaces,
- HashMap<String, Integer> configResetVerifiedTimesMap) {
- verifyOnlySettingsReset(resetMode);
- }
-
- private void verifyOnlySettingsReset(int resetMode) {
- verify(() -> Settings.Global.resetToDefaultsAsUser(mMockContentResolver, null,
- resetMode, UserHandle.USER_SYSTEM));
- verify(() -> Settings.Secure.resetToDefaultsAsUser(eq(mMockContentResolver), isNull(),
- eq(resetMode), anyInt()));
- }
-
- private void verifyNoSettingsReset(int resetMode) {
- verify(() -> Settings.Global.resetToDefaultsAsUser(mMockContentResolver, null,
- resetMode, UserHandle.USER_SYSTEM), never());
- verify(() -> Settings.Secure.resetToDefaultsAsUser(eq(mMockContentResolver), isNull(),
- eq(resetMode), anyInt()), never());
- }
-
private void noteBoot(int mitigationCount) {
RescuePartyObserver.getInstance(mMockContext).onExecuteBootLoopMitigation(mitigationCount);
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/crashrecovery/Android.bp b/services/tests/mockingservicestests/src/com/android/server/crashrecovery/Android.bp
index 8eae9c7d71fa..e030b3f19e4f 100644
--- a/services/tests/mockingservicestests/src/com/android/server/crashrecovery/Android.bp
+++ b/services/tests/mockingservicestests/src/com/android/server/crashrecovery/Android.bp
@@ -32,18 +32,18 @@ android_test {
static_libs: [
"androidx.test.core",
"androidx.test.runner",
+ "flag-junit",
"mockito-target-extended-minus-junit4",
"services.core",
"truth",
- "flag-junit",
] + select(soong_config_variable("ANDROID", "release_crashrecovery_module"), {
"true": ["service-crashrecovery-pre-jarjar"],
default: [],
}),
libs: [
- "android.test.mock.stubs.system",
"android.test.base.stubs.system",
+ "android.test.mock.stubs.system",
"android.test.runner.stubs.system",
],
@@ -55,7 +55,9 @@ android_test {
certificate: "platform",
platform_apis: true,
test_suites: [
- "device-tests",
"automotive-tests",
+ "device-tests",
+ "mts-crashrecovery",
],
+ min_sdk_version: "36",
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/rollback/Android.bp b/services/tests/mockingservicestests/src/com/android/server/rollback/Android.bp
index 5a802d9de2ff..36b064b9b090 100644
--- a/services/tests/mockingservicestests/src/com/android/server/rollback/Android.bp
+++ b/services/tests/mockingservicestests/src/com/android/server/rollback/Android.bp
@@ -30,18 +30,18 @@ android_test {
static_libs: [
"androidx.test.runner",
+ "flag-junit",
"mockito-target-extended-minus-junit4",
"services.core",
"truth",
- "flag-junit",
] + select(soong_config_variable("ANDROID", "release_crashrecovery_module"), {
"true": ["service-crashrecovery-pre-jarjar"],
default: [],
}),
libs: [
- "android.test.mock.stubs.system",
"android.test.base.stubs.system",
+ "android.test.mock.stubs.system",
"android.test.runner.stubs.system",
],
@@ -53,9 +53,11 @@ android_test {
certificate: "platform",
platform_apis: true,
test_suites: [
- "device-tests",
"automotive-tests",
+ "device-tests",
+ "mts-crashrecovery",
],
+ min_sdk_version: "36",
}
test_module_config {
diff --git a/services/tests/mockingservicestests/src/com/android/server/rollback/RollbackPackageHealthObserverTest.java b/services/tests/mockingservicestests/src/com/android/server/rollback/RollbackPackageHealthObserverTest.java
index 347dc81c6734..fb4d81ac831c 100644
--- a/services/tests/mockingservicestests/src/com/android/server/rollback/RollbackPackageHealthObserverTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/rollback/RollbackPackageHealthObserverTest.java
@@ -43,7 +43,6 @@ import android.content.pm.VersionedPackage;
import android.content.rollback.PackageRollbackInfo;
import android.content.rollback.RollbackInfo;
import android.content.rollback.RollbackManager;
-import android.crashrecovery.flags.Flags;
import android.os.Handler;
import android.os.MessageQueue;
import android.os.SystemProperties;
@@ -273,7 +272,6 @@ public class RollbackPackageHealthObserverTest {
@Test
public void healthCheckFailed_impactLevelLow_onePackage()
throws PackageManager.NameNotFoundException {
- mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION);
VersionedPackage appAFrom = new VersionedPackage(APP_A, VERSION_CODE_2);
VersionedPackage appATo = new VersionedPackage(APP_A, VERSION_CODE);
PackageRollbackInfo packageRollbackInfo = new PackageRollbackInfo(appAFrom, appATo,
@@ -304,7 +302,6 @@ public class RollbackPackageHealthObserverTest {
@Test
public void healthCheckFailed_impactLevelHigh_onePackage()
throws PackageManager.NameNotFoundException {
- mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION);
VersionedPackage appAFrom = new VersionedPackage(APP_A, VERSION_CODE_2);
VersionedPackage appATo = new VersionedPackage(APP_A, VERSION_CODE);
PackageRollbackInfo packageRollbackInfo = new PackageRollbackInfo(appAFrom, appATo,
@@ -335,7 +332,6 @@ public class RollbackPackageHealthObserverTest {
@Test
public void healthCheckFailed_impactLevelManualOnly_onePackage()
throws PackageManager.NameNotFoundException {
- mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION);
VersionedPackage appAFrom = new VersionedPackage(APP_A, VERSION_CODE_2);
VersionedPackage appATo = new VersionedPackage(APP_A, VERSION_CODE);
PackageRollbackInfo packageRollbackInfo = new PackageRollbackInfo(appAFrom, appATo,
@@ -365,7 +361,6 @@ public class RollbackPackageHealthObserverTest {
@Test
public void healthCheckFailed_impactLevelLowAndHigh_onePackage()
throws PackageManager.NameNotFoundException {
- mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION);
VersionedPackage appAFrom = new VersionedPackage(APP_A, VERSION_CODE_2);
VersionedPackage appATo = new VersionedPackage(APP_A, VERSION_CODE);
PackageRollbackInfo packageRollbackInfo = new PackageRollbackInfo(appAFrom, appATo,
@@ -404,7 +399,6 @@ public class RollbackPackageHealthObserverTest {
@Test
public void execute_impactLevelLow_nativeCrash_rollback()
throws PackageManager.NameNotFoundException {
- mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION);
int rollbackId = 1;
VersionedPackage appAFrom = new VersionedPackage(APP_A, VERSION_CODE_2);
VersionedPackage appATo = new VersionedPackage(APP_A, VERSION_CODE);
@@ -438,7 +432,6 @@ public class RollbackPackageHealthObserverTest {
@Test
public void execute_impactLevelLow_rollbackFailedPackage()
throws PackageManager.NameNotFoundException {
- mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION);
int rollbackId1 = 1;
VersionedPackage appAFrom = new VersionedPackage(APP_A, VERSION_CODE_2);
VersionedPackage appATo = new VersionedPackage(APP_A, VERSION_CODE);
@@ -483,7 +476,6 @@ public class RollbackPackageHealthObserverTest {
@Test
public void execute_impactLevelLow_rollbackAll()
throws PackageManager.NameNotFoundException {
- mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION);
int rollbackId1 = 1;
VersionedPackage appAFrom = new VersionedPackage(APP_A, VERSION_CODE_2);
VersionedPackage appATo = new VersionedPackage(APP_A, VERSION_CODE);
@@ -530,7 +522,6 @@ public class RollbackPackageHealthObserverTest {
@Test
public void execute_impactLevelLowAndHigh_rollbackLow()
throws PackageManager.NameNotFoundException {
- mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION);
int rollbackId1 = 1;
VersionedPackage appAFrom = new VersionedPackage(APP_A, VERSION_CODE_2);
VersionedPackage appATo = new VersionedPackage(APP_A, VERSION_CODE);
@@ -578,7 +569,6 @@ public class RollbackPackageHealthObserverTest {
@Test
public void execute_impactLevelHigh_rollbackHigh()
throws PackageManager.NameNotFoundException {
- mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION);
int rollbackId2 = 2;
VersionedPackage appBFrom = new VersionedPackage(APP_B, VERSION_CODE_2);
VersionedPackage appBTo = new VersionedPackage(APP_B, VERSION_CODE);
@@ -612,7 +602,6 @@ public class RollbackPackageHealthObserverTest {
*/
@Test
public void onBootLoop_impactLevelLow_onePackage() throws PackageManager.NameNotFoundException {
- mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION);
VersionedPackage appAFrom = new VersionedPackage(APP_A, VERSION_CODE_2);
VersionedPackage appATo = new VersionedPackage(APP_A, VERSION_CODE);
PackageRollbackInfo packageRollbackInfo = new PackageRollbackInfo(appAFrom, appATo,
@@ -637,7 +626,6 @@ public class RollbackPackageHealthObserverTest {
@Test
public void onBootLoop_impactLevelHigh_onePackage()
throws PackageManager.NameNotFoundException {
- mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION);
VersionedPackage appAFrom = new VersionedPackage(APP_A, VERSION_CODE_2);
VersionedPackage appATo = new VersionedPackage(APP_A, VERSION_CODE);
PackageRollbackInfo packageRollbackInfo = new PackageRollbackInfo(appAFrom, appATo,
@@ -662,7 +650,6 @@ public class RollbackPackageHealthObserverTest {
@Test
public void onBootLoop_impactLevelHighDisableHighImpactRollback_onePackage()
throws PackageManager.NameNotFoundException {
- mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION);
SystemProperties.set(PROP_DISABLE_HIGH_IMPACT_ROLLBACK_FLAG, Boolean.toString(true));
VersionedPackage appAFrom = new VersionedPackage(APP_A, VERSION_CODE_2);
VersionedPackage appATo = new VersionedPackage(APP_A, VERSION_CODE);
@@ -692,7 +679,6 @@ public class RollbackPackageHealthObserverTest {
@Test
public void onBootLoop_impactLevelManualOnly_onePackage()
throws PackageManager.NameNotFoundException {
- mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION);
VersionedPackage appAFrom = new VersionedPackage(APP_A, VERSION_CODE_2);
VersionedPackage appATo = new VersionedPackage(APP_A, VERSION_CODE);
PackageRollbackInfo packageRollbackInfo = new PackageRollbackInfo(appAFrom, appATo,
@@ -720,7 +706,6 @@ public class RollbackPackageHealthObserverTest {
@Test
public void onBootLoop_impactLevelLowAndHigh_onePackage()
throws PackageManager.NameNotFoundException {
- mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION);
VersionedPackage appAFrom = new VersionedPackage(APP_A, VERSION_CODE_2);
VersionedPackage appATo = new VersionedPackage(APP_A, VERSION_CODE);
PackageRollbackInfo packageRollbackInfo = new PackageRollbackInfo(appAFrom, appATo,
@@ -757,7 +742,6 @@ public class RollbackPackageHealthObserverTest {
@Test
public void executeBootLoopMitigation_impactLevelLow_rollbackAll()
throws PackageManager.NameNotFoundException {
- mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION);
int rollbackId1 = 1;
VersionedPackage appAFrom = new VersionedPackage(APP_A, VERSION_CODE_2);
VersionedPackage appATo = new VersionedPackage(APP_A, VERSION_CODE);
@@ -802,7 +786,6 @@ public class RollbackPackageHealthObserverTest {
@Test
public void executeBootLoopMitigation_impactLevelLowAndHigh_rollbackLow()
throws PackageManager.NameNotFoundException {
- mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION);
int rollbackId1 = 1;
VersionedPackage appAFrom = new VersionedPackage(APP_A, VERSION_CODE_2);
VersionedPackage appATo = new VersionedPackage(APP_A, VERSION_CODE);
@@ -847,7 +830,6 @@ public class RollbackPackageHealthObserverTest {
@Test
public void executeBootLoopMitigation_impactLevelHigh_rollbackHigh()
throws PackageManager.NameNotFoundException {
- mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION);
int rollbackId2 = 2;
VersionedPackage appBFrom = new VersionedPackage(APP_B, VERSION_CODE_2);
VersionedPackage appBTo = new VersionedPackage(APP_B, VERSION_CODE);
@@ -882,7 +864,6 @@ public class RollbackPackageHealthObserverTest {
@Test
public void execute_impactLevelLowAndManual_rollbackLowImpactOnly()
throws PackageManager.NameNotFoundException, InterruptedException {
- mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION);
int rollbackId1 = 1;
VersionedPackage appAFrom = new VersionedPackage(APP_A, VERSION_CODE_2);
VersionedPackage appATo = new VersionedPackage(APP_A, VERSION_CODE);
@@ -928,7 +909,6 @@ public class RollbackPackageHealthObserverTest {
@Test
public void execute_impactLevelManual_rollbackLowImpactOnly()
throws PackageManager.NameNotFoundException {
- mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION);
int rollbackId1 = 1;
VersionedPackage appAFrom = new VersionedPackage(APP_A, VERSION_CODE_2);
VersionedPackage appATo = new VersionedPackage(APP_A, VERSION_CODE);
@@ -962,7 +942,6 @@ public class RollbackPackageHealthObserverTest {
@Test
public void executeBootLoopMitigation_impactLevelHighMultiplePackage_rollbackHigh()
throws PackageManager.NameNotFoundException {
- mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION);
int rollbackId1 = 1;
VersionedPackage appBFrom = new VersionedPackage(APP_B, VERSION_CODE_2);
VersionedPackage appBTo = new VersionedPackage(APP_B, VERSION_CODE);
@@ -1008,7 +987,6 @@ public class RollbackPackageHealthObserverTest {
@Test
public void executeBootLoopMitigation_impactLevelHighKillSwitchTrue_rollbackHigh()
throws PackageManager.NameNotFoundException {
- mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION);
SystemProperties.set(PROP_DISABLE_HIGH_IMPACT_ROLLBACK_FLAG, Boolean.toString(true));
int rollbackId1 = 1;
VersionedPackage appBFrom = new VersionedPackage(APP_B, VERSION_CODE_2);
diff --git a/services/tests/powerservicetests/src/com/android/server/power/NotifierTest.java b/services/tests/powerservicetests/src/com/android/server/power/NotifierTest.java
index 83a390d7f70b..4e56422ec391 100644
--- a/services/tests/powerservicetests/src/com/android/server/power/NotifierTest.java
+++ b/services/tests/powerservicetests/src/com/android/server/power/NotifierTest.java
@@ -437,6 +437,42 @@ public class NotifierTest {
}
@Test
+ public void testOnGroupChanged_perDisplayWakeByTouchEnabled() {
+ createNotifier();
+ // GIVEN per-display wake by touch is enabled and one display group has been defined with
+ // two displays
+ when(mPowerManagerFlags.isPerDisplayWakeByTouchEnabled()).thenReturn(true);
+ final int groupId = 121;
+ final int displayId1 = 1221;
+ final int displayId2 = 1222;
+ final int[] displays = new int[]{displayId1, displayId2};
+ when(mDisplayManagerInternal.getDisplayIds()).thenReturn(IntArray.wrap(displays));
+ when(mDisplayManagerInternal.getDisplayIdsForGroup(groupId)).thenReturn(displays);
+ SparseArray<int[]> displayIdsByGroupId = new SparseArray<>();
+ displayIdsByGroupId.put(groupId, displays);
+ when(mDisplayManagerInternal.getDisplayIdsByGroupsIds()).thenReturn(displayIdsByGroupId);
+ mNotifier.onGroupWakefulnessChangeStarted(
+ groupId, WAKEFULNESS_AWAKE, PowerManager.WAKE_REASON_TAP, /* eventTime= */ 1000);
+ final SparseBooleanArray expectedDisplayInteractivities = new SparseBooleanArray();
+ expectedDisplayInteractivities.put(displayId1, true);
+ expectedDisplayInteractivities.put(displayId2, true);
+ verify(mInputManagerInternal).setDisplayInteractivities(expectedDisplayInteractivities);
+
+ // WHEN display group is changed to only contain one display
+ SparseArray<int[]> newDisplayIdsByGroupId = new SparseArray<>();
+ newDisplayIdsByGroupId.put(groupId, new int[]{displayId1});
+ when(mDisplayManagerInternal.getDisplayIdsByGroupsIds()).thenReturn(newDisplayIdsByGroupId);
+ mNotifier.onGroupChanged();
+
+ // THEN native input manager is informed that the displays in the group have changed
+ final SparseBooleanArray expectedDisplayInteractivitiesAfterChange =
+ new SparseBooleanArray();
+ expectedDisplayInteractivitiesAfterChange.put(displayId1, true);
+ verify(mInputManagerInternal).setDisplayInteractivities(
+ expectedDisplayInteractivitiesAfterChange);
+ }
+
+ @Test
public void testOnWakeLockReleased_FrameworkStatsLogged_NoChains() {
when(mPowerManagerFlags.isMoveWscLoggingToNotifierEnabled()).thenReturn(true);
createNotifier();
diff --git a/services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java b/services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java
index 29a17e1c85ab..ff6796561926 100644
--- a/services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java
+++ b/services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java
@@ -2501,6 +2501,49 @@ public class PowerManagerServiceTest {
}
@Test
+ public void testMultiDisplay_twoDisplays_onlyDefaultDisplayCanDream() {
+ final int nonDefaultDisplayGroupId = Display.DEFAULT_DISPLAY_GROUP + 1;
+ final int nonDefaultDisplay = Display.DEFAULT_DISPLAY + 1;
+ final AtomicReference<DisplayManagerInternal.DisplayGroupListener> listener =
+ new AtomicReference<>();
+ doAnswer((Answer<Void>) invocation -> {
+ listener.set(invocation.getArgument(0));
+ return null;
+ }).when(mDisplayManagerInternalMock).registerDisplayGroupListener(any());
+ final DisplayInfo info = new DisplayInfo();
+ info.displayGroupId = nonDefaultDisplayGroupId;
+ when(mDisplayManagerInternalMock.getDisplayInfo(nonDefaultDisplay)).thenReturn(info);
+ when(mBatteryManagerInternalMock.isPowered(anyInt())).thenReturn(true);
+ Settings.Secure.putInt(mContextSpy.getContentResolver(),
+ Settings.Secure.SCREENSAVER_ACTIVATE_ON_SLEEP, 1);
+ doAnswer(inv -> {
+ when(mDreamManagerInternalMock.isDreaming()).thenReturn(true);
+ return null;
+ }).when(mDreamManagerInternalMock).startDream(anyBoolean(), anyString());
+
+ setMinimumScreenOffTimeoutConfig(5);
+ createService();
+ startSystem();
+
+ listener.get().onDisplayGroupAdded(nonDefaultDisplayGroupId);
+
+ assertThat(mService.getWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP)).isEqualTo(
+ WAKEFULNESS_AWAKE);
+ assertThat(mService.getWakefulnessLocked(nonDefaultDisplayGroupId)).isEqualTo(
+ WAKEFULNESS_AWAKE);
+ assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+
+ advanceTime(15000);
+
+ // Only the default display group is dreaming.
+ assertThat(mService.getWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP)).isEqualTo(
+ WAKEFULNESS_DREAMING);
+ assertThat(mService.getWakefulnessLocked(nonDefaultDisplayGroupId)).isEqualTo(
+ WAKEFULNESS_DOZING);
+ assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_DREAMING);
+ }
+
+ @Test
public void testMultiDisplay_addNewDisplay_becomeGloballyAwakeButDefaultRemainsDozing() {
final int nonDefaultDisplayGroupId = Display.DEFAULT_DISPLAY_GROUP + 1;
final int nonDefaultDisplay = Display.DEFAULT_DISPLAY + 1;
diff --git a/services/tests/powerstatstests/res/raw/battery-history.zip b/services/tests/powerstatstests/res/raw/battery-history.zip
new file mode 100644
index 000000000000..ed82ac0f79cc
--- /dev/null
+++ b/services/tests/powerstatstests/res/raw/battery-history.zip
Binary files differ
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsProviderPerfTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsProviderPerfTest.java
new file mode 100644
index 000000000000..8fc8c9f677a6
--- /dev/null
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsProviderPerfTest.java
@@ -0,0 +1,179 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.power.stats;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.os.BatteryConsumer;
+import android.os.BatteryUsageStats;
+import android.os.BatteryUsageStatsQuery;
+import android.os.ConditionVariable;
+import android.os.FileUtils;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.perftests.utils.BenchmarkState;
+import android.perftests.utils.PerfStatusReporter;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.LargeTest;
+
+import com.android.internal.os.Clock;
+import com.android.internal.os.CpuScalingPolicies;
+import com.android.internal.os.CpuScalingPolicyReader;
+import com.android.internal.os.MonotonicClock;
+import com.android.internal.os.PowerProfile;
+import com.android.server.power.stats.processor.MultiStatePowerAttributor;
+
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.file.Files;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
+
+@RunWith(AndroidJUnit4.class)
+@LargeTest
+@android.platform.test.annotations.DisabledOnRavenwood(reason = "Performance test")
+@Ignore("Performance experiment. Comment out @Ignore to run")
+public class BatteryUsageStatsProviderPerfTest {
+ @Rule
+ public final PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
+
+ private final Clock mClock = new MockClock();
+ private MonotonicClock mMonotonicClock;
+ private PowerProfile mPowerProfile;
+ private CpuScalingPolicies mCpuScalingPolicies;
+ private File mDirectory;
+ private Handler mHandler;
+ private MockBatteryStatsImpl mBatteryStats;
+
+ @Before
+ public void setup() throws Exception {
+ Context context = InstrumentationRegistry.getContext();
+ mPowerProfile = new PowerProfile(context);
+ mCpuScalingPolicies = new CpuScalingPolicyReader().read();
+
+ HandlerThread mHandlerThread = new HandlerThread("batterystats-handler");
+ mHandlerThread.start();
+ mHandler = new Handler(mHandlerThread.getLooper());
+
+ // Extract accumulated battery history to ensure consistent iterations
+ mDirectory = Files.createTempDirectory("BatteryUsageStatsProviderPerfTest").toFile();
+ File historyDirectory = new File(mDirectory, "battery-history");
+ historyDirectory.mkdir();
+
+ long maxMonotonicTime = 0;
+
+ // To recreate battery-history.zip if necessary, perform these commands:
+ // cd /tmp
+ // mkdir battery-history
+ // adb pull /data/system/battery-history
+ // zip battery-history.zip battery-history/*
+ // cp battery-history.zip \
+ // $ANDROID_BUILD_TOP/frameworks/base/services/tests/powerstatstests/res/raw
+ Resources resources = context.getResources();
+ int resId = resources.getIdentifier("battery-history", "raw", context.getPackageName());
+ try (InputStream in = resources.openRawResource(resId)) {
+ try (ZipInputStream zis = new ZipInputStream(in)) {
+ ZipEntry ze;
+ while ((ze = zis.getNextEntry()) != null) {
+ if (!ze.getName().endsWith(".bh")) {
+ continue;
+ }
+ File file = new File(mDirectory, ze.getName());
+ try (OutputStream out = new FileOutputStream(
+ file)) {
+ FileUtils.copy(zis, out);
+ }
+ long timestamp = Long.parseLong(file.getName().replace(".bh", ""));
+ if (timestamp > maxMonotonicTime) {
+ maxMonotonicTime = timestamp;
+ }
+ }
+ }
+ }
+
+ mMonotonicClock = new MonotonicClock(maxMonotonicTime + 1000000000, mClock);
+ mBatteryStats = new MockBatteryStatsImpl(mClock, mDirectory);
+ }
+
+ @Test
+ public void getBatteryUsageStats_accumulated() {
+ BatteryUsageStatsQuery query = new BatteryUsageStatsQuery.Builder()
+ .setMaxStatsAgeMs(0)
+ .includePowerStateData()
+ .includeScreenStateData()
+ .includeProcessStateData()
+ .accumulated()
+ .build();
+
+ double expectedCpuPower = 0;
+ BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ while (state.keepRunning()) {
+ state.pauseTiming();
+
+ waitForBackgroundThread();
+
+ BatteryUsageStatsProvider provider = createBatteryUsageStatsProvider();
+ state.resumeTiming();
+
+ BatteryUsageStats stats = provider.getBatteryUsageStats(mBatteryStats, query);
+ waitForBackgroundThread();
+
+ state.pauseTiming();
+
+ double cpuConsumedPower = stats.getAggregateBatteryConsumer(
+ BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE)
+ .getConsumedPower(BatteryConsumer.POWER_COMPONENT_CPU);
+ assertThat(cpuConsumedPower).isNonZero();
+ if (expectedCpuPower == 0) {
+ expectedCpuPower = cpuConsumedPower;
+ } else {
+ // Verify that all iterations produce the same result
+ assertThat(cpuConsumedPower).isEqualTo(expectedCpuPower);
+ }
+ state.resumeTiming();
+ }
+ }
+
+ private BatteryUsageStatsProvider createBatteryUsageStatsProvider() {
+ Context context = InstrumentationRegistry.getContext();
+
+ PowerStatsStore store = new PowerStatsStore(mDirectory, mHandler);
+ store.reset();
+
+ MultiStatePowerAttributor powerAttributor = new MultiStatePowerAttributor(context, store,
+ mPowerProfile, mCpuScalingPolicies, mPowerProfile::getBatteryCapacity);
+ return new BatteryUsageStatsProvider(context, powerAttributor, mPowerProfile,
+ mCpuScalingPolicies, store, 10000000, mClock, mMonotonicClock);
+ }
+
+ private void waitForBackgroundThread() {
+ ConditionVariable done = new ConditionVariable();
+ mHandler.post(done::open);
+ done.block();
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/audio/AudioDeviceInventoryTest.java b/services/tests/servicestests/src/com/android/server/audio/AudioDeviceInventoryTest.java
index b5a538fa09f8..c7da27420cbb 100644
--- a/services/tests/servicestests/src/com/android/server/audio/AudioDeviceInventoryTest.java
+++ b/services/tests/servicestests/src/com/android/server/audio/AudioDeviceInventoryTest.java
@@ -103,7 +103,7 @@ public class AudioDeviceInventoryTest {
// NOTE: for now this is only when flag asDeviceConnectionFailure is true
if (asDeviceConnectionFailure()) {
when(mSpyAudioSystem.setDeviceConnectionState(ada, AudioSystem.DEVICE_STATE_AVAILABLE,
- AudioSystem.AUDIO_FORMAT_DEFAULT))
+ AudioSystem.AUDIO_FORMAT_DEFAULT, false /*deviceSwitch*/))
.thenReturn(AudioSystem.AUDIO_STATUS_ERROR);
runWithBluetoothPrivilegedPermission(
() -> mDevInventory.onSetBtActiveDevice(/*btInfo*/ btInfo,
@@ -115,7 +115,7 @@ public class AudioDeviceInventoryTest {
// test that the device is added when AudioSystem returns AUDIO_STATUS_OK
// when setDeviceConnectionState is called for the connection
when(mSpyAudioSystem.setDeviceConnectionState(ada, AudioSystem.DEVICE_STATE_AVAILABLE,
- AudioSystem.AUDIO_FORMAT_DEFAULT))
+ AudioSystem.AUDIO_FORMAT_DEFAULT, false /*deviceSwitch*/))
.thenReturn(AudioSystem.AUDIO_STATUS_OK);
runWithBluetoothPrivilegedPermission(
() -> mDevInventory.onSetBtActiveDevice(/*btInfo*/ btInfo,
diff --git a/services/tests/servicestests/src/com/android/server/audio/NoOpAudioSystemAdapter.java b/services/tests/servicestests/src/com/android/server/audio/NoOpAudioSystemAdapter.java
index ce59a86c6ca3..39e7d727f7c5 100644
--- a/services/tests/servicestests/src/com/android/server/audio/NoOpAudioSystemAdapter.java
+++ b/services/tests/servicestests/src/com/android/server/audio/NoOpAudioSystemAdapter.java
@@ -51,9 +51,9 @@ public class NoOpAudioSystemAdapter extends AudioSystemAdapter {
// Overrides of AudioSystemAdapter
@Override
public int setDeviceConnectionState(AudioDeviceAttributes attributes, int state,
- int codecFormat) {
- Log.i(TAG, String.format("setDeviceConnectionState(0x%s, %d, 0x%s",
- attributes.toString(), state, Integer.toHexString(codecFormat)));
+ int codecFormat, boolean deviceSwitch) {
+ Log.i(TAG, String.format("setDeviceConnectionState(0x%s, %d, 0x%s %b",
+ attributes.toString(), state, Integer.toHexString(codecFormat), deviceSwitch));
return AudioSystem.AUDIO_STATUS_OK;
}
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index 30aa8cebdff6..e0023e59af50 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -1768,6 +1768,7 @@ public class DevicePolicyManagerTest extends DpmTestBase {
}
@Test
+ @Ignore // b/396073342
public void testCertificateDisclosure() throws Exception {
final int userId = CALLER_USER_HANDLE;
final UserHandle user = UserHandle.of(userId);
@@ -4612,6 +4613,7 @@ public class DevicePolicyManagerTest extends DpmTestBase {
}
@Test
+ @Ignore // b/396073342
public void testGetLastBugReportRequestTime() throws Exception {
mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
setupDeviceOwner();
@@ -4659,6 +4661,7 @@ public class DevicePolicyManagerTest extends DpmTestBase {
}
@Test
+ @Ignore // b/396073342
public void testGetLastNetworkLogRetrievalTime() throws Exception {
mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
setupDeviceOwner();
@@ -6441,6 +6444,7 @@ public class DevicePolicyManagerTest extends DpmTestBase {
}
@Test
+ @Ignore // b/396073342
public void testGetOwnerInstalledCaCertsForDeviceOwner() throws Exception {
mServiceContext.packageName = mRealTestContext.getPackageName();
mServiceContext.applicationInfo = new ApplicationInfo();
@@ -6452,6 +6456,7 @@ public class DevicePolicyManagerTest extends DpmTestBase {
}
@Test
+ @Ignore // b/396073342
public void testGetOwnerInstalledCaCertsForProfileOwner() throws Exception {
mServiceContext.packageName = mRealTestContext.getPackageName();
mServiceContext.applicationInfo = new ApplicationInfo();
@@ -6464,6 +6469,7 @@ public class DevicePolicyManagerTest extends DpmTestBase {
}
@Test
+ @Ignore // b/396073342
public void testGetOwnerInstalledCaCertsForDelegate() throws Exception {
mServiceContext.packageName = mRealTestContext.getPackageName();
mServiceContext.applicationInfo = new ApplicationInfo();
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/BaseAbsoluteVolumeBehaviorTest.java b/services/tests/servicestests/src/com/android/server/hdmi/BaseAbsoluteVolumeBehaviorTest.java
index fca0cfbc7d2f..cf2c15c5daca 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/BaseAbsoluteVolumeBehaviorTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/BaseAbsoluteVolumeBehaviorTest.java
@@ -432,7 +432,7 @@ public abstract class BaseAbsoluteVolumeBehaviorTest {
.setMaxVolumeIndex(AudioStatus.MAX_VOLUME)
.setMinVolumeIndex(AudioStatus.MIN_VOLUME)
.build()),
- any(), any(), anyBoolean());
+ anyBoolean(), any(), any());
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/BaseTvToAudioSystemAvbTest.java b/services/tests/servicestests/src/com/android/server/hdmi/BaseTvToAudioSystemAvbTest.java
index ec44a918f8e8..f44517a47f55 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/BaseTvToAudioSystemAvbTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/BaseTvToAudioSystemAvbTest.java
@@ -112,7 +112,7 @@ public abstract class BaseTvToAudioSystemAvbTest extends BaseAbsoluteVolumeBehav
.setMaxVolumeIndex(AudioStatus.MAX_VOLUME)
.setMinVolumeIndex(AudioStatus.MIN_VOLUME)
.build()),
- any(), any(), anyBoolean());
+ anyBoolean(), any(), any());
}
@@ -135,7 +135,7 @@ public abstract class BaseTvToAudioSystemAvbTest extends BaseAbsoluteVolumeBehav
.setMaxVolumeIndex(AudioStatus.MAX_VOLUME)
.setMinVolumeIndex(AudioStatus.MIN_VOLUME)
.build()),
- any(), any(), anyBoolean());
+ anyBoolean(), any(), any());
}
@Test
@@ -160,7 +160,7 @@ public abstract class BaseTvToAudioSystemAvbTest extends BaseAbsoluteVolumeBehav
.setMaxVolumeIndex(AudioStatus.MAX_VOLUME)
.setMinVolumeIndex(AudioStatus.MIN_VOLUME)
.build()),
- any(), any(), anyBoolean());
+ anyBoolean(), any(), any());
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/FakeAudioFramework.java b/services/tests/servicestests/src/com/android/server/hdmi/FakeAudioFramework.java
index 7294ba62cdae..90f94cb4b596 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/FakeAudioFramework.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/FakeAudioFramework.java
@@ -183,9 +183,9 @@ public class FakeAudioFramework {
public void setDeviceAbsoluteVolumeBehavior(
@NonNull AudioDeviceAttributes device,
@NonNull VolumeInfo volume,
+ boolean handlesVolumeAdjustment,
@NonNull @CallbackExecutor Executor executor,
- @NonNull OnAudioDeviceVolumeChangedListener vclistener,
- boolean handlesVolumeAdjustment) {
+ @NonNull OnAudioDeviceVolumeChangedListener vclistener) {
setVolumeBehaviorHelper(device, AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE);
}
@@ -193,9 +193,9 @@ public class FakeAudioFramework {
public void setDeviceAbsoluteVolumeAdjustOnlyBehavior(
@NonNull AudioDeviceAttributes device,
@NonNull VolumeInfo volume,
+ boolean handlesVolumeAdjustment,
@NonNull @CallbackExecutor Executor executor,
- @NonNull OnAudioDeviceVolumeChangedListener vclistener,
- boolean handlesVolumeAdjustment) {
+ @NonNull OnAudioDeviceVolumeChangedListener vclistener) {
setVolumeBehaviorHelper(device,
AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_ADJUST_ONLY);
}
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
index f74e2ace7ae3..563baacf5811 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
@@ -66,6 +66,7 @@ import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
import org.junit.Before;
+import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
@@ -1033,6 +1034,7 @@ public class HdmiCecLocalDeviceTvTest {
}
@Test
+ @Ignore("b/360768278")
public void onHotplug_doNotSend_systemAudioModeRequestWithParameter(){
// Add a device to the network and assert that this device is included in the list of
// devices.
diff --git a/services/tests/uiservicestests/Android.bp b/services/tests/uiservicestests/Android.bp
index 0eb20eb22380..66d7611a29c6 100644
--- a/services/tests/uiservicestests/Android.bp
+++ b/services/tests/uiservicestests/Android.bp
@@ -32,6 +32,7 @@ android_test {
"androidx.test.rules",
"hamcrest-library",
"mockito-target-inline-minus-junit4",
+ "mockito-target-extended",
"platform-compat-test-rules",
"platform-test-annotations",
"platformprotosnano",
diff --git a/services/tests/uiservicestests/src/android/app/NotificationManagerZenTest.java b/services/tests/uiservicestests/src/android/app/NotificationManagerZenTest.java
index 779fa1aa2f72..dbbe40fd42e6 100644
--- a/services/tests/uiservicestests/src/android/app/NotificationManagerZenTest.java
+++ b/services/tests/uiservicestests/src/android/app/NotificationManagerZenTest.java
@@ -80,7 +80,7 @@ public class NotificationManagerZenTest {
}
@Test
- @RequiresFlagsEnabled({Flags.FLAG_MODES_API, Flags.FLAG_MODES_UI})
+ @RequiresFlagsEnabled(Flags.FLAG_MODES_UI)
public void setAutomaticZenRuleState_manualActivation() {
AutomaticZenRule ruleToCreate = createZenRule("rule");
String ruleId = mNotificationManager.addAutomaticZenRule(ruleToCreate);
@@ -111,7 +111,7 @@ public class NotificationManagerZenTest {
}
@Test
- @RequiresFlagsEnabled({Flags.FLAG_MODES_API, Flags.FLAG_MODES_UI})
+ @RequiresFlagsEnabled(Flags.FLAG_MODES_UI)
public void setAutomaticZenRuleState_manualDeactivation() {
AutomaticZenRule ruleToCreate = createZenRule("rule");
String ruleId = mNotificationManager.addAutomaticZenRule(ruleToCreate);
@@ -145,7 +145,7 @@ public class NotificationManagerZenTest {
}
@Test
- @RequiresFlagsEnabled({Flags.FLAG_MODES_API, Flags.FLAG_MODES_UI})
+ @RequiresFlagsEnabled(Flags.FLAG_MODES_UI)
public void setAutomaticZenRuleState_respectsManuallyActivated() {
AutomaticZenRule ruleToCreate = createZenRule("rule");
String ruleId = mNotificationManager.addAutomaticZenRule(ruleToCreate);
@@ -178,7 +178,7 @@ public class NotificationManagerZenTest {
}
@Test
- @RequiresFlagsEnabled({Flags.FLAG_MODES_API, Flags.FLAG_MODES_UI})
+ @RequiresFlagsEnabled(Flags.FLAG_MODES_UI)
public void setAutomaticZenRuleState_respectsManuallyDeactivated() {
AutomaticZenRule ruleToCreate = createZenRule("rule");
String ruleId = mNotificationManager.addAutomaticZenRule(ruleToCreate);
@@ -212,7 +212,7 @@ public class NotificationManagerZenTest {
}
@Test
- @RequiresFlagsEnabled({Flags.FLAG_MODES_API, Flags.FLAG_MODES_UI})
+ @RequiresFlagsEnabled(Flags.FLAG_MODES_UI)
public void setAutomaticZenRuleState_manualActivationFromApp() {
AutomaticZenRule ruleToCreate = createZenRule("rule");
String ruleId = mNotificationManager.addAutomaticZenRule(ruleToCreate);
@@ -244,7 +244,7 @@ public class NotificationManagerZenTest {
}
@Test
- @RequiresFlagsEnabled({Flags.FLAG_MODES_API, Flags.FLAG_MODES_UI})
+ @RequiresFlagsEnabled(Flags.FLAG_MODES_UI)
public void setAutomaticZenRuleState_manualDeactivationFromApp() {
AutomaticZenRule ruleToCreate = createZenRule("rule");
String ruleId = mNotificationManager.addAutomaticZenRule(ruleToCreate);
diff --git a/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java
index c4b8599a483c..9930c9f07ed8 100644
--- a/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java
@@ -70,7 +70,6 @@ import android.Manifest;
import android.app.Activity;
import android.app.ActivityManager;
import android.app.AlarmManager;
-import android.app.Flags;
import android.app.IOnProjectionStateChangedListener;
import android.app.IUiModeManager;
import android.content.BroadcastReceiver;
@@ -91,7 +90,6 @@ import android.os.Process;
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.test.FakePermissionEnforcer;
-import android.platform.test.annotations.EnableFlags;
import android.platform.test.flag.junit.SetFlagsRule;
import android.provider.Settings;
import android.service.dreams.DreamManagerInternal;
@@ -1508,13 +1506,11 @@ public class UiModeManagerServiceTest extends UiServiceTestCase {
}
@Test
- @EnableFlags(Flags.FLAG_MODES_API)
public void testAttentionModeThemeOverlay_nightModeDisabled() throws RemoteException {
testAttentionModeThemeOverlay(false);
}
@Test
- @EnableFlags(Flags.FLAG_MODES_API)
public void testAttentionModeThemeOverlay_nightModeEnabled() throws RemoteException {
testAttentionModeThemeOverlay(true);
}
diff --git a/services/tests/uiservicestests/src/com/android/server/UiServiceTestCase.java b/services/tests/uiservicestests/src/com/android/server/UiServiceTestCase.java
index b3ec2153542a..c9d5241c57b7 100644
--- a/services/tests/uiservicestests/src/com/android/server/UiServiceTestCase.java
+++ b/services/tests/uiservicestests/src/com/android/server/UiServiceTestCase.java
@@ -30,6 +30,7 @@ import android.testing.TestableContext;
import androidx.test.InstrumentationRegistry;
+import com.android.server.pm.UserManagerInternal;
import com.android.server.uri.UriGrantsManagerInternal;
import org.junit.After;
@@ -41,6 +42,7 @@ import org.mockito.MockitoAnnotations;
public class UiServiceTestCase {
@Mock protected PackageManagerInternal mPmi;
+ @Mock protected UserManagerInternal mUmi;
@Mock protected UriGrantsManagerInternal mUgmInternal;
protected static final String PKG_N_MR1 = "com.example.n_mr1";
@@ -92,6 +94,8 @@ public class UiServiceTestCase {
}
});
+ LocalServices.removeServiceForTest(UserManagerInternal.class);
+ LocalServices.addService(UserManagerInternal.class, mUmi);
LocalServices.removeServiceForTest(UriGrantsManagerInternal.class);
LocalServices.addService(UriGrantsManagerInternal.class, mUgmInternal);
when(mUgmInternal.checkGrantUriPermission(
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/DefaultDeviceEffectsApplierTest.java b/services/tests/uiservicestests/src/com/android/server/notification/DefaultDeviceEffectsApplierTest.java
index 1890879da69d..5ce9a3e8d4d4 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/DefaultDeviceEffectsApplierTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/DefaultDeviceEffectsApplierTest.java
@@ -47,7 +47,6 @@ import android.content.Intent;
import android.content.IntentFilter;
import android.hardware.display.ColorDisplayManager;
import android.os.PowerManager;
-import android.platform.test.annotations.EnableFlags;
import android.platform.test.flag.junit.SetFlagsRule;
import android.service.notification.ZenDeviceEffects;
import android.testing.TestableContext;
@@ -102,8 +101,6 @@ public class DefaultDeviceEffectsApplierTest {
@Test
public void apply_appliesEffects() {
- mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
-
ZenDeviceEffects effects = new ZenDeviceEffects.Builder()
.setShouldSuppressAmbientDisplay(true)
.setShouldDimWallpaper(true)
@@ -119,7 +116,6 @@ public class DefaultDeviceEffectsApplierTest {
}
@Test
- @EnableFlags(android.app.Flags.FLAG_MODES_API)
public void apply_logsToZenLog() {
when(mPowerManager.isInteractive()).thenReturn(true);
ArgumentCaptor<BroadcastReceiver> broadcastReceiverCaptor =
@@ -155,8 +151,6 @@ public class DefaultDeviceEffectsApplierTest {
@Test
public void apply_removesEffects() {
- mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
-
ZenDeviceEffects previousEffects = new ZenDeviceEffects.Builder()
.setShouldSuppressAmbientDisplay(true)
.setShouldDimWallpaper(true)
@@ -180,8 +174,6 @@ public class DefaultDeviceEffectsApplierTest {
@Test
public void apply_removesOnlyPreviouslyAppliedEffects() {
- mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
-
ZenDeviceEffects previousEffects = new ZenDeviceEffects.Builder()
.setShouldSuppressAmbientDisplay(true)
.build();
@@ -197,7 +189,6 @@ public class DefaultDeviceEffectsApplierTest {
@Test
public void apply_missingSomeServices_okay() {
- mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
mContext.addMockSystemService(ColorDisplayManager.class, null);
mContext.addMockSystemService(WallpaperManager.class, null);
mApplier = new DefaultDeviceEffectsApplier(mContext);
@@ -216,7 +207,6 @@ public class DefaultDeviceEffectsApplierTest {
@Test
public void apply_disabledWallpaperService_dimWallpaperNotApplied() {
- mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
WallpaperManager disabledWallpaperService = mock(WallpaperManager.class);
when(mWallpaperManager.isWallpaperSupported()).thenReturn(false);
mContext.addMockSystemService(WallpaperManager.class, disabledWallpaperService);
@@ -236,8 +226,6 @@ public class DefaultDeviceEffectsApplierTest {
@Test
public void apply_someEffects_onlyThoseEffectsApplied() {
- mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
-
ZenDeviceEffects effects = new ZenDeviceEffects.Builder()
.setShouldDimWallpaper(true)
.setShouldDisplayGrayscale(true)
@@ -253,8 +241,6 @@ public class DefaultDeviceEffectsApplierTest {
@Test
public void apply_onlyEffectDeltaApplied() {
- mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
-
mApplier.apply(new ZenDeviceEffects.Builder().setShouldDimWallpaper(true).build(),
ORIGIN_USER_IN_SYSTEMUI);
verify(mWallpaperManager).setWallpaperDimAmount(eq(0.6f));
@@ -272,7 +258,6 @@ public class DefaultDeviceEffectsApplierTest {
@Test
public void apply_nightModeFromApp_appliedOnScreenOff() {
- mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
ArgumentCaptor<BroadcastReceiver> broadcastReceiverCaptor =
ArgumentCaptor.forClass(BroadcastReceiver.class);
ArgumentCaptor<IntentFilter> intentFilterCaptor =
@@ -301,8 +286,6 @@ public class DefaultDeviceEffectsApplierTest {
@Test
public void apply_nightModeWithScreenOff_appliedImmediately(
@TestParameter ZenChangeOrigin origin) {
- mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
-
when(mPowerManager.isInteractive()).thenReturn(false);
mApplier.apply(new ZenDeviceEffects.Builder().setShouldUseNightMode(true).build(),
@@ -314,7 +297,6 @@ public class DefaultDeviceEffectsApplierTest {
}
@Test
- @EnableFlags({android.app.Flags.FLAG_MODES_API, android.app.Flags.FLAG_MODES_UI})
public void apply_nightModeWithScreenOnAndKeyguardShowing_appliedImmediately(
@TestParameter ZenChangeOrigin origin) {
@@ -334,8 +316,6 @@ public class DefaultDeviceEffectsApplierTest {
"{origin: ORIGIN_INIT}", "{origin: ORIGIN_INIT_USER}"})
public void apply_nightModeWithScreenOn_appliedImmediatelyBasedOnOrigin(
ZenChangeOrigin origin) {
- mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
-
when(mPowerManager.isInteractive()).thenReturn(true);
mApplier.apply(new ZenDeviceEffects.Builder().setShouldUseNightMode(true).build(),
@@ -351,8 +331,6 @@ public class DefaultDeviceEffectsApplierTest {
"{origin: ORIGIN_SYSTEM}", "{origin: ORIGIN_UNKNOWN}"})
public void apply_nightModeWithScreenOn_willBeAppliedLaterBasedOnOrigin(
ZenChangeOrigin origin) {
- mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
-
when(mPowerManager.isInteractive()).thenReturn(true);
mApplier.apply(new ZenDeviceEffects.Builder().setShouldUseNightMode(true).build(),
@@ -367,8 +345,6 @@ public class DefaultDeviceEffectsApplierTest {
@Test
public void apply_servicesThrow_noCrash() {
- mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
-
doThrow(new RuntimeException()).when(mPowerManager)
.suppressAmbientDisplay(anyString(), anyBoolean());
doThrow(new RuntimeException()).when(mColorDisplayManager).setSaturationLevel(anyInt());
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java
index e5c42082ab97..98440ecdad82 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java
@@ -17,12 +17,17 @@ package com.android.server.notification;
import static android.content.Context.DEVICE_POLICY_SERVICE;
import static android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR;
+import static android.os.UserHandle.USER_ALL;
+import static android.os.UserHandle.USER_CURRENT;
import static android.os.UserManager.USER_TYPE_FULL_SECONDARY;
import static android.os.UserManager.USER_TYPE_PROFILE_CLONE;
import static android.os.UserManager.USER_TYPE_PROFILE_MANAGED;
import static android.os.UserManager.USER_TYPE_PROFILE_PRIVATE;
import static android.service.notification.NotificationListenerService.META_DATA_DEFAULT_AUTOBIND;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+import static com.android.server.notification.Flags.FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER;
+import static com.android.server.notification.Flags.managedServicesConcurrentMultiuser;
import static com.android.server.notification.ManagedServices.APPROVAL_BY_COMPONENT;
import static com.android.server.notification.ManagedServices.APPROVAL_BY_PACKAGE;
import static com.android.server.notification.NotificationManagerService.privateSpaceFlagsEnabled;
@@ -66,7 +71,9 @@ import android.os.IInterface;
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
+import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.ArrayMap;
@@ -83,6 +90,7 @@ import com.android.server.UiServiceTestCase;
import com.google.android.collect.Lists;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
@@ -105,6 +113,9 @@ import java.util.concurrent.CountDownLatch;
public class ManagedServicesTest extends UiServiceTestCase {
+ @Rule
+ public SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
@Mock
private IPackageManager mIpm;
@Mock
@@ -155,6 +166,7 @@ public class ManagedServicesTest extends UiServiceTestCase {
users.add(new UserInfo(11, "11", 0));
users.add(new UserInfo(12, "12", 0));
users.add(new UserInfo(13, "13", 0));
+ users.add(new UserInfo(99, "99", 0));
for (UserInfo user : users) {
when(mUm.getUserInfo(eq(user.id))).thenReturn(user);
}
@@ -804,6 +816,7 @@ public class ManagedServicesTest extends UiServiceTestCase {
}
@Test
+ @DisableFlags(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER)
public void rebindServices_onlyBindsExactMatchesIfComponent() throws Exception {
// If the primary and secondary lists contain component names, only those components within
// the package should be matched
@@ -841,6 +854,45 @@ public class ManagedServicesTest extends UiServiceTestCase {
}
@Test
+ @EnableFlags(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER)
+ public void rebindServices_onlyBindsExactMatchesIfComponent_concurrent_multiUser()
+ throws Exception {
+ // If the primary and secondary lists contain component names, only those components within
+ // the package should be matched
+ ManagedServices service = new TestManagedServices(getContext(), mLock, mUserProfiles,
+ mIpm,
+ ManagedServices.APPROVAL_BY_COMPONENT);
+
+ List<String> packages = new ArrayList<>();
+ packages.add("package");
+ packages.add("anotherPackage");
+ addExpectedServices(service, packages, 0);
+
+ // only 2 components are approved per package
+ mExpectedPrimaryComponentNames.clear();
+ mExpectedPrimaryComponentNames.put(0, "package/C1:package/C2");
+ mExpectedSecondaryComponentNames.clear();
+ mExpectedSecondaryComponentNames.put(0, "anotherPackage/C1:anotherPackage/C2");
+
+ loadXml(service);
+ // verify the 2 components per package are enabled (bound)
+ verifyExpectedBoundEntries(service, true, 0);
+ verifyExpectedBoundEntries(service, false, 0);
+
+ // verify the last component per package is not enabled/we don't try to bind to it
+ for (String pkg : packages) {
+ ComponentName unapprovedAdditionalComponent =
+ ComponentName.unflattenFromString(pkg + "/C3");
+ assertFalse(
+ service.isComponentEnabledForUser(
+ unapprovedAdditionalComponent, 0));
+ verify(mIpm, never()).getServiceInfo(
+ eq(unapprovedAdditionalComponent), anyLong(), anyInt());
+ }
+ }
+
+ @Test
+ @DisableFlags(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER)
public void rebindServices_bindsEverythingInAPackage() throws Exception {
// If the primary and secondary lists contain packages, all components within those packages
// should be bound
@@ -866,6 +918,32 @@ public class ManagedServicesTest extends UiServiceTestCase {
}
@Test
+ @EnableFlags(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER)
+ public void rebindServices_bindsEverythingInAPackage_concurrent_multiUser() throws Exception {
+ // If the primary and secondary lists contain packages, all components within those packages
+ // should be bound
+ ManagedServices service = new TestManagedServices(getContext(), mLock, mUserProfiles, mIpm,
+ APPROVAL_BY_PACKAGE);
+
+ List<String> packages = new ArrayList<>();
+ packages.add("package");
+ packages.add("packagea");
+ addExpectedServices(service, packages, 0);
+
+ // 2 approved packages
+ mExpectedPrimaryPackages.clear();
+ mExpectedPrimaryPackages.put(0, "package");
+ mExpectedSecondaryPackages.clear();
+ mExpectedSecondaryPackages.put(0, "packagea");
+
+ loadXml(service);
+
+ // verify the 3 components per package are enabled (bound)
+ verifyExpectedBoundEntries(service, true, 0);
+ verifyExpectedBoundEntries(service, false, 0);
+ }
+
+ @Test
public void reregisterService_checksAppIsApproved_pkg() throws Exception {
Context context = mock(Context.class);
PackageManager pm = mock(PackageManager.class);
@@ -1118,6 +1196,7 @@ public class ManagedServicesTest extends UiServiceTestCase {
}
@Test
+ @DisableFlags(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER)
public void testUpgradeAppBindsNewServices() throws Exception {
// If the primary and secondary lists contain component names, only those components within
// the package should be matched
@@ -1159,6 +1238,49 @@ public class ManagedServicesTest extends UiServiceTestCase {
}
@Test
+ @EnableFlags(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER)
+ public void testUpgradeAppBindsNewServices_concurrent_multiUser() throws Exception {
+ // If the primary and secondary lists contain component names, only those components within
+ // the package should be matched
+ ManagedServices service = new TestManagedServices(getContext(), mLock, mUserProfiles,
+ mIpm,
+ ManagedServices.APPROVAL_BY_PACKAGE);
+
+ List<String> packages = new ArrayList<>();
+ packages.add("package");
+ addExpectedServices(service, packages, 0);
+
+ // only 2 components are approved per package
+ mExpectedPrimaryComponentNames.clear();
+ mExpectedPrimaryPackages.clear();
+ mExpectedPrimaryComponentNames.put(0, "package/C1:package/C2");
+ mExpectedSecondaryComponentNames.clear();
+ mExpectedSecondaryPackages.clear();
+
+ loadXml(service);
+
+ // new component expected
+ mExpectedPrimaryComponentNames.put(0, "package/C1:package/C2:package/C3");
+
+ service.onPackagesChanged(false, new String[]{"package"}, new int[]{0});
+
+ // verify the 3 components per package are enabled (bound)
+ verifyExpectedBoundEntries(service, true, 0);
+
+ // verify the last component per package is not enabled/we don't try to bind to it
+ for (String pkg : packages) {
+ ComponentName unapprovedAdditionalComponent =
+ ComponentName.unflattenFromString(pkg + "/C3");
+ assertFalse(
+ service.isComponentEnabledForUser(
+ unapprovedAdditionalComponent, 0));
+ verify(mIpm, never()).getServiceInfo(
+ eq(unapprovedAdditionalComponent), anyLong(), anyInt());
+ }
+ }
+
+ @Test
+ @DisableFlags(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER)
public void testUpgradeAppNoPermissionNoRebind() throws Exception {
Context context = spy(getContext());
doReturn(true).when(context).bindServiceAsUser(any(), any(), anyInt(), any());
@@ -1211,6 +1333,59 @@ public class ManagedServicesTest extends UiServiceTestCase {
}
@Test
+ @EnableFlags(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER)
+ public void testUpgradeAppNoPermissionNoRebind_concurrent_multiUser() throws Exception {
+ Context context = spy(getContext());
+ doReturn(true).when(context).bindServiceAsUser(any(), any(), anyInt(), any());
+
+ ManagedServices service = new TestManagedServices(context, mLock, mUserProfiles,
+ mIpm,
+ APPROVAL_BY_COMPONENT);
+
+ List<String> packages = new ArrayList<>();
+ packages.add("package");
+ addExpectedServices(service, packages, 0);
+
+ final ComponentName unapprovedComponent = ComponentName.unflattenFromString("package/C1");
+ final ComponentName approvedComponent = ComponentName.unflattenFromString("package/C2");
+
+ // Both components are approved initially
+ mExpectedPrimaryComponentNames.clear();
+ mExpectedPrimaryPackages.clear();
+ mExpectedPrimaryComponentNames.put(0, "package/C1:package/C2");
+ mExpectedSecondaryComponentNames.clear();
+ mExpectedSecondaryPackages.clear();
+
+ loadXml(service);
+
+ //Component package/C1 loses bind permission
+ when(mIpm.getServiceInfo(any(), anyLong(), anyInt())).thenAnswer(
+ (Answer<ServiceInfo>) invocation -> {
+ ComponentName invocationCn = invocation.getArgument(0);
+ if (invocationCn != null) {
+ ServiceInfo serviceInfo = new ServiceInfo();
+ serviceInfo.packageName = invocationCn.getPackageName();
+ serviceInfo.name = invocationCn.getClassName();
+ if (invocationCn.equals(unapprovedComponent)) {
+ serviceInfo.permission = "none";
+ } else {
+ serviceInfo.permission = service.getConfig().bindPermission;
+ }
+ serviceInfo.metaData = null;
+ return serviceInfo;
+ }
+ return null;
+ }
+ );
+
+ // Trigger package update
+ service.onPackagesChanged(false, new String[]{"package"}, new int[]{0});
+
+ assertFalse(service.isComponentEnabledForUser(unapprovedComponent, 0));
+ assertTrue(service.isComponentEnabledForUser(approvedComponent, 0));
+ }
+
+ @Test
public void testSetPackageOrComponentEnabled() throws Exception {
for (int approvalLevel : new int[] {APPROVAL_BY_COMPONENT, APPROVAL_BY_PACKAGE}) {
ManagedServices service = new TestManagedServices(getContext(), mLock, mUserProfiles,
@@ -1517,6 +1692,201 @@ public class ManagedServicesTest extends UiServiceTestCase {
assertTrue(componentsToBind.get(10).contains(ComponentName.unflattenFromString("c/c")));
}
+ @SuppressWarnings("GuardedBy")
+ @Test
+ @EnableFlags(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER)
+ public void testPopulateComponentsToBindWithNonProfileUser() {
+ ManagedServices service = new TestManagedServices(getContext(), mLock, mUserProfiles, mIpm,
+ APPROVAL_BY_COMPONENT);
+ spyOn(service);
+
+ SparseArray<ArraySet<ComponentName>> approvedComponentsByUser = new SparseArray<>();
+ ArraySet<ComponentName> allowed0 = new ArraySet<>();
+ allowed0.add(ComponentName.unflattenFromString("a/a"));
+ approvedComponentsByUser.put(0, allowed0);
+ ArraySet<ComponentName> allowed10 = new ArraySet<>();
+ allowed10.add(ComponentName.unflattenFromString("b/b"));
+ approvedComponentsByUser.put(10, allowed10);
+
+ int nonProfileUser = 99;
+ ArraySet<ComponentName> allowedForNonProfileUser = new ArraySet<>();
+ allowedForNonProfileUser.add(ComponentName.unflattenFromString("c/c"));
+ approvedComponentsByUser.put(nonProfileUser, allowedForNonProfileUser);
+
+ IntArray users = new IntArray();
+ users.add(nonProfileUser);
+ users.add(10);
+ users.add(0);
+
+ SparseArray<Set<ComponentName>> componentsToBind = new SparseArray<>();
+ spyOn(service.mUmInternal);
+ when(service.mUmInternal.isVisibleBackgroundFullUser(nonProfileUser)).thenReturn(true);
+
+ service.populateComponentsToBind(componentsToBind, users, approvedComponentsByUser);
+
+ assertTrue(service.isComponentEnabledForUser(
+ ComponentName.unflattenFromString("a/a"), 0));
+ assertTrue(service.isComponentEnabledForPackage("a", 0));
+ assertTrue(service.isComponentEnabledForUser(
+ ComponentName.unflattenFromString("b/b"), 10));
+ assertTrue(service.isComponentEnabledForPackage("b", 0));
+ assertTrue(service.isComponentEnabledForPackage("b", 10));
+ assertTrue(service.isComponentEnabledForUser(
+ ComponentName.unflattenFromString("c/c"), nonProfileUser));
+ assertTrue(service.isComponentEnabledForPackage("c", nonProfileUser));
+ }
+
+
+ @Test
+ @EnableFlags(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER)
+ public void testRebindService_profileUser() throws Exception {
+ final int profileUserId = 10;
+ when(mUserProfiles.isProfileUser(profileUserId, mContext)).thenReturn(true);
+ spyOn(mService);
+ ArgumentCaptor<IntArray> captor = ArgumentCaptor.forClass(
+ IntArray.class);
+ when(mService.allowRebindForParentUser()).thenReturn(true);
+
+ mService.rebindServices(false, profileUserId);
+
+ verify(mService).populateComponentsToBind(any(), captor.capture(), any());
+ assertTrue(captor.getValue().contains(0));
+ assertTrue(captor.getValue().contains(profileUserId));
+ }
+
+ @Test
+ @EnableFlags(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER)
+ public void testRebindService_nonProfileUser() throws Exception {
+ final int userId = 99;
+ when(mUserProfiles.isProfileUser(userId, mContext)).thenReturn(false);
+ spyOn(mService);
+ ArgumentCaptor<IntArray> captor = ArgumentCaptor.forClass(
+ IntArray.class);
+ when(mService.allowRebindForParentUser()).thenReturn(true);
+
+ mService.rebindServices(false, userId);
+
+ verify(mService).populateComponentsToBind(any(), captor.capture(), any());
+ assertFalse(captor.getValue().contains(0));
+ assertTrue(captor.getValue().contains(userId));
+ }
+
+ @Test
+ @EnableFlags(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER)
+ public void testRebindService_userAll() throws Exception {
+ final int userId = 99;
+ spyOn(mService);
+ spyOn(mService.mUmInternal);
+ when(mService.mUmInternal.isVisibleBackgroundFullUser(userId)).thenReturn(true);
+ ArgumentCaptor<IntArray> captor = ArgumentCaptor.forClass(
+ IntArray.class);
+ when(mService.allowRebindForParentUser()).thenReturn(true);
+
+ mService.rebindServices(false, USER_ALL);
+
+ verify(mService).populateComponentsToBind(any(), captor.capture(), any());
+ assertTrue(captor.getValue().contains(0));
+ assertTrue(captor.getValue().contains(userId));
+ }
+
+ @Test
+ @EnableFlags(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER)
+ public void testOnUserStoppedWithVisibleBackgroundUser() throws Exception {
+ ManagedServices service = new TestManagedServices(getContext(), mLock, mUserProfiles, mIpm,
+ APPROVAL_BY_COMPONENT);
+ spyOn(service);
+ int userId = 99;
+ SparseArray<ArraySet<ComponentName>> approvedComponentsByUser = new SparseArray<>();
+ ArraySet<ComponentName> allowedForNonProfileUser = new ArraySet<>();
+ allowedForNonProfileUser.add(ComponentName.unflattenFromString("a/a"));
+ approvedComponentsByUser.put(userId, allowedForNonProfileUser);
+ IntArray users = new IntArray();
+ users.add(userId);
+ SparseArray<Set<ComponentName>> componentsToBind = new SparseArray<>();
+ spyOn(service.mUmInternal);
+ when(service.mUmInternal.isVisibleBackgroundFullUser(userId)).thenReturn(true);
+ service.populateComponentsToBind(componentsToBind, users, approvedComponentsByUser);
+ assertTrue(service.isComponentEnabledForUser(
+ ComponentName.unflattenFromString("a/a"), userId));
+ assertTrue(service.isComponentEnabledForPackage("a", userId));
+
+ service.onUserStopped(userId);
+
+ assertFalse(service.isComponentEnabledForUser(
+ ComponentName.unflattenFromString("a/a"), userId));
+ assertFalse(service.isComponentEnabledForPackage("a", userId));
+ verify(service).unbindUserServices(eq(userId));
+ }
+
+ @Test
+ @EnableFlags(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER)
+ public void testUnbindServicesImpl_serviceOfForegroundUser() throws Exception {
+ int switchingUserId = 10;
+ int userId = 99;
+
+ ManagedServices service = new TestManagedServices(getContext(), mLock, mUserProfiles, mIpm,
+ APPROVAL_BY_COMPONENT);
+ spyOn(service);
+ spyOn(service.mUmInternal);
+ when(service.mUmInternal.isVisibleBackgroundFullUser(userId)).thenReturn(false);
+
+ IInterface iInterface = mock(IInterface.class);
+ when(iInterface.asBinder()).thenReturn(mock(IBinder.class));
+
+ ManagedServices.ManagedServiceInfo serviceInfo = service.new ManagedServiceInfo(
+ iInterface, ComponentName.unflattenFromString("a/a"), userId, false,
+ mock(ServiceConnection.class), 26, 34);
+
+ Set<ManagedServices.ManagedServiceInfo> removableBoundServices = new ArraySet<>();
+ removableBoundServices.add(serviceInfo);
+
+ when(service.getRemovableConnectedServices()).thenReturn(removableBoundServices);
+ ArgumentCaptor<SparseArray<Set<ComponentName>>> captor = ArgumentCaptor.forClass(
+ SparseArray.class);
+
+ service.unbindServicesImpl(switchingUserId, true);
+
+ verify(service).unbindFromServices(captor.capture());
+
+ assertEquals(captor.getValue().size(), 1);
+ assertTrue(captor.getValue().indexOfKey(userId) != -1);
+ assertTrue(captor.getValue().get(userId).contains(
+ ComponentName.unflattenFromString("a/a")));
+ }
+
+ @Test
+ @EnableFlags(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER)
+ public void testUnbindServicesImpl_serviceOfVisibleBackgroundUser() throws Exception {
+ int switchingUserId = 10;
+ int userId = 99;
+
+ ManagedServices service = new TestManagedServices(getContext(), mLock, mUserProfiles, mIpm,
+ APPROVAL_BY_COMPONENT);
+ spyOn(service);
+ spyOn(service.mUmInternal);
+ when(service.mUmInternal.isVisibleBackgroundFullUser(userId)).thenReturn(true);
+
+ IInterface iInterface = mock(IInterface.class);
+ when(iInterface.asBinder()).thenReturn(mock(IBinder.class));
+
+ ManagedServices.ManagedServiceInfo serviceInfo = service.new ManagedServiceInfo(
+ iInterface, ComponentName.unflattenFromString("a/a"), userId,
+ false, mock(ServiceConnection.class), 26, 34);
+
+ Set<ManagedServices.ManagedServiceInfo> removableBoundServices = new ArraySet<>();
+ removableBoundServices.add(serviceInfo);
+
+ when(service.getRemovableConnectedServices()).thenReturn(removableBoundServices);
+ ArgumentCaptor<SparseArray<Set<ComponentName>>> captor = ArgumentCaptor.forClass(
+ SparseArray.class);
+
+ service.unbindServicesImpl(switchingUserId, true);
+
+ verify(service).unbindFromServices(captor.capture());
+
+ assertEquals(captor.getValue().size(), 0);
+ }
+
@Test
public void testOnNullBinding() throws Exception {
Context context = mock(Context.class);
@@ -1681,6 +2051,7 @@ public class ManagedServicesTest extends UiServiceTestCase {
assertFalse(service.isBound(cn, mZero.id));
assertFalse(service.isBound(cn, mTen.id));
}
+
@Test
public void testOnPackagesChanged_nullValuesPassed_noNullPointers() {
for (int approvalLevel : new int[] {APPROVAL_BY_COMPONENT, APPROVAL_BY_PACKAGE}) {
@@ -2012,6 +2383,7 @@ public class ManagedServicesTest extends UiServiceTestCase {
}
@Test
+ @DisableFlags(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER)
public void isComponentEnabledForCurrentProfiles_isThreadSafe() throws InterruptedException {
for (UserInfo userInfo : mUm.getUsers()) {
mService.addApprovedList("pkg1/cmp1:pkg2/cmp2:pkg3/cmp3", userInfo.id, true);
@@ -2024,6 +2396,20 @@ public class ManagedServicesTest extends UiServiceTestCase {
}
@Test
+ @EnableFlags(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER)
+ public void isComponentEnabledForUser_isThreadSafe() throws InterruptedException {
+ for (UserInfo userInfo : mUm.getUsers()) {
+ mService.addApprovedList("pkg1/cmp1:pkg2/cmp2:pkg3/cmp3", userInfo.id, true);
+ }
+ testThreadSafety(() -> {
+ mService.rebindServices(false, 0);
+ assertThat(mService.isComponentEnabledForUser(
+ new ComponentName("pkg1", "cmp1"), 0)).isTrue();
+ }, 20, 30);
+ }
+
+ @Test
+ @DisableFlags(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER)
public void isComponentEnabledForCurrentProfiles_profileUserId() {
final int profileUserId = 10;
when(mUserProfiles.isProfileUser(profileUserId, mContext)).thenReturn(true);
@@ -2037,6 +2423,24 @@ public class ManagedServicesTest extends UiServiceTestCase {
}
@Test
+ @EnableFlags(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER)
+ public void isComponentEnabledForUser_profileUserId() {
+ final int profileUserId = 10;
+ when(mUserProfiles.isProfileUser(profileUserId, mContext)).thenReturn(true);
+ spyOn(mService);
+ doReturn(USER_CURRENT).when(mService).resolveUserId(anyInt());
+
+ // Only approve for parent user (0)
+ mService.addApprovedList("pkg1/cmp1:pkg2/cmp2:pkg3/cmp3", 0, true);
+
+ // Test that the component is enabled after calling rebindServices with profile userId (10)
+ mService.rebindServices(false, profileUserId);
+ assertThat(mService.isComponentEnabledForUser(
+ new ComponentName("pkg1", "cmp1"), profileUserId)).isTrue();
+ }
+
+ @Test
+ @DisableFlags(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER)
public void isComponentEnabledForCurrentProfiles_profileUserId_NAS() {
final int profileUserId = 10;
when(mUserProfiles.isProfileUser(profileUserId, mContext)).thenReturn(true);
@@ -2054,6 +2458,25 @@ public class ManagedServicesTest extends UiServiceTestCase {
}
@Test
+ @EnableFlags(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER)
+ public void isComponentEnabledForUser_profileUserId_NAS() {
+ final int profileUserId = 10;
+ when(mUserProfiles.isProfileUser(profileUserId, mContext)).thenReturn(true);
+ // Do not rebind for parent users (NAS use-case)
+ ManagedServices service = spy(mService);
+ when(service.allowRebindForParentUser()).thenReturn(false);
+ doReturn(USER_CURRENT).when(service).resolveUserId(anyInt());
+
+ // Only approve for parent user (0)
+ service.addApprovedList("pkg1/cmp1:pkg2/cmp2:pkg3/cmp3", 0, true);
+
+ // Test that the component is disabled after calling rebindServices with profile userId (10)
+ service.rebindServices(false, profileUserId);
+ assertThat(service.isComponentEnabledForUser(
+ new ComponentName("pkg1", "cmp1"), profileUserId)).isFalse();
+ }
+
+ @Test
@EnableFlags(FLAG_LIFETIME_EXTENSION_REFACTOR)
public void testManagedServiceInfoIsSystemUi() {
ManagedServices service = new TestManagedServices(getContext(), mLock, mUserProfiles, mIpm,
@@ -2069,6 +2492,48 @@ public class ManagedServicesTest extends UiServiceTestCase {
assertThat(service0.isSystemUi()).isFalse();
}
+ @Test
+ @EnableFlags(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER)
+ public void testUserMatchesAndEnabled_profileUser() throws Exception {
+ int currentUserId = 10;
+ int profileUserId = 11;
+
+ ManagedServices service = new TestManagedServices(getContext(), mLock, mUserProfiles, mIpm,
+ APPROVAL_BY_COMPONENT);
+ ManagedServices.ManagedServiceInfo listener = spy(service.new ManagedServiceInfo(
+ mock(IInterface.class), ComponentName.unflattenFromString("a/a"), currentUserId,
+ false, mock(ServiceConnection.class), 26, 34));
+
+ doReturn(currentUserId).when(service.mUmInternal).getProfileParentId(profileUserId);
+ doReturn(currentUserId).when(service.mUmInternal).getProfileParentId(currentUserId);
+ doReturn(true).when(listener).isEnabledForUser();
+ doReturn(true).when(mUserProfiles).isCurrentProfile(anyInt());
+
+ assertThat(listener.enabledAndUserMatches(profileUserId)).isTrue();
+ }
+
+ @Test
+ @EnableFlags(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER)
+ public void testUserMatchesAndDisabled_visibleBackgroudUser() throws Exception {
+ int currentUserId = 10;
+ int profileUserId = 11;
+ int visibleBackgroundUserId = 12;
+
+ ManagedServices service = new TestManagedServices(getContext(), mLock, mUserProfiles, mIpm,
+ APPROVAL_BY_COMPONENT);
+ ManagedServices.ManagedServiceInfo listener = spy(service.new ManagedServiceInfo(
+ mock(IInterface.class), ComponentName.unflattenFromString("a/a"), profileUserId,
+ false, mock(ServiceConnection.class), 26, 34));
+
+ doReturn(currentUserId).when(service.mUmInternal).getProfileParentId(profileUserId);
+ doReturn(currentUserId).when(service.mUmInternal).getProfileParentId(currentUserId);
+ doReturn(visibleBackgroundUserId).when(service.mUmInternal)
+ .getProfileParentId(visibleBackgroundUserId);
+ doReturn(true).when(listener).isEnabledForUser();
+
+ assertThat(listener.enabledAndUserMatches(visibleBackgroundUserId)).isFalse();
+ }
+
private void mockServiceInfoWithMetaData(List<ComponentName> componentNames,
ManagedServices service, ArrayMap<ComponentName, Bundle> metaDatas)
throws RemoteException {
@@ -2247,26 +2712,47 @@ public class ManagedServicesTest extends UiServiceTestCase {
private void verifyExpectedBoundEntries(ManagedServices service, boolean primary)
throws Exception {
+ verifyExpectedBoundEntries(service, primary, UserHandle.USER_CURRENT);
+ }
+
+ private void verifyExpectedBoundEntries(ManagedServices service, boolean primary,
+ int targetUserId) throws Exception {
ArrayMap<Integer, String> verifyMap = primary ? mExpectedPrimary.get(service.mApprovalLevel)
: mExpectedSecondary.get(service.mApprovalLevel);
for (int userId : verifyMap.keySet()) {
for (String packageOrComponent : verifyMap.get(userId).split(":")) {
if (!TextUtils.isEmpty(packageOrComponent)) {
if (service.mApprovalLevel == APPROVAL_BY_PACKAGE) {
- assertTrue(packageOrComponent,
- service.isComponentEnabledForPackage(packageOrComponent));
+ if (managedServicesConcurrentMultiuser()) {
+ assertTrue(packageOrComponent,
+ service.isComponentEnabledForPackage(packageOrComponent,
+ targetUserId));
+ } else {
+ assertTrue(packageOrComponent,
+ service.isComponentEnabledForPackage(packageOrComponent));
+ }
for (int i = 1; i <= 3; i++) {
ComponentName componentName = ComponentName.unflattenFromString(
packageOrComponent +"/C" + i);
- assertTrue(service.isComponentEnabledForCurrentProfiles(
- componentName));
+ if (managedServicesConcurrentMultiuser()) {
+ assertTrue(service.isComponentEnabledForUser(
+ componentName, targetUserId));
+ } else {
+ assertTrue(service.isComponentEnabledForCurrentProfiles(
+ componentName));
+ }
verify(mIpm, times(1)).getServiceInfo(
eq(componentName), anyLong(), anyInt());
}
} else {
ComponentName componentName =
ComponentName.unflattenFromString(packageOrComponent);
- assertTrue(service.isComponentEnabledForCurrentProfiles(componentName));
+ if (managedServicesConcurrentMultiuser()) {
+ assertTrue(service.isComponentEnabledForUser(componentName,
+ targetUserId));
+ } else {
+ assertTrue(service.isComponentEnabledForCurrentProfiles(componentName));
+ }
verify(mIpm, times(1)).getServiceInfo(
eq(componentName), anyLong(), anyInt());
}
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 415e3accfa39..37ab541f12da 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -140,6 +140,7 @@ import static com.android.server.am.PendingIntentRecord.FLAG_ACTIVITY_SENDER;
import static com.android.server.am.PendingIntentRecord.FLAG_BROADCAST_SENDER;
import static com.android.server.am.PendingIntentRecord.FLAG_SERVICE_SENDER;
import static com.android.server.notification.Flags.FLAG_ALL_NOTIFS_NEED_TTL;
+import static com.android.server.notification.Flags.FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER;
import static com.android.server.notification.Flags.FLAG_REJECT_OLD_NOTIFICATIONS;
import static com.android.server.notification.GroupHelper.AUTOGROUP_KEY;
import static com.android.server.notification.NotificationManagerService.BITMAP_DURATION;
@@ -867,7 +868,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
&& filter.hasAction(Intent.ACTION_PACKAGES_SUSPENDED)) {
mPackageIntentReceiver = broadcastReceivers.get(i);
}
- if (filter.hasAction(Intent.ACTION_USER_SWITCHED)
+ if (filter.hasAction(Intent.ACTION_USER_STOPPED)
+ || filter.hasAction(Intent.ACTION_USER_SWITCHED)
|| filter.hasAction(Intent.ACTION_PROFILE_UNAVAILABLE)
|| filter.hasAction(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE)) {
// There may be multiple receivers, get the NMS one
@@ -11028,7 +11030,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
}
@Test
- @EnableFlags(android.app.Flags.FLAG_MODES_API)
public void testAddAutomaticZenRule_typeManagedCanBeUsedByDeviceOwners() throws Exception {
ZenModeHelper zenModeHelper = setUpMockZenTest();
mService.setCallerIsNormalPackage();
@@ -11046,20 +11047,17 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
}
@Test
- @EnableFlags(android.app.Flags.FLAG_MODES_API)
public void testAddAutomaticZenRule_typeManagedCanBeUsedBySystem() throws Exception {
addAutomaticZenRule_restrictedRuleTypeCanBeUsedBySystem(AutomaticZenRule.TYPE_MANAGED);
}
@Test
- @EnableFlags(android.app.Flags.FLAG_MODES_API)
public void testAddAutomaticZenRule_typeManagedCannotBeUsedByRegularApps() throws Exception {
addAutomaticZenRule_restrictedRuleTypeCannotBeUsedByRegularApps(
AutomaticZenRule.TYPE_MANAGED);
}
@Test
- @EnableFlags(android.app.Flags.FLAG_MODES_API)
public void testAddAutomaticZenRule_typeBedtimeCanBeUsedByWellbeing() throws Exception {
ZenModeHelper zenModeHelper = setUpMockZenTest();
mService.setCallerIsNormalPackage();
@@ -11082,7 +11080,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
}
@Test
- @EnableFlags(android.app.Flags.FLAG_MODES_API)
public void testAddAutomaticZenRule_typeBedtimeCanBeUsedBySystem() throws Exception {
reset(mPackageManagerInternal);
when(mPackageManagerInternal.isSameApp(eq(mPkg), eq(mUid), anyInt())).thenReturn(true);
@@ -11090,7 +11087,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
}
@Test
- @EnableFlags(android.app.Flags.FLAG_MODES_API)
public void testAddAutomaticZenRule_typeBedtimeCannotBeUsedByRegularApps() throws Exception {
reset(mPackageManagerInternal);
when(mPackageManagerInternal.isSameApp(eq(mPkg), eq(mUid), anyInt())).thenReturn(true);
@@ -11133,7 +11129,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
}
@Test
- @EnableFlags(android.app.Flags.FLAG_MODES_API)
public void addAutomaticZenRule_fromUser_mappedToOriginUser() throws Exception {
ZenModeHelper zenModeHelper = setUpMockZenTest();
mService.isSystemUid = true;
@@ -11145,7 +11140,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
}
@Test
- @EnableFlags(android.app.Flags.FLAG_MODES_API)
public void addAutomaticZenRule_fromSystemNotUser_mappedToOriginSystem() throws Exception {
ZenModeHelper zenModeHelper = setUpMockZenTest();
mService.isSystemUid = true;
@@ -11157,7 +11151,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
}
@Test
- @EnableFlags(android.app.Flags.FLAG_MODES_API)
public void addAutomaticZenRule_fromApp_mappedToOriginApp() throws Exception {
ZenModeHelper zenModeHelper = setUpMockZenTest();
mService.setCallerIsNormalPackage();
@@ -11169,7 +11162,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
}
@Test
- @EnableFlags(android.app.Flags.FLAG_MODES_API)
public void addAutomaticZenRule_fromAppFromUser_blocked() throws Exception {
setUpMockZenTest();
mService.setCallerIsNormalPackage();
@@ -11179,7 +11171,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
}
@Test
- @EnableFlags(android.app.Flags.FLAG_MODES_API)
public void updateAutomaticZenRule_fromUserFromSystem_allowed() throws Exception {
ZenModeHelper zenModeHelper = setUpMockZenTest();
mService.isSystemUid = true;
@@ -11191,7 +11182,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
}
@Test
- @EnableFlags(android.app.Flags.FLAG_MODES_API)
public void updateAutomaticZenRule_fromUserFromApp_blocked() throws Exception {
setUpMockZenTest();
mService.setCallerIsNormalPackage();
@@ -11201,7 +11191,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
}
@Test
- @EnableFlags(android.app.Flags.FLAG_MODES_API)
public void removeAutomaticZenRule_fromUserFromSystem_allowed() throws Exception {
ZenModeHelper zenModeHelper = setUpMockZenTest();
mService.isSystemUid = true;
@@ -11213,7 +11202,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
}
@Test
- @EnableFlags(android.app.Flags.FLAG_MODES_API)
public void removeAutomaticZenRule_fromUserFromApp_blocked() throws Exception {
setUpMockZenTest();
mService.setCallerIsNormalPackage();
@@ -11223,7 +11211,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
}
@Test
- @EnableFlags(android.app.Flags.FLAG_MODES_API)
public void setAutomaticZenRuleState_fromAppWithConditionFromUser_originUserInApp()
throws Exception {
ZenModeHelper zenModeHelper = setUpMockZenTest();
@@ -11238,7 +11225,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
}
@Test
- @EnableFlags(android.app.Flags.FLAG_MODES_API)
public void setAutomaticZenRuleState_fromAppWithConditionNotFromUser_originApp()
throws Exception {
ZenModeHelper zenModeHelper = setUpMockZenTest();
@@ -11253,7 +11239,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
}
@Test
- @EnableFlags(android.app.Flags.FLAG_MODES_API)
public void setAutomaticZenRuleState_fromSystemWithConditionFromUser_originUserInSystemUi()
throws Exception {
ZenModeHelper zenModeHelper = setUpMockZenTest();
@@ -11267,7 +11252,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
eq(ZenModeConfig.ORIGIN_USER_IN_SYSTEMUI), anyInt());
}
@Test
- @EnableFlags(android.app.Flags.FLAG_MODES_API)
public void setAutomaticZenRuleState_fromSystemWithConditionNotFromUser_originSystem()
throws Exception {
ZenModeHelper zenModeHelper = setUpMockZenTest();
@@ -11438,7 +11422,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
}
@Test
- @EnableFlags(android.app.Flags.FLAG_MODES_API)
public void onAutomaticRuleStatusChanged_sendsBroadcastToRuleOwner() throws Exception {
mService.mZenModeHelper.getCallbacks().forEach(c -> c.onAutomaticRuleStatusChanged(
mUserId, "rule.owner.pkg", "rule_id", AUTOMATIC_RULE_STATUS_ACTIVATED));
@@ -16302,7 +16285,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
InOrder inOrder = inOrder(mPreferencesHelper, mService.mZenModeHelper);
inOrder.verify(mService.mZenModeHelper).onUserSwitched(eq(20));
- inOrder.verify(mPreferencesHelper).syncChannelsBypassingDnd();
+ inOrder.verify(mPreferencesHelper).syncHasPriorityChannels();
inOrder.verifyNoMoreInteractions();
}
@@ -16318,11 +16301,25 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
InOrder inOrder = inOrder(mPreferencesHelper, mService.mZenModeHelper);
inOrder.verify(mService.mZenModeHelper).onUserSwitched(eq(20));
- inOrder.verify(mPreferencesHelper).syncChannelsBypassingDnd();
+ inOrder.verify(mPreferencesHelper).syncHasPriorityChannels();
inOrder.verifyNoMoreInteractions();
}
@Test
+ @EnableFlags(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER)
+ public void onUserStopped_callBackToListeners() {
+ Intent intent = new Intent(Intent.ACTION_USER_STOPPED);
+ intent.putExtra(Intent.EXTRA_USER_HANDLE, 20);
+
+ mUserIntentReceiver.onReceive(mContext, intent);
+
+ verify(mConditionProviders).onUserStopped(eq(20));
+ verify(mListeners).onUserStopped(eq(20));
+ verify(mAssistants).onUserStopped(eq(20));
+ }
+
+ @Test
+ @DisableFlags(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER)
public void isNotificationPolicyAccessGranted_invalidPackage() throws Exception {
final String notReal = "NOT REAL";
final var checker = mService.permissionChecker;
@@ -16339,6 +16336,25 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
}
@Test
+ @EnableFlags(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER)
+ public void isNotificationPolicyAccessGranted_invalidPackage_concurrent_multiUser()
+ throws Exception {
+ final String notReal = "NOT REAL";
+ final var checker = mService.permissionChecker;
+
+ when(mPackageManagerClient.getPackageUidAsUser(eq(notReal), anyInt())).thenThrow(
+ PackageManager.NameNotFoundException.class);
+
+ assertThat(mBinderService.isNotificationPolicyAccessGranted(notReal)).isFalse();
+ verify(mPackageManagerClient).getPackageUidAsUser(eq(notReal), anyInt());
+ verify(checker, never()).check(any(), anyInt(), anyInt(), anyBoolean());
+ verify(mConditionProviders, never()).isPackageOrComponentAllowed(eq(notReal), anyInt());
+ verify(mListeners, never()).isComponentEnabledForPackage(any(), anyInt());
+ verify(mDevicePolicyManager, never()).isActiveDeviceOwner(anyInt());
+ }
+
+ @Test
+ @DisableFlags(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER)
public void isNotificationPolicyAccessGranted_hasPermission() throws Exception {
final String packageName = "target";
final int uid = 123;
@@ -16357,6 +16373,27 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
}
@Test
+ @EnableFlags(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER)
+ public void isNotificationPolicyAccessGranted_hasPermission_concurrent_multiUser()
+ throws Exception {
+ final String packageName = "target";
+ final int uid = 123;
+ final var checker = mService.permissionChecker;
+
+ when(mPackageManagerClient.getPackageUidAsUser(eq(packageName), anyInt())).thenReturn(uid);
+ when(checker.check(android.Manifest.permission.MANAGE_NOTIFICATIONS, uid, -1, true))
+ .thenReturn(PackageManager.PERMISSION_GRANTED);
+
+ assertThat(mBinderService.isNotificationPolicyAccessGranted(packageName)).isTrue();
+ verify(mPackageManagerClient).getPackageUidAsUser(eq(packageName), anyInt());
+ verify(checker).check(android.Manifest.permission.MANAGE_NOTIFICATIONS, uid, -1, true);
+ verify(mConditionProviders, never()).isPackageOrComponentAllowed(eq(packageName), anyInt());
+ verify(mListeners, never()).isComponentEnabledForPackage(any(), anyInt());
+ verify(mDevicePolicyManager, never()).isActiveDeviceOwner(anyInt());
+ }
+
+ @Test
+ @DisableFlags(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER)
public void isNotificationPolicyAccessGranted_isPackageAllowed() throws Exception {
final String packageName = "target";
final int uid = 123;
@@ -16375,6 +16412,27 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
}
@Test
+ @EnableFlags(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER)
+ public void isNotificationPolicyAccessGranted_isPackageAllowed_concurrent_multiUser()
+ throws Exception {
+ final String packageName = "target";
+ final int uid = 123;
+ final var checker = mService.permissionChecker;
+
+ when(mPackageManagerClient.getPackageUidAsUser(eq(packageName), anyInt())).thenReturn(uid);
+ when(mConditionProviders.isPackageOrComponentAllowed(eq(packageName), anyInt()))
+ .thenReturn(true);
+
+ assertThat(mBinderService.isNotificationPolicyAccessGranted(packageName)).isTrue();
+ verify(mPackageManagerClient).getPackageUidAsUser(eq(packageName), anyInt());
+ verify(checker).check(android.Manifest.permission.MANAGE_NOTIFICATIONS, uid, -1, true);
+ verify(mConditionProviders).isPackageOrComponentAllowed(eq(packageName), anyInt());
+ verify(mListeners, never()).isComponentEnabledForPackage(any(), anyInt());
+ verify(mDevicePolicyManager, never()).isActiveDeviceOwner(anyInt());
+ }
+
+ @Test
+ @DisableFlags(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER)
public void isNotificationPolicyAccessGranted_isComponentEnabled() throws Exception {
final String packageName = "target";
final int uid = 123;
@@ -16392,6 +16450,26 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
}
@Test
+ @EnableFlags(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER)
+ public void isNotificationPolicyAccessGranted_isComponentEnabled_concurrent_multiUser()
+ throws Exception {
+ final String packageName = "target";
+ final int uid = 123;
+ final var checker = mService.permissionChecker;
+
+ when(mPackageManagerClient.getPackageUidAsUser(eq(packageName), anyInt())).thenReturn(uid);
+ when(mListeners.isComponentEnabledForPackage(packageName, mUserId)).thenReturn(true);
+
+ assertThat(mBinderService.isNotificationPolicyAccessGranted(packageName)).isTrue();
+ verify(mPackageManagerClient).getPackageUidAsUser(eq(packageName), anyInt());
+ verify(checker).check(android.Manifest.permission.MANAGE_NOTIFICATIONS, uid, -1, true);
+ verify(mConditionProviders).isPackageOrComponentAllowed(eq(packageName), anyInt());
+ verify(mListeners).isComponentEnabledForPackage(packageName, mUserId);
+ verify(mDevicePolicyManager, never()).isActiveDeviceOwner(anyInt());
+ }
+
+ @Test
+ @DisableFlags(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER)
public void isNotificationPolicyAccessGranted_isDeviceOwner() throws Exception {
final String packageName = "target";
final int uid = 123;
@@ -16408,10 +16486,30 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
verify(mDevicePolicyManager).isActiveDeviceOwner(uid);
}
+ @Test
+ @EnableFlags(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER)
+ public void isNotificationPolicyAccessGranted_isDeviceOwner_concurrent_multiUser()
+ throws Exception {
+ final String packageName = "target";
+ final int uid = 123;
+ final var checker = mService.permissionChecker;
+
+ when(mPackageManagerClient.getPackageUidAsUser(eq(packageName), anyInt())).thenReturn(uid);
+ when(mDevicePolicyManager.isActiveDeviceOwner(uid)).thenReturn(true);
+
+ assertThat(mBinderService.isNotificationPolicyAccessGranted(packageName)).isTrue();
+ verify(mPackageManagerClient).getPackageUidAsUser(eq(packageName), anyInt());
+ verify(checker).check(android.Manifest.permission.MANAGE_NOTIFICATIONS, uid, -1, true);
+ verify(mConditionProviders).isPackageOrComponentAllowed(eq(packageName), anyInt());
+ verify(mListeners).isComponentEnabledForPackage(packageName, mUserId);
+ verify(mDevicePolicyManager).isActiveDeviceOwner(uid);
+ }
+
/**
* b/292163859
*/
@Test
+ @DisableFlags(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER)
public void isNotificationPolicyAccessGranted_callerIsDeviceOwner() throws Exception {
final String packageName = "target";
final int uid = 123;
@@ -16430,7 +16528,32 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
verify(mDevicePolicyManager, never()).isActiveDeviceOwner(callingUid);
}
+ /**
+ * b/292163859
+ */
@Test
+ @EnableFlags(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER)
+ public void isNotificationPolicyAccessGranted_callerIsDeviceOwner_concurrent_multiUser()
+ throws Exception {
+ final String packageName = "target";
+ final int uid = 123;
+ final int callingUid = Binder.getCallingUid();
+ final var checker = mService.permissionChecker;
+
+ when(mPackageManagerClient.getPackageUidAsUser(eq(packageName), anyInt())).thenReturn(uid);
+ when(mDevicePolicyManager.isActiveDeviceOwner(callingUid)).thenReturn(true);
+
+ assertThat(mBinderService.isNotificationPolicyAccessGranted(packageName)).isFalse();
+ verify(mPackageManagerClient).getPackageUidAsUser(eq(packageName), anyInt());
+ verify(checker).check(android.Manifest.permission.MANAGE_NOTIFICATIONS, uid, -1, true);
+ verify(mConditionProviders).isPackageOrComponentAllowed(eq(packageName), anyInt());
+ verify(mListeners).isComponentEnabledForPackage(packageName, mUserId);
+ verify(mDevicePolicyManager).isActiveDeviceOwner(uid);
+ verify(mDevicePolicyManager, never()).isActiveDeviceOwner(callingUid);
+ }
+
+ @Test
+ @DisableFlags(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER)
public void isNotificationPolicyAccessGranted_notGranted() throws Exception {
final String packageName = "target";
final int uid = 123;
@@ -16447,6 +16570,24 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
}
@Test
+ @EnableFlags(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER)
+ public void isNotificationPolicyAccessGranted_notGranted_concurrent_multiUser()
+ throws Exception {
+ final String packageName = "target";
+ final int uid = 123;
+ final var checker = mService.permissionChecker;
+
+ when(mPackageManagerClient.getPackageUidAsUser(eq(packageName), anyInt())).thenReturn(uid);
+
+ assertThat(mBinderService.isNotificationPolicyAccessGranted(packageName)).isFalse();
+ verify(mPackageManagerClient).getPackageUidAsUser(eq(packageName), anyInt());
+ verify(checker).check(android.Manifest.permission.MANAGE_NOTIFICATIONS, uid, -1, true);
+ verify(mConditionProviders).isPackageOrComponentAllowed(eq(packageName), anyInt());
+ verify(mListeners).isComponentEnabledForPackage(packageName, mUserId);
+ verify(mDevicePolicyManager).isActiveDeviceOwner(uid);
+ }
+
+ @Test
public void testResetDefaultDnd() {
TestableNotificationManagerService service = spy(mService);
UserInfo user = new UserInfo(0, "owner", 0);
@@ -16481,7 +16622,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
}
@Test
- @EnableFlags(android.app.Flags.FLAG_MODES_API)
public void setDeviceEffectsApplier_succeeds() throws Exception {
initNMS(SystemService.PHASE_SYSTEM_SERVICES_READY);
@@ -16492,7 +16632,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
}
@Test
- @EnableFlags(android.app.Flags.FLAG_MODES_API)
public void setDeviceEffectsApplier_tooLate_throws() throws Exception {
initNMS(SystemService.PHASE_THIRD_PARTY_APPS_CAN_START);
@@ -16501,7 +16640,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
}
@Test
- @EnableFlags(android.app.Flags.FLAG_MODES_API)
public void setDeviceEffectsApplier_calledTwice_throws() throws Exception {
initNMS(SystemService.PHASE_SYSTEM_SERVICES_READY);
@@ -16513,7 +16651,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
@Test
@EnableCompatChanges(NotificationManagerService.MANAGE_GLOBAL_ZEN_VIA_IMPLICIT_RULES)
public void setNotificationPolicy_mappedToImplicitRule() throws RemoteException {
- mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
mService.setCallerIsNormalPackage();
ZenModeHelper zenHelper = mock(ZenModeHelper.class);
mService.mZenModeHelper = zenHelper;
@@ -16530,7 +16667,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
@Test
@EnableCompatChanges(NotificationManagerService.MANAGE_GLOBAL_ZEN_VIA_IMPLICIT_RULES)
public void setNotificationPolicy_systemCaller_setsGlobalPolicy() throws RemoteException {
- mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
ZenModeHelper zenModeHelper = mock(ZenModeHelper.class);
mService.mZenModeHelper = zenModeHelper;
when(mConditionProviders.isPackageOrComponentAllowed(anyString(), anyInt()))
@@ -16570,7 +16706,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
private void setNotificationPolicy_dependingOnCompanionAppDevice_maySetGlobalPolicy(
@AssociationRequest.DeviceProfile String deviceProfile, boolean canSetGlobalPolicy)
throws RemoteException {
- mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
mService.setCallerIsNormalPackage();
ZenModeHelper zenModeHelper = mock(ZenModeHelper.class);
mService.mZenModeHelper = zenModeHelper;
@@ -16597,7 +16732,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
@Test
@DisableCompatChanges(NotificationManagerService.MANAGE_GLOBAL_ZEN_VIA_IMPLICIT_RULES)
public void setNotificationPolicy_withoutCompat_setsGlobalPolicy() throws RemoteException {
- mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
mService.setCallerIsNormalPackage();
ZenModeHelper zenModeHelper = mock(ZenModeHelper.class);
mService.mZenModeHelper = zenModeHelper;
@@ -16613,7 +16747,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
@Test
@EnableCompatChanges(NotificationManagerService.MANAGE_GLOBAL_ZEN_VIA_IMPLICIT_RULES)
public void getNotificationPolicy_mappedFromImplicitRule() throws RemoteException {
- mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
mService.setCallerIsNormalPackage();
ZenModeHelper zenHelper = mock(ZenModeHelper.class);
mService.mZenModeHelper = zenHelper;
@@ -16628,7 +16761,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
@Test
@EnableCompatChanges(NotificationManagerService.MANAGE_GLOBAL_ZEN_VIA_IMPLICIT_RULES)
public void setInterruptionFilter_mappedToImplicitRule() throws RemoteException {
- mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
mService.setCallerIsNormalPackage();
ZenModeHelper zenHelper = mock(ZenModeHelper.class);
mService.mZenModeHelper = zenHelper;
@@ -16644,7 +16776,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
@Test
@EnableCompatChanges(NotificationManagerService.MANAGE_GLOBAL_ZEN_VIA_IMPLICIT_RULES)
public void setInterruptionFilter_systemCaller_setsGlobalPolicy() throws RemoteException {
- mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
mService.setCallerIsNormalPackage();
ZenModeHelper zenModeHelper = mock(ZenModeHelper.class);
mService.mZenModeHelper = zenModeHelper;
@@ -16683,7 +16814,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
private void setInterruptionFilter_dependingOnCompanionAppDevice_maySetGlobalZen(
@AssociationRequest.DeviceProfile String deviceProfile, boolean canSetGlobalPolicy)
throws RemoteException {
- mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
ZenModeHelper zenModeHelper = mock(ZenModeHelper.class);
mService.mZenModeHelper = zenModeHelper;
mService.setCallerIsNormalPackage();
@@ -16708,7 +16838,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
}
@Test
- @EnableFlags(android.app.Flags.FLAG_MODES_API)
@EnableCompatChanges(NotificationManagerService.MANAGE_GLOBAL_ZEN_VIA_IMPLICIT_RULES)
public void requestInterruptionFilterFromListener_fromApp_doesNotSetGlobalZen()
throws Exception {
@@ -16726,7 +16855,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
}
@Test
- @EnableFlags(android.app.Flags.FLAG_MODES_API)
@EnableCompatChanges(NotificationManagerService.MANAGE_GLOBAL_ZEN_VIA_IMPLICIT_RULES)
public void requestInterruptionFilterFromListener_fromSystem_setsGlobalZen()
throws Exception {
@@ -16745,24 +16873,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
}
@Test
- @DisableFlags(android.app.Flags.FLAG_MODES_API)
- public void requestInterruptionFilterFromListener_flagOff_callsRequestFromListener()
- throws Exception {
- mService.setCallerIsNormalPackage();
- mService.mZenModeHelper = mock(ZenModeHelper.class);
- ManagedServices.ManagedServiceInfo info = mock(ManagedServices.ManagedServiceInfo.class);
- when(mListeners.checkServiceTokenLocked(any())).thenReturn(info);
- info.component = new ComponentName("pkg", "cls");
-
- mBinderService.requestInterruptionFilterFromListener(mock(INotificationListener.class),
- INTERRUPTION_FILTER_PRIORITY);
-
- verify(mService.mZenModeHelper).requestFromListener(eq(info.component),
- eq(INTERRUPTION_FILTER_PRIORITY), eq(mUid), /* fromSystemOrSystemUi= */ eq(false));
- }
-
- @Test
- @EnableFlags(android.app.Flags.FLAG_MODES_API)
@EnableCompatChanges(NotificationManagerService.MANAGE_GLOBAL_ZEN_VIA_IMPLICIT_RULES)
public void updateAutomaticZenRule_implicitRuleWithoutCPS_disallowedFromApp() throws Exception {
setUpRealZenTest();
@@ -16788,7 +16898,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
}
@Test
- @EnableFlags(android.app.Flags.FLAG_MODES_API)
@EnableCompatChanges(NotificationManagerService.MANAGE_GLOBAL_ZEN_VIA_IMPLICIT_RULES)
public void updateAutomaticZenRule_implicitRuleWithoutCPS_allowedFromSystem() throws Exception {
setUpRealZenTest();
@@ -16814,7 +16923,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
}
@Test
- @EnableFlags({android.app.Flags.FLAG_MODES_API, android.app.Flags.FLAG_MODES_UI})
+ @EnableFlags(android.app.Flags.FLAG_MODES_UI)
public void setNotificationPolicy_fromSystemApp_appliesPriorityChannelsAllowed()
throws Exception {
setUpRealZenTest();
@@ -16844,7 +16953,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
}
@Test
- @EnableFlags({android.app.Flags.FLAG_MODES_API, android.app.Flags.FLAG_MODES_UI})
+ @EnableFlags(android.app.Flags.FLAG_MODES_UI)
@DisableCompatChanges(NotificationManagerService.MANAGE_GLOBAL_ZEN_VIA_IMPLICIT_RULES)
public void setNotificationPolicy_fromRegularAppThatCanModifyPolicy_ignoresState()
throws Exception {
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 640de174ba20..5dea44d6ebf4 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
@@ -363,7 +363,7 @@ public class PreferencesHelperTest extends UiServiceTestCase {
.when(mTestIContentProvider).uncanonicalize(any(), eq(CANONICAL_SOUND_URI));
mTestNotificationPolicy = new NotificationManager.Policy(0, 0, 0, 0,
- NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND, 0);
+ NotificationManager.Policy.STATE_HAS_PRIORITY_CHANNELS, 0);
when(mMockZenModeHelper.getNotificationPolicy(any())).thenReturn(mTestNotificationPolicy);
when(mAppOpsManager.noteOpNoThrow(anyInt(), anyInt(),
anyString(), eq(null), anyString())).thenReturn(MODE_DEFAULT);
@@ -2733,7 +2733,7 @@ public class PreferencesHelperTest extends UiServiceTestCase {
NotificationChannel channel = new NotificationChannel("id1", "name1", IMPORTANCE_LOW);
mHelper.createNotificationChannel(PKG_N_MR1, uid, channel, true, false,
uid, false);
- assertFalse(mHelper.areChannelsBypassingDnd());
+ assertFalse(mHelper.hasPriorityChannels());
if (android.app.Flags.modesUi()) {
verify(mMockZenModeHelper, never()).updateHasPriorityChannels(any(), anyBoolean());
} else {
@@ -2748,7 +2748,7 @@ public class PreferencesHelperTest extends UiServiceTestCase {
channel2.setBypassDnd(true);
mHelper.createNotificationChannel(PKG_N_MR1, uid, channel2, true, true,
uid, false);
- assertTrue(mHelper.areChannelsBypassingDnd());
+ assertTrue(mHelper.hasPriorityChannels());
if (android.app.Flags.modesUi()) {
verify(mMockZenModeHelper, times(1)).updateHasPriorityChannels(eq(UserHandle.CURRENT),
eq(true));
@@ -2760,7 +2760,7 @@ public class PreferencesHelperTest extends UiServiceTestCase {
// delete channels
mHelper.deleteNotificationChannel(PKG_N_MR1, uid, channel.getId(), uid, false);
- assertTrue(mHelper.areChannelsBypassingDnd()); // channel2 can still bypass DND
+ assertTrue(mHelper.hasPriorityChannels()); // channel2 can still bypass DND
if (android.app.Flags.modesUi()) {
verify(mMockZenModeHelper, never()).updateHasPriorityChannels(any(), anyBoolean());
} else {
@@ -2770,7 +2770,7 @@ public class PreferencesHelperTest extends UiServiceTestCase {
resetZenModeHelper();
mHelper.deleteNotificationChannel(PKG_N_MR1, uid, channel2.getId(), uid, false);
- assertFalse(mHelper.areChannelsBypassingDnd());
+ assertFalse(mHelper.hasPriorityChannels());
if (android.app.Flags.modesUi()) {
verify(mMockZenModeHelper, times(1)).updateHasPriorityChannels(eq(UserHandle.CURRENT),
eq(false));
@@ -2792,7 +2792,7 @@ public class PreferencesHelperTest extends UiServiceTestCase {
NotificationChannel channel = new NotificationChannel("id1", "name1", IMPORTANCE_LOW);
mHelper.createNotificationChannel(PKG_N_MR1, uid, channel, true, false,
uid, false);
- assertFalse(mHelper.areChannelsBypassingDnd());
+ assertFalse(mHelper.hasPriorityChannels());
if (android.app.Flags.modesUi()) {
verify(mMockZenModeHelper, never()).updateHasPriorityChannels(any(), anyBoolean());
} else {
@@ -2807,7 +2807,7 @@ public class PreferencesHelperTest extends UiServiceTestCase {
mHelper.createNotificationChannel(PKG_N_MR1, uid, update, true, true,
uid, false);
- assertTrue(mHelper.areChannelsBypassingDnd());
+ assertTrue(mHelper.hasPriorityChannels());
if (android.app.Flags.modesUi()) {
verify(mMockZenModeHelper, times(1)).updateHasPriorityChannels(eq(UserHandle.CURRENT),
eq(true));
@@ -2829,7 +2829,7 @@ public class PreferencesHelperTest extends UiServiceTestCase {
NotificationChannel channel = new NotificationChannel("id1", "name1", IMPORTANCE_LOW);
mHelper.createNotificationChannel(PKG_N_MR1, uid, channel, true, false,
uid, false);
- assertFalse(mHelper.areChannelsBypassingDnd());
+ assertFalse(mHelper.hasPriorityChannels());
if (android.app.Flags.modesUi()) {
verify(mMockZenModeHelper, never()).updateHasPriorityChannels(any(), anyBoolean());
} else {
@@ -2844,7 +2844,7 @@ public class PreferencesHelperTest extends UiServiceTestCase {
channel2.setBypassDnd(true);
mHelper.createNotificationChannel(PKG_N_MR1, uid, channel2, true, true,
uid, false);
- assertTrue(mHelper.areChannelsBypassingDnd());
+ assertTrue(mHelper.hasPriorityChannels());
if (android.app.Flags.modesUi()) {
verify(mMockZenModeHelper, times(1)).updateHasPriorityChannels(eq(UserHandle.CURRENT),
eq(true));
@@ -2856,7 +2856,7 @@ public class PreferencesHelperTest extends UiServiceTestCase {
// delete channels
mHelper.deleteNotificationChannel(PKG_N_MR1, uid, channel.getId(), uid, false);
- assertTrue(mHelper.areChannelsBypassingDnd()); // channel2 can still bypass DND
+ assertTrue(mHelper.hasPriorityChannels()); // channel2 can still bypass DND
if (android.app.Flags.modesUi()) {
verify(mMockZenModeHelper, never()).updateHasPriorityChannels(any(), anyBoolean());
} else {
@@ -2866,7 +2866,7 @@ public class PreferencesHelperTest extends UiServiceTestCase {
resetZenModeHelper();
mHelper.deleteNotificationChannel(PKG_N_MR1, uid, channel2.getId(), uid, false);
- assertFalse(mHelper.areChannelsBypassingDnd());
+ assertFalse(mHelper.hasPriorityChannels());
if (android.app.Flags.modesUi()) {
verify(mMockZenModeHelper, times(1)).updateHasPriorityChannels(eq(UserHandle.CURRENT),
eq(false));
@@ -2884,9 +2884,9 @@ public class PreferencesHelperTest extends UiServiceTestCase {
// start in a 'allowed to bypass dnd state'
mTestNotificationPolicy = new NotificationManager.Policy(0, 0, 0, 0,
- NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND, 0);
+ NotificationManager.Policy.STATE_HAS_PRIORITY_CHANNELS, 0);
when(mMockZenModeHelper.getNotificationPolicy(any())).thenReturn(mTestNotificationPolicy);
- mHelper.syncChannelsBypassingDnd();
+ mHelper.syncHasPriorityChannels();
// create notification channel that can bypass dnd, but app is blocked
// expected result: areChannelsBypassingDnd = false
@@ -2899,7 +2899,7 @@ public class PreferencesHelperTest extends UiServiceTestCase {
channel2.setBypassDnd(true);
mHelper.createNotificationChannel(PKG_N_MR1, uid, channel2, true, true,
uid, false);
- assertFalse(mHelper.areChannelsBypassingDnd());
+ assertFalse(mHelper.hasPriorityChannels());
if (android.app.Flags.modesUi()) {
verify(mMockZenModeHelper, times(1)).updateHasPriorityChannels(eq(UserHandle.CURRENT),
eq(false));
@@ -2917,9 +2917,9 @@ public class PreferencesHelperTest extends UiServiceTestCase {
// start in a 'allowed to bypass dnd state'
mTestNotificationPolicy = new NotificationManager.Policy(0, 0, 0, 0,
- NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND, 0);
+ NotificationManager.Policy.STATE_HAS_PRIORITY_CHANNELS, 0);
when(mMockZenModeHelper.getNotificationPolicy(any())).thenReturn(mTestNotificationPolicy);
- mHelper.syncChannelsBypassingDnd();
+ mHelper.syncHasPriorityChannels();
// create notification channel that can bypass dnd, but app is blocked
// expected result: areChannelsBypassingDnd = false
@@ -2927,7 +2927,7 @@ public class PreferencesHelperTest extends UiServiceTestCase {
channel2.setBypassDnd(true);
mHelper.createNotificationChannel(PKG_N_MR1, uid, channel2, true, true,
uid, false);
- assertFalse(mHelper.areChannelsBypassingDnd());
+ assertFalse(mHelper.hasPriorityChannels());
if (android.app.Flags.modesUi()) {
verify(mMockZenModeHelper, times(1)).updateHasPriorityChannels(eq(UserHandle.CURRENT),
eq(false));
@@ -2945,9 +2945,9 @@ public class PreferencesHelperTest extends UiServiceTestCase {
// start in a 'allowed to bypass dnd state'
mTestNotificationPolicy = new NotificationManager.Policy(0, 0, 0, 0,
- NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND, 0);
+ NotificationManager.Policy.STATE_HAS_PRIORITY_CHANNELS, 0);
when(mMockZenModeHelper.getNotificationPolicy(any())).thenReturn(mTestNotificationPolicy);
- mHelper.syncChannelsBypassingDnd();
+ mHelper.syncHasPriorityChannels();
// create notification channel that can bypass dnd, but app is blocked
// expected result: areChannelsBypassingDnd = false
@@ -2955,7 +2955,7 @@ public class PreferencesHelperTest extends UiServiceTestCase {
channel2.setBypassDnd(true);
mHelper.createNotificationChannel(PKG_N_MR1, uid, channel2, true, true,
uid, false);
- assertFalse(mHelper.areChannelsBypassingDnd());
+ assertFalse(mHelper.hasPriorityChannels());
if (android.app.Flags.modesUi()) {
verify(mMockZenModeHelper, times(1)).updateHasPriorityChannels(eq(UserHandle.CURRENT),
eq(false));
@@ -2977,7 +2977,7 @@ public class PreferencesHelperTest extends UiServiceTestCase {
NotificationChannel channel = new NotificationChannel("id1", "name1", IMPORTANCE_LOW);
mHelper.createNotificationChannel(PKG_N_MR1, uid, channel, true, false,
uid, false);
- assertFalse(mHelper.areChannelsBypassingDnd());
+ assertFalse(mHelper.hasPriorityChannels());
if (android.app.Flags.modesUi()) {
verify(mMockZenModeHelper, never()).updateHasPriorityChannels(any(), anyBoolean());
} else {
@@ -2990,7 +2990,7 @@ public class PreferencesHelperTest extends UiServiceTestCase {
// expected result: areChannelsBypassingDnd = true
channel.setBypassDnd(true);
mHelper.updateNotificationChannel(PKG_N_MR1, uid, channel, true, SYSTEM_UID, true);
- assertTrue(mHelper.areChannelsBypassingDnd());
+ assertTrue(mHelper.hasPriorityChannels());
if (android.app.Flags.modesUi()) {
verify(mMockZenModeHelper, times(1)).updateHasPriorityChannels(eq(UserHandle.CURRENT),
eq(true));
@@ -3004,7 +3004,7 @@ public class PreferencesHelperTest extends UiServiceTestCase {
// expected result: areChannelsBypassingDnd = false
channel.setBypassDnd(false);
mHelper.updateNotificationChannel(PKG_N_MR1, uid, channel, true, SYSTEM_UID, true);
- assertFalse(mHelper.areChannelsBypassingDnd());
+ assertFalse(mHelper.hasPriorityChannels());
if (android.app.Flags.modesUi()) {
verify(mMockZenModeHelper, times(1)).updateHasPriorityChannels(eq(UserHandle.CURRENT),
eq(false));
@@ -3020,10 +3020,10 @@ public class PreferencesHelperTest extends UiServiceTestCase {
// start notification policy off with mAreChannelsBypassingDnd = true, but
// RankingHelper should change to false
mTestNotificationPolicy = new NotificationManager.Policy(0, 0, 0, 0,
- NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND, 0);
+ NotificationManager.Policy.STATE_HAS_PRIORITY_CHANNELS, 0);
when(mMockZenModeHelper.getNotificationPolicy(any())).thenReturn(mTestNotificationPolicy);
- mHelper.syncChannelsBypassingDnd();
- assertFalse(mHelper.areChannelsBypassingDnd());
+ mHelper.syncHasPriorityChannels();
+ assertFalse(mHelper.hasPriorityChannels());
if (android.app.Flags.modesUi()) {
verify(mMockZenModeHelper, times(1)).updateHasPriorityChannels(eq(UserHandle.CURRENT),
eq(false));
@@ -3039,7 +3039,7 @@ public class PreferencesHelperTest extends UiServiceTestCase {
// start notification policy off with mAreChannelsBypassingDnd = false
mTestNotificationPolicy = new NotificationManager.Policy(0, 0, 0, 0, 0, 0);
when(mMockZenModeHelper.getNotificationPolicy(any())).thenReturn(mTestNotificationPolicy);
- assertFalse(mHelper.areChannelsBypassingDnd());
+ assertFalse(mHelper.hasPriorityChannels());
if (android.app.Flags.modesUi()) {
verify(mMockZenModeHelper, never()).updateHasPriorityChannels(any(), anyBoolean());
} else {
@@ -3050,7 +3050,7 @@ public class PreferencesHelperTest extends UiServiceTestCase {
}
@Test
- public void syncChannelsBypassingDnd_includesProfilesOfCurrentUser() throws Exception {
+ public void syncHasPriorityChannels_includesProfilesOfCurrentUser() throws Exception {
when(mUserProfiles.getCurrentProfileIds()).thenReturn(IntArray.wrap(new int[] {0, 10}));
when(mPermissionHelper.hasPermission(anyInt())).thenReturn(true);
ApplicationInfo appInfo = new ApplicationInfo();
@@ -3067,13 +3067,13 @@ public class PreferencesHelperTest extends UiServiceTestCase {
mHelper.createNotificationChannel("com.example", UserHandle.getUid(10, 444), withBypass,
false, false, Process.SYSTEM_UID, true);
- mHelper.syncChannelsBypassingDnd();
+ mHelper.syncHasPriorityChannels();
- assertThat(mHelper.areChannelsBypassingDnd()).isTrue();
+ assertThat(mHelper.hasPriorityChannels()).isTrue();
}
@Test
- public void syncChannelsBypassingDnd_excludesOtherUsers() throws Exception {
+ public void syncHasPriorityChannels_excludesOtherUsers() throws Exception {
when(mUserProfiles.getCurrentProfileIds()).thenReturn(IntArray.wrap(new int[] {0}));
when(mPermissionHelper.hasPermission(anyInt())).thenReturn(true);
ApplicationInfo appInfo = new ApplicationInfo();
@@ -3090,9 +3090,9 @@ public class PreferencesHelperTest extends UiServiceTestCase {
mHelper.createNotificationChannel("com.example", UserHandle.getUid(10, 444), withBypass,
false, false, Process.SYSTEM_UID, true);
- mHelper.syncChannelsBypassingDnd();
+ mHelper.syncHasPriorityChannels();
- assertThat(mHelper.areChannelsBypassingDnd()).isFalse();
+ assertThat(mHelper.hasPriorityChannels()).isFalse();
}
@Test
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/RankingHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/RankingHelperTest.java
index f90034614383..ec428d506e7b 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/RankingHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/RankingHelperTest.java
@@ -17,9 +17,10 @@ package com.android.server.notification;
import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
import static android.app.NotificationManager.IMPORTANCE_LOW;
-
import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT;
+
import static com.google.common.truth.Truth.assertThat;
+
import static junit.framework.TestCase.assertEquals;
import static org.junit.Assert.assertTrue;
@@ -29,7 +30,6 @@ import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
-import android.app.Flags;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
@@ -40,7 +40,6 @@ import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.Signature;
-import android.media.AudioAttributes;
import android.net.Uri;
import android.os.Build;
import android.os.UserHandle;
@@ -67,7 +66,6 @@ import org.mockito.MockitoAnnotations;
import java.util.ArrayList;
import java.util.Collections;
-import java.util.List;
@SmallTest
@RunWith(AndroidJUnit4.class)
@@ -154,7 +152,7 @@ public class RankingHelperTest extends UiServiceTestCase {
.thenReturn(SOUND_URI);
mTestNotificationPolicy = new NotificationManager.Policy(0, 0, 0, 0,
- NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND, 0);
+ NotificationManager.Policy.STATE_HAS_PRIORITY_CHANNELS, 0);
when(mMockZenModeHelper.getNotificationPolicy(any())).thenReturn(mTestNotificationPolicy);
mHelper = new RankingHelper(getContext(), mHandler, mConfig, mMockZenModeHelper,
mUsageStats, new String[] {ImportanceExtractor.class.getName()},
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenAdaptersTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenAdaptersTest.java
index 75552bc433c5..f3813437a9c5 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenAdaptersTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenAdaptersTest.java
@@ -20,10 +20,7 @@ import static android.service.notification.ZenAdapters.notificationPolicyToZenPo
import static com.google.common.truth.Truth.assertThat;
-import android.app.Flags;
import android.app.NotificationManager.Policy;
-import android.platform.test.annotations.DisableFlags;
-import android.platform.test.annotations.EnableFlags;
import android.platform.test.flag.junit.SetFlagsRule;
import android.service.notification.ZenPolicy;
@@ -137,8 +134,7 @@ public class ZenAdaptersTest extends UiServiceTestCase {
}
@Test
- @EnableFlags(Flags.FLAG_MODES_API)
- public void notificationPolicyToZenPolicy_modesApi_priorityChannels() {
+ public void notificationPolicyToZenPolicy_priorityChannels() {
Policy policy = new Policy(0, 0, 0, 0,
Policy.policyState(false, true), 0);
@@ -151,20 +147,4 @@ public class ZenAdaptersTest extends UiServiceTestCase {
assertThat(zenPolicyNotAllowed.getPriorityChannelsAllowed()).isEqualTo(
ZenPolicy.STATE_DISALLOW);
}
-
- @Test
- @DisableFlags(Flags.FLAG_MODES_API)
- public void notificationPolicyToZenPolicy_noModesApi_priorityChannelsUnset() {
- Policy policy = new Policy(0, 0, 0, 0,
- Policy.policyState(false, true), 0);
-
- ZenPolicy zenPolicy = notificationPolicyToZenPolicy(policy);
- assertThat(zenPolicy.getPriorityChannelsAllowed()).isEqualTo(ZenPolicy.STATE_UNSET);
-
- Policy notAllowed = new Policy(0, 0, 0, 0,
- Policy.policyState(false, false), 0);
- ZenPolicy zenPolicyNotAllowed = notificationPolicyToZenPolicy(notAllowed);
- assertThat(zenPolicyNotAllowed.getPriorityChannelsAllowed()).isEqualTo(
- ZenPolicy.STATE_UNSET);
- }
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenDeviceEffectsTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenDeviceEffectsTest.java
index af911e811e5e..9a2b748a9bcc 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenDeviceEffectsTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenDeviceEffectsTest.java
@@ -20,7 +20,6 @@ import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertThrows;
-import android.app.Flags;
import android.os.Parcel;
import android.platform.test.flag.junit.SetFlagsRule;
import android.service.notification.ZenDeviceEffects;
@@ -31,7 +30,6 @@ import com.android.server.UiServiceTestCase;
import com.google.common.collect.ImmutableSet;
-import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -42,11 +40,6 @@ public class ZenDeviceEffectsTest extends UiServiceTestCase {
@Rule
public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
- @Before
- public final void setUp() {
- mSetFlagsRule.enableFlags(Flags.FLAG_MODES_API);
- }
-
@Test
public void builder() {
ZenDeviceEffects deviceEffects =
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java
index b42a6a5a7382..67efb9e76692 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java
@@ -174,7 +174,7 @@ public class ZenModeConfigTest extends UiServiceTestCase {
}
assertTrue(ZenModeConfig.areAllPriorityOnlyRingerSoundsMuted(config));
- config.areChannelsBypassingDnd = true;
+ config.hasPriorityChannels = true;
assertTrue(ZenModeConfig.areAllPriorityOnlyRingerSoundsMuted(config));
if (Flags.modesUi()) {
@@ -187,7 +187,7 @@ public class ZenModeConfigTest extends UiServiceTestCase {
assertFalse(ZenModeConfig.areAllPriorityOnlyRingerSoundsMuted(config));
- config.areChannelsBypassingDnd = false;
+ config.hasPriorityChannels = false;
if (Flags.modesUi()) {
config.manualRule.zenPolicy = new ZenPolicy.Builder(config.manualRule.zenPolicy)
.allowPriorityChannels(false)
@@ -417,7 +417,7 @@ public class ZenModeConfigTest extends UiServiceTestCase {
assertTrue(ZenModeConfig.areAllPriorityOnlyRingerSoundsMuted(config));
assertTrue(ZenModeConfig.areAllZenBehaviorSoundsMuted(config));
- config.areChannelsBypassingDnd = true;
+ config.hasPriorityChannels = true;
if (Flags.modesUi()) {
config.manualRule.zenPolicy = new ZenPolicy.Builder(config.manualRule.zenPolicy)
.allowPriorityChannels(true)
@@ -429,7 +429,7 @@ public class ZenModeConfigTest extends UiServiceTestCase {
assertFalse(ZenModeConfig.areAllPriorityOnlyRingerSoundsMuted(config));
assertFalse(ZenModeConfig.areAllZenBehaviorSoundsMuted(config));
- config.areChannelsBypassingDnd = false;
+ config.hasPriorityChannels = false;
if (Flags.modesUi()) {
config.manualRule.zenPolicy = new ZenPolicy.Builder(config.manualRule.zenPolicy)
.allowPriorityChannels(false)
@@ -488,33 +488,33 @@ public class ZenModeConfigTest extends UiServiceTestCase {
ZenModeConfig.ZenRule rule = new ZenModeConfig.ZenRule();
rule.zenPolicy = null;
rule.zenDeviceEffects = null;
- assertThat(rule.canBeUpdatedByApp()).isTrue();
+ assertThat(rule.isUserModified()).isFalse();
rule.userModifiedFields = 1;
- assertThat(rule.canBeUpdatedByApp()).isFalse();
+ assertThat(rule.isUserModified()).isTrue();
}
@Test
public void testCanBeUpdatedByApp_policyModified() throws Exception {
ZenModeConfig.ZenRule rule = new ZenModeConfig.ZenRule();
rule.zenPolicy = new ZenPolicy();
- assertThat(rule.canBeUpdatedByApp()).isTrue();
+ assertThat(rule.isUserModified()).isFalse();
rule.zenPolicyUserModifiedFields = 1;
- assertThat(rule.canBeUpdatedByApp()).isFalse();
+ assertThat(rule.isUserModified()).isTrue();
}
@Test
public void testCanBeUpdatedByApp_deviceEffectsModified() throws Exception {
ZenModeConfig.ZenRule rule = new ZenModeConfig.ZenRule();
rule.zenDeviceEffects = new ZenDeviceEffects.Builder().build();
- assertThat(rule.canBeUpdatedByApp()).isTrue();
+ assertThat(rule.isUserModified()).isFalse();
rule.zenDeviceEffectsUserModifiedFields = 1;
- assertThat(rule.canBeUpdatedByApp()).isFalse();
+ assertThat(rule.isUserModified()).isTrue();
}
@Test
@@ -548,7 +548,6 @@ public class ZenModeConfigTest extends UiServiceTestCase {
rule.creationTime = 123;
rule.id = "id";
rule.zenMode = INTERRUPTION_FILTER;
- rule.modified = true;
rule.name = NAME;
rule.setConditionOverride(OVERRIDE_DEACTIVATE);
rule.pkg = OWNER.getPackageName();
@@ -564,6 +563,9 @@ public class ZenModeConfigTest extends UiServiceTestCase {
rule.deletionInstant = Instant.ofEpochMilli(1701790147000L);
if (Flags.modesUi()) {
rule.disabledOrigin = ZenModeConfig.ORIGIN_USER_IN_SYSTEMUI;
+ if (Flags.modesCleanupImplicit()) {
+ rule.lastActivation = Instant.ofEpochMilli(456);
+ }
}
config.automaticRules.put(rule.id, rule);
@@ -585,7 +587,6 @@ public class ZenModeConfigTest extends UiServiceTestCase {
assertEquals(rule.condition, ruleActual.condition);
assertEquals(rule.enabled, ruleActual.enabled);
assertEquals(rule.creationTime, ruleActual.creationTime);
- assertEquals(rule.modified, ruleActual.modified);
assertEquals(rule.conditionId, ruleActual.conditionId);
assertEquals(rule.name, ruleActual.name);
assertEquals(rule.zenMode, ruleActual.zenMode);
@@ -602,6 +603,9 @@ public class ZenModeConfigTest extends UiServiceTestCase {
assertEquals(rule.deletionInstant, ruleActual.deletionInstant);
if (Flags.modesUi()) {
assertEquals(rule.disabledOrigin, ruleActual.disabledOrigin);
+ if (Flags.modesCleanupImplicit()) {
+ assertEquals(rule.lastActivation, ruleActual.lastActivation);
+ }
}
if (Flags.backupRestoreLogging()) {
verify(logger).logItemsBackedUp(DATA_TYPE_ZEN_RULES, 2);
@@ -620,7 +624,6 @@ public class ZenModeConfigTest extends UiServiceTestCase {
rule.creationTime = 123;
rule.id = "id";
rule.zenMode = INTERRUPTION_FILTER;
- rule.modified = true;
rule.name = NAME;
rule.setConditionOverride(OVERRIDE_DEACTIVATE);
rule.pkg = OWNER.getPackageName();
@@ -636,6 +639,9 @@ public class ZenModeConfigTest extends UiServiceTestCase {
rule.deletionInstant = Instant.ofEpochMilli(1701790147000L);
if (Flags.modesUi()) {
rule.disabledOrigin = ZenModeConfig.ORIGIN_USER_IN_SYSTEMUI;
+ if (Flags.modesCleanupImplicit()) {
+ rule.lastActivation = Instant.ofEpochMilli(789);
+ }
}
Parcel parcel = Parcel.obtain();
@@ -651,7 +657,6 @@ public class ZenModeConfigTest extends UiServiceTestCase {
assertEquals(rule.condition, parceled.condition);
assertEquals(rule.enabled, parceled.enabled);
assertEquals(rule.creationTime, parceled.creationTime);
- assertEquals(rule.modified, parceled.modified);
assertEquals(rule.conditionId, parceled.conditionId);
assertEquals(rule.name, parceled.name);
assertEquals(rule.zenMode, parceled.zenMode);
@@ -668,6 +673,9 @@ public class ZenModeConfigTest extends UiServiceTestCase {
assertEquals(rule.deletionInstant, parceled.deletionInstant);
if (Flags.modesUi()) {
assertEquals(rule.disabledOrigin, parceled.disabledOrigin);
+ if (Flags.modesCleanupImplicit()) {
+ assertEquals(rule.lastActivation, parceled.lastActivation);
+ }
}
assertEquals(rule, parceled);
@@ -685,7 +693,6 @@ public class ZenModeConfigTest extends UiServiceTestCase {
rule.creationTime = 123;
rule.id = "id";
rule.zenMode = Settings.Global.ZEN_MODE_ALARMS;
- rule.modified = true;
rule.name = "name";
rule.snoozing = true;
rule.pkg = "b";
@@ -705,7 +712,6 @@ public class ZenModeConfigTest extends UiServiceTestCase {
assertEquals(rule.condition, fromXml.condition);
assertEquals(rule.enabled, fromXml.enabled);
assertEquals(rule.creationTime, fromXml.creationTime);
- assertEquals(rule.modified, fromXml.modified);
assertEquals(rule.conditionId, fromXml.conditionId);
assertEquals(rule.name, fromXml.name);
assertEquals(rule.zenMode, fromXml.zenMode);
@@ -721,7 +727,6 @@ public class ZenModeConfigTest extends UiServiceTestCase {
rule.enabled = ENABLED;
rule.id = "id";
rule.zenMode = INTERRUPTION_FILTER;
- rule.modified = true;
rule.name = NAME;
rule.setConditionOverride(OVERRIDE_DEACTIVATE);
rule.pkg = OWNER.getPackageName();
@@ -753,6 +758,9 @@ public class ZenModeConfigTest extends UiServiceTestCase {
rule.deletionInstant = Instant.ofEpochMilli(1701790147000L);
if (Flags.modesUi()) {
rule.disabledOrigin = ZenModeConfig.ORIGIN_APP;
+ if (Flags.modesCleanupImplicit()) {
+ rule.lastActivation = Instant.ofEpochMilli(123);
+ }
}
ByteArrayOutputStream baos = new ByteArrayOutputStream();
@@ -770,7 +778,6 @@ public class ZenModeConfigTest extends UiServiceTestCase {
assertEquals(rule.condition, fromXml.condition);
assertEquals(rule.enabled, fromXml.enabled);
assertEquals(rule.creationTime, fromXml.creationTime);
- assertEquals(rule.modified, fromXml.modified);
assertEquals(rule.conditionId, fromXml.conditionId);
assertEquals(rule.name, fromXml.name);
assertEquals(rule.zenMode, fromXml.zenMode);
@@ -789,6 +796,9 @@ public class ZenModeConfigTest extends UiServiceTestCase {
assertEquals(rule.deletionInstant, fromXml.deletionInstant);
if (Flags.modesUi()) {
assertEquals(rule.disabledOrigin, fromXml.disabledOrigin);
+ if (Flags.modesCleanupImplicit()) {
+ assertEquals(rule.lastActivation, fromXml.lastActivation);
+ }
}
}
@@ -916,7 +926,7 @@ public class ZenModeConfigTest extends UiServiceTestCase {
ZenModeConfig.ZenRule rule = new ZenModeConfig.ZenRule();
rule.userModifiedFields |= AutomaticZenRule.FIELD_NAME;
assertThat(rule.userModifiedFields).isEqualTo(1);
- assertThat(rule.canBeUpdatedByApp()).isFalse();
+ assertThat(rule.isUserModified()).isTrue();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
writeRuleXml(rule, baos);
@@ -924,7 +934,7 @@ public class ZenModeConfigTest extends UiServiceTestCase {
ZenModeConfig.ZenRule fromXml = readRuleXml(bais);
assertThat(fromXml.userModifiedFields).isEqualTo(rule.userModifiedFields);
- assertThat(fromXml.canBeUpdatedByApp()).isFalse();
+ assertThat(fromXml.isUserModified()).isTrue();
}
@Test
@@ -1259,7 +1269,6 @@ public class ZenModeConfigTest extends UiServiceTestCase {
rule.creationTime = 123;
rule.id = "id";
rule.zenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS;
- rule.modified = true;
rule.name = "name";
rule.pkg = "b";
config.automaticRules.put("key", rule);
@@ -1348,7 +1357,7 @@ public class ZenModeConfigTest extends UiServiceTestCase {
config.setSuppressedVisualEffects(0);
config.setAllowPriorityChannels(false);
}
- config.areChannelsBypassingDnd = false;
+ config.hasPriorityChannels = false;
return config;
}
@@ -1383,7 +1392,7 @@ public class ZenModeConfigTest extends UiServiceTestCase {
config.setSuppressedVisualEffects(0);
config.setAllowPriorityChannels(true);
}
- config.areChannelsBypassingDnd = false;
+ config.hasPriorityChannels = false;
return config;
}
@@ -1410,7 +1419,7 @@ public class ZenModeConfigTest extends UiServiceTestCase {
config.setAllowConversationsFrom(CONVERSATION_SENDERS_NONE);
config.setSuppressedVisualEffects(0);
}
- config.areChannelsBypassingDnd = false;
+ config.hasPriorityChannels = false;
return config;
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeDiffTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeDiffTest.java
index b138c72875a6..6d0bf8b322fd 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeDiffTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeDiffTest.java
@@ -16,7 +16,6 @@
package com.android.server.notification;
-import static android.app.Flags.FLAG_MODES_API;
import static android.app.Flags.FLAG_MODES_UI;
import static com.google.common.truth.Truth.assertThat;
@@ -64,7 +63,6 @@ import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
-import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Optional;
@@ -78,20 +76,14 @@ public class ZenModeDiffTest extends UiServiceTestCase {
// version is not included in the diff; manual & automatic rules have special handling;
// deleted rules are not included in the diff.
public static final Set<String> ZEN_MODE_CONFIG_EXEMPT_FIELDS =
- android.app.Flags.modesApi()
- ? Set.of("version", "manualRule", "automaticRules", "deletedRules")
- : Set.of("version", "manualRule", "automaticRules");
-
- // allowPriorityChannels is flagged by android.app.modes_api
- public static final Set<String> ZEN_MODE_CONFIG_FLAGGED_FIELDS =
- Set.of("allowPriorityChannels");
+ Set.of("version", "manualRule", "automaticRules", "deletedRules");
@Rule
public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
@Parameters(name = "{0}")
public static List<FlagsParameterization> getParams() {
- return FlagsParameterization.progressionOf(FLAG_MODES_API, FLAG_MODES_UI);
+ return FlagsParameterization.progressionOf(FLAG_MODES_UI);
}
public ZenModeDiffTest(FlagsParameterization flags) {
@@ -147,7 +139,7 @@ public class ZenModeDiffTest extends UiServiceTestCase {
}
@Test
- @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI})
+ @EnableFlags(FLAG_MODES_UI)
public void testRuleDiff_toStringNoChangeAddRemove() throws Exception {
// Start with two identical rules
ZenModeConfig.ZenRule r1 = makeRule();
@@ -164,7 +156,7 @@ public class ZenModeDiffTest extends UiServiceTestCase {
}
@Test
- @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI})
+ @EnableFlags(FLAG_MODES_UI)
public void testRuleDiff_toString() throws Exception {
// Start with two identical rules
ZenModeConfig.ZenRule r1 = makeRule();
@@ -218,7 +210,6 @@ public class ZenModeDiffTest extends UiServiceTestCase {
+ "mPriorityCalls:2->1, "
+ "mConversationSenders:2->1, "
+ "mAllowChannels:2->1}, "
- + "modified:true->false, "
+ "pkg:string1->string2, "
+ "zenDeviceEffects:ZenDeviceEffectsDiff{"
+ "mGrayscale:true->false, "
@@ -241,7 +232,7 @@ public class ZenModeDiffTest extends UiServiceTestCase {
}
@Test
- @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI})
+ @EnableFlags(FLAG_MODES_UI)
public void testRuleDiff_toStringNullStartPolicy() throws Exception {
// Start with two identical rules
ZenModeConfig.ZenRule r1 = makeRule();
@@ -278,7 +269,6 @@ public class ZenModeDiffTest extends UiServiceTestCase {
+ "creationTime:200->100, "
+ "enabler:string1->string2, "
+ "zenPolicy:ZenPolicyDiff{added}, "
- + "modified:true->false, "
+ "pkg:string1->string2, "
+ "zenDeviceEffects:ZenDeviceEffectsDiff{added}, "
+ "triggerDescription:string1->string2, "
@@ -485,16 +475,10 @@ public class ZenModeDiffTest extends UiServiceTestCase {
// "Metadata" fields are never compared.
Set<String> exemptFields = new LinkedHashSet<>(
Set.of("userModifiedFields", "zenPolicyUserModifiedFields",
- "zenDeviceEffectsUserModifiedFields", "deletionInstant", "disabledOrigin"));
+ "zenDeviceEffectsUserModifiedFields", "deletionInstant", "disabledOrigin",
+ "lastActivation"));
// Flagged fields are only compared if their flag is on.
- if (!Flags.modesApi()) {
- exemptFields.addAll(
- Set.of(RuleDiff.FIELD_TYPE, RuleDiff.FIELD_TRIGGER_DESCRIPTION,
- RuleDiff.FIELD_ICON_RES, RuleDiff.FIELD_ALLOW_MANUAL,
- RuleDiff.FIELD_ZEN_DEVICE_EFFECTS,
- RuleDiff.FIELD_LEGACY_SUPPRESSED_EFFECTS));
- }
- if (Flags.modesApi() && Flags.modesUi()) {
+ if (Flags.modesUi()) {
exemptFields.add(RuleDiff.FIELD_SNOOZING); // Obsolete.
} else {
exemptFields.add(RuleDiff.FIELD_CONDITION_OVERRIDE);
@@ -530,35 +514,6 @@ public class ZenModeDiffTest extends UiServiceTestCase {
ArrayMap<String, Object> expectedFrom = new ArrayMap<>();
ArrayMap<String, Object> expectedTo = new ArrayMap<>();
List<Field> fieldsForDiff = getFieldsForDiffCheck(
- ZenModeConfig.class, getConfigExemptAndFlaggedFields(), false);
- generateFieldDiffs(c1, c2, fieldsForDiff, expectedFrom, expectedTo);
-
- ZenModeDiff.ConfigDiff d = new ZenModeDiff.ConfigDiff(c1, c2);
- assertTrue(d.hasDiff());
-
- // Now diff them and check that each of the fields has a diff
- for (Field f : fieldsForDiff) {
- String name = f.getName();
- assertNotNull("diff not found for field: " + name, d.getDiffForField(name));
- assertTrue(d.getDiffForField(name).hasDiff());
- assertTrue("unexpected field: " + name, expectedFrom.containsKey(name));
- assertTrue("unexpected field: " + name, expectedTo.containsKey(name));
- assertEquals(expectedFrom.get(name), d.getDiffForField(name).from());
- assertEquals(expectedTo.get(name), d.getDiffForField(name).to());
- }
- }
-
- @Test
- @EnableFlags(FLAG_MODES_API)
- public void testConfigDiff_fieldDiffs_flagOn() throws Exception {
- // these two start the same
- ZenModeConfig c1 = new ZenModeConfig();
- ZenModeConfig c2 = new ZenModeConfig();
-
- // maps mapping field name -> expected output value as we set diffs
- ArrayMap<String, Object> expectedFrom = new ArrayMap<>();
- ArrayMap<String, Object> expectedTo = new ArrayMap<>();
- List<Field> fieldsForDiff = getFieldsForDiffCheck(
ZenModeConfig.class, ZEN_MODE_CONFIG_EXEMPT_FIELDS, false);
generateFieldDiffs(c1, c2, fieldsForDiff, expectedFrom, expectedTo);
@@ -656,14 +611,6 @@ public class ZenModeDiffTest extends UiServiceTestCase {
assertEquals("different", automaticDiffs.get("ruleId").getDiffForField("pkg").to());
}
- // Helper method that merges the base exempt fields with fields that are flagged
- private Set getConfigExemptAndFlaggedFields() {
- Set merged = new HashSet();
- merged.addAll(ZEN_MODE_CONFIG_EXEMPT_FIELDS);
- merged.addAll(ZEN_MODE_CONFIG_FLAGGED_FIELDS);
- return merged;
- }
-
// Helper methods for working with configs, policies, rules
// Just makes a zen rule with fields filled in
private ZenModeConfig.ZenRule makeRule() {
@@ -676,20 +623,17 @@ public class ZenModeDiffTest extends UiServiceTestCase {
rule.creationTime = 123;
rule.id = "ruleId";
rule.zenMode = Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
- rule.modified = false;
rule.name = "name";
rule.setConditionOverride(ZenModeConfig.ZenRule.OVERRIDE_DEACTIVATE);
rule.pkg = "a";
- if (android.app.Flags.modesApi()) {
- rule.allowManualInvocation = true;
- rule.type = AutomaticZenRule.TYPE_SCHEDULE_TIME;
- rule.iconResName = "res";
- rule.triggerDescription = "At night";
- rule.zenDeviceEffects = new ZenDeviceEffects.Builder()
- .setShouldDimWallpaper(true)
- .build();
- rule.userModifiedFields = AutomaticZenRule.FIELD_NAME;
- }
+ rule.allowManualInvocation = true;
+ rule.type = AutomaticZenRule.TYPE_SCHEDULE_TIME;
+ rule.iconResName = "res";
+ rule.triggerDescription = "At night";
+ rule.zenDeviceEffects = new ZenDeviceEffects.Builder()
+ .setShouldDimWallpaper(true)
+ .build();
+ rule.userModifiedFields = AutomaticZenRule.FIELD_NAME;
return rule;
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeFilteringTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeFilteringTest.java
index a49f5a89b11b..2f0b3ecb593a 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeFilteringTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeFilteringTest.java
@@ -37,7 +37,6 @@ import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
-import android.app.Flags;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager.Policy;
@@ -492,8 +491,6 @@ public class ZenModeFilteringTest extends UiServiceTestCase {
@Test
public void testAllowChannels_priorityPackage() {
- mSetFlagsRule.enableFlags(Flags.FLAG_MODES_API);
-
// Notification with package priority = PRIORITY_MAX (assigned to indicate canBypassDnd)
NotificationRecord r = getNotificationRecord();
r.setPackagePriority(Notification.PRIORITY_MAX);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
index 31b9cf72584c..0ab11e0cbe3d 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
@@ -23,7 +23,7 @@ import static android.app.AutomaticZenRule.TYPE_SCHEDULE_TIME;
import static android.app.AutomaticZenRule.TYPE_THEATER;
import static android.app.AutomaticZenRule.TYPE_UNKNOWN;
import static android.app.Flags.FLAG_BACKUP_RESTORE_LOGGING;
-import static android.app.Flags.FLAG_MODES_API;
+import static android.app.Flags.FLAG_MODES_CLEANUP_IMPLICIT;
import static android.app.Flags.FLAG_MODES_MULTIUSER;
import static android.app.Flags.FLAG_MODES_UI;
import static android.app.NotificationManager.AUTOMATIC_RULE_STATUS_ACTIVATED;
@@ -85,6 +85,7 @@ import static android.service.notification.ZenPolicy.VISUAL_EFFECT_LIGHTS;
import static android.service.notification.ZenPolicy.VISUAL_EFFECT_PEEK;
import static com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags.LOG_DND_STATE_EVENTS;
+import static com.android.os.dnd.DNDProtoEnums.CONV_IMPORTANT;
import static com.android.os.dnd.DNDProtoEnums.PEOPLE_STARRED;
import static com.android.os.dnd.DNDProtoEnums.ROOT_CONFIG;
import static com.android.os.dnd.DNDProtoEnums.STATE_ALLOW;
@@ -124,7 +125,10 @@ import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import static org.mockito.Mockito.withSettings;
+import static java.time.temporal.ChronoUnit.DAYS;
+
import android.Manifest;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SuppressLint;
import android.app.AlarmManager;
@@ -219,11 +223,11 @@ import java.io.Reader;
import java.io.StringWriter;
import java.time.Instant;
import java.time.ZoneOffset;
-import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.LinkedList;
import java.util.List;
+import java.util.Map;
import java.util.Objects;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@@ -713,7 +717,6 @@ public class ZenModeHelperTest extends UiServiceTestCase {
}
@Test
- @EnableFlags(FLAG_MODES_API)
public void testTotalSilence_consolidatedPolicyDisallowsAll() {
// Start with zen mode off just to make sure global/manual mode isn't doing anything.
mZenModeHelper.mZenMode = ZEN_MODE_OFF;
@@ -746,7 +749,6 @@ public class ZenModeHelperTest extends UiServiceTestCase {
}
@Test
- @EnableFlags(FLAG_MODES_API)
public void testAlarmsOnly_consolidatedPolicyOnlyAllowsAlarmsAndMedia() {
// Start with zen mode off just to make sure global/manual mode isn't doing anything.
mZenModeHelper.mZenMode = ZEN_MODE_OFF;
@@ -1136,8 +1138,7 @@ public class ZenModeHelperTest extends UiServiceTestCase {
@Test
public void testProto() throws InvalidProtocolBufferException {
mZenModeHelper.setManualZenMode(UserHandle.CURRENT, ZEN_MODE_IMPORTANT_INTERRUPTIONS, null,
- Flags.modesApi() ? ORIGIN_USER_IN_SYSTEMUI : ORIGIN_SYSTEM, null,
- "test", CUSTOM_PKG_UID);
+ ORIGIN_USER_IN_SYSTEMUI, null, "test", CUSTOM_PKG_UID);
mZenModeHelper.mConfig.automaticRules = new ArrayMap<>(); // no automatic rules
@@ -1262,7 +1263,6 @@ public class ZenModeHelperTest extends UiServiceTestCase {
}
@Test
- @EnableFlags(FLAG_MODES_API)
public void testProtoWithAutoRuleCustomPolicy() throws Exception {
setupZenConfig();
// clear any automatic rules just to make sure
@@ -1304,7 +1304,6 @@ public class ZenModeHelperTest extends UiServiceTestCase {
}
@Test
- @EnableFlags(FLAG_MODES_API)
public void testProtoWithAutoRuleWithModifiedFields() throws Exception {
setupZenConfig();
mZenModeHelper.mConfig.automaticRules = new ArrayMap<>();
@@ -2005,7 +2004,6 @@ public class ZenModeHelperTest extends UiServiceTestCase {
}
@Test
- @EnableFlags(FLAG_MODES_API)
public void testReadXml_onModesApi_noUpgrade() throws Exception {
// When reading XML for something that is already on the modes API system, make sure no
// rules' policies get changed.
@@ -2053,7 +2051,6 @@ public class ZenModeHelperTest extends UiServiceTestCase {
}
@Test
- @EnableFlags(FLAG_MODES_API)
public void testReadXml_upgradeToModesApi_makesCustomPolicies() throws Exception {
// When reading in an XML file written from a pre-modes-API version, confirm that we create
// a custom policy matching the global config for any automatic rule with no specified
@@ -2105,7 +2102,6 @@ public class ZenModeHelperTest extends UiServiceTestCase {
}
@Test
- @EnableFlags(FLAG_MODES_API)
public void testReadXml_upgradeToModesApi_fillsInCustomPolicies() throws Exception {
// When reading in an XML file written from a pre-modes-API version, confirm that for an
// underspecified ZenPolicy, we fill in all of the gaps with things from the global config
@@ -2165,7 +2161,6 @@ public class ZenModeHelperTest extends UiServiceTestCase {
}
@Test
- @EnableFlags(FLAG_MODES_API)
public void testReadXml_upgradeToModesApi_existingDefaultRulesGetCustomPolicy()
throws Exception {
setupZenConfig();
@@ -2227,7 +2222,7 @@ public class ZenModeHelperTest extends UiServiceTestCase {
}
@Test
- @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI})
+ @EnableFlags(FLAG_MODES_UI)
public void testReadXml_upgradeToModesUi_resetsImplicitRuleIcon() throws Exception {
setupZenConfig();
mZenModeHelper.mConfig.automaticRules.clear();
@@ -2241,13 +2236,12 @@ public class ZenModeHelperTest extends UiServiceTestCase {
mZenModeHelper.mConfig.automaticRules.put(implicitRuleBeforeModesUi.id,
implicitRuleBeforeModesUi);
// Plus one other normal rule.
- ZenRule anotherRule = newZenRule("other_pkg", Instant.now(), null);
- anotherRule.id = "other_rule";
+ ZenRule anotherRule = newZenRule("other_rule", "other_pkg", Instant.now());
anotherRule.iconResName = "other_icon";
anotherRule.type = TYPE_IMMERSIVE;
mZenModeHelper.mConfig.automaticRules.put(anotherRule.id, anotherRule);
- // Write with pre-modes-ui = (modes_api) version, then re-read.
+ // Write with pre-modes-ui version, then re-read.
ByteArrayOutputStream baos = writeXmlAndPurge(ZenModeConfig.XML_VERSION_MODES_API);
TypedXmlPullParser parser = Xml.newFastPullParser();
parser.setInput(new BufferedInputStream(
@@ -2265,7 +2259,7 @@ public class ZenModeHelperTest extends UiServiceTestCase {
}
@Test
- @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI})
+ @EnableFlags(FLAG_MODES_UI)
public void testReadXml_onModesUi_implicitRulesUntouched() throws Exception {
setupZenConfig();
mZenModeHelper.mConfig.automaticRules.clear();
@@ -2279,8 +2273,7 @@ public class ZenModeHelperTest extends UiServiceTestCase {
implicitRuleWithModesUi);
// Plus one other normal rule.
- ZenRule anotherRule = newZenRule("other_pkg", Instant.now(), null);
- anotherRule.id = "other_rule";
+ ZenRule anotherRule = newZenRule("other_rule", "other_pkg", Instant.now());
anotherRule.iconResName = "other_icon";
anotherRule.type = TYPE_IMMERSIVE;
mZenModeHelper.mConfig.automaticRules.put(anotherRule.id, anotherRule);
@@ -2342,7 +2335,7 @@ public class ZenModeHelperTest extends UiServiceTestCase {
// shouldn't update rule that's been modified
ZenModeConfig.ZenRule updatedDefaultRule = new ZenModeConfig.ZenRule();
- updatedDefaultRule.modified = true;
+ updatedDefaultRule.userModifiedFields = AutomaticZenRule.FIELD_NAME;
updatedDefaultRule.enabled = false;
updatedDefaultRule.creationTime = 0;
updatedDefaultRule.id = SCHEDULE_DEFAULT_RULE_ID;
@@ -2370,8 +2363,8 @@ public class ZenModeHelperTest extends UiServiceTestCase {
// will update rule that is not enabled and modified
ZenModeConfig.ZenRule customDefaultRule = new ZenModeConfig.ZenRule();
customDefaultRule.pkg = SystemZenRules.PACKAGE_ANDROID;
+ customDefaultRule.userModifiedFields = AutomaticZenRule.FIELD_NAME;
customDefaultRule.enabled = false;
- customDefaultRule.modified = false;
customDefaultRule.creationTime = 0;
customDefaultRule.id = SCHEDULE_DEFAULT_RULE_ID;
customDefaultRule.name = "Schedule Default Rule";
@@ -2391,7 +2384,7 @@ public class ZenModeHelperTest extends UiServiceTestCase {
ZenModeConfig.ZenRule ruleAfterUpdating =
mZenModeHelper.mConfig.automaticRules.get(SCHEDULE_DEFAULT_RULE_ID);
assertEquals(customDefaultRule.enabled, ruleAfterUpdating.enabled);
- assertEquals(customDefaultRule.modified, ruleAfterUpdating.modified);
+ assertEquals(customDefaultRule.userModifiedFields, ruleAfterUpdating.userModifiedFields);
assertEquals(customDefaultRule.id, ruleAfterUpdating.id);
assertEquals(customDefaultRule.conditionId, ruleAfterUpdating.conditionId);
assertNotEquals(defaultRuleName, ruleAfterUpdating.name); // update name
@@ -2401,8 +2394,7 @@ public class ZenModeHelperTest extends UiServiceTestCase {
}
@Test
- @EnableFlags(FLAG_MODES_API)
- public void testDefaultRulesFromConfig_modesApi_getPolicies() {
+ public void testDefaultRulesFromConfig_getPolicies() {
// After mZenModeHelper was created, set some things in the policy so it's changed from
// default.
setupZenConfig();
@@ -2530,7 +2522,6 @@ public class ZenModeHelperTest extends UiServiceTestCase {
ZenModeConfig.ZenRule ruleInConfig = mZenModeHelper.mConfig.automaticRules.get(id);
assertTrue(ruleInConfig != null);
assertEquals(zenRule.isEnabled(), ruleInConfig.enabled);
- assertEquals(zenRule.isModified(), ruleInConfig.modified);
assertEquals(zenRule.getConditionId(), ruleInConfig.conditionId);
assertEquals(NotificationManager.zenModeFromInterruptionFilter(
zenRule.getInterruptionFilter(), -1), ruleInConfig.zenMode);
@@ -2551,7 +2542,6 @@ public class ZenModeHelperTest extends UiServiceTestCase {
ZenModeConfig.ZenRule ruleInConfig = mZenModeHelper.mConfig.automaticRules.get(id);
assertTrue(ruleInConfig != null);
assertEquals(zenRule.isEnabled(), ruleInConfig.enabled);
- assertEquals(zenRule.isModified(), ruleInConfig.modified);
assertEquals(zenRule.getConditionId(), ruleInConfig.conditionId);
assertEquals(NotificationManager.zenModeFromInterruptionFilter(
zenRule.getInterruptionFilter(), -1), ruleInConfig.zenMode);
@@ -2560,8 +2550,7 @@ public class ZenModeHelperTest extends UiServiceTestCase {
}
@Test
- @EnableFlags(FLAG_MODES_API)
- public void testAddAutomaticZenRule_modesApi_fillsInDefaultValues() {
+ public void testAddAutomaticZenRule_fillsInDefaultValues() {
// When a new automatic zen rule is added with only some fields filled in, ensure that
// all unset fields are filled in with device defaults.
@@ -2762,7 +2751,6 @@ public class ZenModeHelperTest extends UiServiceTestCase {
}
@Test
- @EnableFlags(FLAG_MODES_API)
public void addAutomaticZenRule_fromApp_ignoresHiddenEffects() {
ZenDeviceEffects zde =
new ZenDeviceEffects.Builder()
@@ -2799,7 +2787,6 @@ public class ZenModeHelperTest extends UiServiceTestCase {
}
@Test
- @EnableFlags(FLAG_MODES_API)
public void addAutomaticZenRule_fromSystem_respectsHiddenEffects() {
ZenDeviceEffects zde = new ZenDeviceEffects.Builder()
.setShouldDisplayGrayscale(true)
@@ -2828,7 +2815,6 @@ public class ZenModeHelperTest extends UiServiceTestCase {
}
@Test
- @EnableFlags(FLAG_MODES_API)
public void addAutomaticZenRule_fromUser_respectsHiddenEffects() throws Exception {
ZenDeviceEffects zde = new ZenDeviceEffects.Builder()
.setShouldDisplayGrayscale(true)
@@ -2859,7 +2845,6 @@ public class ZenModeHelperTest extends UiServiceTestCase {
}
@Test
- @EnableFlags(FLAG_MODES_API)
public void updateAutomaticZenRule_fromApp_preservesPreviousHiddenEffects() {
ZenDeviceEffects original = new ZenDeviceEffects.Builder()
.setShouldDisableTapToWake(true)
@@ -2896,7 +2881,6 @@ public class ZenModeHelperTest extends UiServiceTestCase {
}
@Test
- @EnableFlags(FLAG_MODES_API)
public void updateAutomaticZenRule_fromSystem_updatesHiddenEffects() {
ZenDeviceEffects original = new ZenDeviceEffects.Builder()
.setShouldDisableTapToWake(true)
@@ -2925,7 +2909,6 @@ public class ZenModeHelperTest extends UiServiceTestCase {
}
@Test
- @EnableFlags(FLAG_MODES_API)
public void updateAutomaticZenRule_fromUser_updatesHiddenEffects() {
ZenDeviceEffects original = new ZenDeviceEffects.Builder()
.setShouldDisableTapToWake(true)
@@ -2958,7 +2941,6 @@ public class ZenModeHelperTest extends UiServiceTestCase {
}
@Test
- @EnableFlags(FLAG_MODES_API)
public void updateAutomaticZenRule_nullPolicy_doesNothing() {
// Test that when updateAutomaticZenRule is called with a null policy, nothing changes
// about the existing policy.
@@ -2985,7 +2967,6 @@ public class ZenModeHelperTest extends UiServiceTestCase {
}
@Test
- @EnableFlags(FLAG_MODES_API)
public void updateAutomaticZenRule_overwritesExistingPolicy() {
// Test that when updating an automatic zen rule with an existing policy, the newly set
// fields overwrite those from the previous policy, but unset fields in the new policy
@@ -3024,7 +3005,6 @@ public class ZenModeHelperTest extends UiServiceTestCase {
@Test
- @EnableFlags(FLAG_MODES_API)
public void addAutomaticZenRule_withTypeBedtime_replacesDisabledSleeping() {
ZenRule sleepingRule = createCustomAutomaticRule(ZEN_MODE_IMPORTANT_INTERRUPTIONS,
ZenModeConfig.EVERY_NIGHT_DEFAULT_RULE_ID);
@@ -3044,7 +3024,6 @@ public class ZenModeHelperTest extends UiServiceTestCase {
}
@Test
- @EnableFlags(FLAG_MODES_API)
public void addAutomaticZenRule_withTypeBedtime_keepsEnabledSleeping() {
ZenRule sleepingRule = createCustomAutomaticRule(ZEN_MODE_IMPORTANT_INTERRUPTIONS,
ZenModeConfig.EVERY_NIGHT_DEFAULT_RULE_ID);
@@ -3065,7 +3044,6 @@ public class ZenModeHelperTest extends UiServiceTestCase {
}
@Test
- @EnableFlags(FLAG_MODES_API)
public void addAutomaticZenRule_withTypeBedtime_keepsCustomizedSleeping() {
ZenRule sleepingRule = createCustomAutomaticRule(ZEN_MODE_IMPORTANT_INTERRUPTIONS,
ZenModeConfig.EVERY_NIGHT_DEFAULT_RULE_ID);
@@ -3086,7 +3064,7 @@ public class ZenModeHelperTest extends UiServiceTestCase {
}
@Test
- @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI})
+ @EnableFlags(FLAG_MODES_UI)
public void updateAutomaticZenRule_withTypeBedtime_replacesDisabledSleeping() {
ZenRule sleepingRule = createCustomAutomaticRule(ZEN_MODE_IMPORTANT_INTERRUPTIONS,
ZenModeConfig.EVERY_NIGHT_DEFAULT_RULE_ID);
@@ -3113,7 +3091,34 @@ public class ZenModeHelperTest extends UiServiceTestCase {
}
@Test
- @EnableFlags(FLAG_MODES_API)
+ public void getAutomaticZenRules_returnsOwnedRules() {
+ AutomaticZenRule myRule1 = new AutomaticZenRule.Builder("My Rule 1", Uri.parse("1"))
+ .setPackage(mPkg)
+ .setConfigurationActivity(new ComponentName(mPkg, "myActivity"))
+ .build();
+ AutomaticZenRule myRule2 = new AutomaticZenRule.Builder("My Rule 2", Uri.parse("2"))
+ .setPackage(mPkg)
+ .setConfigurationActivity(new ComponentName(mPkg, "myActivity"))
+ .build();
+ AutomaticZenRule otherPkgRule = new AutomaticZenRule.Builder("Other", Uri.parse("3"))
+ .setPackage("com.other.package")
+ .setConfigurationActivity(new ComponentName("com.other.package", "theirActivity"))
+ .build();
+
+ String rule1Id = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT, mPkg, myRule1,
+ ORIGIN_APP, "reason", CUSTOM_PKG_UID);
+ String rule2Id = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT, mPkg, myRule2,
+ ORIGIN_APP, "reason", CUSTOM_PKG_UID);
+ String otherRuleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT,
+ "com.other.package", otherPkgRule, ORIGIN_APP, "reason", CUSTOM_PKG_UID);
+
+ Map<String, AutomaticZenRule> rules = mZenModeHelper.getAutomaticZenRules(
+ UserHandle.CURRENT, CUSTOM_PKG_UID);
+
+ assertThat(rules.keySet()).containsExactly(rule1Id, rule2Id);
+ }
+
+ @Test
public void testSetManualZenMode() {
setupZenConfig();
@@ -3133,7 +3138,6 @@ public class ZenModeHelperTest extends UiServiceTestCase {
}
@Test
- @EnableFlags(FLAG_MODES_API)
@DisableFlags(FLAG_MODES_UI)
public void setManualZenMode_off_snoozesActiveRules() {
for (ZenChangeOrigin origin : ZenChangeOrigin.values()) {
@@ -3172,7 +3176,7 @@ public class ZenModeHelperTest extends UiServiceTestCase {
}
@Test
- @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI})
+ @EnableFlags(FLAG_MODES_UI)
public void setManualZenMode_off_doesNotSnoozeRulesIfFromUserInSystemUi() {
for (ZenChangeOrigin origin : ZenChangeOrigin.values()) {
// Start with an active rule and an inactive rule
@@ -3246,7 +3250,7 @@ public class ZenModeHelperTest extends UiServiceTestCase {
// Turn zen mode on (to important_interruptions)
// Need to additionally call the looper in order to finish the post-apply-config process
mZenModeHelper.setManualZenMode(UserHandle.CURRENT, ZEN_MODE_IMPORTANT_INTERRUPTIONS, null,
- Flags.modesApi() ? ORIGIN_USER_IN_SYSTEMUI : ORIGIN_SYSTEM, "", null, SYSTEM_UID);
+ ORIGIN_USER_IN_SYSTEMUI, "", null, SYSTEM_UID);
// Now turn zen mode off, but via a different package UID -- this should get registered as
// "not an action by the user" because some other app is changing zen mode
@@ -3273,14 +3277,13 @@ public class ZenModeHelperTest extends UiServiceTestCase {
assertEquals(ZEN_MODE_IMPORTANT_INTERRUPTIONS, mZenModeEventLogger.getNewZenMode(0));
assertEquals(DNDProtoEnums.MANUAL_RULE, mZenModeEventLogger.getChangedRuleType(0));
assertEquals(1, mZenModeEventLogger.getNumRulesActive(0));
- assertThat(mZenModeEventLogger.getFromSystemOrSystemUi(0)).isEqualTo(
- !(Flags.modesUi() || Flags.modesApi()));
+ assertThat(mZenModeEventLogger.getFromSystemOrSystemUi(0)).isFalse();
assertTrue(mZenModeEventLogger.getIsUserAction(0));
assertEquals(SYSTEM_UID, mZenModeEventLogger.getPackageUid(0));
checkDndProtoMatchesSetupZenConfig(mZenModeEventLogger.getPolicyProto(0));
// change origin should be populated only under modes_ui
assertThat(mZenModeEventLogger.getChangeOrigin(0)).isEqualTo(
- (Flags.modesApi() && Flags.modesUi()) ? ORIGIN_USER_IN_SYSTEMUI : 0);
+ (Flags.modesUi()) ? ORIGIN_USER_IN_SYSTEMUI : 0);
// and from turning zen mode off:
// - event ID: DND_TURNED_OFF
@@ -3298,11 +3301,7 @@ public class ZenModeHelperTest extends UiServiceTestCase {
assertEquals(0, mZenModeEventLogger.getNumRulesActive(1));
assertFalse(mZenModeEventLogger.getIsUserAction(1));
assertEquals(CUSTOM_PKG_UID, mZenModeEventLogger.getPackageUid(1));
- if (Flags.modesApi()) {
- assertThat(mZenModeEventLogger.getPolicyProto(1)).isNull();
- } else {
- checkDndProtoMatchesSetupZenConfig(mZenModeEventLogger.getPolicyProto(1));
- }
+ assertThat(mZenModeEventLogger.getPolicyProto(1)).isNull();
assertThat(mZenModeEventLogger.getChangeOrigin(1)).isEqualTo(
Flags.modesUi() ? ORIGIN_APP : 0);
}
@@ -3334,8 +3333,7 @@ public class ZenModeHelperTest extends UiServiceTestCase {
// Event 2: "User" turns off the automatic rule (sets it to not enabled)
zenRule.setEnabled(false);
mZenModeHelper.updateAutomaticZenRule(UserHandle.CURRENT, id, zenRule,
- Flags.modesApi() ? ORIGIN_USER_IN_SYSTEMUI : ORIGIN_SYSTEM, "",
- SYSTEM_UID);
+ ORIGIN_USER_IN_SYSTEMUI, "", SYSTEM_UID);
AutomaticZenRule systemRule = new AutomaticZenRule("systemRule",
null,
@@ -3345,8 +3343,7 @@ public class ZenModeHelperTest extends UiServiceTestCase {
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
String systemId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT,
mContext.getPackageName(), systemRule,
- Flags.modesApi() ? ORIGIN_USER_IN_SYSTEMUI : ORIGIN_SYSTEM, "test",
- SYSTEM_UID);
+ ORIGIN_USER_IN_SYSTEMUI, "test", SYSTEM_UID);
// Event 3: turn on the system rule
mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, systemId,
@@ -3355,8 +3352,7 @@ public class ZenModeHelperTest extends UiServiceTestCase {
// Event 4: "User" deletes the rule
mZenModeHelper.removeAutomaticZenRule(UserHandle.CURRENT, systemId,
- Flags.modesApi() ? ORIGIN_USER_IN_SYSTEMUI : ORIGIN_SYSTEM, "",
- SYSTEM_UID);
+ ORIGIN_USER_IN_SYSTEMUI, "", SYSTEM_UID);
// In total, this represents 4 events
assertEquals(4, mZenModeEventLogger.numLoggedChanges());
@@ -3394,11 +3390,7 @@ public class ZenModeHelperTest extends UiServiceTestCase {
assertTrue(mZenModeEventLogger.getIsUserAction(1));
assertThat(mZenModeEventLogger.getPackageUid(1)).isEqualTo(
Flags.modesUi() ? CUSTOM_PKG_UID : SYSTEM_UID);
- if (Flags.modesApi()) {
- assertThat(mZenModeEventLogger.getPolicyProto(1)).isNull();
- } else {
- checkDndProtoMatchesSetupZenConfig(mZenModeEventLogger.getPolicyProto(1));
- }
+ assertThat(mZenModeEventLogger.getPolicyProto(1)).isNull();
assertThat(mZenModeEventLogger.getChangeOrigin(1)).isEqualTo(
Flags.modesUi() ? ORIGIN_USER_IN_SYSTEMUI : 0);
@@ -3426,7 +3418,6 @@ public class ZenModeHelperTest extends UiServiceTestCase {
}
@Test
- @EnableFlags(FLAG_MODES_API)
public void testZenModeEventLog_automaticRuleActivatedFromAppByAppAndUser()
throws IllegalArgumentException {
mTestFlagResolver.setFlagOverride(LOG_DND_STATE_EVENTS, true);
@@ -3816,13 +3807,13 @@ public class ZenModeHelperTest extends UiServiceTestCase {
// Now change apps bypassing to true
ZenModeConfig newConfig = mZenModeHelper.mConfig.copy();
- newConfig.areChannelsBypassingDnd = true;
+ newConfig.hasPriorityChannels = true;
mZenModeHelper.setNotificationPolicy(UserHandle.CURRENT, newConfig.toNotificationPolicy(),
ORIGIN_SYSTEM, SYSTEM_UID);
assertEquals(2, mZenModeEventLogger.numLoggedChanges());
// and then back to false, all without changing anything else
- newConfig.areChannelsBypassingDnd = false;
+ newConfig.hasPriorityChannels = false;
mZenModeHelper.setNotificationPolicy(UserHandle.CURRENT, newConfig.toNotificationPolicy(),
ORIGIN_SYSTEM, SYSTEM_UID);
assertEquals(3, mZenModeEventLogger.numLoggedChanges());
@@ -3869,10 +3860,9 @@ public class ZenModeHelperTest extends UiServiceTestCase {
}
@Test
- @EnableFlags(FLAG_MODES_API)
public void testZenModeEventLog_policyAllowChannels() {
- // when modes_api flag is on, ensure that any change in allow_channels gets logged,
- // even when there are no other changes.
+ // Ensure that any change in allow_channels gets logged, even when there are no other
+ // changes.
mTestFlagResolver.setFlagOverride(LOG_DND_STATE_EVENTS, true);
// Default zen config has allow channels = priority (aka on)
@@ -3919,7 +3909,6 @@ public class ZenModeHelperTest extends UiServiceTestCase {
}
@Test
- @EnableFlags(FLAG_MODES_API)
public void testZenModeEventLog_ruleWithInterruptionFilterAll_notLoggedAsDndChange() {
mTestFlagResolver.setFlagOverride(LOG_DND_STATE_EVENTS, true);
setupZenConfig();
@@ -3961,7 +3950,6 @@ public class ZenModeHelperTest extends UiServiceTestCase {
}
@Test
- @EnableFlags(FLAG_MODES_API)
public void testZenModeEventLog_activeRuleTypes() {
mTestFlagResolver.setFlagOverride(LOG_DND_STATE_EVENTS, true);
setupZenConfig();
@@ -4050,43 +4038,7 @@ public class ZenModeHelperTest extends UiServiceTestCase {
}
@Test
- @DisableFlags(FLAG_MODES_API)
- public void testUpdateConsolidatedPolicy_preModesApiDefaultRulesOnly_takesGlobalDefault() {
- setupZenConfig();
- // When there's one automatic rule active and it doesn't specify a policy, test that the
- // resulting consolidated policy is one that matches the default rule settings.
- AutomaticZenRule zenRule = new AutomaticZenRule("name",
- null,
- new ComponentName(CUSTOM_PKG_NAME, "ScheduleConditionProvider"),
- ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
- null,
- NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
- String id = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT,
- mContext.getPackageName(), zenRule, ORIGIN_SYSTEM, "test", SYSTEM_UID);
-
- // enable the rule
- mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, id,
- new Condition(zenRule.getConditionId(), "", STATE_TRUE),
- ORIGIN_SYSTEM, SYSTEM_UID);
-
- assertEquals(mZenModeHelper.getNotificationPolicy(UserHandle.CURRENT),
- mZenModeHelper.getConsolidatedNotificationPolicy());
-
- // inspect the consolidated policy. Based on setupZenConfig() values.
- assertFalse(mZenModeHelper.mConsolidatedPolicy.allowAlarms());
- assertFalse(mZenModeHelper.mConsolidatedPolicy.allowMedia());
- assertFalse(mZenModeHelper.mConsolidatedPolicy.allowSystem());
- assertTrue(mZenModeHelper.mConsolidatedPolicy.allowReminders());
- assertTrue(mZenModeHelper.mConsolidatedPolicy.allowCalls());
- assertEquals(PRIORITY_SENDERS_STARRED, mZenModeHelper.mConsolidatedPolicy.allowCallsFrom());
- assertTrue(mZenModeHelper.mConsolidatedPolicy.allowMessages());
- assertTrue(mZenModeHelper.mConsolidatedPolicy.allowConversations());
- assertTrue(mZenModeHelper.mConsolidatedPolicy.allowRepeatCallers());
- assertFalse(mZenModeHelper.mConsolidatedPolicy.showBadges());
- }
-
- @Test
- public void testUpdateConsolidatedPolicy_modesApiDefaultRulesOnly_takesDefault() {
+ public void testUpdateConsolidatedPolicy_defaultRulesOnly_takesDefault() {
setupZenConfig();
// When there's one automatic rule active and it doesn't specify a policy, test that the
@@ -4113,53 +4065,7 @@ public class ZenModeHelperTest extends UiServiceTestCase {
}
@Test
- @DisableFlags(FLAG_MODES_API)
- public void testUpdateConsolidatedPolicy_preModesApiCustomPolicyOnly_fillInWithGlobal() {
- setupZenConfig();
-
- // when there's only one automatic rule active and it has a custom policy, make sure that's
- // what the consolidated policy reflects whether or not it's stricter than what the global
- // config would specify.
- ZenPolicy customPolicy = new ZenPolicy.Builder()
- .allowAlarms(true) // more lenient than default
- .allowMedia(true) // more lenient than default
- .allowRepeatCallers(false) // more restrictive than default
- .allowCalls(ZenPolicy.PEOPLE_TYPE_NONE) // more restrictive than default
- .showBadges(true) // more lenient
- .showPeeking(false) // more restrictive
- .build();
-
- AutomaticZenRule zenRule = new AutomaticZenRule("name",
- null,
- new ComponentName(CUSTOM_PKG_NAME, "ScheduleConditionProvider"),
- ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
- customPolicy,
- NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
- String id = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT,
- mContext.getPackageName(), zenRule, ORIGIN_SYSTEM, "test", SYSTEM_UID);
-
- // enable the rule; this will update the consolidated policy
- mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, id,
- new Condition(zenRule.getConditionId(), "", STATE_TRUE), ORIGIN_SYSTEM, SYSTEM_UID);
-
- // since this is the only active rule, the consolidated policy should match the custom
- // policy for every field specified, and take default values (from device default or
- // manual policy) for unspecified things
- assertTrue(mZenModeHelper.mConsolidatedPolicy.allowAlarms()); // custom
- assertTrue(mZenModeHelper.mConsolidatedPolicy.allowMedia()); // custom
- assertFalse(mZenModeHelper.mConsolidatedPolicy.allowSystem()); // default
- assertTrue(mZenModeHelper.mConsolidatedPolicy.allowReminders()); // default
- assertFalse(mZenModeHelper.mConsolidatedPolicy.allowCalls()); // custom
- assertTrue(mZenModeHelper.mConsolidatedPolicy.allowMessages()); // default
- assertTrue(mZenModeHelper.mConsolidatedPolicy.allowConversations()); // default
- assertFalse(mZenModeHelper.mConsolidatedPolicy.allowRepeatCallers()); // custom
- assertTrue(mZenModeHelper.mConsolidatedPolicy.showBadges()); // custom
- assertFalse(mZenModeHelper.mConsolidatedPolicy.showPeeking()); // custom
- }
-
- @Test
- @EnableFlags(FLAG_MODES_API)
- public void testUpdateConsolidatedPolicy_modesApiCustomPolicyOnly_fillInWithDefault() {
+ public void testUpdateConsolidatedPolicy_customPolicyOnly_fillInWithDefault() {
setupZenConfig();
// when there's only one automatic rule active and it has a custom policy, make sure that's
@@ -4204,68 +4110,7 @@ public class ZenModeHelperTest extends UiServiceTestCase {
}
@Test
- @DisableFlags(FLAG_MODES_API)
- public void testUpdateConsolidatedPolicy_preModesApiDefaultAndCustomActive_mergesWithGlobal() {
- setupZenConfig();
-
- // when there are two rules active, one inheriting the default policy and one setting its
- // own custom policy, they should be merged to form the most restrictive combination.
-
- // rule 1: no custom policy
- AutomaticZenRule zenRule = new AutomaticZenRule("name",
- null,
- new ComponentName(CUSTOM_PKG_NAME, "ScheduleConditionProvider"),
- ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
- null,
- NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
- String id = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT,
- mContext.getPackageName(), zenRule, ORIGIN_SYSTEM, "test", SYSTEM_UID);
-
- // enable rule 1
- mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, id,
- new Condition(zenRule.getConditionId(), "", STATE_TRUE), ORIGIN_SYSTEM, SYSTEM_UID);
-
- // custom policy for rule 2
- ZenPolicy customPolicy = new ZenPolicy.Builder()
- .allowAlarms(true) // more lenient than default
- .allowMedia(true) // more lenient than default
- .allowRepeatCallers(false) // more restrictive than default
- .allowCalls(ZenPolicy.PEOPLE_TYPE_NONE) // more restrictive than default
- .showBadges(true) // more lenient
- .showPeeking(false) // more restrictive
- .build();
-
- AutomaticZenRule zenRule2 = new AutomaticZenRule("name2",
- null,
- new ComponentName(CUSTOM_PKG_NAME, "ScheduleConditionProvider"),
- ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
- customPolicy,
- NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
- String id2 = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT,
- mContext.getPackageName(), zenRule2, ORIGIN_SYSTEM, "test", SYSTEM_UID);
-
- // enable rule 2; this will update the consolidated policy
- mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, id2,
- new Condition(zenRule2.getConditionId(), "", STATE_TRUE),
- ORIGIN_SYSTEM, SYSTEM_UID);
-
- // now both rules should be on, and the consolidated policy should reflect the most
- // restrictive option of each of the two
- assertFalse(mZenModeHelper.mConsolidatedPolicy.allowAlarms()); // default stricter
- assertFalse(mZenModeHelper.mConsolidatedPolicy.allowMedia()); // default stricter
- assertFalse(mZenModeHelper.mConsolidatedPolicy.allowSystem()); // default, unset in custom
- assertTrue(mZenModeHelper.mConsolidatedPolicy.allowReminders()); // default
- assertFalse(mZenModeHelper.mConsolidatedPolicy.allowCalls()); // custom stricter
- assertTrue(mZenModeHelper.mConsolidatedPolicy.allowMessages()); // default, unset in custom
- assertTrue(mZenModeHelper.mConsolidatedPolicy.allowConversations()); // default
- assertFalse(mZenModeHelper.mConsolidatedPolicy.allowRepeatCallers()); // custom stricter
- assertFalse(mZenModeHelper.mConsolidatedPolicy.showBadges()); // default stricter
- assertFalse(mZenModeHelper.mConsolidatedPolicy.showPeeking()); // custom stricter
- }
-
- @Test
- @EnableFlags(FLAG_MODES_API)
- public void testUpdateConsolidatedPolicy_modesApiDefaultAndCustomActive_mergesWithDefault() {
+ public void testUpdateConsolidatedPolicy_defaultAndCustomActive_mergesWithDefault() {
setupZenConfig();
// when there are two rules active, one inheriting the default policy and one setting its
@@ -4328,7 +4173,6 @@ public class ZenModeHelperTest extends UiServiceTestCase {
}
@Test
- @EnableFlags(FLAG_MODES_API)
public void testUpdateConsolidatedPolicy_allowChannels() {
setupZenConfig();
@@ -4377,7 +4221,6 @@ public class ZenModeHelperTest extends UiServiceTestCase {
}
@Test
- @EnableFlags(FLAG_MODES_API)
public void testUpdateConsolidatedPolicy_ignoresActiveRulesWithInterruptionFilterAll() {
setupZenConfig();
@@ -4428,7 +4271,6 @@ public class ZenModeHelperTest extends UiServiceTestCase {
}
@Test
- @EnableFlags(FLAG_MODES_API)
public void zenRuleToAutomaticZenRule_allFields() {
when(mPackageManager.getPackagesForUid(anyInt())).thenReturn(
new String[]{OWNER.getPackageName()});
@@ -4442,7 +4284,6 @@ public class ZenModeHelperTest extends UiServiceTestCase {
rule.creationTime = 123;
rule.id = "id";
rule.zenMode = INTERRUPTION_FILTER_ZR;
- rule.modified = true;
rule.name = NAME;
rule.setConditionOverride(OVERRIDE_DEACTIVATE);
rule.pkg = OWNER.getPackageName();
@@ -4473,7 +4314,6 @@ public class ZenModeHelperTest extends UiServiceTestCase {
}
@Test
- @EnableFlags(FLAG_MODES_API)
public void automaticZenRuleToZenRule_allFields() {
when(mPackageManager.getPackagesForUid(anyInt())).thenReturn(
new String[]{OWNER.getPackageName()});
@@ -4515,7 +4355,6 @@ public class ZenModeHelperTest extends UiServiceTestCase {
}
@Test
- @EnableFlags(FLAG_MODES_API)
public void updateAutomaticZenRule_fromApp_updatesNameUnlessUserModified() {
// Add a starting rule with the name OriginalName.
AutomaticZenRule azrBase = new AutomaticZenRule.Builder("OriginalName", CONDITION_ID)
@@ -4573,7 +4412,6 @@ public class ZenModeHelperTest extends UiServiceTestCase {
}
@Test
- @EnableFlags(FLAG_MODES_API)
public void updateAutomaticZenRule_fromUser_updatesBitmaskAndValue() {
// Adds a starting rule with empty zen policies and device effects
AutomaticZenRule azrBase = new AutomaticZenRule.Builder(NAME, CONDITION_ID)
@@ -4627,7 +4465,6 @@ public class ZenModeHelperTest extends UiServiceTestCase {
}
@Test
- @EnableFlags(FLAG_MODES_API)
public void updateAutomaticZenRule_fromSystemUi_updatesValues() {
// Adds a starting rule with empty zen policies and device effects
AutomaticZenRule azrBase = new AutomaticZenRule.Builder(NAME, CONDITION_ID)
@@ -4678,7 +4515,6 @@ public class ZenModeHelperTest extends UiServiceTestCase {
}
@Test
- @EnableFlags(FLAG_MODES_API)
public void updateAutomaticZenRule_fromApp_updatesValuesIfRuleNotUserModified() {
// Adds a starting rule with empty zen policies and device effects
AutomaticZenRule azrBase = new AutomaticZenRule.Builder(NAME, CONDITION_ID)
@@ -4754,7 +4590,6 @@ public class ZenModeHelperTest extends UiServiceTestCase {
}
@Test
- @EnableFlags(FLAG_MODES_API)
public void addAutomaticZenRule_updatesValues() {
// Adds a starting rule with empty zen policies and device effects
AutomaticZenRule azrBase = new AutomaticZenRule.Builder(NAME, CONDITION_ID)
@@ -4777,11 +4612,10 @@ public class ZenModeHelperTest extends UiServiceTestCase {
assertThat(rule.getDeviceEffects().shouldDisplayGrayscale()).isTrue();
ZenRule storedRule = mZenModeHelper.mConfig.automaticRules.get(ruleId);
- assertThat(storedRule.canBeUpdatedByApp()).isTrue();
+ assertThat(storedRule.isUserModified()).isFalse();
}
@Test
- @EnableFlags(FLAG_MODES_API)
public void updateAutomaticZenRule_nullDeviceEffectsUpdate() {
// Adds a starting rule with empty zen policies and device effects
ZenDeviceEffects zde = new ZenDeviceEffects.Builder().setShouldUseNightMode(true).build();
@@ -4809,7 +4643,6 @@ public class ZenModeHelperTest extends UiServiceTestCase {
}
@Test
- @EnableFlags(FLAG_MODES_API)
public void updateAutomaticZenRule_nullPolicyUpdate() {
// Adds a starting rule with set zen policy and empty device effects
AutomaticZenRule azrBase = new AutomaticZenRule.Builder(NAME, CONDITION_ID)
@@ -4838,7 +4671,6 @@ public class ZenModeHelperTest extends UiServiceTestCase {
}
@Test
- @EnableFlags(FLAG_MODES_API)
public void automaticZenRuleToZenRule_nullToNonNullPolicyUpdate() {
when(mContext.checkCallingPermission(anyString()))
.thenReturn(PackageManager.PERMISSION_GRANTED);
@@ -4888,7 +4720,7 @@ public class ZenModeHelperTest extends UiServiceTestCase {
STATE_DISALLOW);
ZenRule storedRule = mZenModeHelper.mConfig.automaticRules.get(ruleId);
- assertThat(storedRule.canBeUpdatedByApp()).isFalse();
+ assertThat(storedRule.isUserModified()).isTrue();
assertThat(storedRule.zenPolicyUserModifiedFields).isEqualTo(
ZenPolicy.FIELD_ALLOW_CHANNELS
| ZenPolicy.FIELD_PRIORITY_CATEGORY_REMINDERS
@@ -4902,7 +4734,6 @@ public class ZenModeHelperTest extends UiServiceTestCase {
}
@Test
- @EnableFlags(FLAG_MODES_API)
public void automaticZenRuleToZenRule_nullToNonNullDeviceEffectsUpdate() {
// Adds a starting rule with empty zen policies and device effects
AutomaticZenRule azrBase = new AutomaticZenRule.Builder(NAME, CONDITION_ID)
@@ -4931,7 +4762,7 @@ public class ZenModeHelperTest extends UiServiceTestCase {
assertThat(rule.getDeviceEffects().shouldDisplayGrayscale()).isTrue();
ZenRule storedRule = mZenModeHelper.mConfig.automaticRules.get(ruleId);
- assertThat(storedRule.canBeUpdatedByApp()).isFalse();
+ assertThat(storedRule.isUserModified()).isTrue();
assertThat(storedRule.zenDeviceEffectsUserModifiedFields).isEqualTo(
ZenDeviceEffects.FIELD_GRAYSCALE);
}
@@ -5007,7 +4838,6 @@ public class ZenModeHelperTest extends UiServiceTestCase {
}
@Test
- @EnableFlags(FLAG_MODES_API)
public void testUpdateAutomaticRule_activated_triggersBroadcast() throws Exception {
setupZenConfig();
@@ -5047,7 +4877,6 @@ public class ZenModeHelperTest extends UiServiceTestCase {
}
@Test
- @EnableFlags(FLAG_MODES_API)
public void testUpdateAutomaticRule_deactivatedByUser_triggersBroadcast() throws Exception {
setupZenConfig();
@@ -5091,7 +4920,6 @@ public class ZenModeHelperTest extends UiServiceTestCase {
}
@Test
- @EnableFlags(FLAG_MODES_API)
public void testUpdateAutomaticRule_deactivatedByApp_triggersBroadcast() throws Exception {
setupZenConfig();
@@ -5167,7 +4995,6 @@ public class ZenModeHelperTest extends UiServiceTestCase {
}
@Test
- @EnableFlags(FLAG_MODES_API)
public void updateAutomaticZenRule_ruleChanged_deactivatesRule() {
assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_OFF);
AutomaticZenRule rule = new AutomaticZenRule.Builder("rule", CONDITION_ID)
@@ -5191,7 +5018,6 @@ public class ZenModeHelperTest extends UiServiceTestCase {
}
@Test
- @EnableFlags(FLAG_MODES_API)
public void updateAutomaticZenRule_ruleNotChanged_doesNotDeactivateRule() {
assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_OFF);
AutomaticZenRule rule = new AutomaticZenRule.Builder("rule", CONDITION_ID)
@@ -5214,7 +5040,7 @@ public class ZenModeHelperTest extends UiServiceTestCase {
}
@Test
- @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI})
+ @EnableFlags(FLAG_MODES_UI)
public void updateAutomaticZenRule_ruleChangedByUser_doesNotDeactivateRule() {
assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_OFF);
AutomaticZenRule rule = new AutomaticZenRule.Builder("rule", CONDITION_ID)
@@ -5239,7 +5065,6 @@ public class ZenModeHelperTest extends UiServiceTestCase {
}
@Test
- @EnableFlags(FLAG_MODES_API)
public void updateAutomaticZenRule_ruleChangedByUser_doesNotDeactivateRule_forWatch() {
when(mContext.getPackageManager()).thenReturn(mPackageManager);
when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_WATCH)).thenReturn(true);
@@ -5266,7 +5091,7 @@ public class ZenModeHelperTest extends UiServiceTestCase {
}
@Test
- @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI})
+ @EnableFlags(FLAG_MODES_UI)
public void updateAutomaticZenRule_ruleDisabledByUser_doesNotReactivateOnReenable() {
assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_OFF);
AutomaticZenRule rule = new AutomaticZenRule.Builder("rule", CONDITION_ID)
@@ -5291,7 +5116,7 @@ public class ZenModeHelperTest extends UiServiceTestCase {
}
@Test
- @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI})
+ @EnableFlags(FLAG_MODES_UI)
public void updateAutomaticZenRule_changeOwnerForSystemRule_allowed() {
when(mContext.checkCallingPermission(anyString()))
.thenReturn(PackageManager.PERMISSION_GRANTED);
@@ -5314,7 +5139,7 @@ public class ZenModeHelperTest extends UiServiceTestCase {
}
@Test
- @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI})
+ @EnableFlags(FLAG_MODES_UI)
public void updateAutomaticZenRule_changeOwnerForAppOwnedRule_ignored() {
AutomaticZenRule original = new AutomaticZenRule.Builder("Rule", Uri.EMPTY)
.setOwner(new ComponentName(mContext.getPackageName(), "old.third.party.cps"))
@@ -5335,7 +5160,6 @@ public class ZenModeHelperTest extends UiServiceTestCase {
}
@Test
- @EnableFlags(FLAG_MODES_API)
public void removeAutomaticZenRule_propagatesOriginToEffectsApplier() {
mZenModeHelper.setDeviceEffectsApplier(mDeviceEffectsApplier);
reset(mDeviceEffectsApplier);
@@ -5358,7 +5182,6 @@ public class ZenModeHelperTest extends UiServiceTestCase {
}
@Test
- @EnableFlags(FLAG_MODES_API)
public void testDeviceEffects_applied() {
mZenModeHelper.setDeviceEffectsApplier(mDeviceEffectsApplier);
verify(mDeviceEffectsApplier).apply(eq(NO_EFFECTS), eq(ORIGIN_INIT));
@@ -5384,7 +5207,6 @@ public class ZenModeHelperTest extends UiServiceTestCase {
}
@Test
- @EnableFlags(FLAG_MODES_API)
public void testSettingDeviceEffects_throwsExceptionIfAlreadySet() {
mZenModeHelper.setDeviceEffectsApplier(mDeviceEffectsApplier);
@@ -5394,7 +5216,6 @@ public class ZenModeHelperTest extends UiServiceTestCase {
}
@Test
- @EnableFlags(FLAG_MODES_API)
public void testDeviceEffects_onDeactivateRule_applied() {
mZenModeHelper.setDeviceEffectsApplier(mDeviceEffectsApplier);
@@ -5413,7 +5234,6 @@ public class ZenModeHelperTest extends UiServiceTestCase {
}
@Test
- @EnableFlags(FLAG_MODES_API)
public void testDeviceEffects_changeToConsolidatedEffects_applied() {
mZenModeHelper.setDeviceEffectsApplier(mDeviceEffectsApplier);
verify(mDeviceEffectsApplier).apply(eq(NO_EFFECTS), eq(ORIGIN_INIT));
@@ -5453,7 +5273,6 @@ public class ZenModeHelperTest extends UiServiceTestCase {
}
@Test
- @EnableFlags(FLAG_MODES_API)
public void testDeviceEffects_noChangeToConsolidatedEffects_notApplied() {
mZenModeHelper.setDeviceEffectsApplier(mDeviceEffectsApplier);
verify(mDeviceEffectsApplier).apply(eq(NO_EFFECTS), eq(ORIGIN_INIT));
@@ -5478,7 +5297,6 @@ public class ZenModeHelperTest extends UiServiceTestCase {
}
@Test
- @EnableFlags(FLAG_MODES_API)
public void testDeviceEffects_activeBeforeApplierProvided_appliedWhenProvided() {
ZenDeviceEffects zde = new ZenDeviceEffects.Builder().setShouldUseNightMode(true).build();
String ruleId = addRuleWithEffects(zde);
@@ -5494,7 +5312,6 @@ public class ZenModeHelperTest extends UiServiceTestCase {
}
@Test
- @EnableFlags(FLAG_MODES_API)
public void testDeviceEffects_onUserSwitch_appliedImmediately() {
mZenModeHelper.setDeviceEffectsApplier(mDeviceEffectsApplier);
verify(mDeviceEffectsApplier).apply(eq(NO_EFFECTS), eq(ORIGIN_INIT));
@@ -5522,7 +5339,7 @@ public class ZenModeHelperTest extends UiServiceTestCase {
}
@Test
- @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI, FLAG_PREVENT_ZEN_DEVICE_EFFECTS_WHILE_DRIVING})
+ @EnableFlags({FLAG_MODES_UI, FLAG_PREVENT_ZEN_DEVICE_EFFECTS_WHILE_DRIVING})
public void testDeviceEffects_allowsGrayscale() {
mZenModeHelper.setDeviceEffectsApplier(mDeviceEffectsApplier);
reset(mDeviceEffectsApplier);
@@ -5539,7 +5356,7 @@ public class ZenModeHelperTest extends UiServiceTestCase {
}
@Test
- @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI, FLAG_PREVENT_ZEN_DEVICE_EFFECTS_WHILE_DRIVING})
+ @EnableFlags({FLAG_MODES_UI, FLAG_PREVENT_ZEN_DEVICE_EFFECTS_WHILE_DRIVING})
public void testDeviceEffects_whileDriving_avoidsGrayscale() {
mZenModeHelper.setDeviceEffectsApplier(mDeviceEffectsApplier);
reset(mDeviceEffectsApplier);
@@ -5563,7 +5380,7 @@ public class ZenModeHelperTest extends UiServiceTestCase {
}
@Test
- @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI, FLAG_PREVENT_ZEN_DEVICE_EFFECTS_WHILE_DRIVING})
+ @EnableFlags({FLAG_MODES_UI, FLAG_PREVENT_ZEN_DEVICE_EFFECTS_WHILE_DRIVING})
public void testDeviceEffects_whileDrivingWithGrayscale_allowsGrayscale() {
mZenModeHelper.setDeviceEffectsApplier(mDeviceEffectsApplier);
reset(mDeviceEffectsApplier);
@@ -5594,7 +5411,6 @@ public class ZenModeHelperTest extends UiServiceTestCase {
}
@Test
- @EnableFlags(FLAG_MODES_API)
public void removeAndAddAutomaticZenRule_wasCustomized_isRestored() {
// Start with a rule.
mZenModeHelper.mConfig.automaticRules.clear();
@@ -5651,7 +5467,6 @@ public class ZenModeHelperTest extends UiServiceTestCase {
}
@Test
- @EnableFlags(FLAG_MODES_API)
public void removeAndAddAutomaticZenRule_wasNotCustomized_isNotRestored() {
// Start with a single rule.
mZenModeHelper.mConfig.automaticRules.clear();
@@ -5685,7 +5500,6 @@ public class ZenModeHelperTest extends UiServiceTestCase {
}
@Test
- @EnableFlags(FLAG_MODES_API)
public void removeAndAddAutomaticZenRule_recreatedButNotByApp_isNotRestored() {
// Start with a single rule.
mZenModeHelper.mConfig.automaticRules.clear();
@@ -5736,7 +5550,6 @@ public class ZenModeHelperTest extends UiServiceTestCase {
}
@Test
- @EnableFlags(FLAG_MODES_API)
public void removeAndAddAutomaticZenRule_removedByUser_isNotRestored() {
// Start with a single rule.
mZenModeHelper.mConfig.automaticRules.clear();
@@ -5779,7 +5592,7 @@ public class ZenModeHelperTest extends UiServiceTestCase {
}
@Test
- @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI})
+ @EnableFlags(FLAG_MODES_UI)
public void removeAndAddAutomaticZenRule_ifChangingComponent_isAllowedAndDoesNotRestore() {
// Start with a rule.
mZenModeHelper.mConfig.automaticRules.clear();
@@ -5824,7 +5637,6 @@ public class ZenModeHelperTest extends UiServiceTestCase {
}
@Test
- @EnableFlags(FLAG_MODES_API)
public void removeAutomaticZenRule_preservedForRestoringByPackageAndConditionId() {
mContext.getTestablePermissions().setPermission(Manifest.permission.MANAGE_NOTIFICATIONS,
PERMISSION_GRANTED); // So that canManageAZR passes although packages don't match.
@@ -5874,7 +5686,6 @@ public class ZenModeHelperTest extends UiServiceTestCase {
}
@Test
- @EnableFlags(FLAG_MODES_API)
public void removeAllZenRules_preservedForRestoring() {
mZenModeHelper.mConfig.automaticRules.clear();
@@ -5898,14 +5709,13 @@ public class ZenModeHelperTest extends UiServiceTestCase {
}
@Test
- @EnableFlags(FLAG_MODES_API)
public void removeAllZenRules_fromSystem_deletesPreservedRulesToo() {
mZenModeHelper.mConfig.automaticRules.clear();
// Start with deleted rules from 2 different packages.
Instant now = Instant.ofEpochMilli(1701796461000L);
- ZenRule pkg1Rule = newZenRule("pkg1", now.minus(1, ChronoUnit.DAYS), now);
- ZenRule pkg2Rule = newZenRule("pkg2", now.minus(2, ChronoUnit.DAYS), now);
+ ZenRule pkg1Rule = newDeletedZenRule("1", "pkg1", now.minus(1, DAYS), now);
+ ZenRule pkg2Rule = newDeletedZenRule("2", "pkg2", now.minus(2, DAYS), now);
mZenModeHelper.mConfig.deletedRules.put(ZenModeConfig.deletedRuleKey(pkg1Rule), pkg1Rule);
mZenModeHelper.mConfig.deletedRules.put(ZenModeConfig.deletedRuleKey(pkg2Rule), pkg2Rule);
@@ -5918,7 +5728,6 @@ public class ZenModeHelperTest extends UiServiceTestCase {
}
@Test
- @EnableFlags(FLAG_MODES_API)
public void removeAndAddAutomaticZenRule_wasActive_isRestoredAsInactive() {
// Start with a rule.
mZenModeHelper.mConfig.automaticRules.clear();
@@ -5968,7 +5777,6 @@ public class ZenModeHelperTest extends UiServiceTestCase {
}
@Test
- @EnableFlags(FLAG_MODES_API)
public void removeAndAddAutomaticZenRule_wasSnoozed_isRestoredAsInactive() {
// Start with a rule.
mZenModeHelper.mConfig.automaticRules.clear();
@@ -6023,12 +5831,11 @@ public class ZenModeHelperTest extends UiServiceTestCase {
}
@Test
- @EnableFlags(FLAG_MODES_API)
public void testRuleCleanup() throws Exception {
Instant now = Instant.ofEpochMilli(1701796461000L);
- Instant yesterday = now.minus(1, ChronoUnit.DAYS);
- Instant aWeekAgo = now.minus(7, ChronoUnit.DAYS);
- Instant twoMonthsAgo = now.minus(60, ChronoUnit.DAYS);
+ Instant yesterday = now.minus(1, DAYS);
+ Instant aWeekAgo = now.minus(7, DAYS);
+ Instant twoMonthsAgo = now.minus(60, DAYS);
mTestClock.setNowMillis(now.toEpochMilli());
when(mPackageManager.getPackageInfo(eq("good_pkg"), anyInt()))
@@ -6041,24 +5848,28 @@ public class ZenModeHelperTest extends UiServiceTestCase {
config.user = 42;
mZenModeHelper.mConfigs.put(42, config);
// okay rules (not deleted, package exists, with a range of creation dates).
- config.automaticRules.put("ar1", newZenRule("good_pkg", now, null));
- config.automaticRules.put("ar2", newZenRule("good_pkg", yesterday, null));
- config.automaticRules.put("ar3", newZenRule("good_pkg", twoMonthsAgo, null));
+ config.automaticRules.put("ar1", newZenRule("ar1", "good_pkg", now));
+ config.automaticRules.put("ar2", newZenRule("ar2", "good_pkg", yesterday));
+ config.automaticRules.put("ar3", newZenRule("ar3", "good_pkg", twoMonthsAgo));
// newish rules for a missing package
- config.automaticRules.put("ar4", newZenRule("bad_pkg", yesterday, null));
+ config.automaticRules.put("ar4", newZenRule("ar4", "bad_pkg", yesterday));
// oldish rules belonging to a missing package
- config.automaticRules.put("ar5", newZenRule("bad_pkg", aWeekAgo, null));
+ config.automaticRules.put("ar5", newZenRule("ar5", "bad_pkg", aWeekAgo));
// rules deleted recently
- config.deletedRules.put("del1", newZenRule("good_pkg", twoMonthsAgo, yesterday));
- config.deletedRules.put("del2", newZenRule("good_pkg", twoMonthsAgo, aWeekAgo));
+ config.deletedRules.put("del1",
+ newDeletedZenRule("del1", "good_pkg", twoMonthsAgo, yesterday));
+ config.deletedRules.put("del2",
+ newDeletedZenRule("del2", "good_pkg", twoMonthsAgo, aWeekAgo));
// rules deleted a long time ago
- config.deletedRules.put("del3", newZenRule("good_pkg", twoMonthsAgo, twoMonthsAgo));
+ config.deletedRules.put("del3",
+ newDeletedZenRule("del3", "good_pkg", twoMonthsAgo, twoMonthsAgo));
// rules for a missing package, created recently and deleted recently
- config.deletedRules.put("del4", newZenRule("bad_pkg", yesterday, now));
+ config.deletedRules.put("del4", newDeletedZenRule("del4", "bad_pkg", yesterday, now));
// rules for a missing package, created a long time ago and deleted recently
- config.deletedRules.put("del5", newZenRule("bad_pkg", twoMonthsAgo, now));
+ config.deletedRules.put("del5", newDeletedZenRule("del5", "bad_pkg", twoMonthsAgo, now));
// rules for a missing package, created a long time ago and deleted a long time ago
- config.deletedRules.put("del6", newZenRule("bad_pkg", twoMonthsAgo, twoMonthsAgo));
+ config.deletedRules.put("del6",
+ newDeletedZenRule("del6", "bad_pkg", twoMonthsAgo, twoMonthsAgo));
mZenModeHelper.onUserSwitched(42); // copies config and cleans it up.
@@ -6068,20 +5879,120 @@ public class ZenModeHelperTest extends UiServiceTestCase {
.containsExactly("del1", "del2", "del4");
}
- private static ZenRule newZenRule(String pkg, Instant createdAt, @Nullable Instant deletedAt) {
+ @Test
+ @EnableFlags({FLAG_MODES_UI, FLAG_MODES_CLEANUP_IMPLICIT})
+ public void testRuleCleanup_removesNotRecentlyUsedNotModifiedImplicitRules() throws Exception {
+ Instant now = Instant.ofEpochMilli(1701796461000L);
+ Instant yesterday = now.minus(1, DAYS);
+ Instant aWeekAgo = now.minus(7, DAYS);
+ Instant twoMonthsAgo = now.minus(60, DAYS);
+ Instant aYearAgo = now.minus(365, DAYS);
+ mTestClock.setNowMillis(now.toEpochMilli());
+ when(mPackageManager.getPackageInfo(anyString(), anyInt())).thenReturn(new PackageInfo());
+
+ // Set up a config to be loaded, containing a bunch of implicit rules
+ ZenModeConfig config = new ZenModeConfig();
+ config.user = 42;
+ mZenModeHelper.mConfigs.put(42, config);
+ // used recently
+ ZenRule usedRecently1 = newImplicitZenRule("pkg1", aYearAgo, yesterday);
+ ZenRule usedRecently2 = newImplicitZenRule("pkg2", aYearAgo, aWeekAgo);
+ config.automaticRules.put(usedRecently1.id, usedRecently1);
+ config.automaticRules.put(usedRecently2.id, usedRecently2);
+ // not used in a long time
+ ZenRule longUnused = newImplicitZenRule("pkg3", aYearAgo, twoMonthsAgo);
+ config.automaticRules.put(longUnused.id, longUnused);
+ // created a long time ago, before lastActivation tracking
+ ZenRule oldAndLastUsageUnknown = newImplicitZenRule("pkg4", twoMonthsAgo, null);
+ config.automaticRules.put(oldAndLastUsageUnknown.id, oldAndLastUsageUnknown);
+ // created a short time ago, before lastActivation tracking
+ ZenRule newAndLastUsageUnknown = newImplicitZenRule("pkg5", aWeekAgo, null);
+ config.automaticRules.put(newAndLastUsageUnknown.id, newAndLastUsageUnknown);
+ // not used in a long time, but was customized by user
+ ZenRule longUnusedButCustomized = newImplicitZenRule("pkg6", aYearAgo, twoMonthsAgo);
+ longUnusedButCustomized.zenPolicyUserModifiedFields = ZenPolicy.FIELD_CONVERSATIONS;
+ config.automaticRules.put(longUnusedButCustomized.id, longUnusedButCustomized);
+ // created a long time ago, before lastActivation tracking, and was customized by user
+ ZenRule oldAndLastUsageUnknownAndCustomized = newImplicitZenRule("pkg7", twoMonthsAgo,
+ null);
+ oldAndLastUsageUnknownAndCustomized.userModifiedFields = AutomaticZenRule.FIELD_ICON;
+ config.automaticRules.put(oldAndLastUsageUnknownAndCustomized.id,
+ oldAndLastUsageUnknownAndCustomized);
+
+ mZenModeHelper.onUserSwitched(42); // copies config and cleans it up.
+
+ // The recently used OR modified OR last-used-unknown rules stay.
+ assertThat(mZenModeHelper.mConfig.automaticRules.values())
+ .comparingElementsUsing(IGNORE_METADATA)
+ .containsExactly(usedRecently1, usedRecently2, oldAndLastUsageUnknown,
+ newAndLastUsageUnknown, longUnusedButCustomized,
+ oldAndLastUsageUnknownAndCustomized);
+ }
+
+ @Test
+ @EnableFlags({FLAG_MODES_UI, FLAG_MODES_CLEANUP_IMPLICIT})
+ public void testRuleCleanup_assignsLastActivationToImplicitRules() throws Exception {
+ Instant now = Instant.ofEpochMilli(1701796461000L);
+ Instant aWeekAgo = now.minus(7, DAYS);
+ Instant aYearAgo = now.minus(365, DAYS);
+ mTestClock.setNowMillis(now.toEpochMilli());
+ when(mPackageManager.getPackageInfo(anyString(), anyInt())).thenReturn(new PackageInfo());
+
+ // Set up a config to be loaded, containing implicit rules.
+ ZenModeConfig config = new ZenModeConfig();
+ config.user = 42;
+ mZenModeHelper.mConfigs.put(42, config);
+ // with last activation known
+ ZenRule usedRecently = newImplicitZenRule("pkg1", aYearAgo, aWeekAgo);
+ config.automaticRules.put(usedRecently.id, usedRecently);
+ // created a long time ago, with last activation unknown
+ ZenRule oldAndLastUsageUnknown = newImplicitZenRule("pkg4", aYearAgo, null);
+ config.automaticRules.put(oldAndLastUsageUnknown.id, oldAndLastUsageUnknown);
+ // created a short time ago, with last activation unknown
+ ZenRule newAndLastUsageUnknown = newImplicitZenRule("pkg5", aWeekAgo, null);
+ config.automaticRules.put(newAndLastUsageUnknown.id, newAndLastUsageUnknown);
+
+ mZenModeHelper.onUserSwitched(42); // copies config and cleans it up.
+
+ // All rules stayed.
+ usedRecently = getZenRule(usedRecently.id);
+ oldAndLastUsageUnknown = getZenRule(oldAndLastUsageUnknown.id);
+ newAndLastUsageUnknown = getZenRule(newAndLastUsageUnknown.id);
+
+ // The rules with an unknown last usage have been assigned a placeholder one.
+ assertThat(usedRecently.lastActivation).isEqualTo(aWeekAgo);
+ assertThat(oldAndLastUsageUnknown.lastActivation).isEqualTo(now);
+ assertThat(newAndLastUsageUnknown.lastActivation).isEqualTo(now);
+ }
+
+ private static ZenRule newDeletedZenRule(String id, String pkg, Instant createdAt,
+ @NonNull Instant deletedAt) {
+ ZenRule rule = newZenRule(id, pkg, createdAt);
+ rule.deletionInstant = deletedAt;
+ return rule;
+ }
+
+ private static ZenRule newImplicitZenRule(String pkg, @NonNull Instant createdAt,
+ @Nullable Instant lastActivatedAt) {
+ ZenRule implicitRule = newZenRule(implicitRuleId(pkg), pkg, createdAt);
+ implicitRule.lastActivation = lastActivatedAt;
+ return implicitRule;
+ }
+
+ private static ZenRule newZenRule(String id, String pkg, Instant createdAt) {
ZenRule rule = new ZenRule();
+ rule.id = id;
rule.pkg = pkg;
rule.creationTime = createdAt.toEpochMilli();
rule.enabled = true;
- rule.deletionInstant = deletedAt;
+ rule.deletionInstant = null;
// Plus stuff so that isValidAutomaticRule() passes
- rule.name = "A rule from " + pkg + " created on " + createdAt;
+ rule.name = "Rule " + id;
rule.conditionId = Uri.parse(rule.name);
return rule;
}
@Test
- @EnableFlags(FLAG_MODES_API)
public void getAutomaticZenRuleState_ownedRule_returnsRuleState() {
String id = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT,
mContext.getPackageName(),
@@ -6112,14 +6023,13 @@ public class ZenModeHelperTest extends UiServiceTestCase {
}
@Test
- @EnableFlags(FLAG_MODES_API)
public void getAutomaticZenRuleState_notOwnedRule_returnsStateUnknown() {
// Assume existence of a system-owned rule that is currently ACTIVE.
- ZenRule systemRule = newZenRule("android", Instant.now(), null);
+ ZenRule systemRule = newZenRule("systemRule", "android", Instant.now());
systemRule.zenMode = ZEN_MODE_ALARMS;
systemRule.condition = new Condition(systemRule.conditionId, "on", Condition.STATE_TRUE);
ZenModeConfig config = mZenModeHelper.mConfig.copy();
- config.automaticRules.put("systemRule", systemRule);
+ config.automaticRules.put(systemRule.id, systemRule);
mZenModeHelper.setConfig(config, null, ORIGIN_INIT, "", SYSTEM_UID);
assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_ALARMS);
@@ -6128,15 +6038,14 @@ public class ZenModeHelperTest extends UiServiceTestCase {
}
@Test
- @EnableFlags(FLAG_MODES_API)
public void setAutomaticZenRuleState_idForNotOwnedRule_ignored() {
// Assume existence of an other-package-owned rule that is currently ACTIVE.
assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_OFF);
- ZenRule otherRule = newZenRule("another.package", Instant.now(), null);
+ ZenRule otherRule = newZenRule("otherRule", "another.package", Instant.now());
otherRule.zenMode = ZEN_MODE_ALARMS;
otherRule.condition = new Condition(otherRule.conditionId, "on", Condition.STATE_TRUE);
ZenModeConfig config = mZenModeHelper.mConfig.copy();
- config.automaticRules.put("otherRule", otherRule);
+ config.automaticRules.put(otherRule.id, otherRule);
mZenModeHelper.setConfig(config, null, ORIGIN_INIT, "", SYSTEM_UID);
assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_ALARMS);
@@ -6149,15 +6058,14 @@ public class ZenModeHelperTest extends UiServiceTestCase {
}
@Test
- @EnableFlags(FLAG_MODES_API)
public void setAutomaticZenRuleStateFromConditionProvider_conditionForNotOwnedRule_ignored() {
// Assume existence of an other-package-owned rule that is currently ACTIVE.
assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_OFF);
- ZenRule otherRule = newZenRule("another.package", Instant.now(), null);
+ ZenRule otherRule = newZenRule("otherRule", "another.package", Instant.now());
otherRule.zenMode = ZEN_MODE_ALARMS;
otherRule.condition = new Condition(otherRule.conditionId, "on", Condition.STATE_TRUE);
ZenModeConfig config = mZenModeHelper.mConfig.copy();
- config.automaticRules.put("otherRule", otherRule);
+ config.automaticRules.put(otherRule.id, otherRule);
mZenModeHelper.setConfig(config, null, ORIGIN_INIT, "", SYSTEM_UID);
assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_ALARMS);
@@ -6171,7 +6079,6 @@ public class ZenModeHelperTest extends UiServiceTestCase {
}
@Test
- @EnableFlags(FLAG_MODES_API)
public void testCallbacks_policy() throws Exception {
setupZenConfig();
assertThat(mZenModeHelper.getNotificationPolicy(UserHandle.CURRENT).allowReminders())
@@ -6193,7 +6100,6 @@ public class ZenModeHelperTest extends UiServiceTestCase {
}
@Test
- @EnableFlags(FLAG_MODES_API)
public void testCallbacks_consolidatedPolicy() throws Exception {
assertThat(mZenModeHelper.getConsolidatedNotificationPolicy().allowMedia()).isTrue();
SettableFuture<Policy> futureConsolidatedPolicy = SettableFuture.create();
@@ -6219,7 +6125,6 @@ public class ZenModeHelperTest extends UiServiceTestCase {
}
@Test
- @EnableFlags(FLAG_MODES_API)
public void applyGlobalZenModeAsImplicitZenRule_createsImplicitRuleAndActivatesIt() {
mZenModeHelper.mConfig.automaticRules.clear();
@@ -6235,7 +6140,6 @@ public class ZenModeHelperTest extends UiServiceTestCase {
}
@Test
- @EnableFlags(FLAG_MODES_API)
public void applyGlobalZenModeAsImplicitZenRule_updatesImplicitRuleAndActivatesIt() {
mZenModeHelper.mConfig.automaticRules.clear();
@@ -6257,7 +6161,6 @@ public class ZenModeHelperTest extends UiServiceTestCase {
}
@Test
- @EnableFlags(FLAG_MODES_API)
public void applyGlobalZenModeAsImplicitZenRule_ruleCustomized_doesNotUpdateRule() {
mZenModeHelper.mConfig.automaticRules.clear();
String pkg = mContext.getPackageName();
@@ -6290,7 +6193,6 @@ public class ZenModeHelperTest extends UiServiceTestCase {
}
@Test
- @EnableFlags(FLAG_MODES_API)
public void applyGlobalZenModeAsImplicitZenRule_ruleCustomizedButNotFilter_updatesRule() {
mZenModeHelper.mConfig.automaticRules.clear();
String pkg = mContext.getPackageName();
@@ -6322,7 +6224,6 @@ public class ZenModeHelperTest extends UiServiceTestCase {
}
@Test
- @EnableFlags(FLAG_MODES_API)
public void applyGlobalZenModeAsImplicitZenRule_modeOff_deactivatesImplicitRule() {
mZenModeHelper.mConfig.automaticRules.clear();
mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(UserHandle.CURRENT, mPkg, CUSTOM_PKG_UID,
@@ -6339,7 +6240,6 @@ public class ZenModeHelperTest extends UiServiceTestCase {
}
@Test
- @EnableFlags(FLAG_MODES_API)
public void applyGlobalZenModeAsImplicitZenRule_modeOffButNoPreviousRule_ignored() {
mZenModeHelper.mConfig.automaticRules.clear();
@@ -6350,7 +6250,6 @@ public class ZenModeHelperTest extends UiServiceTestCase {
}
@Test
- @EnableFlags(FLAG_MODES_API)
public void applyGlobalZenModeAsImplicitZenRule_update_unsnoozesRule() {
mZenModeHelper.mConfig.automaticRules.clear();
@@ -6375,7 +6274,7 @@ public class ZenModeHelperTest extends UiServiceTestCase {
}
@Test
- @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI})
+ @EnableFlags(FLAG_MODES_UI)
public void applyGlobalZenModeAsImplicitZenRule_again_refreshesRuleName() {
mZenModeHelper.mConfig.automaticRules.clear();
mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(UserHandle.CURRENT,
@@ -6394,7 +6293,7 @@ public class ZenModeHelperTest extends UiServiceTestCase {
}
@Test
- @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI})
+ @EnableFlags(FLAG_MODES_UI)
public void applyGlobalZenModeAsImplicitZenRule_again_doesNotChangeCustomizedRuleName() {
mZenModeHelper.mConfig.automaticRules.clear();
mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(UserHandle.CURRENT,
@@ -6420,19 +6319,6 @@ public class ZenModeHelperTest extends UiServiceTestCase {
}
@Test
- @DisableFlags(FLAG_MODES_API)
- public void applyGlobalZenModeAsImplicitZenRule_flagOff_ignored() {
- mZenModeHelper.mConfig.automaticRules.clear();
-
- withoutWtfCrash(
- () -> mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(UserHandle.CURRENT,
- CUSTOM_PKG_NAME, CUSTOM_PKG_UID, ZEN_MODE_IMPORTANT_INTERRUPTIONS));
-
- assertThat(mZenModeHelper.mConfig.automaticRules).isEmpty();
- }
-
- @Test
- @EnableFlags(FLAG_MODES_API)
public void applyGlobalPolicyAsImplicitZenRule_createsImplicitRule() {
mZenModeHelper.mConfig.automaticRules.clear();
@@ -6457,7 +6343,6 @@ public class ZenModeHelperTest extends UiServiceTestCase {
}
@Test
- @EnableFlags(FLAG_MODES_API)
public void applyGlobalPolicyAsImplicitZenRule_updatesImplicitRule() {
mZenModeHelper.mConfig.automaticRules.clear();
@@ -6489,7 +6374,6 @@ public class ZenModeHelperTest extends UiServiceTestCase {
}
@Test
- @EnableFlags(FLAG_MODES_API)
public void applyGlobalPolicyAsImplicitZenRule_ruleCustomized_doesNotUpdateRule() {
mZenModeHelper.mConfig.automaticRules.clear();
String pkg = mContext.getPackageName();
@@ -6532,7 +6416,6 @@ public class ZenModeHelperTest extends UiServiceTestCase {
}
@Test
- @EnableFlags(FLAG_MODES_API)
public void applyGlobalPolicyAsImplicitZenRule_ruleCustomizedButNotZenPolicy_updatesRule() {
mZenModeHelper.mConfig.automaticRules.clear();
String pkg = mContext.getPackageName();
@@ -6572,7 +6455,7 @@ public class ZenModeHelperTest extends UiServiceTestCase {
}
@Test
- @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI})
+ @EnableFlags(FLAG_MODES_UI)
public void applyGlobalPolicyAsImplicitZenRule_again_refreshesRuleName() {
mZenModeHelper.mConfig.automaticRules.clear();
mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(UserHandle.CURRENT,
@@ -6591,7 +6474,7 @@ public class ZenModeHelperTest extends UiServiceTestCase {
}
@Test
- @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI})
+ @EnableFlags(FLAG_MODES_UI)
public void applyGlobalPolicyAsImplicitZenRule_again_doesNotChangeCustomizedRuleName() {
mZenModeHelper.mConfig.automaticRules.clear();
mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(UserHandle.CURRENT,
@@ -6617,19 +6500,6 @@ public class ZenModeHelperTest extends UiServiceTestCase {
}
@Test
- @DisableFlags(FLAG_MODES_API)
- public void applyGlobalPolicyAsImplicitZenRule_flagOff_ignored() {
- mZenModeHelper.mConfig.automaticRules.clear();
-
- withoutWtfCrash(
- () -> mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(UserHandle.CURRENT,
- CUSTOM_PKG_NAME, CUSTOM_PKG_UID, new Policy(0, 0, 0)));
-
- assertThat(mZenModeHelper.mConfig.automaticRules).isEmpty();
- }
-
- @Test
- @EnableFlags(FLAG_MODES_API)
public void getNotificationPolicyFromImplicitZenRule_returnsSetPolicy() {
Policy writtenPolicy = new Policy(PRIORITY_CATEGORY_CALLS | PRIORITY_CATEGORY_CONVERSATIONS,
PRIORITY_SENDERS_CONTACTS, PRIORITY_SENDERS_STARRED,
@@ -6645,7 +6515,6 @@ public class ZenModeHelperTest extends UiServiceTestCase {
}
@Test
- @EnableFlags(FLAG_MODES_API)
@DisableFlags(FLAG_MODES_UI)
public void getNotificationPolicyFromImplicitZenRule_ruleWithoutPolicy_copiesGlobalPolicy() {
// Implicit rule will get the global policy at the time of rule creation.
@@ -6665,7 +6534,6 @@ public class ZenModeHelperTest extends UiServiceTestCase {
}
@Test
- @EnableFlags(FLAG_MODES_API)
public void getNotificationPolicyFromImplicitZenRule_noImplicitRule_returnsGlobalPolicy() {
Policy policy = new Policy(PRIORITY_CATEGORY_CALLS, PRIORITY_SENDERS_STARRED, 0);
mZenModeHelper.setNotificationPolicy(UserHandle.CURRENT, policy, ORIGIN_APP,
@@ -6680,7 +6548,6 @@ public class ZenModeHelperTest extends UiServiceTestCase {
}
@Test
- @EnableFlags(FLAG_MODES_API)
@DisableFlags(FLAG_MODES_UI)
public void setNotificationPolicy_updatesRulePolicies_ifRulePolicyIsDefaultOrGlobalPolicy() {
ZenPolicy defaultZenPolicy = mZenModeHelper.getDefaultZenPolicy();
@@ -6726,7 +6593,6 @@ public class ZenModeHelperTest extends UiServiceTestCase {
}
@Test
- @EnableFlags(Flags.FLAG_MODES_API)
public void addRule_iconIdWithResourceNameTooLong_ignoresIcon() {
int resourceId = 999;
String veryLongResourceName = "com.android.server.notification:drawable/"
@@ -6745,7 +6611,7 @@ public class ZenModeHelperTest extends UiServiceTestCase {
}
@Test
- @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI})
+ @EnableFlags(FLAG_MODES_UI)
public void setManualZenRuleDeviceEffects_noPreexistingMode() {
ZenDeviceEffects effects = new ZenDeviceEffects.Builder()
.setShouldDimWallpaper(true)
@@ -6759,7 +6625,7 @@ public class ZenModeHelperTest extends UiServiceTestCase {
}
@Test
- @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI})
+ @EnableFlags(FLAG_MODES_UI)
public void setManualZenRuleDeviceEffects_preexistingMode() {
mZenModeHelper.setManualZenMode(UserHandle.CURRENT, ZEN_MODE_OFF, Uri.EMPTY,
ORIGIN_USER_IN_SYSTEMUI, "create manual rule", "settings", SYSTEM_UID);
@@ -6776,7 +6642,7 @@ public class ZenModeHelperTest extends UiServiceTestCase {
}
@Test
- @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI})
+ @EnableFlags(FLAG_MODES_UI)
public void addAutomaticZenRule_startsDisabled_recordsDisabledOrigin() {
AutomaticZenRule startsDisabled = new AutomaticZenRule.Builder("Rule", Uri.EMPTY)
.setOwner(new ComponentName(mPkg, "SomeProvider"))
@@ -6792,7 +6658,7 @@ public class ZenModeHelperTest extends UiServiceTestCase {
}
@Test
- @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI})
+ @EnableFlags(FLAG_MODES_UI)
public void updateAutomaticZenRule_disabling_recordsDisabledOrigin() {
AutomaticZenRule startsEnabled = new AutomaticZenRule.Builder("Rule", Uri.EMPTY)
.setOwner(new ComponentName(mPkg, "SomeProvider"))
@@ -6815,7 +6681,7 @@ public class ZenModeHelperTest extends UiServiceTestCase {
}
@Test
- @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI})
+ @EnableFlags(FLAG_MODES_UI)
public void updateAutomaticZenRule_keepingDisabled_preservesPreviousDisabledOrigin() {
AutomaticZenRule startsEnabled = new AutomaticZenRule.Builder("Rule", Uri.EMPTY)
.setOwner(new ComponentName(mPkg, "SomeProvider"))
@@ -6845,7 +6711,7 @@ public class ZenModeHelperTest extends UiServiceTestCase {
}
@Test
- @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI})
+ @EnableFlags(FLAG_MODES_UI)
public void updateAutomaticZenRule_enabling_clearsDisabledOrigin() {
AutomaticZenRule startsEnabled = new AutomaticZenRule.Builder("Rule", Uri.EMPTY)
.setOwner(new ComponentName(mPkg, "SomeProvider"))
@@ -6875,7 +6741,7 @@ public class ZenModeHelperTest extends UiServiceTestCase {
}
@Test
- @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI})
+ @EnableFlags(FLAG_MODES_UI)
public void setAutomaticZenRuleState_manualActivation_appliesOverride() {
AutomaticZenRule rule = new AutomaticZenRule.Builder("Rule", Uri.parse("cond"))
.setPackage(mPkg)
@@ -6893,7 +6759,7 @@ public class ZenModeHelperTest extends UiServiceTestCase {
}
@Test
- @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI})
+ @EnableFlags(FLAG_MODES_UI)
public void setAutomaticZenRuleState_manualActivationAndThenDeactivation_removesOverride() {
AutomaticZenRule rule = new AutomaticZenRule.Builder("Rule", Uri.parse("cond"))
.setPackage(mPkg)
@@ -6930,7 +6796,7 @@ public class ZenModeHelperTest extends UiServiceTestCase {
}
@Test
- @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI})
+ @EnableFlags(FLAG_MODES_UI)
public void setAutomaticZenRuleState_manualDeactivationAndThenReactivation_removesOverride() {
AutomaticZenRule rule = new AutomaticZenRule.Builder("Rule", Uri.parse("cond"))
.setPackage(mPkg)
@@ -6976,7 +6842,7 @@ public class ZenModeHelperTest extends UiServiceTestCase {
}
@Test
- @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI})
+ @EnableFlags(FLAG_MODES_UI)
public void setAutomaticZenRuleState_manualDeactivation_appliesOverride() {
AutomaticZenRule rule = new AutomaticZenRule.Builder("Rule", Uri.parse("cond"))
.setPackage(mPkg)
@@ -7002,7 +6868,7 @@ public class ZenModeHelperTest extends UiServiceTestCase {
}
@Test
- @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI})
+ @EnableFlags(FLAG_MODES_UI)
public void setAutomaticZenRuleState_ifManualActive_appCannotDeactivateBeforeActivating() {
AutomaticZenRule rule = new AutomaticZenRule.Builder("Rule", Uri.parse("cond"))
.setPackage(mPkg)
@@ -7039,7 +6905,7 @@ public class ZenModeHelperTest extends UiServiceTestCase {
}
@Test
- @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI})
+ @EnableFlags(FLAG_MODES_UI)
public void setAutomaticZenRuleState_ifManualInactive_appCannotReactivateBeforeDeactivating() {
AutomaticZenRule rule = new AutomaticZenRule.Builder("Rule", Uri.parse("cond"))
.setPackage(mPkg)
@@ -7085,7 +6951,7 @@ public class ZenModeHelperTest extends UiServiceTestCase {
}
@Test
- @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI})
+ @EnableFlags(FLAG_MODES_UI)
public void setAutomaticZenRuleState_withActivationOverride_userActionFromAppCanDeactivate() {
AutomaticZenRule rule = new AutomaticZenRule.Builder("Rule", Uri.parse("cond"))
.setPackage(mPkg)
@@ -7109,7 +6975,7 @@ public class ZenModeHelperTest extends UiServiceTestCase {
}
@Test
- @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI})
+ @EnableFlags(FLAG_MODES_UI)
public void setAutomaticZenRuleState_withDeactivationOverride_userActionFromAppCanActivate() {
AutomaticZenRule rule = new AutomaticZenRule.Builder("Rule", Uri.parse("cond"))
.setPackage(mPkg)
@@ -7140,7 +7006,7 @@ public class ZenModeHelperTest extends UiServiceTestCase {
}
@Test
- @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI})
+ @EnableFlags(FLAG_MODES_UI)
public void setAutomaticZenRuleState_manualActionFromApp_isNotOverride() {
AutomaticZenRule rule = new AutomaticZenRule.Builder("Rule", Uri.parse("cond"))
.setPackage(mPkg)
@@ -7165,7 +7031,7 @@ public class ZenModeHelperTest extends UiServiceTestCase {
}
@Test
- @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI})
+ @EnableFlags(FLAG_MODES_UI)
public void setAutomaticZenRuleState_implicitRuleManualActivation_doesNotUseOverride() {
mContext.getTestablePermissions().setPermission(Manifest.permission.MANAGE_NOTIFICATIONS,
PERMISSION_GRANTED); // So that canManageAZR succeeds.
@@ -7188,7 +7054,7 @@ public class ZenModeHelperTest extends UiServiceTestCase {
}
@Test
- @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI})
+ @EnableFlags(FLAG_MODES_UI)
public void setAutomaticZenRuleState_implicitRuleManualDeactivation_doesNotUseOverride() {
mContext.getTestablePermissions().setPermission(Manifest.permission.MANAGE_NOTIFICATIONS,
PERMISSION_GRANTED); // So that canManageAZR succeeds.
@@ -7214,24 +7080,8 @@ public class ZenModeHelperTest extends UiServiceTestCase {
}
@Test
- @DisableFlags({FLAG_MODES_API, FLAG_MODES_UI})
- public void testDefaultConfig_preModesApi_rulesAreBare() {
- // Create a new user, which should get a copy of the default policy.
- mZenModeHelper.onUserSwitched(101);
-
- ZenRule eventsRule = mZenModeHelper.mConfig.automaticRules.get(
- ZenModeConfig.EVENTS_OBSOLETE_RULE_ID);
-
- assertThat(eventsRule).isNotNull();
- assertThat(eventsRule.zenPolicy).isNull();
- assertThat(eventsRule.type).isEqualTo(TYPE_UNKNOWN);
- assertThat(eventsRule.triggerDescription).isNull();
- }
-
- @Test
- @EnableFlags(FLAG_MODES_API)
@DisableFlags(FLAG_MODES_UI)
- public void testDefaultConfig_modesApi_rulesHaveFullPolicy() {
+ public void testDefaultConfig_rulesHaveFullPolicy() {
// Create a new user, which should get a copy of the default policy.
mZenModeHelper.onUserSwitched(201);
@@ -7245,7 +7095,7 @@ public class ZenModeHelperTest extends UiServiceTestCase {
}
@Test
- @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI})
+ @EnableFlags(FLAG_MODES_UI)
public void testDefaultConfig_modesUi_rulesHaveFullPolicy() {
// Create a new user, which should get a copy of the default policy.
mZenModeHelper.onUserSwitched(301);
@@ -7260,7 +7110,7 @@ public class ZenModeHelperTest extends UiServiceTestCase {
}
@Test
- @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI})
+ @EnableFlags(FLAG_MODES_UI)
public void setAutomaticZenRuleState_withManualActivation_activeOnReboot()
throws Exception {
AutomaticZenRule rule = new AutomaticZenRule.Builder("Rule", Uri.parse("cond"))
@@ -7298,7 +7148,7 @@ public class ZenModeHelperTest extends UiServiceTestCase {
}
@Test
- @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI})
+ @EnableFlags(FLAG_MODES_UI)
public void setAutomaticZenRuleState_withManualDeactivation_clearedOnReboot()
throws Exception {
AutomaticZenRule rule = new AutomaticZenRule.Builder("Rule", Uri.parse("cond"))
@@ -7336,7 +7186,6 @@ public class ZenModeHelperTest extends UiServiceTestCase {
}
@Test
- @EnableFlags(FLAG_MODES_API)
public void addAutomaticZenRule_withoutPolicy_getsItsOwnInstanceOfDefaultPolicy() {
// Add a rule without policy -> uses default config
AutomaticZenRule azr = new AutomaticZenRule.Builder("Rule", Uri.parse("cond"))
@@ -7352,7 +7201,7 @@ public class ZenModeHelperTest extends UiServiceTestCase {
}
@Test
- @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI})
+ @EnableFlags(FLAG_MODES_UI)
public void readXml_withDisabledEventsRule_deletesIt() throws Exception {
ZenRule rule = new ZenRule();
rule.id = ZenModeConfig.EVENTS_OBSOLETE_RULE_ID;
@@ -7372,7 +7221,7 @@ public class ZenModeHelperTest extends UiServiceTestCase {
}
@Test
- @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI})
+ @EnableFlags(FLAG_MODES_UI)
public void readXml_withEnabledEventsRule_keepsIt() throws Exception {
ZenRule rule = new ZenRule();
rule.id = ZenModeConfig.EVENTS_OBSOLETE_RULE_ID;
@@ -7392,7 +7241,7 @@ public class ZenModeHelperTest extends UiServiceTestCase {
}
@Test
- @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI})
+ @EnableFlags(FLAG_MODES_UI)
public void updateHasPriorityChannels_keepsChannelSettings() {
setupZenConfig();
@@ -7512,6 +7361,125 @@ public class ZenModeHelperTest extends UiServiceTestCase {
"config: setAzrStateFromCps: cond/cond (ORIGIN_APP) from uid " + CUSTOM_PKG_UID);
}
+ @Test
+ @EnableFlags({FLAG_MODES_UI, FLAG_MODES_CLEANUP_IMPLICIT})
+ public void setAutomaticZenRuleState_updatesLastActivation() {
+ String ruleOne = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT, mPkg,
+ new AutomaticZenRule.Builder("rule", CONDITION_ID)
+ .setConfigurationActivity(new ComponentName(mPkg, "cls"))
+ .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
+ .build(),
+ ORIGIN_APP, "reason", CUSTOM_PKG_UID);
+ String ruleTwo = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT, mPkg,
+ new AutomaticZenRule.Builder("unrelated", Uri.parse("other.condition"))
+ .setConfigurationActivity(new ComponentName(mPkg, "cls"))
+ .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
+ .build(),
+ ORIGIN_APP, "reason", CUSTOM_PKG_UID);
+
+ assertThat(getZenRule(ruleOne).lastActivation).isNull();
+ assertThat(getZenRule(ruleTwo).lastActivation).isNull();
+
+ Instant firstActivation = Instant.ofEpochMilli(100);
+ mTestClock.setNow(firstActivation);
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, ruleOne, CONDITION_TRUE,
+ ORIGIN_APP, CUSTOM_PKG_UID);
+
+ assertThat(getZenRule(ruleOne).lastActivation).isEqualTo(firstActivation);
+ assertThat(getZenRule(ruleTwo).lastActivation).isNull();
+
+ mTestClock.setNow(Instant.ofEpochMilli(300));
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, ruleOne, CONDITION_FALSE,
+ ORIGIN_APP, CUSTOM_PKG_UID);
+
+ assertThat(getZenRule(ruleOne).lastActivation).isEqualTo(firstActivation);
+ assertThat(getZenRule(ruleTwo).lastActivation).isNull();
+
+ Instant secondActivation = Instant.ofEpochMilli(500);
+ mTestClock.setNow(secondActivation);
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, ruleOne, CONDITION_TRUE,
+ ORIGIN_APP, CUSTOM_PKG_UID);
+
+ assertThat(getZenRule(ruleOne).lastActivation).isEqualTo(secondActivation);
+ assertThat(getZenRule(ruleTwo).lastActivation).isNull();
+ }
+
+ @Test
+ @EnableFlags({FLAG_MODES_UI, FLAG_MODES_CLEANUP_IMPLICIT})
+ public void setManualZenMode_updatesLastActivation() {
+ assertThat(mZenModeHelper.mConfig.manualRule.lastActivation).isNull();
+ Instant instant = Instant.ofEpochMilli(100);
+ mTestClock.setNow(instant);
+
+ mZenModeHelper.setManualZenMode(UserHandle.CURRENT, ZEN_MODE_ALARMS, null,
+ ORIGIN_USER_IN_SYSTEMUI, "reason", "systemui", SYSTEM_UID);
+
+ assertThat(mZenModeHelper.mConfig.manualRule.lastActivation).isEqualTo(instant);
+ }
+
+ @Test
+ @EnableFlags({FLAG_MODES_UI, FLAG_MODES_CLEANUP_IMPLICIT})
+ public void applyGlobalZenModeAsImplicitZenRule_updatesLastActivation() {
+ Instant instant = Instant.ofEpochMilli(100);
+ mTestClock.setNow(instant);
+
+ mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(UserHandle.CURRENT, CUSTOM_PKG_NAME,
+ CUSTOM_PKG_UID, ZEN_MODE_ALARMS);
+
+ ZenRule implicitRule = getZenRule(implicitRuleId(CUSTOM_PKG_NAME));
+ assertThat(implicitRule.lastActivation).isEqualTo(instant);
+ }
+
+ @Test
+ @EnableFlags({FLAG_MODES_UI, FLAG_MODES_CLEANUP_IMPLICIT})
+ public void setAutomaticZenRuleState_notChangingActiveState_doesNotUpdateLastActivation() {
+ String ruleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT, mPkg,
+ new AutomaticZenRule.Builder("rule", CONDITION_ID)
+ .setConfigurationActivity(new ComponentName(mPkg, "cls"))
+ .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
+ .build(),
+ ORIGIN_APP, "reason", CUSTOM_PKG_UID);
+
+ assertThat(getZenRule(ruleId).lastActivation).isNull();
+
+ // Manual activation comes first
+ Instant firstActivation = Instant.ofEpochMilli(100);
+ mTestClock.setNow(firstActivation);
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, ruleId, CONDITION_TRUE,
+ ORIGIN_USER_IN_SYSTEMUI, SYSTEM_UID);
+
+ assertThat(getZenRule(ruleId).lastActivation).isEqualTo(firstActivation);
+
+ // Now the app says the rule should be active (assume it's on a schedule, and the app
+ // doesn't listen to broadcasts so it doesn't know an override was present). This doesn't
+ // change the activation state.
+ mTestClock.setNow(Instant.ofEpochMilli(300));
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, ruleId, CONDITION_TRUE,
+ ORIGIN_APP, CUSTOM_PKG_UID);
+
+ assertThat(getZenRule(ruleId).lastActivation).isEqualTo(firstActivation);
+ }
+
+ @Test
+ @EnableFlags({FLAG_MODES_UI, FLAG_MODES_CLEANUP_IMPLICIT})
+ public void addOrUpdateRule_doesNotUpdateLastActivation() {
+ AutomaticZenRule azr = new AutomaticZenRule.Builder("rule", CONDITION_ID)
+ .setConfigurationActivity(new ComponentName(mPkg, "cls"))
+ .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
+ .build();
+
+ String ruleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT, mPkg, azr,
+ ORIGIN_APP, "reason", CUSTOM_PKG_UID);
+
+ assertThat(getZenRule(ruleId).lastActivation).isNull();
+
+ mZenModeHelper.updateAutomaticZenRule(UserHandle.CURRENT, ruleId,
+ new AutomaticZenRule.Builder(azr).setName("New name").build(), ORIGIN_APP, "reason",
+ CUSTOM_PKG_UID);
+
+ assertThat(getZenRule(ruleId).lastActivation).isNull();
+ }
+
private static void addZenRule(ZenModeConfig config, String id, String ownerPkg, int zenMode,
@Nullable ZenPolicy zenPolicy) {
ZenRule rule = new ZenRule();
@@ -7529,22 +7497,27 @@ public class ZenModeHelperTest extends UiServiceTestCase {
}
private static final Correspondence<ZenRule, ZenRule> IGNORE_METADATA =
- Correspondence.transforming(zr -> {
- Parcel p = Parcel.obtain();
- try {
- zr.writeToParcel(p, 0);
- p.setDataPosition(0);
- ZenRule copy = new ZenRule(p);
- copy.creationTime = 0;
- copy.userModifiedFields = 0;
- copy.zenPolicyUserModifiedFields = 0;
- copy.zenDeviceEffectsUserModifiedFields = 0;
- return copy;
- } finally {
- p.recycle();
- }
- },
- "Ignoring timestamp and userModifiedFields");
+ Correspondence.transforming(
+ ZenModeHelperTest::cloneWithoutMetadata,
+ ZenModeHelperTest::cloneWithoutMetadata,
+ "Ignoring timestamps and userModifiedFields");
+
+ private static ZenRule cloneWithoutMetadata(ZenRule rule) {
+ Parcel p = Parcel.obtain();
+ try {
+ rule.writeToParcel(p, 0);
+ p.setDataPosition(0);
+ ZenRule copy = new ZenRule(p);
+ copy.creationTime = 0;
+ copy.userModifiedFields = 0;
+ copy.zenPolicyUserModifiedFields = 0;
+ copy.zenDeviceEffectsUserModifiedFields = 0;
+ copy.lastActivation = null;
+ return copy;
+ } finally {
+ p.recycle();
+ }
+ }
private ZenRule expectedImplicitRule(String ownerPkg, int zenMode, ZenPolicy policy,
@Nullable Boolean conditionActive) {
@@ -7574,7 +7547,6 @@ public class ZenModeHelperTest extends UiServiceTestCase {
return rule;
}
- // TODO: b/310620812 - Update setup methods to include allowChannels() when MODES_API is inlined
private void setupZenConfig() {
Policy customPolicy = new Policy(PRIORITY_CATEGORY_REMINDERS
| PRIORITY_CATEGORY_CALLS | PRIORITY_CATEGORY_MESSAGES
@@ -7583,7 +7555,7 @@ public class ZenModeHelperTest extends UiServiceTestCase {
PRIORITY_SENDERS_STARRED,
PRIORITY_SENDERS_STARRED,
SUPPRESSED_EFFECT_BADGE,
- 0,
+ 0, // allows priority channels.
CONVERSATION_SENDERS_IMPORTANT);
mZenModeHelper.setNotificationPolicy(UserHandle.CURRENT, customPolicy, ORIGIN_UNKNOWN, 1);
if (!Flags.modesUi()) {
@@ -7599,8 +7571,11 @@ public class ZenModeHelperTest extends UiServiceTestCase {
assertEquals(STATE_ALLOW, dndProto.getCalls().getNumber());
assertEquals(PEOPLE_STARRED, dndProto.getAllowCallsFrom().getNumber());
assertEquals(STATE_ALLOW, dndProto.getMessages().getNumber());
+ assertEquals(PEOPLE_STARRED, dndProto.getAllowMessagesFrom().getNumber());
assertEquals(STATE_ALLOW, dndProto.getEvents().getNumber());
assertEquals(STATE_ALLOW, dndProto.getRepeatCallers().getNumber());
+ assertEquals(STATE_ALLOW, dndProto.getConversations().getNumber());
+ assertEquals(CONV_IMPORTANT, dndProto.getAllowConversationsFrom().getNumber());
assertEquals(STATE_ALLOW, dndProto.getFullscreen().getNumber());
assertEquals(STATE_ALLOW, dndProto.getLights().getNumber());
assertEquals(STATE_ALLOW, dndProto.getPeek().getNumber());
@@ -7616,7 +7591,7 @@ public class ZenModeHelperTest extends UiServiceTestCase {
return;
}
- // When modes_api flag is on, the default zen config is the device defaults.
+ // The default zen config is the device defaults.
assertThat(dndProto.getAlarms().getNumber()).isEqualTo(STATE_ALLOW);
assertThat(dndProto.getMedia().getNumber()).isEqualTo(STATE_ALLOW);
assertThat(dndProto.getSystem().getNumber()).isEqualTo(STATE_DISALLOW);
@@ -7627,6 +7602,8 @@ public class ZenModeHelperTest extends UiServiceTestCase {
assertThat(dndProto.getAllowMessagesFrom().getNumber()).isEqualTo(PEOPLE_STARRED);
assertThat(dndProto.getEvents().getNumber()).isEqualTo(STATE_DISALLOW);
assertThat(dndProto.getRepeatCallers().getNumber()).isEqualTo(STATE_ALLOW);
+ assertThat(dndProto.getConversations().getNumber()).isEqualTo(STATE_ALLOW);
+ assertThat(dndProto.getAllowConversationsFrom().getNumber()).isEqualTo(CONV_IMPORTANT);
assertThat(dndProto.getFullscreen().getNumber()).isEqualTo(STATE_DISALLOW);
assertThat(dndProto.getLights().getNumber()).isEqualTo(STATE_DISALLOW);
assertThat(dndProto.getPeek().getNumber()).isEqualTo(STATE_DISALLOW);
@@ -7634,6 +7611,8 @@ public class ZenModeHelperTest extends UiServiceTestCase {
assertThat(dndProto.getBadge().getNumber()).isEqualTo(STATE_ALLOW);
assertThat(dndProto.getAmbient().getNumber()).isEqualTo(STATE_DISALLOW);
assertThat(dndProto.getNotificationList().getNumber()).isEqualTo(STATE_ALLOW);
+ assertThat(dndProto.getAllowChannels().getNumber()).isEqualTo(
+ DNDProtoEnums.CHANNEL_POLICY_PRIORITY);
}
private static String getZenLog() {
@@ -7642,16 +7621,6 @@ public class ZenModeHelperTest extends UiServiceTestCase {
return zenLogWriter.toString();
}
- private static void withoutWtfCrash(Runnable test) {
- Log.TerribleFailureHandler oldHandler = Log.setWtfHandler((tag, what, system) -> {
- });
- try {
- test.run();
- } finally {
- Log.setWtfHandler(oldHandler);
- }
- }
-
/**
* Wrapper to use TypedXmlPullParser as XmlResourceParser for Resources.getXml()
*/
@@ -7954,6 +7923,10 @@ public class ZenModeHelperTest extends UiServiceTestCase {
return mNowMillis;
}
+ private void setNow(Instant instant) {
+ mNowMillis = instant.toEpochMilli();
+ }
+
private void setNowMillis(long millis) {
mNowMillis = millis;
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenPolicyTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenPolicyTest.java
index 6433b76defc3..8b9376454c0f 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenPolicyTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenPolicyTest.java
@@ -21,7 +21,6 @@ import static com.google.common.truth.Truth.assertThat;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.fail;
-import android.app.Flags;
import android.os.Parcel;
import android.platform.test.flag.junit.SetFlagsRule;
import android.service.notification.ZenPolicy;
@@ -34,7 +33,6 @@ import com.android.server.UiServiceTestCase;
import com.google.protobuf.nano.InvalidProtocolBufferNanoException;
-import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -50,11 +48,6 @@ public class ZenPolicyTest extends UiServiceTestCase {
@Rule
public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
- @Before
- public final void setUp() {
- mSetFlagsRule.enableFlags(Flags.FLAG_MODES_API);
- }
-
@Test
public void testZenPolicyApplyAllowedToDisallowed() {
ZenPolicy.Builder builder = new ZenPolicy.Builder();
@@ -207,8 +200,6 @@ public class ZenPolicyTest extends UiServiceTestCase {
@Test
public void testZenPolicyApplyChannels_applyUnset() {
- mSetFlagsRule.enableFlags(Flags.FLAG_MODES_API);
-
ZenPolicy.Builder builder = new ZenPolicy.Builder();
ZenPolicy unset = builder.build();
@@ -223,8 +214,6 @@ public class ZenPolicyTest extends UiServiceTestCase {
@Test
public void testZenPolicyApplyChannels_applyStricter() {
- mSetFlagsRule.enableFlags(Flags.FLAG_MODES_API);
-
ZenPolicy.Builder builder = new ZenPolicy.Builder();
builder.allowPriorityChannels(false);
ZenPolicy none = builder.build();
@@ -239,8 +228,6 @@ public class ZenPolicyTest extends UiServiceTestCase {
@Test
public void testZenPolicyApplyChannels_applyLooser() {
- mSetFlagsRule.enableFlags(Flags.FLAG_MODES_API);
-
ZenPolicy.Builder builder = new ZenPolicy.Builder();
builder.allowPriorityChannels(false);
ZenPolicy none = builder.build();
@@ -255,8 +242,6 @@ public class ZenPolicyTest extends UiServiceTestCase {
@Test
public void testZenPolicyApplyChannels_applySet() {
- mSetFlagsRule.enableFlags(Flags.FLAG_MODES_API);
-
ZenPolicy.Builder builder = new ZenPolicy.Builder();
ZenPolicy unset = builder.build();
@@ -270,8 +255,6 @@ public class ZenPolicyTest extends UiServiceTestCase {
@Test
public void testZenPolicyOverwrite_allUnsetPolicies() {
- mSetFlagsRule.enableFlags(Flags.FLAG_MODES_API);
-
ZenPolicy.Builder builder = new ZenPolicy.Builder();
ZenPolicy unset = builder.build();
@@ -292,8 +275,6 @@ public class ZenPolicyTest extends UiServiceTestCase {
@Test
public void testZenPolicyOverwrite_someOverlappingFields_takeNewPolicy() {
- mSetFlagsRule.enableFlags(Flags.FLAG_MODES_API);
-
ZenPolicy p1 = new ZenPolicy.Builder()
.allowCalls(ZenPolicy.PEOPLE_TYPE_CONTACTS)
.allowMessages(ZenPolicy.PEOPLE_TYPE_STARRED)
@@ -375,7 +356,6 @@ public class ZenPolicyTest extends UiServiceTestCase {
@Test
public void testEmptyZenPolicy_emptyChannels() {
- mSetFlagsRule.enableFlags(Flags.FLAG_MODES_API);
ZenPolicy.Builder builder = new ZenPolicy.Builder();
ZenPolicy policy = builder.build();
@@ -688,22 +668,7 @@ public class ZenPolicyTest extends UiServiceTestCase {
}
@Test
- public void testAllowChannels_noFlag() {
- mSetFlagsRule.disableFlags(Flags.FLAG_MODES_API);
-
- // allowChannels should be unset, not be modifiable, and not show up in any output
- ZenPolicy.Builder builder = new ZenPolicy.Builder();
- builder.allowPriorityChannels(true);
- ZenPolicy policy = builder.build();
-
- assertThat(policy.getPriorityChannelsAllowed()).isEqualTo(ZenPolicy.STATE_UNSET);
- assertThat(policy.toString().contains("allowChannels")).isFalse();
- }
-
- @Test
public void testAllowChannels() {
- mSetFlagsRule.enableFlags(Flags.FLAG_MODES_API);
-
// allow priority channels
ZenPolicy.Builder builder = new ZenPolicy.Builder();
builder.allowPriorityChannels(true);
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java
index 9e7575f1c644..f5bda9f2b9e3 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java
@@ -104,7 +104,6 @@ public class ActivityMetricsLaunchObserverTests extends WindowTestsBase {
.setTask(mTrampolineActivity.getTask())
.setComponent(createRelative(DEFAULT_COMPONENT_PACKAGE_NAME, "TopActivity"))
.build();
- mTopActivity.mDisplayContent.mOpeningApps.add(mTopActivity);
mTransition = new Transition(TRANSIT_OPEN, 0 /* flags */,
mTopActivity.mTransitionController, createTestBLASTSyncEngine());
mTransition.mParticipants.add(mTopActivity);
@@ -485,7 +484,6 @@ public class ActivityMetricsLaunchObserverTests extends WindowTestsBase {
@Test
public void testActivityDrawnWithoutTransition() {
- mTopActivity.mDisplayContent.mOpeningApps.remove(mTopActivity);
mTransition.mParticipants.remove(mTopActivity);
onIntentStarted(mTopActivity.intent);
notifyAndVerifyActivityLaunched(mTopActivity);
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index bb296148dad1..280e432f0245 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -3326,16 +3326,13 @@ public class ActivityRecordTests extends WindowTestsBase {
makeWindowVisibleAndDrawn(app);
// Put the activity in close transition.
- mDisplayContent.mOpeningApps.clear();
- mDisplayContent.mClosingApps.add(app.mActivityRecord);
- mDisplayContent.prepareAppTransition(TRANSIT_CLOSE);
+ requestTransition(app.mActivityRecord, WindowManager.TRANSIT_CLOSE);
// Remove window during transition, so it is requested to hide, but won't be committed until
// the transition is finished.
app.mActivityRecord.onRemovedFromDisplay();
app.mActivityRecord.prepareSurfaces();
- assertTrue(mDisplayContent.mClosingApps.contains(app.mActivityRecord));
assertFalse(app.mActivityRecord.isVisibleRequested());
assertTrue(app.mActivityRecord.isVisible());
assertTrue(app.mActivityRecord.isSurfaceShowing());
@@ -3353,11 +3350,6 @@ public class ActivityRecordTests extends WindowTestsBase {
makeWindowVisibleAndDrawn(app);
app.mActivityRecord.prepareSurfaces();
- // Put the activity in close transition.
- mDisplayContent.mOpeningApps.clear();
- mDisplayContent.mClosingApps.add(app.mActivityRecord);
- mDisplayContent.prepareAppTransition(TRANSIT_CLOSE);
-
// Commit visibility before start transition.
app.mActivityRecord.commitVisibility(false, false);
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java
index 3c74ad06a21f..a9be47d71213 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java
@@ -509,6 +509,32 @@ public class ActivityTaskSupervisorTests extends WindowTestsBase {
assertThat(mSupervisor.mOpaqueContainerHelper.isOpaque(rootTask)).isTrue();
}
+ @Test
+ public void testOpaque_nonLeafTaskFragmentWithDirectActivity_opaque() {
+ final ActivityRecord directChildActivity = new ActivityBuilder(mAtm).setCreateTask(true)
+ .build();
+ directChildActivity.setOccludesParent(true);
+ final Task nonLeafTask = directChildActivity.getTask();
+ final TaskFragment directChildFragment = new TaskFragment(mAtm, new Binder(),
+ true /* createdByOrganizer */, false /* isEmbedded */);
+ nonLeafTask.addChild(directChildFragment, 0);
+
+ assertThat(mSupervisor.mOpaqueContainerHelper.isOpaque(nonLeafTask)).isTrue();
+ }
+
+ @Test
+ public void testOpaque_nonLeafTaskFragmentWithDirectActivity_transparent() {
+ final ActivityRecord directChildActivity = new ActivityBuilder(mAtm).setCreateTask(true)
+ .build();
+ directChildActivity.setOccludesParent(false);
+ final Task nonLeafTask = directChildActivity.getTask();
+ final TaskFragment directChildFragment = new TaskFragment(mAtm, new Binder(),
+ true /* createdByOrganizer */, false /* isEmbedded */);
+ nonLeafTask.addChild(directChildFragment, 0);
+
+ assertThat(mSupervisor.mOpaqueContainerHelper.isOpaque(nonLeafTask)).isFalse();
+ }
+
@NonNull
private TaskFragment createChildTaskFragment(@NonNull Task parent,
@WindowConfiguration.WindowingMode int windowingMode,
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java
index 018ea58e7120..d016e735f0c7 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java
@@ -151,6 +151,10 @@ class AppCompatActivityRobot {
doReturn(taskBounds).when(mTaskStack.top()).getBounds();
}
+ void configureTaskAppBounds(@NonNull Rect appBounds) {
+ mTaskStack.top().getWindowConfiguration().setAppBounds(appBounds);
+ }
+
void configureTopActivityBounds(@NonNull Rect activityBounds) {
doReturn(activityBounds).when(mActivityStack.top()).getBounds();
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatLetterboxPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatLetterboxPolicyTest.java
index 0c310324ce67..2603d787ae37 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppCompatLetterboxPolicyTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatLetterboxPolicyTest.java
@@ -16,7 +16,9 @@
package com.android.server.wm;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.view.InsetsSource.FLAG_INSETS_ROUNDED_CORNER;
+import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
@@ -27,6 +29,7 @@ import static org.junit.Assert.assertNull;
import static org.mockito.Mockito.mock;
import android.graphics.Rect;
+import android.platform.test.annotations.EnableFlags;
import android.platform.test.annotations.Presubmit;
import android.view.InsetsSource;
import android.view.InsetsState;
@@ -40,6 +43,7 @@ import androidx.annotation.Nullable;
import androidx.test.filters.SmallTest;
import com.android.internal.R;
+import com.android.window.flags.Flags;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -225,6 +229,53 @@ public class AppCompatLetterboxPolicyTest extends WindowTestsBase {
});
}
+ @Test
+ @EnableFlags(Flags.FLAG_EXCLUDE_CAPTION_FROM_APP_BOUNDS)
+ public void testGetRoundedCornersRadius_letterboxBoundsMatchHeightInFreeform_notRounded() {
+ runTestScenario((robot) -> {
+ robot.conf().setLetterboxActivityCornersRadius(15);
+ robot.configureWindowState();
+ robot.activity().createActivityWithComponent();
+ robot.setTopActivityInLetterboxAnimation(/* inLetterboxAnimation */ false);
+ robot.activity().setTopActivityVisible(/* isVisible */ true);
+ robot.setIsLetterboxedForFixedOrientationAndAspectRatio(/* inLetterbox */ true);
+ robot.conf().setLetterboxActivityCornersRounded(/* rounded */ true);
+ robot.resources().configureGetDimensionPixelSize(R.dimen.taskbar_frame_height, 20);
+
+ robot.activity().setTaskWindowingMode(WINDOWING_MODE_FREEFORM);
+ final Rect appBounds = new Rect(0, 0, 100, 500);
+ robot.configureWindowStateFrame(appBounds);
+ robot.activity().configureTaskAppBounds(appBounds);
+
+ robot.startLetterbox();
+
+ robot.checkWindowStateRoundedCornersRadius(/* expected */ 0);
+ });
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_EXCLUDE_CAPTION_FROM_APP_BOUNDS)
+ public void testGetRoundedCornersRadius_letterboxBoundsNotMatchHeightInFreeform_rounded() {
+ runTestScenario((robot) -> {
+ robot.conf().setLetterboxActivityCornersRadius(15);
+ robot.configureWindowState();
+ robot.activity().createActivityWithComponent();
+ robot.setTopActivityInLetterboxAnimation(/* inLetterboxAnimation */ false);
+ robot.activity().setTopActivityVisible(/* isVisible */ true);
+ robot.setIsLetterboxedForFixedOrientationAndAspectRatio(/* inLetterbox */ true);
+ robot.conf().setLetterboxActivityCornersRounded(/* rounded */ true);
+ robot.resources().configureGetDimensionPixelSize(R.dimen.taskbar_frame_height, 20);
+
+ robot.activity().setTaskWindowingMode(WINDOWING_MODE_FREEFORM);
+ robot.configureWindowStateFrame(new Rect(0, 0, 500, 200));
+ robot.activity().configureTaskAppBounds(new Rect(0, 0, 500, 500));
+
+ robot.startLetterbox();
+
+ robot.checkWindowStateRoundedCornersRadius(/* expected */ 15);
+ });
+ }
+
/**
* Runs a test scenario providing a Robot.
*/
@@ -265,6 +316,10 @@ public class AppCompatLetterboxPolicyTest extends WindowTestsBase {
spyOn(getTransparentPolicy());
}
+ void startLetterbox() {
+ getAppCompatLetterboxPolicy().start(mWindowState);
+ }
+
void configureWindowStateWithTaskBar(boolean hasInsetsRoundedCorners) {
configureWindowState(/* withTaskBar */ true, hasInsetsRoundedCorners);
}
@@ -273,6 +328,10 @@ public class AppCompatLetterboxPolicyTest extends WindowTestsBase {
configureWindowState(/* withTaskBar */ false, /* hasInsetsRoundedCorners */ false);
}
+ void configureWindowStateFrame(@NonNull Rect frame) {
+ doReturn(frame).when(mWindowState).getFrame();
+ }
+
void configureInsetsRoundedCorners(@NonNull RoundedCorners roundedCorners) {
mInsetsState.setRoundedCorners(roundedCorners);
}
@@ -290,6 +349,7 @@ public class AppCompatLetterboxPolicyTest extends WindowTestsBase {
}
mWindowState.mInvGlobalScale = 1f;
final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams();
+ attrs.type = TYPE_BASE_APPLICATION;
doReturn(mInsetsState).when(mWindowState).getInsetsState();
doReturn(attrs).when(mWindowState).getAttrs();
doReturn(true).when(mWindowState).isDrawn();
diff --git a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
index fbb123e25b29..e08197155f03 100644
--- a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
@@ -294,6 +294,14 @@ public class BackNavigationControllerTests extends WindowTestsBase {
backNavigationInfo = startBackNavigation();
assertThat(typeToString(backNavigationInfo.getType()))
.isEqualTo(typeToString(BackNavigationInfo.TYPE_CROSS_ACTIVITY));
+
+ // reset drawing status, test previous activity has no process.
+ backNavigationInfo.onBackNavigationFinished(false);
+ mBackNavigationController.clearBackAnimations(true);
+ doReturn(false).when(testCase.recordBack).hasProcess();
+ backNavigationInfo = startBackNavigation();
+ assertThat(typeToString(backNavigationInfo.getType()))
+ .isEqualTo(typeToString(BackNavigationInfo.TYPE_CALLBACK));
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/DesktopModeHelperTest.java b/services/tests/wmtests/src/com/android/server/wm/DesktopModeHelperTest.java
index eaffc481098e..1e91bedb5c18 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DesktopModeHelperTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DesktopModeHelperTest.java
@@ -16,8 +16,6 @@
package com.android.server.wm;
-import static android.provider.Settings.Global.DEVELOPMENT_OVERRIDE_DESKTOP_MODE_FEATURES;
-
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.doReturn;
@@ -30,7 +28,6 @@ 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.window.DesktopModeFlags;
import androidx.test.filters.SmallTest;
@@ -74,14 +71,12 @@ public class DesktopModeHelperTest {
doReturn(mContext.getContentResolver()).when(mMockContext).getContentResolver();
resetDesktopModeFlagsCache();
resetEnforceDeviceRestriction();
- resetFlagOverride();
}
@After
public void tearDown() throws Exception {
resetDesktopModeFlagsCache();
resetEnforceDeviceRestriction();
- resetFlagOverride();
}
@DisableFlags({Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE,
@@ -167,7 +162,8 @@ public class DesktopModeHelperTest {
@DisableFlags(Flags.FLAG_ENABLE_DESKTOP_MODE_THROUGH_DEV_OPTION)
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
@Test
- public void canEnterDesktopMode_DWFlagEnabled_configDevOptionOn_flagOverrideOn_returnsTrue() {
+ public void canEnterDesktopMode_DWFlagEnabled_configDevOptionOn_flagOverrideOn_returnsTrue()
+ throws Exception {
doReturn(true).when(mMockResources).getBoolean(
eq(R.bool.config_isDesktopModeDevOptionSupported)
);
@@ -177,22 +173,41 @@ public class DesktopModeHelperTest {
}
@Test
- public void isDeviceEligibleForDesktopMode_configDEModeOn_returnsTrue() {
+ public void isDeviceEligibleForDesktopMode_configDEModeOnAndIntDispHostsDesktop_returnsTrue() {
+ doReturn(true).when(mMockResources).getBoolean(eq(R.bool.config_isDesktopModeSupported));
+ doReturn(true).when(mMockResources)
+ .getBoolean(eq(R.bool.config_canInternalDisplayHostDesktops));
+
+ assertThat(DesktopModeHelper.isDeviceEligibleForDesktopMode(mMockContext)).isTrue();
+ }
+
+ @Test
+ public void isDeviceEligibleForDesktopMode_configDEModeOffAndIntDispHostsDesktop_returnsFalse() {
+ doReturn(true).when(mMockResources).getBoolean(eq(R.bool.config_isDesktopModeSupported));
+ doReturn(false).when(mMockResources)
+ .getBoolean(eq(R.bool.config_canInternalDisplayHostDesktops));
+
+ assertThat(DesktopModeHelper.isDeviceEligibleForDesktopMode(mMockContext)).isFalse();
+ }
+
+ @Test
+ public void isDeviceEligibleForDesktopMode_configDEModeOnAndIntDispHostsDesktopOff_returnsFalse() {
+ doReturn(false).when(mMockResources).getBoolean(eq(R.bool.config_isDesktopModeSupported));
doReturn(true).when(mMockResources).getBoolean(eq(R.bool.config_canInternalDisplayHostDesktops));
- assertThat(DesktopModeHelper.isInternalDisplayEligibleToHostDesktops(mMockContext)).isTrue();
+ assertThat(DesktopModeHelper.isDeviceEligibleForDesktopMode(mMockContext)).isFalse();
}
@DisableFlags(Flags.FLAG_ENABLE_DESKTOP_MODE_THROUGH_DEV_OPTION)
@Test
public void isDeviceEligibleForDesktopMode_supportFlagOff_returnsFalse() {
- assertThat(DesktopModeHelper.isInternalDisplayEligibleToHostDesktops(mMockContext)).isFalse();
+ assertThat(DesktopModeHelper.isDeviceEligibleForDesktopMode(mMockContext)).isFalse();
}
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_MODE_THROUGH_DEV_OPTION)
@Test
public void isDeviceEligibleForDesktopMode_supportFlagOn_returnsFalse() {
- assertThat(DesktopModeHelper.isInternalDisplayEligibleToHostDesktops(mMockContext)).isFalse();
+ assertThat(DesktopModeHelper.isDeviceEligibleForDesktopMode(mMockContext)).isFalse();
}
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_MODE_THROUGH_DEV_OPTION)
@@ -202,7 +217,7 @@ public class DesktopModeHelperTest {
eq(R.bool.config_isDesktopModeDevOptionSupported)
);
- assertThat(DesktopModeHelper.isInternalDisplayEligibleToHostDesktops(mMockContext)).isTrue();
+ assertThat(DesktopModeHelper.isDeviceEligibleForDesktopMode(mMockContext)).isTrue();
}
private void resetEnforceDeviceRestriction() throws Exception {
@@ -227,13 +242,10 @@ public class DesktopModeHelperTest {
cachedToggleOverride.set(/* obj= */ null, /* value= */ null);
}
- private void resetFlagOverride() {
- Settings.Global.putString(mContext.getContentResolver(),
- DEVELOPMENT_OVERRIDE_DESKTOP_MODE_FEATURES, null);
- }
-
- private void setFlagOverride(DesktopModeFlags.ToggleOverride override) {
- Settings.Global.putInt(mContext.getContentResolver(),
- DEVELOPMENT_OVERRIDE_DESKTOP_MODE_FEATURES, override.getSetting());
+ private void setFlagOverride(DesktopModeFlags.ToggleOverride override) throws Exception {
+ Field cachedToggleOverride = DesktopModeFlags.class.getDeclaredField(
+ "sCachedToggleOverride");
+ cachedToggleOverride.setAccessible(true);
+ cachedToggleOverride.set(/* obj= */ null, /* value= */ override);
}
} \ No newline at end of file
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index 82435b24dad6..d5b9751b0f51 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -164,6 +164,7 @@ import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
+import java.util.function.BooleanSupplier;
/**
* Tests for the {@link DisplayContent} class.
@@ -1799,8 +1800,7 @@ public class DisplayContentTests extends WindowTestsBase {
final ActivityRecord app = new ActivityBuilder(mAtm).setCreateTask(true).build();
app.setVisible(false);
app.setState(ActivityRecord.State.RESUMED, "test");
- mDisplayContent.prepareAppTransition(WindowManager.TRANSIT_OPEN);
- mDisplayContent.mOpeningApps.add(app);
+ requestTransition(app, WindowManager.TRANSIT_OPEN);
final int newOrientation = getRotatedOrientation(mDisplayContent);
app.setRequestedOrientation(newOrientation);
@@ -2674,16 +2674,67 @@ public class DisplayContentTests extends WindowTestsBase {
public void testKeyguardGoingAwayWhileAodShown() {
mDisplayContent.getDisplayPolicy().setAwake(true);
- final WindowState appWin = newWindowBuilder("appWin", TYPE_APPLICATION).setDisplay(
- mDisplayContent).build();
- final ActivityRecord activity = appWin.mActivityRecord;
+ final KeyguardController keyguard = mAtm.mKeyguardController;
+ final ActivityRecord activity = new ActivityBuilder(mAtm).setCreateTask(true).build();
+ final int displayId = mDisplayContent.getDisplayId();
+
+ final BooleanSupplier keyguardShowing = () -> keyguard.isKeyguardShowing(displayId);
+ final BooleanSupplier keyguardGoingAway = () -> keyguard.isKeyguardGoingAway(displayId);
+ final BooleanSupplier appVisible = activity::isVisibleRequested;
+
+ // Begin locked and in AOD
+ keyguard.setKeyguardShown(displayId, true /* keyguard */, true /* aod */);
+ assertFalse(keyguardGoingAway.getAsBoolean());
+ assertFalse(appVisible.getAsBoolean());
+
+ // Start unlocking from AOD.
+ keyguard.keyguardGoingAway(displayId, 0x0 /* flags */);
+ assertTrue(keyguardGoingAway.getAsBoolean());
+ assertTrue(appVisible.getAsBoolean());
- mAtm.mKeyguardController.setKeyguardShown(appWin.getDisplayId(), true /* keyguardShowing */,
- true /* aodShowing */);
- assertFalse(activity.isVisibleRequested());
+ // Clear AOD. This does *not* clear the going-away status.
+ keyguard.setKeyguardShown(displayId, true /* keyguard */, false /* aod */);
+ assertTrue(keyguardGoingAway.getAsBoolean());
+ assertTrue(appVisible.getAsBoolean());
+
+ // Finish unlock
+ keyguard.setKeyguardShown(displayId, false /* keyguard */, false /* aod */);
+ assertFalse(keyguardGoingAway.getAsBoolean());
+ assertTrue(appVisible.getAsBoolean());
+ }
+
+ @Test
+ public void testKeyguardGoingAwayCanceledWhileAodShown() {
+ mDisplayContent.getDisplayPolicy().setAwake(true);
- mAtm.mKeyguardController.keyguardGoingAway(appWin.getDisplayId(), 0 /* flags */);
- assertTrue(activity.isVisibleRequested());
+ final KeyguardController keyguard = mAtm.mKeyguardController;
+ final ActivityRecord activity = new ActivityBuilder(mAtm).setCreateTask(true).build();
+ final int displayId = mDisplayContent.getDisplayId();
+
+ final BooleanSupplier keyguardShowing = () -> keyguard.isKeyguardShowing(displayId);
+ final BooleanSupplier keyguardGoingAway = () -> keyguard.isKeyguardGoingAway(displayId);
+ final BooleanSupplier appVisible = activity::isVisibleRequested;
+
+ // Begin locked and in AOD
+ keyguard.setKeyguardShown(displayId, true /* keyguard */, true /* aod */);
+ assertFalse(keyguardGoingAway.getAsBoolean());
+ assertFalse(appVisible.getAsBoolean());
+
+ // Start unlocking from AOD.
+ keyguard.keyguardGoingAway(displayId, 0x0 /* flags */);
+ assertTrue(keyguardGoingAway.getAsBoolean());
+ assertTrue(appVisible.getAsBoolean());
+
+ // Clear AOD. This does *not* clear the going-away status.
+ keyguard.setKeyguardShown(displayId, true /* keyguard */, false /* aod */);
+ assertTrue(keyguardGoingAway.getAsBoolean());
+ assertTrue(appVisible.getAsBoolean());
+
+ // Same API call a second time cancels the unlock, because AOD isn't changing.
+ keyguard.setKeyguardShown(displayId, true /* keyguard */, false /* aod */);
+ assertTrue(keyguardShowing.getAsBoolean());
+ assertFalse(keyguardGoingAway.getAsBoolean());
+ assertFalse(appVisible.getAsBoolean());
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index 95bca2b17efb..1dc32b00acba 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -4486,6 +4486,49 @@ public class SizeCompatTests extends WindowTestsBase {
}
@Test
+ @EnableFlags(Flags.FLAG_EXCLUDE_CAPTION_FROM_APP_BOUNDS)
+ @DisableCompatChanges({ActivityInfo.INSETS_DECOUPLED_CONFIGURATION_ENFORCED})
+ public void testInFreeform_boundsSandboxedToAppBounds() {
+ allowDesktopMode();
+ 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
@EnableFlags(Flags.FLAG_IGNORE_ASPECT_RATIO_RESTRICTIONS_FOR_RESIZEABLE_FREEFORM_ACTIVITIES)
public void testUserAspectRatioOverridesNotAppliedToResizeableFreeformActivity() {
final TaskBuilder taskBuilder =
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
index 7ab55bf7e874..cc2a76dcc9f2 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
@@ -189,11 +189,12 @@ public class TaskFragmentTest extends WindowTestsBase {
doReturn(true).when(mTaskFragment).isVisible();
doReturn(true).when(mTaskFragment).isVisibleRequested();
+ spyOn(mTaskFragment.mTransitionController);
clearInvocations(mTransaction);
mTaskFragment.setBounds(endBounds);
// No change transition, but update the organized surface position.
- verify(mTaskFragment, never()).initializeChangeTransition(any(), any());
+ verify(mTaskFragment.mTransitionController, never()).collectVisibleChange(any());
verify(mTransaction).setPosition(mLeash, endBounds.left, endBounds.top);
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
index edffab801499..cee98fb1b34c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
@@ -409,17 +409,6 @@ public class WindowContainerTests extends WindowTestsBase {
}
@Test
- public void testIsAnimating_TransitionFlag() {
- final TestWindowContainerBuilder builder = new TestWindowContainerBuilder(mWm);
- final TestWindowContainer root = builder.setLayer(0).build();
- final TestWindowContainer child1 = root.addChildWindow(
- builder.setWaitForTransitionStart(true));
-
- assertFalse(root.isAnimating(TRANSITION));
- assertTrue(child1.isAnimating(TRANSITION));
- }
-
- @Test
public void testIsAnimating_ParentsFlag() {
final TestWindowContainerBuilder builder = new TestWindowContainerBuilder(mWm);
final TestWindowContainer root = builder.setLayer(0).build();
@@ -1655,7 +1644,7 @@ public class WindowContainerTests extends WindowTestsBase {
};
TestWindowContainer(WindowManagerService wm, int layer, boolean isAnimating,
- boolean isVisible, boolean waitTransitStart, Integer orientation, WindowState ws) {
+ boolean isVisible, Integer orientation, WindowState ws) {
super(wm);
mLayer = layer;
@@ -1663,7 +1652,6 @@ public class WindowContainerTests extends WindowTestsBase {
mIsVisible = isVisible;
mFillsParent = true;
mOrientation = orientation;
- mWaitForTransitStart = waitTransitStart;
mWindowState = ws;
spyOn(mSurfaceAnimator);
doReturn(mIsAnimating).when(mSurfaceAnimator).isAnimating();
@@ -1729,11 +1717,6 @@ public class WindowContainerTests extends WindowTestsBase {
}
@Override
- boolean isWaitingForTransitionStart() {
- return mWaitForTransitStart;
- }
-
- @Override
WindowState asWindowState() {
return mWindowState;
}
@@ -1744,7 +1727,6 @@ public class WindowContainerTests extends WindowTestsBase {
private int mLayer;
private boolean mIsAnimating;
private boolean mIsVisible;
- private boolean mIsWaitTransitStart;
private Integer mOrientation;
private WindowState mWindowState;
@@ -1782,14 +1764,9 @@ public class WindowContainerTests extends WindowTestsBase {
return this;
}
- TestWindowContainerBuilder setWaitForTransitionStart(boolean waitTransitStart) {
- mIsWaitTransitStart = waitTransitStart;
- return this;
- }
-
TestWindowContainer build() {
return new TestWindowContainer(mWm, mLayer, mIsAnimating, mIsVisible,
- mIsWaitTransitStart, mOrientation, mWindowState);
+ mOrientation, mWindowState);
}
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java
index 1dfb20a41816..d228970e0371 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java
@@ -19,6 +19,7 @@ package com.android.server.wm;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
import static android.content.pm.ActivityInfo.INSETS_DECOUPLED_CONFIGURATION_ENFORCED;
+import static android.content.pm.ActivityInfo.OVERRIDE_ENABLE_INSETS_DECOUPLED_CONFIGURATION;
import static android.content.res.Configuration.GRAMMATICAL_GENDER_NOT_SPECIFIED;
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
@@ -33,9 +34,11 @@ import static com.android.server.wm.ActivityRecord.State.RESUMED;
import static com.android.server.wm.ActivityRecord.State.STARTED;
import static com.android.server.wm.ActivityRecord.State.STOPPED;
import static com.android.server.wm.ActivityRecord.State.STOPPING;
+import static com.android.server.wm.ConfigurationContainer.applySizeOverrideIfNeeded;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
@@ -58,6 +61,7 @@ import android.content.res.Configuration;
import android.graphics.Rect;
import android.os.LocaleList;
import android.os.RemoteException;
+import android.platform.test.annotations.EnableFlags;
import android.platform.test.annotations.Presubmit;
import org.junit.Before;
@@ -453,6 +457,56 @@ public class WindowProcessControllerTests extends WindowTestsBase {
assertEquals(topDisplayArea, mWpc.getTopActivityDisplayArea());
}
+ @Test
+ @EnableFlags(com.android.window.flags.Flags.FLAG_INSETS_DECOUPLED_CONFIGURATION)
+ public void testOverrideConfigurationApplied() {
+ final DisplayContent displayContent = new TestDisplayContent.Builder(mAtm, 1000, 1500)
+ .setSystemDecorations(true).setDensityDpi(160).build();
+ final DisplayPolicy displayPolicy = displayContent.getDisplayPolicy();
+ // Setup the decor insets info.
+ final DisplayPolicy.DecorInsets.Info decorInsetsInfo = new DisplayPolicy.DecorInsets.Info();
+ final Rect emptyRect = new Rect();
+ decorInsetsInfo.mNonDecorInsets.set(emptyRect);
+ decorInsetsInfo.mConfigInsets.set(emptyRect);
+ decorInsetsInfo.mOverrideConfigInsets.set(new Rect(0, 100, 0, 200));
+ decorInsetsInfo.mOverrideNonDecorInsets.set(new Rect(0, 0, 0, 200));
+ decorInsetsInfo.mNonDecorFrame.set(new Rect(0, 0, 1000, 1500));
+ decorInsetsInfo.mConfigFrame.set(new Rect(0, 0, 1000, 1500));
+ decorInsetsInfo.mOverrideConfigFrame.set(new Rect(0, 100, 1000, 1300));
+ decorInsetsInfo.mOverrideNonDecorFrame.set(new Rect(0, 0, 1000, 1300));
+ doReturn(decorInsetsInfo).when(displayPolicy)
+ .getDecorInsetsInfo(anyInt(), anyInt(), anyInt());
+
+ final Configuration newParentConfig = displayContent.getConfiguration();
+ final Configuration resolvedConfig = new Configuration();
+
+ // Mock the app info to not enforce the decoupled configuration to apply the override.
+ final ApplicationInfo appInfo = mock(ApplicationInfo.class);
+ doReturn(false).when(appInfo)
+ .isChangeEnabled(INSETS_DECOUPLED_CONFIGURATION_ENFORCED);
+ doReturn(false).when(appInfo)
+ .isChangeEnabled(OVERRIDE_ENABLE_INSETS_DECOUPLED_CONFIGURATION);
+
+ // No value should be set before override.
+ assertNull(resolvedConfig.windowConfiguration.getAppBounds());
+ applySizeOverrideIfNeeded(
+ displayContent,
+ appInfo,
+ newParentConfig,
+ resolvedConfig,
+ false /* optsOutEdgeToEdge */,
+ false /* hasFixedRotationTransform */,
+ false /* hasCompatDisplayInsets */,
+ null /* task */);
+
+ // Assert the override config insets are applied.
+ // Status bars, and all non-decor insets should be deducted for the config screen size.
+ assertEquals(1200, resolvedConfig.screenHeightDp);
+ // Only the non-decor insets should be deducted for the app bounds.
+ assertNotNull(resolvedConfig.windowConfiguration.getAppBounds());
+ assertEquals(1300, resolvedConfig.windowConfiguration.getAppBounds().height());
+ }
+
private TestDisplayContent createTestDisplayContentInContainer() {
return new TestDisplayContent.Builder(mAtm, 1000, 1500).build();
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTracingPerfettoTest.java b/services/tests/wmtests/src/com/android/server/wm/WindowTracingPerfettoTest.java
index 12b744546f5e..9367941e32a3 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTracingPerfettoTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTracingPerfettoTest.java
@@ -18,12 +18,16 @@ package com.android.server.wm;
import static android.tools.traces.Utils.busyWaitForDataSourceRegistration;
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
import static com.android.dx.mockito.inline.extended.ExtendedMockito.times;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verifyZeroInteractions;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
@@ -34,11 +38,15 @@ import android.platform.test.annotations.Presubmit;
import android.tools.ScenarioBuilder;
import android.tools.traces.io.ResultWriter;
import android.tools.traces.monitors.PerfettoTraceMonitor;
+import android.util.Log;
import android.view.Choreographer;
+import androidx.test.filters.FlakyTest;
import androidx.test.filters.SmallTest;
+import androidx.test.uiautomator.UiDevice;
import org.junit.After;
+import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
@@ -51,14 +59,15 @@ import java.io.IOException;
/**
* Test class for {@link WindowTracingPerfetto}.
*/
+@FlakyTest(bugId = 372558379)
@SmallTest
@Presubmit
public class WindowTracingPerfettoTest {
private static final String TEST_DATA_SOURCE_NAME = "android.windowmanager.test";
private static WindowManagerService sWmMock;
- private static Choreographer sChoreographer;
private static WindowTracing sWindowTracing;
+ private static Boolean sIsDataSourceRegisteredSuccessfully;
private PerfettoTraceMonitor mTraceMonitor;
@@ -66,19 +75,39 @@ public class WindowTracingPerfettoTest {
public static void setUpOnce() throws Exception {
sWmMock = Mockito.mock(WindowManagerService.class);
Mockito.doNothing().when(sWmMock).dumpDebugLocked(Mockito.any(), Mockito.anyInt());
- sChoreographer = Mockito.mock(Choreographer.class);
- sWindowTracing = new WindowTracingPerfetto(sWmMock, sChoreographer,
+ sWindowTracing = new WindowTracingPerfetto(sWmMock, Mockito.mock(Choreographer.class),
new WindowManagerGlobalLock(), TEST_DATA_SOURCE_NAME);
- busyWaitForDataSourceRegistration(TEST_DATA_SOURCE_NAME);
+ }
+
+ @AfterClass
+ public static void tearDownOnce() {
+ sWmMock = null;
+ sWindowTracing = null;
}
@Before
public void setUp() throws IOException {
- Mockito.clearInvocations(sWmMock);
+ if (sIsDataSourceRegisteredSuccessfully != null) {
+ assumeTrue("Failed to register data source", sIsDataSourceRegisteredSuccessfully);
+ return;
+ }
+ try {
+ busyWaitForDataSourceRegistration(TEST_DATA_SOURCE_NAME);
+ sIsDataSourceRegisteredSuccessfully = true;
+ } catch (Exception e) {
+ sIsDataSourceRegisteredSuccessfully = false;
+ final String perfettoStatus = UiDevice.getInstance(getInstrumentation())
+ .executeShellCommand("perfetto --query");
+ Log.e(WindowTracingPerfettoTest.class.getSimpleName(),
+ "Failed to register data source: " + perfettoStatus);
+ // Only fail once. The rest tests will be skipped by assumeTrue.
+ fail("Failed to register data source");
+ }
}
@After
public void tearDown() throws IOException {
+ Mockito.clearInvocations(sWmMock);
stopTracing();
}
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 0b3d720bf52a..58833e8f8141 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -9786,7 +9786,6 @@ public class CarrierConfigManager {
* <p>
* This config is empty by default.
*/
- @FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG)
public static final String KEY_CARRIER_SUPPORTED_SATELLITE_SERVICES_PER_PROVIDER_BUNDLE =
"carrier_supported_satellite_services_per_provider_bundle";
@@ -9826,7 +9825,6 @@ public class CarrierConfigManager {
*
* The default value is false.
*/
- @FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG)
public static final String KEY_SATELLITE_ATTACH_SUPPORTED_BOOL =
"satellite_attach_supported_bool";
@@ -9848,7 +9846,6 @@ public class CarrierConfigManager {
* <p>
* The default value is 180 seconds.
*/
- @FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG)
public static final String KEY_SATELLITE_CONNECTION_HYSTERESIS_SEC_INT =
"satellite_connection_hysteresis_sec_int";
@@ -9863,7 +9860,6 @@ public class CarrierConfigManager {
* See SignalStrength#MAX_LTE_RSRP and SignalStrength#MIN_LTE_RSRP. Any signal level outside
* these boundaries is considered invalid.
*/
- @FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG)
public static final String KEY_NTN_LTE_RSRP_THRESHOLDS_INT_ARRAY =
"ntn_lte_rsrp_thresholds_int_array";
@@ -9883,7 +9879,6 @@ public class CarrierConfigManager {
* This key is considered invalid if the format is violated. If the key is invalid or
* not configured, a default value set will apply.
*/
- @FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG)
public static final String KEY_NTN_LTE_RSRQ_THRESHOLDS_INT_ARRAY =
"ntn_lte_rsrq_thresholds_int_array";
@@ -9901,7 +9896,6 @@ public class CarrierConfigManager {
* This key is considered invalid if the format is violated. If the key is invalid or
* not configured, a default value set will apply.
*/
- @FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG)
public static final String KEY_NTN_LTE_RSSNR_THRESHOLDS_INT_ARRAY =
"ntn_lte_rssnr_thresholds_int_array";
@@ -9926,7 +9920,6 @@ public class CarrierConfigManager {
* If the key is invalid or not configured, a default value (RSRP = 1 << 0) will apply.
*
*/
- @FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG)
public static final String KEY_PARAMETERS_USED_FOR_NTN_LTE_SIGNAL_BAR_INT =
"parameters_used_for_ntn_lte_signal_bar_int";
@@ -10018,7 +10011,6 @@ public class CarrierConfigManager {
*
* The default value is 7 days.
*/
- @FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG)
public static final String KEY_SATELLITE_ENTITLEMENT_STATUS_REFRESH_DAYS_INT =
"satellite_entitlement_status_refresh_days_int";
@@ -10029,7 +10021,6 @@ public class CarrierConfigManager {
*
* The default value is false.
*/
- @FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG)
public static final String KEY_SATELLITE_ENTITLEMENT_SUPPORTED_BOOL =
"satellite_entitlement_supported_bool";
@@ -10207,6 +10198,17 @@ public class CarrierConfigManager {
"carrier_supported_satellite_notification_hysteresis_sec_int";
/**
+ * Satellite notification display restriction reset time in seconds.
+ *
+ * The device shows a notification when it connects to a satellite. If the user interacts
+ * with the notification, it won't be shown again immediately. Instead, the notification
+ * will only reappear after below key mentioned amount of time has passed.
+ */
+ @FlaggedApi(Flags.FLAG_SATELLITE_25Q4_APIS)
+ public static final String KEY_SATELLITE_CONNECTED_NOTIFICATION_THROTTLE_MILLIS_INT =
+ "satellite_connected_notification_throttle_millis_int";
+
+ /**
* An integer key holds the timeout duration in seconds used to determine whether to exit
* carrier-roaming NB-IOT satellite mode.
*
@@ -11428,6 +11430,10 @@ public class CarrierConfigManager {
sDefaults.putInt(KEY_CARRIER_ROAMING_NTN_EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE_INT,
SatelliteManager.EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE_T911);
sDefaults.putInt(KEY_CARRIER_SUPPORTED_SATELLITE_NOTIFICATION_HYSTERESIS_SEC_INT, 180);
+ if (Flags.starlinkDataBugfix()) {
+ sDefaults.putLong(KEY_SATELLITE_CONNECTED_NOTIFICATION_THROTTLE_MILLIS_INT,
+ TimeUnit.DAYS.toMillis(7));
+ }
sDefaults.putInt(KEY_SATELLITE_ROAMING_SCREEN_OFF_INACTIVITY_TIMEOUT_SEC_INT, 30);
sDefaults.putInt(KEY_SATELLITE_ROAMING_P2P_SMS_INACTIVITY_TIMEOUT_SEC_INT, 180);
sDefaults.putInt(KEY_SATELLITE_ROAMING_ESOS_INACTIVITY_TIMEOUT_SEC_INT, 600);
diff --git a/telephony/java/android/telephony/NetworkRegistrationInfo.java b/telephony/java/android/telephony/NetworkRegistrationInfo.java
index 0c324e6061cb..7de0a2a8edb5 100644
--- a/telephony/java/android/telephony/NetworkRegistrationInfo.java
+++ b/telephony/java/android/telephony/NetworkRegistrationInfo.java
@@ -209,7 +209,6 @@ public final class NetworkRegistrationInfo implements Parcelable {
/**
* MMS service
*/
- @FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG)
public static final int SERVICE_TYPE_MMS = 6;
/** @hide */
@@ -713,7 +712,6 @@ public final class NetworkRegistrationInfo implements Parcelable {
*
* @return {@code true} if network is a non-terrestrial network else {@code false}.
*/
- @FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG)
public boolean isNonTerrestrialNetwork() {
return mIsNonTerrestrialNetwork;
}
@@ -1198,7 +1196,6 @@ public final class NetworkRegistrationInfo implements Parcelable {
* else {@code false}.
* @return The builder.
*/
- @FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG)
public @NonNull Builder setIsNonTerrestrialNetwork(boolean isNonTerrestrialNetwork) {
mIsNonTerrestrialNetwork = isNonTerrestrialNetwork;
return this;
diff --git a/telephony/java/android/telephony/ServiceState.java b/telephony/java/android/telephony/ServiceState.java
index 127bbff01575..35dd8b240e13 100644
--- a/telephony/java/android/telephony/ServiceState.java
+++ b/telephony/java/android/telephony/ServiceState.java
@@ -16,7 +16,6 @@
package android.telephony;
-import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -36,7 +35,6 @@ import android.telephony.NetworkRegistrationInfo.Domain;
import android.telephony.NetworkRegistrationInfo.NRState;
import android.text.TextUtils;
-import com.android.internal.telephony.flags.Flags;
import com.android.telephony.Rlog;
import java.lang.annotation.Retention;
@@ -2262,7 +2260,6 @@ public class ServiceState implements Parcelable {
*
* @return {@code true} if device is connected to a non-terrestrial network else {@code false}.
*/
- @FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG)
public boolean isUsingNonTerrestrialNetwork() {
synchronized (mNetworkRegistrationInfos) {
for (NetworkRegistrationInfo nri : mNetworkRegistrationInfos) {
diff --git a/telephony/java/android/telephony/TelephonyDisplayInfo.java b/telephony/java/android/telephony/TelephonyDisplayInfo.java
index bb4ce6e787de..4411873b40dd 100644
--- a/telephony/java/android/telephony/TelephonyDisplayInfo.java
+++ b/telephony/java/android/telephony/TelephonyDisplayInfo.java
@@ -16,15 +16,12 @@
package android.telephony;
-import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.os.Parcel;
import android.os.Parcelable;
import android.telephony.Annotation.NetworkType;
import android.telephony.Annotation.OverrideNetworkType;
-import com.android.internal.telephony.flags.Flags;
-
import java.util.Objects;
/**
@@ -97,10 +94,8 @@ public final class TelephonyDisplayInfo implements Parcelable {
private final boolean mIsRoaming;
- @FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG)
private final boolean mIsNtn;
- @FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG)
private final boolean mIsSatelliteConstrainedData;
/**
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 504605d0a1a2..41569deeddb5 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -7080,7 +7080,8 @@ public class TelephonyManager {
*/
@Deprecated
public boolean isVoiceCapable() {
- return hasCapability(PackageManager.FEATURE_TELEPHONY_CALLING,
+ if (mContext == null) return true;
+ return mContext.getResources().getBoolean(
com.android.internal.R.bool.config_voice_capable);
}
@@ -7104,7 +7105,8 @@ public class TelephonyManager {
* @see SubscriptionInfo#getServiceCapabilities()
*/
public boolean isDeviceVoiceCapable() {
- return isVoiceCapable();
+ return hasCapability(PackageManager.FEATURE_TELEPHONY_CALLING,
+ com.android.internal.R.bool.config_voice_capable);
}
/**
diff --git a/telephony/java/android/telephony/data/ApnSetting.java b/telephony/java/android/telephony/data/ApnSetting.java
index 22624e22d534..7eb1eb9475d3 100644
--- a/telephony/java/android/telephony/data/ApnSetting.java
+++ b/telephony/java/android/telephony/data/ApnSetting.java
@@ -126,7 +126,6 @@ public class ApnSetting implements Parcelable {
/** APN type for ENTERPRISE. */
public static final int TYPE_ENTERPRISE = ApnTypes.ENTERPRISE;
/** APN type for RCS (Rich Communication Services). */
- @FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG)
public static final int TYPE_RCS = ApnTypes.RCS;
/** APN type for OEM_PAID networks (Automotive PANS) */
@FlaggedApi(Flags.FLAG_OEM_PAID_PRIVATE)
@@ -379,7 +378,6 @@ public class ApnSetting implements Parcelable {
* modem components or carriers. Non-system apps should use the integer variants instead.
* @hide
*/
- @FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG)
@SystemApi
public static final String TYPE_RCS_STRING = "rcs";
diff --git a/telephony/java/android/telephony/satellite/SatelliteManager.java b/telephony/java/android/telephony/satellite/SatelliteManager.java
index b7b209b78300..13096548a8ba 100644
--- a/telephony/java/android/telephony/satellite/SatelliteManager.java
+++ b/telephony/java/android/telephony/satellite/SatelliteManager.java
@@ -755,7 +755,6 @@ public final class SatelliteManager {
* @hide
*/
@SystemApi
- @FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG)
public static final int EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE_T911 = 2;
/**
@@ -821,6 +820,25 @@ public final class SatelliteManager {
"android.telephony.METADATA_SATELLITE_MANUAL_CONNECT_P2P_SUPPORT";
/**
+ * A boolean value indicating whether application is optimized to utilize low bandwidth
+ * satellite data.
+ * The applications that are optimized for low bandwidth satellite data should set this
+ * property to {@code true} in the manifest to indicate to platform about the same.
+ * {@code
+ * <application>
+ * <meta-data
+ * android:name="android.telephony.PROPERTY_SATELLITE_DATA_OPTIMIZED"
+ * android:value="true"/>
+ * </application>
+ * }
+ * <p>
+ * When {@code true}, satellite data optimized network is available for applications.
+ */
+ @FlaggedApi(Flags.FLAG_SATELLITE_25Q4_APIS)
+ public static final String PROPERTY_SATELLITE_DATA_OPTIMIZED =
+ "android.telephony.PROPERTY_SATELLITE_DATA_OPTIMIZED";
+
+ /**
* Registers a {@link SatelliteStateChangeListener} to receive callbacks when the satellite
* state may have changed.
*
@@ -1555,7 +1573,6 @@ public final class SatelliteManager {
* @hide
*/
@SystemApi
- @FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG)
public static final int SATELLITE_COMMUNICATION_RESTRICTION_REASON_GEOLOCATION = 1;
/**
@@ -1565,7 +1582,6 @@ public final class SatelliteManager {
* @hide
*/
@SystemApi
- @FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG)
public static final int SATELLITE_COMMUNICATION_RESTRICTION_REASON_ENTITLEMENT = 2;
/** @hide */
@@ -2738,7 +2754,6 @@ public final class SatelliteManager {
*/
@SystemApi
@RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
- @FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG)
public void requestAttachEnabledForCarrier(int subId, boolean enableSatellite,
@NonNull @CallbackExecutor Executor executor,
@SatelliteResult @NonNull Consumer<Integer> resultListener) {
@@ -2775,7 +2790,6 @@ public final class SatelliteManager {
*/
@SystemApi
@RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
- @FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG)
public void requestIsAttachEnabledForCarrier(int subId,
@NonNull @CallbackExecutor Executor executor,
@NonNull OutcomeReceiver<Boolean, SatelliteException> callback) {
@@ -2803,7 +2817,6 @@ public final class SatelliteManager {
*/
@SystemApi
@RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
- @FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG)
public void addAttachRestrictionForCarrier(int subId,
@SatelliteCommunicationRestrictionReason int reason,
@NonNull @CallbackExecutor Executor executor,
@@ -2851,7 +2864,6 @@ public final class SatelliteManager {
*/
@SystemApi
@RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
- @FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG)
public void removeAttachRestrictionForCarrier(int subId,
@SatelliteCommunicationRestrictionReason int reason,
@NonNull @CallbackExecutor Executor executor,
@@ -2899,7 +2911,6 @@ public final class SatelliteManager {
@SystemApi
@RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
@SatelliteCommunicationRestrictionReason
- @FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG)
@NonNull public Set<Integer> getAttachRestrictionReasonsForCarrier(int subId) {
if (!SubscriptionManager.isValidSubscriptionId(subId)) {
throw new IllegalArgumentException("Invalid subscription ID");
@@ -3298,7 +3309,6 @@ public final class SatelliteManager {
*/
@SystemApi
@RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
- @FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG)
@NonNull public List<String> getSatellitePlmnsForCarrier(int subId) {
if (!SubscriptionManager.isValidSubscriptionId(subId)) {
throw new IllegalArgumentException("Invalid subscription ID");
@@ -3840,6 +3850,35 @@ public final class SatelliteManager {
}
}
+ /**
+ * Get list of application packages name that are optimized for low bandwidth satellite data.
+ *
+ * @return List of application packages name with data optimized network property.
+ *
+ * {@link #PROPERTY_SATELLITE_DATA_OPTIMIZED}
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
+ @FlaggedApi(Flags.FLAG_SATELLITE_25Q4_APIS)
+ public @NonNull List<String> getSatelliteDataOptimizedApps() {
+ List<String> appsNames = new ArrayList<>();
+ try {
+ ITelephony telephony = getITelephony();
+ if (telephony != null) {
+ appsNames = telephony.getSatelliteDataOptimizedApps();
+ } else {
+ throw new IllegalStateException("telephony service is null.");
+ }
+ } catch (RemoteException ex) {
+ loge("getSatelliteDataOptimizedApps() RemoteException:" + ex);
+ ex.rethrowAsRuntimeException();
+ }
+
+ return appsNames;
+ }
+
@Nullable
private static ITelephony getITelephony() {
ITelephony binder = ITelephony.Stub.asInterface(TelephonyFrameworkInitializer
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 08c003027c5b..1c6652daf498 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -3596,4 +3596,15 @@ interface ITelephony {
* @hide
*/
int getCarrierIdFromIdentifier(in CarrierIdentifier carrierIdentifier);
+
+
+ /**
+ * Get list of applications that are optimized for low bandwidth satellite data.
+ *
+ * @return List of Application Name with data optimized network property.
+ * {@link #PROPERTY_SATELLITE_DATA_OPTIMIZED}
+ */
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
+ + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
+ List<String> getSatelliteDataOptimizedApps();
}
diff --git a/tests/AppJankTest/res/values/strings.xml b/tests/AppJankTest/res/values/strings.xml
new file mode 100644
index 000000000000..ab2d18fa9d53
--- /dev/null
+++ b/tests/AppJankTest/res/values/strings.xml
@@ -0,0 +1,3 @@
+<resources>
+ <string name="continue_test">Continue Test</string>
+</resources> \ No newline at end of file
diff --git a/tests/AppJankTest/src/android/app/jank/tests/IntegrationTests.java b/tests/AppJankTest/src/android/app/jank/tests/IntegrationTests.java
index fe9f63615757..3498974b348e 100644
--- a/tests/AppJankTest/src/android/app/jank/tests/IntegrationTests.java
+++ b/tests/AppJankTest/src/android/app/jank/tests/IntegrationTests.java
@@ -209,7 +209,8 @@ public class IntegrationTests {
JankTrackerActivity.class);
resumeJankTracker.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
mEmptyActivity.startActivity(resumeJankTracker);
- mDevice.wait(Until.findObject(By.text("Edit Text")), WAIT_FOR_TIMEOUT_MS);
+ mDevice.wait(Until.findObject(By.text(mEmptyActivity.getString(R.string.continue_test))),
+ WAIT_FOR_TIMEOUT_MS);
assertTrue(jankTracker.shouldTrack());
}
diff --git a/tests/AppJankTest/src/android/app/jank/tests/JankTrackerActivity.java b/tests/AppJankTest/src/android/app/jank/tests/JankTrackerActivity.java
index 80ab6ad3e587..686758200853 100644
--- a/tests/AppJankTest/src/android/app/jank/tests/JankTrackerActivity.java
+++ b/tests/AppJankTest/src/android/app/jank/tests/JankTrackerActivity.java
@@ -18,15 +18,41 @@ package android.app.jank.tests;
import android.app.Activity;
import android.os.Bundle;
+import android.widget.EditText;
public class JankTrackerActivity extends Activity {
+ private static final int CONTINUE_TEST_DELAY_MS = 4000;
+
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.jank_tracker_activity_layout);
}
+
+ /**
+ * In IntegrationTests#jankTrackingResumed_whenActivityBecomesVisibleAgain this activity is
+ * placed into the background and then resumed via an intent. The test waits until the
+ * `continue_test` string is visible on the screen before validating that Jank tracking has
+ * resumed.
+ *
+ * <p>The 4 second delay allows JankTracker to re-register its callbacks and start receiving
+ * JankData before the test proceeds.
+ */
+ @Override
+ protected void onResume() {
+ super.onResume();
+ getActivityThread().getHandler().postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ EditText editTextView = findViewById(R.id.edit_text);
+ if (editTextView != null) {
+ editTextView.setText(R.string.continue_test);
+ }
+ }
+ }, CONTINUE_TEST_DELAY_MS);
+ }
}
diff --git a/tests/AttestationVerificationTest/src/com/android/server/security/CertificateRevocationStatusManagerTest.java b/tests/AttestationVerificationTest/src/com/android/server/security/CertificateRevocationStatusManagerTest.java
index c38517ace5e6..586bb76388f6 100644
--- a/tests/AttestationVerificationTest/src/com/android/server/security/CertificateRevocationStatusManagerTest.java
+++ b/tests/AttestationVerificationTest/src/com/android/server/security/CertificateRevocationStatusManagerTest.java
@@ -277,6 +277,72 @@ public class CertificateRevocationStatusManagerTest {
}
}
+ @Test
+ public void checkRevocationStatus_allCertificatesRecentlyChecked_doesNotFetchRemoteCrl()
+ throws Exception {
+ copyFromAssetToFile(
+ REVOCATION_LIST_WITHOUT_CERTIFICATES_USED_IN_THIS_TEST, mRevocationListFile);
+ mCertificateRevocationStatusManager =
+ new CertificateRevocationStatusManager(
+ mContext, mRevocationListUrl, mRevocationStatusFile, false);
+ mCertificateRevocationStatusManager.checkRevocationStatus(mCertificates1);
+ // indirectly verifies the remote list is not fetched by simulating a remote revocation
+ copyFromAssetToFile(
+ REVOCATION_LIST_WITH_CERTIFICATES_USED_IN_THIS_TEST, mRevocationListFile);
+
+ // no exception
+ mCertificateRevocationStatusManager.checkRevocationStatus(mCertificates1);
+ }
+
+ @Test
+ public void checkRevocationStatus_allCertificatesBarelyRecentlyChecked_doesNotFetchRemoteCrl()
+ throws Exception {
+ copyFromAssetToFile(
+ REVOCATION_LIST_WITH_CERTIFICATES_USED_IN_THIS_TEST, mRevocationListFile);
+ mCertificateRevocationStatusManager =
+ new CertificateRevocationStatusManager(
+ mContext, mRevocationListUrl, mRevocationStatusFile, false);
+ Map<String, LocalDateTime> lastCheckedDates = new HashMap<>();
+ LocalDateTime barelyRecently =
+ LocalDateTime.now()
+ .minusHours(
+ CertificateRevocationStatusManager.NUM_HOURS_BEFORE_NEXT_CHECK - 1);
+ for (X509Certificate certificate : mCertificates1) {
+ lastCheckedDates.put(getSerialNumber(certificate), barelyRecently);
+ }
+ mCertificateRevocationStatusManager.storeLastRevocationCheckData(lastCheckedDates);
+
+ // Indirectly verify the remote CRL is not checked by checking there is no exception despite
+ // a certificate being revoked. This test differs from the next only in the lastCheckedDate,
+ // one before the NUM_HOURS_BEFORE_NEXT_CHECK cutoff and one after
+ mCertificateRevocationStatusManager.checkRevocationStatus(mCertificates1);
+ }
+
+ @Test
+ public void checkRevocationStatus_certificatesRevokedAfterCheck_throwsException()
+ throws Exception {
+ copyFromAssetToFile(
+ REVOCATION_LIST_WITH_CERTIFICATES_USED_IN_THIS_TEST, mRevocationListFile);
+ mCertificateRevocationStatusManager =
+ new CertificateRevocationStatusManager(
+ mContext, mRevocationListUrl, mRevocationStatusFile, false);
+ Map<String, LocalDateTime> lastCheckedDates = new HashMap<>();
+ // To save network use, we do not check the remote CRL if all the certificates are recently
+ // checked, so we set the lastCheckDate to some time not recent.
+ LocalDateTime notRecently =
+ LocalDateTime.now()
+ .minusHours(
+ CertificateRevocationStatusManager.NUM_HOURS_BEFORE_NEXT_CHECK + 1);
+ for (X509Certificate certificate : mCertificates1) {
+ lastCheckedDates.put(getSerialNumber(certificate), notRecently);
+ }
+ mCertificateRevocationStatusManager.storeLastRevocationCheckData(lastCheckedDates);
+
+ assertThrows(
+ CertPathValidatorException.class,
+ () -> mCertificateRevocationStatusManager.checkRevocationStatus(mCertificates1));
+ }
+
private List<X509Certificate> getCertificateChain(String fileName) throws Exception {
Collection<? extends Certificate> certificates =
mFactory.generateCertificates(mContext.getResources().getAssets().open(fileName));
diff --git a/tests/PackageWatchdog/Android.bp b/tests/PackageWatchdog/Android.bp
index 8be74eaccd20..44e545bac0ce 100644
--- a/tests/PackageWatchdog/Android.bp
+++ b/tests/PackageWatchdog/Android.bp
@@ -26,12 +26,12 @@ android_test {
name: "PackageWatchdogTest",
srcs: ["src/**/*.java"],
static_libs: [
- "junit",
- "mockito-target-extended-minus-junit4",
+ "PlatformProperties",
+ "androidx.test.rules",
"flag-junit",
"frameworks-base-testutils",
- "androidx.test.rules",
- "PlatformProperties",
+ "junit",
+ "mockito-target-extended-minus-junit4",
"services.core",
"services.net",
"truth",
@@ -49,5 +49,9 @@ android_test {
"libstaticjvmtiagent",
],
platform_apis: true,
- test_suites: ["device-tests"],
+ test_suites: [
+ "device-tests",
+ "mts-crashrecovery",
+ ],
+ min_sdk_version: "36",
}
diff --git a/tests/PackageWatchdog/src/com/android/server/CrashRecoveryTest.java b/tests/PackageWatchdog/src/com/android/server/CrashRecoveryTest.java
index 8ac3433033c6..c2ab0550ea05 100644
--- a/tests/PackageWatchdog/src/com/android/server/CrashRecoveryTest.java
+++ b/tests/PackageWatchdog/src/com/android/server/CrashRecoveryTest.java
@@ -48,8 +48,6 @@ import android.net.ConnectivityModuleConnector.ConnectivityModuleHealthListener;
import android.os.Handler;
import android.os.SystemProperties;
import android.os.test.TestLooper;
-import android.platform.test.annotations.DisableFlags;
-import android.platform.test.annotations.EnableFlags;
import android.platform.test.flag.junit.SetFlagsRule;
import android.provider.DeviceConfig;
import android.util.AtomicFile;
@@ -135,7 +133,6 @@ public class CrashRecoveryTest {
@Before
public void setUp() throws Exception {
- mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION);
MockitoAnnotations.initMocks(this);
new File(InstrumentationRegistry.getContext().getFilesDir(),
"package-watchdog.xml").delete();
@@ -305,90 +302,6 @@ public class CrashRecoveryTest {
}
@Test
- @DisableFlags(Flags.FLAG_DEPRECATE_FLAGS_AND_SETTINGS_RESETS)
- public void testBootLoopWithRescuePartyAndRollbackObserver() throws Exception {
- PackageWatchdog watchdog = createWatchdog();
- RescuePartyObserver rescuePartyObserver = setUpRescuePartyObserver(watchdog);
- RollbackPackageHealthObserver rollbackObserver =
- setUpRollbackPackageHealthObserver(watchdog);
-
- verify(rescuePartyObserver, never()).onExecuteBootLoopMitigation(1);
- verify(rollbackObserver, never()).onExecuteBootLoopMitigation(1);
- for (int i = 0; i < PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT; i++) {
- watchdog.noteBoot();
- }
- mTestLooper.dispatchAll();
- verify(rescuePartyObserver).onExecuteBootLoopMitigation(1);
- verify(rescuePartyObserver, never()).onExecuteBootLoopMitigation(2);
- verify(rollbackObserver, never()).onExecuteBootLoopMitigation(1);
-
- watchdog.noteBoot();
-
- mTestLooper.dispatchAll();
- verify(rescuePartyObserver).onExecuteBootLoopMitigation(2);
- verify(rescuePartyObserver, never()).onExecuteBootLoopMitigation(3);
- verify(rollbackObserver, never()).onExecuteBootLoopMitigation(2);
-
- watchdog.noteBoot();
-
- mTestLooper.dispatchAll();
- verify(rescuePartyObserver, never()).onExecuteBootLoopMitigation(3);
- verify(rollbackObserver).onExecuteBootLoopMitigation(1);
- verify(rollbackObserver, never()).onExecuteBootLoopMitigation(2);
- // Update the list of available rollbacks after executing bootloop mitigation once
- when(mRollbackManager.getAvailableRollbacks()).thenReturn(List.of(ROLLBACK_INFO_HIGH,
- ROLLBACK_INFO_MANUAL));
-
- watchdog.noteBoot();
-
- mTestLooper.dispatchAll();
- verify(rescuePartyObserver).onExecuteBootLoopMitigation(3);
- verify(rescuePartyObserver, never()).onExecuteBootLoopMitigation(4);
- verify(rollbackObserver, never()).onExecuteBootLoopMitigation(2);
-
- watchdog.noteBoot();
-
- mTestLooper.dispatchAll();
- verify(rescuePartyObserver).onExecuteBootLoopMitigation(4);
- verify(rescuePartyObserver, never()).onExecuteBootLoopMitigation(5);
- verify(rollbackObserver, never()).onExecuteBootLoopMitigation(2);
-
- watchdog.noteBoot();
-
- mTestLooper.dispatchAll();
- verify(rescuePartyObserver).onExecuteBootLoopMitigation(5);
- verify(rescuePartyObserver, never()).onExecuteBootLoopMitigation(6);
- verify(rollbackObserver, never()).onExecuteBootLoopMitigation(2);
-
- watchdog.noteBoot();
-
- mTestLooper.dispatchAll();
- verify(rescuePartyObserver, never()).onExecuteBootLoopMitigation(6);
- verify(rollbackObserver).onExecuteBootLoopMitigation(2);
- verify(rollbackObserver, never()).onExecuteBootLoopMitigation(3);
- // Update the list of available rollbacks after executing bootloop mitigation
- when(mRollbackManager.getAvailableRollbacks()).thenReturn(List.of(ROLLBACK_INFO_MANUAL));
-
- watchdog.noteBoot();
-
- mTestLooper.dispatchAll();
- verify(rescuePartyObserver).onExecuteBootLoopMitigation(6);
- verify(rescuePartyObserver, never()).onExecuteBootLoopMitigation(7);
- verify(rollbackObserver, never()).onExecuteBootLoopMitigation(3);
-
- moveTimeForwardAndDispatch(PackageWatchdog.DEFAULT_DEESCALATION_WINDOW_MS + 1);
- Mockito.reset(rescuePartyObserver);
-
- for (int i = 0; i < PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT; i++) {
- watchdog.noteBoot();
- }
- mTestLooper.dispatchAll();
- verify(rescuePartyObserver).onExecuteBootLoopMitigation(1);
- verify(rescuePartyObserver, never()).onExecuteBootLoopMitigation(2);
- }
-
- @Test
- @EnableFlags(Flags.FLAG_DEPRECATE_FLAGS_AND_SETTINGS_RESETS)
public void testBootLoopWithRescuePartyAndRollbackObserverNoFlags() throws Exception {
PackageWatchdog watchdog = createWatchdog();
RescuePartyObserver rescuePartyObserver = setUpRescuePartyObserver(watchdog);
@@ -443,80 +356,6 @@ public class CrashRecoveryTest {
}
@Test
- @DisableFlags(Flags.FLAG_DEPRECATE_FLAGS_AND_SETTINGS_RESETS)
- public void testCrashLoopWithRescuePartyAndRollbackObserver() throws Exception {
- PackageWatchdog watchdog = createWatchdog();
- RescuePartyObserver rescuePartyObserver = setUpRescuePartyObserver(watchdog);
- RollbackPackageHealthObserver rollbackObserver =
- setUpRollbackPackageHealthObserver(watchdog);
- VersionedPackage versionedPackageA = new VersionedPackage(APP_A, VERSION_CODE);
-
- when(mMockPackageManager.getApplicationInfo(anyString(), anyInt())).then(inv -> {
- ApplicationInfo info = new ApplicationInfo();
- info.flags |= ApplicationInfo.FLAG_PERSISTENT
- | ApplicationInfo.FLAG_SYSTEM;
- return info;
- });
-
- raiseFatalFailureAndDispatch(watchdog,
- Arrays.asList(versionedPackageA), PackageWatchdog.FAILURE_REASON_APP_CRASH);
-
- // Mitigation: SCOPED_DEVICE_CONFIG_RESET
- verify(rescuePartyObserver).onExecuteHealthCheckMitigation(versionedPackageA,
- PackageWatchdog.FAILURE_REASON_APP_CRASH, 1);
- verify(rescuePartyObserver, never()).onExecuteHealthCheckMitigation(versionedPackageA,
- PackageWatchdog.FAILURE_REASON_APP_CRASH, 2);
- verify(rollbackObserver, never()).onExecuteHealthCheckMitigation(versionedPackageA,
- PackageWatchdog.FAILURE_REASON_APP_CRASH, 1);
-
- raiseFatalFailureAndDispatch(watchdog,
- Arrays.asList(versionedPackageA), PackageWatchdog.FAILURE_REASON_APP_CRASH);
-
- // Mitigation: ALL_DEVICE_CONFIG_RESET
- verify(rescuePartyObserver).onExecuteHealthCheckMitigation(versionedPackageA,
- PackageWatchdog.FAILURE_REASON_APP_CRASH, 2);
- verify(rescuePartyObserver, never()).onExecuteHealthCheckMitigation(versionedPackageA,
- PackageWatchdog.FAILURE_REASON_APP_CRASH, 3);
- verify(rollbackObserver, never()).onExecuteHealthCheckMitigation(versionedPackageA,
- PackageWatchdog.FAILURE_REASON_APP_CRASH, 1);
-
- raiseFatalFailureAndDispatch(watchdog,
- Arrays.asList(versionedPackageA), PackageWatchdog.FAILURE_REASON_APP_CRASH);
-
- // Mitigation: WARM_REBOOT
- verify(rescuePartyObserver).onExecuteHealthCheckMitigation(versionedPackageA,
- PackageWatchdog.FAILURE_REASON_APP_CRASH, 3);
- verify(rescuePartyObserver, never()).onExecuteHealthCheckMitigation(versionedPackageA,
- PackageWatchdog.FAILURE_REASON_APP_CRASH, 4);
- verify(rollbackObserver, never()).onExecuteHealthCheckMitigation(versionedPackageA,
- PackageWatchdog.FAILURE_REASON_APP_CRASH, 1);
-
- raiseFatalFailureAndDispatch(watchdog,
- Arrays.asList(versionedPackageA), PackageWatchdog.FAILURE_REASON_APP_CRASH);
-
- // Mitigation: Low impact rollback
- verify(rollbackObserver).onExecuteHealthCheckMitigation(versionedPackageA,
- PackageWatchdog.FAILURE_REASON_APP_CRASH, 1);
- verify(rescuePartyObserver, never()).onExecuteHealthCheckMitigation(versionedPackageA,
- PackageWatchdog.FAILURE_REASON_APP_CRASH, 4);
-
- // update available rollbacks to mock rollbacks being applied after the call to
- // rollbackObserver.onExecuteHealthCheckMitigation
- when(mRollbackManager.getAvailableRollbacks()).thenReturn(
- List.of(ROLLBACK_INFO_HIGH, ROLLBACK_INFO_MANUAL));
-
- raiseFatalFailureAndDispatch(watchdog,
- Arrays.asList(versionedPackageA), PackageWatchdog.FAILURE_REASON_APP_CRASH);
-
- // DEFAULT_MAJOR_USER_IMPACT_LEVEL_THRESHOLD reached. No more mitigations applied
- verify(rescuePartyObserver, never()).onExecuteHealthCheckMitigation(versionedPackageA,
- PackageWatchdog.FAILURE_REASON_APP_CRASH, 4);
- verify(rollbackObserver, never()).onExecuteHealthCheckMitigation(versionedPackageA,
- PackageWatchdog.FAILURE_REASON_APP_CRASH, 2);
- }
-
- @Test
- @EnableFlags(Flags.FLAG_DEPRECATE_FLAGS_AND_SETTINGS_RESETS)
public void testCrashLoopWithRescuePartyAndRollbackObserverEnableDeprecateFlagReset()
throws Exception {
PackageWatchdog watchdog = createWatchdog();
@@ -569,123 +408,6 @@ public class CrashRecoveryTest {
}
@Test
- @DisableFlags(Flags.FLAG_DEPRECATE_FLAGS_AND_SETTINGS_RESETS)
- public void testCrashLoopSystemUIWithRescuePartyAndRollbackObserver() throws Exception {
- PackageWatchdog watchdog = createWatchdog();
- RescuePartyObserver rescuePartyObserver = setUpRescuePartyObserver(watchdog);
- RollbackPackageHealthObserver rollbackObserver =
- setUpRollbackPackageHealthObserver(watchdog);
- String systemUi = "com.android.systemui";
- VersionedPackage versionedPackageUi = new VersionedPackage(
- systemUi, VERSION_CODE);
- RollbackInfo rollbackInfoUi = getRollbackInfo(systemUi, VERSION_CODE, 1,
- PackageManager.ROLLBACK_USER_IMPACT_LOW);
- when(mRollbackManager.getAvailableRollbacks()).thenReturn(List.of(ROLLBACK_INFO_LOW,
- ROLLBACK_INFO_HIGH, ROLLBACK_INFO_MANUAL, rollbackInfoUi));
-
- when(mMockPackageManager.getApplicationInfo(anyString(), anyInt())).then(inv -> {
- ApplicationInfo info = new ApplicationInfo();
- info.flags |= ApplicationInfo.FLAG_PERSISTENT
- | ApplicationInfo.FLAG_SYSTEM;
- return info;
- });
-
- raiseFatalFailureAndDispatch(watchdog,
- Arrays.asList(versionedPackageUi), PackageWatchdog.FAILURE_REASON_APP_CRASH);
-
- // Mitigation: SCOPED_DEVICE_CONFIG_RESET
- verify(rescuePartyObserver).onExecuteHealthCheckMitigation(versionedPackageUi,
- PackageWatchdog.FAILURE_REASON_APP_CRASH, 1);
- verify(rescuePartyObserver, never()).onExecuteHealthCheckMitigation(versionedPackageUi,
- PackageWatchdog.FAILURE_REASON_APP_CRASH, 2);
- verify(rollbackObserver, never()).onExecuteHealthCheckMitigation(versionedPackageUi,
- PackageWatchdog.FAILURE_REASON_APP_CRASH, 1);
-
- raiseFatalFailureAndDispatch(watchdog,
- Arrays.asList(versionedPackageUi), PackageWatchdog.FAILURE_REASON_APP_CRASH);
-
- // Mitigation: ALL_DEVICE_CONFIG_RESET
- verify(rescuePartyObserver).onExecuteHealthCheckMitigation(versionedPackageUi,
- PackageWatchdog.FAILURE_REASON_APP_CRASH, 2);
- verify(rescuePartyObserver, never()).onExecuteHealthCheckMitigation(versionedPackageUi,
- PackageWatchdog.FAILURE_REASON_APP_CRASH, 3);
- verify(rollbackObserver, never()).onExecuteHealthCheckMitigation(versionedPackageUi,
- PackageWatchdog.FAILURE_REASON_APP_CRASH, 1);
-
- raiseFatalFailureAndDispatch(watchdog,
- Arrays.asList(versionedPackageUi), PackageWatchdog.FAILURE_REASON_APP_CRASH);
-
- // Mitigation: WARM_REBOOT
- verify(rescuePartyObserver).onExecuteHealthCheckMitigation(versionedPackageUi,
- PackageWatchdog.FAILURE_REASON_APP_CRASH, 3);
- verify(rescuePartyObserver, never()).onExecuteHealthCheckMitigation(versionedPackageUi,
- PackageWatchdog.FAILURE_REASON_APP_CRASH, 4);
- verify(rollbackObserver, never()).onExecuteHealthCheckMitigation(versionedPackageUi,
- PackageWatchdog.FAILURE_REASON_APP_CRASH, 1);
-
- raiseFatalFailureAndDispatch(watchdog,
- Arrays.asList(versionedPackageUi), PackageWatchdog.FAILURE_REASON_APP_CRASH);
-
- // Mitigation: Low impact rollback
- verify(rollbackObserver).onExecuteHealthCheckMitigation(versionedPackageUi,
- PackageWatchdog.FAILURE_REASON_APP_CRASH, 1);
- verify(rescuePartyObserver, never()).onExecuteHealthCheckMitigation(versionedPackageUi,
- PackageWatchdog.FAILURE_REASON_APP_CRASH, 4);
- verify(rollbackObserver, never()).onExecuteHealthCheckMitigation(versionedPackageUi,
- PackageWatchdog.FAILURE_REASON_APP_CRASH, 2);
-
- // update available rollbacks to mock rollbacks being applied after the call to
- // rollbackObserver.onExecuteHealthCheckMitigation
- when(mRollbackManager.getAvailableRollbacks()).thenReturn(
- List.of(ROLLBACK_INFO_HIGH, ROLLBACK_INFO_MANUAL));
-
- raiseFatalFailureAndDispatch(watchdog,
- Arrays.asList(versionedPackageUi), PackageWatchdog.FAILURE_REASON_APP_CRASH);
-
- // Mitigation: RESET_SETTINGS_UNTRUSTED_DEFAULTS
- verify(rescuePartyObserver).onExecuteHealthCheckMitigation(versionedPackageUi,
- PackageWatchdog.FAILURE_REASON_APP_CRASH, 4);
- verify(rescuePartyObserver, never()).onExecuteHealthCheckMitigation(versionedPackageUi,
- PackageWatchdog.FAILURE_REASON_APP_CRASH, 5);
- verify(rollbackObserver, never()).onExecuteHealthCheckMitigation(versionedPackageUi,
- PackageWatchdog.FAILURE_REASON_APP_CRASH, 2);
-
- raiseFatalFailureAndDispatch(watchdog,
- Arrays.asList(versionedPackageUi), PackageWatchdog.FAILURE_REASON_APP_CRASH);
-
- // Mitigation: RESET_SETTINGS_UNTRUSTED_CHANGES
- verify(rescuePartyObserver).onExecuteHealthCheckMitigation(versionedPackageUi,
- PackageWatchdog.FAILURE_REASON_APP_CRASH, 5);
- verify(rescuePartyObserver, never()).onExecuteHealthCheckMitigation(versionedPackageUi,
- PackageWatchdog.FAILURE_REASON_APP_CRASH, 6);
- verify(rollbackObserver, never()).onExecuteHealthCheckMitigation(versionedPackageUi,
- PackageWatchdog.FAILURE_REASON_APP_CRASH, 2);
-
- raiseFatalFailureAndDispatch(watchdog,
- Arrays.asList(versionedPackageUi), PackageWatchdog.FAILURE_REASON_APP_CRASH);
-
- // Mitigation: RESET_SETTINGS_TRUSTED_DEFAULTS
- verify(rescuePartyObserver).onExecuteHealthCheckMitigation(versionedPackageUi,
- PackageWatchdog.FAILURE_REASON_APP_CRASH, 6);
- verify(rescuePartyObserver, never()).onExecuteHealthCheckMitigation(versionedPackageUi,
- PackageWatchdog.FAILURE_REASON_APP_CRASH, 7);
- verify(rollbackObserver, never()).onExecuteHealthCheckMitigation(versionedPackageUi,
- PackageWatchdog.FAILURE_REASON_APP_CRASH, 2);
-
- raiseFatalFailureAndDispatch(watchdog,
- Arrays.asList(versionedPackageUi), PackageWatchdog.FAILURE_REASON_APP_CRASH);
-
- // Mitigation: Factory reset. High impact rollbacks are performed only for boot loops.
- verify(rescuePartyObserver).onExecuteHealthCheckMitigation(versionedPackageUi,
- PackageWatchdog.FAILURE_REASON_APP_CRASH, 7);
- verify(rescuePartyObserver, never()).onExecuteHealthCheckMitigation(versionedPackageUi,
- PackageWatchdog.FAILURE_REASON_APP_CRASH, 8);
- verify(rollbackObserver, never()).onExecuteHealthCheckMitigation(versionedPackageUi,
- PackageWatchdog.FAILURE_REASON_APP_CRASH, 2);
- }
-
- @Test
- @EnableFlags(Flags.FLAG_DEPRECATE_FLAGS_AND_SETTINGS_RESETS)
public void testCrashLoopSystemUIWithRescuePartyAndRollbackObserverEnableDeprecateFlagReset()
throws Exception {
PackageWatchdog watchdog = createWatchdog();
@@ -1043,8 +765,6 @@ public class CrashRecoveryTest {
watchdog.notifyPackageFailure(packages, failureReason);
}
mTestLooper.dispatchAll();
- if (Flags.recoverabilityDetection()) {
- moveTimeForwardAndDispatch(watchdog.DEFAULT_MITIGATION_WINDOW_MS);
- }
+ moveTimeForwardAndDispatch(watchdog.DEFAULT_MITIGATION_WINDOW_MS);
}
}
diff --git a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
index 1c50cb1b55fd..b8274841f65b 100644
--- a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
+++ b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
@@ -150,7 +150,6 @@ public class PackageWatchdogTest {
@Before
public void setUp() throws Exception {
- mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION);
MockitoAnnotations.initMocks(this);
new File(InstrumentationRegistry.getContext().getFilesDir(),
"package-watchdog.xml").delete();
@@ -480,60 +479,6 @@ public class PackageWatchdogTest {
assertThat(observer.mHealthCheckFailedPackages).isEmpty();
}
-
- /**
- * Test package failure and notifies only least impact observers.
- */
- @Test
- public void testPackageFailureNotifyAllDifferentImpacts() throws Exception {
- mSetFlagsRule.disableFlags(Flags.FLAG_RECOVERABILITY_DETECTION);
- PackageWatchdog watchdog = createWatchdog();
- TestObserver observerNone = new TestObserver(OBSERVER_NAME_1,
- PackageHealthObserverImpact.USER_IMPACT_LEVEL_0);
- TestObserver observerHigh = new TestObserver(OBSERVER_NAME_2,
- PackageHealthObserverImpact.USER_IMPACT_LEVEL_100);
- TestObserver observerMid = new TestObserver(OBSERVER_NAME_3,
- PackageHealthObserverImpact.USER_IMPACT_LEVEL_30);
- TestObserver observerLow = new TestObserver(OBSERVER_NAME_4,
- PackageHealthObserverImpact.USER_IMPACT_LEVEL_10);
-
- // Start observing for all impact observers
- watchdog.registerHealthObserver(mTestExecutor, observerNone);
- watchdog.startExplicitHealthCheck(Arrays.asList(APP_A, APP_B, APP_C, APP_D),
- SHORT_DURATION, observerNone);
- watchdog.registerHealthObserver(mTestExecutor, observerHigh);
- watchdog.startExplicitHealthCheck(Arrays.asList(APP_A, APP_B, APP_C), SHORT_DURATION,
- observerHigh);
- watchdog.registerHealthObserver(mTestExecutor, observerMid);
- watchdog.startExplicitHealthCheck(Arrays.asList(APP_A, APP_B), SHORT_DURATION,
- observerMid);
- watchdog.registerHealthObserver(mTestExecutor, observerLow);
- watchdog.startExplicitHealthCheck(Arrays.asList(APP_A), SHORT_DURATION, observerLow);
-
- // Then fail all apps above the threshold
- raiseFatalFailureAndDispatch(watchdog,
- Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE),
- new VersionedPackage(APP_B, VERSION_CODE),
- new VersionedPackage(APP_C, VERSION_CODE),
- new VersionedPackage(APP_D, VERSION_CODE)),
- PackageWatchdog.FAILURE_REASON_UNKNOWN);
-
- // Verify least impact observers are notifed of package failures
- List<String> observerNonePackages = observerNone.mMitigatedPackages;
- List<String> observerHighPackages = observerHigh.mMitigatedPackages;
- List<String> observerMidPackages = observerMid.mMitigatedPackages;
- List<String> observerLowPackages = observerLow.mMitigatedPackages;
-
- // APP_D failure observed by only observerNone is not caught cos its impact is none
- assertThat(observerNonePackages).isEmpty();
- // APP_C failure is caught by observerHigh cos it's the lowest impact observer
- assertThat(observerHighPackages).containsExactly(APP_C);
- // APP_B failure is caught by observerMid cos it's the lowest impact observer
- assertThat(observerMidPackages).containsExactly(APP_B);
- // APP_A failure is caught by observerLow cos it's the lowest impact observer
- assertThat(observerLowPackages).containsExactly(APP_A);
- }
-
@Test
public void testPackageFailureNotifyAllDifferentImpactsRecoverability() throws Exception {
PackageWatchdog watchdog = createWatchdog();
@@ -583,84 +528,6 @@ public class PackageWatchdogTest {
assertThat(observerLowPackages).containsExactly(APP_A);
}
- /**
- * Test package failure and least impact observers are notified successively.
- * State transistions:
- *
- * <ul>
- * <li>(observer1:low, observer2:mid) -> {observer1}
- * <li>(observer1:high, observer2:mid) -> {observer2}
- * <li>(observer1:high, observer2:none) -> {observer1}
- * <li>(observer1:none, observer2:none) -> {}
- * <ul>
- */
- @Test
- public void testPackageFailureNotifyLeastImpactSuccessively() throws Exception {
- mSetFlagsRule.disableFlags(Flags.FLAG_RECOVERABILITY_DETECTION);
- PackageWatchdog watchdog = createWatchdog();
- TestObserver observerFirst = new TestObserver(OBSERVER_NAME_1,
- PackageHealthObserverImpact.USER_IMPACT_LEVEL_10);
- TestObserver observerSecond = new TestObserver(OBSERVER_NAME_2,
- PackageHealthObserverImpact.USER_IMPACT_LEVEL_30);
-
- // Start observing for observerFirst and observerSecond with failure handling
- watchdog.registerHealthObserver(mTestExecutor, observerFirst);
- watchdog.startExplicitHealthCheck(Arrays.asList(APP_A), LONG_DURATION, observerFirst);
- watchdog.registerHealthObserver(mTestExecutor, observerSecond);
- watchdog.startExplicitHealthCheck(Arrays.asList(APP_A), LONG_DURATION, observerSecond);
-
- // Then fail APP_A above the threshold
- raiseFatalFailureAndDispatch(watchdog,
- Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)),
- PackageWatchdog.FAILURE_REASON_UNKNOWN);
-
- // Verify only observerFirst is notifed
- assertThat(observerFirst.mMitigatedPackages).containsExactly(APP_A);
- assertThat(observerSecond.mMitigatedPackages).isEmpty();
-
- // After observerFirst handles failure, next action it has is high impact
- observerFirst.mImpact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_100;
- observerFirst.mMitigatedPackages.clear();
- observerSecond.mMitigatedPackages.clear();
-
- // Then fail APP_A again above the threshold
- raiseFatalFailureAndDispatch(watchdog,
- Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)),
- PackageWatchdog.FAILURE_REASON_UNKNOWN);
-
- // Verify only observerSecond is notifed cos it has least impact
- assertThat(observerSecond.mMitigatedPackages).containsExactly(APP_A);
- assertThat(observerFirst.mMitigatedPackages).isEmpty();
-
- // After observerSecond handles failure, it has no further actions
- observerSecond.mImpact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_0;
- observerFirst.mMitigatedPackages.clear();
- observerSecond.mMitigatedPackages.clear();
-
- // Then fail APP_A again above the threshold
- raiseFatalFailureAndDispatch(watchdog,
- Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)),
- PackageWatchdog.FAILURE_REASON_UNKNOWN);
-
- // Verify only observerFirst is notifed cos it has the only action
- assertThat(observerFirst.mMitigatedPackages).containsExactly(APP_A);
- assertThat(observerSecond.mMitigatedPackages).isEmpty();
-
- // After observerFirst handles failure, it too has no further actions
- observerFirst.mImpact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_0;
- observerFirst.mMitigatedPackages.clear();
- observerSecond.mMitigatedPackages.clear();
-
- // Then fail APP_A again above the threshold
- raiseFatalFailureAndDispatch(watchdog,
- Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)),
- PackageWatchdog.FAILURE_REASON_UNKNOWN);
-
- // Verify no observer is notified cos no actions left
- assertThat(observerFirst.mMitigatedPackages).isEmpty();
- assertThat(observerSecond.mMitigatedPackages).isEmpty();
- }
-
@Test
public void testPackageFailureNotifyLeastImpactSuccessivelyRecoverability() throws Exception {
PackageWatchdog watchdog = createWatchdog();
@@ -727,34 +594,6 @@ public class PackageWatchdogTest {
assertThat(observerSecond.mMitigatedPackages).isEmpty();
}
- /**
- * Test package failure and notifies only one observer even with observer impact tie.
- */
- @Test
- public void testPackageFailureNotifyOneSameImpact() throws Exception {
- mSetFlagsRule.disableFlags(Flags.FLAG_RECOVERABILITY_DETECTION);
- PackageWatchdog watchdog = createWatchdog();
- TestObserver observer1 = new TestObserver(OBSERVER_NAME_1,
- PackageHealthObserverImpact.USER_IMPACT_LEVEL_100);
- TestObserver observer2 = new TestObserver(OBSERVER_NAME_2,
- PackageHealthObserverImpact.USER_IMPACT_LEVEL_100);
-
- // Start observing for observer1 and observer2 with failure handling
- watchdog.registerHealthObserver(mTestExecutor, observer2);
- watchdog.startExplicitHealthCheck(Arrays.asList(APP_A), SHORT_DURATION, observer2);
- watchdog.registerHealthObserver(mTestExecutor, observer1);
- watchdog.startExplicitHealthCheck(Arrays.asList(APP_A), SHORT_DURATION, observer1);
-
- // Then fail APP_A above the threshold
- raiseFatalFailureAndDispatch(watchdog,
- Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)),
- PackageWatchdog.FAILURE_REASON_UNKNOWN);
-
- // Verify only one observer is notifed
- assertThat(observer1.mMitigatedPackages).containsExactly(APP_A);
- assertThat(observer2.mMitigatedPackages).isEmpty();
- }
-
@Test
public void testPackageFailureNotifyOneSameImpactRecoverabilityDetection() throws Exception {
PackageWatchdog watchdog = createWatchdog();
@@ -1015,27 +854,6 @@ public class PackageWatchdogTest {
@Test
@RequiresFlagsDisabled(Flags.FLAG_REFACTOR_CRASHRECOVERY)
- public void testNetworkStackFailure() {
- mSetFlagsRule.disableFlags(Flags.FLAG_RECOVERABILITY_DETECTION);
- final PackageWatchdog wd = createWatchdog();
-
- // Start observing with failure handling
- TestObserver observer = new TestObserver(OBSERVER_NAME_1,
- PackageHealthObserverImpact.USER_IMPACT_LEVEL_100);
- wd.startExplicitHealthCheck(Collections.singletonList(APP_A), SHORT_DURATION, observer);
-
- // Notify of NetworkStack failure
- mConnectivityModuleCallbackCaptor.getValue().onNetworkStackFailure(APP_A);
-
- // Run handler so package failures are dispatched to observers
- mTestLooper.dispatchAll();
-
- // Verify the NetworkStack observer is notified
- assertThat(observer.mMitigatedPackages).containsExactly(APP_A);
- }
-
- @Test
- @RequiresFlagsDisabled(Flags.FLAG_REFACTOR_CRASHRECOVERY)
public void testNetworkStackFailureRecoverabilityDetection() {
final PackageWatchdog wd = createWatchdog();
@@ -1270,21 +1088,6 @@ public class PackageWatchdogTest {
assertThat(persistentObserver.mHealthCheckFailedPackages).isEmpty();
}
-
- /** Ensure that boot loop mitigation is done when the number of boots meets the threshold. */
- @Test
- public void testBootLoopDetection_meetsThreshold() {
- mSetFlagsRule.disableFlags(Flags.FLAG_RECOVERABILITY_DETECTION);
- PackageWatchdog watchdog = createWatchdog();
- TestObserver bootObserver = new TestObserver(OBSERVER_NAME_1);
- watchdog.registerHealthObserver(mTestExecutor, bootObserver);
- for (int i = 0; i < PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT; i++) {
- watchdog.noteBoot();
- }
- mTestLooper.dispatchAll();
- assertThat(bootObserver.mitigatedBootLoop()).isTrue();
- }
-
@Test
public void testBootLoopDetection_meetsThresholdRecoverability() {
PackageWatchdog watchdog = createWatchdog();
@@ -1330,27 +1133,6 @@ public class PackageWatchdogTest {
assertThat(bootObserver.mitigatedBootLoop()).isFalse();
}
- /**
- * Ensure that boot loop mitigation is done for the observer with the lowest user impact
- */
- @Test
- public void testBootLoopMitigationDoneForLowestUserImpact() {
- mSetFlagsRule.disableFlags(Flags.FLAG_RECOVERABILITY_DETECTION);
- PackageWatchdog watchdog = createWatchdog();
- TestObserver bootObserver1 = new TestObserver(OBSERVER_NAME_1);
- bootObserver1.setImpact(PackageHealthObserverImpact.USER_IMPACT_LEVEL_10);
- TestObserver bootObserver2 = new TestObserver(OBSERVER_NAME_2);
- bootObserver2.setImpact(PackageHealthObserverImpact.USER_IMPACT_LEVEL_30);
- watchdog.registerHealthObserver(mTestExecutor, bootObserver1);
- watchdog.registerHealthObserver(mTestExecutor, bootObserver2);
- for (int i = 0; i < PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT; i++) {
- watchdog.noteBoot();
- }
- mTestLooper.dispatchAll();
- assertThat(bootObserver1.mitigatedBootLoop()).isTrue();
- assertThat(bootObserver2.mitigatedBootLoop()).isFalse();
- }
-
@Test
public void testBootLoopMitigationDoneForLowestUserImpactRecoverability() {
PackageWatchdog watchdog = createWatchdog();
@@ -1368,32 +1150,6 @@ public class PackageWatchdogTest {
assertThat(bootObserver2.mitigatedBootLoop()).isFalse();
}
- /**
- * Ensure that the correct mitigation counts are sent to the boot loop observer.
- */
- @Test
- public void testMultipleBootLoopMitigation() {
- mSetFlagsRule.disableFlags(Flags.FLAG_RECOVERABILITY_DETECTION);
- PackageWatchdog watchdog = createWatchdog();
- TestObserver bootObserver = new TestObserver(OBSERVER_NAME_1);
- watchdog.registerHealthObserver(mTestExecutor, bootObserver);
- for (int i = 0; i < 4; i++) {
- for (int j = 0; j < PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT; j++) {
- watchdog.noteBoot();
- }
- }
-
- moveTimeForwardAndDispatch(PackageWatchdog.DEFAULT_DEESCALATION_WINDOW_MS + 1);
-
- for (int i = 0; i < 4; i++) {
- for (int j = 0; j < PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT; j++) {
- watchdog.noteBoot();
- }
- }
- mTestLooper.dispatchAll();
- assertThat(bootObserver.mBootMitigationCounts).isEqualTo(List.of(1, 2, 3, 4, 1, 2, 3, 4));
- }
-
@Test
public void testMultipleBootLoopMitigationRecoverabilityLowImpact() {
PackageWatchdog watchdog = createWatchdog();
@@ -1800,9 +1556,7 @@ public class PackageWatchdogTest {
watchdog.notifyPackageFailure(packages, failureReason);
}
mTestLooper.dispatchAll();
- if (Flags.recoverabilityDetection()) {
- moveTimeForwardAndDispatch(watchdog.DEFAULT_MITIGATION_WINDOW_MS);
- }
+ moveTimeForwardAndDispatch(watchdog.DEFAULT_MITIGATION_WINDOW_MS);
}
private PackageWatchdog createWatchdog() {
diff --git a/tests/utils/testutils/java/android/os/test/TestLooper.java b/tests/utils/testutils/java/android/os/test/TestLooper.java
index 83d22d923c78..4d379e45a81a 100644
--- a/tests/utils/testutils/java/android/os/test/TestLooper.java
+++ b/tests/utils/testutils/java/android/os/test/TestLooper.java
@@ -18,18 +18,24 @@ package android.os.test;
import static org.junit.Assert.assertTrue;
+import android.os.Build;
import android.os.Handler;
import android.os.HandlerExecutor;
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.platform.app.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;
/**
@@ -44,7 +50,9 @@ import java.util.concurrent.Executor;
* 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;
@@ -54,24 +62,46 @@ public class TestLooper {
private static final Method MESSAGE_MARK_IN_USE_METHOD;
private static final String TAG = "TestLooper";
- private final Clock mClock;
-
private AutoDispatchThread mAutoDispatchThread;
+ /**
+ * Baklava introduces new {@link TestLooperManager} APIs that we can use instead of reflection.
+ */
+ private static boolean isAtLeastBaklava() {
+ Method[] methods = TestLooperManager.class.getMethods();
+ for (Method method : methods) {
+ if (method.getName().equals("peekWhen")) {
+ return true;
+ }
+ }
+ return false;
+ // TODO(shayba): delete the above, uncomment the below.
+ // SDK_INT has not yet ramped to Baklava in all 25Q2 builds.
+ // return Build.VERSION.SDK_INT >= Build.VERSION_CODES.BAKLAVA;
+ }
+
static {
try {
LOOPER_CONSTRUCTOR = Looper.class.getDeclaredConstructor(Boolean.TYPE);
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);
+
+ if (isAtLeastBaklava()) {
+ MESSAGE_QUEUE_MESSAGES_FIELD = null;
+ MESSAGE_NEXT_FIELD = null;
+ MESSAGE_WHEN_FIELD = null;
+ MESSAGE_MARK_IN_USE_METHOD = null;
+ } else {
+ 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);
}
@@ -106,6 +136,13 @@ public class TestLooper {
throw new RuntimeException("Reflection error constructing or accessing looper", e);
}
+ if (isAtLeastBaklava()) {
+ mTestLooperManager =
+ InstrumentationRegistry.getInstrumentation().acquireLooperManager(mLooper);
+ } else {
+ mTestLooperManager = null;
+ }
+
mClock = clock;
}
@@ -117,19 +154,61 @@ public class TestLooper {
return new HandlerExecutor(new Handler(getLooper()));
}
- private Message getMessageLinkedList() {
+ private Message getMessageLinkedListLegacy() {
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);
+ e);
}
}
public void moveTimeForward(long milliSeconds) {
+ if (isAtLeastBaklava()) {
+ moveTimeForwardBaklava(milliSeconds);
+ } else {
+ moveTimeForwardLegacy(milliSeconds);
+ }
+ }
+
+ private void moveTimeForwardBaklava(long milliSeconds) {
+ // Drain all Messages from the queue.
+ Queue<Message> messages = new ArrayDeque<>();
+ while (true) {
+ Message message = mTestLooperManager.poll();
+ if (message == null) {
+ break;
+ }
+ messages.add(message);
+ }
+
+ // Repost all Messages back to the queue with a new time.
+ while (true) {
+ Message message = messages.poll();
+ if (message == null) {
+ break;
+ }
+
+ // Ugly trick to reset the Message's "in use" flag.
+ // This is needed because the Message cannot be re-enqueued if it's
+ // marked in use.
+ message.copyFrom(message);
+
+ // Adjust the Message's delivery time.
+ long newWhen = message.getWhen() - milliSeconds;
+ if (newWhen < 0) {
+ newWhen = 0;
+ }
+
+ // Send the Message back to its Handler to be re-enqueued.
+ message.getTarget().sendMessageAtTime(message, newWhen);
+ }
+ }
+
+ private void moveTimeForwardLegacy(long milliSeconds) {
try {
- Message msg = getMessageLinkedList();
+ Message msg = getMessageLinkedListLegacy();
while (msg != null) {
long updatedWhen = msg.getWhen() - milliSeconds;
if (updatedWhen < 0) {
@@ -147,12 +226,12 @@ public class TestLooper {
return mClock.uptimeMillis();
}
- private Message messageQueueNext() {
+ private Message messageQueueNextLegacy() {
try {
long now = currentTime();
Message prevMsg = null;
- Message msg = getMessageLinkedList();
+ Message msg = getMessageLinkedListLegacy();
if (msg != null && msg.getTarget() == null) {
// Stalled by a barrier. Find the next asynchronous message in
// the queue.
@@ -185,18 +264,46 @@ public class TestLooper {
/**
* @return true if there are pending messages in the message queue
*/
- public synchronized boolean isIdle() {
- Message messageList = getMessageLinkedList();
+ public boolean isIdle() {
+ if (isAtLeastBaklava()) {
+ return isIdleBaklava();
+ } else {
+ return isIdleLegacy();
+ }
+ }
+
+ private boolean isIdleBaklava() {
+ Long when = mTestLooperManager.peekWhen();
+ return when != null && currentTime() >= when;
+ }
+ private synchronized boolean isIdleLegacy() {
+ Message messageList = getMessageLinkedListLegacy();
return messageList != null && currentTime() >= messageList.getWhen();
}
/**
* @return the next message in the Looper's message queue or null if there is none
*/
- public synchronized Message nextMessage() {
+ public Message nextMessage() {
+ if (isAtLeastBaklava()) {
+ return nextMessageBaklava();
+ } else {
+ return nextMessageLegacy();
+ }
+ }
+
+ private Message nextMessageBaklava() {
+ if (isIdle()) {
+ return mTestLooperManager.poll();
+ } else {
+ return null;
+ }
+ }
+
+ private synchronized Message nextMessageLegacy() {
if (isIdle()) {
- return messageQueueNext();
+ return messageQueueNextLegacy();
} else {
return null;
}
@@ -206,9 +313,26 @@ public class TestLooper {
* Dispatch the next message in the queue
* Asserts that there is a message in the queue
*/
- public synchronized void dispatchNext() {
+ public void dispatchNext() {
+ if (isAtLeastBaklava()) {
+ dispatchNextBaklava();
+ } else {
+ dispatchNextLegacy();
+ }
+ }
+
+ private void dispatchNextBaklava() {
+ assertTrue(isIdle());
+ Message msg = mTestLooperManager.poll();
+ if (msg == null) {
+ return;
+ }
+ msg.getTarget().dispatchMessage(msg);
+ }
+
+ private synchronized void dispatchNextLegacy() {
assertTrue(isIdle());
- Message msg = messageQueueNext();
+ Message msg = messageQueueNextLegacy();
if (msg == null) {
return;
}
diff --git a/tests/vcn/Android.bp b/tests/vcn/Android.bp
index 51a300bff7ea..661ed07a5669 100644
--- a/tests/vcn/Android.bp
+++ b/tests/vcn/Android.bp
@@ -16,13 +16,19 @@ android_test {
name: "FrameworksVcnTests",
// For access hidden connectivity methods in tests
defaults: ["framework-connectivity-test-defaults"],
+
+ // TODO: b/374174952 Use 36 after Android B finalization
+ min_sdk_version: "35",
+
srcs: [
"java/**/*.java",
"java/**/*.kt",
],
platform_apis: true,
- test_suites: ["device-tests"],
- certificate: "platform",
+ test_suites: [
+ "general-tests",
+ "mts-tethering",
+ ],
static_libs: [
"android.net.vcn.flags-aconfig-java-export",
"androidx.test.rules",
diff --git a/tests/vcn/AndroidManifest.xml b/tests/vcn/AndroidManifest.xml
index a8f657c89f76..08effbd1f7cf 100644
--- a/tests/vcn/AndroidManifest.xml
+++ b/tests/vcn/AndroidManifest.xml
@@ -16,8 +16,9 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.frameworks.tests.vcn">
- <uses-sdk android:minSdkVersion="33"
- android:targetSdkVersion="33"/>
+ <!-- TODO: b/374174952 Use 36 after Android B finalization -->
+ <uses-sdk android:minSdkVersion="35" android:targetSdkVersion="35" />
+
<application>
<uses-library android:name="android.test.runner" />
</application>
diff --git a/tests/vcn/AndroidTest.xml b/tests/vcn/AndroidTest.xml
index dc521fd7bcd9..9c8362f36cb2 100644
--- a/tests/vcn/AndroidTest.xml
+++ b/tests/vcn/AndroidTest.xml
@@ -14,12 +14,20 @@
limitations under the License.
-->
<configuration description="Runs VCN Tests.">
- <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true" />
<option name="test-file-name" value="FrameworksVcnTests.apk" />
</target_preparer>
<option name="test-suite-tag" value="apct" />
<option name="test-tag" value="FrameworksVcnTests" />
+
+ <!-- Run tests in MTS only if the Tethering Mainline module is installed. -->
+ <object type="module_controller"
+ class="com.android.tradefed.testtype.suite.module.MainlineTestModuleController">
+ <option name="mainline-module-package-name" value="com.google.android.tethering" />
+ </object>
+
<test class="com.android.tradefed.testtype.AndroidJUnitTest" >
<option name="package" value="com.android.frameworks.tests.vcn" />
<option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
diff --git a/tests/vcn/java/android/net/vcn/VcnCellUnderlyingNetworkTemplateTest.java b/tests/vcn/java/android/net/vcn/VcnCellUnderlyingNetworkTemplateTest.java
index 156961312323..0fa11ae1fe7d 100644
--- a/tests/vcn/java/android/net/vcn/VcnCellUnderlyingNetworkTemplateTest.java
+++ b/tests/vcn/java/android/net/vcn/VcnCellUnderlyingNetworkTemplateTest.java
@@ -23,11 +23,24 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.fail;
+import android.os.Build;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRunner;
+
import org.junit.Test;
+import org.junit.runner.RunWith;
import java.util.HashSet;
import java.util.Set;
+// TODO: b/374174952 After B finalization, use Sdk36ModuleController to ensure VCN tests only run on
+// Android B/B+
+@RunWith(DevSdkIgnoreRunner.class)
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.VANILLA_ICE_CREAM)
+@SmallTest
public class VcnCellUnderlyingNetworkTemplateTest extends VcnUnderlyingNetworkTemplateTestBase {
private static final Set<String> ALLOWED_PLMN_IDS = new HashSet<>();
private static final Set<Integer> ALLOWED_CARRIER_IDS = new HashSet<>();
diff --git a/tests/vcn/java/android/net/vcn/VcnConfigTest.java b/tests/vcn/java/android/net/vcn/VcnConfigTest.java
index 73a0a6183cb6..fa97de0aff45 100644
--- a/tests/vcn/java/android/net/vcn/VcnConfigTest.java
+++ b/tests/vcn/java/android/net/vcn/VcnConfigTest.java
@@ -29,11 +29,14 @@ import static org.mockito.Mockito.mock;
import android.annotation.NonNull;
import android.content.Context;
+import android.os.Build;
import android.os.Parcel;
import android.util.ArraySet;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
+
+import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRunner;
import org.junit.Before;
import org.junit.Test;
@@ -42,7 +45,10 @@ import org.junit.runner.RunWith;
import java.util.Collections;
import java.util.Set;
-@RunWith(AndroidJUnit4.class)
+// TODO: b/374174952 After B finalization, use Sdk36ModuleController to ensure VCN tests only run on
+// Android B/B+
+@RunWith(DevSdkIgnoreRunner.class)
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.VANILLA_ICE_CREAM)
@SmallTest
public class VcnConfigTest {
private static final String TEST_PACKAGE_NAME = VcnConfigTest.class.getPackage().getName();
diff --git a/tests/vcn/java/android/net/vcn/VcnGatewayConnectionConfigTest.java b/tests/vcn/java/android/net/vcn/VcnGatewayConnectionConfigTest.java
index 59dc68900100..990cc74caf6c 100644
--- a/tests/vcn/java/android/net/vcn/VcnGatewayConnectionConfigTest.java
+++ b/tests/vcn/java/android/net/vcn/VcnGatewayConnectionConfigTest.java
@@ -34,10 +34,13 @@ import android.net.ipsec.ike.IkeSessionParams;
import android.net.ipsec.ike.IkeTunnelConnectionParams;
import android.net.vcn.persistablebundleutils.IkeSessionParamsUtilsTest;
import android.net.vcn.persistablebundleutils.TunnelConnectionParamsUtilsTest;
+import android.os.Build;
import android.os.PersistableBundle;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
+
+import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRunner;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -49,7 +52,10 @@ import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
-@RunWith(AndroidJUnit4.class)
+// TODO: b/374174952 After B finalization, use Sdk36ModuleController to ensure VCN tests only run on
+// Android B/B+
+@RunWith(DevSdkIgnoreRunner.class)
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.VANILLA_ICE_CREAM)
@SmallTest
public class VcnGatewayConnectionConfigTest {
// Public for use in VcnGatewayConnectionTest
diff --git a/tests/vcn/java/android/net/vcn/VcnManagerTest.java b/tests/vcn/java/android/net/vcn/VcnManagerTest.java
index 8461de6d877b..1739fbc0fa6d 100644
--- a/tests/vcn/java/android/net/vcn/VcnManagerTest.java
+++ b/tests/vcn/java/android/net/vcn/VcnManagerTest.java
@@ -38,16 +38,28 @@ import android.net.NetworkCapabilities;
import android.net.vcn.VcnManager.VcnStatusCallback;
import android.net.vcn.VcnManager.VcnStatusCallbackBinder;
import android.net.vcn.VcnManager.VcnUnderlyingNetworkPolicyListener;
+import android.os.Build;
import android.os.ParcelUuid;
+import androidx.test.filters.SmallTest;
+
+import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRunner;
+
import org.junit.Before;
import org.junit.Test;
+import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import java.net.UnknownHostException;
import java.util.UUID;
import java.util.concurrent.Executor;
+// TODO: b/374174952 After B finalization, use Sdk36ModuleController to ensure VCN tests only run on
+// Android B/B+
+@RunWith(DevSdkIgnoreRunner.class)
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.VANILLA_ICE_CREAM)
+@SmallTest
public class VcnManagerTest {
private static final ParcelUuid SUB_GROUP = new ParcelUuid(new UUID(0, 0));
private static final String GATEWAY_CONNECTION_NAME = "gatewayConnectionName";
diff --git a/tests/vcn/java/android/net/vcn/VcnTransportInfoTest.java b/tests/vcn/java/android/net/vcn/VcnTransportInfoTest.java
index 7bc9970629a6..52952eb3f2cc 100644
--- a/tests/vcn/java/android/net/vcn/VcnTransportInfoTest.java
+++ b/tests/vcn/java/android/net/vcn/VcnTransportInfoTest.java
@@ -30,12 +30,24 @@ import static org.junit.Assert.fail;
import android.net.NetworkCapabilities;
import android.net.wifi.WifiConfiguration;
import android.net.wifi.WifiInfo;
+import android.os.Build;
import android.os.Parcel;
+import androidx.test.filters.SmallTest;
+
+import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRunner;
+
import org.junit.Test;
+import org.junit.runner.RunWith;
import java.util.Arrays;
+// TODO: b/374174952 After B finalization, use Sdk36ModuleController to ensure VCN tests only run on
+// Android B/B+
+@RunWith(DevSdkIgnoreRunner.class)
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.VANILLA_ICE_CREAM)
+@SmallTest
public class VcnTransportInfoTest {
private static final int SUB_ID = 1;
private static final int NETWORK_ID = 5;
diff --git a/tests/vcn/java/android/net/vcn/VcnUnderlyingNetworkPolicyTest.java b/tests/vcn/java/android/net/vcn/VcnUnderlyingNetworkPolicyTest.java
index a674425efea3..c82d2003dbf6 100644
--- a/tests/vcn/java/android/net/vcn/VcnUnderlyingNetworkPolicyTest.java
+++ b/tests/vcn/java/android/net/vcn/VcnUnderlyingNetworkPolicyTest.java
@@ -22,9 +22,21 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import android.net.NetworkCapabilities;
+import android.os.Build;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRunner;
import org.junit.Test;
+import org.junit.runner.RunWith;
+// TODO: b/374174952 After B finalization, use Sdk36ModuleController to ensure VCN tests only run on
+// Android B/B+
+@RunWith(DevSdkIgnoreRunner.class)
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.VANILLA_ICE_CREAM)
+@SmallTest
public class VcnUnderlyingNetworkPolicyTest {
private static final VcnUnderlyingNetworkPolicy DEFAULT_NETWORK_POLICY =
new VcnUnderlyingNetworkPolicy(
diff --git a/tests/vcn/java/android/net/vcn/VcnUnderlyingNetworkSpecifierTest.java b/tests/vcn/java/android/net/vcn/VcnUnderlyingNetworkSpecifierTest.java
index 2110d6ee7c86..22361cc71f12 100644
--- a/tests/vcn/java/android/net/vcn/VcnUnderlyingNetworkSpecifierTest.java
+++ b/tests/vcn/java/android/net/vcn/VcnUnderlyingNetworkSpecifierTest.java
@@ -22,14 +22,20 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import android.net.TelephonyNetworkSpecifier;
+import android.os.Build;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
+
+import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRunner;
import org.junit.Test;
import org.junit.runner.RunWith;
-@RunWith(AndroidJUnit4.class)
+// TODO: b/374174952 After B finalization, use Sdk36ModuleController to ensure VCN tests only run on
+// Android B/B+
+@RunWith(DevSdkIgnoreRunner.class)
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.VANILLA_ICE_CREAM)
@SmallTest
public class VcnUnderlyingNetworkSpecifierTest {
private static final int[] TEST_SUB_IDS = new int[] {1, 2, 3, 5};
diff --git a/tests/vcn/java/android/net/vcn/VcnUtilsTest.java b/tests/vcn/java/android/net/vcn/VcnUtilsTest.java
index 3ce6c8f9386d..fb040d8f9b91 100644
--- a/tests/vcn/java/android/net/vcn/VcnUtilsTest.java
+++ b/tests/vcn/java/android/net/vcn/VcnUtilsTest.java
@@ -30,13 +30,25 @@ import android.net.Network;
import android.net.NetworkCapabilities;
import android.net.TelephonyNetworkSpecifier;
import android.net.wifi.WifiInfo;
+import android.os.Build;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRunner;
import org.junit.Before;
import org.junit.Test;
+import org.junit.runner.RunWith;
import java.util.Arrays;
import java.util.Collections;
+// TODO: b/374174952 After B finalization, use Sdk36ModuleController to ensure VCN tests only run on
+// Android B/B+
+@RunWith(DevSdkIgnoreRunner.class)
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.VANILLA_ICE_CREAM)
+@SmallTest
public class VcnUtilsTest {
private static final int SUB_ID = 1;
diff --git a/tests/vcn/java/android/net/vcn/VcnWifiUnderlyingNetworkTemplateTest.java b/tests/vcn/java/android/net/vcn/VcnWifiUnderlyingNetworkTemplateTest.java
index 4063178e005d..2c072e1cbc88 100644
--- a/tests/vcn/java/android/net/vcn/VcnWifiUnderlyingNetworkTemplateTest.java
+++ b/tests/vcn/java/android/net/vcn/VcnWifiUnderlyingNetworkTemplateTest.java
@@ -22,10 +22,23 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
+import android.os.Build;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRunner;
+
import org.junit.Test;
+import org.junit.runner.RunWith;
import java.util.Set;
+// TODO: b/374174952 After B finalization, use Sdk36ModuleController to ensure VCN tests only run on
+// Android B/B+
+@RunWith(DevSdkIgnoreRunner.class)
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.VANILLA_ICE_CREAM)
+@SmallTest
public class VcnWifiUnderlyingNetworkTemplateTest extends VcnUnderlyingNetworkTemplateTestBase {
private static final String SSID = "TestWifi";
diff --git a/tests/vcn/java/android/net/vcn/persistablebundleutils/EapSessionConfigUtilsTest.java b/tests/vcn/java/android/net/vcn/persistablebundleutils/EapSessionConfigUtilsTest.java
index bc8e9d3200b6..01e9ac2ac3cf 100644
--- a/tests/vcn/java/android/net/vcn/persistablebundleutils/EapSessionConfigUtilsTest.java
+++ b/tests/vcn/java/android/net/vcn/persistablebundleutils/EapSessionConfigUtilsTest.java
@@ -21,11 +21,14 @@ import static android.telephony.TelephonyManager.APPTYPE_USIM;
import static org.junit.Assert.assertEquals;
import android.net.eap.EapSessionConfig;
+import android.os.Build;
import android.os.PersistableBundle;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
+
+import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRunner;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -35,7 +38,10 @@ import java.nio.charset.StandardCharsets;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
-@RunWith(AndroidJUnit4.class)
+// TODO: b/374174952 After B finalization, use Sdk36ModuleController to ensure VCN tests only run on
+// Android B/B+
+@RunWith(DevSdkIgnoreRunner.class)
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.VANILLA_ICE_CREAM)
@SmallTest
public class EapSessionConfigUtilsTest {
private static final byte[] EAP_ID = "test@android.net".getBytes(StandardCharsets.US_ASCII);
diff --git a/tests/vcn/java/android/net/vcn/persistablebundleutils/IkeIdentificationUtilsTest.java b/tests/vcn/java/android/net/vcn/persistablebundleutils/IkeIdentificationUtilsTest.java
index 4f3930f9b5af..821e5a6c94cb 100644
--- a/tests/vcn/java/android/net/vcn/persistablebundleutils/IkeIdentificationUtilsTest.java
+++ b/tests/vcn/java/android/net/vcn/persistablebundleutils/IkeIdentificationUtilsTest.java
@@ -25,10 +25,13 @@ import android.net.ipsec.ike.IkeIpv4AddrIdentification;
import android.net.ipsec.ike.IkeIpv6AddrIdentification;
import android.net.ipsec.ike.IkeKeyIdIdentification;
import android.net.ipsec.ike.IkeRfc822AddrIdentification;
+import android.os.Build;
import android.os.PersistableBundle;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
+
+import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRunner;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -39,7 +42,10 @@ import java.net.InetAddress;
import javax.security.auth.x500.X500Principal;
-@RunWith(AndroidJUnit4.class)
+// TODO: b/374174952 After B finalization, use Sdk36ModuleController to ensure VCN tests only run on
+// Android B/B+
+@RunWith(DevSdkIgnoreRunner.class)
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.VANILLA_ICE_CREAM)
@SmallTest
public class IkeIdentificationUtilsTest {
private static void verifyPersistableBundleEncodeDecodeIsLossless(IkeIdentification id) {
diff --git a/tests/vcn/java/android/net/vcn/persistablebundleutils/IkeSessionParamsUtilsTest.java b/tests/vcn/java/android/net/vcn/persistablebundleutils/IkeSessionParamsUtilsTest.java
index 9f7d2390938f..7200aee1c012 100644
--- a/tests/vcn/java/android/net/vcn/persistablebundleutils/IkeSessionParamsUtilsTest.java
+++ b/tests/vcn/java/android/net/vcn/persistablebundleutils/IkeSessionParamsUtilsTest.java
@@ -29,14 +29,16 @@ import android.net.InetAddresses;
import android.net.eap.EapSessionConfig;
import android.net.ipsec.ike.IkeFqdnIdentification;
import android.net.ipsec.ike.IkeSessionParams;
+import android.os.Build;
import android.os.PersistableBundle;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import com.android.internal.org.bouncycastle.util.io.pem.PemObject;
import com.android.internal.org.bouncycastle.util.io.pem.PemReader;
+import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRunner;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -52,7 +54,10 @@ import java.security.cert.X509Certificate;
import java.security.interfaces.RSAPrivateKey;
import java.util.concurrent.TimeUnit;
-@RunWith(AndroidJUnit4.class)
+// TODO: b/374174952 After B finalization, use Sdk36ModuleController to ensure VCN tests only run on
+// Android B/B+
+@RunWith(DevSdkIgnoreRunner.class)
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.VANILLA_ICE_CREAM)
@SmallTest
public class IkeSessionParamsUtilsTest {
// Public for use in VcnGatewayConnectionConfigTest, EncryptedTunnelParamsUtilsTest
diff --git a/tests/vcn/java/android/net/vcn/persistablebundleutils/IkeTrafficSelectorUtilsTest.java b/tests/vcn/java/android/net/vcn/persistablebundleutils/IkeTrafficSelectorUtilsTest.java
index 28cf38a2a583..957e785d70c0 100644
--- a/tests/vcn/java/android/net/vcn/persistablebundleutils/IkeTrafficSelectorUtilsTest.java
+++ b/tests/vcn/java/android/net/vcn/persistablebundleutils/IkeTrafficSelectorUtilsTest.java
@@ -20,17 +20,23 @@ import static org.junit.Assert.assertEquals;
import android.net.InetAddresses;
import android.net.ipsec.ike.IkeTrafficSelector;
+import android.os.Build;
import android.os.PersistableBundle;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
+
+import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRunner;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.net.InetAddress;
-@RunWith(AndroidJUnit4.class)
+// TODO: b/374174952 After B finalization, use Sdk36ModuleController to ensure VCN tests only run on
+// Android B/B+
+@RunWith(DevSdkIgnoreRunner.class)
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.VANILLA_ICE_CREAM)
@SmallTest
public class IkeTrafficSelectorUtilsTest {
private static final int START_PORT = 16;
diff --git a/tests/vcn/java/android/net/vcn/persistablebundleutils/SaProposalUtilsTest.java b/tests/vcn/java/android/net/vcn/persistablebundleutils/SaProposalUtilsTest.java
index 664044a9e7d4..1e8f5ff2dc07 100644
--- a/tests/vcn/java/android/net/vcn/persistablebundleutils/SaProposalUtilsTest.java
+++ b/tests/vcn/java/android/net/vcn/persistablebundleutils/SaProposalUtilsTest.java
@@ -21,15 +21,21 @@ import static org.junit.Assert.assertEquals;
import android.net.ipsec.ike.ChildSaProposal;
import android.net.ipsec.ike.IkeSaProposal;
import android.net.ipsec.ike.SaProposal;
+import android.os.Build;
import android.os.PersistableBundle;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
+
+import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRunner;
import org.junit.Test;
import org.junit.runner.RunWith;
-@RunWith(AndroidJUnit4.class)
+// TODO: b/374174952 After B finalization, use Sdk36ModuleController to ensure VCN tests only run on
+// Android B/B+
+@RunWith(DevSdkIgnoreRunner.class)
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.VANILLA_ICE_CREAM)
@SmallTest
public class SaProposalUtilsTest {
/** Package private so that IkeSessionParamsUtilsTest can use it */
diff --git a/tests/vcn/java/android/net/vcn/persistablebundleutils/TunnelConnectionParamsUtilsTest.java b/tests/vcn/java/android/net/vcn/persistablebundleutils/TunnelConnectionParamsUtilsTest.java
index f9dc9eb4d5ae..7d17724112ec 100644
--- a/tests/vcn/java/android/net/vcn/persistablebundleutils/TunnelConnectionParamsUtilsTest.java
+++ b/tests/vcn/java/android/net/vcn/persistablebundleutils/TunnelConnectionParamsUtilsTest.java
@@ -20,14 +20,20 @@ import static org.junit.Assert.assertEquals;
import android.net.ipsec.ike.IkeSessionParams;
import android.net.ipsec.ike.IkeTunnelConnectionParams;
+import android.os.Build;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
+
+import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRunner;
import org.junit.Test;
import org.junit.runner.RunWith;
-@RunWith(AndroidJUnit4.class)
+// TODO: b/374174952 After B finalization, use Sdk36ModuleController to ensure VCN tests only run on
+// Android B/B+
+@RunWith(DevSdkIgnoreRunner.class)
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.VANILLA_ICE_CREAM)
@SmallTest
public class TunnelConnectionParamsUtilsTest {
// Public for use in VcnGatewayConnectionConfigTest
diff --git a/tests/vcn/java/android/net/vcn/persistablebundleutils/TunnelModeChildSessionParamsUtilsTest.java b/tests/vcn/java/android/net/vcn/persistablebundleutils/TunnelModeChildSessionParamsUtilsTest.java
index e0b5f0ef0381..3d7348a79b8c 100644
--- a/tests/vcn/java/android/net/vcn/persistablebundleutils/TunnelModeChildSessionParamsUtilsTest.java
+++ b/tests/vcn/java/android/net/vcn/persistablebundleutils/TunnelModeChildSessionParamsUtilsTest.java
@@ -25,10 +25,13 @@ import android.net.InetAddresses;
import android.net.ipsec.ike.ChildSaProposal;
import android.net.ipsec.ike.IkeTrafficSelector;
import android.net.ipsec.ike.TunnelModeChildSessionParams;
+import android.os.Build;
import android.os.PersistableBundle;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
+
+import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRunner;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -37,7 +40,10 @@ import java.net.Inet4Address;
import java.net.Inet6Address;
import java.util.concurrent.TimeUnit;
-@RunWith(AndroidJUnit4.class)
+// TODO: b/374174952 After B finalization, use Sdk36ModuleController to ensure VCN tests only run on
+// Android B/B+
+@RunWith(DevSdkIgnoreRunner.class)
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.VANILLA_ICE_CREAM)
@SmallTest
public class TunnelModeChildSessionParamsUtilsTest {
// Package private for use in EncryptedTunnelParamsUtilsTest
diff --git a/tests/vcn/java/android/net/vcn/util/MtuUtilsTest.java b/tests/vcn/java/android/net/vcn/util/MtuUtilsTest.java
index 47638b002f37..99c7aa72146b 100644
--- a/tests/vcn/java/android/net/vcn/util/MtuUtilsTest.java
+++ b/tests/vcn/java/android/net/vcn/util/MtuUtilsTest.java
@@ -33,9 +33,12 @@ import static org.junit.Assert.assertTrue;
import static java.util.Collections.emptyList;
import android.net.ipsec.ike.ChildSaProposal;
+import android.os.Build;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
+
+import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRunner;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -43,7 +46,10 @@ import org.junit.runner.RunWith;
import java.util.Arrays;
import java.util.List;
-@RunWith(AndroidJUnit4.class)
+// TODO: b/374174952 After B finalization, use Sdk36ModuleController to ensure VCN tests only run on
+// Android B/B+
+@RunWith(DevSdkIgnoreRunner.class)
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.VANILLA_ICE_CREAM)
@SmallTest
public class MtuUtilsTest {
private void verifyUnderlyingMtuZero(boolean isIpv4) {
diff --git a/tests/vcn/java/android/net/vcn/util/PersistableBundleUtilsTest.java b/tests/vcn/java/android/net/vcn/util/PersistableBundleUtilsTest.java
index c84e60086b37..f7786af840ee 100644
--- a/tests/vcn/java/android/net/vcn/util/PersistableBundleUtilsTest.java
+++ b/tests/vcn/java/android/net/vcn/util/PersistableBundleUtilsTest.java
@@ -21,10 +21,13 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import android.os.Build;
import android.os.PersistableBundle;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
+
+import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRunner;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -35,7 +38,10 @@ import java.util.LinkedHashMap;
import java.util.List;
import java.util.Objects;
-@RunWith(AndroidJUnit4.class)
+// TODO: b/374174952 After B finalization, use Sdk36ModuleController to ensure VCN tests only run on
+// Android B/B+
+@RunWith(DevSdkIgnoreRunner.class)
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.VANILLA_ICE_CREAM)
@SmallTest
public class PersistableBundleUtilsTest {
private static final String TEST_KEY = "testKey";
diff --git a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java
index 26a2a0636792..a97f9a837bab 100644
--- a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java
+++ b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java
@@ -79,6 +79,7 @@ import android.net.vcn.VcnManager;
import android.net.vcn.VcnUnderlyingNetworkPolicy;
import android.net.vcn.util.PersistableBundleUtils;
import android.net.vcn.util.PersistableBundleUtils.PersistableBundleWrapper;
+import android.os.Build;
import android.os.IBinder;
import android.os.ParcelUuid;
import android.os.PersistableBundle;
@@ -93,7 +94,6 @@ import android.telephony.TelephonyManager;
import android.util.ArraySet;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import com.android.server.VcnManagementService.VcnCallback;
import com.android.server.VcnManagementService.VcnStatusCallbackInfo;
@@ -101,6 +101,8 @@ import com.android.server.vcn.TelephonySubscriptionTracker;
import com.android.server.vcn.Vcn;
import com.android.server.vcn.VcnContext;
import com.android.server.vcn.VcnNetworkProvider;
+import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRunner;
import org.junit.Before;
import org.junit.Rule;
@@ -117,8 +119,10 @@ import java.util.Map.Entry;
import java.util.Set;
import java.util.UUID;
-/** Tests for {@link VcnManagementService}. */
-@RunWith(AndroidJUnit4.class)
+// TODO: b/374174952 After B finalization, use Sdk36ModuleController to ensure VCN tests only run on
+// Android B/B+
+@RunWith(DevSdkIgnoreRunner.class)
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.VANILLA_ICE_CREAM)
@SmallTest
public class VcnManagementServiceTest {
@Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
diff --git a/tests/vcn/java/com/android/server/vcn/TelephonySubscriptionTrackerTest.java b/tests/vcn/java/com/android/server/vcn/TelephonySubscriptionTrackerTest.java
index 77f82f0d8cf4..6276be27fbf5 100644
--- a/tests/vcn/java/com/android/server/vcn/TelephonySubscriptionTrackerTest.java
+++ b/tests/vcn/java/com/android/server/vcn/TelephonySubscriptionTrackerTest.java
@@ -54,6 +54,7 @@ import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.vcn.VcnManager;
+import android.os.Build;
import android.os.Handler;
import android.os.ParcelUuid;
import android.os.PersistableBundle;
@@ -69,9 +70,10 @@ import android.util.ArrayMap;
import android.util.ArraySet;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import com.android.modules.utils.HandlerExecutor;
+import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRunner;
import org.junit.Before;
import org.junit.Test;
@@ -87,8 +89,10 @@ import java.util.Map;
import java.util.Set;
import java.util.UUID;
-/** Tests for TelephonySubscriptionTracker */
-@RunWith(AndroidJUnit4.class)
+// TODO: b/374174952 After B finalization, use Sdk36ModuleController to ensure VCN tests only run on
+// Android B/B+
+@RunWith(DevSdkIgnoreRunner.class)
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.VANILLA_ICE_CREAM)
@SmallTest
public class TelephonySubscriptionTrackerTest {
private static final String PACKAGE_NAME =
diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java
index 74db6a5211a0..6608dda95a4b 100644
--- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java
+++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java
@@ -70,16 +70,18 @@ import android.net.vcn.VcnGatewayConnectionConfigTest;
import android.net.vcn.VcnManager.VcnErrorCode;
import android.net.vcn.VcnTransportInfo;
import android.net.vcn.util.MtuUtils;
+import android.os.Build;
import android.os.PersistableBundle;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import com.android.server.vcn.VcnGatewayConnection.VcnChildSessionCallback;
import com.android.server.vcn.VcnGatewayConnection.VcnChildSessionConfiguration;
import com.android.server.vcn.VcnGatewayConnection.VcnIkeSession;
import com.android.server.vcn.VcnGatewayConnection.VcnNetworkAgent;
import com.android.server.vcn.routeselection.UnderlyingNetworkRecord;
+import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRunner;
import org.junit.Before;
import org.junit.Test;
@@ -94,8 +96,10 @@ import java.util.Collections;
import java.util.List;
import java.util.function.Consumer;
-/** Tests for VcnGatewayConnection.ConnectedState */
-@RunWith(AndroidJUnit4.class)
+// TODO: b/374174952 After B finalization, use Sdk36ModuleController to ensure VCN tests only run on
+// Android B/B+
+@RunWith(DevSdkIgnoreRunner.class)
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.VANILLA_ICE_CREAM)
@SmallTest
public class VcnGatewayConnectionConnectedStateTest extends VcnGatewayConnectionTestBase {
private static final int PARALLEL_SA_COUNT = 4;
diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectingStateTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectingStateTest.java
index 3c70759a2fa6..f6123d29f35a 100644
--- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectingStateTest.java
+++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectingStateTest.java
@@ -26,17 +26,22 @@ import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import android.net.ipsec.ike.IkeSessionParams;
+import android.os.Build;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
+
+import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRunner;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
-/** Tests for VcnGatewayConnection.ConnectingState */
-@RunWith(AndroidJUnit4.class)
+// TODO: b/374174952 After B finalization, use Sdk36ModuleController to ensure VCN tests only run on
+// Android B/B+
+@RunWith(DevSdkIgnoreRunner.class)
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.VANILLA_ICE_CREAM)
@SmallTest
public class VcnGatewayConnectionConnectingStateTest extends VcnGatewayConnectionTestBase {
private VcnIkeSession mIkeSession;
diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectedStateTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectedStateTest.java
index f3eb82f46de7..7cfaf5be5111 100644
--- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectedStateTest.java
+++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectedStateTest.java
@@ -30,16 +30,21 @@ import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import android.net.IpSecManager;
+import android.os.Build;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
+
+import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRunner;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
-/** Tests for VcnGatewayConnection.DisconnectedState */
-@RunWith(AndroidJUnit4.class)
+// TODO: b/374174952 After B finalization, use Sdk36ModuleController to ensure VCN tests only run on
+// Android B/B+
+@RunWith(DevSdkIgnoreRunner.class)
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.VANILLA_ICE_CREAM)
@SmallTest
public class VcnGatewayConnectionDisconnectedStateTest extends VcnGatewayConnectionTestBase {
@Before
diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectingStateTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectingStateTest.java
index 78aefad9f8ff..9132d830c54e 100644
--- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectingStateTest.java
+++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectingStateTest.java
@@ -23,15 +23,21 @@ import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
+import android.os.Build;
+
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
+
+import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRunner;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
-/** Tests for VcnGatewayConnection.DisconnectedState */
-@RunWith(AndroidJUnit4.class)
+// TODO: b/374174952 After B finalization, use Sdk36ModuleController to ensure VCN tests only run on
+// Android B/B+
+@RunWith(DevSdkIgnoreRunner.class)
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.VANILLA_ICE_CREAM)
@SmallTest
public class VcnGatewayConnectionDisconnectingStateTest extends VcnGatewayConnectionTestBase {
@Before
diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionRetryTimeoutStateTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionRetryTimeoutStateTest.java
index 6568cdd44377..d5ef4e028709 100644
--- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionRetryTimeoutStateTest.java
+++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionRetryTimeoutStateTest.java
@@ -27,15 +27,21 @@ import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
+import android.os.Build;
+
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
+
+import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRunner;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
-/** Tests for VcnGatewayConnection.RetryTimeoutState */
-@RunWith(AndroidJUnit4.class)
+// TODO: b/374174952 After B finalization, use Sdk36ModuleController to ensure VCN tests only run on
+// Android B/B+
+@RunWith(DevSdkIgnoreRunner.class)
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.VANILLA_ICE_CREAM)
@SmallTest
public class VcnGatewayConnectionRetryTimeoutStateTest extends VcnGatewayConnectionTestBase {
private long mFirstRetryInterval;
diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTest.java
index b9fe76a24d20..5283322682ee 100644
--- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTest.java
+++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTest.java
@@ -61,15 +61,17 @@ import android.net.vcn.VcnGatewayConnectionConfigTest;
import android.net.vcn.VcnManager;
import android.net.vcn.VcnTransportInfo;
import android.net.wifi.WifiInfo;
+import android.os.Build;
import android.os.ParcelUuid;
import android.os.Process;
import android.telephony.SubscriptionInfo;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot;
import com.android.server.vcn.routeselection.UnderlyingNetworkRecord;
+import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRunner;
import org.junit.Before;
import org.junit.Test;
@@ -87,8 +89,10 @@ import java.util.UUID;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
-/** Tests for TelephonySubscriptionTracker */
-@RunWith(AndroidJUnit4.class)
+// TODO: b/374174952 After B finalization, use Sdk36ModuleController to ensure VCN tests only run on
+// Android B/B+
+@RunWith(DevSdkIgnoreRunner.class)
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.VANILLA_ICE_CREAM)
@SmallTest
public class VcnGatewayConnectionTest extends VcnGatewayConnectionTestBase {
private static final int TEST_UID = Process.myUid() + 1;
diff --git a/tests/vcn/java/com/android/server/vcn/VcnNetworkProviderTest.java b/tests/vcn/java/com/android/server/vcn/VcnNetworkProviderTest.java
index e9026e22b6b2..2b92428918db 100644
--- a/tests/vcn/java/com/android/server/vcn/VcnNetworkProviderTest.java
+++ b/tests/vcn/java/com/android/server/vcn/VcnNetworkProviderTest.java
@@ -29,12 +29,14 @@ import android.annotation.NonNull;
import android.content.Context;
import android.net.ConnectivityManager;
import android.net.NetworkRequest;
+import android.os.Build;
import android.os.test.TestLooper;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import com.android.server.vcn.VcnNetworkProvider.NetworkRequestListener;
+import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRunner;
import org.junit.Before;
import org.junit.Test;
@@ -44,8 +46,10 @@ import org.mockito.ArgumentCaptor;
import java.util.ArrayList;
import java.util.List;
-/** Tests for TelephonySubscriptionTracker */
-@RunWith(AndroidJUnit4.class)
+// TODO: b/374174952 After B finalization, use Sdk36ModuleController to ensure VCN tests only run on
+// Android B/B+
+@RunWith(DevSdkIgnoreRunner.class)
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.VANILLA_ICE_CREAM)
@SmallTest
public class VcnNetworkProviderTest {
private static final int TEST_SCORE_UNSATISFIED = 0;
diff --git a/tests/vcn/java/com/android/server/vcn/VcnTest.java b/tests/vcn/java/com/android/server/vcn/VcnTest.java
index 6d269686e42f..bd4aeba761da 100644
--- a/tests/vcn/java/com/android/server/vcn/VcnTest.java
+++ b/tests/vcn/java/com/android/server/vcn/VcnTest.java
@@ -49,20 +49,26 @@ import android.net.Uri;
import android.net.vcn.VcnConfig;
import android.net.vcn.VcnGatewayConnectionConfig;
import android.net.vcn.VcnGatewayConnectionConfigTest;
+import android.os.Build;
import android.os.ParcelUuid;
import android.os.test.TestLooper;
import android.provider.Settings;
import android.telephony.TelephonyManager;
import android.util.ArraySet;
+import androidx.test.filters.SmallTest;
+
import com.android.server.VcnManagementService.VcnCallback;
import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot;
import com.android.server.vcn.Vcn.VcnGatewayStatusCallback;
import com.android.server.vcn.Vcn.VcnUserMobileDataStateListener;
import com.android.server.vcn.VcnNetworkProvider.NetworkRequestListener;
+import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRunner;
import org.junit.Before;
import org.junit.Test;
+import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import java.util.ArrayList;
@@ -73,6 +79,11 @@ import java.util.Map.Entry;
import java.util.Set;
import java.util.UUID;
+// TODO: b/374174952 After B finalization, use Sdk36ModuleController to ensure VCN tests only run on
+// Android B/B+
+@RunWith(DevSdkIgnoreRunner.class)
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.VANILLA_ICE_CREAM)
+@SmallTest
public class VcnTest {
private static final String PKG_NAME = VcnTest.class.getPackage().getName();
private static final ParcelUuid TEST_SUB_GROUP = new ParcelUuid(new UUID(0, 0));
diff --git a/tests/vcn/java/com/android/server/vcn/routeselection/IpSecPacketLossDetectorTest.java b/tests/vcn/java/com/android/server/vcn/routeselection/IpSecPacketLossDetectorTest.java
index c11b6bb3435d..53a36d3e4d6a 100644
--- a/tests/vcn/java/com/android/server/vcn/routeselection/IpSecPacketLossDetectorTest.java
+++ b/tests/vcn/java/com/android/server/vcn/routeselection/IpSecPacketLossDetectorTest.java
@@ -44,16 +44,22 @@ import static org.mockito.Mockito.when;
import android.content.BroadcastReceiver;
import android.content.Intent;
import android.net.IpSecTransformState;
+import android.os.Build;
import android.os.OutcomeReceiver;
import android.os.PowerManager;
+import androidx.test.filters.SmallTest;
+
import com.android.server.vcn.routeselection.IpSecPacketLossDetector.PacketLossCalculationResult;
import com.android.server.vcn.routeselection.IpSecPacketLossDetector.PacketLossCalculator;
import com.android.server.vcn.routeselection.NetworkMetricMonitor.IpSecTransformWrapper;
import com.android.server.vcn.routeselection.NetworkMetricMonitor.NetworkMetricMonitorCallback;
+import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRunner;
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;
@@ -63,6 +69,11 @@ import java.util.Arrays;
import java.util.BitSet;
import java.util.concurrent.TimeUnit;
+// TODO: b/374174952 After B finalization, use Sdk36ModuleController to ensure VCN tests only run on
+// Android B/B+
+@RunWith(DevSdkIgnoreRunner.class)
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.VANILLA_ICE_CREAM)
+@SmallTest
public class IpSecPacketLossDetectorTest extends NetworkEvaluationTestBase {
private static final String TAG = IpSecPacketLossDetectorTest.class.getSimpleName();
diff --git a/tests/vcn/java/com/android/server/vcn/routeselection/NetworkPriorityClassifierTest.java b/tests/vcn/java/com/android/server/vcn/routeselection/NetworkPriorityClassifierTest.java
index 4f34f9f8f74c..a9c637f7c943 100644
--- a/tests/vcn/java/com/android/server/vcn/routeselection/NetworkPriorityClassifierTest.java
+++ b/tests/vcn/java/com/android/server/vcn/routeselection/NetworkPriorityClassifierTest.java
@@ -42,16 +42,28 @@ import android.net.vcn.VcnGatewayConnectionConfig;
import android.net.vcn.VcnManager;
import android.net.vcn.VcnUnderlyingNetworkTemplate;
import android.net.vcn.VcnWifiUnderlyingNetworkTemplate;
+import android.os.Build;
import android.os.PersistableBundle;
import android.util.ArraySet;
+import androidx.test.filters.SmallTest;
+
+import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRunner;
+
import org.junit.Before;
import org.junit.Test;
+import org.junit.runner.RunWith;
import java.util.Collections;
import java.util.List;
import java.util.Set;
+// TODO: b/374174952 After B finalization, use Sdk36ModuleController to ensure VCN tests only run on
+// Android B/B+
+@RunWith(DevSdkIgnoreRunner.class)
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.VANILLA_ICE_CREAM)
+@SmallTest
public class NetworkPriorityClassifierTest extends NetworkEvaluationTestBase {
private UnderlyingNetworkRecord mWifiNetworkRecord;
private UnderlyingNetworkRecord mCellNetworkRecord;
diff --git a/tests/vcn/java/com/android/server/vcn/routeselection/UnderlyingNetworkControllerTest.java b/tests/vcn/java/com/android/server/vcn/routeselection/UnderlyingNetworkControllerTest.java
index e540932d0e1f..99c508c139ec 100644
--- a/tests/vcn/java/com/android/server/vcn/routeselection/UnderlyingNetworkControllerTest.java
+++ b/tests/vcn/java/com/android/server/vcn/routeselection/UnderlyingNetworkControllerTest.java
@@ -58,6 +58,7 @@ import android.net.vcn.VcnCellUnderlyingNetworkTemplate;
import android.net.vcn.VcnCellUnderlyingNetworkTemplateTest;
import android.net.vcn.VcnGatewayConnectionConfigTest;
import android.net.vcn.VcnUnderlyingNetworkTemplate;
+import android.os.Build;
import android.os.ParcelUuid;
import android.os.test.TestLooper;
import android.telephony.CarrierConfigManager;
@@ -65,6 +66,8 @@ import android.telephony.SubscriptionInfo;
import android.telephony.TelephonyManager;
import android.util.ArraySet;
+import androidx.test.filters.SmallTest;
+
import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot;
import com.android.server.vcn.VcnContext;
import com.android.server.vcn.VcnNetworkProvider;
@@ -73,9 +76,12 @@ import com.android.server.vcn.routeselection.UnderlyingNetworkController.Network
import com.android.server.vcn.routeselection.UnderlyingNetworkController.UnderlyingNetworkControllerCallback;
import com.android.server.vcn.routeselection.UnderlyingNetworkController.UnderlyingNetworkListener;
import com.android.server.vcn.routeselection.UnderlyingNetworkEvaluator.NetworkEvaluatorCallback;
+import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRunner;
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;
@@ -89,6 +95,11 @@ import java.util.List;
import java.util.Set;
import java.util.UUID;
+// TODO: b/374174952 After B finalization, use Sdk36ModuleController to ensure VCN tests only run on
+// Android B/B+
+@RunWith(DevSdkIgnoreRunner.class)
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.VANILLA_ICE_CREAM)
+@SmallTest
public class UnderlyingNetworkControllerTest {
private static final ParcelUuid SUB_GROUP = new ParcelUuid(new UUID(0, 0));
private static final int INITIAL_SUB_ID_1 = 1;
diff --git a/tests/vcn/java/com/android/server/vcn/routeselection/UnderlyingNetworkEvaluatorTest.java b/tests/vcn/java/com/android/server/vcn/routeselection/UnderlyingNetworkEvaluatorTest.java
index a315b0690ec5..27c1bc105bde 100644
--- a/tests/vcn/java/com/android/server/vcn/routeselection/UnderlyingNetworkEvaluatorTest.java
+++ b/tests/vcn/java/com/android/server/vcn/routeselection/UnderlyingNetworkEvaluatorTest.java
@@ -38,19 +38,30 @@ import static org.mockito.Mockito.when;
import android.net.IpSecTransform;
import android.net.vcn.VcnGatewayConnectionConfig;
+import android.os.Build;
+
+import androidx.test.filters.SmallTest;
import com.android.server.vcn.routeselection.NetworkMetricMonitor.NetworkMetricMonitorCallback;
import com.android.server.vcn.routeselection.UnderlyingNetworkEvaluator.Dependencies;
import com.android.server.vcn.routeselection.UnderlyingNetworkEvaluator.NetworkEvaluatorCallback;
+import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRunner;
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 java.util.concurrent.TimeUnit;
+// TODO: b/374174952 After B finalization, use Sdk36ModuleController to ensure VCN tests only run on
+// Android B/B+
+@RunWith(DevSdkIgnoreRunner.class)
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.VANILLA_ICE_CREAM)
+@SmallTest
public class UnderlyingNetworkEvaluatorTest extends NetworkEvaluationTestBase {
private static final int PENALTY_TIMEOUT_MIN = 10;
private static final long PENALTY_TIMEOUT_MS = TimeUnit.MINUTES.toMillis(PENALTY_TIMEOUT_MIN);
diff --git a/tools/aapt2/Debug.cpp b/tools/aapt2/Debug.cpp
index e24fe07f959b..9ef8b7dc9947 100644
--- a/tools/aapt2/Debug.cpp
+++ b/tools/aapt2/Debug.cpp
@@ -349,20 +349,22 @@ void Debug::PrintTable(const ResourceTable& table, const DebugPrintTableOptions&
value->value->Accept(&body_printer);
printer->Undent();
}
- printer->Println("Flag disabled values:");
- for (const auto& value : entry.flag_disabled_values) {
- printer->Print("(");
- printer->Print(value->config.to_string());
- printer->Print(") ");
- value->value->Accept(&headline_printer);
- if (options.show_sources && !value->value->GetSource().path.empty()) {
- printer->Print(" src=");
- printer->Print(value->value->GetSource().to_string());
+ if (!entry.flag_disabled_values.empty()) {
+ printer->Println("Flag disabled values:");
+ for (const auto& value : entry.flag_disabled_values) {
+ printer->Print("(");
+ printer->Print(value->config.to_string());
+ printer->Print(") ");
+ value->value->Accept(&headline_printer);
+ if (options.show_sources && !value->value->GetSource().path.empty()) {
+ printer->Print(" src=");
+ printer->Print(value->value->GetSource().to_string());
+ }
+ printer->Println();
+ printer->Indent();
+ value->value->Accept(&body_printer);
+ printer->Undent();
}
- printer->Println();
- printer->Indent();
- value->value->Accept(&body_printer);
- printer->Undent();
}
printer->Undent();
}