summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--apct-tests/perftests/core/src/android/libcore/regression/ExpensiveObjectsPerfTest.java22
-rw-r--r--apex/jobscheduler/service/aconfig/job.aconfig12
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java6
-rw-r--r--cmds/svc/src/com/android/commands/svc/OWNERS2
-rw-r--r--core/api/current.txt48
-rw-r--r--core/api/system-current.txt8
-rw-r--r--core/java/android/app/ActivityThread.java2
-rw-r--r--core/java/android/app/INotificationManager.aidl1
-rw-r--r--core/java/android/app/contextualsearch/ContextualSearchManager.java121
-rw-r--r--core/java/android/app/contextualsearch/IContextualSearchManager.aidl7
-rw-r--r--core/java/android/app/contextualsearch/flags.aconfig8
-rw-r--r--core/java/android/app/notification.aconfig10
-rw-r--r--core/java/android/appwidget/flags.aconfig10
-rw-r--r--core/java/android/content/AttributionSource.java9
-rw-r--r--core/java/android/content/Context.java2
-rw-r--r--core/java/android/content/Intent.java2
-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/hardware/contexthub/HubEndpointSession.java27
-rw-r--r--core/java/android/os/UserManager.java11
-rw-r--r--core/java/android/os/vibrator/VibratorFrequencyProfile.java22
-rw-r--r--core/java/android/permission/flags.aconfig7
-rw-r--r--core/java/android/security/FileIntegrityManager.java8
-rw-r--r--core/java/android/security/IFileIntegrityService.aidl2
-rw-r--r--core/java/android/security/flags.aconfig10
-rw-r--r--core/java/android/security/intrusiondetection/IntrusionDetectionEvent.java8
-rw-r--r--core/java/android/text/Layout.java8
-rw-r--r--core/java/android/view/ViewRootImpl.java16
-rw-r--r--core/java/android/view/WindowManager.java10
-rw-r--r--core/java/android/window/DesktopModeFlags.java2
-rw-r--r--core/java/android/window/TaskFragmentOperation.java88
-rw-r--r--core/java/android/window/TransitionInfo.java4
-rw-r--r--core/java/android/window/flags/lse_desktop_experience.aconfig27
-rw-r--r--core/java/com/android/internal/jank/Cuj.java2
-rw-r--r--core/java/com/android/internal/pm/pkg/component/AconfigFlags.java15
-rw-r--r--core/java/com/android/internal/protolog/IProtoLogConfigurationService.aidl12
-rw-r--r--core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java16
-rw-r--r--core/java/com/android/internal/protolog/ProcessedPerfettoProtoLogImpl.java7
-rw-r--r--core/java/com/android/internal/protolog/ProtoLogConfigurationServiceImpl.java74
-rw-r--r--core/java/com/android/internal/protolog/UnprocessedPerfettoProtoLogImpl.java2
-rw-r--r--core/java/com/android/internal/security/TEST_MAPPING4
-rw-r--r--core/java/com/android/internal/security/VerityUtils.java3
-rw-r--r--core/java/com/android/internal/widget/NotificationProgressBar.java7
-rw-r--r--core/java/com/android/internal/widget/remotecompose/OWNERS1
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/CoreDocument.java35
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/Operations.java9
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/PaintContext.java7
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/RemoteComposeBuffer.java41
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/RemoteContext.java6
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/ColorExpression.java36
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/DataListFloat.java9
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/DataListIds.java9
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase2.java2
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase3.java6
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase4.java8
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase6.java12
-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/DrawTextAnchored.java16
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/DrawTextOnPath.java14
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/FloatConstant.java27
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/FloatExpression.java15
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/Header.java365
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/IntegerExpression.java15
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/NamedVariable.java28
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/PaintData.java9
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/PathAppend.java12
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/PathCreate.java12
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/PathTween.java14
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/RootContentDescription.java9
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/ShaderData.java15
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/TextAttribute.java34
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/TextData.java9
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/TextFromFloat.java15
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/TextLookup.java13
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/TextLookupInt.java13
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/TextMerge.java13
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/TouchExpression.java17
-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/ImpulseProcess.java10
-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/LoopOperation.java18
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/TextLayout.java6
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/BackgroundModifierOperation.java2
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/BorderModifierOperation.java2
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/DrawContentOperation.java125
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/RippleModifierOperation.java2
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/paint/PaintBundle.java207
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/FloatAnimation.java15
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/serialize/MapSerializer.java34
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/serialize/SerializeTags.java1
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/types/BooleanConstant.java11
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/types/IntegerConstant.java15
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/types/LongConstant.java15
-rw-r--r--core/java/com/android/internal/widget/remotecompose/player/RemoteComposePlayer.java52
-rw-r--r--core/java/com/android/internal/widget/remotecompose/player/platform/AndroidPaintContext.java6
-rw-r--r--core/java/com/android/internal/widget/remotecompose/player/platform/RemoteComposeCanvas.java7
-rw-r--r--core/jni/android_content_res_ApkAssets.cpp108
-rw-r--r--core/jni/android_media_AudioRecord.cpp2
-rw-r--r--core/jni/android_media_AudioTrack.cpp2
-rw-r--r--core/proto/android/nfc/OWNERS2
-rw-r--r--core/res/Android.bp1
-rw-r--r--core/res/AndroidManifest.xml186
-rw-r--r--core/res/res/layout/notification_2025_template_header.xml26
-rw-r--r--core/res/res/layout/notification_template_header.xml24
-rw-r--r--core/res/res/values/attrs.xml1
-rw-r--r--core/res/res/values/config.xml2
-rw-r--r--core/res/res/values/public-final.xml4
-rw-r--r--core/res/res/values/strings.xml49
-rw-r--r--core/res/res/values/symbols.xml3
-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--core/tests/coretests/src/com/android/internal/util/RateLimitingCacheTest.java65
-rw-r--r--data/etc/platform.xml1
-rwxr-xr-xdata/fonts/script/test/test_commandline.py14
-rwxr-xr-xdata/fonts/script/test/test_xml_builder.py20
-rw-r--r--graphics/java/android/graphics/HardwareRenderer.java12
-rw-r--r--libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleTaskViewListenerTest.kt491
-rw-r--r--libs/WindowManager/Shell/res/values/dimen.xml11
-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.xml9
-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/DragZoneFactory.kt90
-rw-r--r--libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java41
-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/BubbleTaskViewHelper.java292
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewListener.java279
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTransitions.java84
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java23
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipDesktopState.java6
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvWMShellModule.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java19
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java5
-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/DesktopModeVisualIndicator.java405
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt87
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt28
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandler.java10
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/VisualIndicatorViewContainer.kt491
-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/pip2/phone/PipDragToResizeHandler.java218
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipPinchToResizeHandler.java129
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipResizeGestureHandler.java196
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentsAnimationRunner.aidl5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java9
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java8
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java50
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleImageButton.kt41
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositioner.kt8
-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.java1
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/SnapEventHandler.kt43
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleTransitionsTest.java11
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandlerTest.kt141
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicatorTest.kt16
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt129
-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/VisualIndicatorViewContainerTest.kt250
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentsTransitionHandlerTest.java26
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatusTest.kt27
-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/MultiDisplayVeiledResizeTaskPositionerTest.kt19
-rw-r--r--libs/androidfw/ApkAssets.cpp9
-rw-r--r--libs/androidfw/AssetsProvider.cpp91
-rw-r--r--libs/androidfw/Idmap.cpp21
-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.aconfig10
-rw-r--r--libs/hwui/jni/android_graphics_HardwareRenderer.cpp16
-rw-r--r--media/java/android/media/MediaRoute2ProviderService.java167
-rw-r--r--media/jni/android_media_MediaPlayer.cpp2
-rw-r--r--media/jni/android_media_MediaRecorder.cpp2
-rw-r--r--nfc-extras/OWNERS2
-rw-r--r--nfc-non-updatable/java/android/nfc/cardemulation/ApduServiceInfo.java31
-rw-r--r--omapi/OWNERS2
-rw-r--r--omapi/java/android/se/OWNERS2
-rw-r--r--omapi/java/android/se/omapi/OWNERS2
-rw-r--r--packages/SettingsLib/IntroPreference/res/layout/settingslib_expressive_preference_intro.xml5
-rw-r--r--packages/SettingsLib/IntroPreference/src/com/android/settingslib/widget/IntroPreference.kt31
-rw-r--r--packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBindings.kt6
-rw-r--r--packages/SettingsLib/SettingsTheme/src/com/android/settingslib/widget/CollapsableTextView.kt47
-rw-r--r--packages/SettingsLib/res/values/strings.xml27
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java97
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/utils/CustomDialogHelper.java47
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java86
-rw-r--r--packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java14
-rw-r--r--packages/SettingsProvider/src/com/android/providers/settings/WritableNamespacePrefixes.java1
-rw-r--r--packages/SettingsProvider/test/src/com/android/providers/settings/SettingsHelperRestoreTest.java5
-rw-r--r--packages/SystemUI/Android.bp25
-rw-r--r--packages/SystemUI/aconfig/systemui.aconfig44
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/Icon.kt11
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenContent.kt10
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt45
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/MediaCarouselSection.kt7
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt36
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/TopAreaSection.kt3
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt17
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/common/ui/view/SeekBarWithIconButtonsViewTest.java25
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalBackActionInteractorTest.kt61
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorTest.kt13
-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/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractorTest.kt97
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorTest.kt68
-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.kt7
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt7
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt2
-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/KeyguardMediaViewModelTest.kt17
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelTest.kt155
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java4
-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/qs/tiles/InternetTileNewImplTest.kt26
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java10
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/InternetTileMapperTest.kt6
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileUserActionInteractorTest.kt24
-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/system/QuickStepContractTest.kt3
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/domain/interactor/SingleNotificationChipInteractorTest.kt22
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/domain/interactor/StatusBarNotificationChipsInteractorTest.kt79
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/TimeRemainingStateTest.kt237
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/ColorizedFgsCoordinatorTest.java209
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/ColorizedFgsCoordinatorTest.kt191
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractorTest.kt30
-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/PromotedNotificationInfoTest.java11
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/shared/TestActiveNotificationModel.kt7
-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/phone/StatusBarKeyguardViewManagerTest.java11
-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/window/ui/viewmodel/WindowRootViewModelTest.kt7
-rw-r--r--packages/SystemUI/res-keyguard/drawable/pin_bouncer_confirm.xml30
-rw-r--r--packages/SystemUI/res-keyguard/drawable/pin_bouncer_delete.xml25
-rw-r--r--packages/SystemUI/res/layout/battery_status_chip.xml12
-rw-r--r--packages/SystemUI/res/layout/bindable_status_bar_compose_icon.xml33
-rw-r--r--packages/SystemUI/res/layout/magic_action_button.xml3
-rw-r--r--packages/SystemUI/res/layout/media_session_view.xml2
-rw-r--r--packages/SystemUI/res/layout/promoted_notification_info.xml36
-rw-r--r--packages/SystemUI/res/layout/status_bar_event_chip_compose.xml34
-rw-r--r--packages/SystemUI/res/layout/volume_dialog.xml38
-rw-r--r--packages/SystemUI/res/layout/volume_dialog_bottom_section.xml26
-rw-r--r--packages/SystemUI/res/layout/volume_dialog_top_section.xml (renamed from packages/SystemUI/res/layout/volume_ringer_drawer.xml)5
-rw-r--r--packages/SystemUI/res/values/dimens.xml27
-rw-r--r--packages/SystemUI/res/values/strings.xml8
-rw-r--r--packages/SystemUI/res/values/styles.xml13
-rw-r--r--packages/SystemUI/res/xml/media_session_collapsed.xml7
-rw-r--r--packages/SystemUI/res/xml/volume_dialog_constraint_set.xml2
-rw-r--r--packages/SystemUI/res/xml/volume_dialog_half_folded_constraint_set.xml2
-rw-r--r--packages/SystemUI/res/xml/volume_dialog_ringer_drawer_motion_scene.xml29
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java5
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationListener.java3
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java7
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardViewController.java5
-rw-r--r--packages/SystemUI/src/com/android/keyguard/NumPadAnimator.java45
-rw-r--r--packages/SystemUI/src/com/android/keyguard/NumPadKey.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/CameraAvailabilityListener.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/FaceScanningOverlay.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/ScreenDecorations.java22
-rw-r--r--packages/SystemUI/src/com/android/systemui/SwipeHelper.java16
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/FullscreenMagnificationController.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java15
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/extradim/ExtraDimDialogManager.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuController.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/fontscaling/FontScalingDialogDelegate.kt14
-rw-r--r--packages/SystemUI/src/com/android/systemui/back/domain/interactor/BackActionInteractor.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/BiometricNotificationService.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothDetailsContent.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothDetailsContentManager.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothDetailsContentViewModel.kt (renamed from packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModel.kt)9
-rw-r--r--packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothDetailsViewModel.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegate.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/bouncer/shared/constants/KeyguardBouncerConstants.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/common/ui/view/SeekBarWithIconButtonsView.java13
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/CommunalBackupRestoreStartable.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalBackActionInteractor.kt56
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneTransitionInteractor.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/decor/ScreenDecorCommand.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/homecontrols/service/TaskFragmentComponent.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java234
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt13
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt53
-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/FromPrimaryBouncerTransitionInteractor.kt48
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractor.kt108
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt29
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/LightRevealScrimViewBinder.kt1
-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/keyguard/ui/viewmodel/KeyguardMediaViewModel.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt73
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt156
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewController.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java162
-rw-r--r--packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogV2.kt113
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java12
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTileNewImpl.kt13
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java22
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/ScreenRecordDetailsContent.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/InternetTileMapper.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileUserActionInteractor.kt15
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt14
-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.kt34
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/scroll/ImageTileSet.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/BaseShadeControllerImpl.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java10
-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/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/statusbar/BatteryStatusChip.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt25
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/interactor/SingleNotificationChipInteractor.kt21
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/interactor/StatusBarNotificationChipsInteractor.kt15
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/model/NotificationChipModel.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModel.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/ChipContent.kt25
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/OngoingActivityChip.kt76
-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/chips/ui/viewmodel/TimeRemainingState.kt122
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/events/StatusEvent.kt23
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/events/ui/view/BatteryStatusEventComposeChip.kt100
-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/icon/IconManager.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractor.kt23
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/AODPromotedNotification.kt55
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/domain/interactor/AODPromotedNotificationInteractor.kt13
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/MagicActionBackgroundDrawable.kt265
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/MagicActionButton.kt36
-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/PromotedNotificationInfo.java36
-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/row/wrapper/NotificationHeaderViewWrapper.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MagneticNotificationRowManagerImpl.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java99
-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/NotificationSwipeHelper.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationScrollView.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt36
-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/BiometricUnlockController.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java48
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java13
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/battery/domain/interactor/BatteryInteractor.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/icons/shared/BindableIconsRegistry.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt19
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileUiAdapter.kt26
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileViewLogger.kt16
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/StackedMobileBindableIcon.kt52
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/StackedMobileIconBinder.kt65
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/StackedMobileIconViewModel.kt90
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/HomeStatusBarViewBinder.kt20
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/composable/StackedMobileIcon.kt180
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/composable/StatusBarRoot.kt19
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/view/SingleBindableStatusBarComposeIconView.kt155
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java37
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/concurrency/GlobalConcurrencyModule.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/concurrency/SysUIConcurrencyModule.kt13
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/module/VolumeDialogModule.kt15
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/binder/VolumeDialogRingerViewBinder.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/settings/ui/binder/VolumeDialogSettingsButtonViewBinder.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSlidersViewBinder.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/ui/binder/ViewBinder.kt25
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/ui/binder/VolumeDialogViewBinder.kt108
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/ui/utils/JankListenerFactory.kt39
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/ui/utils/SuspendAnimators.kt30
-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/window/data/repository/NoopWindowRootViewBlurRepository.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/window/data/repository/WindowRootViewBlurRepository.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/window/domain/interactor/WindowRootViewBlurInteractor.kt22
-rw-r--r--packages/SystemUI/src/com/android/systemui/window/ui/WindowRootViewBinder.kt13
-rw-r--r--packages/SystemUI/src/com/android/systemui/window/ui/viewmodel/WindowRootViewModel.kt36
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationSettingsTest.java13
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothDetailsContentManagerTest.kt8
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothDetailsContentViewModelTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModelTest.kt)34
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegateTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java166
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BluetoothTileTest.kt12
-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/phone/ScrimControllerTest.java104
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalSceneTransitionInteractorKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractorKosmos.kt4
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractorKosmos.kt6
-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/keyguard/ui/viewmodel/KeyguardMediaViewModelFactoryKosmos.kt7
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt4
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelKosmos.kt4
-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/domain/interactor/ShadeDisplaysInteractorKosmos.kt5
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/EntryUtil.kt3
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractorKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/promoted/domain/interactor/AODPromotedNotificationInteractorKosmos.kt (renamed from packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalBackActionInteractorKosmos.kt)16
-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/mobile/domain/interactor/FakeMobileIconsInteractor.kt3
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/ui/binder/VolumeDialogViewBinderKosmos.kt10
-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/window/data/repository/WindowRootViewBlurRepositoryKosmos.kt1
-rw-r--r--services/accessibility/accessibility.aconfig7
-rw-r--r--services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickController.java5
-rw-r--r--services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickTypePanel.java4
-rw-r--r--services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java5
-rw-r--r--services/contextualsearch/java/com/android/server/contextualsearch/ContextualSearchManagerService.java77
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerService.java8
-rw-r--r--services/core/java/com/android/server/am/OomAdjuster.java7
-rw-r--r--services/core/java/com/android/server/am/UserController.java38
-rw-r--r--services/core/java/com/android/server/am/flags.aconfig18
-rw-r--r--services/core/java/com/android/server/appbinding/AppBindingConstants.java13
-rw-r--r--services/core/java/com/android/server/audio/AudioDeviceBroker.java40
-rw-r--r--services/core/java/com/android/server/audio/AudioDeviceInventory.java11
-rw-r--r--services/core/java/com/android/server/audio/BtHelper.java23
-rw-r--r--services/core/java/com/android/server/backup/SystemBackupAgent.java8
-rw-r--r--services/core/java/com/android/server/backup/WearBackupHelper.java49
-rw-r--r--services/core/java/com/android/server/backup/WearBackupInternal.java32
-rw-r--r--services/core/java/com/android/server/display/DisplayDevice.java29
-rw-r--r--services/core/java/com/android/server/display/DisplayManagerService.java1
-rw-r--r--services/core/java/com/android/server/display/LocalDisplayAdapter.java14
-rw-r--r--services/core/java/com/android/server/display/LogicalDisplay.java13
-rw-r--r--services/core/java/com/android/server/display/LogicalDisplayMapper.java3
-rw-r--r--services/core/java/com/android/server/display/OverlayDisplayAdapter.java2
-rw-r--r--services/core/java/com/android/server/display/VirtualDisplayAdapter.java15
-rw-r--r--services/core/java/com/android/server/display/WifiDisplayAdapter.java2
-rw-r--r--services/core/java/com/android/server/display/feature/DisplayManagerFlags.java10
-rw-r--r--services/core/java/com/android/server/dreams/DreamManagerService.java3
-rw-r--r--services/core/java/com/android/server/flags/people.aconfig12
-rw-r--r--services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java3
-rw-r--r--services/core/java/com/android/server/inputmethod/InputMethodManagerService.java23
-rw-r--r--services/core/java/com/android/server/media/MediaRoute2ProviderWatcher.java3
-rw-r--r--services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java26
-rw-r--r--services/core/java/com/android/server/media/quality/MediaQualityService.java83
-rw-r--r--services/core/java/com/android/server/notification/ManagedServices.java293
-rw-r--r--services/core/java/com/android/server/notification/NotificationManagerService.java61
-rw-r--r--services/core/java/com/android/server/notification/PreferencesHelper.java23
-rw-r--r--services/core/java/com/android/server/notification/flags.aconfig7
-rw-r--r--services/core/java/com/android/server/pm/InstallPackageHelper.java29
-rw-r--r--services/core/java/com/android/server/pm/InstallingSession.java24
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerService.java4
-rw-r--r--services/core/java/com/android/server/policy/PhoneWindowManager.java35
-rw-r--r--services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java13
-rw-r--r--services/core/java/com/android/server/resources/ResourcesManagerShellCommand.java1
-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/security/intrusiondetection/DataAggregator.java2
-rw-r--r--services/core/java/com/android/server/security/intrusiondetection/NetworkLogSource.java4
-rw-r--r--services/core/java/com/android/server/security/intrusiondetection/SecurityLogSource.java15
-rw-r--r--services/core/java/com/android/server/stats/pull/StatsPullAtomService.java14
-rw-r--r--services/core/java/com/android/server/updates/CertPinInstallReceiver.java10
-rw-r--r--services/core/java/com/android/server/wearable/WearableSensingManagerPerUserService.java3
-rw-r--r--services/core/java/com/android/server/wearable/WearableSensingSecureChannel.java4
-rw-r--r--services/core/java/com/android/server/wm/ActivityRecord.java230
-rw-r--r--services/core/java/com/android/server/wm/AppCompatController.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.java29
-rw-r--r--services/core/java/com/android/server/wm/BackNavigationController.java4
-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/DisplayContent.java54
-rw-r--r--services/core/java/com/android/server/wm/DragState.java55
-rw-r--r--services/core/java/com/android/server/wm/KeyguardController.java24
-rw-r--r--services/core/java/com/android/server/wm/NonAppWindowAnimationAdapter.java227
-rw-r--r--services/core/java/com/android/server/wm/RemoteAnimationController.java636
-rw-r--r--services/core/java/com/android/server/wm/TaskDisplayArea.java8
-rw-r--r--services/core/java/com/android/server/wm/TaskFragment.java13
-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/WallpaperAnimationAdapter.java191
-rw-r--r--services/core/java/com/android/server/wm/WallpaperController.java25
-rw-r--r--services/core/java/com/android/server/wm/WallpaperWindowToken.java6
-rw-r--r--services/core/java/com/android/server/wm/WindowContainer.java66
-rw-r--r--services/core/java/com/android/server/wm/WindowContainerThumbnail.java218
-rw-r--r--services/core/java/com/android/server/wm/WindowOrganizerController.java51
-rw-r--r--services/people/java/com/android/server/people/PeopleService.java14
-rw-r--r--services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/AndroidManifest.xml4
-rw-r--r--services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/InputMethodServiceTest.java414
-rw-r--r--services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java5
-rw-r--r--services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/res/xml/method.xml11
-rw-r--r--services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/src/com/android/apps/inputmethod/simpleime/SimpleKeyboardView.java10
-rw-r--r--services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/src/com/android/apps/inputmethod/simpleime/ims/InputMethodServiceWrapper.java67
-rw-r--r--services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/src/com/android/apps/inputmethod/simpleime/testing/TestActivity.java51
-rw-r--r--services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageParserTest.java43
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceTest.java67
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java4
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayTest.java30
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java86
-rw-r--r--services/tests/powerservicetests/src/com/android/server/power/NotifierTest.java36
-rw-r--r--services/tests/security/intrusiondetection/src/com/android/server/security/intrusiondetection/IntrusionDetectionServiceTest.java178
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickControllerTest.java74
-rw-r--r--services/tests/servicestests/src/com/android/server/am/UserControllerTest.java19
-rw-r--r--services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java6
-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/com/android/server/UiServiceTestCase.java4
-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.java198
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java61
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/DesktopModeHelperTest.java29
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java757
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java43
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java22
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowContainerThumbnailTest.java65
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java1
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowTracingPerfettoTest.java39
-rw-r--r--tests/AttestationVerificationTest/src/com/android/server/security/CertificateRevocationStatusManagerTest.java66
-rw-r--r--tests/Input/src/com/android/server/input/InputManagerServiceTests.kt30
-rw-r--r--tests/Tracing/src/com/android/internal/protolog/ProtoLogConfigurationServiceTest.java58
-rw-r--r--tests/testables/tests/src/android/animation/AnimatorTestRuleToolkitTest.kt21
-rw-r--r--tools/aapt2/cmd/Diff.cpp183
-rw-r--r--tools/aapt2/cmd/Diff.h15
557 files changed, 13472 insertions, 7541 deletions
diff --git a/apct-tests/perftests/core/src/android/libcore/regression/ExpensiveObjectsPerfTest.java b/apct-tests/perftests/core/src/android/libcore/regression/ExpensiveObjectsPerfTest.java
index ecbfc7169945..10ec2bfcb49a 100644
--- a/apct-tests/perftests/core/src/android/libcore/regression/ExpensiveObjectsPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/regression/ExpensiveObjectsPerfTest.java
@@ -75,8 +75,19 @@ public class ExpensiveObjectsPerfTest {
@Test(timeout = 900000)
public void timeNewCollator() {
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ int i = 0;
while (state.keepRunning()) {
Collator.getInstance(Locale.US);
+
+ if (++i % 1000 == 0) {
+ state.pauseTiming();
+ // GC and finalize occasionally to avoid GC for alloc and/or
+ // blocking on finalization during benchmark time.
+ // See: b/394961590
+ System.gc();
+ System.runFinalization();
+ state.resumeTiming();
+ }
}
}
@@ -84,8 +95,19 @@ public class ExpensiveObjectsPerfTest {
public void timeClonedCollator() {
Collator c = Collator.getInstance(Locale.US);
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ int i = 0;
while (state.keepRunning()) {
c.clone();
+
+ if (++i % 1000 == 0) {
+ state.pauseTiming();
+ // GC and finalize occasionally to avoid GC for alloc and/or
+ // blocking on finalization during benchmark time.
+ // See: b/394961590
+ System.gc();
+ System.runFinalization();
+ state.resumeTiming();
+ }
}
}
diff --git a/apex/jobscheduler/service/aconfig/job.aconfig b/apex/jobscheduler/service/aconfig/job.aconfig
index 86ed06bf4e3d..29df80fda33d 100644
--- a/apex/jobscheduler/service/aconfig/job.aconfig
+++ b/apex/jobscheduler/service/aconfig/job.aconfig
@@ -105,4 +105,14 @@ flag {
metadata {
purpose: PURPOSE_BUGFIX
}
-} \ No newline at end of file
+}
+
+flag {
+ name: "include_trace_tag_in_job_name"
+ namespace: "backstage_power"
+ description: "Add the trace tag to the job name"
+ bug: "354795473"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
index aaf69864fe97..2d069f934d0d 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
@@ -674,6 +674,12 @@ public final class JobStatus {
this.job = job;
StringBuilder batteryName = new StringBuilder();
+ if (com.android.server.job.Flags.includeTraceTagInJobName()) {
+ final String filteredTraceTag = this.getFilteredTraceTag();
+ if (filteredTraceTag != null) {
+ batteryName.append("#").append(filteredTraceTag).append("#");
+ }
+ }
if (namespace != null) {
batteryName.append("@").append(namespace).append("@");
}
diff --git a/cmds/svc/src/com/android/commands/svc/OWNERS b/cmds/svc/src/com/android/commands/svc/OWNERS
index d5a5d7b3b858..a901dfdeddfb 100644
--- a/cmds/svc/src/com/android/commands/svc/OWNERS
+++ b/cmds/svc/src/com/android/commands/svc/OWNERS
@@ -1,2 +1,2 @@
# Bug component: 48448
-per-file NfcCommand.java = file:platform/packages/apps/Nfc:/OWNERS
+per-file NfcCommand.java = file:platform/packages/modules/Nfc:/OWNERS
diff --git a/core/api/current.txt b/core/api/current.txt
index d4ed533cad9e..e1c26adb2275 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 {
@@ -9188,6 +9197,14 @@ package android.app.blob {
}
+package android.app.contextualsearch {
+
+ @FlaggedApi("android.app.contextualsearch.flags.self_invocation") public final class ContextualSearchManager {
+ method @FlaggedApi("android.app.contextualsearch.flags.self_invocation") public void startContextualSearch();
+ }
+
+}
+
package android.app.jank {
@FlaggedApi("android.app.jank.detailed_app_jank_metrics_api") public final class AppJankStats {
@@ -25039,8 +25056,10 @@ package android.media {
method public final void notifySessionCreated(long, @NonNull android.media.RoutingSessionInfo);
method public final void notifySessionReleased(@NonNull String);
method public final void notifySessionUpdated(@NonNull android.media.RoutingSessionInfo);
+ method @FlaggedApi("com.android.media.flags.enable_mirroring_in_media_router_2") @Nullable @RequiresPermission("android.permission.MODIFY_AUDIO_ROUTING") public final android.media.MediaRoute2ProviderService.MediaStreams notifySystemRoutingSessionCreated(long, @NonNull android.media.RoutingSessionInfo, @NonNull android.media.MediaRoute2ProviderService.MediaStreamsFormats);
method @CallSuper @Nullable public android.os.IBinder onBind(@NonNull android.content.Intent);
method public abstract void onCreateSession(long, @NonNull String, @NonNull String, @Nullable android.os.Bundle);
+ method @FlaggedApi("com.android.media.flags.enable_mirroring_in_media_router_2") public void onCreateSystemRoutingSession(long, @NonNull String, @NonNull android.media.MediaRoute2ProviderService.SystemRoutingSessionParams);
method public abstract void onDeselectRoute(long, @NonNull String, @NonNull String);
method public void onDiscoveryPreferenceChanged(@NonNull android.media.RouteDiscoveryPreference);
method public abstract void onReleaseSession(long, @NonNull String);
@@ -25048,15 +25067,44 @@ package android.media {
method public abstract void onSetRouteVolume(long, @NonNull String, int);
method public abstract void onSetSessionVolume(long, @NonNull String, int);
method public abstract void onTransferToRoute(long, @NonNull String, @NonNull String);
+ field @FlaggedApi("com.android.media.flags.enable_mirroring_in_media_router_2") public static final String CATEGORY_SYSTEM_MEDIA = "android.media.MediaRoute2ProviderService.SYSTEM_MEDIA";
+ field @FlaggedApi("com.android.media.flags.enable_mirroring_in_media_router_2") public static final int REASON_FAILED_TO_REROUTE_SYSTEM_MEDIA = 6; // 0x6
field public static final int REASON_INVALID_COMMAND = 4; // 0x4
field public static final int REASON_NETWORK_ERROR = 2; // 0x2
field public static final int REASON_REJECTED = 1; // 0x1
field public static final int REASON_ROUTE_NOT_AVAILABLE = 3; // 0x3
+ field @FlaggedApi("com.android.media.flags.enable_mirroring_in_media_router_2") public static final int REASON_UNIMPLEMENTED = 5; // 0x5
field public static final int REASON_UNKNOWN_ERROR = 0; // 0x0
field public static final long REQUEST_ID_NONE = 0L; // 0x0L
field public static final String SERVICE_INTERFACE = "android.media.MediaRoute2ProviderService";
}
+ @FlaggedApi("com.android.media.flags.enable_mirroring_in_media_router_2") public static final class MediaRoute2ProviderService.MediaStreams {
+ method @Nullable public android.media.AudioRecord getAudioRecord();
+ }
+
+ @FlaggedApi("com.android.media.flags.enable_mirroring_in_media_router_2") public static final class MediaRoute2ProviderService.MediaStreamsFormats {
+ method @FlaggedApi("com.android.media.flags.enable_mirroring_in_media_router_2") @Nullable public android.media.AudioFormat getAudioFormat();
+ }
+
+ @FlaggedApi("com.android.media.flags.enable_mirroring_in_media_router_2") public static final class MediaRoute2ProviderService.MediaStreamsFormats.Builder {
+ ctor public MediaRoute2ProviderService.MediaStreamsFormats.Builder();
+ method @NonNull public android.media.MediaRoute2ProviderService.MediaStreamsFormats build();
+ method @NonNull public android.media.MediaRoute2ProviderService.MediaStreamsFormats.Builder setAudioFormat(@NonNull android.media.AudioFormat);
+ }
+
+ @FlaggedApi("com.android.media.flags.enable_mirroring_in_media_router_2") public static final class MediaRoute2ProviderService.SystemRoutingSessionParams {
+ method @FlaggedApi("com.android.media.flags.enable_mirroring_in_media_router_2") @NonNull public android.os.Bundle getExtras();
+ method @FlaggedApi("com.android.media.flags.enable_mirroring_in_media_router_2") @NonNull public String getPackageName();
+ }
+
+ public static final class MediaRoute2ProviderService.SystemRoutingSessionParams.Builder {
+ ctor public MediaRoute2ProviderService.SystemRoutingSessionParams.Builder();
+ method @NonNull public android.media.MediaRoute2ProviderService.SystemRoutingSessionParams build();
+ method @NonNull public android.media.MediaRoute2ProviderService.SystemRoutingSessionParams.Builder setExtras(@NonNull android.os.Bundle);
+ method @NonNull public android.media.MediaRoute2ProviderService.SystemRoutingSessionParams.Builder setPackageName(@NonNull String);
+ }
+
public class MediaRouter {
method public void addCallback(int, android.media.MediaRouter.Callback);
method public void addCallback(int, android.media.MediaRouter.Callback, int);
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 76cce7439454..03607d45eabb 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -7,7 +7,7 @@ package android {
field public static final String ACCESS_BROADCAST_RADIO = "android.permission.ACCESS_BROADCAST_RADIO";
field public static final String ACCESS_BROADCAST_RESPONSE_STATS = "android.permission.ACCESS_BROADCAST_RESPONSE_STATS";
field public static final String ACCESS_CACHE_FILESYSTEM = "android.permission.ACCESS_CACHE_FILESYSTEM";
- field @FlaggedApi("android.app.contextualsearch.flags.enable_service") public static final String ACCESS_CONTEXTUAL_SEARCH = "android.permission.ACCESS_CONTEXTUAL_SEARCH";
+ field public static final String ACCESS_CONTEXTUAL_SEARCH = "android.permission.ACCESS_CONTEXTUAL_SEARCH";
field public static final String ACCESS_CONTEXT_HUB = "android.permission.ACCESS_CONTEXT_HUB";
field public static final String ACCESS_DRM_CERTIFICATES = "android.permission.ACCESS_DRM_CERTIFICATES";
field @FlaggedApi("android.permission.flags.fine_power_monitor_permission") public static final String ACCESS_FINE_POWER_MONITORS = "android.permission.ACCESS_FINE_POWER_MONITORS";
@@ -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 {
@@ -2231,7 +2235,7 @@ package android.app.contextualsearch {
field @NonNull public static final android.os.Parcelable.Creator<android.app.contextualsearch.CallbackToken> CREATOR;
}
- public final class ContextualSearchManager {
+ @FlaggedApi("android.app.contextualsearch.flags.self_invocation") public final class ContextualSearchManager {
method @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXTUAL_SEARCH) public void startContextualSearch(int);
field public static final String ACTION_LAUNCH_CONTEXTUAL_SEARCH = "android.app.contextualsearch.action.LAUNCH_CONTEXTUAL_SEARCH";
field public static final int ENTRYPOINT_LONG_PRESS_HOME = 2; // 0x2
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 2d7ed46fe64a..f63170aa159d 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;
@@ -5283,6 +5284,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/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl
index 6fedcbe50f30..b9255ecaf1b6 100644
--- a/core/java/android/app/INotificationManager.aidl
+++ b/core/java/android/app/INotificationManager.aidl
@@ -126,6 +126,7 @@ interface INotificationManager
boolean areChannelsBypassingDnd();
ParceledListSlice getNotificationChannelsBypassingDnd(String pkg, int uid);
ParceledListSlice getPackagesBypassingDnd(int userId);
+ List<String> getPackagesWithAnyChannels(int userId);
boolean isPackagePaused(String pkg);
void deleteNotificationHistoryItem(String pkg, int uid, long postedTime);
boolean isPermissionFixed(String pkg, int userId);
diff --git a/core/java/android/app/contextualsearch/ContextualSearchManager.java b/core/java/android/app/contextualsearch/ContextualSearchManager.java
index 2ce431dcb32d..4e5fa6bac951 100644
--- a/core/java/android/app/contextualsearch/ContextualSearchManager.java
+++ b/core/java/android/app/contextualsearch/ContextualSearchManager.java
@@ -32,6 +32,9 @@ import android.util.Log;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
/**
* {@link ContextualSearchManager} is a system service to facilitate contextual search experience on
@@ -39,10 +42,8 @@ import java.lang.annotation.RetentionPolicy;
* <p>
* This class lets a caller start contextual search by calling {@link #startContextualSearch}
* method.
- *
- * @hide
*/
-@SystemApi
+@FlaggedApi(Flags.FLAG_SELF_INVOCATION)
public final class ContextualSearchManager {
/**
@@ -50,7 +51,9 @@ public final class ContextualSearchManager {
* Only supposed to be used with ACTION_LAUNCH_CONTEXTUAL_SEARCH.
*
* @see #ACTION_LAUNCH_CONTEXTUAL_SEARCH
+ * @hide
*/
+ @SystemApi
public static final String EXTRA_ENTRYPOINT =
"android.app.contextualsearch.extra.ENTRYPOINT";
@@ -60,7 +63,9 @@ public final class ContextualSearchManager {
* Only supposed to be used with ACTION_LAUNCH_CONTEXTUAL_SEARCH.
*
* @see #ACTION_LAUNCH_CONTEXTUAL_SEARCH
+ * @hide
*/
+ @SystemApi
public static final String EXTRA_FLAG_SECURE_FOUND =
"android.app.contextualsearch.extra.FLAG_SECURE_FOUND";
@@ -69,7 +74,9 @@ public final class ContextualSearchManager {
* Only supposed to be used with ACTION_LAUNCH_CONTEXTUAL_SEARCH.
*
* @see #ACTION_LAUNCH_CONTEXTUAL_SEARCH
+ * @hide
*/
+ @SystemApi
public static final String EXTRA_SCREENSHOT =
"android.app.contextualsearch.extra.SCREENSHOT";
@@ -79,7 +86,9 @@ public final class ContextualSearchManager {
* Only supposed to be used with ACTION_LAUNCH_CONTEXTUAL_SEARCH.
*
* @see #ACTION_LAUNCH_CONTEXTUAL_SEARCH
+ * @hide
*/
+ @SystemApi
public static final String EXTRA_IS_MANAGED_PROFILE_VISIBLE =
"android.app.contextualsearch.extra.IS_MANAGED_PROFILE_VISIBLE";
@@ -89,7 +98,9 @@ public final class ContextualSearchManager {
* Only supposed to be used with ACTION_LAUNCH_CONTEXTUAL_SEARCH.
*
* @see #ACTION_LAUNCH_CONTEXTUAL_SEARCH
+ * @hide
*/
+ @SystemApi
public static final String EXTRA_VISIBLE_PACKAGE_NAMES =
"android.app.contextualsearch.extra.VISIBLE_PACKAGE_NAMES";
@@ -98,10 +109,9 @@ public final class ContextualSearchManager {
* {@link SystemClock#uptimeMillis()}.
* Only supposed to be used with ACTION_LAUNCH_CONTEXTUAL_SEARCH.
*
- * @see #ACTION_LAUNCH_CONTEXTUAL_SEARCH
- *
* TODO: un-hide in W
*
+ * @see #ACTION_LAUNCH_CONTEXTUAL_SEARCH
* @hide
*/
public static final String EXTRA_INVOCATION_TIME_MS =
@@ -113,7 +123,9 @@ public final class ContextualSearchManager {
* Only supposed to be used with ACTION_LAUNCH_CONTEXTUAL_SEARCH.
*
* @see #ACTION_LAUNCH_CONTEXTUAL_SEARCH
+ * @hide
*/
+ @SystemApi
public static final String EXTRA_TOKEN = "android.app.contextualsearch.extra.TOKEN";
/**
@@ -132,7 +144,10 @@ public final class ContextualSearchManager {
* experience must add this intent filter action to the activity it wants to be launched.
* <br>
* <b>Note</b> This activity must not be exported.
+ *
+ * @hide
*/
+ @SystemApi
public static final String ACTION_LAUNCH_CONTEXTUAL_SEARCH =
"android.app.contextualsearch.action.LAUNCH_CONTEXTUAL_SEARCH";
@@ -144,23 +159,63 @@ public final class ContextualSearchManager {
public static final String FEATURE_CONTEXTUAL_SEARCH =
"com.google.android.feature.CONTEXTUAL_SEARCH";
- /** Entrypoint to be used when a user long presses on the nav handle. */
+ /**
+ * Entrypoint to be used when a user long presses on the nav handle.
+ *
+ * @hide
+ */
+ @SystemApi
public static final int ENTRYPOINT_LONG_PRESS_NAV_HANDLE = 1;
- /** Entrypoint to be used when a user long presses on the home button. */
+
+ /** Entrypoint to be used when a user long presses on the home button.
+ *
+ * @hide
+ */
+ @SystemApi
public static final int ENTRYPOINT_LONG_PRESS_HOME = 2;
- /** Entrypoint to be used when a user long presses on the overview button. */
+
+ /** Entrypoint to be used when a user long presses on the overview button.
+ *
+ * @hide
+ */
+ @SystemApi
public static final int ENTRYPOINT_LONG_PRESS_OVERVIEW = 3;
- /** Entrypoint to be used when a user presses the action button in overview. */
+
+ /**
+ * Entrypoint to be used when a user presses the action button in overview.
+ *
+ * @hide
+ */
+ @SystemApi
public static final int ENTRYPOINT_OVERVIEW_ACTION = 4;
- /** Entrypoint to be used when a user presses the context menu button in overview. */
+
+ /**
+ * Entrypoint to be used when a user presses the context menu button in overview.
+ *
+ * @hide
+ */
+ @SystemApi
public static final int ENTRYPOINT_OVERVIEW_MENU = 5;
- /** Entrypoint to be used by system actions like TalkBack, Accessibility etc. */
+
+ /**
+ * Entrypoint to be used by system actions like TalkBack, Accessibility etc.
+ *
+ * @hide
+ */
+ @SystemApi
public static final int ENTRYPOINT_SYSTEM_ACTION = 9;
- /** Entrypoint to be used when a user long presses on the meta key. */
+
+ /**
+ * Entrypoint to be used when a user long presses on the meta key.
+ *
+ * @hide
+ */
+ @SystemApi
public static final int ENTRYPOINT_LONG_PRESS_META = 10;
+
/**
* The {@link Entrypoint} annotation is used to standardize the entrypoints supported by
- * {@link #startContextualSearch} method.
+ * {@link #startContextualSearch(int entrypoint)} method.
*
* @hide
*/
@@ -174,8 +229,18 @@ public final class ContextualSearchManager {
ENTRYPOINT_LONG_PRESS_META
})
@Retention(RetentionPolicy.SOURCE)
- public @interface Entrypoint {
- }
+ public @interface Entrypoint {}
+
+ private static final Set<Integer> VALID_ENTRYPOINT_VALUES = new HashSet<>(Arrays.asList(
+ ENTRYPOINT_LONG_PRESS_NAV_HANDLE,
+ ENTRYPOINT_LONG_PRESS_HOME,
+ ENTRYPOINT_LONG_PRESS_OVERVIEW,
+ ENTRYPOINT_OVERVIEW_ACTION,
+ ENTRYPOINT_OVERVIEW_MENU,
+ ENTRYPOINT_SYSTEM_ACTION,
+ ENTRYPOINT_LONG_PRESS_META
+ ));
+
private static final String TAG = ContextualSearchManager.class.getSimpleName();
private static final boolean DEBUG = false;
@@ -189,7 +254,7 @@ public final class ContextualSearchManager {
}
/**
- * Used to start contextual search.
+ * Used to start contextual search for a given system entrypoint.
* <p>
* When {@link #startContextualSearch} is called, the system server does the following:
* <ul>
@@ -202,9 +267,15 @@ public final class ContextualSearchManager {
* </p>
*
* @param entrypoint the invocation entrypoint
+ *
+ * @hide
*/
@RequiresPermission(ACCESS_CONTEXTUAL_SEARCH)
+ @SystemApi
public void startContextualSearch(@Entrypoint int entrypoint) {
+ if (!VALID_ENTRYPOINT_VALUES.contains(entrypoint)) {
+ throw new IllegalArgumentException("Invalid entrypoint: " + entrypoint);
+ }
if (DEBUG) Log.d(TAG, "startContextualSearch for entrypoint: " + entrypoint);
try {
mService.startContextualSearch(entrypoint);
@@ -213,4 +284,22 @@ public final class ContextualSearchManager {
e.rethrowFromSystemServer();
}
}
+
+ /**
+ * Used to start contextual search from within an app.
+ *
+ * <p>System apps should use the available System APIs rather than this method.
+ *
+ * @throws SecurityException if the caller does not have a foreground Activity.
+ */
+ @FlaggedApi(Flags.FLAG_SELF_INVOCATION)
+ public void startContextualSearch() {
+ if (DEBUG) Log.d(TAG, "startContextualSearch from app");
+ try {
+ mService.startContextualSearchForForegroundApp();
+ } catch (RemoteException e) {
+ if (DEBUG) Log.d(TAG, "Failed to startContextualSearch", e);
+ e.rethrowFromSystemServer();
+ }
+ }
}
diff --git a/core/java/android/app/contextualsearch/IContextualSearchManager.aidl b/core/java/android/app/contextualsearch/IContextualSearchManager.aidl
index 9b0b8b775971..8789daab3afe 100644
--- a/core/java/android/app/contextualsearch/IContextualSearchManager.aidl
+++ b/core/java/android/app/contextualsearch/IContextualSearchManager.aidl
@@ -4,7 +4,8 @@ import android.app.contextualsearch.IContextualSearchCallback;
/**
* @hide
*/
-oneway interface IContextualSearchManager {
- void startContextualSearch(int entrypoint);
- void getContextualSearchState(in IBinder token, in IContextualSearchCallback callback);
+interface IContextualSearchManager {
+ void startContextualSearchForForegroundApp();
+ oneway void startContextualSearch(int entrypoint);
+ oneway void getContextualSearchState(in IBinder token, in IContextualSearchCallback callback);
}
diff --git a/core/java/android/app/contextualsearch/flags.aconfig b/core/java/android/app/contextualsearch/flags.aconfig
index d81ec1e8b883..bc1f7cea7fce 100644
--- a/core/java/android/app/contextualsearch/flags.aconfig
+++ b/core/java/android/app/contextualsearch/flags.aconfig
@@ -39,3 +39,11 @@ flag {
description: "Add audio playing status to the contextual search invocation intent."
bug: "372935419"
}
+
+flag {
+ name: "self_invocation"
+ namespace: "sysui_integrations"
+ description: "Enable apps to self-invoke Contextual Search."
+ bug: "368653769"
+ is_exported: true
+}
diff --git a/core/java/android/app/notification.aconfig b/core/java/android/app/notification.aconfig
index a10b6ff39a37..9d8ab03982e6 100644
--- a/core/java/android/app/notification.aconfig
+++ b/core/java/android/app/notification.aconfig
@@ -308,6 +308,16 @@ flag {
}
flag {
+ name: "nm_binder_perf_get_apps_with_channels"
+ namespace: "systemui"
+ description: "Use a single binder call to get the set of apps with channels for a user"
+ bug: "362981561"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "no_sbnholder"
namespace: "systemui"
description: "removes sbnholder from NLS"
diff --git a/core/java/android/appwidget/flags.aconfig b/core/java/android/appwidget/flags.aconfig
index 9914ba2b020a..58bf4ef8100b 100644
--- a/core/java/android/appwidget/flags.aconfig
+++ b/core/java/android/appwidget/flags.aconfig
@@ -97,16 +97,6 @@ flag {
}
flag {
- name: "check_remote_views_uri_permission"
- namespace: "app_widgets"
- description: "Check that the widget provider has permissions to access any URIs within its RemoteViews"
- bug: "369137473"
- metadata {
- purpose: PURPOSE_BUGFIX
- }
-}
-
-flag {
name: "engagement_metrics"
namespace: "app_widgets"
description: "Enable collection of widget engagement metrics"
diff --git a/core/java/android/content/AttributionSource.java b/core/java/android/content/AttributionSource.java
index ffed5366d31b..ebf522b66c4e 100644
--- a/core/java/android/content/AttributionSource.java
+++ b/core/java/android/content/AttributionSource.java
@@ -34,6 +34,7 @@ import android.os.UserHandle;
import android.permission.PermissionManager;
import android.permission.flags.Flags;
import android.util.ArraySet;
+import android.util.Log;
import com.android.internal.annotations.Immutable;
@@ -90,6 +91,7 @@ import java.util.Set;
*/
@Immutable
public final class AttributionSource implements Parcelable {
+ private static final String TAG = "AttributionSource";
private static final String DESCRIPTOR = "android.content.AttributionSource";
private static final Binder sDefaultToken = new Binder(DESCRIPTOR);
@@ -273,6 +275,13 @@ public final class AttributionSource implements Parcelable {
final AttributionSource globalSource = ActivityThread.currentAttributionSource();
if (globalSource != null) {
+ if (Flags.enforceDefaultDeviceIdInMyAttributionSource()
+ && globalSource.getDeviceId() != Context.DEVICE_ID_DEFAULT) {
+ Log.w(TAG,
+ "Avoid using myAttributionSource() to fetch an attributionSource with a "
+ + "non-default device Id");
+ return globalSource.withDeviceId(Context.DEVICE_ID_DEFAULT);
+ }
return globalSource;
}
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 8378695fd7a7..3391e79b2ae4 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -3334,7 +3334,7 @@ public abstract class Context {
* this case, only one of these can be returned directly by the function;
* which of these that is returned is arbitrarily decided by the system.
*
- * <p>If you know the Intent your are registering for is sticky, you can
+ * <p>If you know the Intent you are registering for is sticky, you can
* supply null for your <var>receiver</var>. In this case, no receiver is
* registered -- the function simply returns the sticky Intent that
* matches <var>filter</var>. In the case of multiple matches, the same
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 0312ad7a739a..038756148a32 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -8181,7 +8181,7 @@ public class Intent implements Parcelable, Cloneable {
/**
* Create an intent from a URI. This URI may encode the action,
* category, and other intent fields, if it was returned by
- * {@link #toUri}. If the Intent was not generate by toUri(), its data
+ * {@link #toUri}. If the Intent was not generated by toUri(), its data
* will be the entire URI and its action will be ACTION_VIEW.
*
* <p>The URI given here must not be relative -- that is, it must include
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/hardware/contexthub/HubEndpointSession.java b/core/java/android/hardware/contexthub/HubEndpointSession.java
index dd6e52f51df0..ca59be8fcc65 100644
--- a/core/java/android/hardware/contexthub/HubEndpointSession.java
+++ b/core/java/android/hardware/contexthub/HubEndpointSession.java
@@ -27,6 +27,7 @@ import android.hardware.location.ContextHubTransactionHelper;
import android.hardware.location.IContextHubTransactionCallback;
import android.util.CloseGuard;
+import java.util.Objects;
import java.util.concurrent.atomic.AtomicBoolean;
/**
@@ -160,6 +161,32 @@ public class HubEndpointSession implements AutoCloseable {
return stringBuilder.toString();
}
+ @Override
+ public boolean equals(@Nullable Object object) {
+ if (object == this) {
+ return true;
+ }
+
+ boolean isEqual = false;
+ if (object instanceof HubEndpointSession other) {
+ isEqual = (other.getId() == mId);
+ if (mServiceDescriptor != null) {
+ isEqual &= mServiceDescriptor.equals(other.getServiceDescriptor());
+ } else {
+ isEqual &= (other.getServiceDescriptor() == null);
+ }
+ isEqual &=
+ mInitiator.equals(other.mInitiator) && mDestination.equals(other.mDestination);
+ }
+
+ return isEqual;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mId, mServiceDescriptor, mInitiator, mDestination);
+ }
+
/** @hide */
protected void finalize() throws Throwable {
try {
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index c00f31db1a38..33bf4a29ecc6 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -982,8 +982,8 @@ public class UserManager {
/**
* Specifies if a user is disallowed from adding new users. This can only be set by device
* owners or profile owners on the main user. The default value is <code>false</code>.
- * <p> When the device is an organization-owned device provisioned with a managed profile,
- * this restriction will be set as a base restriction which cannot be removed by any admin.
+ * <p> When the device is an organization-owned device, this restriction will be set as
+ * a base restriction which cannot be removed by any admin.
*
* <p>Holders of the permission
* {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_MODIFY_USERS}
@@ -2776,7 +2776,7 @@ public class UserManager {
}
/**
- * Returns whether logging out is currently allowed for the context user.
+ * Returns whether logging out is currently allowed for the specified user.
*
* <p>Logging out is not allowed in the following cases:
* <ol>
@@ -2794,11 +2794,10 @@ public class UserManager {
* {@link #LOGOUTABILITY_STATUS_CANNOT_SWITCH}.
* @hide
*/
- @UserHandleAware
@RequiresPermission(Manifest.permission.MANAGE_USERS)
- public @UserLogoutability int getUserLogoutability() {
+ public @UserLogoutability int getUserLogoutability(@UserIdInt int userId) {
try {
- return mService.getUserLogoutability(mUserId);
+ return mService.getUserLogoutability(userId);
} catch (RemoteException re) {
throw re.rethrowFromSystemServer();
}
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/permission/flags.aconfig b/core/java/android/permission/flags.aconfig
index daa5584462ba..d469a2f985fa 100644
--- a/core/java/android/permission/flags.aconfig
+++ b/core/java/android/permission/flags.aconfig
@@ -507,3 +507,10 @@ flag {
description: "Use IoThread handler for AppOpsService background/IO work."
bug: "394380603"
}
+
+flag {
+ name: "enforce_default_device_id_in_my_attribution_source"
+ namespace: "permissions"
+ description: "Force AttributionSource.myAttributionSource() to return a default device id"
+ bug: "343121936"
+}
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/security/flags.aconfig b/core/java/android/security/flags.aconfig
index 792e6ff52d01..3a3ea189b227 100644
--- a/core/java/android/security/flags.aconfig
+++ b/core/java/android/security/flags.aconfig
@@ -126,6 +126,16 @@ flag {
}
flag {
+ name: "internal_log_event_listener"
+ namespace: "hardware_backed_security"
+ description: "Use internal callback to gather SecurityMonitor logs."
+ bug: "389732143"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "protect_device_config_flags"
namespace: "psap_ai"
description: "Feature flag to limit adb shell to allowlisted flags"
diff --git a/core/java/android/security/intrusiondetection/IntrusionDetectionEvent.java b/core/java/android/security/intrusiondetection/IntrusionDetectionEvent.java
index 76ee4480c222..f5f334891d2d 100644
--- a/core/java/android/security/intrusiondetection/IntrusionDetectionEvent.java
+++ b/core/java/android/security/intrusiondetection/IntrusionDetectionEvent.java
@@ -19,10 +19,10 @@ package android.security.intrusiondetection;
import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
+import android.annotation.SystemApi;
import android.app.admin.ConnectEvent;
import android.app.admin.DnsEvent;
import android.app.admin.SecurityLog.SecurityEvent;
-import android.annotation.SystemApi;
import android.os.Parcel;
import android.os.Parcelable;
import android.security.Flags;
@@ -223,13 +223,13 @@ public final class IntrusionDetectionEvent implements Parcelable {
out.writeInt(mType);
switch (mType) {
case SECURITY_EVENT:
- out.writeParcelable(mSecurityEvent, flags);
+ mSecurityEvent.writeToParcel(out, flags);
break;
case NETWORK_EVENT_DNS:
- out.writeParcelable(mNetworkEventDns, flags);
+ mNetworkEventDns.writeToParcel(out, flags);
break;
case NETWORK_EVENT_CONNECT:
- out.writeParcelable(mNetworkEventConnect, flags);
+ mNetworkEventConnect.writeToParcel(out, flags);
break;
default:
throw new IllegalArgumentException("Invalid event type: " + mType);
diff --git a/core/java/android/text/Layout.java b/core/java/android/text/Layout.java
index 323d83b92143..2fa56137a8a0 100644
--- a/core/java/android/text/Layout.java
+++ b/core/java/android/text/Layout.java
@@ -672,7 +672,7 @@ public abstract class Layout {
// LINT.IfChange(hct_darken)
var lab = new double[3];
ColorUtils.colorToLAB(color, lab);
- return lab[0] < 50.0;
+ return lab[0] <= 50.0;
// LINT.ThenChange(/libs/hwui/hwui/DrawTextFunctor.h:hct_darken)
}
@@ -1021,6 +1021,12 @@ public abstract class Layout {
return;
}
+ if (!mSpannedText || mSpanColors == null) {
+ if (mPaint.getAlpha() == 0) {
+ return;
+ }
+ }
+
var padding = Math.max(HIGH_CONTRAST_TEXT_BORDER_WIDTH_MIN_PX,
mPaint.getTextSize() * HIGH_CONTRAST_TEXT_BORDER_WIDTH_FACTOR);
var cornerRadius = mPaint.density * HIGH_CONTRAST_TEXT_BACKGROUND_CORNER_RADIUS_DP;
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 80b4f2caabbb..de3e45b8ebde 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -538,6 +538,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<>();
@@ -1342,6 +1347,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) {
@@ -13562,4 +13572,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/window/DesktopModeFlags.java b/core/java/android/window/DesktopModeFlags.java
index 1ce5df7cd137..d43469fa76ca 100644
--- a/core/java/android/window/DesktopModeFlags.java
+++ b/core/java/android/window/DesktopModeFlags.java
@@ -57,6 +57,8 @@ public enum DesktopModeFlags {
true),
ENABLE_DESKTOP_CLOSE_SHORTCUT_BUGFIX(Flags::enableDesktopCloseShortcutBugfix, false),
ENABLE_DESKTOP_COMPAT_UI_VISIBILITY_STATUS(Flags::enableCompatUiVisibilityStatus, true),
+ ENABLE_DESKTOP_INDICATOR_IN_SEPARATE_THREAD_BUGFIX(
+ Flags::enableDesktopIndicatorInSeparateThreadBugfix, false),
ENABLE_DESKTOP_RECENTS_TRANSITIONS_CORNERS_BUGFIX(
Flags::enableDesktopRecentsTransitionsCornersBugfix, false),
ENABLE_DESKTOP_SKIP_COMPAT_UI_EDUCATION_IN_DESKTOP_MODE_BUGFIX(
diff --git a/core/java/android/window/TaskFragmentOperation.java b/core/java/android/window/TaskFragmentOperation.java
index 9d0ea547e734..1fd79ccf9e3f 100644
--- a/core/java/android/window/TaskFragmentOperation.java
+++ b/core/java/android/window/TaskFragmentOperation.java
@@ -90,26 +90,6 @@ public final class TaskFragmentOperation implements Parcelable {
public static final int OP_TYPE_SET_ISOLATED_NAVIGATION = 11;
/**
- * Reorders the TaskFragment to be the bottom-most in the Task. Note that this op will bring the
- * TaskFragment to the bottom of the Task below all the other Activities and TaskFragments.
- *
- * This is only allowed for system organizers. See
- * {@link com.android.server.wm.TaskFragmentOrganizerController#registerOrganizer(
- * ITaskFragmentOrganizer, boolean)}
- */
- public static final int OP_TYPE_REORDER_TO_BOTTOM_OF_TASK = 12;
-
- /**
- * Reorders the TaskFragment to be the top-most in the Task. Note that this op will bring the
- * TaskFragment to the top of the Task above all the other Activities and TaskFragments.
- *
- * This is only allowed for system organizers. See
- * {@link com.android.server.wm.TaskFragmentOrganizerController#registerOrganizer(
- * ITaskFragmentOrganizer, boolean)}
- */
- public static final int OP_TYPE_REORDER_TO_TOP_OF_TASK = 13;
-
- /**
* Creates a decor surface in the parent Task of the TaskFragment. The created decor surface
* will be provided in {@link TaskFragmentTransaction#TYPE_TASK_FRAGMENT_PARENT_INFO_CHANGED}
* event callback. If a decor surface already exists in the parent Task, the current
@@ -118,27 +98,17 @@ public final class TaskFragmentOperation implements Parcelable {
*
* The decor surface can be used to draw the divider between TaskFragments or other decorations.
*/
- public static final int OP_TYPE_CREATE_OR_MOVE_TASK_FRAGMENT_DECOR_SURFACE = 14;
+ public static final int OP_TYPE_CREATE_OR_MOVE_TASK_FRAGMENT_DECOR_SURFACE = 12;
/**
* Removes the decor surface in the parent Task of the TaskFragment.
*/
- public static final int OP_TYPE_REMOVE_TASK_FRAGMENT_DECOR_SURFACE = 15;
+ public static final int OP_TYPE_REMOVE_TASK_FRAGMENT_DECOR_SURFACE = 13;
/**
* Applies dimming on the parent Task which could cross two TaskFragments.
*/
- public static final int OP_TYPE_SET_DIM_ON_TASK = 16;
-
- /**
- * Sets this TaskFragment to move to bottom of the Task if any of the activities below it is
- * launched in a mode requiring clear top.
- *
- * This is only allowed for system organizers. See
- * {@link com.android.server.wm.TaskFragmentOrganizerController#registerOrganizer(
- * ITaskFragmentOrganizer, boolean)}
- */
- public static final int OP_TYPE_SET_MOVE_TO_BOTTOM_IF_CLEAR_WHEN_LAUNCH = 17;
+ public static final int OP_TYPE_SET_DIM_ON_TASK = 14;
/**
* Sets whether the decor surface will be boosted. When not boosted, the decor surface is placed
@@ -147,7 +117,7 @@ public final class TaskFragmentOperation implements Parcelable {
* surface is placed above all the non-boosted windows in the Task, the content of these
* non-boosted windows will be hidden and inputs are disabled.
*/
- public static final int OP_TYPE_SET_DECOR_SURFACE_BOOSTED = 18;
+ public static final int OP_TYPE_SET_DECOR_SURFACE_BOOSTED = 15;
/**
* Sets the TaskFragment to be pinned.
@@ -159,7 +129,44 @@ public final class TaskFragmentOperation implements Parcelable {
* <p>
* See {@link #OP_TYPE_REORDER_TO_FRONT} on how to reorder a pinned TaskFragment to the top.
*/
- public static final int OP_TYPE_SET_PINNED = 19;
+ public static final int OP_TYPE_SET_PINNED = 16;
+
+ /**
+ * The start index of the privileged operations. Only system organizers are allowed to use
+ * operations with index greater than or equal to this value.
+ */
+ public static final int PRIVILEGED_OP_START = 1000;
+
+ /**
+ * Reorders the TaskFragment to be the bottom-most in the Task. Note that this op will bring the
+ * TaskFragment to the bottom of the Task below all the other Activities and TaskFragments.
+ *
+ * This is only allowed for system organizers. See
+ * {@link com.android.server.wm.TaskFragmentOrganizerController#registerOrganizer(
+ * ITaskFragmentOrganizer, boolean)}
+ */
+ public static final int OP_TYPE_PRIVILEGED_REORDER_TO_BOTTOM_OF_TASK = PRIVILEGED_OP_START + 1;
+
+ /**
+ * Reorders the TaskFragment to be the top-most in the Task. Note that this op will bring the
+ * TaskFragment to the top of the Task above all the other Activities and TaskFragments.
+ *
+ * This is only allowed for system organizers. See
+ * {@link com.android.server.wm.TaskFragmentOrganizerController#registerOrganizer(
+ * ITaskFragmentOrganizer, boolean)}
+ */
+ public static final int OP_TYPE_PRIVILEGED_REORDER_TO_TOP_OF_TASK = PRIVILEGED_OP_START + 2;
+
+ /**
+ * Sets this TaskFragment to move to bottom of the Task if any of the activities below it is
+ * launched in a mode requiring clear top.
+ *
+ * This is only allowed for system organizers. See
+ * {@link com.android.server.wm.TaskFragmentOrganizerController#registerOrganizer(
+ * ITaskFragmentOrganizer, boolean)}
+ */
+ public static final int OP_TYPE_PRIVILEGED_SET_MOVE_TO_BOTTOM_IF_CLEAR_WHEN_LAUNCH =
+ PRIVILEGED_OP_START + 3;
/**
* Sets whether this TaskFragment can affect system UI flags such as the status bar. Default
@@ -169,7 +176,8 @@ public final class TaskFragmentOperation implements Parcelable {
* {@link com.android.server.wm.TaskFragmentOrganizerController#registerOrganizer(
* ITaskFragmentOrganizer, boolean)}
*/
- public static final int OP_TYPE_SET_CAN_AFFECT_SYSTEM_UI_FLAGS = 20;
+ public static final int OP_TYPE_PRIVILEGED_SET_CAN_AFFECT_SYSTEM_UI_FLAGS =
+ PRIVILEGED_OP_START + 4;
@IntDef(prefix = { "OP_TYPE_" }, value = {
OP_TYPE_UNKNOWN,
@@ -185,15 +193,15 @@ public final class TaskFragmentOperation implements Parcelable {
OP_TYPE_SET_RELATIVE_BOUNDS,
OP_TYPE_REORDER_TO_FRONT,
OP_TYPE_SET_ISOLATED_NAVIGATION,
- OP_TYPE_REORDER_TO_BOTTOM_OF_TASK,
- OP_TYPE_REORDER_TO_TOP_OF_TASK,
OP_TYPE_CREATE_OR_MOVE_TASK_FRAGMENT_DECOR_SURFACE,
OP_TYPE_REMOVE_TASK_FRAGMENT_DECOR_SURFACE,
OP_TYPE_SET_DIM_ON_TASK,
- OP_TYPE_SET_MOVE_TO_BOTTOM_IF_CLEAR_WHEN_LAUNCH,
OP_TYPE_SET_DECOR_SURFACE_BOOSTED,
OP_TYPE_SET_PINNED,
- OP_TYPE_SET_CAN_AFFECT_SYSTEM_UI_FLAGS,
+ OP_TYPE_PRIVILEGED_REORDER_TO_BOTTOM_OF_TASK,
+ OP_TYPE_PRIVILEGED_REORDER_TO_TOP_OF_TASK,
+ OP_TYPE_PRIVILEGED_SET_MOVE_TO_BOTTOM_IF_CLEAR_WHEN_LAUNCH,
+ OP_TYPE_PRIVILEGED_SET_CAN_AFFECT_SYSTEM_UI_FLAGS,
})
@Retention(RetentionPolicy.SOURCE)
public @interface OperationType {}
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 b805ac560b8d..e358540afe6b 100644
--- a/core/java/android/window/flags/lse_desktop_experience.aconfig
+++ b/core/java/android/window/flags/lse_desktop_experience.aconfig
@@ -165,6 +165,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."
@@ -623,6 +633,13 @@ flag {
}
flag {
+ name: "enable_multi_display_split"
+ namespace: "lse_desktop_experience"
+ description: "Enables split screen on multiple displays at the same time"
+ bug: "395943397"
+}
+
+flag {
name: "exclude_caption_from_app_bounds"
namespace: "lse_desktop_experience"
description: "Whether caption insets are excluded from app bounds in freeform"
@@ -706,3 +723,13 @@ flag {
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ name: "enable_desktop_indicator_in_separate_thread_bugfix"
+ namespace: "lse_desktop_experience"
+ description: "Enables running visual indicator view operations in ShellDesktopThread."
+ bug: "366413536"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/core/java/com/android/internal/jank/Cuj.java b/core/java/com/android/internal/jank/Cuj.java
index 9085bbec949f..41e2ca9cdfad 100644
--- a/core/java/com/android/internal/jank/Cuj.java
+++ b/core/java/com/android/internal/jank/Cuj.java
@@ -816,7 +816,7 @@ public class Cuj {
case CUJ_LAUNCHER_WORK_UTILITY_VIEW_SHRINK:
return "LAUNCHER_WORK_UTILITY_VIEW_SHRINK";
case CUJ_DEFAULT_TASK_TO_TASK_ANIMATION:
- return "CUJ_DEFAULT_TASK_TO_TASK_ANIMATION";
+ return "DEFAULT_TASK_TO_TASK_ANIMATION";
}
return "UNKNOWN";
}
diff --git a/core/java/com/android/internal/pm/pkg/component/AconfigFlags.java b/core/java/com/android/internal/pm/pkg/component/AconfigFlags.java
index 21d000dc5224..b57acf3d97fd 100644
--- a/core/java/com/android/internal/pm/pkg/component/AconfigFlags.java
+++ b/core/java/com/android/internal/pm/pkg/component/AconfigFlags.java
@@ -226,6 +226,21 @@ public class AconfigFlags {
}
private Boolean getFlagValueFromNewStorage(String flagPackageAndName) {
+ // We still need to check mFlagValues in case addFlagValuesForTesting() was called for
+ // testing purposes.
+ if (!mFlagValues.isEmpty() && mFlagValues.containsKey(flagPackageAndName)) {
+ Boolean value = mFlagValues.get(flagPackageAndName);
+ if (DEBUG) {
+ Slog.v(
+ LOG_TAG,
+ "Aconfig flag value (FOR TESTING) for "
+ + flagPackageAndName
+ + " = "
+ + value);
+ }
+ return value;
+ }
+
int index = flagPackageAndName.lastIndexOf('.');
if (index < 0) {
Slog.e(LOG_TAG, "Unable to parse package name from " + flagPackageAndName);
diff --git a/core/java/com/android/internal/protolog/IProtoLogConfigurationService.aidl b/core/java/com/android/internal/protolog/IProtoLogConfigurationService.aidl
index 9b60f49d1446..a6f0b306f4bf 100644
--- a/core/java/com/android/internal/protolog/IProtoLogConfigurationService.aidl
+++ b/core/java/com/android/internal/protolog/IProtoLogConfigurationService.aidl
@@ -40,12 +40,12 @@ import com.android.internal.protolog.IProtoLogClient;
*
* {@hide}
*/
-oneway interface IProtoLogConfigurationService {
- interface IRegisterClientArgs {
- String[] getGroups();
- boolean[] getGroupsDefaultLogcatStatus();
- String getViewerConfigFile();
+interface IProtoLogConfigurationService {
+ parcelable RegisterClientArgs {
+ String[] groups;
+ boolean[] groupsDefaultLogcatStatus;
+ String viewerConfigFile;
}
- void registerClient(IProtoLogClient client, IRegisterClientArgs args);
+ oneway void registerClient(IProtoLogClient client, in RegisterClientArgs args);
} \ No newline at end of file
diff --git a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
index d8cf258e23ba..93be3b02a12a 100644
--- a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
+++ b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
@@ -62,7 +62,7 @@ import android.util.Slog;
import android.util.proto.ProtoOutputStream;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.protolog.ProtoLogConfigurationServiceImpl.RegisterClientArgs;
+import com.android.internal.protolog.IProtoLogConfigurationService.RegisterClientArgs;
import com.android.internal.protolog.common.ILogger;
import com.android.internal.protolog.common.IProtoLog;
import com.android.internal.protolog.common.IProtoLogGroup;
@@ -164,11 +164,15 @@ public abstract class PerfettoProtoLogImpl extends IProtoLogClient.Stub implemen
try {
var args = createConfigurationServiceRegisterClientArgs();
- final var groupArgs = mLogGroups.values().stream()
- .map(group -> new RegisterClientArgs
- .GroupConfig(group.name(), group.isLogToLogcat()))
- .toArray(RegisterClientArgs.GroupConfig[]::new);
- args.setGroups(groupArgs);
+ args.groups = new String[mLogGroups.size()];
+ args.groupsDefaultLogcatStatus = new boolean[mLogGroups.size()];
+
+ var groups = mLogGroups.values().stream().toList();
+ for (var i = 0; i < groups.size(); i++) {
+ var group = groups.get(i);
+ args.groups[i] = group.name();
+ args.groupsDefaultLogcatStatus[i] = group.isLogToLogcat();
+ }
mConfigurationService.registerClient(this, args);
} catch (RemoteException e) {
diff --git a/core/java/com/android/internal/protolog/ProcessedPerfettoProtoLogImpl.java b/core/java/com/android/internal/protolog/ProcessedPerfettoProtoLogImpl.java
index 1f9df3cc842a..d9c54d695a87 100644
--- a/core/java/com/android/internal/protolog/ProcessedPerfettoProtoLogImpl.java
+++ b/core/java/com/android/internal/protolog/ProcessedPerfettoProtoLogImpl.java
@@ -22,7 +22,7 @@ import android.os.ServiceManager;
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.protolog.ProtoLogConfigurationServiceImpl.RegisterClientArgs;
+import com.android.internal.protolog.IProtoLogConfigurationService.RegisterClientArgs;
import com.android.internal.protolog.common.ILogger;
import com.android.internal.protolog.common.IProtoLogGroup;
@@ -104,8 +104,9 @@ public class ProcessedPerfettoProtoLogImpl extends PerfettoProtoLogImpl {
@NonNull
@Override
protected RegisterClientArgs createConfigurationServiceRegisterClientArgs() {
- return new RegisterClientArgs()
- .setViewerConfigFile(mViewerConfigFilePath);
+ var args = new RegisterClientArgs();
+ args.viewerConfigFile = mViewerConfigFilePath;
+ return args;
}
/**
diff --git a/core/java/com/android/internal/protolog/ProtoLogConfigurationServiceImpl.java b/core/java/com/android/internal/protolog/ProtoLogConfigurationServiceImpl.java
index 23f7c2a3d987..f83359dddfcc 100644
--- a/core/java/com/android/internal/protolog/ProtoLogConfigurationServiceImpl.java
+++ b/core/java/com/android/internal/protolog/ProtoLogConfigurationServiceImpl.java
@@ -134,74 +134,6 @@ public class ProtoLogConfigurationServiceImpl extends IProtoLogConfigurationServ
mDataSource = datasource;
}
- public static class RegisterClientArgs extends IRegisterClientArgs.Stub {
- /**
- * The viewer config file to be registered for this client ProtoLog process.
- */
- @Nullable
- private String mViewerConfigFile = null;
- /**
- * The list of all groups that this client protolog process supports and might trace.
- */
- @NonNull
- private String[] mGroups = new String[0];
- /**
- * The default logcat status of the ProtoLog client. True is logging to logcat, false
- * otherwise. The indices should match the indices in {@link mGroups}.
- */
- @NonNull
- private boolean[] mLogcatStatus = new boolean[0];
-
- public record GroupConfig(@NonNull String group, boolean logToLogcat) {}
-
- /**
- * Specify groups to register with this client that will be used for protologging in this
- * process.
- * @param groups to register with this client.
- * @return self
- */
- public RegisterClientArgs setGroups(GroupConfig... groups) {
- mGroups = new String[groups.length];
- mLogcatStatus = new boolean[groups.length];
-
- for (int i = 0; i < groups.length; i++) {
- mGroups[i] = groups[i].group;
- mLogcatStatus[i] = groups[i].logToLogcat;
- }
-
- return this;
- }
-
- /**
- * Set the viewer config file that the logs in this process are using.
- * @param viewerConfigFile The file path of the viewer config.
- * @return self
- */
- public RegisterClientArgs setViewerConfigFile(@NonNull String viewerConfigFile) {
- mViewerConfigFile = viewerConfigFile;
-
- return this;
- }
-
- @Override
- @NonNull
- public String[] getGroups() {
- return mGroups;
- }
-
- @Override
- @NonNull
- public boolean[] getGroupsDefaultLogcatStatus() {
- return mLogcatStatus;
- }
-
- @Nullable
- @Override
- public String getViewerConfigFile() {
- return mViewerConfigFile;
- }
- }
-
@FunctionalInterface
public interface ViewerConfigFileTracer {
/**
@@ -216,16 +148,16 @@ public class ProtoLogConfigurationServiceImpl extends IProtoLogConfigurationServ
}
@Override
- public void registerClient(@NonNull IProtoLogClient client, @NonNull IRegisterClientArgs args)
+ public void registerClient(@NonNull IProtoLogClient client, @NonNull RegisterClientArgs args)
throws RemoteException {
client.asBinder().linkToDeath(() -> onClientBinderDeath(client), /* flags */ 0);
- final String viewerConfigFile = args.getViewerConfigFile();
+ final String viewerConfigFile = args.viewerConfigFile;
if (viewerConfigFile != null) {
registerViewerConfigFile(client, viewerConfigFile);
}
- registerGroups(client, args.getGroups(), args.getGroupsDefaultLogcatStatus());
+ registerGroups(client, args.groups, args.groupsDefaultLogcatStatus);
}
@Override
diff --git a/core/java/com/android/internal/protolog/UnprocessedPerfettoProtoLogImpl.java b/core/java/com/android/internal/protolog/UnprocessedPerfettoProtoLogImpl.java
index ebb07a04270d..39b01fb59d9e 100644
--- a/core/java/com/android/internal/protolog/UnprocessedPerfettoProtoLogImpl.java
+++ b/core/java/com/android/internal/protolog/UnprocessedPerfettoProtoLogImpl.java
@@ -19,7 +19,7 @@ package com.android.internal.protolog;
import android.annotation.NonNull;
import android.os.ServiceManager;
-import com.android.internal.protolog.ProtoLogConfigurationServiceImpl.RegisterClientArgs;
+import com.android.internal.protolog.IProtoLogConfigurationService.RegisterClientArgs;
import com.android.internal.protolog.common.IProtoLogGroup;
public class UnprocessedPerfettoProtoLogImpl extends PerfettoProtoLogImpl {
diff --git a/core/java/com/android/internal/security/TEST_MAPPING b/core/java/com/android/internal/security/TEST_MAPPING
index 5bd9d2e4512d..be50bebf0831 100644
--- a/core/java/com/android/internal/security/TEST_MAPPING
+++ b/core/java/com/android/internal/security/TEST_MAPPING
@@ -6,10 +6,6 @@
{
"name": "UpdatableSystemFontTest",
"file_patterns": ["VerityUtils\\.java"]
- },
- {
- "name": "CtsApkVerityInstallHostTestCases",
- "file_patterns": ["VerityUtils\\.java"]
}
],
"postsubmit": [
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/NotificationProgressBar.java b/core/java/com/android/internal/widget/NotificationProgressBar.java
index 905d4dd547f3..c0fe0d125e44 100644
--- a/core/java/com/android/internal/widget/NotificationProgressBar.java
+++ b/core/java/com/android/internal/widget/NotificationProgressBar.java
@@ -64,6 +64,7 @@ public final class NotificationProgressBar extends ProgressBar implements
NotificationProgressDrawable.BoundsChangeListener {
private static final String TAG = "NotificationProgressBar";
private static final boolean DEBUG = false;
+ private static final float FADED_OPACITY = 0.5f;
private NotificationProgressDrawable mNotificationProgressDrawable;
private final Rect mProgressDrawableBounds = new Rect();
@@ -851,12 +852,12 @@ public final class NotificationProgressBar extends ProgressBar implements
}
/**
- * Get a color with an opacity that's 50% of the input color.
+ * Get a color that's the input color with opacity updated to FADED_OPACITY.
*/
@ColorInt
static int getFadedColor(@ColorInt int color) {
return Color.argb(
- (int) (Color.alpha(color) * 0.5f + 0.5f),
+ (int) (Color.alpha(color) * FADED_OPACITY + 0.5f),
Color.red(color),
Color.green(color),
Color.blue(color));
@@ -1200,7 +1201,7 @@ public final class NotificationProgressBar extends ProgressBar implements
* <p>
* <pre>
* When mFaded is set to true, a combination of the following is done to the segment:
- * 1. The drawing color is mColor with opacity updated to 50%.
+ * 1. The drawing color is mColor with opacity updated to FADED_OPACITY.
* 2. The gap between faded and non-faded segments is:
* - the segment-segment gap, when there is no tracker icon
* - 0, when there is tracker icon
diff --git a/core/java/com/android/internal/widget/remotecompose/OWNERS b/core/java/com/android/internal/widget/remotecompose/OWNERS
index 54facab0c3f4..e163474bccb9 100644
--- a/core/java/com/android/internal/widget/remotecompose/OWNERS
+++ b/core/java/com/android/internal/widget/remotecompose/OWNERS
@@ -5,4 +5,3 @@ sihua@google.com
sunnygoyal@google.com
oscarad@google.com
pinyaoting@google.com
-zakcohen@google.com
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 dea1caf76a5c..b8503da2c09b 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/CoreDocument.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/CoreDocument.java
@@ -35,6 +35,7 @@ import com.android.internal.widget.remotecompose.core.operations.layout.LoopOper
import com.android.internal.widget.remotecompose.core.operations.layout.RootLayoutComponent;
import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ComponentModifiers;
import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ModifierOperation;
+import com.android.internal.widget.remotecompose.core.operations.utilities.IntMap;
import com.android.internal.widget.remotecompose.core.operations.utilities.StringSerializer;
import com.android.internal.widget.remotecompose.core.serialize.MapSerializer;
import com.android.internal.widget.remotecompose.core.serialize.Serializable;
@@ -64,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.0f;
+ static final float BUILD = 0.2f;
@NonNull ArrayList<Operation> mOperations = new ArrayList<>();
@@ -99,6 +100,8 @@ public class CoreDocument implements Serializable {
private int mLastId = 1; // last component id when inflating the file
+ private IntMap<Object> mDocProperties;
+
/** Returns a version number that is monotonically increasing. */
public static int getDocumentApiLevel() {
return DOCUMENT_API_LEVEL;
@@ -407,10 +410,31 @@ public class CoreDocument implements Serializable {
@Override
public void serialize(MapSerializer serializer) {
- serializer.add("type", "CoreDocument");
- serializer.add("width", mWidth);
- serializer.add("height", mHeight);
- serializer.add("operations", mOperations);
+ serializer
+ .add("type", "CoreDocument")
+ .add("width", mWidth)
+ .add("height", mHeight)
+ .add("operations", mOperations);
+ }
+
+ /**
+ * Set the properties of the document
+ *
+ * @param properties the properties to set
+ */
+ public void setProperties(IntMap<Object> properties) {
+ mDocProperties = properties;
+ }
+
+ /**
+ * @param key the key
+ * @return the value associated with the key
+ */
+ public Object getProperty(short key) {
+ if (mDocProperties == null) {
+ return null;
+ }
+ return mDocProperties.get(key);
}
// ============== Haptic support ==================
@@ -718,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/PaintContext.java b/core/java/com/android/internal/widget/remotecompose/core/PaintContext.java
index a5b669d6aecf..1b0b9d783dff 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/PaintContext.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/PaintContext.java
@@ -207,6 +207,13 @@ public abstract class PaintContext {
public abstract void restorePaint();
/**
+ * Replace the current paint with the PaintBundle
+ *
+ * @param paint
+ */
+ public abstract void replacePaint(PaintBundle paint);
+
+ /**
* draw a round rect
*
* @param left left coordinate of the rectangle
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 c6ef1d3c457d..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;
@@ -191,6 +193,11 @@ public class RemoteComposeBuffer {
// Supported operations on the buffer
///////////////////////////////////////////////////////////////////////////////////////////////
+ /** Insert a header */
+ public void addHeader(short[] tags, Object[] values) {
+ Header.apply(mBuffer, tags, values);
+ }
+
/**
* Insert a header
*
@@ -219,6 +226,28 @@ public class RemoteComposeBuffer {
* @param width the width of the document in pixels
* @param height the height of the document in pixels
* @param contentDescription content description of the document
+ * @param capabilities bitmask indicating needed capabilities (unused for now)
+ */
+ public void addHeader(
+ int width,
+ int height,
+ @Nullable String contentDescription,
+ float density,
+ long capabilities) {
+ Header.apply(mBuffer, width, height, density, capabilities);
+ int contentDescriptionId = 0;
+ if (contentDescription != null) {
+ contentDescriptionId = addText(contentDescription);
+ RootContentDescription.apply(mBuffer, contentDescriptionId);
+ }
+ }
+
+ /**
+ * Insert a header
+ *
+ * @param width the width of the document in pixels
+ * @param height the height of the document in pixels
+ * @param contentDescription content description of the document
*/
public void header(int width, int height, @Nullable String contentDescription) {
header(width, height, contentDescription, 1f, 0);
@@ -1860,7 +1889,7 @@ public class RemoteComposeBuffer {
}
/** Add a component end tag */
- public void addComponentEnd() {
+ public void addContainerEnd() {
ContainerEnd.apply(mBuffer);
}
@@ -2204,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
*
@@ -2400,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/RemoteContext.java b/core/java/com/android/internal/widget/remotecompose/core/RemoteContext.java
index 36e4ec1ff303..622f0c8d12b7 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/RemoteContext.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/RemoteContext.java
@@ -26,6 +26,7 @@ import com.android.internal.widget.remotecompose.core.operations.layout.Componen
import com.android.internal.widget.remotecompose.core.operations.utilities.ArrayAccess;
import com.android.internal.widget.remotecompose.core.operations.utilities.CollectionsAccess;
import com.android.internal.widget.remotecompose.core.operations.utilities.DataMap;
+import com.android.internal.widget.remotecompose.core.operations.utilities.IntMap;
import java.time.LocalDateTime;
import java.time.OffsetDateTime;
@@ -392,6 +393,7 @@ public abstract class RemoteContext {
* @param width original width of the document when created
* @param height original height of the document when created
* @param capabilities bitmask of capabilities used in the document (TBD)
+ * @param properties properties of the document (TBD)
*/
public void header(
int majorVersion,
@@ -399,13 +401,15 @@ public abstract class RemoteContext {
int patchVersion,
int width,
int height,
- long capabilities) {
+ long capabilities,
+ IntMap<Object> properties) {
mRemoteComposeState.setWindowWidth(width);
mRemoteComposeState.setWindowHeight(height);
mDocument.setVersion(majorVersion, minorVersion, patchVersion);
mDocument.setWidth(width);
mDocument.setHeight(height);
mDocument.setRequiredCapabilities(capabilities);
+ mDocument.setProperties(properties);
}
/**
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/ColorExpression.java b/core/java/com/android/internal/widget/remotecompose/core/operations/ColorExpression.java
index a593241888df..d5af7914607a 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/ColorExpression.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/ColorExpression.java
@@ -27,6 +27,8 @@ import com.android.internal.widget.remotecompose.core.VariableSupport;
import com.android.internal.widget.remotecompose.core.WireBuffer;
import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder;
import com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation;
+import com.android.internal.widget.remotecompose.core.serialize.MapSerializer;
+import com.android.internal.widget.remotecompose.core.serialize.Serializable;
import java.util.List;
@@ -34,7 +36,7 @@ import java.util.List;
* Operation to Colors Color modes mMode = 0 two colors and a tween mMode = 1 color1 is a colorID.
* mMode = 2 color2 is a colorID. mMode = 3 color1 & color2 are ids mMode = 4 H S V mode
*/
-public class ColorExpression extends Operation implements VariableSupport {
+public class ColorExpression extends Operation implements VariableSupport, Serializable {
private static final int OP_CODE = Operations.COLOR_EXPRESSIONS;
private static final String CLASS_NAME = "ColorExpression";
public int mId;
@@ -502,4 +504,36 @@ public class ColorExpression extends Operation implements VariableSupport {
public String deepToString(@NonNull String indent) {
return indent + toString();
}
+
+ @Override
+ public void serialize(MapSerializer serializer) {
+ serializer.add("type", CLASS_NAME).add("id", mId);
+ switch (mMode) {
+ case COLOR_COLOR_INTERPOLATE:
+ case ID_COLOR_INTERPOLATE:
+ case COLOR_ID_INTERPOLATE:
+ case ID_ID_INTERPOLATE:
+ serializer.add("mode", "TWEEN");
+ serializer.add("startColor", mColor1, mOutColor1);
+ serializer.add("endColor", mColor2, mOutColor2);
+ serializer.add("startColor", mTween, mOutTween);
+ break;
+ case HSV_MODE:
+ serializer.add("mode", "HSV");
+ serializer.add("hue", mHue, mOutHue);
+ serializer.add("sat", mSat, mOutSat);
+ serializer.add("val", mValue, mOutValue);
+ break;
+ case ARGB_MODE:
+ case IDARGB_MODE:
+ serializer.add("mode", "ARGB");
+ serializer.add("a", mArgbAlpha, mOutArgbAlpha);
+ serializer.add("r", mArgbRed, mOutArgbRed);
+ serializer.add("g", mArgbGreen, mOutArgbGreen);
+ serializer.add("b", mArgbBlue, mOutArgbBlue);
+ break;
+ default:
+ serializer.add("mode", "NONE");
+ }
+ }
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DataListFloat.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DataListFloat.java
index 7a72b109b2a8..fb3abdbb0626 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DataListFloat.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DataListFloat.java
@@ -28,11 +28,13 @@ import com.android.internal.widget.remotecompose.core.WireBuffer;
import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder;
import com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation;
import com.android.internal.widget.remotecompose.core.operations.utilities.ArrayAccess;
+import com.android.internal.widget.remotecompose.core.serialize.MapSerializer;
+import com.android.internal.widget.remotecompose.core.serialize.Serializable;
import java.util.Arrays;
import java.util.List;
-public class DataListFloat extends Operation implements VariableSupport, ArrayAccess {
+public class DataListFloat extends Operation implements VariableSupport, ArrayAccess, Serializable {
private static final int OP_CODE = Operations.FLOAT_LIST;
private static final String CLASS_NAME = "IdListData";
private final int mId;
@@ -145,4 +147,9 @@ public class DataListFloat extends Operation implements VariableSupport, ArrayAc
public int getLength() {
return mValues.length;
}
+
+ @Override
+ public void serialize(MapSerializer serializer) {
+ serializer.add("type", CLASS_NAME).add("id", mId).add("values", List.of(mValues));
+ }
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DataListIds.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DataListIds.java
index 7e29620ec104..58fd74fdcf3e 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DataListIds.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DataListIds.java
@@ -29,11 +29,13 @@ import com.android.internal.widget.remotecompose.core.WireBuffer;
import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder;
import com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation;
import com.android.internal.widget.remotecompose.core.operations.utilities.ArrayAccess;
+import com.android.internal.widget.remotecompose.core.serialize.MapSerializer;
+import com.android.internal.widget.remotecompose.core.serialize.Serializable;
import java.util.Arrays;
import java.util.List;
-public class DataListIds extends Operation implements VariableSupport, ArrayAccess {
+public class DataListIds extends Operation implements VariableSupport, ArrayAccess, Serializable {
private static final int OP_CODE = Operations.ID_LIST;
private static final String CLASS_NAME = "IdListData";
private final int mId;
@@ -147,4 +149,9 @@ public class DataListIds extends Operation implements VariableSupport, ArrayAcce
public int getIntValue(int index) {
return 0;
}
+
+ @Override
+ public void serialize(MapSerializer serializer) {
+ serializer.add("type", CLASS_NAME).add("id", mId).add("ids", List.of(mIds));
+ }
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase2.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase2.java
index 6df4b91cc405..5dbaf29b8cc0 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase2.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase2.java
@@ -121,6 +121,6 @@ public abstract class DrawBase2 extends PaintOperation implements VariableSuppor
}
protected MapSerializer serialize(MapSerializer serializer, String v1Name, String v2Name) {
- return serializer.add(v1Name, mV1, mValue1).add(v2Name, mV2, mValue2);
+ return serializer.add(v1Name, mValue1, mV1).add(v2Name, mValue2, mV2);
}
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase3.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase3.java
index 16ead454d84f..238f10e794d0 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase3.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase3.java
@@ -126,8 +126,8 @@ public abstract class DrawBase3 extends PaintOperation implements VariableSuppor
protected MapSerializer serialize(
MapSerializer serializer, String v1Name, String v2Name, String v3Name) {
return serializer
- .add(v1Name, mV1, mValue1)
- .add(v2Name, mV2, mValue2)
- .add(v3Name, mV3, mValue3);
+ .add(v1Name, mValue1, mV1)
+ .add(v2Name, mValue2, mV2)
+ .add(v3Name, mValue3, mV3);
}
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase4.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase4.java
index 0733b833be11..ca34e01f1725 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase4.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase4.java
@@ -158,9 +158,9 @@ public abstract class DrawBase4 extends PaintOperation implements VariableSuppor
protected MapSerializer serialize(
MapSerializer serializer, String x1Name, String y1Name, String x2Name, String y2Name) {
return serializer
- .add(x1Name, mX1, mX1Value)
- .add(y1Name, mY1, mY1Value)
- .add(x2Name, mX2, mX2Value)
- .add(y2Name, mY2, mY2Value);
+ .add(x1Name, mX1Value, mX1)
+ .add(y1Name, mY1Value, mY1)
+ .add(x2Name, mX2Value, mX2)
+ .add(y2Name, mY2Value, mY2);
}
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase6.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase6.java
index 75b87c04e810..ca0584d0ee73 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase6.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase6.java
@@ -173,11 +173,11 @@ public abstract class DrawBase6 extends PaintOperation implements VariableSuppor
String v5Name,
String v6Name) {
return serializer
- .add(v1Name, mV1, mValue1)
- .add(v2Name, mV2, mValue2)
- .add(v3Name, mV3, mValue3)
- .add(v4Name, mV4, mValue4)
- .add(v5Name, mV5, mValue5)
- .add(v6Name, mV6, mValue6);
+ .add(v1Name, mValue1, mV1)
+ .add(v2Name, mValue2, mV2)
+ .add(v3Name, mValue3, mV3)
+ .add(v4Name, mValue4, mV4)
+ .add(v5Name, mValue5, mV5)
+ .add(v6Name, mValue6, mV6);
}
}
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/DrawTextAnchored.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTextAnchored.java
index 5d0c43723ea1..92469d1834ba 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTextAnchored.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTextAnchored.java
@@ -26,11 +26,13 @@ import com.android.internal.widget.remotecompose.core.VariableSupport;
import com.android.internal.widget.remotecompose.core.WireBuffer;
import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder;
import com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation;
+import com.android.internal.widget.remotecompose.core.serialize.MapSerializer;
+import com.android.internal.widget.remotecompose.core.serialize.Serializable;
import java.util.List;
/** Draw Text in Anchored to a point */
-public class DrawTextAnchored extends PaintOperation implements VariableSupport {
+public class DrawTextAnchored extends PaintOperation implements VariableSupport, Serializable {
private static final int OP_CODE = Operations.DRAW_TEXT_ANCHOR;
private static final String CLASS_NAME = "DrawTextAnchored";
int mTextID;
@@ -238,4 +240,16 @@ public class DrawTextAnchored extends PaintOperation implements VariableSupport
float y = Float.isNaN(mOutPanY) ? mOutY : mOutY + getVerticalOffset();
context.drawTextRun(mTextID, 0, -1, 0, 1, x, y, (mFlags & ANCHOR_TEXT_RTL) == 1);
}
+
+ @Override
+ public void serialize(MapSerializer serializer) {
+ serializer
+ .add("type", CLASS_NAME)
+ .add("textId", mTextID)
+ .add("x", mX, mOutX)
+ .add("y", mY, mOutY)
+ .add("panX", mPanX, mOutPanX)
+ .add("panY", mPanY, mOutPanY)
+ .add("flags", mFlags);
+ }
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTextOnPath.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTextOnPath.java
index 3ab4a87c614c..1f7910ede4b5 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTextOnPath.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTextOnPath.java
@@ -26,11 +26,13 @@ import com.android.internal.widget.remotecompose.core.VariableSupport;
import com.android.internal.widget.remotecompose.core.WireBuffer;
import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder;
import com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation;
+import com.android.internal.widget.remotecompose.core.serialize.MapSerializer;
+import com.android.internal.widget.remotecompose.core.serialize.Serializable;
import java.util.List;
/** Draw text along a path. */
-public class DrawTextOnPath extends PaintOperation implements VariableSupport {
+public class DrawTextOnPath extends PaintOperation implements VariableSupport, Serializable {
private static final int OP_CODE = Operations.DRAW_TEXT_ON_PATH;
private static final String CLASS_NAME = "DrawTextOnPath";
int mPathId;
@@ -153,4 +155,14 @@ public class DrawTextOnPath extends PaintOperation implements VariableSupport {
public void paint(@NonNull PaintContext context) {
context.drawTextOnPath(mTextId, mPathId, mOutHOffset, mOutVOffset);
}
+
+ @Override
+ public void serialize(MapSerializer serializer) {
+ serializer
+ .add("type", CLASS_NAME)
+ .add("pathId", mPathId)
+ .add("textId", mTextId)
+ .add("vOffset", mVOffset, mOutVOffset)
+ .add("hOffset", mHOffset, mOutHOffset);
+ }
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/FloatConstant.java b/core/java/com/android/internal/widget/remotecompose/core/operations/FloatConstant.java
index e04e691c312c..7f3c3ed6bcff 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/FloatConstant.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/FloatConstant.java
@@ -25,30 +25,32 @@ import com.android.internal.widget.remotecompose.core.RemoteContext;
import com.android.internal.widget.remotecompose.core.WireBuffer;
import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder;
import com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation;
+import com.android.internal.widget.remotecompose.core.serialize.MapSerializer;
+import com.android.internal.widget.remotecompose.core.serialize.Serializable;
import java.util.List;
-/** Operation to deal with Text data */
-public class FloatConstant extends Operation {
+/** Used to represent a float */
+public class FloatConstant extends Operation implements Serializable {
private static final int OP_CODE = Operations.DATA_FLOAT;
private static final String CLASS_NAME = "FloatConstant";
- public int mTextId;
+ public int mId;
public float mValue;
- public FloatConstant(int textId, float value) {
- this.mTextId = textId;
+ public FloatConstant(int id, float value) {
+ this.mId = id;
this.mValue = value;
}
@Override
public void write(@NonNull WireBuffer buffer) {
- apply(buffer, mTextId, mValue);
+ apply(buffer, mId, mValue);
}
@NonNull
@Override
public String toString() {
- return "FloatConstant[" + mTextId + "] = " + mValue;
+ return "FloatConstant[" + mId + "] = " + mValue;
}
/**
@@ -90,10 +92,10 @@ public class FloatConstant extends Operation {
* @param operations the list of operations that will be added to
*/
public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
- int textId = buffer.readInt();
+ int id = buffer.readInt();
float value = buffer.readFloat();
- operations.add(new FloatConstant(textId, value));
+ operations.add(new FloatConstant(id, value));
}
/**
@@ -110,7 +112,7 @@ public class FloatConstant extends Operation {
@Override
public void apply(@NonNull RemoteContext context) {
- context.loadFloat(mTextId, mValue);
+ context.loadFloat(mId, mValue);
}
@NonNull
@@ -118,4 +120,9 @@ public class FloatConstant extends Operation {
public String deepToString(@NonNull String indent) {
return indent + toString();
}
+
+ @Override
+ public void serialize(MapSerializer serializer) {
+ serializer.add("type", CLASS_NAME).add("id", mId).add("value", mValue);
+ }
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/FloatExpression.java b/core/java/com/android/internal/widget/remotecompose/core/operations/FloatExpression.java
index e09745aa8546..c1fa898ec619 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/FloatExpression.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/FloatExpression.java
@@ -34,6 +34,9 @@ import com.android.internal.widget.remotecompose.core.operations.utilities.Anima
import com.android.internal.widget.remotecompose.core.operations.utilities.NanMap;
import com.android.internal.widget.remotecompose.core.operations.utilities.easing.FloatAnimation;
import com.android.internal.widget.remotecompose.core.operations.utilities.easing.SpringStopEngine;
+import com.android.internal.widget.remotecompose.core.serialize.MapSerializer;
+import com.android.internal.widget.remotecompose.core.serialize.Serializable;
+import com.android.internal.widget.remotecompose.core.serialize.SerializeTags;
import java.util.List;
@@ -42,7 +45,7 @@ import java.util.List;
* like injecting the width of the component int draw rect As well as supporting generalized
* animation floats. The floats represent a RPN style calculator
*/
-public class FloatExpression extends Operation implements VariableSupport {
+public class FloatExpression extends Operation implements VariableSupport, Serializable {
private static final int OP_CODE = Operations.ANIMATED_FLOAT;
private static final String CLASS_NAME = "FloatExpression";
public int mId;
@@ -336,4 +339,14 @@ public class FloatExpression extends Operation implements VariableSupport {
public String deepToString(@NonNull String indent) {
return indent + toString();
}
+
+ @Override
+ public void serialize(MapSerializer serializer) {
+ serializer
+ .addTags(SerializeTags.EXPRESSION)
+ .add("type", CLASS_NAME)
+ .add("id", mId)
+ .addFloatExpressionSrc("srcValues", mSrcValue)
+ .add("animation", mFloatAnimation);
+ }
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/Header.java b/core/java/com/android/internal/widget/remotecompose/core/operations/Header.java
index 98c2745380d7..3d6316b67e8f 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/Header.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/Header.java
@@ -30,7 +30,11 @@ import com.android.internal.widget.remotecompose.core.RemoteComposeOperation;
import com.android.internal.widget.remotecompose.core.RemoteContext;
import com.android.internal.widget.remotecompose.core.WireBuffer;
import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder;
+import com.android.internal.widget.remotecompose.core.operations.utilities.IntMap;
+import java.io.DataInputStream;
+import java.io.IOException;
+import java.io.InputStream;
import java.util.List;
/**
@@ -42,16 +46,75 @@ import java.util.List;
public class Header extends Operation implements RemoteComposeOperation {
private static final int OP_CODE = Operations.HEADER;
private static final String CLASS_NAME = "Header";
+ private static final int MAGIC_NUMBER = 0x048C0000; // to uniquly identify the protocol
int mMajorVersion;
int mMinorVersion;
int mPatchVersion;
- int mWidth;
- int mHeight;
+ int mWidth = 256;
+ int mHeight = 256;
- float mDensity;
- long mCapabilities;
+ float mDensity = 3;
+ long mCapabilities = 0;
+ private IntMap<Object> mProperties;
+
+ /**
+ * Get a property on the header
+ *
+ * @param property the property to get
+ * @return the value of the property
+ */
+ public Object get(short property) {
+ return mProperties.get(property);
+ }
+
+ /** the width of the document */
+ public static final short DOC_WIDTH = 5;
+
+ /** The height of the document */
+ public static final short DOC_HEIGHT = 6;
+
+ /** The density at generation */
+ public static final short DOC_DENSITY_AT_GENERATION = 7;
+
+ /** The desired FPS for the document */
+ public static final short DOC_DESIRED_FPS = 8;
+
+ /** The description of the contents of the document */
+ public static final short DOC_CONTENT_DESCRIPTION = 9;
+
+ /** The source of the document */
+ public static final short DOC_SOURCE = 11;
+
+ /** The object is an integer */
+ private static final short DATA_TYPE_INT = 0;
+
+ /** The object is an float */
+ private static final short DATA_TYPE_FLOAT = 1;
+
+ /** The object is an LONG */
+ private static final short DATA_TYPE_LONG = 2;
+
+ /** The object is an UTF-8 encoded string */
+ private static final short DATA_TYPE_STRING = 3;
+
+ private static final short[] KEYS = {
+ DOC_WIDTH,
+ DOC_HEIGHT,
+ DOC_DENSITY_AT_GENERATION,
+ DOC_DESIRED_FPS,
+ DOC_CONTENT_DESCRIPTION,
+ DOC_SOURCE
+ };
+ private static final String[] KEY_NAMES = {
+ "DOC_WIDTH",
+ "DOC_HEIGHT",
+ "DOC_DENSITY_AT_GENERATION",
+ "DOC_DESIRED_FPS",
+ "DOC_CONTENT_DESCRIPTION",
+ "DOC_SOURCE"
+ };
/**
* It encodes the version of the document (following semantic versioning) as well as the
@@ -82,6 +145,60 @@ public class Header extends Operation implements RemoteComposeOperation {
this.mCapabilities = capabilities;
}
+ /**
+ * @param majorVersion the major version of the RemoteCompose document API
+ * @param minorVersion the minor version of the RemoteCompose document API
+ * @param patchVersion the patch version of the RemoteCompose document API
+ * @param properties the properties of the document
+ */
+ public Header(int majorVersion, int minorVersion, int patchVersion, IntMap<Object> properties) {
+ this.mMajorVersion = majorVersion;
+ this.mMinorVersion = minorVersion;
+ this.mPatchVersion = patchVersion;
+ if (properties != null) {
+ this.mProperties = properties;
+ this.mWidth = getInt(DOC_WIDTH, 256);
+ this.mHeight = getInt(DOC_HEIGHT, 256);
+ this.mDensity = getFloat(DOC_DENSITY_AT_GENERATION, 0);
+ }
+ }
+
+ private int getInt(int key, int defaultValue) {
+ Integer value = (Integer) mProperties.get(key);
+ if (value != null) {
+ return value;
+ } else {
+ return defaultValue;
+ }
+ }
+
+ private long getLong(int key, long defaultValue) {
+ Long value = (Long) mProperties.get(key);
+ if (value != null) {
+ return value;
+ } else {
+ return defaultValue;
+ }
+ }
+
+ private float getFloat(int key, float defaultValue) {
+ Float value = (Float) mProperties.get(key);
+ if (value != null) {
+ return value;
+ } else {
+ return defaultValue;
+ }
+ }
+
+ private String getString(int key, String defaultValue) {
+ String value = (String) mProperties.get(key);
+ if (value != null) {
+ return value;
+ } else {
+ return defaultValue;
+ }
+ }
+
@Override
public void write(@NonNull WireBuffer buffer) {
apply(buffer, mWidth, mHeight, mDensity, mCapabilities);
@@ -90,6 +207,16 @@ public class Header extends Operation implements RemoteComposeOperation {
@NonNull
@Override
public String toString() {
+ String prop = "";
+ if (mProperties != null) {
+ for (int i = 0; i < KEYS.length; i++) {
+ Object p = mProperties.get(KEYS[i]);
+ if (p != null) {
+ prop += "\n " + KEY_NAMES[i] + " " + p.toString();
+ }
+ }
+ return "HEADER v" + mMajorVersion + "." + mMinorVersion + "." + mPatchVersion + prop;
+ }
return "HEADER v"
+ mMajorVersion
+ "."
@@ -102,12 +229,20 @@ public class Header extends Operation implements RemoteComposeOperation {
+ mHeight
+ " ["
+ mCapabilities
- + "]";
+ + "]"
+ + prop;
}
@Override
public void apply(@NonNull RemoteContext context) {
- context.header(mMajorVersion, mMinorVersion, mPatchVersion, mWidth, mHeight, mCapabilities);
+ context.header(
+ mMajorVersion,
+ mMinorVersion,
+ mPatchVersion,
+ mWidth,
+ mHeight,
+ mCapabilities,
+ mProperties);
}
@NonNull
@@ -157,22 +292,44 @@ public class Header extends Operation implements RemoteComposeOperation {
}
/**
- * Read this operation and add it to the list of operations
+ * Apply the header to the wire buffer
*
- * @param buffer the buffer to read
- * @param operations the list of operations that will be added to
+ * @param buffer
*/
- public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
- int majorVersion = buffer.readInt();
- int minorVersion = buffer.readInt();
- int patchVersion = buffer.readInt();
- int width = buffer.readInt();
- int height = buffer.readInt();
- // float density = buffer.readFloat();
- float density = 1f;
- long capabilities = buffer.readLong();
- Header header =
- new Header(
+ public static void apply(@NonNull WireBuffer buffer, short[] type, Object[] value) {
+ buffer.start(OP_CODE);
+ buffer.writeInt(MAJOR_VERSION | MAGIC_NUMBER); // major version number of the protocol
+ buffer.writeInt(MINOR_VERSION); // minor version number of the protocol
+ buffer.writeInt(PATCH_VERSION); // patch version number of the protocol
+ buffer.writeInt(type.length);
+ writeMap(buffer, type, value);
+ }
+
+ /**
+ * @param is the stream to read from
+ * @return the header
+ * @throws IOException if there is an error reading the header
+ */
+ public static Header readDirect(InputStream is) throws IOException {
+ DataInputStream stream = new DataInputStream(is);
+ try {
+
+ int type = stream.readByte();
+
+ if (type != OP_CODE) {
+ throw new IOException("Invalid header " + type + " != " + OP_CODE);
+ }
+ int majorVersion = stream.readInt();
+ int minorVersion = stream.readInt();
+ int patchVersion = stream.readInt();
+
+ if (majorVersion < 0x10000) {
+ int width = stream.readInt();
+ int height = stream.readInt();
+ // float density = is.read();
+ float density = 1f;
+ long capabilities = stream.readLong();
+ return new Header(
majorVersion,
minorVersion,
patchVersion,
@@ -180,7 +337,173 @@ public class Header extends Operation implements RemoteComposeOperation {
height,
density,
capabilities);
- operations.add(header);
+ }
+
+ if ((majorVersion & 0xFFFF0000) != MAGIC_NUMBER) {
+ throw new IOException(
+ "Invalid header MAGIC_NUMBER "
+ + (majorVersion & 0xFFFF0000)
+ + " != "
+ + MAGIC_NUMBER);
+ }
+ majorVersion &= 0xFFFF;
+ int len = stream.readInt();
+ short[] types = new short[len];
+ Object[] values = new Object[len];
+ readMap(stream, types, values);
+ IntMap<Object> map = new IntMap<>();
+ for (int i = 0; i < len; i++) {
+ map.put(types[i], values[i]);
+ }
+ return new Header(majorVersion, minorVersion, patchVersion, map);
+
+ } finally {
+ stream.close();
+ }
+ }
+
+ /**
+ * Read this operation and add it to the list of operations
+ *
+ * @param stream the buffer to read
+ * @param types the list of types that will be populated
+ * @param values the list of values that will be populated
+ */
+ private static void readMap(DataInputStream stream, short[] types, Object[] values)
+ throws IOException {
+ for (int i = 0; i < types.length; i++) {
+ short tag = (short) stream.readShort();
+ int itemLen = stream.readShort();
+ int dataType = tag >> 10;
+ types[i] = (short) (tag & 0x3F);
+ Object value;
+ switch (dataType) {
+ case DATA_TYPE_INT:
+ values[i] = stream.readInt();
+ break;
+ case DATA_TYPE_FLOAT:
+ values[i] = stream.readFloat();
+ break;
+ case DATA_TYPE_LONG:
+ values[i] = stream.readLong();
+ break;
+ case DATA_TYPE_STRING:
+ int slen = stream.readInt();
+ byte[] data = new byte[slen];
+ stream.readFully(data);
+ values[i] = new String(data);
+ break;
+ }
+ }
+ }
+
+ /**
+ * Read this operation and add it to the list of operations
+ *
+ * @param buffer the buffer to read
+ * @param operations the list of operations that will be added to
+ */
+ public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
+ int majorVersion = buffer.readInt();
+ int minorVersion = buffer.readInt();
+ int patchVersion = buffer.readInt();
+ if (majorVersion < 0x10000) {
+ int width = buffer.readInt();
+ int height = buffer.readInt();
+ // float density = buffer.readFloat();
+ float density = 1f;
+ long capabilities = buffer.readLong();
+ Header header =
+ new Header(
+ majorVersion,
+ minorVersion,
+ patchVersion,
+ width,
+ height,
+ density,
+ capabilities);
+ operations.add(header);
+ } else {
+ majorVersion &= 0xFFFF;
+ int length = buffer.readInt();
+ short[] types = new short[length];
+ Object[] values = new Object[length];
+ readMap(buffer, types, values);
+ IntMap<Object> map = new IntMap<>();
+ for (int i = 0; i < length; i++) {
+ map.put(types[i], values[i]);
+ }
+ Header header = new Header(majorVersion, minorVersion, patchVersion, map);
+ operations.add(header);
+ }
+ }
+
+ /**
+ * Read this operation and add it to the list of operations
+ *
+ * @param buffer the buffer to read
+ * @param types the list of types that will be populated
+ * @param values the list of values that will be populated
+ */
+ private static void readMap(@NonNull WireBuffer buffer, short[] types, Object[] values) {
+ for (int i = 0; i < types.length; i++) {
+ short tag = (short) buffer.readShort();
+ int itemLen = buffer.readShort();
+ int dataType = tag >> 10;
+ types[i] = (short) (tag & 0x3F);
+ Object value;
+ switch (dataType) {
+ case DATA_TYPE_INT:
+ values[i] = buffer.readInt();
+ break;
+ case DATA_TYPE_FLOAT:
+ values[i] = buffer.readFloat();
+ break;
+ case DATA_TYPE_LONG:
+ values[i] = buffer.readLong();
+ break;
+ case DATA_TYPE_STRING:
+ values[i] = buffer.readUTF8();
+ break;
+ }
+ }
+ }
+
+ /**
+ * Write the map of values to the buffer
+ *
+ * @param buffer the buffer to read
+ * @param types the list of types that will be written
+ * @param values the list of values that will be written
+ */
+ private static void writeMap(@NonNull WireBuffer buffer, short[] types, Object[] values) {
+ for (int i = 0; i < types.length; i++) {
+ short tag = types[i];
+ if (values[i] instanceof String) {
+ tag = (short) (tag | (DATA_TYPE_STRING << 10));
+ buffer.writeShort(tag);
+ String str = (String) values[i];
+ byte[] data = str.getBytes();
+ buffer.writeShort((data.length + 4));
+ buffer.writeBuffer(data);
+ } else if (values[i] instanceof Integer) {
+ tag = (short) (tag | (DATA_TYPE_INT << 10));
+ buffer.writeShort(tag);
+ buffer.writeShort(4);
+ buffer.writeInt((Integer) values[i]);
+ } else if (values[i] instanceof Float) {
+ tag = (short) (tag | (DATA_TYPE_FLOAT << 10));
+ buffer.writeShort(tag);
+ buffer.writeShort(4);
+
+ buffer.writeFloat((float) values[i]);
+ } else if (values[i] instanceof Long) {
+ tag = (short) (tag | (DATA_TYPE_LONG << 10));
+ buffer.writeShort(tag);
+ buffer.writeShort(8);
+ buffer.writeLong((Long) values[i]);
+ }
+ }
}
/**
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/IntegerExpression.java b/core/java/com/android/internal/widget/remotecompose/core/operations/IntegerExpression.java
index f04f30dc62fb..2a5260c0c9b1 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/IntegerExpression.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/IntegerExpression.java
@@ -29,6 +29,9 @@ import com.android.internal.widget.remotecompose.core.WireBuffer;
import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder;
import com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation;
import com.android.internal.widget.remotecompose.core.operations.utilities.IntegerExpressionEvaluator;
+import com.android.internal.widget.remotecompose.core.serialize.MapSerializer;
+import com.android.internal.widget.remotecompose.core.serialize.Serializable;
+import com.android.internal.widget.remotecompose.core.serialize.SerializeTags;
import java.util.Arrays;
import java.util.List;
@@ -38,7 +41,7 @@ import java.util.List;
* like injecting the width of the component int draw rect As well as supporting generalized
* animation floats. The floats represent a RPN style calculator
*/
-public class IntegerExpression extends Operation implements VariableSupport {
+public class IntegerExpression extends Operation implements VariableSupport, Serializable {
private static final int OP_CODE = Operations.INTEGER_EXPRESSION;
private static final String CLASS_NAME = "IntegerExpression";
public int mId;
@@ -225,4 +228,14 @@ public class IntegerExpression extends Operation implements VariableSupport {
public static boolean isId(int mask, int i, int value) {
return ((1 << i) & mask) != 0 && value < IntegerExpressionEvaluator.OFFSET;
}
+
+ @Override
+ public void serialize(MapSerializer serializer) {
+ serializer
+ .addTags(SerializeTags.EXPRESSION)
+ .add("type", CLASS_NAME)
+ .add("id", mId)
+ .add("mask", mId)
+ .addIntExpressionSrc("srcValues", mSrcValue, mMask);
+ }
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/NamedVariable.java b/core/java/com/android/internal/widget/remotecompose/core/operations/NamedVariable.java
index dde632e0c346..96628fd51225 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/NamedVariable.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/NamedVariable.java
@@ -26,11 +26,13 @@ import com.android.internal.widget.remotecompose.core.RemoteContext;
import com.android.internal.widget.remotecompose.core.WireBuffer;
import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder;
import com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation;
+import com.android.internal.widget.remotecompose.core.serialize.MapSerializer;
+import com.android.internal.widget.remotecompose.core.serialize.Serializable;
import java.util.List;
/** Operation to deal with Text data */
-public class NamedVariable extends Operation {
+public class NamedVariable extends Operation implements Serializable {
private static final int OP_CODE = Operations.NAMED_VARIABLE;
private static final String CLASS_NAME = "NamedVariable";
public final int mVarId;
@@ -135,4 +137,28 @@ public class NamedVariable extends Operation {
public String deepToString(@NonNull String indent) {
return indent + toString();
}
+
+ @Override
+ public void serialize(MapSerializer serializer) {
+ serializer
+ .add("type", CLASS_NAME)
+ .add("varId", mVarId)
+ .add("varName", mVarName)
+ .add("varType", typeToString());
+ }
+
+ private String typeToString() {
+ switch (mVarType) {
+ case COLOR_TYPE:
+ return "COLOR_TYPE";
+ case FLOAT_TYPE:
+ return "FLOAT_TYPE";
+ case STRING_TYPE:
+ return "STRING_TYPE";
+ case IMAGE_TYPE:
+ return "IMAGE_TYPE";
+ default:
+ return "INVALID_TYPE";
+ }
+ }
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/PaintData.java b/core/java/com/android/internal/widget/remotecompose/core/operations/PaintData.java
index f756b76b86c3..8389aa707ee6 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/PaintData.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/PaintData.java
@@ -29,11 +29,13 @@ 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.paint.PaintBundle;
+import com.android.internal.widget.remotecompose.core.serialize.MapSerializer;
+import com.android.internal.widget.remotecompose.core.serialize.Serializable;
import java.util.List;
/** Paint data operation */
-public class PaintData extends PaintOperation implements VariableSupport {
+public class PaintData extends PaintOperation implements VariableSupport, Serializable {
private static final int OP_CODE = Operations.PAINT_VALUES;
private static final String CLASS_NAME = "PaintData";
@NonNull public PaintBundle mPaintData = new PaintBundle();
@@ -126,4 +128,9 @@ public class PaintData extends PaintOperation implements VariableSupport {
public void paint(@NonNull PaintContext context) {
context.applyPaint(mPaintData);
}
+
+ @Override
+ public void serialize(MapSerializer serializer) {
+ serializer.add("type", CLASS_NAME).add("paintBundle", mPaintData);
+ }
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/PathAppend.java b/core/java/com/android/internal/widget/remotecompose/core/operations/PathAppend.java
index e7cce03f0c4b..8f353ce4a26b 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/PathAppend.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/PathAppend.java
@@ -30,11 +30,13 @@ import com.android.internal.widget.remotecompose.core.VariableSupport;
import com.android.internal.widget.remotecompose.core.WireBuffer;
import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder;
import com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation;
+import com.android.internal.widget.remotecompose.core.serialize.MapSerializer;
+import com.android.internal.widget.remotecompose.core.serialize.Serializable;
import java.util.Arrays;
import java.util.List;
-public class PathAppend extends PaintOperation implements VariableSupport {
+public class PathAppend extends PaintOperation implements VariableSupport, Serializable {
private static final int OP_CODE = Operations.PATH_ADD;
private static final String CLASS_NAME = "PathAppend";
int mInstanceId;
@@ -253,4 +255,12 @@ public class PathAppend extends PaintOperation implements VariableSupport {
}
return str.toString();
}
+
+ @Override
+ public void serialize(MapSerializer serializer) {
+ serializer
+ .add("type", CLASS_NAME)
+ .add("id", mInstanceId)
+ .add("path", pathString(mFloatPath));
+ }
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/PathCreate.java b/core/java/com/android/internal/widget/remotecompose/core/operations/PathCreate.java
index 1f76639b1b1f..7aa3390b53ee 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/PathCreate.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/PathCreate.java
@@ -29,11 +29,13 @@ import com.android.internal.widget.remotecompose.core.VariableSupport;
import com.android.internal.widget.remotecompose.core.WireBuffer;
import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder;
import com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation;
+import com.android.internal.widget.remotecompose.core.serialize.MapSerializer;
+import com.android.internal.widget.remotecompose.core.serialize.Serializable;
import java.util.Arrays;
import java.util.List;
-public class PathCreate extends PaintOperation implements VariableSupport {
+public class PathCreate extends PaintOperation implements VariableSupport, Serializable {
private static final int OP_CODE = Operations.PATH_CREATE;
private static final String CLASS_NAME = "PathCreate";
int mInstanceId;
@@ -237,4 +239,12 @@ public class PathCreate extends PaintOperation implements VariableSupport {
public void apply(@NonNull RemoteContext context) {
context.loadPathData(mInstanceId, mOutputPath);
}
+
+ @Override
+ public void serialize(MapSerializer serializer) {
+ serializer
+ .add("type", CLASS_NAME)
+ .add("id", mInstanceId)
+ .add("path", pathString(mFloatPath));
+ }
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/PathTween.java b/core/java/com/android/internal/widget/remotecompose/core/operations/PathTween.java
index 65adfeabefa6..c5add57d4dd0 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/PathTween.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/PathTween.java
@@ -29,11 +29,13 @@ import com.android.internal.widget.remotecompose.core.VariableSupport;
import com.android.internal.widget.remotecompose.core.WireBuffer;
import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder;
import com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation;
+import com.android.internal.widget.remotecompose.core.serialize.MapSerializer;
+import com.android.internal.widget.remotecompose.core.serialize.Serializable;
import java.util.List;
/** Operation to deal with Path data */
-public class PathTween extends PaintOperation implements VariableSupport {
+public class PathTween extends PaintOperation implements VariableSupport, Serializable {
private static final int OP_CODE = Operations.PATH_TWEEN;
private static final String CLASS_NAME = "PathTween";
public int mOutId;
@@ -155,4 +157,14 @@ public class PathTween extends PaintOperation implements VariableSupport {
public void paint(PaintContext context) {
context.tweenPath(mOutId, mPathId1, mPathId2, mTweenOut);
}
+
+ @Override
+ public void serialize(MapSerializer serializer) {
+ serializer
+ .add("type", CLASS_NAME)
+ .add("outId", mOutId)
+ .add("pathId1", mPathId1)
+ .add("pathId2", mPathId2)
+ .add("tween", mTween, mTweenOut);
+ }
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/RootContentDescription.java b/core/java/com/android/internal/widget/remotecompose/core/operations/RootContentDescription.java
index c1ddf63264fa..a6a8a810d2ad 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/RootContentDescription.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/RootContentDescription.java
@@ -25,12 +25,14 @@ import com.android.internal.widget.remotecompose.core.WireBuffer;
import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder;
import com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation;
import com.android.internal.widget.remotecompose.core.semantics.AccessibleComponent;
+import com.android.internal.widget.remotecompose.core.serialize.MapSerializer;
+import com.android.internal.widget.remotecompose.core.serialize.Serializable;
import java.util.List;
/** Describe a content description for the document */
public class RootContentDescription extends Operation
- implements RemoteComposeOperation, AccessibleComponent {
+ implements RemoteComposeOperation, AccessibleComponent, Serializable {
private static final int OP_CODE = Operations.ROOT_CONTENT_DESCRIPTION;
private static final String CLASS_NAME = "RootContentDescription";
int mContentDescription;
@@ -128,4 +130,9 @@ public class RootContentDescription extends Operation
.description("Content description of root")
.field(DocumentedOperation.INT, "id", "id of Int");
}
+
+ @Override
+ public void serialize(MapSerializer serializer) {
+ serializer.add("type", CLASS_NAME).add("contentDescriptionId", mContentDescription);
+ }
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/ShaderData.java b/core/java/com/android/internal/widget/remotecompose/core/operations/ShaderData.java
index 46a32905b96c..5f6162b68e9e 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/ShaderData.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/ShaderData.java
@@ -32,6 +32,8 @@ import com.android.internal.widget.remotecompose.core.VariableSupport;
import com.android.internal.widget.remotecompose.core.WireBuffer;
import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder;
import com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation;
+import com.android.internal.widget.remotecompose.core.serialize.MapSerializer;
+import com.android.internal.widget.remotecompose.core.serialize.Serializable;
import java.util.Arrays;
import java.util.HashMap;
@@ -41,7 +43,7 @@ import java.util.List;
* Operation to deal with bitmap data On getting an Image during a draw call the bitmap is
* compressed and saved in playback the image is decompressed
*/
-public class ShaderData extends Operation implements VariableSupport {
+public class ShaderData extends Operation implements VariableSupport, Serializable {
private static final int OP_CODE = Operations.DATA_SHADER;
private static final String CLASS_NAME = "ShaderData";
int mShaderTextId; // the actual text of a shader
@@ -384,4 +386,15 @@ public class ShaderData extends Operation implements VariableSupport {
public void enable(boolean shaderValid) {
mShaderValid = shaderValid;
}
+
+ @Override
+ public void serialize(MapSerializer serializer) {
+ serializer
+ .add("type", CLASS_NAME)
+ .add("shaderTextId", mShaderTextId)
+ .add("shaderID", mShaderID)
+ .add("uniformRawFloatMap", mUniformRawFloatMap)
+ .add("uniformFloatMap", mUniformFloatMap)
+ .add("uniformBitmapMap", mUniformBitmapMap);
+ }
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/TextAttribute.java b/core/java/com/android/internal/widget/remotecompose/core/operations/TextAttribute.java
index 45cced3f8b45..3e72995de9db 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/TextAttribute.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/TextAttribute.java
@@ -27,11 +27,13 @@ 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.serialize.MapSerializer;
+import com.android.internal.widget.remotecompose.core.serialize.Serializable;
import java.util.List;
/** Operation to Measure Text data */
-public class TextAttribute extends PaintOperation {
+public class TextAttribute extends PaintOperation implements Serializable {
private static final int OP_CODE = Operations.ATTRIBUTE_TEXT;
private static final String CLASS_NAME = "TextMeasure";
public int mId;
@@ -167,4 +169,34 @@ public class TextAttribute extends PaintOperation {
break;
}
}
+
+ @Override
+ public void serialize(MapSerializer serializer) {
+ serializer
+ .add("type", CLASS_NAME)
+ .add("id", mId)
+ .add("textId", mTextId)
+ .add("measureType", typeToString());
+ }
+
+ private String typeToString() {
+ switch (mType) {
+ case MEASURE_WIDTH:
+ return "MEASURE_WIDTH";
+ case MEASURE_HEIGHT:
+ return "MEASURE_HEIGHT";
+ case MEASURE_LEFT:
+ return "MEASURE_LEFT";
+ case MEASURE_RIGHT:
+ return "MEASURE_RIGHT";
+ case MEASURE_TOP:
+ return "MEASURE_TOP";
+ case MEASURE_BOTTOM:
+ return "MEASURE_BOTTOM";
+ case TEXT_LENGTH:
+ return "TEXT_LENGTH";
+ default:
+ return "INVALID_TYPE";
+ }
+ }
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/TextData.java b/core/java/com/android/internal/widget/remotecompose/core/operations/TextData.java
index 5788d8f4da64..419e6d074479 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/TextData.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/TextData.java
@@ -27,11 +27,13 @@ import com.android.internal.widget.remotecompose.core.WireBuffer;
import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder;
import com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation;
import com.android.internal.widget.remotecompose.core.operations.utilities.StringSerializer;
+import com.android.internal.widget.remotecompose.core.serialize.MapSerializer;
+import com.android.internal.widget.remotecompose.core.serialize.Serializable;
import java.util.List;
/** Operation to deal with Text data */
-public class TextData extends Operation implements SerializableToString {
+public class TextData extends Operation implements SerializableToString, Serializable {
private static final int OP_CODE = Operations.DATA_TEXT;
private static final String CLASS_NAME = "TextData";
public final int mTextId;
@@ -131,4 +133,9 @@ public class TextData extends Operation implements SerializableToString {
private String getSerializedName() {
return "DATA_TEXT";
}
+
+ @Override
+ public void serialize(MapSerializer serializer) {
+ serializer.add("type", CLASS_NAME).add("textId", mTextId).add("text", mText);
+ }
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/TextFromFloat.java b/core/java/com/android/internal/widget/remotecompose/core/operations/TextFromFloat.java
index cc0ff025f09b..6b2f49be76f0 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/TextFromFloat.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/TextFromFloat.java
@@ -28,6 +28,8 @@ import com.android.internal.widget.remotecompose.core.WireBuffer;
import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder;
import com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation;
import com.android.internal.widget.remotecompose.core.operations.utilities.StringUtils;
+import com.android.internal.widget.remotecompose.core.serialize.MapSerializer;
+import com.android.internal.widget.remotecompose.core.serialize.Serializable;
import java.util.List;
@@ -36,7 +38,7 @@ import java.util.List;
* [command][textID][before,after][flags] before and after define number of digits before and after
* the decimal point
*/
-public class TextFromFloat extends Operation implements VariableSupport {
+public class TextFromFloat extends Operation implements VariableSupport, Serializable {
private static final int OP_CODE = Operations.TEXT_FROM_FLOAT;
private static final String CLASS_NAME = "TextFromFloat";
public int mTextId;
@@ -209,4 +211,15 @@ public class TextFromFloat extends Operation implements VariableSupport {
public String deepToString(@NonNull String indent) {
return indent + toString();
}
+
+ @Override
+ public void serialize(MapSerializer serializer) {
+ serializer
+ .add("type", CLASS_NAME)
+ .add("textId", mTextId)
+ .add("value", mValue, mOutValue)
+ .add("digitsBefore", mDigitsBefore)
+ .add("digitsAfter", mDigitsAfter)
+ .add("flags", mFlags);
+ }
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/TextLookup.java b/core/java/com/android/internal/widget/remotecompose/core/operations/TextLookup.java
index dceb8b67ec3a..e8865c26db12 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/TextLookup.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/TextLookup.java
@@ -26,6 +26,8 @@ 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.serialize.MapSerializer;
+import com.android.internal.widget.remotecompose.core.serialize.Serializable;
import java.util.List;
@@ -34,7 +36,7 @@ import java.util.List;
* [command][textID][before,after][flags] before and after define number of digits before and after
* the decimal point
*/
-public class TextLookup extends Operation implements VariableSupport {
+public class TextLookup extends Operation implements VariableSupport, Serializable {
private static final int OP_CODE = Operations.TEXT_LOOKUP;
private static final String CLASS_NAME = "TextFromFloat";
public int mTextId;
@@ -150,4 +152,13 @@ public class TextLookup extends Operation implements VariableSupport {
public String deepToString(@NonNull String indent) {
return indent + toString();
}
+
+ @Override
+ public void serialize(MapSerializer serializer) {
+ serializer
+ .add("type", CLASS_NAME)
+ .add("textId", mTextId)
+ .add("dataSetId", mDataSetId)
+ .add("indexId", mIndex, mOutIndex);
+ }
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/TextLookupInt.java b/core/java/com/android/internal/widget/remotecompose/core/operations/TextLookupInt.java
index 823b70656c86..de2025569d46 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/TextLookupInt.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/TextLookupInt.java
@@ -26,11 +26,13 @@ import com.android.internal.widget.remotecompose.core.VariableSupport;
import com.android.internal.widget.remotecompose.core.WireBuffer;
import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder;
import com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation;
+import com.android.internal.widget.remotecompose.core.serialize.MapSerializer;
+import com.android.internal.widget.remotecompose.core.serialize.Serializable;
import java.util.List;
/** Operation convert int index of a list to text */
-public class TextLookupInt extends Operation implements VariableSupport {
+public class TextLookupInt extends Operation implements VariableSupport, Serializable {
private static final int OP_CODE = Operations.TEXT_LOOKUP_INT;
private static final String CLASS_NAME = "TextFromINT";
public int mTextId;
@@ -143,4 +145,13 @@ public class TextLookupInt extends Operation implements VariableSupport {
public String deepToString(@NonNull String indent) {
return indent + toString();
}
+
+ @Override
+ public void serialize(MapSerializer serializer) {
+ serializer
+ .add("type", CLASS_NAME)
+ .add("textId", mTextId)
+ .add("dataSetId", mDataSetId)
+ .add("indexId", mIndex, mOutIndex);
+ }
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/TextMerge.java b/core/java/com/android/internal/widget/remotecompose/core/operations/TextMerge.java
index d69561566b56..262916dd9d0c 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/TextMerge.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/TextMerge.java
@@ -25,11 +25,13 @@ import com.android.internal.widget.remotecompose.core.RemoteContext;
import com.android.internal.widget.remotecompose.core.WireBuffer;
import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder;
import com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation;
+import com.android.internal.widget.remotecompose.core.serialize.MapSerializer;
+import com.android.internal.widget.remotecompose.core.serialize.Serializable;
import java.util.List;
/** Operation to deal with Text data */
-public class TextMerge extends Operation {
+public class TextMerge extends Operation implements Serializable {
private static final int OP_CODE = Operations.TEXT_MERGE;
private static final String CLASS_NAME = "TextMerge";
public int mTextId;
@@ -126,4 +128,13 @@ public class TextMerge extends Operation {
public String deepToString(@NonNull String indent) {
return indent + toString();
}
+
+ @Override
+ public void serialize(MapSerializer serializer) {
+ serializer
+ .add("type", CLASS_NAME)
+ .add("id", mTextId)
+ .add("leftId", mSrcId1)
+ .add("rightId", mSrcId2);
+ }
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/TouchExpression.java b/core/java/com/android/internal/widget/remotecompose/core/operations/TouchExpression.java
index 997628140c46..2591a4c39778 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/TouchExpression.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/TouchExpression.java
@@ -35,6 +35,8 @@ import com.android.internal.widget.remotecompose.core.operations.layout.RootLayo
import com.android.internal.widget.remotecompose.core.operations.utilities.AnimatedFloatExpression;
import com.android.internal.widget.remotecompose.core.operations.utilities.NanMap;
import com.android.internal.widget.remotecompose.core.operations.utilities.touch.VelocityEasing;
+import com.android.internal.widget.remotecompose.core.serialize.MapSerializer;
+import com.android.internal.widget.remotecompose.core.serialize.Serializable;
import java.util.Arrays;
import java.util.List;
@@ -44,7 +46,8 @@ import java.util.List;
* touch behaviours. Including animating to Notched, positions. and tweaking the dynamics of the
* animation.
*/
-public class TouchExpression extends Operation implements VariableSupport, TouchListener {
+public class TouchExpression extends Operation
+ implements VariableSupport, TouchListener, Serializable {
private static final int OP_CODE = Operations.TOUCH_EXPRESSION;
private static final String CLASS_NAME = "TouchExpression";
private float mDefValue;
@@ -709,4 +712,16 @@ public class TouchExpression extends Operation implements VariableSupport, Touch
public String deepToString(@NonNull String indent) {
return indent + toString();
}
+
+ @Override
+ public void serialize(MapSerializer serializer) {
+ serializer
+ .add("type", CLASS_NAME)
+ .add("id", mId)
+ .add("mDefValue", mDefValue, mOutDefValue)
+ .add("min", mMin, mOutMin)
+ .add("max", mMax, mOutMax)
+ .add("mode", mMode)
+ .addFloatExpressionSrc("srcExp", mSrcExp);
+ }
}
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/ImpulseProcess.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ImpulseProcess.java
index f896f3d8ee9c..8c9dd76c9ed5 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ImpulseProcess.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ImpulseProcess.java
@@ -25,12 +25,15 @@ 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.serialize.MapSerializer;
+import com.android.internal.widget.remotecompose.core.serialize.Serializable;
import java.util.ArrayList;
import java.util.List;
/** Represents the repeating part of an Impulse. */
-public class ImpulseProcess extends PaintOperation implements VariableSupport, Container {
+public class ImpulseProcess extends PaintOperation
+ implements VariableSupport, Container, Serializable {
private static final int OP_CODE = Operations.IMPULSE_PROCESS;
private static final String CLASS_NAME = "ImpulseProcess";
@@ -151,4 +154,9 @@ public class ImpulseProcess extends PaintOperation implements VariableSupport, C
public int estimateIterations() {
return 1;
}
+
+ @Override
+ public void serialize(MapSerializer serializer) {
+ serializer.add("type", CLASS_NAME).add("list", mList);
+ }
}
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/LoopOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/LoopOperation.java
index 0f4cf562eae6..2b63cf246555 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/LoopOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/LoopOperation.java
@@ -27,12 +27,16 @@ import com.android.internal.widget.remotecompose.core.WireBuffer;
import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder;
import com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation;
import com.android.internal.widget.remotecompose.core.operations.Utils;
+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 loop of operations */
-public class LoopOperation extends PaintOperation implements Container, VariableSupport {
+public class LoopOperation extends PaintOperation
+ implements Container, VariableSupport, Serializable {
+ private static final String CLASS_NAME = "LoopOperation";
private static final int OP_CODE = Operations.LOOP_START;
@@ -198,4 +202,16 @@ public class LoopOperation extends PaintOperation implements Container, Variable
}
return 10; // this is a generic estmate if the values are variables;
}
+
+ @Override
+ public void serialize(MapSerializer serializer) {
+ serializer
+ .add("type", CLASS_NAME)
+ .add("indexVariableId", mIndexVariableId)
+ .add("until", mUntil, mUntilOut)
+ .add("from", mFrom, mFromOut)
+ .add("step", mStep, mStepOut)
+ .add("mUntilOut", mUntilOut)
+ .add("list", mList);
+ }
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/TextLayout.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/TextLayout.java
index 1cfb50724e0b..d5db74b5ca51 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/TextLayout.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/TextLayout.java
@@ -209,7 +209,7 @@ public class TextLayout extends LayoutManager implements VariableSupport, Access
mPaint.setColor(mColor);
mPaint.setTextSize(mFontSize);
mPaint.setTextStyle(mType, (int) mFontWeight, mFontStyle == 1);
- context.applyPaint(mPaint);
+ context.replacePaint(mPaint);
if (mCachedString == null) {
return;
}
@@ -330,7 +330,7 @@ public class TextLayout extends LayoutManager implements VariableSupport, Access
mPaint.setTextSize(mFontSize);
mPaint.setTextStyle(mType, (int) mFontWeight, mFontStyle == 1);
mPaint.setColor(mColor);
- context.applyPaint(mPaint);
+ context.replacePaint(mPaint);
float[] bounds = new float[4];
if (mCachedString == null) {
return;
@@ -343,7 +343,7 @@ public class TextLayout extends LayoutManager implements VariableSupport, Access
flags |= PaintContext.TEXT_COMPLEX;
}
context.getTextBounds(mTextId, 0, mCachedString.length(), flags, bounds);
- if (bounds[2] - bounds[1] > maxWidth) {
+ if (bounds[2] - bounds[1] > maxWidth && mMaxLines > 1) {
mComputedTextLayout =
context.layoutComplexText(
mTextId,
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/BackgroundModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/BackgroundModifierOperation.java
index dc1b875f0f9c..fd5f8c9cdbe7 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/BackgroundModifierOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/BackgroundModifierOperation.java
@@ -196,7 +196,7 @@ public class BackgroundModifierOperation extends DecoratorModifierOperation {
mPaint.reset();
mPaint.setStyle(PaintBundle.STYLE_FILL);
mPaint.setColor(mR, mG, mB, mA);
- context.applyPaint(mPaint);
+ context.replacePaint(mPaint);
if (mShapeType == ShapeType.RECTANGLE) {
context.drawRect(0f, 0f, mWidth, mHeight);
} else if (mShapeType == ShapeType.CIRCLE) {
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/BorderModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/BorderModifierOperation.java
index 3acbd88329c2..e5f318307a75 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/BorderModifierOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/BorderModifierOperation.java
@@ -252,7 +252,7 @@ public class BorderModifierOperation extends DecoratorModifierOperation {
paint.setColor(mR, mG, mB, mA);
paint.setStrokeWidth(mBorderWidth);
paint.setStyle(PaintBundle.STYLE_STROKE);
- context.applyPaint(paint);
+ context.replacePaint(paint);
if (mShapeType == ShapeType.RECTANGLE) {
context.drawRect(0f, 0f, mWidth, mHeight);
} else {
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/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/RippleModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/RippleModifierOperation.java
index d3004aa73a77..69ace8478e08 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/RippleModifierOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/RippleModifierOperation.java
@@ -126,7 +126,7 @@ public class RippleModifierOperation extends DecoratorModifierOperation implemen
float radius = Math.max(mWidth, mHeight) * tweenRadius;
mPaint.setColor(paintedColor);
- context.applyPaint(mPaint);
+ context.replacePaint(mPaint);
context.clipRect(0f, 0f, mWidth, mHeight);
context.drawCircle(mAnimateRippleX, mAnimateRippleY, radius);
context.restorePaint();
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/paint/PaintBundle.java b/core/java/com/android/internal/widget/remotecompose/core/operations/paint/PaintBundle.java
index 4c7f503e0bf8..0f17b114133d 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/paint/PaintBundle.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/paint/PaintBundle.java
@@ -15,6 +15,8 @@
*/
package com.android.internal.widget.remotecompose.core.operations.paint;
+import static com.android.internal.widget.remotecompose.core.serialize.MapSerializer.orderedOf;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -23,11 +25,16 @@ import com.android.internal.widget.remotecompose.core.RemoteContext;
import com.android.internal.widget.remotecompose.core.VariableSupport;
import com.android.internal.widget.remotecompose.core.WireBuffer;
import com.android.internal.widget.remotecompose.core.operations.Utils;
+import com.android.internal.widget.remotecompose.core.serialize.MapSerializer;
+import com.android.internal.widget.remotecompose.core.serialize.Serializable;
+import java.util.ArrayList;
import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
/** Paint Bundle represents a delta of changes to a paint object */
-public class PaintBundle {
+public class PaintBundle implements Serializable {
@NonNull int[] mArray = new int[200];
@Nullable int[] mOutArray = null;
int mPos = 0;
@@ -337,7 +344,6 @@ public class PaintBundle {
}
}
}
-
len = array[ret++]; // stops
for (int j = 0; j < len; j++) {
registerFloat(array[ret++], context, support);
@@ -1239,4 +1245,201 @@ public class PaintBundle {
return ret;
}
+
+ @Override
+ public void serialize(MapSerializer serializer) {
+ serializer.add("type", "PaintBundle");
+ List<Map<String, Object>> list = new ArrayList<>();
+ int i = 0;
+ while (i < mPos) {
+ int cmd = mArray[i++];
+ int type = cmd & 0xFFFF;
+ switch (type) {
+ case TEXT_SIZE:
+ list.add(orderedOf("type", "TextSize", "size", getVariable(mArray[i++])));
+ break;
+ case TYPEFACE:
+ int style = (cmd >> 16);
+ float weight = (float) (style & 0x3ff);
+ boolean italic = (style >> 10) > 0;
+ int fontFamily = mArray[i++];
+ list.add(orderedOf("type", "FontFamily", "fontFamily", fontFamily));
+ list.add(orderedOf("type", "FontWeight", "weight", weight));
+ list.add(orderedOf("type", "TypeFace", "italic", italic));
+ break;
+ case COLOR:
+ list.add(orderedOf("type", "Color", "color", colorInt(mArray[i++])));
+ break;
+ case COLOR_ID:
+ list.add(orderedOf("type", "ColorId", "id", mArray[i++]));
+ break;
+ case STROKE_WIDTH:
+ list.add(orderedOf("type", "StrokeWidth", "width", getVariable(mArray[i++])));
+ break;
+ case STROKE_MITER:
+ list.add(orderedOf("type", "StrokeMiter", "miter", getVariable(mArray[i++])));
+ break;
+ case STROKE_CAP:
+ list.add(orderedOf("type", "StrokeCap", "cap", cmd >> 16));
+ break;
+ case STYLE:
+ list.add(orderedOf("type", "Style", "style", cmd >> 16));
+ break;
+ case COLOR_FILTER:
+ list.add(
+ orderedOf(
+ "type",
+ "ColorFilter",
+ "color",
+ colorInt(mArray[i++]),
+ "mode",
+ blendModeString(cmd >> 16)));
+ break;
+ case COLOR_FILTER_ID:
+ list.add(
+ orderedOf(
+ "type",
+ "ColorFilterID",
+ "id",
+ mArray[i++],
+ "mode",
+ blendModeString(cmd >> 16)));
+ break;
+ case CLEAR_COLOR_FILTER:
+ list.add(orderedOf("type", "ClearColorFilter"));
+ break;
+ case SHADER:
+ list.add(orderedOf("type", "Shader", "id", mArray[i++]));
+ break;
+ case ALPHA:
+ list.add(orderedOf("type", "Alpha", "alpha", getVariable(mArray[i++])));
+ break;
+ case IMAGE_FILTER_QUALITY:
+ list.add(orderedOf("type", "ImageFilterQuality", "quality", cmd >> 16));
+ break;
+ case BLEND_MODE:
+ list.add(orderedOf("type", "BlendMode", "mode", blendModeString(cmd >> 16)));
+ break;
+ case FILTER_BITMAP:
+ list.add(orderedOf("type", "FilterBitmap", "enabled", !(cmd >> 16 == 0)));
+ break;
+ case STROKE_JOIN:
+ list.add(orderedOf("type", "StrokeJoin", "strokeJoin", cmd >> 16));
+ break;
+ case ANTI_ALIAS:
+ list.add(orderedOf("type", "AntiAlias", "enabled", !(cmd >> 16 == 0)));
+ break;
+ case GRADIENT:
+ i = serializeGradient(cmd, mArray, i, list);
+ }
+ }
+ serializer.add("operations", list);
+ }
+
+ private static Map<String, Object> getVariable(int value) {
+ float fValue = Float.intBitsToFloat(value);
+ if (Float.isNaN(fValue)) {
+ return orderedOf("type", "Variable", "id", Utils.idFromNan(fValue));
+ }
+ return orderedOf("type", "Value", "value", fValue);
+ }
+
+ private static int serializeGradient(
+ int cmd, int[] array, int i, List<Map<String, Object>> list) {
+ int ret = i;
+ int gradientType = (cmd >> 16);
+
+ int len = 0xFF & array[ret++]; // maximum 256 colors
+
+ String[] colors = null;
+ if (len > 0) {
+ colors = new String[len];
+ for (int j = 0; j < colors.length; j++) {
+ colors[j] = colorInt(array[ret++]);
+ }
+ }
+ len = array[ret++];
+ float[] stops = null;
+ if (len > 0) {
+ stops = new float[len];
+ for (int j = 0; j < colors.length; j++) {
+ stops[j] = Float.intBitsToFloat(array[ret++]);
+ }
+ }
+
+ if (colors == null) {
+ return ret;
+ }
+
+ int tileMode;
+ int centerX;
+ int centerY;
+
+ switch (gradientType) {
+ case LINEAR_GRADIENT:
+ int startX = array[ret++];
+ int startY = array[ret++];
+ int endX = array[ret++];
+ int endY = array[ret++];
+ tileMode = array[ret++];
+ list.add(
+ orderedOf(
+ "type",
+ "LinearGradient",
+ "colors",
+ colors,
+ "stops",
+ stops == null ? List.of() : stops,
+ "startX",
+ getVariable(startX),
+ "startY",
+ getVariable(startY),
+ "endX",
+ getVariable(endX),
+ "endY",
+ getVariable(endY),
+ "tileMode",
+ tileMode));
+ break;
+ case RADIAL_GRADIENT:
+ centerX = array[ret++];
+ centerY = array[ret++];
+ int radius = array[ret++];
+ tileMode = array[ret++];
+ list.add(
+ orderedOf(
+ "type",
+ "LinearGradient",
+ "colors",
+ colors,
+ "stops",
+ stops == null ? List.of() : stops,
+ "centerX",
+ getVariable(centerX),
+ "centerY",
+ getVariable(centerY),
+ "radius",
+ getVariable(radius),
+ "tileMode",
+ tileMode));
+ break;
+ case SWEEP_GRADIENT:
+ centerX = array[ret++];
+ centerY = array[ret++];
+ list.add(
+ orderedOf(
+ "type",
+ "LinearGradient",
+ "colors",
+ colors,
+ "stops",
+ stops == null ? List.of() : stops,
+ "centerX",
+ getVariable(centerX),
+ "centerY",
+ getVariable(centerY)));
+ }
+
+ return ret;
+ }
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/FloatAnimation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/FloatAnimation.java
index 65472c262206..cad76059f7a4 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/FloatAnimation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/FloatAnimation.java
@@ -18,8 +18,11 @@ package com.android.internal.widget.remotecompose.core.operations.utilities.easi
import android.annotation.NonNull;
import android.annotation.Nullable;
+import com.android.internal.widget.remotecompose.core.serialize.MapSerializer;
+import com.android.internal.widget.remotecompose.core.serialize.Serializable;
+
/** Support Animation of the FloatExpression */
-public class FloatAnimation extends Easing {
+public class FloatAnimation extends Easing implements Serializable {
float[] mSpec;
// mSpec[0] = duration
// int(mSpec[1]) = num_of_param << 16 | type
@@ -391,4 +394,14 @@ public class FloatAnimation extends Easing {
public float getInitialValue() {
return mInitialValue;
}
+
+ @Override
+ public void serialize(MapSerializer serializer) {
+ serializer
+ .add("type", "FloatAnimation")
+ .add("initialValue", mInitialValue)
+ .add("targetValue", mInitialValue)
+ .add("duration", mInitialValue)
+ .add("easing", Easing.getString(mEasingCurve.getType()));
+ }
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/serialize/MapSerializer.java b/core/java/com/android/internal/widget/remotecompose/core/serialize/MapSerializer.java
index bcdac22f7baa..f9ecf0f4f672 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/serialize/MapSerializer.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/serialize/MapSerializer.java
@@ -17,6 +17,7 @@ package com.android.internal.widget.remotecompose.core.serialize;
import android.annotation.Nullable;
+import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@@ -24,6 +25,24 @@ import java.util.Map;
public interface MapSerializer {
/**
+ * Add a float expression
+ *
+ * @param key
+ * @param value
+ * @return
+ */
+ MapSerializer addFloatExpressionSrc(String key, float[] value);
+
+ /**
+ * Add an int expression
+ *
+ * @param key The key
+ * @param value The int src
+ * @param mask For determining ID from int
+ */
+ MapSerializer addIntExpressionSrc(String key, int[] value, int mask);
+
+ /**
* Add metadata to this map for filtering by the data format generator.
*
* @param value A set of tags to add
@@ -146,4 +165,19 @@ public interface MapSerializer {
* @param value The Enum
*/
<T extends Enum<T>> MapSerializer add(String key, @Nullable Enum<T> value);
+
+ /**
+ * Similar to Map.of, but create a LinkedHashMap preserving insertion order for predictable
+ * serialization.
+ *
+ * @param keysAndValues a even number of items, repeating String key and Object value.
+ * @return A LinkedHashMap.
+ */
+ static LinkedHashMap<String, Object> orderedOf(Object... keysAndValues) {
+ final LinkedHashMap<String, Object> map = new LinkedHashMap<>();
+ for (int i = 0; i < keysAndValues.length; i += 2) {
+ map.put((String) keysAndValues[i], keysAndValues[i + 1]);
+ }
+ return map;
+ }
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/serialize/SerializeTags.java b/core/java/com/android/internal/widget/remotecompose/core/serialize/SerializeTags.java
index 99cac0fc75a8..c29dd98fbd7d 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/serialize/SerializeTags.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/serialize/SerializeTags.java
@@ -22,4 +22,5 @@ public enum SerializeTags {
A11Y,
ACTION,
DRAW_OPERATION,
+ EXPRESSION,
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/types/BooleanConstant.java b/core/java/com/android/internal/widget/remotecompose/core/types/BooleanConstant.java
index 2c874b183a62..cb759a61249a 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/types/BooleanConstant.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/types/BooleanConstant.java
@@ -25,11 +25,15 @@ import com.android.internal.widget.remotecompose.core.RemoteContext;
import com.android.internal.widget.remotecompose.core.WireBuffer;
import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder;
import com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation;
+import com.android.internal.widget.remotecompose.core.serialize.MapSerializer;
+import com.android.internal.widget.remotecompose.core.serialize.Serializable;
import java.util.List;
/** Used to represent a boolean */
-public class BooleanConstant extends Operation {
+public class BooleanConstant extends Operation implements Serializable {
+ private static final String CLASS_NAME = "BooleanConstant";
+
private static final int OP_CODE = Operations.DATA_BOOLEAN;
private boolean mValue = false;
private int mId;
@@ -124,4 +128,9 @@ public class BooleanConstant extends Operation {
.field(DocumentedOperation.INT, "id", "id of Int")
.field(BYTE, "value", "8-bit 0 or 1");
}
+
+ @Override
+ public void serialize(MapSerializer serializer) {
+ serializer.add("type", CLASS_NAME).add("id", mId).add("value", mValue);
+ }
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/types/IntegerConstant.java b/core/java/com/android/internal/widget/remotecompose/core/types/IntegerConstant.java
index 5462d3e069ed..c734f813ede3 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/types/IntegerConstant.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/types/IntegerConstant.java
@@ -25,13 +25,17 @@ import com.android.internal.widget.remotecompose.core.RemoteContext;
import com.android.internal.widget.remotecompose.core.WireBuffer;
import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder;
import com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation;
+import com.android.internal.widget.remotecompose.core.serialize.MapSerializer;
+import com.android.internal.widget.remotecompose.core.serialize.Serializable;
import java.util.List;
/** Represents a single integer typically used for states or named for input into the system */
-public class IntegerConstant extends Operation {
- private int mValue = 0;
- private int mId;
+public class IntegerConstant extends Operation implements Serializable {
+ private static final String CLASS_NAME = "IntegerConstant";
+
+ private final int mValue;
+ private final int mId;
IntegerConstant(int id, int value) {
mId = id;
@@ -116,4 +120,9 @@ public class IntegerConstant extends Operation {
.field(DocumentedOperation.INT, "id", "id of Int")
.field(INT, "value", "32-bit int value");
}
+
+ @Override
+ public void serialize(MapSerializer serializer) {
+ serializer.add("type", CLASS_NAME).add("id", mId).add("value", mValue);
+ }
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/types/LongConstant.java b/core/java/com/android/internal/widget/remotecompose/core/types/LongConstant.java
index 1a3cdb1a96d7..50509f3636b3 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/types/LongConstant.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/types/LongConstant.java
@@ -25,14 +25,18 @@ import com.android.internal.widget.remotecompose.core.RemoteContext;
import com.android.internal.widget.remotecompose.core.WireBuffer;
import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder;
import com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation;
+import com.android.internal.widget.remotecompose.core.serialize.MapSerializer;
+import com.android.internal.widget.remotecompose.core.serialize.Serializable;
import java.util.List;
/** Used to represent a long */
-public class LongConstant extends Operation {
+public class LongConstant extends Operation implements Serializable {
+ private static final String CLASS_NAME = "LongConstant";
+
private static final int OP_CODE = Operations.DATA_LONG;
- private long mValue;
- private int mId;
+ private final long mValue;
+ private final int mId;
public LongConstant(int id, long value) {
mId = id;
@@ -107,4 +111,9 @@ public class LongConstant extends Operation {
.field(DocumentedOperation.INT, "id", "id of Int")
.field(LONG, "value", "The long Value");
}
+
+ @Override
+ public void serialize(MapSerializer serializer) {
+ serializer.add("type", CLASS_NAME).add("id", mId).add("value", mValue);
+ }
}
diff --git a/core/java/com/android/internal/widget/remotecompose/player/RemoteComposePlayer.java b/core/java/com/android/internal/widget/remotecompose/player/RemoteComposePlayer.java
index 77f4b6a83eef..1d1e579ebc2f 100644
--- a/core/java/com/android/internal/widget/remotecompose/player/RemoteComposePlayer.java
+++ b/core/java/com/android/internal/widget/remotecompose/player/RemoteComposePlayer.java
@@ -29,7 +29,6 @@ import android.hardware.SensorManager;
import android.util.AttributeSet;
import android.util.Log;
import android.util.TypedValue;
-import android.view.HapticFeedbackConstants;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.HorizontalScrollView;
@@ -596,29 +595,34 @@ public class RemoteComposePlayer extends FrameLayout implements RemoteContextAwa
}
}
- private static int[] sHapticTable = {
- HapticFeedbackConstants.NO_HAPTICS,
- HapticFeedbackConstants.LONG_PRESS,
- HapticFeedbackConstants.VIRTUAL_KEY,
- HapticFeedbackConstants.KEYBOARD_TAP,
- HapticFeedbackConstants.CLOCK_TICK,
- HapticFeedbackConstants.CONTEXT_CLICK,
- HapticFeedbackConstants.KEYBOARD_PRESS,
- HapticFeedbackConstants.KEYBOARD_RELEASE,
- HapticFeedbackConstants.VIRTUAL_KEY_RELEASE,
- HapticFeedbackConstants.TEXT_HANDLE_MOVE,
- HapticFeedbackConstants.GESTURE_START,
- HapticFeedbackConstants.GESTURE_END,
- HapticFeedbackConstants.CONFIRM,
- HapticFeedbackConstants.REJECT,
- HapticFeedbackConstants.TOGGLE_ON,
- HapticFeedbackConstants.TOGGLE_OFF,
- HapticFeedbackConstants.GESTURE_THRESHOLD_ACTIVATE,
- HapticFeedbackConstants.GESTURE_THRESHOLD_DEACTIVATE,
- HapticFeedbackConstants.DRAG_START,
- HapticFeedbackConstants.SEGMENT_TICK,
- HapticFeedbackConstants.SEGMENT_FREQUENT_TICK,
- };
+ private static final int[] sHapticTable;
+
+ static {
+ sHapticTable =
+ new int[] {
+ android.view.HapticFeedbackConstants.NO_HAPTICS,
+ android.view.HapticFeedbackConstants.LONG_PRESS,
+ android.view.HapticFeedbackConstants.VIRTUAL_KEY,
+ android.view.HapticFeedbackConstants.KEYBOARD_TAP,
+ android.view.HapticFeedbackConstants.CLOCK_TICK,
+ android.view.HapticFeedbackConstants.CONTEXT_CLICK,
+ android.view.HapticFeedbackConstants.KEYBOARD_PRESS,
+ android.view.HapticFeedbackConstants.KEYBOARD_RELEASE,
+ android.view.HapticFeedbackConstants.VIRTUAL_KEY_RELEASE,
+ android.view.HapticFeedbackConstants.TEXT_HANDLE_MOVE,
+ android.view.HapticFeedbackConstants.GESTURE_START,
+ android.view.HapticFeedbackConstants.GESTURE_END,
+ android.view.HapticFeedbackConstants.CONFIRM,
+ android.view.HapticFeedbackConstants.REJECT,
+ android.view.HapticFeedbackConstants.TOGGLE_ON,
+ android.view.HapticFeedbackConstants.TOGGLE_OFF,
+ android.view.HapticFeedbackConstants.GESTURE_THRESHOLD_ACTIVATE,
+ android.view.HapticFeedbackConstants.GESTURE_THRESHOLD_DEACTIVATE,
+ android.view.HapticFeedbackConstants.DRAG_START,
+ android.view.HapticFeedbackConstants.SEGMENT_TICK,
+ android.view.HapticFeedbackConstants.SEGMENT_FREQUENT_TICK,
+ };
+ }
private void provideHapticFeedback(int type) {
performHapticFeedback(sHapticTable[type % sHapticTable.length]);
diff --git a/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidPaintContext.java b/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidPaintContext.java
index 6b1a30a7545c..ac4a294b5e5e 100644
--- a/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidPaintContext.java
+++ b/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidPaintContext.java
@@ -246,6 +246,12 @@ public class AndroidPaintContext extends PaintContext {
}
@Override
+ public void replacePaint(PaintBundle paintBundle) {
+ mPaint.reset();
+ applyPaint(paintBundle);
+ }
+
+ @Override
public void drawRoundRect(
float left, float top, float right, float bottom, float radiusX, float radiusY) {
mCanvas.drawRoundRect(left, top, right, bottom, radiusX, radiusY, mPaint);
diff --git a/core/java/com/android/internal/widget/remotecompose/player/platform/RemoteComposeCanvas.java b/core/java/com/android/internal/widget/remotecompose/player/platform/RemoteComposeCanvas.java
index f76794fc0372..4d2dd05ca603 100644
--- a/core/java/com/android/internal/widget/remotecompose/player/platform/RemoteComposeCanvas.java
+++ b/core/java/com/android/internal/widget/remotecompose/player/platform/RemoteComposeCanvas.java
@@ -31,6 +31,7 @@ import android.widget.FrameLayout;
import com.android.internal.widget.remotecompose.core.CoreDocument;
import com.android.internal.widget.remotecompose.core.RemoteContext;
+import com.android.internal.widget.remotecompose.core.operations.Header;
import com.android.internal.widget.remotecompose.core.operations.RootContentBehavior;
import com.android.internal.widget.remotecompose.core.operations.Theme;
import com.android.internal.widget.remotecompose.player.RemoteComposeDocument;
@@ -105,10 +106,16 @@ public class RemoteComposeCanvas extends FrameLayout implements View.OnAttachSta
mARContext.setDensity(mDensity);
mARContext.setUseChoreographer(true);
setContentDescription(mDocument.getDocument().getContentDescription());
+
updateClickAreas();
requestLayout();
mARContext.loadFloat(RemoteContext.ID_TOUCH_EVENT_TIME, -Float.MAX_VALUE);
invalidate();
+ Integer fps = (Integer) mDocument.getDocument().getProperty(Header.DOC_DESIRED_FPS);
+ if (fps != null && fps > 0) {
+ mMaxFrameRate = fps;
+ mMaxFrameDelay = (long) (1000 / mMaxFrameRate);
+ }
}
@Override
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_AudioRecord.cpp b/core/jni/android_media_AudioRecord.cpp
index 102edc944c22..22298b3f9525 100644
--- a/core/jni/android_media_AudioRecord.cpp
+++ b/core/jni/android_media_AudioRecord.cpp
@@ -598,7 +598,7 @@ static jintArray android_media_AudioRecord_getRoutedDeviceIds(JNIEnv *env, jobje
}
jint *values = env->GetIntArrayElements(result, 0);
for (unsigned int i = 0; i < deviceIds.size(); i++) {
- values[i++] = static_cast<jint>(deviceIds[i]);
+ values[i] = static_cast<jint>(deviceIds[i]);
}
env->ReleaseIntArrayElements(result, values, 0);
return result;
diff --git a/core/jni/android_media_AudioTrack.cpp b/core/jni/android_media_AudioTrack.cpp
index 3fc1a02f46b6..3cf5d5fdde24 100644
--- a/core/jni/android_media_AudioTrack.cpp
+++ b/core/jni/android_media_AudioTrack.cpp
@@ -1204,7 +1204,7 @@ static jintArray android_media_AudioTrack_getRoutedDeviceIds(JNIEnv *env, jobjec
}
jint *values = env->GetIntArrayElements(result, 0);
for (unsigned int i = 0; i < deviceIds.size(); i++) {
- values[i++] = static_cast<jint>(deviceIds[i]);
+ values[i] = static_cast<jint>(deviceIds[i]);
}
env->ReleaseIntArrayElements(result, values, 0);
return result;
diff --git a/core/proto/android/nfc/OWNERS b/core/proto/android/nfc/OWNERS
index ca16721eacc1..36823aee4dbb 100644
--- a/core/proto/android/nfc/OWNERS
+++ b/core/proto/android/nfc/OWNERS
@@ -1 +1 @@
-include platform/packages/apps/Nfc:/OWNERS \ No newline at end of file
+include platform/packages/modules/Nfc:/OWNERS \ No newline at end of file
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 51049889ecd6..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 />
@@ -7688,12 +7864,12 @@
<permission android:name="android.permission.ACCESS_SMARTSPACE"
android:protectionLevel="signature|privileged|development" />
- <!-- @SystemApi Allows an application to start a contextual search.
- @FlaggedApi("android.app.contextualsearch.flags.enable_service")
- @hide <p>Not for use by third-party applications.</p> -->
+ <!-- @SystemApi Allows a system application to start a contextual search.
+ Other applications can start a contextual search only if they have a
+ foreground activity.
+ @hide <p>Not for use by third-party applications.</p> -->
<permission android:name="android.permission.ACCESS_CONTEXTUAL_SEARCH"
- android:protectionLevel="signature|privileged"
- android:featureFlag="android.app.contextualsearch.flags.enable_service"/>
+ android:protectionLevel="signature|privileged" />
<!-- @SystemApi Allows an application to manage the wallpaper effects
generation service.
diff --git a/core/res/res/layout/notification_2025_template_header.xml b/core/res/res/layout/notification_2025_template_header.xml
index 72b3798e0780..5aae67802af9 100644
--- a/core/res/res/layout/notification_2025_template_header.xml
+++ b/core/res/res/layout/notification_2025_template_header.xml
@@ -59,7 +59,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentStart="true"
- android:layout_toStartOf="@id/expand_button"
+ android:layout_toStartOf="@id/expand_button_container"
android:layout_alignWithParentIfMissing="true"
android:layout_marginVertical="@dimen/notification_2025_margin"
android:clipChildren="false"
@@ -81,12 +81,30 @@
android:focusable="false"
/>
- <include layout="@layout/notification_2025_expand_button"
+
+ <LinearLayout
+ android:id="@+id/expand_button_container"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_gravity="top|end"
- android:layout_alignParentEnd="true" />
+ android:layout_alignParentEnd="true"
+ android:orientation="vertical"
+ >
+ <FrameLayout
+ android:id="@+id/expand_button_spacer"
+ android:layout_width="@dimen/notification_2025_expand_button_pill_width"
+ android:layout_height="@dimen/notification_2025_expand_button_pill_height"
+ android:layout_centerVertical="true"
+ android:layout_alignParentEnd="true"
+ android:layout_margin="@dimen/notification_2025_margin"
+ android:visibility="gone" />
+
+ <include layout="@layout/notification_2025_expand_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="top|end"
+ android:layout_alignParentEnd="true" />
+ </LinearLayout>
<include layout="@layout/notification_close_button"
android:id="@+id/close_button"
android:layout_width="@dimen/notification_close_button_size"
diff --git a/core/res/res/layout/notification_template_header.xml b/core/res/res/layout/notification_template_header.xml
index 57959361bd48..b7fd737a4e39 100644
--- a/core/res/res/layout/notification_template_header.xml
+++ b/core/res/res/layout/notification_template_header.xml
@@ -62,7 +62,7 @@
android:layout_height="match_parent"
android:layout_alignParentStart="true"
android:layout_centerVertical="true"
- android:layout_toStartOf="@id/expand_button"
+ android:layout_toStartOf="@id/expand_button_container"
android:layout_alignWithParentIfMissing="true"
android:clipChildren="false"
android:gravity="center_vertical"
@@ -83,12 +83,28 @@
android:focusable="false"
/>
- <include layout="@layout/notification_expand_button"
+ <LinearLayout
+ android:id="@+id/expand_button_container"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_centerVertical="true"
- android:layout_alignParentEnd="true" />
+ android:layout_alignParentEnd="true"
+ android:orientation="vertical"
+ >
+ <FrameLayout
+ android:id="@+id/expand_button_spacer"
+ android:layout_width="@dimen/notification_expand_button_pill_height"
+ android:layout_height="@dimen/notification_header_height"
+ android:layout_centerVertical="true"
+ android:layout_alignParentEnd="true"
+ android:layout_marginHorizontal="16dp"
+ android:visibility="gone" />
+ <include layout="@layout/notification_expand_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_centerVertical="true"
+ android:layout_alignParentEnd="true" />
+ </LinearLayout>
<include layout="@layout/notification_close_button"
android:id="@+id/close_button"
android:layout_width="@dimen/notification_close_button_size"
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index a1f85c380a95..66111785af4f 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -3313,7 +3313,6 @@
<!-- Provides brief supplemental information for the view, such as the purpose of
the view when that purpose is not conveyed within its textual representation.
This property is used primarily for accessibility. -->
- <!-- @FlaggedApi("android.view.accessibility.supplemental_description") -->
<attr name="supplementalDescription" format="string" localization="suggested" />
<!-- Sets the id of a view that screen readers are requested to visit after this view.
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 17acf9aed278..1a311d572e0b 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -60,6 +60,7 @@
<item><xliff:g id="id">@string/status_bar_oem_satellite</xliff:g></item>
<item><xliff:g id="id">@string/status_bar_wifi</xliff:g></item>
<item><xliff:g id="id">@string/status_bar_hotspot</xliff:g></item>
+ <item><xliff:g id="id">@string/status_bar_stacked_mobile</xliff:g></item>
<item><xliff:g id="id">@string/status_bar_mobile</xliff:g></item>
<item><xliff:g id="id">@string/status_bar_airplane</xliff:g></item>
<item><xliff:g id="id">@string/status_bar_battery</xliff:g></item>
@@ -94,6 +95,7 @@
<string translatable="false" name="status_bar_secure">secure</string>
<string translatable="false" name="status_bar_clock">clock</string>
<string translatable="false" name="status_bar_mobile">mobile</string>
+ <string translatable="false" name="status_bar_stacked_mobile">stacked_mobile</string>
<string translatable="false" name="status_bar_vpn">vpn</string>
<string translatable="false" name="status_bar_ethernet">ethernet</string>
<string translatable="false" name="status_bar_microphone">microphone</string>
diff --git a/core/res/res/values/public-final.xml b/core/res/res/values/public-final.xml
index af1e5123096d..61e2a28562d2 100644
--- a/core/res/res/values/public-final.xml
+++ b/core/res/res/values/public-final.xml
@@ -3941,7 +3941,7 @@
<!-- @FlaggedApi("android.permission.flags.replace_body_sensor_permission_enabled")
@hide @SystemApi -->
<public name="backgroundPermission"/>
- <!-- @FlaggedApi(android.view.accessibility.supplemental_description) -->
+ <!-- @FlaggedApi("android.view.accessibility.supplemental_description") -->
<public name="supplementalDescription"/>
<!-- @FlaggedApi("android.security.enable_intent_matching_flags") -->
<public name="intentMatchingFlags"/>
@@ -3969,7 +3969,7 @@
<!-- @FlaggedApi("android.permission.flags.replace_body_sensor_permission_enabled")
@hide @SystemApi -->
<public type="attr" name="backgroundPermission" id="0x010106a7" />
- <!-- @FlaggedApi(android.view.accessibility.supplemental_description) -->
+ <!-- @FlaggedApi("android.view.accessibility.supplemental_description") -->
<public type="attr" name="supplementalDescription" id="0x010106a8" />
<!-- @FlaggedApi("android.security.enable_intent_matching_flags") -->
<public type="attr" name="intentMatchingFlags" id="0x010106a9" />
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 da6fb3b58f21..c62732d36038 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3235,6 +3235,8 @@
<java-symbol type="id" name="header_text" />
<java-symbol type="id" name="header_text_secondary" />
<java-symbol type="id" name="expand_button" />
+ <java-symbol type="id" name="expand_button_spacer" />
+ <java-symbol type="id" name="expand_button_container" />
<java-symbol type="id" name="expand_button_pill" />
<java-symbol type="id" name="expand_button_pill_colorized_layer" />
<java-symbol type="id" name="expand_button_number" />
@@ -3318,6 +3320,7 @@
<java-symbol type="string" name="status_bar_no_calling" />
<java-symbol type="string" name="status_bar_call_strength" />
<java-symbol type="string" name="status_bar_mobile" />
+ <java-symbol type="string" name="status_bar_stacked_mobile" />
<java-symbol type="string" name="status_bar_ethernet" />
<java-symbol type="string" name="status_bar_vpn" />
<java-symbol type="string" name="status_bar_microphone" />
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/core/tests/coretests/src/com/android/internal/util/RateLimitingCacheTest.java b/core/tests/coretests/src/com/android/internal/util/RateLimitingCacheTest.java
index 2edab6268bec..82d90fc71cb3 100644
--- a/core/tests/coretests/src/com/android/internal/util/RateLimitingCacheTest.java
+++ b/core/tests/coretests/src/com/android/internal/util/RateLimitingCacheTest.java
@@ -21,8 +21,7 @@ import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import android.os.SystemClock;
-
-import android.os.SystemClock;
+import android.platform.test.annotations.Presubmit;
import androidx.test.runner.AndroidJUnit4;
@@ -36,6 +35,7 @@ import org.junit.runner.RunWith;
/**
* Test the RateLimitingCache class.
*/
+@Presubmit
@RunWith(AndroidJUnit4.class)
public class RateLimitingCacheTest {
@@ -56,13 +56,13 @@ public class RateLimitingCacheTest {
*/
@Test
public void testTtl_Zero() {
- RateLimitingCache<Integer> s = new RateLimitingCache<>(0);
+ TestRateLimitingCache<Integer> s = new TestRateLimitingCache<>(0);
int first = s.get(mFetcher);
assertEquals(first, 0);
int second = s.get(mFetcher);
assertEquals(second, 1);
- SystemClock.sleep(20);
+ s.advanceTime(20);
int third = s.get(mFetcher);
assertEquals(third, 2);
}
@@ -73,14 +73,14 @@ public class RateLimitingCacheTest {
*/
@Test
public void testTtl_100() {
- RateLimitingCache<Integer> s = new RateLimitingCache<>(100);
+ TestRateLimitingCache<Integer> s = new TestRateLimitingCache<>(100);
int first = s.get(mFetcher);
assertEquals(first, 0);
int second = s.get(mFetcher);
// Too early to change
assertEquals(second, 0);
- SystemClock.sleep(150);
+ s.advanceTime(150);
int third = s.get(mFetcher);
// Changed by now
assertEquals(third, 1);
@@ -95,11 +95,11 @@ public class RateLimitingCacheTest {
*/
@Test
public void testTtl_Negative() {
- RateLimitingCache<Integer> s = new RateLimitingCache<>(-1);
+ TestRateLimitingCache<Integer> s = new TestRateLimitingCache<>(-1);
int first = s.get(mFetcher);
assertEquals(first, 0);
- SystemClock.sleep(200);
+ s.advanceTime(200);
// Should return the original value every time
int second = s.get(mFetcher);
assertEquals(second, 0);
@@ -111,7 +111,7 @@ public class RateLimitingCacheTest {
*/
@Test
public void testTtl_Spam() {
- RateLimitingCache<Integer> s = new RateLimitingCache<>(100);
+ TestRateLimitingCache<Integer> s = new TestRateLimitingCache<>(100);
assertCount(s, 1000, 7, 15);
}
@@ -121,28 +121,13 @@ public class RateLimitingCacheTest {
*/
@Test
public void testRate_10hz() {
- RateLimitingCache<Integer> s = new RateLimitingCache<>(1000, 10);
+ TestRateLimitingCache<Integer> s = new TestRateLimitingCache<>(1000, 10);
// At 10 per second, 2 seconds should not exceed about 30, assuming overlap into left and
// right windows that allow 10 each
assertCount(s, 2000, 20, 33);
}
/**
- * Test that using a different timebase works correctly.
- */
- @Test
- public void testTimebase() {
- RateLimitingCache<Integer> s = new RateLimitingCache<>(1000, 10) {
- @Override
- protected long getTime() {
- return SystemClock.elapsedRealtime() / 2;
- }
- };
- // Timebase is moving at half the speed, so only allows for 1 second worth in 2 seconds.
- assertCount(s, 2000, 10, 22);
- }
-
- /**
* Exercises concurrent access to the cache.
*/
@Test
@@ -287,15 +272,37 @@ public class RateLimitingCacheTest {
* @param minCount the lower end of the expected number of fetches, with a margin for error
* @param maxCount the higher end of the expected number of fetches, with a margin for error
*/
- private void assertCount(RateLimitingCache<Integer> cache, long period,
+ private void assertCount(TestRateLimitingCache<Integer> cache, long period,
int minCount, int maxCount) {
- long startTime = SystemClock.elapsedRealtime();
- while (SystemClock.elapsedRealtime() < startTime + period) {
+ long startTime = cache.getTime();
+ while (cache.getTime() < startTime + period) {
int value = cache.get(mFetcher);
- SystemClock.sleep(5);
+ cache.advanceTime(5);
}
int latest = cache.get(mFetcher);
assertTrue("Latest should be between " + minCount + " and " + maxCount
+ " but is " + latest, latest <= maxCount && latest >= minCount);
}
+
+ private static class TestRateLimitingCache<Value> extends RateLimitingCache<Value> {
+ // Start at a non-zero time to avoid confusion with uninitialized state.
+ private long mTime = 1;
+
+ public TestRateLimitingCache(long periodMillis) {
+ super(periodMillis);
+ }
+
+ public TestRateLimitingCache(long periodMillis, int count) {
+ super(periodMillis, count);
+ }
+
+ public void advanceTime(long time) {
+ mTime += time;
+ }
+
+ @Override
+ public long getTime() {
+ return mTime;
+ }
+ }
}
diff --git a/data/etc/platform.xml b/data/etc/platform.xml
index baaec86828eb..ca20aebf95d8 100644
--- a/data/etc/platform.xml
+++ b/data/etc/platform.xml
@@ -210,6 +210,7 @@
<assign-permission name="android.permission.STATSCOMPANION" uid="statsd" />
<assign-permission name="android.permission.UPDATE_APP_OPS_STATS" uid="statsd" />
+ <assign-permission name="android.permission.REGISTER_STATS_PULL_ATOM" uid="mmd" />
<assign-permission name="android.permission.REGISTER_STATS_PULL_ATOM" uid="gpu_service" />
<assign-permission name="android.permission.REGISTER_STATS_PULL_ATOM" uid="keystore" />
diff --git a/data/fonts/script/test/test_commandline.py b/data/fonts/script/test/test_commandline.py
index 75318cc10e68..dbcba417c5e1 100755
--- a/data/fonts/script/test/test_commandline.py
+++ b/data/fonts/script/test/test_commandline.py
@@ -75,20 +75,20 @@ class CommandlineTest(unittest.TestCase):
functools.partial(CommandlineTest.fileread, filemap),
)
- self.assertEquals("output.xml", args.outfile)
+ self.assertEqual("output.xml", args.outfile)
- self.assertEquals(1, len(args.aliases))
- self.assertEquals("sans-serif-thin", args.aliases[0].name)
- self.assertEquals("sans-serif", args.aliases[0].to)
- self.assertEquals(100, args.aliases[0].weight)
+ self.assertEqual(1, len(args.aliases))
+ self.assertEqual("sans-serif-thin", args.aliases[0].name)
+ self.assertEqual("sans-serif", args.aliases[0].to)
+ self.assertEqual(100, args.aliases[0].weight)
- self.assertEquals(2, len(args.fallback))
+ self.assertEqual(2, len(args.fallback))
# Order is not a part of expectation. Check the expected lang is included.
langs = set(["und-Arab", "und-Ethi"])
self.assertTrue(args.fallback[0].lang in langs)
self.assertTrue(args.fallback[1].lang in langs)
- self.assertEquals(3, len(args.families))
+ self.assertEqual(3, len(args.families))
# Order is not a part of expectation. Check the expected name is included.
names = set(["sans-serif", "sans-serif-condensed", "roboto-flex"])
self.assertTrue(args.families[0].name in names)
diff --git a/data/fonts/script/test/test_xml_builder.py b/data/fonts/script/test/test_xml_builder.py
index 24a033b43cbc..f15c51379b46 100755
--- a/data/fonts/script/test/test_xml_builder.py
+++ b/data/fonts/script/test/test_xml_builder.py
@@ -328,16 +328,16 @@ class XmlBuilderTest(unittest.TestCase):
self.expect_xml(xml)
def expect_xml(self, xml):
- self.assertEquals("sans-serif", xml.families[0].name) # _SANS_SERIF
- self.assertEquals("serif", xml.families[1].name) # _SERIF
- self.assertEquals("und-Arab", xml.families[2].lang) # __ARABIC
- self.assertEquals("elegant", xml.families[2].variant)
- self.assertEquals("und-Arab", xml.families[3].lang) # _ARABIC_UI
- self.assertEquals("zh-Hans", xml.families[4].lang) # _HANS (_HANS_SERIF)
- self.assertEquals(2, len(xml.families[4].fonts))
- self.assertEquals("serif", xml.families[4].fonts[1].fallback_for)
- self.assertEquals("ja", xml.families[5].lang) # _HANS (_HANS_SERIF)
- self.assertEquals("serif", xml.families[5].fonts[1].fallback_for)
+ self.assertEqual("sans-serif", xml.families[0].name) # _SANS_SERIF
+ self.assertEqual("serif", xml.families[1].name) # _SERIF
+ self.assertEqual("und-Arab", xml.families[2].lang) # __ARABIC
+ self.assertEqual("elegant", xml.families[2].variant)
+ self.assertEqual("und-Arab", xml.families[3].lang) # _ARABIC_UI
+ self.assertEqual("zh-Hans", xml.families[4].lang) # _HANS (_HANS_SERIF)
+ self.assertEqual(2, len(xml.families[4].fonts))
+ self.assertEqual("serif", xml.families[4].fonts[1].fallback_for)
+ self.assertEqual("ja", xml.families[5].lang) # _HANS (_HANS_SERIF)
+ self.assertEqual("serif", xml.families[5].fonts[1].fallback_for)
if __name__ == "__main__":
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/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleTaskViewListenerTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleTaskViewListenerTest.kt
new file mode 100644
index 000000000000..9ebc3d78b3a7
--- /dev/null
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleTaskViewListenerTest.kt
@@ -0,0 +1,491 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.bubbles
+
+import android.app.Notification
+import android.app.PendingIntent
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.content.pm.ShortcutInfo
+import android.graphics.drawable.Icon
+import android.os.UserHandle
+import android.service.notification.NotificationListenerService.Ranking
+import android.service.notification.StatusBarNotification
+import android.view.View
+import android.widget.FrameLayout
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
+import com.android.internal.protolog.ProtoLog
+import com.android.wm.shell.R
+import com.android.wm.shell.bubbles.Bubbles.BubbleMetadataFlagListener
+import com.android.wm.shell.common.TestShellExecutor
+import com.android.wm.shell.taskview.TaskView
+import com.android.wm.shell.taskview.TaskViewController
+import com.android.wm.shell.taskview.TaskViewTaskController
+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.never
+import org.mockito.Mockito.reset
+import org.mockito.kotlin.any
+import org.mockito.kotlin.anyOrNull
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
+
+/**
+ * Tests for [BubbleTaskViewListener].
+ */
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class BubbleTaskViewListenerTest {
+
+ private val context = ApplicationProvider.getApplicationContext<Context>()
+
+ private var taskViewController = mock<TaskViewController>()
+ private var listenerCallback = mock<BubbleTaskViewListener.Callback>()
+ private var expandedViewManager = mock<BubbleExpandedViewManager>()
+
+ private lateinit var bubbleTaskViewListener: BubbleTaskViewListener
+ private lateinit var taskView: TaskView
+ private lateinit var bubbleTaskView: BubbleTaskView
+ private lateinit var parentView: ViewPoster
+ private lateinit var mainExecutor: TestShellExecutor
+ private lateinit var bgExecutor: TestShellExecutor
+
+ @Before
+ fun setUp() {
+ ProtoLog.REQUIRE_PROTOLOGTOOL = false
+ ProtoLog.init()
+
+ parentView = ViewPoster(context)
+ mainExecutor = TestShellExecutor()
+ bgExecutor = TestShellExecutor()
+
+ taskView = TaskView(context, taskViewController, mock<TaskViewTaskController>())
+ bubbleTaskView = BubbleTaskView(taskView, mainExecutor)
+
+ bubbleTaskViewListener =
+ BubbleTaskViewListener(
+ context,
+ bubbleTaskView,
+ parentView,
+ expandedViewManager,
+ listenerCallback
+ )
+ }
+
+ @Test
+ fun createBubbleTaskViewListener_withCreatedTaskView() {
+ // Make the bubbleTaskView look like it's been created
+ val taskId = 123
+ bubbleTaskView.listener.onTaskCreated(taskId, mock<ComponentName>())
+ reset(listenerCallback)
+
+ bubbleTaskViewListener =
+ BubbleTaskViewListener(
+ context,
+ bubbleTaskView,
+ parentView,
+ expandedViewManager,
+ listenerCallback
+ )
+
+ assertThat(bubbleTaskView.delegateListener).isEqualTo(bubbleTaskViewListener)
+ assertThat(bubbleTaskViewListener.taskView).isEqualTo(bubbleTaskView.taskView)
+
+ verify(listenerCallback).onTaskCreated()
+ assertThat(bubbleTaskViewListener.taskId).isEqualTo(taskId)
+ }
+
+ @Test
+ fun createBubbleTaskViewListener() {
+ bubbleTaskViewListener =
+ BubbleTaskViewListener(
+ context,
+ bubbleTaskView,
+ parentView,
+ expandedViewManager,
+ listenerCallback
+ )
+
+ assertThat(bubbleTaskView.delegateListener).isEqualTo(bubbleTaskViewListener)
+ assertThat(bubbleTaskViewListener.taskView).isEqualTo(bubbleTaskView.taskView)
+ verify(listenerCallback, never()).onTaskCreated()
+ }
+
+ @Test
+ fun onInitialized_pendingIntentChatBubble() {
+ val target = Intent(context, TestActivity::class.java)
+ val pendingIntent = PendingIntent.getActivity(context, 0, target,
+ PendingIntent.FLAG_MUTABLE)
+
+ val b = createChatBubble("key", pendingIntent)
+ bubbleTaskViewListener.setBubble(b)
+
+ assertThat(b.isChat).isTrue()
+ // Has shortcut info
+ assertThat(b.shortcutInfo).isNotNull()
+ // But it didn't use that on bubble metadata
+ assertThat(b.metadataShortcutId).isNull()
+
+ getInstrumentation().runOnMainSync {
+ bubbleTaskViewListener.onInitialized()
+ }
+ getInstrumentation().waitForIdleSync()
+
+ // ..so it's pending intent-based, and launches that
+ assertThat(b.isPendingIntentActive).isTrue()
+ verify(taskViewController).startActivity(any(), eq(pendingIntent), any(), any(), any())
+ }
+
+ @Test
+ fun onInitialized_shortcutChatBubble() {
+ val shortcutInfo = ShortcutInfo.Builder(context)
+ .setId("mockShortcutId")
+ .build()
+ val b = createChatBubble("key", shortcutInfo)
+ bubbleTaskViewListener.setBubble(b)
+
+ assertThat(b.isChat).isTrue()
+ assertThat(b.shortcutInfo).isNotNull()
+ // Chat bubble using a shortcut
+ assertThat(b.metadataShortcutId).isNotNull()
+
+ getInstrumentation().runOnMainSync {
+ bubbleTaskViewListener.onInitialized()
+ }
+ getInstrumentation().waitForIdleSync()
+
+ assertThat(b.isPendingIntentActive).isFalse()
+ verify(taskViewController).startShortcutActivity(any(), eq(shortcutInfo), any(), any())
+ }
+
+ @Test
+ fun onInitialized_appBubble() {
+ val b = createAppBubble()
+ bubbleTaskViewListener.setBubble(b)
+
+ assertThat(b.isApp).isTrue()
+
+ getInstrumentation().runOnMainSync {
+ bubbleTaskViewListener.onInitialized()
+ }
+ getInstrumentation().waitForIdleSync()
+
+ assertThat(b.isPendingIntentActive).isFalse()
+ verify(taskViewController).startActivity(any(), any(), anyOrNull(), any(), any())
+ }
+
+ @Test
+ fun onInitialized_preparingTransition() {
+ val b = createAppBubble()
+ bubbleTaskViewListener.setBubble(b)
+ taskView = Mockito.spy(taskView)
+ val preparingTransition = mock<BubbleTransitions.BubbleTransition>()
+ b.preparingTransition = preparingTransition
+
+ getInstrumentation().runOnMainSync {
+ bubbleTaskViewListener.onInitialized()
+ }
+ getInstrumentation().waitForIdleSync()
+
+ verify(preparingTransition).surfaceCreated()
+ }
+
+ @Test
+ fun onInitialized_destroyed() {
+ val b = createAppBubble()
+ bubbleTaskViewListener.setBubble(b)
+
+ assertThat(b.isApp).isTrue()
+
+ getInstrumentation().runOnMainSync {
+ bubbleTaskViewListener.onReleased()
+ bubbleTaskViewListener.onInitialized()
+ }
+ getInstrumentation().waitForIdleSync()
+
+ verify(taskViewController, never()).startActivity(any(), any(), anyOrNull(), any(), any())
+ }
+
+ @Test
+ fun onInitialized_initialized() {
+ val b = createAppBubble()
+ bubbleTaskViewListener.setBubble(b)
+
+ assertThat(b.isApp).isTrue()
+
+ getInstrumentation().runOnMainSync {
+ bubbleTaskViewListener.onInitialized()
+ }
+ getInstrumentation().waitForIdleSync()
+
+ reset(taskViewController)
+
+ getInstrumentation().runOnMainSync {
+ bubbleTaskViewListener.onInitialized()
+ }
+ // Already initialized, so no activity should be started.
+ verify(taskViewController, never()).startActivity(any(), any(), anyOrNull(), any(), any())
+ }
+
+ @Test
+ fun onTaskCreated() {
+ val b = createAppBubble()
+ bubbleTaskViewListener.setBubble(b)
+
+ getInstrumentation().runOnMainSync {
+ bubbleTaskViewListener.onInitialized()
+ }
+ getInstrumentation().waitForIdleSync()
+ verify(taskViewController).startActivity(any(), any(), anyOrNull(), any(), any())
+
+ val taskId = 123
+ getInstrumentation().runOnMainSync {
+ bubbleTaskViewListener.onTaskCreated(taskId, mock<ComponentName>())
+ }
+ getInstrumentation().waitForIdleSync()
+
+ verify(listenerCallback).onTaskCreated()
+ verify(expandedViewManager, never()).setNoteBubbleTaskId(any(), any())
+ assertThat(bubbleTaskViewListener.taskId).isEqualTo(taskId)
+ }
+
+ @Test
+ fun onTaskCreated_noteBubble() {
+ val b = createNoteBubble()
+ bubbleTaskViewListener.setBubble(b)
+ assertThat(b.isNote).isTrue()
+
+ getInstrumentation().runOnMainSync {
+ bubbleTaskViewListener.onInitialized()
+ }
+ getInstrumentation().waitForIdleSync()
+ verify(taskViewController).startActivity(any(), any(), anyOrNull(), any(), any())
+
+ val taskId = 123
+ getInstrumentation().runOnMainSync {
+ bubbleTaskViewListener.onTaskCreated(taskId, mock<ComponentName>())
+ }
+ getInstrumentation().waitForIdleSync()
+
+ verify(listenerCallback).onTaskCreated()
+ verify(expandedViewManager).setNoteBubbleTaskId(eq(b.key), eq(taskId))
+ assertThat(bubbleTaskViewListener.taskId).isEqualTo(taskId)
+ }
+
+ @Test
+ fun onTaskVisibilityChanged_true() {
+ getInstrumentation().runOnMainSync {
+ bubbleTaskViewListener.onTaskVisibilityChanged(1, true)
+ }
+ verify(listenerCallback).onContentVisibilityChanged(eq(true))
+ }
+
+ @Test
+ fun onTaskVisibilityChanged_false() {
+ getInstrumentation().runOnMainSync {
+ bubbleTaskViewListener.onTaskVisibilityChanged(1, false)
+ }
+ verify(listenerCallback).onContentVisibilityChanged(eq(false))
+ }
+
+ @Test
+ fun onTaskRemovalStarted() {
+ val mockTaskView = mock<TaskView>()
+ bubbleTaskView = BubbleTaskView(mockTaskView, mainExecutor)
+
+ bubbleTaskViewListener =
+ BubbleTaskViewListener(
+ context,
+ bubbleTaskView,
+ parentView,
+ expandedViewManager,
+ listenerCallback
+ )
+
+ val b = createAppBubble()
+ bubbleTaskViewListener.setBubble(b)
+
+ getInstrumentation().runOnMainSync {
+ bubbleTaskViewListener.onInitialized()
+ }
+ getInstrumentation().waitForIdleSync()
+ verify(mockTaskView).startActivity(any(), anyOrNull(), any(), any())
+
+ getInstrumentation().runOnMainSync {
+ bubbleTaskViewListener.onTaskRemovalStarted(1)
+ }
+
+ verify(expandedViewManager).removeBubble(eq(b.key), eq(Bubbles.DISMISS_TASK_FINISHED))
+ verify(mockTaskView).release()
+ assertThat(parentView.lastRemovedView).isEqualTo(mockTaskView)
+ assertThat(bubbleTaskViewListener.taskView).isNull()
+ verify(listenerCallback).onTaskRemovalStarted()
+ }
+
+ @Test
+ fun onBackPressedOnTaskRoot_expanded() {
+ val taskId = 123
+ whenever(expandedViewManager.isStackExpanded()).doReturn(true)
+
+ getInstrumentation().runOnMainSync {
+ bubbleTaskViewListener.onTaskCreated(taskId, mock<ComponentName>())
+ bubbleTaskViewListener.onBackPressedOnTaskRoot(taskId)
+ }
+ verify(listenerCallback).onBackPressed()
+ }
+
+ @Test
+ fun onBackPressedOnTaskRoot_notExpanded() {
+ val taskId = 123
+ whenever(expandedViewManager.isStackExpanded()).doReturn(false)
+
+ getInstrumentation().runOnMainSync {
+ bubbleTaskViewListener.onTaskCreated(taskId, mock<ComponentName>())
+ bubbleTaskViewListener.onBackPressedOnTaskRoot(taskId)
+ }
+ verify(listenerCallback, never()).onBackPressed()
+ }
+
+ @Test
+ fun onBackPressedOnTaskRoot_taskIdMissMatch() {
+ val taskId = 123
+ whenever(expandedViewManager.isStackExpanded()).doReturn(true)
+
+ getInstrumentation().runOnMainSync {
+ bubbleTaskViewListener.onTaskCreated(taskId, mock<ComponentName>())
+ bubbleTaskViewListener.onBackPressedOnTaskRoot(42)
+ }
+ verify(listenerCallback, never()).onBackPressed()
+ }
+
+ @Test
+ fun setBubble_isNew() {
+ val b = createAppBubble()
+ val isNew = bubbleTaskViewListener.setBubble(b)
+ assertThat(isNew).isTrue()
+ }
+
+ @Test
+ fun setBubble_launchContentChanged() {
+ val target = Intent(context, TestActivity::class.java)
+ val pendingIntent = PendingIntent.getActivity(
+ context, 0, target,
+ PendingIntent.FLAG_MUTABLE
+ )
+
+ val b = createChatBubble("key", pendingIntent)
+ var isNew = bubbleTaskViewListener.setBubble(b)
+ // First time bubble is set, so it is "new"
+ assertThat(isNew).isTrue()
+
+ val b2 = createChatBubble("key", pendingIntent)
+ isNew = bubbleTaskViewListener.setBubble(b2)
+ // Second time bubble is set & it uses same type of launch content, not "new"
+ assertThat(isNew).isFalse()
+
+ val shortcutInfo = ShortcutInfo.Builder(context)
+ .setId("mockShortcutId")
+ .build()
+ val b3 = createChatBubble("key", shortcutInfo)
+ // bubble is using different content, so it is "new"
+ isNew = bubbleTaskViewListener.setBubble(b3)
+ assertThat(isNew).isTrue()
+ }
+
+ private fun createAppBubble(): Bubble {
+ val target = Intent(context, TestActivity::class.java)
+ target.setPackage(context.packageName)
+ return Bubble.createAppBubble(target, mock<UserHandle>(), mock<Icon>(),
+ mainExecutor, bgExecutor)
+ }
+
+ private fun createNoteBubble(): Bubble {
+ val target = Intent(context, TestActivity::class.java)
+ target.setPackage(context.packageName)
+ return Bubble.createNotesBubble(target, mock<UserHandle>(), mock<Icon>(),
+ mainExecutor, bgExecutor)
+ }
+
+ private fun createChatBubble(key: String, shortcutInfo: ShortcutInfo): Bubble {
+ return Bubble(
+ key,
+ shortcutInfo,
+ 0 /* desiredHeight */,
+ 0 /* desiredHeightResId */,
+ "title",
+ -1 /*taskId */,
+ null /* locusId */, true /* isdismissabel */,
+ mainExecutor, bgExecutor, mock<BubbleMetadataFlagListener>()
+ )
+ }
+
+ private fun createChatBubble(key: String, pendingIntent: PendingIntent): Bubble {
+ val metadata = Notification.BubbleMetadata.Builder(
+ pendingIntent,
+ Icon.createWithResource(context, R.drawable.bubble_ic_create_bubble)
+ ).build()
+ val shortcutInfo = ShortcutInfo.Builder(context)
+ .setId("shortcutId")
+ .build()
+ val notification: Notification =
+ Notification.Builder(context, key)
+ .setSmallIcon(mock<Icon>())
+ .setWhen(System.currentTimeMillis())
+ .setContentTitle("title")
+ .setContentText("content")
+ .setBubbleMetadata(metadata)
+ .build()
+ val sbn = mock<StatusBarNotification>()
+ val ranking = mock<Ranking>()
+ whenever(sbn.getNotification()).thenReturn(notification)
+ whenever(sbn.getKey()).thenReturn(key)
+ whenever(ranking.getConversationShortcutInfo()).thenReturn(shortcutInfo)
+ val entry = BubbleEntry(sbn, ranking, true, false, false, false)
+ return Bubble(
+ entry, mock<BubbleMetadataFlagListener>(), null, mainExecutor,
+ bgExecutor
+ )
+ }
+
+ /**
+ * FrameLayout that immediately runs any runnables posted to it and tracks view removals.
+ */
+ class ViewPoster(context: Context) : FrameLayout(context) {
+
+ lateinit var lastRemovedView: View
+
+ override fun post(r: Runnable): Boolean {
+ r.run()
+ return true
+ }
+
+ override fun removeView(v: View) {
+ super.removeView(v)
+ lastRemovedView = v
+ }
+ }
+} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml
index 96e008e42f1c..e395341a5792 100644
--- a/libs/WindowManager/Shell/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/res/values/dimen.xml
@@ -447,6 +447,17 @@
80 dp for handle + 20 dp for room to grow on the sides when hovered. -->
<dimen name="desktop_mode_fullscreen_decor_caption_width">100dp</dimen>
+ <!-- Horizontal padding for desktop mode caption in default unhovered untouched state. -->
+ <dimen name="desktop_mode_fullscreen_decor_caption_horizontal_padding_default">10dp</dimen>
+
+ <!-- Horizontal padding for desktop mode caption when hovered.
+ 1/2 * (100 dp of total width - 80 dp for handle * 1.2 scaling factor). -->
+ <dimen name="desktop_mode_fullscreen_decor_caption_horizontal_padding_hovered">2dp</dimen>
+
+ <!-- Horizontal padding for desktop mode caption when touched.
+ 1/2 * (100 dp of total width - 80 dp for handle * 0.85 scaling factor). -->
+ <dimen name="desktop_mode_fullscreen_decor_caption_horizontal_padding_touched">16dp</dimen>
+
<!-- Required empty space to be visible for partially offscreen tasks. -->
<dimen name="freeform_required_visible_empty_space_in_header">48dp</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..5f013c52d70d 100644
--- a/libs/WindowManager/Shell/shared/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/shared/res/values/dimen.xml
@@ -36,4 +36,13 @@
<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_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/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/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/desktopmode/DesktopModeStatus.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java
index 643c1506e4c2..00901a4d980d 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
@@ -226,6 +226,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 +240,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);
}
/**
@@ -323,25 +323,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/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/BubbleTaskViewHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java
deleted file mode 100644
index e47ac61a53dd..000000000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java
+++ /dev/null
@@ -1,292 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.wm.shell.bubbles;
-
-import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS;
-import static android.app.ActivityTaskManager.INVALID_TASK_ID;
-import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
-import static android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT;
-
-import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BUBBLES;
-
-import android.app.ActivityOptions;
-import android.app.ActivityTaskManager;
-import android.app.PendingIntent;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.graphics.Rect;
-import android.util.Log;
-import android.view.View;
-import android.view.ViewGroup;
-
-import androidx.annotation.Nullable;
-
-import com.android.internal.protolog.ProtoLog;
-import com.android.wm.shell.shared.bubbles.BubbleAnythingFlagHelper;
-import com.android.wm.shell.taskview.TaskView;
-
-/**
- * Handles creating and updating the {@link TaskView} associated with a {@link Bubble}.
- */
-public class BubbleTaskViewHelper {
-
- private static final String TAG = BubbleTaskViewHelper.class.getSimpleName();
-
- /**
- * Listener for users of {@link BubbleTaskViewHelper} to use to be notified of events
- * on the task.
- */
- public interface Listener {
-
- /** Called when the task is first created. */
- void onTaskCreated();
-
- /** Called when the visibility of the task changes. */
- void onContentVisibilityChanged(boolean visible);
-
- /** Called when back is pressed on the task root. */
- void onBackPressed();
-
- /** Called when task removal has started. */
- void onTaskRemovalStarted();
- }
-
- private final Context mContext;
- private final BubbleExpandedViewManager mExpandedViewManager;
- private final BubbleTaskViewHelper.Listener mListener;
- private final View mParentView;
-
- @Nullable
- private Bubble mBubble;
- @Nullable
- private PendingIntent mPendingIntent;
- @Nullable
- private TaskView mTaskView;
- private int mTaskId = INVALID_TASK_ID;
-
- private final TaskView.Listener mTaskViewListener = new TaskView.Listener() {
- private boolean mInitialized = false;
- private boolean mDestroyed = false;
-
- @Override
- public void onInitialized() {
- ProtoLog.d(WM_SHELL_BUBBLES, "onInitialized: destroyed=%b initialized=%b bubble=%s",
- mDestroyed, mInitialized, getBubbleKey());
-
- if (mDestroyed || mInitialized) {
- return;
- }
-
- // Custom options so there is no activity transition animation
- ActivityOptions options = ActivityOptions.makeCustomAnimation(mContext,
- 0 /* enterResId */, 0 /* exitResId */);
-
- Rect launchBounds = new Rect();
- mTaskView.getBoundsOnScreen(launchBounds);
-
- // TODO: I notice inconsistencies in lifecycle
- // Post to keep the lifecycle normal
- // TODO - currently based on type, really it's what the "launch item" is.
- mParentView.post(() -> {
- ProtoLog.d(WM_SHELL_BUBBLES, "onInitialized: calling startActivity, bubble=%s",
- getBubbleKey());
- try {
- options.setTaskAlwaysOnTop(true);
- options.setPendingIntentBackgroundActivityStartMode(
- MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS);
- final boolean isShortcutBubble = (mBubble.hasMetadataShortcutId()
- || (mBubble.isShortcut()
- && BubbleAnythingFlagHelper.enableCreateAnyBubble()));
- if (mBubble.getPreparingTransition() != null) {
- mBubble.getPreparingTransition().surfaceCreated();
- } else if (mBubble.isApp() || mBubble.isNote()) {
- Context context =
- mContext.createContextAsUser(
- mBubble.getUser(), Context.CONTEXT_RESTRICTED);
- Intent fillInIntent = null;
- //first try get pending intent from the bubble
- PendingIntent pi = mBubble.getPendingIntent();
- if (pi == null) {
- // if null - create new one
- pi = PendingIntent.getActivity(
- context,
- /* requestCode= */ 0,
- mBubble.getIntent()
- .addFlags(FLAG_ACTIVITY_MULTIPLE_TASK),
- PendingIntent.FLAG_IMMUTABLE
- | PendingIntent.FLAG_UPDATE_CURRENT,
- /* options= */ null);
- } else {
- fillInIntent = new Intent(pi.getIntent());
- fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
- }
- mTaskView.startActivity(pi, fillInIntent, options, launchBounds);
- } else if (isShortcutBubble) {
- options.setLaunchedFromBubble(true);
- options.setApplyActivityFlagsForBubbles(true);
- mTaskView.startShortcutActivity(mBubble.getShortcutInfo(),
- options, launchBounds);
- } else {
- options.setLaunchedFromBubble(true);
- if (mBubble != null) {
- mBubble.setPendingIntentActive();
- }
- final Intent fillInIntent = new Intent();
- // Apply flags to make behaviour match documentLaunchMode=always.
- fillInIntent.addFlags(FLAG_ACTIVITY_NEW_DOCUMENT);
- fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
- mTaskView.startActivity(mPendingIntent, fillInIntent, options,
- launchBounds);
- }
- } catch (RuntimeException e) {
- // If there's a runtime exception here then there's something
- // wrong with the intent, we can't really recover / try to populate
- // the bubble again so we'll just remove it.
- Log.w(TAG, "Exception while displaying bubble: " + getBubbleKey()
- + ", " + e.getMessage() + "; removing bubble");
- mExpandedViewManager.removeBubble(
- getBubbleKey(), Bubbles.DISMISS_INVALID_INTENT);
- }
- mInitialized = true;
- });
- }
-
- @Override
- public void onReleased() {
- mDestroyed = true;
- }
-
- @Override
- public void onTaskCreated(int taskId, ComponentName name) {
- ProtoLog.d(WM_SHELL_BUBBLES, "onTaskCreated: taskId=%d bubble=%s",
- taskId, getBubbleKey());
- // The taskId is saved to use for removeTask, preventing appearance in recent tasks.
- mTaskId = taskId;
-
- if (mBubble != null && mBubble.isNote()) {
- // Let the controller know sooner what the taskId is.
- mExpandedViewManager.setNoteBubbleTaskId(mBubble.getKey(), mTaskId);
- }
-
- // With the task org, the taskAppeared callback will only happen once the task has
- // already drawn
- mListener.onTaskCreated();
- }
-
- @Override
- public void onTaskVisibilityChanged(int taskId, boolean visible) {
- mListener.onContentVisibilityChanged(visible);
- }
-
- @Override
- public void onTaskRemovalStarted(int taskId) {
- ProtoLog.d(WM_SHELL_BUBBLES, "onTaskRemovalStarted: taskId=%d bubble=%s",
- taskId, getBubbleKey());
- if (mBubble != null) {
- mExpandedViewManager.removeBubble(mBubble.getKey(), Bubbles.DISMISS_TASK_FINISHED);
- }
- if (mTaskView != null) {
- mTaskView.release();
- ((ViewGroup) mParentView).removeView(mTaskView);
- mTaskView = null;
- }
- mListener.onTaskRemovalStarted();
- }
-
- @Override
- public void onBackPressedOnTaskRoot(int taskId) {
- if (mTaskId == taskId && mExpandedViewManager.isStackExpanded()) {
- mListener.onBackPressed();
- }
- }
- };
-
- public BubbleTaskViewHelper(Context context,
- BubbleExpandedViewManager expandedViewManager,
- BubbleTaskViewHelper.Listener listener,
- BubbleTaskView bubbleTaskView,
- View parent) {
- mContext = context;
- mExpandedViewManager = expandedViewManager;
- mListener = listener;
- mParentView = parent;
- mTaskView = bubbleTaskView.getTaskView();
- bubbleTaskView.setDelegateListener(mTaskViewListener);
- if (bubbleTaskView.isCreated()) {
- mTaskId = bubbleTaskView.getTaskId();
- mListener.onTaskCreated();
- }
- }
-
- /**
- * Sets the bubble or updates the bubble used to populate the view.
- *
- * @return true if the bubble is new, false if it was an update to the same bubble.
- */
- public boolean update(Bubble bubble) {
- boolean isNew = mBubble == null || didBackingContentChange(bubble);
- mBubble = bubble;
- if (isNew) {
- mPendingIntent = mBubble.getPendingIntent();
- return true;
- }
- return false;
- }
-
- /** Returns the bubble key associated with this view. */
- @Nullable
- public String getBubbleKey() {
- return mBubble != null ? mBubble.getKey() : null;
- }
-
- /** Returns the TaskView associated with this view. */
- @Nullable
- public TaskView getTaskView() {
- return mTaskView;
- }
-
- /**
- * Returns the task id associated with the task in this view. If the task doesn't exist then
- * {@link ActivityTaskManager#INVALID_TASK_ID}.
- */
- public int getTaskId() {
- return mTaskId;
- }
-
- /** Returns whether the bubble set on the helper is valid to populate the task view. */
- public boolean isValidBubble() {
- return mBubble != null && (mPendingIntent != null || mBubble.hasMetadataShortcutId());
- }
-
- // TODO (b/274980695): Is this still relevant?
- /**
- * Bubbles are backed by a pending intent or a shortcut, once the activity is
- * started we never change it / restart it on notification updates -- unless the bubble's
- * backing data switches.
- *
- * This indicates if the new bubble is backed by a different data source than what was
- * previously shown here (e.g. previously a pending intent & now a shortcut).
- *
- * @param newBubble the bubble this view is being updated with.
- * @return true if the backing content has changed.
- */
- private boolean didBackingContentChange(Bubble newBubble) {
- boolean prevWasIntentBased = mBubble != null && mPendingIntent != null;
- boolean newIsIntentBased = newBubble.getPendingIntent() != null;
- return prevWasIntentBased != newIsIntentBased;
- }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewListener.java
new file mode 100644
index 000000000000..a38debb702dc
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewListener.java
@@ -0,0 +1,279 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.bubbles;
+
+import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS;
+import static android.app.ActivityTaskManager.INVALID_TASK_ID;
+import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
+import static android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT;
+
+import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BUBBLES;
+
+import android.app.ActivityOptions;
+import android.app.ActivityTaskManager;
+import android.app.PendingIntent;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Rect;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.annotation.Nullable;
+
+import com.android.internal.protolog.ProtoLog;
+import com.android.wm.shell.shared.bubbles.BubbleAnythingFlagHelper;
+import com.android.wm.shell.taskview.TaskView;
+
+/**
+ * A listener that works with task views for bubbles, manages launching the appropriate
+ * content into the task view from the bubble and sends updates of task view events back to
+ * the parent view via {@link BubbleTaskViewListener.Callback}.
+ */
+public class BubbleTaskViewListener implements TaskView.Listener {
+ private static final String TAG = BubbleTaskViewListener.class.getSimpleName();
+
+ /**
+ * Callback to let the view parent of TaskView to be notified of different events.
+ */
+ public interface Callback {
+
+ /** Called when the task is first created. */
+ void onTaskCreated();
+
+ /** Called when the visibility of the task changes. */
+ void onContentVisibilityChanged(boolean visible);
+
+ /** Called when back is pressed on the task root. */
+ void onBackPressed();
+
+ /** Called when task removal has started. */
+ void onTaskRemovalStarted();
+ }
+
+ private final Context mContext;
+ private final BubbleExpandedViewManager mExpandedViewManager;
+ private final BubbleTaskViewListener.Callback mCallback;
+ private final View mParentView;
+
+ private Bubble mBubble;
+ @Nullable
+ private PendingIntent mPendingIntent;
+ private int mTaskId = INVALID_TASK_ID;
+ private TaskView mTaskView;
+
+ private boolean mInitialized = false;
+ private boolean mDestroyed = false;
+
+ public BubbleTaskViewListener(Context context, BubbleTaskView bubbleTaskView, View parentView,
+ BubbleExpandedViewManager manager, BubbleTaskViewListener.Callback callback) {
+ mContext = context;
+ mTaskView = bubbleTaskView.getTaskView();
+ mParentView = parentView;
+ mExpandedViewManager = manager;
+ mCallback = callback;
+ bubbleTaskView.setDelegateListener(this);
+ if (bubbleTaskView.isCreated()) {
+ mTaskId = bubbleTaskView.getTaskId();
+ callback.onTaskCreated();
+ }
+ }
+
+ @Override
+ public void onInitialized() {
+ ProtoLog.d(WM_SHELL_BUBBLES, "onInitialized: destroyed=%b initialized=%b bubble=%s",
+ mDestroyed, mInitialized, getBubbleKey());
+
+ if (mDestroyed || mInitialized) {
+ return;
+ }
+
+ // Custom options so there is no activity transition animation
+ ActivityOptions options = ActivityOptions.makeCustomAnimation(mContext,
+ 0 /* enterResId */, 0 /* exitResId */);
+
+ Rect launchBounds = new Rect();
+ mTaskView.getBoundsOnScreen(launchBounds);
+
+ // TODO: I notice inconsistencies in lifecycle
+ // Post to keep the lifecycle normal
+ // TODO - currently based on type, really it's what the "launch item" is.
+ mParentView.post(() -> {
+ ProtoLog.d(WM_SHELL_BUBBLES, "onInitialized: calling startActivity, bubble=%s",
+ getBubbleKey());
+ try {
+ options.setTaskAlwaysOnTop(true);
+ options.setPendingIntentBackgroundActivityStartMode(
+ MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS);
+ final boolean isShortcutBubble = (mBubble.hasMetadataShortcutId()
+ || (mBubble.isShortcut()
+ && BubbleAnythingFlagHelper.enableCreateAnyBubble()));
+ if (mBubble.getPreparingTransition() != null) {
+ mBubble.getPreparingTransition().surfaceCreated();
+ } else if (mBubble.isApp() || mBubble.isNote()) {
+ Context context =
+ mContext.createContextAsUser(
+ mBubble.getUser(), Context.CONTEXT_RESTRICTED);
+ Intent fillInIntent = null;
+ // First try get pending intent from the bubble
+ PendingIntent pi = mBubble.getPendingIntent();
+ if (pi == null) {
+ // If null - create new one
+ pi = PendingIntent.getActivity(
+ context,
+ /* requestCode= */ 0,
+ mBubble.getIntent()
+ .addFlags(FLAG_ACTIVITY_MULTIPLE_TASK),
+ PendingIntent.FLAG_IMMUTABLE
+ | PendingIntent.FLAG_UPDATE_CURRENT,
+ /* options= */ null);
+ } else {
+ fillInIntent = new Intent(pi.getIntent());
+ fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
+ }
+ mTaskView.startActivity(pi, fillInIntent, options, launchBounds);
+ } else if (isShortcutBubble) {
+ options.setLaunchedFromBubble(true);
+ options.setApplyActivityFlagsForBubbles(true);
+ mTaskView.startShortcutActivity(mBubble.getShortcutInfo(),
+ options, launchBounds);
+ } else {
+ options.setLaunchedFromBubble(true);
+ if (mBubble != null) {
+ mBubble.setPendingIntentActive();
+ }
+ final Intent fillInIntent = new Intent();
+ // Apply flags to make behaviour match documentLaunchMode=always.
+ fillInIntent.addFlags(FLAG_ACTIVITY_NEW_DOCUMENT);
+ fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
+ mTaskView.startActivity(mPendingIntent, fillInIntent, options,
+ launchBounds);
+ }
+ } catch (RuntimeException e) {
+ // If there's a runtime exception here then there's something
+ // wrong with the intent, we can't really recover / try to populate
+ // the bubble again so we'll just remove it.
+ Log.w(TAG, "Exception while displaying bubble: " + getBubbleKey()
+ + ", " + e.getMessage() + "; removing bubble");
+ mExpandedViewManager.removeBubble(
+ getBubbleKey(), Bubbles.DISMISS_INVALID_INTENT);
+ }
+ mInitialized = true;
+ });
+ }
+
+ @Override
+ public void onReleased() {
+ mDestroyed = true;
+ }
+
+ @Override
+ public void onTaskCreated(int taskId, ComponentName name) {
+ ProtoLog.d(WM_SHELL_BUBBLES, "onTaskCreated: taskId=%d bubble=%s",
+ taskId, getBubbleKey());
+ // The taskId is saved to use for removeTask, preventing appearance in recent tasks.
+ mTaskId = taskId;
+
+ if (mBubble != null && mBubble.isNote()) {
+ // Let the controller know sooner what the taskId is.
+ mExpandedViewManager.setNoteBubbleTaskId(mBubble.getKey(), mTaskId);
+ }
+
+ // With the task org, the taskAppeared callback will only happen once the task has
+ // already drawn
+ mCallback.onTaskCreated();
+ }
+
+ @Override
+ public void onTaskVisibilityChanged(int taskId, boolean visible) {
+ mCallback.onContentVisibilityChanged(visible);
+ }
+
+ @Override
+ public void onTaskRemovalStarted(int taskId) {
+ ProtoLog.d(WM_SHELL_BUBBLES, "onTaskRemovalStarted: taskId=%d bubble=%s",
+ taskId, getBubbleKey());
+ if (mBubble != null) {
+ mExpandedViewManager.removeBubble(mBubble.getKey(), Bubbles.DISMISS_TASK_FINISHED);
+ }
+ if (mTaskView != null) {
+ mTaskView.release();
+ ((ViewGroup) mParentView).removeView(mTaskView);
+ mTaskView = null;
+ }
+ mCallback.onTaskRemovalStarted();
+ }
+
+ @Override
+ public void onBackPressedOnTaskRoot(int taskId) {
+ if (mTaskId == taskId && mExpandedViewManager.isStackExpanded()) {
+ mCallback.onBackPressed();
+ }
+ }
+
+ /**
+ * Sets the bubble or updates the bubble used to populate the view.
+ *
+ * @return true if the bubble is new or if the launch content of the bubble changed from the
+ * previous bubble.
+ */
+ public boolean setBubble(Bubble bubble) {
+ boolean isNew = mBubble == null || didBackingContentChange(bubble);
+ mBubble = bubble;
+ if (isNew) {
+ mPendingIntent = mBubble.getPendingIntent();
+ }
+ return isNew;
+ }
+
+ /** Returns the TaskView associated with this view. */
+ @Nullable
+ public TaskView getTaskView() {
+ return mTaskView;
+ }
+
+ /**
+ * Returns the task id associated with the task in this view. If the task doesn't exist then
+ * {@link ActivityTaskManager#INVALID_TASK_ID}.
+ */
+ public int getTaskId() {
+ return mTaskId;
+ }
+
+ private String getBubbleKey() {
+ return mBubble != null ? mBubble.getKey() : "";
+ }
+
+ // TODO (b/274980695): Is this still relevant?
+ /**
+ * Bubbles are backed by a pending intent or a shortcut, once the activity is
+ * started we never change it / restart it on notification updates -- unless the bubble's
+ * backing data switches.
+ *
+ * This indicates if the new bubble is backed by a different data source than what was
+ * previously shown here (e.g. previously a pending intent & now a shortcut).
+ *
+ * @param newBubble the bubble this view is being updated with.
+ * @return true if the backing content has changed.
+ */
+ private boolean didBackingContentChange(Bubble newBubble) {
+ boolean prevWasIntentBased = mBubble != null && mPendingIntent != null;
+ boolean newIsIntentBased = newBubble.getPendingIntent() != null;
+ return prevWasIntentBased != newIsIntentBased;
+ }
+}
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 28227a1f8746..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;
+ }
}
/**
@@ -189,14 +200,16 @@ public class BubbleTransitions {
*
* 1. Start inflating the bubble view
* 2. Once inflated (but not-yet visible), tell WM to do the shell-transition.
- * 3. Transition becomes ready, so notify Launcher
- * 4. Launcher responds with showExpandedView which calls continueExpand() to make view visible
- * 5. Surface is created which kicks off actual animation
+ * 3. When the transition becomes ready, notify Launcher in parallel
+ * 4. Wait for surface to be created
+ * 5. Once surface is ready, animate the task to a bubble
*
- * So, constructor -> onInflated -> startAnimation -> continueExpand -> surfaceCreated.
+ * While the animation is pending, we keep a reference to the pending transition in the bubble.
+ * This allows us to check in other parts of the code that this bubble will be shown via the
+ * transition animation.
*
- * continueExpand and surfaceCreated are set-up to happen in either order, though, to support
- * UX/timing adjustments.
+ * startAnimation, continueExpand and surfaceCreated are set-up to happen in either order,
+ * to support UX/timing adjustments.
*/
@VisibleForTesting
class ConvertToBubble implements Transitions.TransitionHandler, BubbleTransition {
@@ -209,9 +222,9 @@ public class BubbleTransitions {
final Rect mStartBounds = new Rect();
SurfaceControl mSnapshot = null;
TaskInfo mTaskInfo;
- boolean mFinishedExpand = false;
BubbleViewProvider mPriorBubble = null;
+ private final TransitionProgress mTransitionProgress = new TransitionProgress();
private SurfaceControl.Transaction mFinishT;
private SurfaceControl mTaskLeash;
@@ -359,12 +372,12 @@ public class BubbleTransitions {
startTransaction.apply();
mTaskViewTransitions.onExternalDone(transition);
+ mTransitionProgress.setTransitionReady();
+ startExpandAnim();
return true;
}
- @Override
- public void continueExpand() {
- mFinishedExpand = true;
+ private void startExpandAnim() {
final boolean animate = mLayerView.canExpandView(mBubble);
if (animate) {
mPriorBubble = mLayerView.prepareConvertedView(mBubble);
@@ -375,19 +388,25 @@ public class BubbleTransitions {
mLayerView.removeView(priorView);
mPriorBubble = null;
}
- if (!animate || mBubble.getTaskView().getSurfaceControl() != null) {
+ if (!animate || mTransitionProgress.isReadyToAnimate()) {
playAnimation(animate);
}
}
@Override
+ public void continueExpand() {
+ mTransitionProgress.setReadyToExpand();
+ }
+
+ @Override
public void surfaceCreated() {
+ mTransitionProgress.setSurfaceReady();
mMainExecutor.execute(() -> {
final TaskViewTaskController tvc = mBubble.getTaskView().getController();
final TaskViewRepository.TaskViewState state = mRepository.byTaskView(tvc);
if (state == null) return;
state.mVisible = true;
- if (mFinishedExpand) {
+ if (mTransitionProgress.isReadyToAnimate()) {
playAnimation(true /* animate */);
}
});
@@ -403,9 +422,6 @@ public class BubbleTransitions {
mFinishWct = null;
}
- // Preparation is complete.
- mBubble.setPreparingTransition(null);
-
if (animate) {
mLayerView.animateConvert(startT, mStartBounds, mSnapshot, mTaskLeash, () -> {
mFinishCb.onTransitionFinished(mFinishWct);
@@ -417,6 +433,42 @@ public class BubbleTransitions {
mFinishCb = null;
}
}
+
+ /**
+ * Keeps track of internal state of different steps of this BubbleTransition.
+ */
+ private class TransitionProgress {
+ private boolean mTransitionReady;
+ private boolean mReadyToExpand;
+ private boolean mSurfaceReady;
+
+ void setTransitionReady() {
+ mTransitionReady = true;
+ onUpdate();
+ }
+
+ void setReadyToExpand() {
+ mReadyToExpand = true;
+ onUpdate();
+ }
+
+ void setSurfaceReady() {
+ mSurfaceReady = true;
+ onUpdate();
+ }
+
+ boolean isReadyToAnimate() {
+ // Animation only depends on transition and surface state
+ return mTransitionReady && mSurfaceReady;
+ }
+
+ private void onUpdate() {
+ if (mTransitionReady && mReadyToExpand && mSurfaceReady) {
+ // Clear the transition from bubble when all the steps are ready
+ mBubble.setPreparingTransition(null);
+ }
+ }
+ }
}
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java
index 6798a88a6da7..d93dbc3c15d9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java
@@ -43,9 +43,10 @@ import com.android.wm.shell.bubbles.BubbleLogger;
import com.android.wm.shell.bubbles.BubbleOverflowContainerView;
import com.android.wm.shell.bubbles.BubblePositioner;
import com.android.wm.shell.bubbles.BubbleTaskView;
-import com.android.wm.shell.bubbles.BubbleTaskViewHelper;
+import com.android.wm.shell.bubbles.BubbleTaskViewListener;
import com.android.wm.shell.bubbles.Bubbles;
import com.android.wm.shell.bubbles.RegionSamplingProvider;
+import com.android.wm.shell.dagger.HasWMComponent;
import com.android.wm.shell.shared.bubbles.BubbleBarLocation;
import com.android.wm.shell.shared.handles.RegionSamplingHelper;
import com.android.wm.shell.taskview.TaskView;
@@ -56,7 +57,7 @@ import java.util.function.Supplier;
import javax.inject.Inject;
/** Expanded view of a bubble when it's part of the bubble bar. */
-public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskViewHelper.Listener {
+public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskViewListener.Callback {
/**
* The expanded view listener notifying the {@link BubbleBarLayerView} about the internal
* actions and events
@@ -110,7 +111,7 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView
private BubbleExpandedViewManager mManager;
private BubblePositioner mPositioner;
private boolean mIsOverflow;
- private BubbleTaskViewHelper mBubbleTaskViewHelper;
+ private BubbleTaskViewListener mBubbleTaskViewListener;
private BubbleBarMenuViewController mMenuViewController;
@Nullable
private Supplier<Rect> mLayerBoundsSupplier;
@@ -205,6 +206,9 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView
protected void onFinishInflate() {
super.onFinishInflate();
Context context = getContext();
+ if (context instanceof HasWMComponent) {
+ ((HasWMComponent) context).getWMComponent().inject(this);
+ }
setElevation(getResources().getDimensionPixelSize(R.dimen.bubble_elevation));
mCaptionHeight = context.getResources().getDimensionPixelSize(
R.dimen.bubble_bar_expanded_view_caption_height);
@@ -246,9 +250,10 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView
mHandleView.setVisibility(View.GONE);
} else {
mTaskView = bubbleTaskView.getTaskView();
- mBubbleTaskViewHelper = new BubbleTaskViewHelper(mContext, expandedViewManager,
- /* listener= */ this, bubbleTaskView,
- /* viewParent= */ this);
+ mBubbleTaskViewListener = new BubbleTaskViewListener(mContext, bubbleTaskView,
+ /* viewParent= */ this,
+ expandedViewManager,
+ /* callback= */ this);
// if the task view is already attached to a parent we need to remove it
if (mTaskView.getParent() != null) {
@@ -535,13 +540,15 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView
/** Updates the bubble shown in the expanded view. */
public void update(Bubble bubble) {
mBubble = bubble;
- mBubbleTaskViewHelper.update(bubble);
+ mBubbleTaskViewListener.setBubble(bubble);
mMenuViewController.updateMenu(bubble);
}
/** The task id of the activity shown in the task view, if it exists. */
public int getTaskId() {
- return mBubbleTaskViewHelper != null ? mBubbleTaskViewHelper.getTaskId() : INVALID_TASK_ID;
+ return mBubbleTaskViewListener != null
+ ? mBubbleTaskViewListener.getTaskId()
+ : INVALID_TASK_ID;
}
/** Sets layer bounds supplier used for obscured touchable region of task view */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipDesktopState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipDesktopState.java
index c10c2c905c97..c6afc313b239 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipDesktopState.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipDesktopState.java
@@ -81,8 +81,8 @@ public class PipDesktopState {
return false;
}
- /** Returns whether PiP is exiting while we're in a Desktop Mode session. */
- private boolean isPipExitingToDesktopMode() {
+ /** Returns whether PiP is active in a display that is in active Desktop Mode session. */
+ public boolean isPipInDesktopMode() {
// Early return if PiP in Desktop Windowing is not supported.
if (!isDesktopWindowingPipEnabled()) {
return false;
@@ -137,7 +137,7 @@ public class PipDesktopState {
// 1) If the display windowing mode is freeform, set windowing mode to UNDEFINED so it will
// resolve the windowing mode to the display's windowing mode.
// 2) If the display windowing mode is not FREEFORM, set windowing mode to FREEFORM.
- if (isPipExitingToDesktopMode()) {
+ if (isPipInDesktopMode()) {
if (isDisplayInFreeform()) {
return WINDOWING_MODE_UNDEFINED;
} else {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvWMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvWMShellModule.java
index aebd94fc173a..34d840eed3f0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvWMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvWMShellModule.java
@@ -92,7 +92,7 @@ public class TvWMShellModule {
MultiInstanceHelper multiInstanceHelper,
SplitState splitState,
@ShellMainThread ShellExecutor mainExecutor,
- Handler mainHandler,
+ @ShellMainThread Handler mainHandler,
SystemWindows systemWindows) {
return new TvSplitScreenController(context, shellInit, shellCommandHandler, shellController,
shellTaskOrganizer, syncQueue, rootTDAOrganizer, displayController,
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 84bb830af9a3..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
@@ -134,6 +134,7 @@ import com.android.wm.shell.recents.RecentsTransitionHandler;
import com.android.wm.shell.shared.TransactionPool;
import com.android.wm.shell.shared.annotations.ShellAnimationThread;
import com.android.wm.shell.shared.annotations.ShellBackgroundThread;
+import com.android.wm.shell.shared.annotations.ShellDesktopThread;
import com.android.wm.shell.shared.annotations.ShellMainThread;
import com.android.wm.shell.shared.desktopmode.DesktopModeCompatPolicy;
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
@@ -750,6 +751,7 @@ public abstract class WMShellModule {
MultiInstanceHelper multiInstanceHelper,
@ShellMainThread ShellExecutor mainExecutor,
@ShellMainThread Handler mainHandler,
+ @ShellDesktopThread ShellExecutor desktopExecutor,
Optional<DesktopTasksLimiter> desktopTasksLimiter,
Optional<RecentTasksController> recentTasksController,
InteractionJankMonitor interactionJankMonitor,
@@ -757,7 +759,6 @@ public abstract class WMShellModule {
FocusTransitionObserver focusTransitionObserver,
DesktopModeEventLogger desktopModeEventLogger,
DesktopModeUiEventLogger desktopModeUiEventLogger,
- DesktopTilingDecorViewModel desktopTilingDecorViewModel,
DesktopWallpaperActivityTokenProvider desktopWallpaperActivityTokenProvider,
Optional<BubbleController> bubbleController,
OverviewToDesktopTransitionObserver overviewToDesktopTransitionObserver,
@@ -789,13 +790,13 @@ public abstract class WMShellModule {
recentsTransitionHandler,
multiInstanceHelper,
mainExecutor,
+ desktopExecutor,
desktopTasksLimiter,
recentTasksController.orElse(null),
interactionJankMonitor,
mainHandler,
desktopModeEventLogger,
desktopModeUiEventLogger,
- desktopTilingDecorViewModel,
desktopWallpaperActivityTokenProvider,
bubbleController,
overviewToDesktopTransitionObserver,
@@ -987,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();
@@ -1003,7 +1005,8 @@ public abstract class WMShellModule {
desktopTasksLimiter, appHandleEducationController, appToWebEducationController,
windowDecorCaptionHandleRepository, activityOrientationChangeHandler,
focusTransitionObserver, desktopModeEventLogger, desktopModeUiEventLogger,
- taskResourceLoader, recentsTransitionHandler, desktopModeCompatPolicy));
+ taskResourceLoader, recentsTransitionHandler, desktopModeCompatPolicy,
+ desktopTilingDecorViewModel));
}
@WMSingleton
@@ -1275,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 e7c76bbd91b2..7d80ee5f3bb6 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
@@ -174,6 +174,7 @@ public abstract class Pip2Module {
@NonNull PipScheduler pipScheduler,
@NonNull SizeSpecSource sizeSpecSource,
@NonNull PipDisplayLayoutState pipDisplayLayoutState,
+ PipDesktopState pipDesktopState,
DisplayController displayController,
PipMotionHelper pipMotionHelper,
FloatingContentCoordinator floatingContentCoordinator,
@@ -182,8 +183,8 @@ public abstract class Pip2Module {
Optional<PipPerfHintController> pipPerfHintControllerOptional) {
return new PipTouchHandler(context, shellInit, shellCommandHandler, menuPhoneController,
pipBoundsAlgorithm, pipBoundsState, pipTransitionState, pipScheduler,
- sizeSpecSource, pipDisplayLayoutState, displayController, pipMotionHelper,
- floatingContentCoordinator, pipUiEventLogger, mainExecutor,
+ sizeSpecSource, pipDisplayLayoutState, pipDesktopState, displayController,
+ pipMotionHelper, floatingContentCoordinator, pipUiEventLogger, mainExecutor,
pipPerfHintControllerOptional);
}
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/DesktopModeVisualIndicator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java
index 27aed17762ff..aecbf1a23cb2 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
@@ -18,9 +18,6 @@ package com.android.wm.shell.desktopmode;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
-import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
-import static android.view.WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL;
-import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
import static com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.IndicatorType.NO_INDICATOR;
import static com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR;
@@ -28,37 +25,27 @@ import static com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.Indica
import static com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_LEFT_INDICATOR;
import static com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_RIGHT_INDICATOR;
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.RectEvaluator;
-import android.animation.ValueAnimator;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.content.Context;
-import android.content.res.Resources;
-import android.graphics.PixelFormat;
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.Region;
-import android.graphics.drawable.LayerDrawable;
-import android.util.DisplayMetrics;
import android.view.SurfaceControl;
-import android.view.SurfaceControlViewHost;
-import android.view.View;
-import android.view.WindowManager;
-import android.view.WindowlessWindowManager;
-import android.view.animation.DecelerateInterpolator;
+import android.window.DesktopModeFlags;
import androidx.annotation.VisibleForTesting;
import com.android.internal.policy.SystemBarUtils;
-import com.android.window.flags.Flags;
import com.android.wm.shell.R;
import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayLayout;
+import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
+import com.android.wm.shell.shared.annotations.ShellDesktopThread;
+import com.android.wm.shell.shared.annotations.ShellMainThread;
import com.android.wm.shell.shared.bubbles.BubbleAnythingFlagHelper;
import com.android.wm.shell.shared.bubbles.BubbleDropTargetBoundsProvider;
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
@@ -115,37 +102,54 @@ public class DesktopModeVisualIndicator {
}
}
+ private final VisualIndicatorViewContainer mVisualIndicatorViewContainer;
+
private final Context mContext;
private final DisplayController mDisplayController;
- private final RootTaskDisplayAreaOrganizer mRootTdaOrganizer;
private final ActivityManager.RunningTaskInfo mTaskInfo;
- private final SurfaceControl mTaskSurface;
- private final @Nullable BubbleDropTargetBoundsProvider mBubbleBoundsProvider;
- private SurfaceControl mLeash;
-
- private final SyncTransactionQueue mSyncQueue;
- private SurfaceControlViewHost mViewHost;
- private View mView;
private IndicatorType mCurrentType;
- private DragStartState mDragStartState;
- private boolean mIsReleased;
+ private final DragStartState mDragStartState;
- public DesktopModeVisualIndicator(SyncTransactionQueue syncQueue,
+ public DesktopModeVisualIndicator(@ShellDesktopThread ShellExecutor desktopExecutor,
+ @ShellMainThread ShellExecutor mainExecutor,
+ SyncTransactionQueue syncQueue,
ActivityManager.RunningTaskInfo taskInfo, DisplayController displayController,
Context context, SurfaceControl taskSurface,
RootTaskDisplayAreaOrganizer taskDisplayAreaOrganizer,
DragStartState dragStartState,
@Nullable BubbleDropTargetBoundsProvider bubbleBoundsProvider) {
- mSyncQueue = syncQueue;
+ SurfaceControl.Builder builder = new SurfaceControl.Builder();
+ taskDisplayAreaOrganizer.attachToDisplayArea(taskInfo.displayId, builder);
+ mVisualIndicatorViewContainer = new VisualIndicatorViewContainer(
+ DesktopModeFlags.ENABLE_DESKTOP_INDICATOR_IN_SEPARATE_THREAD_BUGFIX.isTrue()
+ ? desktopExecutor : mainExecutor,
+ mainExecutor, builder, syncQueue, bubbleBoundsProvider);
mTaskInfo = taskInfo;
mDisplayController = displayController;
mContext = context;
- mTaskSurface = taskSurface;
- mRootTdaOrganizer = taskDisplayAreaOrganizer;
- mBubbleBoundsProvider = bubbleBoundsProvider;
mCurrentType = NO_INDICATOR;
mDragStartState = dragStartState;
+ mVisualIndicatorViewContainer.createView(
+ mContext,
+ mDisplayController.getDisplay(mTaskInfo.displayId),
+ mDisplayController.getDisplayLayout(mTaskInfo.displayId),
+ mTaskInfo,
+ taskSurface
+ );
+ }
+
+ /** Start the fade out animation, running the callback on the main thread once it is done. */
+ public void fadeOutIndicator(
+ @NonNull Runnable callback) {
+ mVisualIndicatorViewContainer.fadeOutIndicator(
+ mDisplayController.getDisplayLayout(mTaskInfo.displayId), mCurrentType, callback
+ );
+ }
+
+ /** Release the visual indicator view and its viewhost. */
+ public void releaseVisualIndicator() {
+ mVisualIndicatorViewContainer.releaseVisualIndicator();
}
/**
@@ -202,7 +206,10 @@ public class DesktopModeVisualIndicator {
}
}
if (mDragStartState != DragStartState.DRAGGED_INTENT) {
- transitionIndicator(result);
+ mVisualIndicatorViewContainer.transitionIndicator(
+ mTaskInfo, mDisplayController, mCurrentType, result
+ );
+ mCurrentType = result;
}
return result;
}
@@ -283,338 +290,8 @@ public class DesktopModeVisualIndicator {
layout.width(), layout.height());
}
- /**
- * Create a fullscreen indicator with no animation
- */
- private void createView() {
- if (mIsReleased) return;
- final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
- final Resources resources = mContext.getResources();
- final DisplayMetrics metrics = resources.getDisplayMetrics();
- final int screenWidth;
- final int screenHeight;
- if (Flags.enableBugFixesForSecondaryDisplay()) {
- final DisplayLayout displayLayout =
- mDisplayController.getDisplayLayout(mTaskInfo.displayId);
- screenWidth = displayLayout.width();
- screenHeight = displayLayout.height();
- } else {
- screenWidth = metrics.widthPixels;
- screenHeight = metrics.heightPixels;
- }
- mView = new View(mContext);
- final SurfaceControl.Builder builder = new SurfaceControl.Builder();
- mRootTdaOrganizer.attachToDisplayArea(mTaskInfo.displayId, builder);
- mLeash = builder
- .setName("Desktop Mode Visual Indicator")
- .setContainerLayer()
- .setCallsite("DesktopModeVisualIndicator.createView")
- .build();
- t.show(mLeash);
- final WindowManager.LayoutParams lp =
- new WindowManager.LayoutParams(screenWidth, screenHeight, TYPE_APPLICATION,
- FLAG_NOT_FOCUSABLE, PixelFormat.TRANSPARENT);
- lp.setTitle("Desktop Mode Visual Indicator");
- lp.setTrustedOverlay();
- lp.inputFeatures |= INPUT_FEATURE_NO_INPUT_CHANNEL;
- final WindowlessWindowManager windowManager = new WindowlessWindowManager(
- mTaskInfo.configuration, mLeash,
- /* hostInputToken= */ null);
- mViewHost = new SurfaceControlViewHost(mContext,
- mDisplayController.getDisplay(mTaskInfo.displayId), windowManager,
- "DesktopModeVisualIndicator");
- mViewHost.setView(mView, lp);
- // We want this indicator to be behind the dragged task, but in front of all others.
- t.setRelativeLayer(mLeash, mTaskSurface, -1);
-
- mSyncQueue.runInSync(transaction -> {
- transaction.merge(t);
- t.close();
- });
- }
-
@VisibleForTesting
Rect getIndicatorBounds() {
- return mView.getBackground().getBounds();
- }
-
- /**
- * Fade indicator in as provided type. Animator fades it in while expanding the bounds outwards.
- */
- private void fadeInIndicator(IndicatorType type) {
- mView.setBackgroundResource(R.drawable.desktop_windowing_transition_background);
- final VisualIndicatorAnimator animator = VisualIndicatorAnimator
- .fadeBoundsIn(mView, type,
- mDisplayController.getDisplayLayout(mTaskInfo.displayId),
- mBubbleBoundsProvider);
- animator.start();
- mCurrentType = type;
- }
-
- /**
- * Fade out indicator without fully releasing it. Animator fades it out while shrinking bounds.
- *
- * @param finishCallback called when animation ends or gets cancelled
- */
- void fadeOutIndicator(@Nullable Runnable finishCallback) {
- if (mCurrentType == NO_INDICATOR) {
- // In rare cases, fade out can be requested before the indicator has determined its
- // initial type and started animating in. In this case, no animator is needed.
- finishCallback.run();
- return;
- }
- final VisualIndicatorAnimator animator = VisualIndicatorAnimator
- .fadeBoundsOut(mView, mCurrentType,
- mDisplayController.getDisplayLayout(mTaskInfo.displayId),
- mBubbleBoundsProvider);
- animator.start();
- if (finishCallback != null) {
- animator.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- finishCallback.run();
- }
- });
- }
- mCurrentType = NO_INDICATOR;
- }
-
- /**
- * Takes existing indicator and animates it to bounds reflecting a new indicator type.
- */
- private void transitionIndicator(IndicatorType newType) {
- if (mCurrentType == newType) return;
- if (mView == null) {
- createView();
- }
- if (mCurrentType == NO_INDICATOR) {
- fadeInIndicator(newType);
- } else if (newType == NO_INDICATOR) {
- fadeOutIndicator(/* finishCallback= */ null);
- } else {
- final VisualIndicatorAnimator animator = VisualIndicatorAnimator.animateIndicatorType(
- mView, mDisplayController.getDisplayLayout(mTaskInfo.displayId), mCurrentType,
- newType, mBubbleBoundsProvider);
- mCurrentType = newType;
- animator.start();
- }
- }
-
- /**
- * Release the indicator and its components when it is no longer needed.
- */
- public void releaseVisualIndicator(SurfaceControl.Transaction t) {
- mIsReleased = true;
- if (mViewHost == null) return;
- if (mViewHost != null) {
- mViewHost.release();
- mViewHost = null;
- }
-
- if (mLeash != null) {
- t.remove(mLeash);
- mLeash = null;
- }
- }
-
- /**
- * Animator for Desktop Mode transitions which supports bounds and alpha animation.
- */
- private static class VisualIndicatorAnimator extends ValueAnimator {
- private static final int FULLSCREEN_INDICATOR_DURATION = 200;
- private static final float FULLSCREEN_SCALE_ADJUSTMENT_PERCENT = 0.015f;
- private static final float INDICATOR_FINAL_OPACITY = 0.35f;
- private static final int MAXIMUM_OPACITY = 255;
-
- /**
- * Determines how this animator will interact with the view's alpha:
- * Fade in, fade out, or no change to alpha
- */
- private enum AlphaAnimType {
- ALPHA_FADE_IN_ANIM, ALPHA_FADE_OUT_ANIM, ALPHA_NO_CHANGE_ANIM
- }
-
- private final View mView;
- private final Rect mStartBounds;
- private final Rect mEndBounds;
- private final RectEvaluator mRectEvaluator;
-
- private VisualIndicatorAnimator(View view, Rect startBounds,
- Rect endBounds) {
- mView = view;
- mStartBounds = new Rect(startBounds);
- mEndBounds = endBounds;
- setFloatValues(0, 1);
- mRectEvaluator = new RectEvaluator(new Rect());
- }
-
- private static VisualIndicatorAnimator fadeBoundsIn(
- @NonNull View view, IndicatorType type, @NonNull DisplayLayout displayLayout,
- @Nullable BubbleDropTargetBoundsProvider bubbleBoundsProvider) {
- final Rect endBounds = getIndicatorBounds(displayLayout, type, bubbleBoundsProvider);
- final Rect startBounds = getMinBounds(endBounds);
- view.getBackground().setBounds(startBounds);
-
- final VisualIndicatorAnimator animator = new VisualIndicatorAnimator(
- view, startBounds, endBounds);
- animator.setInterpolator(new DecelerateInterpolator());
- setupIndicatorAnimation(animator, AlphaAnimType.ALPHA_FADE_IN_ANIM);
- return animator;
- }
-
- private static VisualIndicatorAnimator fadeBoundsOut(
- @NonNull View view, IndicatorType type, @NonNull DisplayLayout displayLayout,
- @Nullable BubbleDropTargetBoundsProvider bubbleBoundsProvider) {
- final Rect startBounds = getIndicatorBounds(displayLayout, type, bubbleBoundsProvider);
- final Rect endBounds = getMinBounds(startBounds);
- view.getBackground().setBounds(startBounds);
-
- final VisualIndicatorAnimator animator = new VisualIndicatorAnimator(
- view, startBounds, endBounds);
- animator.setInterpolator(new DecelerateInterpolator());
- setupIndicatorAnimation(animator, AlphaAnimType.ALPHA_FADE_OUT_ANIM);
- return animator;
- }
-
- /**
- * Create animator for visual indicator changing type (i.e., fullscreen to freeform,
- * freeform to split, etc.)
- *
- * @param view the view for this indicator
- * @param displayLayout information about the display the transitioning task is
- * currently on
- * @param origType the original indicator type
- * @param newType the new indicator type
- * @param bubbleBoundsProvider provides bounds for bubbles indicators
- */
- private static VisualIndicatorAnimator animateIndicatorType(@NonNull View view,
- @NonNull DisplayLayout displayLayout, IndicatorType origType, IndicatorType newType,
- @Nullable BubbleDropTargetBoundsProvider bubbleBoundsProvider) {
- final Rect startBounds = getIndicatorBounds(displayLayout, origType,
- bubbleBoundsProvider);
- final Rect endBounds = getIndicatorBounds(displayLayout, newType, bubbleBoundsProvider);
- final VisualIndicatorAnimator animator = new VisualIndicatorAnimator(
- view, startBounds, endBounds);
- animator.setInterpolator(new DecelerateInterpolator());
- setupIndicatorAnimation(animator, AlphaAnimType.ALPHA_NO_CHANGE_ANIM);
- return animator;
- }
-
- /** Calculates the bounds the indicator should have when fully faded in. */
- private static Rect getIndicatorBounds(DisplayLayout layout, IndicatorType type,
- @Nullable BubbleDropTargetBoundsProvider bubbleBoundsProvider) {
- final Rect desktopStableBounds = new Rect();
- layout.getStableBounds(desktopStableBounds);
- final int padding = desktopStableBounds.top;
- switch (type) {
- case TO_FULLSCREEN_INDICATOR:
- desktopStableBounds.top += padding;
- desktopStableBounds.bottom -= padding;
- desktopStableBounds.left += padding;
- desktopStableBounds.right -= padding;
- return desktopStableBounds;
- case TO_DESKTOP_INDICATOR:
- final float adjustmentPercentage = 1f
- - DesktopTasksController.DESKTOP_MODE_INITIAL_BOUNDS_SCALE;
- return new Rect((int) (adjustmentPercentage * desktopStableBounds.width() / 2),
- (int) (adjustmentPercentage * desktopStableBounds.height() / 2),
- (int) (desktopStableBounds.width()
- - (adjustmentPercentage * desktopStableBounds.width() / 2)),
- (int) (desktopStableBounds.height()
- - (adjustmentPercentage * desktopStableBounds.height() / 2)));
- case TO_SPLIT_LEFT_INDICATOR:
- return new Rect(padding, padding,
- desktopStableBounds.width() / 2 - padding,
- desktopStableBounds.height());
- case TO_SPLIT_RIGHT_INDICATOR:
- return new Rect(desktopStableBounds.width() / 2 + padding, padding,
- desktopStableBounds.width() - padding,
- desktopStableBounds.height());
- case TO_BUBBLE_LEFT_INDICATOR:
- if (bubbleBoundsProvider == null) {
- return new Rect();
- }
- return bubbleBoundsProvider.getBubbleBarExpandedViewDropTargetBounds(
- /* onLeft= */ true);
- case TO_BUBBLE_RIGHT_INDICATOR:
- if (bubbleBoundsProvider == null) {
- return new Rect();
- }
- return bubbleBoundsProvider.getBubbleBarExpandedViewDropTargetBounds(
- /* onLeft= */ false);
- default:
- throw new IllegalArgumentException("Invalid indicator type provided.");
- }
- }
-
- /**
- * Add necessary listener for animation of indicator
- */
- private static void setupIndicatorAnimation(@NonNull VisualIndicatorAnimator animator,
- AlphaAnimType animType) {
- animator.addUpdateListener(a -> {
- if (animator.mView != null) {
- animator.updateBounds(a.getAnimatedFraction(), animator.mView);
- if (animType == AlphaAnimType.ALPHA_FADE_IN_ANIM) {
- animator.updateIndicatorAlpha(a.getAnimatedFraction(), animator.mView);
- } else if (animType == AlphaAnimType.ALPHA_FADE_OUT_ANIM) {
- animator.updateIndicatorAlpha(1 - a.getAnimatedFraction(), animator.mView);
- }
- } else {
- animator.cancel();
- }
- });
- animator.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- animator.mView.getBackground().setBounds(animator.mEndBounds);
- }
- });
- animator.setDuration(FULLSCREEN_INDICATOR_DURATION);
- }
-
- /**
- * Update bounds of view based on current animation fraction.
- * Use of delta is to animate bounds independently, in case we need to
- * run multiple animations simultaneously.
- *
- * @param fraction fraction to use, compared against previous fraction
- * @param view the view to update
- */
- private void updateBounds(float fraction, View view) {
- if (mStartBounds.equals(mEndBounds)) {
- return;
- }
- final Rect currentBounds = mRectEvaluator.evaluate(fraction, mStartBounds, mEndBounds);
- view.getBackground().setBounds(currentBounds);
- }
-
- /**
- * Fade in the fullscreen indicator
- *
- * @param fraction current animation fraction
- */
- private void updateIndicatorAlpha(float fraction, View view) {
- final LayerDrawable drawable = (LayerDrawable) view.getBackground();
- drawable.findDrawableByLayerId(R.id.indicator_stroke)
- .setAlpha((int) (MAXIMUM_OPACITY * fraction));
- drawable.findDrawableByLayerId(R.id.indicator_solid)
- .setAlpha((int) (MAXIMUM_OPACITY * fraction * INDICATOR_FINAL_OPACITY));
- }
-
- /**
- * Return the minimum bounds of a visual indicator, to be used at the end of fading out
- * and the start of fading in.
- */
- private static Rect getMinBounds(Rect maxBounds) {
- return new Rect((int) (maxBounds.left
- + (FULLSCREEN_SCALE_ADJUSTMENT_PERCENT * maxBounds.width())),
- (int) (maxBounds.top
- + (FULLSCREEN_SCALE_ADJUSTMENT_PERCENT * maxBounds.height())),
- (int) (maxBounds.right
- - (FULLSCREEN_SCALE_ADJUSTMENT_PERCENT * maxBounds.width())),
- (int) (maxBounds.bottom
- - (FULLSCREEN_SCALE_ADJUSTMENT_PERCENT * maxBounds.height())));
- }
+ return mVisualIndicatorViewContainer.getIndicatorBounds();
}
}
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 3b2598450800..522d83ec50eb 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
@@ -116,6 +116,7 @@ import com.android.wm.shell.recents.RecentsTransitionStateListener.RecentsTransi
import com.android.wm.shell.recents.RecentsTransitionStateListener.TRANSITION_STATE_NOT_RUNNING
import com.android.wm.shell.shared.TransitionUtil
import com.android.wm.shell.shared.annotations.ExternalThread
+import com.android.wm.shell.shared.annotations.ShellDesktopThread
import com.android.wm.shell.shared.annotations.ShellMainThread
import com.android.wm.shell.shared.desktopmode.DesktopModeCompatPolicy
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
@@ -135,7 +136,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
@@ -143,7 +143,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
@@ -176,13 +176,13 @@ class DesktopTasksController(
private val recentsTransitionHandler: RecentsTransitionHandler,
private val multiInstanceHelper: MultiInstanceHelper,
@ShellMainThread private val mainExecutor: ShellExecutor,
+ @ShellDesktopThread private val desktopExecutor: ShellExecutor,
private val desktopTasksLimiter: Optional<DesktopTasksLimiter>,
private val recentTasksController: RecentTasksController?,
private val interactionJankMonitor: InteractionJankMonitor,
@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,
@@ -202,26 +202,21 @@ class DesktopTasksController(
private var userId: Int
private val desktopModeShellCommandHandler: DesktopModeShellCommandHandler =
DesktopModeShellCommandHandler(this)
- private val mOnAnimationFinishedCallback =
- Consumer<SurfaceControl.Transaction> { t: SurfaceControl.Transaction ->
- visualIndicator?.releaseVisualIndicator(t)
- visualIndicator = null
- }
+
+ private val mOnAnimationFinishedCallback = { releaseVisualIndicator() }
+ private lateinit var snapEventHandler: SnapEventHandler
private val dragToDesktopStateListener =
object : DragToDesktopStateListener {
- override fun onCommitToDesktopAnimationStart(tx: SurfaceControl.Transaction) {
- removeVisualIndicator(tx)
+ override fun onCommitToDesktopAnimationStart() {
+ removeVisualIndicator()
}
- override fun onCancelToDesktopAnimationEnd(tx: SurfaceControl.Transaction) {
- removeVisualIndicator(tx)
+ override fun onCancelToDesktopAnimationEnd() {
+ removeVisualIndicator()
}
- private fun removeVisualIndicator(tx: SurfaceControl.Transaction) {
- visualIndicator?.fadeOutIndicator {
- visualIndicator?.releaseVisualIndicator(tx)
- visualIndicator = null
- }
+ private fun removeVisualIndicator() {
+ visualIndicator?.fadeOutIndicator { releaseVisualIndicator() }
}
}
@@ -274,7 +269,7 @@ class DesktopTasksController(
RecentsTransitionStateListener.stateToString(state),
)
recentsTransitionState = state
- desktopTilingDecorViewModel.onOverviewAnimationStateChange(
+ snapEventHandler.onOverviewAnimationStateChange(
RecentsTransitionStateListener.isAnimating(state)
)
}
@@ -305,6 +300,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) {
@@ -789,7 +789,7 @@ class DesktopTasksController(
taskInfo: RunningTaskInfo,
): ((IBinder) -> Unit)? {
val taskId = taskInfo.taskId
- desktopTilingDecorViewModel.removeTaskIfTiled(displayId, taskId)
+ snapEventHandler.removeTaskIfTiled(displayId, taskId)
performDesktopExitCleanupIfNeeded(taskId, displayId, wct, forceToFullscreen = false)
taskRepository.addClosingTask(displayId, taskId)
taskbarDesktopTaskListener?.onTaskbarCornerRoundingUpdate(
@@ -838,6 +838,7 @@ class DesktopTasksController(
val taskId = taskInfo.taskId
val displayId = taskInfo.displayId
val wct = WindowContainerTransaction()
+ snapEventHandler.removeTaskIfTiled(displayId, taskId)
performDesktopExitCleanupIfNeeded(taskId, displayId, wct, forceToFullscreen = false)
// Notify immersive handler as it might need to exit immersive state.
val exitResult =
@@ -848,7 +849,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,
@@ -863,7 +866,7 @@ class DesktopTasksController(
/** 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)
}
}
@@ -871,7 +874,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)
}
}
@@ -990,7 +993,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()
@@ -1232,7 +1235,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))
}
@@ -1358,7 +1361,6 @@ class DesktopTasksController(
position: SnapPosition,
resizeTrigger: ResizeTrigger,
inputMethod: InputMethod,
- desktopWindowDecoration: DesktopModeWindowDecoration,
) {
desktopModeEventLogger.logTaskResizingStarted(
resizeTrigger,
@@ -1380,13 +1382,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)
}
@@ -1423,7 +1419,6 @@ class DesktopTasksController(
position: SnapPosition,
resizeTrigger: ResizeTrigger,
inputMethod: InputMethod,
- desktopModeWindowDecoration: DesktopModeWindowDecoration,
) {
if (!isSnapResizingAllowed(taskInfo)) {
Toast.makeText(
@@ -1442,7 +1437,6 @@ class DesktopTasksController(
position,
resizeTrigger,
inputMethod,
- desktopModeWindowDecoration,
)
}
@@ -1454,7 +1448,6 @@ class DesktopTasksController(
currentDragBounds: Rect,
dragStartBounds: Rect,
motionEvent: MotionEvent,
- desktopModeWindowDecoration: DesktopModeWindowDecoration,
) {
releaseVisualIndicator()
if (!isSnapResizingAllowed(taskInfo)) {
@@ -1502,7 +1495,6 @@ class DesktopTasksController(
position,
resizeTrigger,
DesktopModeEventLogger.getInputMethodFromMotionEvent(motionEvent),
- desktopModeWindowDecoration,
)
}
}
@@ -1773,13 +1765,8 @@ class DesktopTasksController(
}
fun releaseVisualIndicator() {
- val t = SurfaceControl.Transaction()
- visualIndicator?.releaseVisualIndicator(t)
+ visualIndicator?.releaseVisualIndicator()
visualIndicator = null
- syncQueue.runInSync { transaction ->
- transaction.merge(t)
- t.close()
- }
}
override fun getContext(): Context = context
@@ -1999,6 +1986,9 @@ class DesktopTasksController(
splitPosition,
options.toBundle(),
/* hideTaskToken= */ null,
+ if (enableFlexibleSplit())
+ splitScreenController.determineNewInstanceIndex(callingTask)
+ else SPLIT_INDEX_UNDEFINED,
)
}
}
@@ -2175,7 +2165,7 @@ class DesktopTasksController(
return wct
}
if (!wct.isEmpty) {
- desktopTilingDecorViewModel.removeTaskIfTiled(task.displayId, task.taskId)
+ snapEventHandler.removeTaskIfTiled(task.displayId, task.taskId)
return wct
}
return null
@@ -2271,7 +2261,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(
@@ -2736,7 +2726,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,
@@ -2757,6 +2747,8 @@ class DesktopTasksController(
val indicator =
visualIndicator
?: DesktopModeVisualIndicator(
+ desktopExecutor,
+ mainExecutor,
syncQueue,
taskInfo,
displayController,
@@ -2794,7 +2786,6 @@ class DesktopTasksController(
validDragArea: Rect,
dragStartBounds: Rect,
motionEvent: MotionEvent,
- desktopModeWindowDecoration: DesktopModeWindowDecoration,
) {
if (taskInfo.configuration.windowConfiguration.windowingMode != WINDOWING_MODE_FREEFORM) {
return
@@ -2833,7 +2824,6 @@ class DesktopTasksController(
currentDragBounds,
dragStartBounds,
motionEvent,
- desktopModeWindowDecoration,
)
}
IndicatorType.TO_SPLIT_RIGHT_INDICATOR -> {
@@ -2848,7 +2838,6 @@ class DesktopTasksController(
currentDragBounds,
dragStartBounds,
motionEvent,
- desktopModeWindowDecoration,
)
}
IndicatorType.NO_INDICATOR,
@@ -3134,7 +3123,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. */
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 1aa081340bf9..aaecf8c2d727 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
@@ -257,8 +257,8 @@ sealed class DragToDesktopTransitionHandler(
// 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 +318,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(
@@ -497,8 +501,8 @@ sealed class DragToDesktopTransitionHandler(
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
}
@@ -642,7 +646,7 @@ sealed class DragToDesktopTransitionHandler(
startPosition.y.toInt() + unscaledStartHeight,
)
- dragToDesktopStateListener?.onCommitToDesktopAnimationStart(startTransaction)
+ dragToDesktopStateListener?.onCommitToDesktopAnimationStart()
// Accept the merge by applying the merging transaction (applied by #showResizeVeil)
// and finish callback. Show the veil and position the task at the first frame before
// starting the final animation.
@@ -773,7 +777,7 @@ sealed class DragToDesktopTransitionHandler(
addListener(
object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator) {
- dragToDesktopStateListener?.onCancelToDesktopAnimationEnd(tx)
+ dragToDesktopStateListener?.onCancelToDesktopAnimationEnd()
// Start the cancel transition to restore order.
startCancelDragToDesktopTransition()
}
@@ -866,9 +870,9 @@ sealed class DragToDesktopTransitionHandler(
)
interface DragToDesktopStateListener {
- fun onCommitToDesktopAnimationStart(tx: SurfaceControl.Transaction)
+ fun onCommitToDesktopAnimationStart()
- fun onCancelToDesktopAnimationEnd(tx: SurfaceControl.Transaction)
+ fun onCancelToDesktopAnimationEnd()
}
sealed class TransitionState {
@@ -1081,7 +1085,7 @@ constructor(
val startBoundsWithOffset =
Rect(startBounds).apply { offset(startPosition.x.toInt(), startPosition.y.toInt()) }
- dragToDesktopStateListener?.onCommitToDesktopAnimationStart(startTransaction)
+ dragToDesktopStateListener?.onCommitToDesktopAnimationStart()
// Accept the merge by applying the merging transaction (applied by #showResizeVeil)
// and finish callback. Show the veil and position the task at the first frame before
// starting the final animation.
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 f7f87ed63003..5ae1fca73d4e 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
@@ -50,9 +50,11 @@ import com.android.wm.shell.shared.annotations.ShellMainThread;
import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource;
import com.android.wm.shell.transition.Transitions;
+import kotlin.Unit;
+import kotlin.jvm.functions.Function0;
+
import java.util.ArrayList;
import java.util.List;
-import java.util.function.Consumer;
import java.util.function.Supplier;
@@ -69,7 +71,7 @@ public class ExitDesktopTaskTransitionHandler implements Transitions.TransitionH
@ShellMainThread
private final Handler mHandler;
private final List<IBinder> mPendingTransitionTokens = new ArrayList<>();
- private Consumer<SurfaceControl.Transaction> mOnAnimationFinishedCallback;
+ private Function0<Unit> mOnAnimationFinishedCallback;
private final Supplier<SurfaceControl.Transaction> mTransactionSupplier;
private Point mPosition;
@@ -106,7 +108,7 @@ public class ExitDesktopTaskTransitionHandler implements Transitions.TransitionH
*/
public void startTransition(@NonNull DesktopModeTransitionSource transitionSource,
@NonNull WindowContainerTransaction wct, Point position,
- Consumer<SurfaceControl.Transaction> onAnimationEndCallback) {
+ Function0<Unit> onAnimationEndCallback) {
mPosition = position;
mOnAnimationFinishedCallback = onAnimationEndCallback;
final IBinder token = mTransitions.startTransition(getExitTransitionType(transitionSource),
@@ -192,7 +194,7 @@ public class ExitDesktopTaskTransitionHandler implements Transitions.TransitionH
@Override
public void onAnimationEnd(Animator animation) {
if (mOnAnimationFinishedCallback != null) {
- mOnAnimationFinishedCallback.accept(finishT);
+ mOnAnimationFinishedCallback.invoke();
}
mInteractionJankMonitor.end(Cuj.CUJ_DESKTOP_MODE_EXIT_MODE);
mTransitions.getMainExecutor().execute(
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/VisualIndicatorViewContainer.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/VisualIndicatorViewContainer.kt
new file mode 100644
index 000000000000..2317274dbbf0
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/VisualIndicatorViewContainer.kt
@@ -0,0 +1,491 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.desktopmode
+
+import android.animation.Animator
+import android.animation.AnimatorListenerAdapter
+import android.animation.RectEvaluator
+import android.animation.ValueAnimator
+import android.app.ActivityManager
+import android.content.Context
+import android.graphics.PixelFormat
+import android.graphics.Rect
+import android.graphics.drawable.LayerDrawable
+import android.view.Display
+import android.view.SurfaceControl
+import android.view.SurfaceControlViewHost
+import android.view.View
+import android.view.WindowManager
+import android.view.WindowlessWindowManager
+import android.view.animation.DecelerateInterpolator
+import com.android.internal.annotations.VisibleForTesting
+import com.android.window.flags.Flags
+import com.android.wm.shell.R
+import com.android.wm.shell.common.DisplayController
+import com.android.wm.shell.common.DisplayLayout
+import com.android.wm.shell.common.ShellExecutor
+import com.android.wm.shell.common.SyncTransactionQueue
+import com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.IndicatorType
+import com.android.wm.shell.shared.annotations.ShellDesktopThread
+import com.android.wm.shell.shared.annotations.ShellMainThread
+import com.android.wm.shell.shared.bubbles.BubbleDropTargetBoundsProvider
+import com.android.wm.shell.windowdecor.WindowDecoration.SurfaceControlViewHostFactory
+
+/**
+ * Container for the view / viewhost of the indicator, ensuring it is created / animated off the
+ * main thread.
+ */
+@VisibleForTesting
+class VisualIndicatorViewContainer
+@JvmOverloads
+constructor(
+ @ShellDesktopThread private val desktopExecutor: ShellExecutor,
+ @ShellMainThread private val mainExecutor: ShellExecutor,
+ private val indicatorBuilder: SurfaceControl.Builder,
+ private val syncQueue: SyncTransactionQueue,
+ private val surfaceControlViewHostFactory: SurfaceControlViewHostFactory =
+ object : SurfaceControlViewHostFactory {},
+ private val bubbleBoundsProvider: BubbleDropTargetBoundsProvider?,
+) {
+ @VisibleForTesting var indicatorView: View? = null
+ private var indicatorViewHost: SurfaceControlViewHost? = null
+ // Below variables and the SyncTransactionQueue are the only variables that should
+ // be accessed from shell main thread. Everything else should be used exclusively
+ // from the desktop thread.
+ private var indicatorLeash: SurfaceControl? = null
+ private var isReleased = false
+
+ /** Create a fullscreen indicator with no animation */
+ @ShellMainThread
+ fun createView(
+ context: Context,
+ display: Display,
+ layout: DisplayLayout,
+ taskInfo: ActivityManager.RunningTaskInfo,
+ taskSurface: SurfaceControl,
+ ) {
+ if (isReleased) return
+ desktopExecutor.execute {
+ val resources = context.resources
+ val metrics = resources.displayMetrics
+ val screenWidth: Int
+ val screenHeight: Int
+ if (Flags.enableBugFixesForSecondaryDisplay()) {
+ screenWidth = layout.width()
+ screenHeight = layout.height()
+ } else {
+ screenWidth = metrics.widthPixels
+ screenHeight = metrics.heightPixels
+ }
+ indicatorView = View(context)
+ val leash =
+ indicatorBuilder
+ .setName("Desktop Mode Visual Indicator")
+ .setContainerLayer()
+ .setCallsite("DesktopModeVisualIndicator.createView")
+ .build()
+ val lp =
+ WindowManager.LayoutParams(
+ screenWidth,
+ screenHeight,
+ WindowManager.LayoutParams.TYPE_APPLICATION,
+ WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
+ PixelFormat.TRANSPARENT,
+ )
+ lp.title = "Desktop Mode Visual Indicator"
+ lp.setTrustedOverlay()
+ lp.inputFeatures =
+ lp.inputFeatures or WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL
+ val windowManager =
+ WindowlessWindowManager(
+ taskInfo.configuration,
+ leash,
+ /* hostInputTransferToken= */ null,
+ )
+ indicatorViewHost =
+ surfaceControlViewHostFactory.create(
+ context,
+ display,
+ windowManager,
+ "VisualIndicatorViewContainer",
+ )
+ indicatorView?.let { indicatorViewHost?.setView(it, lp) }
+ showIndicator(taskSurface, leash)
+ }
+ }
+
+ private fun showIndicator(taskSurface: SurfaceControl, leash: SurfaceControl) {
+ mainExecutor.execute {
+ indicatorLeash = leash
+ val t = SurfaceControl.Transaction()
+ t.show(indicatorLeash)
+ // We want this indicator to be behind the dragged task, but in front of all others.
+ t.setRelativeLayer(indicatorLeash, taskSurface, -1)
+ syncQueue.runInSync { transaction: SurfaceControl.Transaction ->
+ transaction.merge(t)
+ t.close()
+ }
+ }
+ }
+
+ @VisibleForTesting
+ fun getIndicatorBounds(): Rect {
+ return indicatorView?.background?.getBounds() ?: Rect()
+ }
+
+ /**
+ * Takes existing indicator and animates it to bounds reflecting a new indicator type. Should
+ * only be called from the main thread.
+ */
+ @ShellMainThread
+ fun transitionIndicator(
+ taskInfo: ActivityManager.RunningTaskInfo,
+ displayController: DisplayController,
+ currentType: IndicatorType,
+ newType: IndicatorType,
+ ) {
+ if (currentType == newType || isReleased) return
+ desktopExecutor.execute {
+ val layout =
+ displayController.getDisplayLayout(taskInfo.displayId)
+ ?: error("Expected to find DisplayLayout for taskId${taskInfo.taskId}.")
+ if (currentType == IndicatorType.NO_INDICATOR) {
+ fadeInIndicator(layout, newType)
+ } else if (newType == IndicatorType.NO_INDICATOR) {
+ fadeOutIndicator(layout, currentType, /* finishCallback= */ null)
+ } else {
+ val animStartType = IndicatorType.valueOf(currentType.name)
+ val animator =
+ indicatorView?.let {
+ VisualIndicatorAnimator.animateIndicatorType(
+ it,
+ layout,
+ animStartType,
+ newType,
+ bubbleBoundsProvider,
+ )
+ } ?: return@execute
+ animator.start()
+ }
+ }
+ }
+
+ /**
+ * Fade indicator in as provided type. Animator fades it in while expanding the bounds outwards.
+ */
+ @VisibleForTesting
+ fun fadeInIndicator(layout: DisplayLayout, type: IndicatorType) {
+ desktopExecutor.assertCurrentThread()
+ indicatorView?.let {
+ it.setBackgroundResource(R.drawable.desktop_windowing_transition_background)
+ val animator =
+ VisualIndicatorAnimator.fadeBoundsIn(it, type, layout, bubbleBoundsProvider)
+ animator.start()
+ }
+ }
+
+ /**
+ * Fade out indicator without fully releasing it. Animator fades it out while shrinking bounds.
+ *
+ * @param finishCallback called when animation ends or gets cancelled
+ */
+ fun fadeOutIndicator(
+ layout: DisplayLayout,
+ currentType: IndicatorType,
+ finishCallback: Runnable?,
+ ) {
+ if (currentType == IndicatorType.NO_INDICATOR) {
+ // In rare cases, fade out can be requested before the indicator has determined its
+ // initial type and started animating in. In this case, no animator is needed.
+ finishCallback?.run()
+ return
+ }
+ desktopExecutor.execute {
+ indicatorView?.let {
+ val animStartType = IndicatorType.valueOf(currentType.name)
+ val animator =
+ VisualIndicatorAnimator.fadeBoundsOut(
+ it,
+ animStartType,
+ layout,
+ bubbleBoundsProvider,
+ )
+ animator.addListener(
+ object : AnimatorListenerAdapter() {
+ override fun onAnimationEnd(animation: Animator) {
+ if (finishCallback != null) {
+ mainExecutor.execute(finishCallback)
+ }
+ }
+ }
+ )
+ animator.start()
+ }
+ }
+ }
+
+ /** Release the indicator and its components when it is no longer needed. */
+ @ShellMainThread
+ fun releaseVisualIndicator() {
+ if (isReleased) return
+ desktopExecutor.execute {
+ indicatorViewHost?.release()
+ indicatorViewHost = null
+ }
+ indicatorLeash?.let {
+ val tx = SurfaceControl.Transaction()
+ tx.remove(it)
+ indicatorLeash = null
+ syncQueue.runInSync { transaction: SurfaceControl.Transaction ->
+ transaction.merge(tx)
+ tx.close()
+ }
+ }
+ isReleased = true
+ }
+
+ /**
+ * Animator for Desktop Mode transitions which supports bounds and alpha animation. Functions
+ * should only be called from the desktop executor.
+ */
+ @VisibleForTesting
+ class VisualIndicatorAnimator(view: View, startBounds: Rect, endBounds: Rect) :
+ ValueAnimator() {
+ /**
+ * Determines how this animator will interact with the view's alpha: Fade in, fade out, or
+ * no change to alpha
+ */
+ private enum class AlphaAnimType {
+ ALPHA_FADE_IN_ANIM,
+ ALPHA_FADE_OUT_ANIM,
+ ALPHA_NO_CHANGE_ANIM,
+ }
+
+ private val indicatorView: View = view
+ @VisibleForTesting val indicatorStartBounds = Rect(startBounds)
+ @VisibleForTesting val indicatorEndBounds = endBounds
+ private val mRectEvaluator: RectEvaluator
+
+ init {
+ setFloatValues(0f, 1f)
+ mRectEvaluator = RectEvaluator(Rect())
+ }
+
+ /**
+ * Update bounds of view based on current animation fraction. Use of delta is to animate
+ * bounds independently, in case we need to run multiple animations simultaneously.
+ *
+ * @param fraction fraction to use, compared against previous fraction
+ * @param view the view to update
+ */
+ @ShellDesktopThread
+ private fun updateBounds(fraction: Float, view: View?) {
+ if (indicatorStartBounds == indicatorEndBounds) {
+ return
+ }
+ val currentBounds =
+ mRectEvaluator.evaluate(fraction, indicatorStartBounds, indicatorEndBounds)
+ view?.background?.bounds = currentBounds
+ }
+
+ /**
+ * Fade in the fullscreen indicator
+ *
+ * @param fraction current animation fraction
+ */
+ @ShellDesktopThread
+ private fun updateIndicatorAlpha(fraction: Float, view: View?) {
+ val drawable = view?.background as LayerDrawable
+ drawable.findDrawableByLayerId(R.id.indicator_stroke).alpha =
+ (MAXIMUM_OPACITY * fraction).toInt()
+ drawable.findDrawableByLayerId(R.id.indicator_solid).alpha =
+ (MAXIMUM_OPACITY * fraction * INDICATOR_FINAL_OPACITY).toInt()
+ }
+
+ companion object {
+ private const val FULLSCREEN_INDICATOR_DURATION = 200
+ private const val FULLSCREEN_SCALE_ADJUSTMENT_PERCENT = 0.015f
+ private const val INDICATOR_FINAL_OPACITY = 0.35f
+ private const val MAXIMUM_OPACITY = 255
+
+ @ShellDesktopThread
+ fun fadeBoundsIn(
+ view: View,
+ type: IndicatorType,
+ displayLayout: DisplayLayout,
+ bubbleBoundsProvider: BubbleDropTargetBoundsProvider?,
+ ): VisualIndicatorAnimator {
+ val endBounds = getIndicatorBounds(displayLayout, type, bubbleBoundsProvider)
+ val startBounds = getMinBounds(endBounds)
+ view.background.bounds = startBounds
+
+ val animator = VisualIndicatorAnimator(view, startBounds, endBounds)
+ animator.interpolator = DecelerateInterpolator()
+ setupIndicatorAnimation(animator, AlphaAnimType.ALPHA_FADE_IN_ANIM)
+ return animator
+ }
+
+ @ShellDesktopThread
+ fun fadeBoundsOut(
+ view: View,
+ type: IndicatorType,
+ displayLayout: DisplayLayout,
+ bubbleBoundsProvider: BubbleDropTargetBoundsProvider?,
+ ): VisualIndicatorAnimator {
+ val startBounds = getIndicatorBounds(displayLayout, type, bubbleBoundsProvider)
+ val endBounds = getMinBounds(startBounds)
+ view.background.bounds = startBounds
+
+ val animator = VisualIndicatorAnimator(view, startBounds, endBounds)
+ animator.interpolator = DecelerateInterpolator()
+ setupIndicatorAnimation(animator, AlphaAnimType.ALPHA_FADE_OUT_ANIM)
+ return animator
+ }
+
+ /**
+ * Create animator for visual indicator changing type (i.e., fullscreen to freeform,
+ * freeform to split, etc.)
+ *
+ * @param view the view for this indicator
+ * @param displayLayout information about the display the transitioning task is
+ * currently on
+ * @param origType the original indicator type
+ * @param newType the new indicator type
+ * @param desktopExecutor: the executor for the ShellDesktopThread; should be the only
+ * thread this function runs on
+ */
+ @ShellDesktopThread
+ fun animateIndicatorType(
+ view: View,
+ displayLayout: DisplayLayout,
+ origType: IndicatorType,
+ newType: IndicatorType,
+ bubbleBoundsProvider: BubbleDropTargetBoundsProvider?,
+ ): VisualIndicatorAnimator {
+ val startBounds = getIndicatorBounds(displayLayout, origType, bubbleBoundsProvider)
+ val endBounds = getIndicatorBounds(displayLayout, newType, bubbleBoundsProvider)
+ val animator = VisualIndicatorAnimator(view, startBounds, endBounds)
+ animator.interpolator = DecelerateInterpolator()
+ setupIndicatorAnimation(animator, AlphaAnimType.ALPHA_NO_CHANGE_ANIM)
+ return animator
+ }
+
+ /** Calculates the bounds the indicator should have when fully faded in. */
+ private fun getIndicatorBounds(
+ layout: DisplayLayout,
+ type: IndicatorType,
+ bubbleBoundsProvider: BubbleDropTargetBoundsProvider?,
+ ): Rect {
+ val desktopStableBounds = Rect()
+ layout.getStableBounds(desktopStableBounds)
+ val padding = desktopStableBounds.top
+ when (type) {
+ IndicatorType.TO_FULLSCREEN_INDICATOR -> {
+ desktopStableBounds.top += padding
+ desktopStableBounds.bottom -= padding
+ desktopStableBounds.left += padding
+ desktopStableBounds.right -= padding
+ return desktopStableBounds
+ }
+
+ IndicatorType.TO_DESKTOP_INDICATOR -> {
+ val adjustmentPercentage =
+ (1f - DesktopTasksController.DESKTOP_MODE_INITIAL_BOUNDS_SCALE)
+ return Rect(
+ (adjustmentPercentage * desktopStableBounds.width() / 2).toInt(),
+ (adjustmentPercentage * desktopStableBounds.height() / 2).toInt(),
+ (desktopStableBounds.width() -
+ (adjustmentPercentage * desktopStableBounds.width() / 2))
+ .toInt(),
+ (desktopStableBounds.height() -
+ (adjustmentPercentage * desktopStableBounds.height() / 2))
+ .toInt(),
+ )
+ }
+
+ IndicatorType.TO_SPLIT_LEFT_INDICATOR ->
+ return Rect(
+ padding,
+ padding,
+ desktopStableBounds.width() / 2 - padding,
+ desktopStableBounds.height(),
+ )
+
+ IndicatorType.TO_SPLIT_RIGHT_INDICATOR ->
+ return Rect(
+ desktopStableBounds.width() / 2 + padding,
+ padding,
+ desktopStableBounds.width() - padding,
+ desktopStableBounds.height(),
+ )
+ IndicatorType.TO_BUBBLE_LEFT_INDICATOR ->
+ return bubbleBoundsProvider?.getBubbleBarExpandedViewDropTargetBounds(
+ /* onLeft= */ true
+ ) ?: Rect()
+ IndicatorType.TO_BUBBLE_RIGHT_INDICATOR ->
+ return bubbleBoundsProvider?.getBubbleBarExpandedViewDropTargetBounds(
+ /* onLeft= */ false
+ ) ?: Rect()
+ else -> throw IllegalArgumentException("Invalid indicator type provided.")
+ }
+ }
+
+ /** Add necessary listener for animation of indicator */
+ private fun setupIndicatorAnimation(
+ animator: VisualIndicatorAnimator,
+ animType: AlphaAnimType,
+ ) {
+ animator.addUpdateListener { a: ValueAnimator ->
+ animator.updateBounds(a.animatedFraction, animator.indicatorView)
+ if (animType == AlphaAnimType.ALPHA_FADE_IN_ANIM) {
+ animator.updateIndicatorAlpha(a.animatedFraction, animator.indicatorView)
+ } else if (animType == AlphaAnimType.ALPHA_FADE_OUT_ANIM) {
+ animator.updateIndicatorAlpha(
+ 1 - a.animatedFraction,
+ animator.indicatorView,
+ )
+ }
+ }
+ animator.addListener(
+ object : AnimatorListenerAdapter() {
+ override fun onAnimationEnd(animation: Animator) {
+ animator.indicatorView.background.bounds = animator.indicatorEndBounds
+ }
+ }
+ )
+ animator.setDuration(FULLSCREEN_INDICATOR_DURATION.toLong())
+ }
+
+ /**
+ * Return the minimum bounds of a visual indicator, to be used at the end of fading out
+ * and the start of fading in.
+ */
+ private fun getMinBounds(maxBounds: Rect): Rect {
+ return Rect(
+ (maxBounds.left + (FULLSCREEN_SCALE_ADJUSTMENT_PERCENT * maxBounds.width()))
+ .toInt(),
+ (maxBounds.top + (FULLSCREEN_SCALE_ADJUSTMENT_PERCENT * maxBounds.height()))
+ .toInt(),
+ (maxBounds.right - (FULLSCREEN_SCALE_ADJUSTMENT_PERCENT * maxBounds.width()))
+ .toInt(),
+ (maxBounds.bottom - (FULLSCREEN_SCALE_ADJUSTMENT_PERCENT * maxBounds.height()))
+ .toInt(),
+ )
+ }
+ }
+ }
+}
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/pip2/phone/PipDragToResizeHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipDragToResizeHandler.java
new file mode 100644
index 000000000000..bd0b810b2a44
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipDragToResizeHandler.java
@@ -0,0 +1,218 @@
+/*
+ * 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;
+
+import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_BOTTOM;
+import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_LEFT;
+import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_RIGHT;
+import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_TOP;
+import static com.android.wm.shell.pip2.phone.PipMenuView.ANIM_TYPE_NONE;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Point;
+import android.graphics.PointF;
+import android.graphics.Rect;
+import android.graphics.Region;
+import android.view.MotionEvent;
+
+import com.android.internal.policy.TaskResizingAlgorithm;
+import com.android.wm.shell.R;
+import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
+import com.android.wm.shell.common.pip.PipBoundsState;
+
+import java.util.function.Function;
+
+/** Helper for handling drag-corner-to-resize gestures. */
+public class PipDragToResizeHandler {
+ private final Context mContext;
+ private final PipResizeGestureHandler mPipResizeGestureHandler;
+ private final PipBoundsState mPipBoundsState;
+ private final PhonePipMenuController mPhonePipMenuController;
+ private final PipBoundsAlgorithm mPipBoundsAlgorithm;
+ private final PipScheduler mPipScheduler;
+
+ private final Region mTmpRegion = new Region();
+ private final Rect mDragCornerSize = new Rect();
+ private final Rect mTmpTopLeftCorner = new Rect();
+ private final Rect mTmpTopRightCorner = new Rect();
+ private final Rect mTmpBottomLeftCorner = new Rect();
+ private final Rect mTmpBottomRightCorner = new Rect();
+ private final Rect mDisplayBounds = new Rect();
+ private final Function<Rect, Rect> mMovementBoundsSupplier;
+ private int mDelta;
+
+ public PipDragToResizeHandler(Context context, PipResizeGestureHandler pipResizeGestureHandler,
+ PipBoundsState pipBoundsState,
+ PhonePipMenuController phonePipMenuController, PipBoundsAlgorithm pipBoundsAlgorithm,
+ PipScheduler pipScheduler, Function<Rect, Rect> movementBoundsSupplier) {
+ mContext = context;
+ mPipResizeGestureHandler = pipResizeGestureHandler;
+ mPipBoundsState = pipBoundsState;
+ mPhonePipMenuController = phonePipMenuController;
+ mPipBoundsAlgorithm = pipBoundsAlgorithm;
+ mPipScheduler = pipScheduler;
+ mMovementBoundsSupplier = movementBoundsSupplier;
+ }
+
+ /** Invoked by {@link PipResizeGestureHandler#reloadResources}. */
+ void reloadResources() {
+ final Resources res = mContext.getResources();
+ mDelta = res.getDimensionPixelSize(R.dimen.pip_resize_edge_size);
+ }
+
+ /** Invoked by {@link PipResizeGestureHandler#onInputEvent} if drag-corner-to-resize is
+ * enabled. */
+ void onDragCornerResize(MotionEvent ev, Rect lastResizeBounds, PointF downPoint,
+ Rect downBounds, Point minSize, Point maxSize, float touchSlop) {
+ int action = ev.getActionMasked();
+ float x = ev.getX();
+ float y = ev.getY();
+ if (action == MotionEvent.ACTION_DOWN) {
+ lastResizeBounds.setEmpty();
+ final boolean allowGesture = isWithinDragResizeRegion((int) x, (int) y);
+ mPipResizeGestureHandler.setAllowGesture(allowGesture);
+ if (allowGesture) {
+ setCtrlType((int) x, (int) y);
+ downPoint.set(x, y);
+ downBounds.set(mPipBoundsState.getBounds());
+ }
+ } else if (mPipResizeGestureHandler.getAllowGesture()) {
+ switch (action) {
+ case MotionEvent.ACTION_POINTER_DOWN:
+ // We do not support multi touch for resizing via drag
+ mPipResizeGestureHandler.setAllowGesture(false);
+ break;
+ case MotionEvent.ACTION_MOVE:
+ final boolean thresholdCrossed = mPipResizeGestureHandler.getThresholdCrossed();
+ // Capture inputs
+ if (!mPipResizeGestureHandler.getThresholdCrossed()
+ && Math.hypot(x - downPoint.x, y - downPoint.y) > touchSlop) {
+ mPipResizeGestureHandler.setThresholdCrossed(true);
+ // Reset the down to begin resizing from this point
+ downPoint.set(x, y);
+ mPipResizeGestureHandler.pilferPointers();
+ }
+ if (mPipResizeGestureHandler.getThresholdCrossed()) {
+ if (mPhonePipMenuController.isMenuVisible()) {
+ mPhonePipMenuController.hideMenu(ANIM_TYPE_NONE,
+ false /* resize */);
+ }
+ final Rect currentPipBounds = mPipBoundsState.getBounds();
+ lastResizeBounds.set(TaskResizingAlgorithm.resizeDrag(x, y,
+ downPoint.x, downPoint.y, currentPipBounds,
+ mPipResizeGestureHandler.getCtrlType(), minSize.x,
+ minSize.y, maxSize, true,
+ downBounds.width() > downBounds.height()));
+ mPipBoundsAlgorithm.transformBoundsToAspectRatio(lastResizeBounds,
+ mPipBoundsState.getAspectRatio(), false /* useCurrentMinEdgeSize */,
+ true /* useCurrentSize */);
+ mPipScheduler.scheduleUserResizePip(lastResizeBounds);
+ mPipBoundsState.setHasUserResizedPip(true);
+ }
+ break;
+ case MotionEvent.ACTION_UP:
+ case MotionEvent.ACTION_CANCEL:
+ mPipResizeGestureHandler.finishResize();
+ break;
+ }
+ }
+ }
+
+ /**
+ * Check whether the current x,y coordinate is within the region in which drag-resize should
+ * start.
+ * This consists of 4 small squares on the 4 corners of the PIP window, a quarter of which
+ * overlaps with the PIP window while the rest goes outside of the PIP window.
+ * _ _ _ _
+ * |_|_|_________|_|_|
+ * |_|_| |_|_|
+ * | PIP |
+ * | WINDOW |
+ * _|_ _|_
+ * |_|_|_________|_|_|
+ * |_|_| |_|_|
+ */
+ boolean isWithinDragResizeRegion(int x, int y) {
+ final Rect currentPipBounds = mPipBoundsState.getBounds();
+ if (currentPipBounds == null) {
+ return false;
+ }
+ resetDragCorners();
+ mTmpTopLeftCorner.offset(currentPipBounds.left - mDelta / 2,
+ currentPipBounds.top - mDelta / 2);
+ mTmpTopRightCorner.offset(currentPipBounds.right - mDelta / 2,
+ currentPipBounds.top - mDelta / 2);
+ mTmpBottomLeftCorner.offset(currentPipBounds.left - mDelta / 2,
+ currentPipBounds.bottom - mDelta / 2);
+ mTmpBottomRightCorner.offset(currentPipBounds.right - mDelta / 2,
+ currentPipBounds.bottom - mDelta / 2);
+
+ mTmpRegion.setEmpty();
+ mTmpRegion.op(mTmpTopLeftCorner, Region.Op.UNION);
+ mTmpRegion.op(mTmpTopRightCorner, Region.Op.UNION);
+ mTmpRegion.op(mTmpBottomLeftCorner, Region.Op.UNION);
+ mTmpRegion.op(mTmpBottomRightCorner, Region.Op.UNION);
+
+ return mTmpRegion.contains(x, y);
+ }
+
+ private void resetDragCorners() {
+ mDragCornerSize.set(0, 0, mDelta, mDelta);
+ mTmpTopLeftCorner.set(mDragCornerSize);
+ mTmpTopRightCorner.set(mDragCornerSize);
+ mTmpBottomLeftCorner.set(mDragCornerSize);
+ mTmpBottomRightCorner.set(mDragCornerSize);
+ }
+
+ private void setCtrlType(int x, int y) {
+ final Rect currentPipBounds = mPipBoundsState.getBounds();
+ int ctrlType = mPipResizeGestureHandler.getCtrlType();
+
+ Rect movementBounds = mMovementBoundsSupplier.apply(currentPipBounds);
+
+ mDisplayBounds.set(movementBounds.left,
+ movementBounds.top,
+ movementBounds.right + currentPipBounds.width(),
+ movementBounds.bottom + currentPipBounds.height());
+
+ if (mTmpTopLeftCorner.contains(x, y) && currentPipBounds.top != mDisplayBounds.top
+ && currentPipBounds.left != mDisplayBounds.left) {
+ ctrlType |= CTRL_LEFT;
+ ctrlType |= CTRL_TOP;
+ }
+ if (mTmpTopRightCorner.contains(x, y) && currentPipBounds.top != mDisplayBounds.top
+ && currentPipBounds.right != mDisplayBounds.right) {
+ ctrlType |= CTRL_RIGHT;
+ ctrlType |= CTRL_TOP;
+ }
+ if (mTmpBottomRightCorner.contains(x, y)
+ && currentPipBounds.bottom != mDisplayBounds.bottom
+ && currentPipBounds.right != mDisplayBounds.right) {
+ ctrlType |= CTRL_RIGHT;
+ ctrlType |= CTRL_BOTTOM;
+ }
+ if (mTmpBottomLeftCorner.contains(x, y)
+ && currentPipBounds.bottom != mDisplayBounds.bottom
+ && currentPipBounds.left != mDisplayBounds.left) {
+ ctrlType |= CTRL_LEFT;
+ ctrlType |= CTRL_BOTTOM;
+ }
+
+ mPipResizeGestureHandler.setCtrlType(ctrlType);
+ }
+
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipPinchToResizeHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipPinchToResizeHandler.java
new file mode 100644
index 000000000000..1e41af379864
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipPinchToResizeHandler.java
@@ -0,0 +1,129 @@
+/*
+ * 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;
+
+import android.graphics.Point;
+import android.graphics.PointF;
+import android.graphics.Rect;
+import android.view.MotionEvent;
+
+import com.android.wm.shell.common.pip.PipBoundsState;
+import com.android.wm.shell.common.pip.PipPinchResizingAlgorithm;
+
+/** Helper for handling pinch-to-resize gestures. */
+public class PipPinchToResizeHandler {
+ private final PipResizeGestureHandler mPipResizeGestureHandler;
+ private final PipBoundsState mPipBoundsState;
+ private final PhonePipMenuController mPhonePipMenuController;
+ private final PipScheduler mPipScheduler;
+ private final PipPinchResizingAlgorithm mPinchResizingAlgorithm;
+
+ private int mFirstIndex = -1;
+ private int mSecondIndex = -1;
+
+ public PipPinchToResizeHandler(PipResizeGestureHandler pipResizeGestureHandler,
+ PipBoundsState pipBoundsState, PhonePipMenuController phonePipMenuController,
+ PipScheduler pipScheduler) {
+ mPipResizeGestureHandler = pipResizeGestureHandler;
+ mPipBoundsState = pipBoundsState;
+ mPhonePipMenuController = phonePipMenuController;
+ mPipScheduler = pipScheduler;
+
+ mPinchResizingAlgorithm = new PipPinchResizingAlgorithm();
+ }
+
+ /** Invoked by {@link PipResizeGestureHandler#onInputEvent} if pinch-to-resize is enabled. */
+ void onPinchResize(MotionEvent ev, PointF downPoint, PointF downSecondPoint, Rect downBounds,
+ PointF lastPoint, PointF lastSecondPoint, Rect lastResizeBounds, float touchSlop,
+ Point minSize, Point maxSize) {
+ int action = ev.getActionMasked();
+
+ if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
+ mFirstIndex = -1;
+ mSecondIndex = -1;
+ mPipResizeGestureHandler.setAllowGesture(false);
+ mPipResizeGestureHandler.finishResize();
+ }
+
+ if (ev.getPointerCount() != 2) {
+ return;
+ }
+
+ final Rect pipBounds = mPipBoundsState.getBounds();
+ if (action == MotionEvent.ACTION_POINTER_DOWN) {
+ if (mFirstIndex == -1 && mSecondIndex == -1
+ && pipBounds.contains((int) ev.getRawX(0), (int) ev.getRawY(0))
+ && pipBounds.contains((int) ev.getRawX(1), (int) ev.getRawY(1))) {
+ mPipResizeGestureHandler.setAllowGesture(true);
+ mFirstIndex = 0;
+ mSecondIndex = 1;
+ downPoint.set(ev.getRawX(mFirstIndex), ev.getRawY(mFirstIndex));
+ downSecondPoint.set(ev.getRawX(mSecondIndex), ev.getRawY(mSecondIndex));
+ downBounds.set(pipBounds);
+
+ lastPoint.set(downPoint);
+ lastSecondPoint.set(lastSecondPoint);
+ lastResizeBounds.set(downBounds);
+
+ // start the high perf session as the second pointer gets detected
+ mPipResizeGestureHandler.startHighPerfSession();
+ }
+ }
+
+ if (action == MotionEvent.ACTION_MOVE) {
+ if (mFirstIndex == -1 || mSecondIndex == -1) {
+ return;
+ }
+
+ float x0 = ev.getRawX(mFirstIndex);
+ float y0 = ev.getRawY(mFirstIndex);
+ float x1 = ev.getRawX(mSecondIndex);
+ float y1 = ev.getRawY(mSecondIndex);
+ lastPoint.set(x0, y0);
+ lastSecondPoint.set(x1, y1);
+
+ // Capture inputs
+ if (!mPipResizeGestureHandler.getThresholdCrossed()
+ && (distanceBetween(downSecondPoint, lastSecondPoint) > touchSlop
+ || distanceBetween(downPoint, lastPoint) > touchSlop)) {
+ mPipResizeGestureHandler.pilferPointers();
+ mPipResizeGestureHandler.setThresholdCrossed(true);
+ // Reset the down to begin resizing from this point
+ downPoint.set(lastPoint);
+ downSecondPoint.set(lastSecondPoint);
+
+ if (mPhonePipMenuController.isMenuVisible()) {
+ mPhonePipMenuController.hideMenu();
+ }
+ }
+
+ if (mPipResizeGestureHandler.getThresholdCrossed()) {
+ final float angle = mPinchResizingAlgorithm.calculateBoundsAndAngle(downPoint,
+ downSecondPoint, lastPoint, lastSecondPoint, minSize, maxSize,
+ downBounds, lastResizeBounds);
+
+ mPipResizeGestureHandler.setAngle(angle);
+ mPipScheduler.scheduleUserResizePip(lastResizeBounds, angle);
+ mPipBoundsState.setHasUserResizedPip(true);
+ }
+ }
+ }
+
+ private float distanceBetween(PointF p1, PointF p2) {
+ return (float) Math.hypot(p2.x - p1.x, p2.y - p1.y);
+ }
+
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipResizeGestureHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipResizeGestureHandler.java
index e4be3f60f86e..b869bf153c34 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipResizeGestureHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipResizeGestureHandler.java
@@ -44,13 +44,14 @@ import com.android.wm.shell.R;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
import com.android.wm.shell.common.pip.PipBoundsState;
+import com.android.wm.shell.common.pip.PipDesktopState;
import com.android.wm.shell.common.pip.PipDisplayLayoutState;
import com.android.wm.shell.common.pip.PipPerfHintController;
-import com.android.wm.shell.common.pip.PipPinchResizingAlgorithm;
import com.android.wm.shell.common.pip.PipUiEventLogger;
import com.android.wm.shell.pip2.animation.PipResizeAnimator;
import java.io.PrintWriter;
+import java.util.function.Function;
/**
* Helper on top of PipTouchHandler that handles inputs OUTSIDE of the PIP window, which is used to
@@ -72,8 +73,8 @@ public class PipResizeGestureHandler implements
private final PipTransitionState mPipTransitionState;
private final PhonePipMenuController mPhonePipMenuController;
private final PipDisplayLayoutState mPipDisplayLayoutState;
+ private final PipDesktopState mPipDesktopState;
private final PipUiEventLogger mPipUiEventLogger;
- private final PipPinchResizingAlgorithm mPinchResizingAlgorithm;
private final ShellExecutor mMainExecutor;
private final PointF mDownPoint = new PointF();
@@ -93,16 +94,18 @@ public class PipResizeGestureHandler implements
private boolean mIsAttached;
private boolean mIsEnabled;
private boolean mEnablePinchResize;
+ private boolean mEnableDragCornerResize;
private boolean mIsSysUiStateValid;
private boolean mThresholdCrossed;
private boolean mOngoingPinchToResize = false;
private boolean mWaitingForBoundsChangeTransition = false;
private float mAngle = 0;
- int mFirstIndex = -1;
- int mSecondIndex = -1;
+
private InputMonitor mInputMonitor;
private InputEventReceiver mInputEventReceiver;
+ private PipDragToResizeHandler mPipDragToResizeHandler;
+ private PipPinchToResizeHandler mPipPinchToResizeHandler;
@Nullable
private final PipPerfHintController mPipPerfHintController;
@@ -121,7 +124,9 @@ public class PipResizeGestureHandler implements
PipTransitionState pipTransitionState,
PipUiEventLogger pipUiEventLogger,
PhonePipMenuController menuActivityController,
+ Function<Rect, Rect> movementBoundsSupplier,
PipDisplayLayoutState pipDisplayLayoutState,
+ PipDesktopState pipDesktopState,
ShellExecutor mainExecutor,
@Nullable PipPerfHintController pipPerfHintController) {
mContext = context;
@@ -137,8 +142,13 @@ public class PipResizeGestureHandler implements
mPhonePipMenuController = menuActivityController;
mPipDisplayLayoutState = pipDisplayLayoutState;
+ mPipDesktopState = pipDesktopState;
mPipUiEventLogger = pipUiEventLogger;
- mPinchResizingAlgorithm = new PipPinchResizingAlgorithm();
+
+ mPipDragToResizeHandler = new PipDragToResizeHandler(context, this, pipBoundsState,
+ menuActivityController, pipBoundsAlgorithm, pipScheduler, movementBoundsSupplier);
+ mPipPinchToResizeHandler = new PipPinchToResizeHandler(this, pipBoundsState,
+ menuActivityController, pipScheduler);
}
void init() {
@@ -163,6 +173,7 @@ public class PipResizeGestureHandler implements
}
private void reloadResources() {
+ mPipDragToResizeHandler.reloadResources();
mTouchSlop = ViewConfiguration.get(mContext).getScaledTouchSlop();
}
@@ -180,6 +191,8 @@ public class PipResizeGestureHandler implements
void onActivityPinned() {
mIsAttached = true;
updateIsEnabled();
+ // Only enable drag-corner-to-resize if PiP was entered when Desktop Mode session is active.
+ mEnableDragCornerResize = mPipDesktopState.isPipInDesktopMode();
}
void onActivityUnpinned() {
@@ -211,9 +224,44 @@ public class PipResizeGestureHandler implements
}
}
+ boolean getAllowGesture() {
+ return mAllowGesture;
+ }
+
+ void setAllowGesture(boolean allowGesture) {
+ mAllowGesture = allowGesture;
+ }
+
+ boolean getThresholdCrossed() {
+ return mThresholdCrossed;
+ }
+
+ void setThresholdCrossed(boolean thresholdCrossed) {
+ mThresholdCrossed = thresholdCrossed;
+ }
+
+ int getCtrlType() {
+ return mCtrlType;
+ }
+
+ void setCtrlType(int ctrlType) {
+ mCtrlType = ctrlType;
+ }
+
+ void setAngle(float angle) {
+ mAngle = angle;
+ }
+
+ void startHighPerfSession() {
+ if (mPipPerfHintController != null) {
+ mPipHighPerfSession = mPipPerfHintController.startSession(
+ this::onHighPerfSessionTimeout, "onPinchResize");
+ }
+ }
+
@VisibleForTesting
void onInputEvent(InputEvent ev) {
- if (!mEnablePinchResize) {
+ if (!mEnableDragCornerResize && !mEnablePinchResize) {
// No need to handle anything if resizing isn't enabled.
return;
}
@@ -240,7 +288,12 @@ public class PipResizeGestureHandler implements
}
if (mOngoingPinchToResize) {
- onPinchResize(mv);
+ mPipPinchToResizeHandler.onPinchResize(mv, mDownPoint, mDownSecondPoint,
+ mDownBounds, mLastPoint, mLastSecondPoint, mLastResizeBounds, mTouchSlop,
+ mMinSize, mMaxSize);
+ } else if (mEnableDragCornerResize) {
+ mPipDragToResizeHandler.onDragCornerResize(mv, mLastResizeBounds, mDownPoint,
+ mDownBounds, mMinSize, mMaxSize, mTouchSlop);
}
}
}
@@ -261,20 +314,31 @@ public class PipResizeGestureHandler implements
}
boolean willStartResizeGesture(MotionEvent ev) {
- if (ev.getActionMasked() == MotionEvent.ACTION_POINTER_DOWN) {
- if (mEnablePinchResize && ev.getPointerCount() == 2) {
- onPinchResize(ev);
- mOngoingPinchToResize = mAllowGesture;
- return mAllowGesture;
- }
+ switch (ev.getActionMasked()) {
+ case MotionEvent.ACTION_DOWN:
+ if (mEnableDragCornerResize && mPipDragToResizeHandler.isWithinDragResizeRegion(
+ (int) ev.getRawX(),
+ (int) ev.getRawY())) {
+ return true;
+ }
+ break;
+
+ case MotionEvent.ACTION_POINTER_DOWN:
+ if (mEnablePinchResize && ev.getPointerCount() == 2) {
+ mPipPinchToResizeHandler.onPinchResize(ev, mDownPoint, mDownSecondPoint,
+ mDownBounds, mLastPoint, mLastSecondPoint, mLastResizeBounds,
+ mTouchSlop, mMinSize, mMaxSize);
+ mOngoingPinchToResize = mAllowGesture;
+ return mAllowGesture;
+ }
+ break;
+
+ default:
+ break;
}
return false;
}
- private boolean isInValidSysUiState() {
- return mIsSysUiStateValid;
- }
-
private void onHighPerfSessionTimeout(PipPerfHintController.PipHighPerfSession session) {}
private void cleanUpHighPerfSessionMaybe() {
@@ -285,83 +349,6 @@ public class PipResizeGestureHandler implements
}
}
- @VisibleForTesting
- void onPinchResize(MotionEvent ev) {
- int action = ev.getActionMasked();
-
- if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
- mFirstIndex = -1;
- mSecondIndex = -1;
- mAllowGesture = false;
- finishResize();
- }
-
- if (ev.getPointerCount() != 2) {
- return;
- }
-
- final Rect pipBounds = mPipBoundsState.getBounds();
- if (action == MotionEvent.ACTION_POINTER_DOWN) {
- if (mFirstIndex == -1 && mSecondIndex == -1
- && pipBounds.contains((int) ev.getRawX(0), (int) ev.getRawY(0))
- && pipBounds.contains((int) ev.getRawX(1), (int) ev.getRawY(1))) {
- mAllowGesture = true;
- mFirstIndex = 0;
- mSecondIndex = 1;
- mDownPoint.set(ev.getRawX(mFirstIndex), ev.getRawY(mFirstIndex));
- mDownSecondPoint.set(ev.getRawX(mSecondIndex), ev.getRawY(mSecondIndex));
- mDownBounds.set(pipBounds);
-
- mLastPoint.set(mDownPoint);
- mLastSecondPoint.set(mLastSecondPoint);
- mLastResizeBounds.set(mDownBounds);
-
- // start the high perf session as the second pointer gets detected
- if (mPipPerfHintController != null) {
- mPipHighPerfSession = mPipPerfHintController.startSession(
- this::onHighPerfSessionTimeout, "onPinchResize");
- }
- }
- }
-
- if (action == MotionEvent.ACTION_MOVE) {
- if (mFirstIndex == -1 || mSecondIndex == -1) {
- return;
- }
-
- float x0 = ev.getRawX(mFirstIndex);
- float y0 = ev.getRawY(mFirstIndex);
- float x1 = ev.getRawX(mSecondIndex);
- float y1 = ev.getRawY(mSecondIndex);
- mLastPoint.set(x0, y0);
- mLastSecondPoint.set(x1, y1);
-
- // Capture inputs
- if (!mThresholdCrossed
- && (distanceBetween(mDownSecondPoint, mLastSecondPoint) > mTouchSlop
- || distanceBetween(mDownPoint, mLastPoint) > mTouchSlop)) {
- pilferPointers();
- mThresholdCrossed = true;
- // Reset the down to begin resizing from this point
- mDownPoint.set(mLastPoint);
- mDownSecondPoint.set(mLastSecondPoint);
-
- if (mPhonePipMenuController.isMenuVisible()) {
- mPhonePipMenuController.hideMenu();
- }
- }
-
- if (mThresholdCrossed) {
- mAngle = mPinchResizingAlgorithm.calculateBoundsAndAngle(mDownPoint,
- mDownSecondPoint, mLastPoint, mLastSecondPoint, mMinSize, mMaxSize,
- mDownBounds, mLastResizeBounds);
-
- mPipScheduler.scheduleUserResizePip(mLastResizeBounds, mAngle);
- mPipBoundsState.setHasUserResizedPip(true);
- }
- }
- }
-
private void snapToMovementBoundsEdge(Rect bounds, Rect movementBounds) {
final int leftEdge = bounds.left;
@@ -404,17 +391,21 @@ public class PipResizeGestureHandler implements
// mPipTaskOrganizer.scheduleFinishResizePip(finalBounds, mUpdateResizeBoundsCallback);
}
- private void finishResize() {
+ /** Handles additional resizing and state changes after gesture resizing is done. */
+ void finishResize() {
if (mLastResizeBounds.isEmpty()) {
resetState();
}
- if (!mOngoingPinchToResize) {
- return;
- }
// Cache initial bounds after release for animation before mLastResizeBounds are modified.
mStartBoundsAfterRelease.set(mLastResizeBounds);
+ // Drag-corner-to-resize - we don't need to adjust the bounds at this point
+ if (!mOngoingPinchToResize) {
+ scheduleBoundsChange();
+ return;
+ }
+
// If user resize is pretty close to max size, just auto resize to max.
if (mLastResizeBounds.width() >= PINCH_RESIZE_AUTO_MAX_RATIO * mMaxSize.x
|| mLastResizeBounds.height() >= PINCH_RESIZE_AUTO_MAX_RATIO * mMaxSize.y) {
@@ -438,6 +429,10 @@ public class PipResizeGestureHandler implements
mLastResizeBounds, movementBounds);
mPipBoundsAlgorithm.applySnapFraction(mLastResizeBounds, snapFraction);
+ scheduleBoundsChange();
+ }
+
+ private void scheduleBoundsChange() {
// Update the transition state to schedule a resize transition.
Bundle extra = new Bundle();
extra.putBoolean(RESIZE_BOUNDS_CHANGE, true);
@@ -489,10 +484,6 @@ public class PipResizeGestureHandler implements
mOhmOffset = offset;
}
- private float distanceBetween(PointF p1, PointF p2) {
- return (float) Math.hypot(p2.x - p1.x, p2.y - p1.y);
- }
-
private void resizeRectAboutCenter(Rect rect, int w, int h) {
int cx = rect.centerX();
int cy = rect.centerY();
@@ -573,6 +564,7 @@ public class PipResizeGestureHandler implements
pw.println(innerPrefix + "mIsAttached=" + mIsAttached);
pw.println(innerPrefix + "mIsEnabled=" + mIsEnabled);
pw.println(innerPrefix + "mEnablePinchResize=" + mEnablePinchResize);
+ pw.println(innerPrefix + "mEnableDragCornerResize=" + mEnableDragCornerResize);
pw.println(innerPrefix + "mThresholdCrossed=" + mThresholdCrossed);
pw.println(innerPrefix + "mOhmOffset=" + mOhmOffset);
pw.println(innerPrefix + "mMinSize=" + mMinSize);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java
index e405f3339054..72346b335a8e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java
@@ -59,6 +59,7 @@ import com.android.wm.shell.common.FloatingContentCoordinator;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
import com.android.wm.shell.common.pip.PipBoundsState;
+import com.android.wm.shell.common.pip.PipDesktopState;
import com.android.wm.shell.common.pip.PipDisplayLayoutState;
import com.android.wm.shell.common.pip.PipDoubleTapHelper;
import com.android.wm.shell.common.pip.PipPerfHintController;
@@ -187,6 +188,7 @@ public class PipTouchHandler implements PipTransitionState.PipTransitionStateCha
@NonNull PipScheduler pipScheduler,
@NonNull SizeSpecSource sizeSpecSource,
@NonNull PipDisplayLayoutState pipDisplayLayoutState,
+ PipDesktopState pipDesktopState,
DisplayController displayController,
PipMotionHelper pipMotionHelper,
FloatingContentCoordinator floatingContentCoordinator,
@@ -226,7 +228,8 @@ public class PipTouchHandler implements PipTransitionState.PipTransitionStateCha
mainExecutor);
mPipResizeGestureHandler = new PipResizeGestureHandler(context, pipBoundsAlgorithm,
pipBoundsState, mTouchState, mPipScheduler, mPipTransitionState, pipUiEventLogger,
- menuController, mPipDisplayLayoutState, mainExecutor, mPipPerfHintController);
+ menuController, this::getMovementBounds, mPipDisplayLayoutState, pipDesktopState,
+ mainExecutor, mPipPerfHintController);
mPipBoundsState.addOnAspectRatioChangedCallback(aspectRatio -> {
updateMinMaxSize(aspectRatio);
onAspectRatioChanged();
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 a57b4b948b42..9adaa3614a0f 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
@@ -300,6 +300,9 @@ public class PipTransition extends PipTransitionController implements
mPipTransitionState.setState(PipTransitionState.EXITING_PIP);
return startRemoveAnimation(info, startTransaction, finishTransaction, finishCallback);
}
+ // For any unhandled transition, make sure the PiP surface is properly updated,
+ // i.e. corner and shadow radius.
+ syncPipSurfaceState(info, startTransaction, finishTransaction);
return false;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentsAnimationRunner.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentsAnimationRunner.aidl
index 8cdb8c4512a9..f8d84e4f3c21 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentsAnimationRunner.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentsAnimationRunner.aidl
@@ -59,11 +59,12 @@ oneway interface IRecentsAnimationRunner {
void onAnimationStart(in IRecentsAnimationController controller,
in RemoteAnimationTarget[] apps, in RemoteAnimationTarget[] wallpapers,
in Rect homeContentInsets, in Rect minimizedHomeBounds, in Bundle extras,
- in TransitionInfo info) = 2;
+ in @nullable TransitionInfo info) = 2;
/**
* Called when the task of an activity that has been started while the recents animation
* was running becomes ready for control.
*/
- void onTasksAppeared(in RemoteAnimationTarget[] app) = 3;
+ void onTasksAppeared(in RemoteAnimationTarget[] app,
+ in @nullable TransitionInfo transitionInfo) = 3;
}
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 7751741ae082..a969845fb8e8 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
@@ -1214,13 +1214,16 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler,
// Since we're accepting the merge, update the finish transaction so that changes via
// that transaction will be applied on top of those of the merged transitions
mFinishTransaction = finishT;
- // not using the incoming anim-only surfaces
- info.releaseAnimSurfaces();
+ boolean passTransitionInfo = ENABLE_DESKTOP_RECENTS_TRANSITIONS_CORNERS_BUGFIX.isTrue();
+ if (!passTransitionInfo) {
+ // not using the incoming anim-only surfaces
+ info.releaseAnimSurfaces();
+ }
if (appearedTargets != null) {
try {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
"[%d] RecentsController.merge: calling onTasksAppeared", mInstanceId);
- mListener.onTasksAppeared(appearedTargets);
+ mListener.onTasksAppeared(appearedTargets, passTransitionInfo ? info : null);
} catch (RemoteException e) {
Slog.e(TAG, "Error sending appeared tasks to recents animation", e);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
index e9f8a4a86d27..ba30d924e0b1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
@@ -638,6 +638,14 @@ public class SplitScreenController implements SplitDragPolicy.Starter,
}
/**
+ * Starts an existing task via StageCoordinator.
+ */
+ public void startTask(int taskId, @SplitPosition int position, @Nullable Bundle options,
+ @Nullable WindowContainerToken hideTaskToken, @SplitIndex int index) {
+ mStageCoordinator.startTask(taskId, position, options, hideTaskToken, index);
+ }
+
+ /**
* See {@link #startShortcut(String, String, int, Bundle, UserHandle)}
* @param instanceId to be used by {@link SplitscreenEventLogger}
*/
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..dd5439a8aa10 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
@@ -389,7 +389,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/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
index 1cc04b421132..add2c54f0e29 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 {
@@ -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
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleImageButton.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleImageButton.kt
index b21c3f522eab..458815d19658 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleImageButton.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleImageButton.kt
@@ -17,9 +17,12 @@
package com.android.wm.shell.windowdecor
import android.animation.ValueAnimator
+import android.annotation.DimenRes
+import android.content.res.Resources;
import android.content.Context
import android.util.AttributeSet
import android.widget.ImageButton
+import com.android.wm.shell.R
/**
* [ImageButton] for the handle at the top of fullscreen apps. Has custom hover
@@ -30,13 +33,23 @@ class HandleImageButton (context: Context?, attrs: AttributeSet?) :
ImageButton(context, attrs) {
private val handleAnimator = ValueAnimator()
+ /** Final horizontal padding for hover enter. **/
+ private val HANDLE_HOVER_ENTER_PADDING = loadDimensionPixelSize(
+ R.dimen.desktop_mode_fullscreen_decor_caption_horizontal_padding_hovered)
+ /** Final horizontal padding for press down. **/
+ private val HANDLE_PRESS_DOWN_PADDING = loadDimensionPixelSize(
+ R.dimen.desktop_mode_fullscreen_decor_caption_horizontal_padding_touched)
+ /** Default horizontal padding. **/
+ private val HANDLE_DEFAULT_PADDING = loadDimensionPixelSize(
+ R.dimen.desktop_mode_fullscreen_decor_caption_horizontal_padding_default)
+
override fun onHoverChanged(hovered: Boolean) {
super.onHoverChanged(hovered)
if (hovered) {
- animateHandle(HANDLE_HOVER_ANIM_DURATION, HANDLE_HOVER_ENTER_SCALE)
+ animateHandle(HANDLE_HOVER_ANIM_DURATION, HANDLE_HOVER_ENTER_PADDING)
} else {
if (!isPressed) {
- animateHandle(HANDLE_HOVER_ANIM_DURATION, HANDLE_DEFAULT_SCALE)
+ animateHandle(HANDLE_HOVER_ANIM_DURATION, HANDLE_DEFAULT_PADDING)
}
}
}
@@ -45,35 +58,37 @@ class HandleImageButton (context: Context?, attrs: AttributeSet?) :
if (isPressed != pressed) {
super.setPressed(pressed)
if (pressed) {
- animateHandle(HANDLE_PRESS_ANIM_DURATION, HANDLE_PRESS_DOWN_SCALE)
+ animateHandle(HANDLE_PRESS_ANIM_DURATION, HANDLE_PRESS_DOWN_PADDING)
} else {
- animateHandle(HANDLE_PRESS_ANIM_DURATION, HANDLE_DEFAULT_SCALE)
+ animateHandle(HANDLE_PRESS_ANIM_DURATION, HANDLE_DEFAULT_PADDING)
}
}
}
- private fun animateHandle(duration: Long, endScale: Float) {
+ private fun animateHandle(duration: Long, endPadding: Int) {
if (handleAnimator.isRunning) {
handleAnimator.cancel()
}
handleAnimator.duration = duration
- handleAnimator.setFloatValues(scaleX, endScale)
+ handleAnimator.setIntValues(paddingLeft, endPadding)
handleAnimator.addUpdateListener { animator ->
- scaleX = animator.animatedValue as Float
+ val padding = animator.animatedValue as Int
+ setPadding(padding, paddingTop, padding, paddingBottom)
}
handleAnimator.start()
}
+ private fun loadDimensionPixelSize(@DimenRes resourceId: Int): Int {
+ if (resourceId == Resources.ID_NULL) {
+ return 0
+ }
+ return context.resources.getDimensionPixelSize(resourceId)
+ }
+
companion object {
/** The duration of animations related to hover state. **/
private const val HANDLE_HOVER_ANIM_DURATION = 300L
/** The duration of animations related to pressed state. **/
private const val HANDLE_PRESS_ANIM_DURATION = 200L
- /** Ending scale for hover enter. **/
- private const val HANDLE_HOVER_ENTER_SCALE = 1.2f
- /** Ending scale for press down. **/
- private const val HANDLE_PRESS_DOWN_SCALE = 0.85f
- /** Default scale for handle. **/
- private const val HANDLE_DEFAULT_SCALE = 1f
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositioner.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositioner.kt
index a9c2e68e62a6..bb20292a51d4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositioner.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositioner.kt
@@ -237,8 +237,12 @@ class MultiDisplayVeiledResizeTaskPositioner(
val startDisplayLayout = displayController.getDisplayLayout(startDisplayId)
val currentDisplayLayout = displayController.getDisplayLayout(displayId)
- if (startDisplayLayout == null || currentDisplayLayout == null) {
- // Fall back to single-display drag behavior if any display layout is unavailable.
+ if (startDisplayId == displayId
+ || startDisplayLayout == null || currentDisplayLayout == null) {
+ // Fall back to single-display drag behavior if:
+ // 1. The drag destination display is the same as the start display. This prevents
+ // unnecessary animations caused by minor width/height changes due to DPI scaling.
+ // 2. Either the starting or current display layout is unavailable.
DragPositioningCallbackUtility.updateTaskBounds(
repositionTaskBounds,
taskBoundsAtDragStart,
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 76005c22972a..25dadfde274d 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
@@ -911,7 +911,6 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
}
}
- @VisibleForTesting
public interface SurfaceControlViewHostFactory {
default SurfaceControlViewHost create(Context c, Display d, WindowlessWindowManager wmm) {
return new SurfaceControlViewHost(c, d, wmm, "WindowDecoration");
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/unittest/src/com/android/wm/shell/bubbles/BubbleTransitionsTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleTransitionsTest.java
index b5911bfa54f2..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
@@ -186,15 +186,20 @@ public class BubbleTransitionsTest extends ShellTestCase {
verify(startT).setPosition(any(), eq(0f), eq(0f));
verify(mBubbleData).notificationEntryUpdated(eq(mBubble), anyBoolean(), anyBoolean());
- ctb.continueExpand();
clearInvocations(mBubble);
verify(mBubble, never()).setPreparingTransition(any());
ctb.surfaceCreated();
- verify(mBubble).setPreparingTransition(isNull());
+ // Check that preparing transition is not reset before continueExpand is called
+ verify(mBubble, never()).setPreparingTransition(any());
ArgumentCaptor<Runnable> animCb = ArgumentCaptor.forClass(Runnable.class);
verify(mLayerView).animateConvert(any(), any(), any(), any(), animCb.capture());
+
+ // continueExpand is now called, check that preparing transition is cleared
+ ctb.continueExpand();
+ verify(mBubble).setPreparingTransition(isNull());
+
assertFalse(finishCalled[0]);
animCb.getValue().run();
assertTrue(finishCalled[0]);
@@ -210,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/desktopmode/DesktopMixedTransitionHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandlerTest.kt
index 0b41952a89d7..77cd1e56853d 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
@@ -128,12 +129,21 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() {
@Test
fun startMinimizedModeTransition_callsFreeformTaskTransitionHandler() {
val wct = WindowContainerTransaction()
- whenever(freeformTaskTransitionHandler.startMinimizedModeTransition(any()))
+ val taskId = 1
+ val isLastTask = false
+ whenever(
+ freeformTaskTransitionHandler.startMinimizedModeTransition(
+ any(),
+ anyInt(),
+ anyBoolean(),
+ )
+ )
.thenReturn(mock())
- mixedHandler.startMinimizedModeTransition(wct)
+ mixedHandler.startMinimizedModeTransition(wct, taskId, isLastTask)
- verify(freeformTaskTransitionHandler).startMinimizedModeTransition(wct)
+ verify(freeformTaskTransitionHandler)
+ .startMinimizedModeTransition(eq(wct), eq(taskId), eq(isLastTask))
}
@Test
@@ -531,6 +541,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/DesktopModeVisualIndicatorTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicatorTest.kt
index 8f45cf4d08fc..20d50aa32f7f 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicatorTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicatorTest.kt
@@ -23,6 +23,7 @@ import android.graphics.Rect
import android.platform.test.annotations.EnableFlags
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper.RunWithLooper
+import android.view.Display
import android.view.SurfaceControl
import androidx.test.filters.SmallTest
import com.android.internal.policy.SystemBarUtils
@@ -30,6 +31,7 @@ import com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE
import com.android.wm.shell.R
import com.android.wm.shell.RootTaskDisplayAreaOrganizer
import com.android.wm.shell.ShellTestCase
+import com.android.wm.shell.TestShellExecutor
import com.android.wm.shell.common.DisplayController
import com.android.wm.shell.common.DisplayLayout
import com.android.wm.shell.common.SyncTransactionQueue
@@ -60,18 +62,22 @@ class DesktopModeVisualIndicatorTest : ShellTestCase() {
private lateinit var taskInfo: RunningTaskInfo
@Mock private lateinit var syncQueue: SyncTransactionQueue
@Mock private lateinit var displayController: DisplayController
+ @Mock private lateinit var display: Display
@Mock private lateinit var taskSurface: SurfaceControl
@Mock private lateinit var taskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer
@Mock private lateinit var displayLayout: DisplayLayout
@Mock private lateinit var bubbleBoundsProvider: BubbleDropTargetBoundsProvider
private lateinit var visualIndicator: DesktopModeVisualIndicator
+ private val desktopExecutor = TestShellExecutor()
+ private val mainExecutor = TestShellExecutor()
@Before
fun setUp() {
whenever(displayLayout.width()).thenReturn(DISPLAY_BOUNDS.width())
whenever(displayLayout.height()).thenReturn(DISPLAY_BOUNDS.height())
whenever(displayLayout.stableInsets()).thenReturn(STABLE_INSETS)
+ whenever(displayController.getDisplay(anyInt())).thenReturn(display)
whenever(displayController.getDisplayLayout(anyInt())).thenReturn(displayLayout)
whenever(displayController.getDisplay(anyInt())).thenReturn(mContext.display)
whenever(bubbleBoundsProvider.getBubbleBarExpandedViewDropTargetBounds(any()))
@@ -305,7 +311,11 @@ class DesktopModeVisualIndicatorTest : ShellTestCase() {
whenever(bubbleBoundsProvider.getBubbleBarExpandedViewDropTargetBounds(/* onLeft= */ true))
.thenReturn(dropTargetBounds)
createVisualIndicator(DesktopModeVisualIndicator.DragStartState.FROM_FULLSCREEN)
+ desktopExecutor.flushAll()
+ mainExecutor.flushAll()
visualIndicator.updateIndicatorType(PointF(100f, 1500f))
+ desktopExecutor.flushAll()
+ mainExecutor.flushAll()
animatorTestRule.advanceTimeBy(200)
@@ -322,7 +332,11 @@ class DesktopModeVisualIndicatorTest : ShellTestCase() {
whenever(bubbleBoundsProvider.getBubbleBarExpandedViewDropTargetBounds(/* onLeft= */ false))
.thenReturn(dropTargetBounds)
createVisualIndicator(DesktopModeVisualIndicator.DragStartState.FROM_FULLSCREEN)
+ desktopExecutor.flushAll()
+ mainExecutor.flushAll()
visualIndicator.updateIndicatorType(PointF(2300f, 1500f))
+ desktopExecutor.flushAll()
+ mainExecutor.flushAll()
animatorTestRule.advanceTimeBy(200)
@@ -332,6 +346,8 @@ class DesktopModeVisualIndicatorTest : ShellTestCase() {
private fun createVisualIndicator(dragStartState: DesktopModeVisualIndicator.DragStartState) {
visualIndicator =
DesktopModeVisualIndicator(
+ desktopExecutor,
+ mainExecutor,
syncQueue,
taskInfo,
displayController,
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 be53272185b6..08a4bb2db1ee 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
@@ -29,6 +29,7 @@ import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
import android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW
import android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED
import android.content.ComponentName
+import android.content.Context
import android.content.Intent
import android.content.pm.ActivityInfo
import android.content.pm.ActivityInfo.CONFIG_DENSITY
@@ -52,6 +53,7 @@ import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
import android.platform.test.flag.junit.FlagsParameterization
import android.testing.TestableContext
+import android.view.Display
import android.view.Display.DEFAULT_DISPLAY
import android.view.DragEvent
import android.view.Gravity
@@ -149,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
@@ -177,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
@@ -210,6 +212,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
@Mock lateinit var shellController: ShellController
@Mock lateinit var displayController: DisplayController
@Mock lateinit var displayLayout: DisplayLayout
+ @Mock lateinit var display: Display
@Mock lateinit var shellTaskOrganizer: ShellTaskOrganizer
@Mock lateinit var syncQueue: SyncTransactionQueue
@Mock lateinit var rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer
@@ -230,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
@@ -242,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
@@ -258,6 +260,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
@Mock private lateinit var userProfileContexts: UserProfileContexts
@Mock private lateinit var desksTransitionsObserver: DesksTransitionObserver
@Mock private lateinit var packageManager: PackageManager
+ @Mock private lateinit var mockDisplayContext: Context
private lateinit var controller: DesktopTasksController
private lateinit var shellInit: ShellInit
@@ -270,6 +273,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
private lateinit var spyContext: TestableContext
private val shellExecutor = TestShellExecutor()
+ private val bgExecutor = TestShellExecutor()
// Mock running tasks are registered here so we can get the list from mock shell task organizer
private val runningTasks = mutableListOf<RunningTaskInfo>()
@@ -326,6 +330,8 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
whenever(transitions.startTransition(anyInt(), any(), isNull())).thenAnswer { Binder() }
whenever(enterDesktopTransitionHandler.moveToDesktop(any(), any())).thenAnswer { Binder() }
whenever(displayController.getDisplayLayout(anyInt())).thenReturn(displayLayout)
+ whenever(displayController.getDisplayContext(anyInt())).thenReturn(mockDisplayContext)
+ whenever(displayController.getDisplay(anyInt())).thenReturn(display)
whenever(displayLayout.getStableBounds(any())).thenAnswer { i ->
(i.arguments.first() as Rect).set(STABLE_BOUNDS)
}
@@ -372,6 +378,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
recentsTransitionStateListener = captor.firstValue
controller.taskbarDesktopTaskListener = taskbarDesktopTaskListener
+ controller.setSnapEventHandler(snapEventHandler)
assumeTrue(ENABLE_SHELL_TRANSITIONS)
@@ -408,13 +415,13 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
recentsTransitionHandler,
multiInstanceHelper,
shellExecutor,
+ bgExecutor,
Optional.of(desktopTasksLimiter),
recentTasksController,
mockInteractionJankMonitor,
mockHandler,
desktopModeEventLogger,
desktopModeUiEventLogger,
- desktopTilingDecorViewModel,
desktopWallpaperActivityTokenProvider,
Optional.of(bubbleController),
overviewToDesktopTransitionObserver,
@@ -2755,13 +2762,20 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
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()
}
@@ -2777,18 +2791,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())
}
@@ -2812,13 +2834,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 }
}
@@ -2827,14 +2856,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)
}
@@ -2843,7 +2879,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)
@@ -2851,7 +2893,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()
}
@@ -2862,13 +2905,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()
}
@@ -2880,7 +2930,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)
@@ -2888,7 +2944,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)
}
@@ -2897,7 +2954,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)
@@ -2910,7 +2973,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(
@@ -4412,7 +4481,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)
@@ -4450,7 +4518,6 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
validDragArea = Rect(0, 50, 2000, 2000),
dragStartBounds = Rect(),
motionEvent,
- desktopWindowDecoration,
)
verify(transitions)
@@ -4490,7 +4557,6 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
validDragArea = Rect(0, 50, 2000, 2000),
dragStartBounds = Rect(),
motionEvent,
- desktopWindowDecoration,
)
verify(transitions)
@@ -4531,7 +4597,6 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
validDragArea = Rect(0, 50, 2000, 2000),
dragStartBounds = Rect(),
motionEvent,
- desktopWindowDecoration,
)
// Assert the task exits desktop mode
@@ -4569,7 +4634,6 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
validDragArea = Rect(0, 50, 2000, 2000),
dragStartBounds = Rect(),
motionEvent,
- desktopWindowDecoration,
)
// Assert bounds set to stable bounds
@@ -4625,7 +4689,6 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
validDragArea = Rect(0, 50, 2000, 2000),
dragStartBounds = Rect(),
motionEvent,
- desktopWindowDecoration,
)
// Assert that task is NOT updated via WCT
@@ -4857,7 +4920,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
val optionsCaptor = argumentCaptor<Bundle>()
runOpenInstance(task, taskToRequest.taskId)
verify(splitScreenController)
- .startTask(anyInt(), anyInt(), optionsCaptor.capture(), anyOrNull())
+ .startTask(anyInt(), anyInt(), optionsCaptor.capture(), anyOrNull(), any())
assertThat(ActivityOptions.fromBundle(optionsCaptor.firstValue).launchWindowingMode)
.isEqualTo(WINDOWING_MODE_MULTI_WINDOW)
}
@@ -4871,7 +4934,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
val optionsCaptor = argumentCaptor<Bundle>()
runOpenInstance(task, taskToRequest.taskId)
verify(splitScreenController)
- .startTask(anyInt(), anyInt(), optionsCaptor.capture(), anyOrNull())
+ .startTask(anyInt(), anyInt(), optionsCaptor.capture(), anyOrNull(), any())
assertThat(ActivityOptions.fromBundle(optionsCaptor.firstValue).launchWindowingMode)
.isEqualTo(WINDOWING_MODE_MULTI_WINDOW)
}
@@ -5045,7 +5108,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)
@@ -5091,7 +5153,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())
@@ -5135,7 +5196,6 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
currentDragBounds,
preDragBounds,
motionEvent,
- desktopWindowDecoration,
)
val wct = getLatestToggleResizeDesktopTaskWct(currentDragBounds)
assertThat(findBoundsChange(wct, task)).isEqualTo(expectedBounds)
@@ -5165,7 +5225,6 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
currentDragBounds,
preDragBounds,
motionEvent,
- desktopWindowDecoration,
)
verify(mReturnToDragStartAnimator)
.start(
@@ -5190,7 +5249,6 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
SnapPosition.LEFT,
ResizeTrigger.SNAP_LEFT_MENU,
InputMethod.MOUSE,
- desktopWindowDecoration,
)
// Assert that task is NOT updated via WCT
@@ -5217,7 +5275,6 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
SnapPosition.LEFT,
ResizeTrigger.SNAP_LEFT_MENU,
InputMethod.MOUSE,
- desktopWindowDecoration,
)
// Assert bounds set to half of the stable bounds
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/VisualIndicatorViewContainerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/VisualIndicatorViewContainerTest.kt
new file mode 100644
index 000000000000..79b0f1c7eadd
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/VisualIndicatorViewContainerTest.kt
@@ -0,0 +1,250 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.desktopmode
+
+import android.app.ActivityManager
+import android.app.ActivityManager.RunningTaskInfo
+import android.graphics.Rect
+import android.graphics.drawable.Drawable
+import android.graphics.drawable.LayerDrawable
+import android.platform.test.annotations.EnableFlags
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper.RunWithLooper
+import android.view.Display
+import android.view.SurfaceControl
+import android.view.SurfaceControlViewHost
+import android.view.View
+import androidx.test.filters.SmallTest
+import com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE
+import com.android.wm.shell.ShellTestCase
+import com.android.wm.shell.TestRunningTaskInfoBuilder
+import com.android.wm.shell.TestShellExecutor
+import com.android.wm.shell.common.DisplayController
+import com.android.wm.shell.common.DisplayLayout
+import com.android.wm.shell.common.SyncTransactionQueue
+import com.android.wm.shell.shared.bubbles.BubbleDropTargetBoundsProvider
+import com.android.wm.shell.windowdecor.WindowDecoration.SurfaceControlViewHostFactory
+import com.google.common.truth.Truth.assertThat
+import kotlin.test.Test
+import org.junit.Before
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.Mock
+import org.mockito.Mockito.mock
+import org.mockito.kotlin.any
+import org.mockito.kotlin.anyOrNull
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.spy
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.verifyZeroInteractions
+import org.mockito.kotlin.whenever
+
+/**
+ * Test class for [VisualIndicatorViewContainer] and [VisualIndicatorAnimator]
+ *
+ * Usage: atest WMShellUnitTests:VisualIndicatorViewContainerTest
+ */
+@SmallTest
+@RunWithLooper
+@RunWith(AndroidTestingRunner::class)
+@EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+class VisualIndicatorViewContainerTest : ShellTestCase() {
+ @Mock private lateinit var view: View
+ @Mock private lateinit var displayLayout: DisplayLayout
+ @Mock private lateinit var displayController: DisplayController
+ @Mock private lateinit var taskSurface: SurfaceControl
+ @Mock private lateinit var syncQueue: SyncTransactionQueue
+ @Mock private lateinit var mockSurfaceControlViewHostFactory: SurfaceControlViewHostFactory
+ @Mock private lateinit var mockBackground: LayerDrawable
+ @Mock private lateinit var bubbleDropTargetBoundsProvider: BubbleDropTargetBoundsProvider
+ private val taskInfo: RunningTaskInfo = createTaskInfo()
+ private val mainExecutor = TestShellExecutor()
+ private val desktopExecutor = TestShellExecutor()
+
+ @Before
+ fun setUp() {
+ whenever(displayController.getDisplayLayout(anyInt())).thenReturn(displayLayout)
+ whenever(displayLayout.getStableBounds(any())).thenAnswer { i ->
+ (i.arguments.first() as Rect).set(DISPLAY_BOUNDS)
+ }
+ whenever(mockSurfaceControlViewHostFactory.create(any(), any(), any()))
+ .thenReturn(mock(SurfaceControlViewHost::class.java))
+ }
+
+ @Test
+ fun testTransitionIndicator_sameTypeReturnsEarly() {
+ val spyViewContainer = setupSpyViewContainer()
+ // Test early return on startType == endType.
+ spyViewContainer.transitionIndicator(
+ taskInfo,
+ displayController,
+ DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR,
+ DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR,
+ )
+ desktopExecutor.flushAll()
+ verify(spyViewContainer)
+ .transitionIndicator(
+ eq(taskInfo),
+ eq(displayController),
+ eq(DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR),
+ eq(DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR),
+ )
+ // Assert fadeIn, fadeOut, and animateIndicatorType were not called.
+ verifyZeroInteractions(spyViewContainer)
+ }
+
+ @Test
+ fun testTransitionIndicator_firstTypeNoIndicator_callsFadeIn() {
+ val spyViewContainer = setupSpyViewContainer()
+ spyViewContainer.transitionIndicator(
+ taskInfo,
+ displayController,
+ DesktopModeVisualIndicator.IndicatorType.NO_INDICATOR,
+ DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR,
+ )
+ desktopExecutor.flushAll()
+ verify(spyViewContainer).fadeInIndicator(any(), any())
+ }
+
+ @Test
+ fun testTransitionIndicator_secondTypeNoIndicator_callsFadeOut() {
+ val spyViewContainer = setupSpyViewContainer()
+ spyViewContainer.transitionIndicator(
+ taskInfo,
+ displayController,
+ DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR,
+ DesktopModeVisualIndicator.IndicatorType.NO_INDICATOR,
+ )
+ desktopExecutor.flushAll()
+ verify(spyViewContainer)
+ .fadeOutIndicator(
+ any(),
+ eq(DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR),
+ anyOrNull(),
+ )
+ }
+
+ @Test
+ fun testTransitionIndicator_differentTypes_callsTransitionIndicator() {
+ val spyViewContainer = setupSpyViewContainer()
+ spyViewContainer.transitionIndicator(
+ taskInfo,
+ displayController,
+ DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR,
+ DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_LEFT_INDICATOR,
+ )
+ desktopExecutor.flushAll()
+ verify(spyViewContainer)
+ .transitionIndicator(
+ any(),
+ any(),
+ eq(DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR),
+ eq(DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_LEFT_INDICATOR),
+ )
+ }
+
+ @Test
+ fun testFadeInBoundsCalculation() {
+ val spyIndicator = setupSpyViewContainer()
+ val animator =
+ spyIndicator.indicatorView?.let {
+ VisualIndicatorViewContainer.VisualIndicatorAnimator.fadeBoundsIn(
+ it,
+ DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR,
+ displayLayout,
+ bubbleDropTargetBoundsProvider,
+ )
+ }
+ assertThat(animator?.indicatorStartBounds).isEqualTo(Rect(15, 15, 985, 985))
+ assertThat(animator?.indicatorEndBounds).isEqualTo(Rect(0, 0, 1000, 1000))
+ }
+
+ @Test
+ fun testFadeOutBoundsCalculation() {
+ val spyIndicator = setupSpyViewContainer()
+ val animator =
+ spyIndicator.indicatorView?.let {
+ VisualIndicatorViewContainer.VisualIndicatorAnimator.fadeBoundsOut(
+ it,
+ DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR,
+ displayLayout,
+ bubbleDropTargetBoundsProvider,
+ )
+ }
+ assertThat(animator?.indicatorStartBounds).isEqualTo(Rect(0, 0, 1000, 1000))
+ assertThat(animator?.indicatorEndBounds).isEqualTo(Rect(15, 15, 985, 985))
+ }
+
+ @Test
+ fun testChangeIndicatorTypeBoundsCalculation() {
+ // Test fullscreen to split-left bounds.
+ var animator =
+ VisualIndicatorViewContainer.VisualIndicatorAnimator.animateIndicatorType(
+ view,
+ displayLayout,
+ DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR,
+ DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_LEFT_INDICATOR,
+ bubbleDropTargetBoundsProvider,
+ )
+ // Test desktop to split-right bounds.
+ animator =
+ VisualIndicatorViewContainer.VisualIndicatorAnimator.animateIndicatorType(
+ view,
+ displayLayout,
+ DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR,
+ DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_RIGHT_INDICATOR,
+ bubbleDropTargetBoundsProvider,
+ )
+ }
+
+ private fun setupSpyViewContainer(): VisualIndicatorViewContainer {
+ val viewContainer =
+ VisualIndicatorViewContainer(
+ desktopExecutor,
+ mainExecutor,
+ SurfaceControl.Builder(),
+ syncQueue,
+ mockSurfaceControlViewHostFactory,
+ bubbleDropTargetBoundsProvider,
+ )
+ viewContainer.createView(
+ context,
+ mock(Display::class.java),
+ displayLayout,
+ taskInfo,
+ taskSurface,
+ )
+ desktopExecutor.flushAll()
+ viewContainer.indicatorView?.background = mockBackground
+ whenever(mockBackground.findDrawableByLayerId(anyInt()))
+ .thenReturn(mock(Drawable::class.java))
+ return spy(viewContainer)
+ }
+
+ private fun createTaskInfo(): RunningTaskInfo {
+ val taskDescriptionBuilder = ActivityManager.TaskDescription.Builder()
+ return TestRunningTaskInfoBuilder()
+ .setDisplayId(Display.DEFAULT_DISPLAY)
+ .setTaskDescriptionBuilder(taskDescriptionBuilder)
+ .setVisible(true)
+ .build()
+ }
+
+ companion object {
+ private val DISPLAY_BOUNDS = Rect(0, 0, 1000, 1000)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentsTransitionHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentsTransitionHandlerTest.java
index 439be9155b26..fd5e567f69ed 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentsTransitionHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentsTransitionHandlerTest.java
@@ -34,6 +34,7 @@ import static com.google.common.truth.Truth.assertThat;
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.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
@@ -293,6 +294,31 @@ public class RecentsTransitionHandlerTest extends ShellTestCase {
@Test
@EnableFlags(FLAG_ENABLE_DESKTOP_RECENTS_TRANSITIONS_CORNERS_BUGFIX)
+ public void testMerge_openingTasks_callsOnTasksAppeared() throws Exception {
+ final IRecentsAnimationRunner animationRunner = mock(IRecentsAnimationRunner.class);
+ TransitionInfo mergeTransitionInfo = new TransitionInfoBuilder(TRANSIT_OPEN)
+ .addChange(TRANSIT_OPEN, new TestRunningTaskInfoBuilder().build())
+ .build();
+ final IBinder transition = startRecentsTransition(/* synthetic= */ false, animationRunner);
+ SurfaceControl.Transaction finishT = mock(SurfaceControl.Transaction.class);
+ mRecentsTransitionHandler.startAnimation(
+ transition, createTransitionInfo(), new StubTransaction(), new StubTransaction(),
+ mock(Transitions.TransitionFinishCallback.class));
+
+ mRecentsTransitionHandler.findController(transition).merge(
+ mergeTransitionInfo,
+ new StubTransaction(),
+ finishT,
+ transition,
+ mock(Transitions.TransitionFinishCallback.class));
+ mMainExecutor.flushAll();
+
+ verify(animationRunner).onTasksAppeared(
+ /* appearedTargets= */ any(), eq(mergeTransitionInfo));
+ }
+
+ @Test
+ @EnableFlags(FLAG_ENABLE_DESKTOP_RECENTS_TRANSITIONS_CORNERS_BUGFIX)
public void testMergeAndFinish_openingFreeformTasks_setsCornerRadius() {
ActivityManager.RunningTaskInfo freeformTask =
new TestRunningTaskInfoBuilder().setWindowingMode(WINDOWING_MODE_FREEFORM).build();
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..741a0fdcf63c 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
@@ -157,23 +157,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 +200,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)
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/MultiDisplayVeiledResizeTaskPositionerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositionerTest.kt
index 63babc5e1c3c..937938df82c8 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositionerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositionerTest.kt
@@ -249,6 +249,25 @@ class MultiDisplayVeiledResizeTaskPositionerTest : ShellTestCase() {
}
@Test
+ fun testDragResize_movesTaskOnSameDisplay_noPxDpConversion() = runOnUiThread {
+ taskPositioner.onDragPositioningStart(
+ CTRL_TYPE_UNDEFINED,
+ DISPLAY_ID_0,
+ STARTING_BOUNDS.left.toFloat(),
+ STARTING_BOUNDS.top.toFloat(),
+ )
+
+ taskPositioner.onDragPositioningEnd(
+ DISPLAY_ID_0,
+ STARTING_BOUNDS.left.toFloat() + 70,
+ STARTING_BOUNDS.top.toFloat() + 20,
+ )
+
+ verify(spyDisplayLayout0, never()).localPxToGlobalDp(any(), any())
+ verify(spyDisplayLayout0, never()).globalDpToLocalPx(any(), any())
+ }
+
+ @Test
fun testDragResize_movesTaskToNewDisplay() = runOnUiThread {
taskPositioner.onDragPositioningStart(
CTRL_TYPE_UNDEFINED,
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/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/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..7e1f2e2a3490 100644
--- a/libs/hwui/aconfig/hwui_flags.aconfig
+++ b/libs/hwui/aconfig/hwui_flags.aconfig
@@ -174,7 +174,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 +187,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/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/media/java/android/media/MediaRoute2ProviderService.java b/media/java/android/media/MediaRoute2ProviderService.java
index 3104f9d42891..e94fb7d9e52b 100644
--- a/media/java/android/media/MediaRoute2ProviderService.java
+++ b/media/java/android/media/MediaRoute2ProviderService.java
@@ -55,6 +55,7 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.Deque;
import java.util.List;
+import java.util.Objects;
import java.util.concurrent.atomic.AtomicBoolean;
/**
@@ -96,12 +97,10 @@ public abstract class MediaRoute2ProviderService extends Service {
* system media, as described by {@link MediaRoute2Info#getSupportedRoutingTypes()}.
*
* @see #onCreateSystemRoutingSession
- * @hide
*/
- // TODO: b/362507305 - Unhide once the implementation and CTS are in place.
@FlaggedApi(Flags.FLAG_ENABLE_MIRRORING_IN_MEDIA_ROUTER_2)
@SdkConstant(SdkConstant.SdkConstantType.INTENT_CATEGORY)
- public static final String SERVICE_INTERFACE_SYSTEM_MEDIA =
+ public static final String CATEGORY_SYSTEM_MEDIA =
"android.media.MediaRoute2ProviderService.SYSTEM_MEDIA";
/**
@@ -165,9 +164,7 @@ public abstract class MediaRoute2ProviderService extends Service {
* The request has failed because the requested operation is not implemented by the provider.
*
* @see #notifyRequestFailed
- * @hide
*/
- // TODO: b/362507305 - Unhide once the implementation and CTS are in place.
@FlaggedApi(Flags.FLAG_ENABLE_MIRRORING_IN_MEDIA_ROUTER_2)
public static final int REASON_UNIMPLEMENTED = 5;
@@ -175,9 +172,7 @@ public abstract class MediaRoute2ProviderService extends Service {
* The request has failed because the provider has failed to route system media.
*
* @see #notifyRequestFailed
- * @hide
*/
- // TODO: b/362507305 - Unhide once the implementation and CTS are in place.
@FlaggedApi(Flags.FLAG_ENABLE_MIRRORING_IN_MEDIA_ROUTER_2)
public static final int REASON_FAILED_TO_REROUTE_SYSTEM_MEDIA = 6;
@@ -217,7 +212,7 @@ public abstract class MediaRoute2ProviderService extends Service {
* package (for example, if they affect the entire system).
*/
@GuardedBy("mRequestIdsLock")
- private final LongSparseArray<Integer> mSystemMediaSessionCreationRequests =
+ private final LongSparseArray<Integer> mSystemRoutingSessionCreationRequests =
new LongSparseArray<>();
@GuardedBy("mSessionLock")
@@ -350,7 +345,7 @@ public abstract class MediaRoute2ProviderService extends Service {
/**
* Notifies the system of the successful creation of a system media routing session.
*
- * <p>This method can only be called as the result of a prior call to {@link
+ * <p>This method must only be called as the result of a prior call to {@link
* #onCreateSystemRoutingSession}.
*
* @param requestId the ID of the {@link #onCreateSystemRoutingSession} request which this call
@@ -365,13 +360,13 @@ public abstract class MediaRoute2ProviderService extends Service {
* where you can clean up this session. {@link AudioRecord#startRecording()} must be called
* immediately on {@link MediaStreams#getAudioRecord()} after calling this method, in order
* to start streaming audio to the receiver.
- * @hide
+ * @throws IllegalStateException If the provided {@code requestId} doesn't correspond to a
+ * previous call to {@link #onCreateSystemRoutingSession}.
*/
- // TODO: b/362507305 - Unhide once the implementation and CTS are in place.
@FlaggedApi(Flags.FLAG_ENABLE_MIRRORING_IN_MEDIA_ROUTER_2)
@RequiresPermission(Manifest.permission.MODIFY_AUDIO_ROUTING)
@Nullable
- public final MediaStreams notifySystemMediaSessionCreated(
+ public final MediaStreams notifySystemRoutingSessionCreated(
long requestId,
@NonNull RoutingSessionInfo sessionInfo,
@NonNull MediaStreamsFormats formats) {
@@ -380,7 +375,7 @@ public abstract class MediaRoute2ProviderService extends Service {
if (DEBUG) {
Log.d(
TAG,
- "notifySystemMediaSessionCreated: Creating a session. requestId="
+ "notifySystemRoutingSessionCreated: Creating a session. requestId="
+ requestId
+ ", sessionInfo="
+ sessionInfo);
@@ -388,8 +383,8 @@ public abstract class MediaRoute2ProviderService extends Service {
Integer uid;
synchronized (mRequestIdsLock) {
- uid = mSystemMediaSessionCreationRequests.get(requestId);
- mSystemMediaSessionCreationRequests.remove(requestId);
+ uid = mSystemRoutingSessionCreationRequests.get(requestId);
+ mSystemRoutingSessionCreationRequests.remove(requestId);
}
if (uid == null) {
@@ -656,37 +651,34 @@ public abstract class MediaRoute2ProviderService extends Service {
/**
* Called when the service receives a request to create a system routing session.
*
- * <p>This method will only be called for routes that support routing of the system media, as
- * described by {@link MediaRoute2Info#getSupportedRoutingTypes()}.
+ * <p>This method must be overridden by subclasses that support routes that support routing
+ * {@link MediaRoute2Info#getSupportedRoutingTypes() system media}. The provided {@code routeId}
+ * will always correspond to a route that supports routing of the system media, as per {@link
+ * MediaRoute2Info#getSupportedRoutingTypes()}.
*
- * <p>Implementors of this method must call {@link #notifySystemMediaSessionCreated} with the
+ * <p>Implementors of this method must call {@link #notifySystemRoutingSessionCreated} with the
* given {@code requestId} to indicate a successful session creation. If the session creation
* fails (for example, if the connection to the receiver device fails), the implementor must
* call {@link #notifyRequestFailed}, passing the {@code requestId}.
*
* <p>Unlike {@link #onCreateSession}, system sessions route the system media (for example,
* audio and/or video) which is to be retrieved by calling {@link
- * #notifySystemMediaSessionCreated}.
+ * #notifySystemRoutingSessionCreated}.
*
* <p>Changes to the session can be notified by calling {@link #notifySessionUpdated}.
*
* @param requestId the ID of this request
- * @param packageName the package name of the application whose media to route.
* @param routeId the ID of the route initially being {@link
* RoutingSessionInfo#getSelectedRoutes() selected}.
- * @param sessionHints an optional bundle of arguments sent by {@link MediaRouter2}, or null if
- * none.
+ * @param parameters {@link SystemRoutingSessionParams} for the session creation.
* @see RoutingSessionInfo.Builder
- * @see #notifySystemMediaSessionCreated
- * @hide
+ * @see #notifySystemRoutingSessionCreated
*/
- // TODO: b/362507305 - Unhide once the implementation and CTS are in place.
@FlaggedApi(Flags.FLAG_ENABLE_MIRRORING_IN_MEDIA_ROUTER_2)
public void onCreateSystemRoutingSession(
long requestId,
- @NonNull String packageName,
@NonNull String routeId,
- @Nullable Bundle sessionHints) {
+ @NonNull SystemRoutingSessionParams parameters) {
mHandler.post(() -> notifyRequestFailed(requestId, REASON_UNIMPLEMENTED));
}
@@ -974,24 +966,29 @@ public abstract class MediaRoute2ProviderService extends Service {
int uid,
String packageName,
String routeId,
- @Nullable Bundle sessionHints) {
- if (!checkCallerIsSystem()) {
+ @Nullable Bundle extras) {
+ if (!Flags.enableMirroringInMediaRouter2() || !checkCallerIsSystem()) {
return;
}
if (!checkRouteIdIsValid(routeId, "requestCreateSession")) {
return;
}
synchronized (mRequestIdsLock) {
- mSystemMediaSessionCreationRequests.put(requestId, uid);
+ mSystemRoutingSessionCreationRequests.put(requestId, uid);
}
+ var sessionParamsBuilder =
+ new SystemRoutingSessionParams.Builder().setPackageName(packageName);
+ if (extras != null) {
+ sessionParamsBuilder.setExtras(extras);
+ }
+ var sessionParams = sessionParamsBuilder.build();
mHandler.sendMessage(
obtainMessage(
MediaRoute2ProviderService::onCreateSystemRoutingSession,
MediaRoute2ProviderService.this,
requestId,
- packageName,
routeId,
- sessionHints));
+ sessionParams));
}
@Override
@@ -1072,14 +1069,12 @@ public abstract class MediaRoute2ProviderService extends Service {
}
/**
- * Holds the streams to be routed as part of a system media routing session.
- *
- * <p>The encoded data format matches the {@link MediaStreamsFormats} passed to {@link
- * #notifySystemMediaSessionCreated}.
+ * Holds the streams to be routed as part of a {@link #onCreateSystemRoutingSession system media
+ * routing session}.
*
- * @hide
+ * <p>The encoded data format will match the {@link MediaStreamsFormats} passed to {@link
+ * #notifySystemRoutingSessionCreated}.
*/
- // TODO: b/362507305 - Unhide once the implementation and CTS are in place.
@FlaggedApi(Flags.FLAG_ENABLE_MIRRORING_IN_MEDIA_ROUTER_2)
public static final class MediaStreams {
@@ -1088,8 +1083,6 @@ public abstract class MediaRoute2ProviderService extends Service {
/**
* Holds the last {@link RoutingSessionInfo} associated with these streams.
- *
- * @hide
*/
@NonNull
// Access guarded by mSessionsLock, but it's not convenient to enforce through @GuardedBy.
@@ -1147,15 +1140,91 @@ public abstract class MediaRoute2ProviderService extends Service {
}
}
+ /**
+ * Holds parameters associated with a {@link #onCreateSystemRoutingSession session creation
+ * request}.
+ */
+ @FlaggedApi(Flags.FLAG_ENABLE_MIRRORING_IN_MEDIA_ROUTER_2)
+ public static final class SystemRoutingSessionParams {
+
+ private final String mPackageName;
+ private final Bundle mExtras;
+
+ private SystemRoutingSessionParams(Builder builder) {
+ this.mPackageName = builder.mPackageName;
+ this.mExtras = builder.mExtras;
+ }
+
+ /**
+ * Returns the name of the package associated with the session, or an empty string if not
+ * applicable.
+ *
+ * <p>The package name is not applicable if the session is not associated with a specific
+ * package, for example is the session affects the entire system.
+ */
+ @FlaggedApi(Flags.FLAG_ENABLE_MIRRORING_IN_MEDIA_ROUTER_2)
+ @NonNull
+ public String getPackageName() {
+ return mPackageName;
+ }
+
+ /** Returns a bundle provided by the client that triggered the session creation request. */
+ @FlaggedApi(Flags.FLAG_ENABLE_MIRRORING_IN_MEDIA_ROUTER_2)
+ @NonNull
+ public Bundle getExtras() {
+ return mExtras;
+ }
+
+ /** A builder for {@link SystemRoutingSessionParams}. */
+ public static final class Builder {
+ private String mPackageName;
+ private Bundle mExtras;
+
+ /** Constructor. */
+ public Builder() {
+ mPackageName = "";
+ mExtras = Bundle.EMPTY;
+ }
+
+ /**
+ * Sets the {@link #getExtras() extras}.
+ *
+ * <p>The default value is an empty {@link Bundle}.
+ *
+ * <p>Note that this bundle is not copied, so avoiding mutating the given {@link Bundle}
+ * after passing it to this method.
+ */
+ @NonNull
+ public Builder setExtras(@NonNull Bundle extras) {
+ mExtras = Objects.requireNonNull(extras);
+ return this;
+ }
+
+ /**
+ * Sets the {@link #getPackageName()}.
+ *
+ * <p>The default value is an empty string.
+ */
+ @NonNull
+ public Builder setPackageName(@NonNull String packageName) {
+ mPackageName = Objects.requireNonNull(packageName);
+ return this;
+ }
+
+ /** Returns a new {@link SystemRoutingSessionParams} instance. */
+ @NonNull
+ public SystemRoutingSessionParams build() {
+ return new SystemRoutingSessionParams(this);
+ }
+ }
+ }
/**
* Holds the formats to encode media data to be read from {@link MediaStreams}.
*
* @see MediaStreams
- * @see #notifySystemMediaSessionCreated
- * @hide
+ * @see #notifySystemRoutingSessionCreated
*/
- // TODO: b/362507305 - Unhide once the implementation and CTS are in place.
@FlaggedApi(Flags.FLAG_ENABLE_MIRRORING_IN_MEDIA_ROUTER_2)
public static final class MediaStreamsFormats {
@@ -1169,29 +1238,25 @@ public abstract class MediaRoute2ProviderService extends Service {
/**
* Returns the audio format to use for creating the {@link MediaStreams#getAudioRecord} to
- * return from {@link #notifySystemMediaSessionCreated}.
- *
- * @hide
+ * return from {@link #notifySystemRoutingSessionCreated}. May be null if the session
+ * doesn't support system audio.
*/
- // TODO: b/362507305 - Unhide once the implementation and CTS are in place.
@FlaggedApi(Flags.FLAG_ENABLE_MIRRORING_IN_MEDIA_ROUTER_2)
+ @Nullable
public AudioFormat getAudioFormat() {
return mAudioFormat;
}
/**
* Builder for {@link MediaStreamsFormats}
- *
- * @hide
*/
- // TODO: b/362507305 - Unhide once the implementation and CTS are in place.
@FlaggedApi(Flags.FLAG_ENABLE_MIRRORING_IN_MEDIA_ROUTER_2)
public static final class Builder {
private AudioFormat mAudioFormat;
/**
* Sets the audio format to use for creating the {@link MediaStreams#getAudioRecord} to
- * return from {@link #notifySystemMediaSessionCreated}.
+ * return from {@link #notifySystemRoutingSessionCreated}.
*
* @param audioFormat the audio format
* @return this builder
diff --git a/media/jni/android_media_MediaPlayer.cpp b/media/jni/android_media_MediaPlayer.cpp
index 647b55353257..9d197f48ed8d 100644
--- a/media/jni/android_media_MediaPlayer.cpp
+++ b/media/jni/android_media_MediaPlayer.cpp
@@ -1378,7 +1378,7 @@ static jintArray android_media_MediaPlayer_getRoutedDeviceIds(JNIEnv *env, jobje
}
jint* values = env->GetIntArrayElements(result, 0);
for (unsigned int i = 0; i < deviceIds.size(); i++) {
- values[i++] = static_cast<jint>(deviceIds[i]);
+ values[i] = static_cast<jint>(deviceIds[i]);
}
env->ReleaseIntArrayElements(result, values, 0);
return result;
diff --git a/media/jni/android_media_MediaRecorder.cpp b/media/jni/android_media_MediaRecorder.cpp
index 643fc8a2d925..2975a39c5fa5 100644
--- a/media/jni/android_media_MediaRecorder.cpp
+++ b/media/jni/android_media_MediaRecorder.cpp
@@ -743,7 +743,7 @@ android_media_MediaRecorder_getRoutedDeviceIds(JNIEnv *env, jobject thiz)
}
jint* values = env->GetIntArrayElements(result, 0);
for (unsigned int i = 0; i < deviceIds.size(); i++) {
- values[i++] = static_cast<jint>(deviceIds[i]);
+ values[i] = static_cast<jint>(deviceIds[i]);
}
env->ReleaseIntArrayElements(result, values, 0);
return result;
diff --git a/nfc-extras/OWNERS b/nfc-extras/OWNERS
index 35e9713f5715..2b82bc8ad073 100644
--- a/nfc-extras/OWNERS
+++ b/nfc-extras/OWNERS
@@ -1,2 +1,2 @@
# Bug component: 48448
-include platform/packages/apps/Nfc:/OWNERS
+include platform/packages/modules/Nfc:/OWNERS
diff --git a/nfc-non-updatable/java/android/nfc/cardemulation/ApduServiceInfo.java b/nfc-non-updatable/java/android/nfc/cardemulation/ApduServiceInfo.java
index d0de1fc14b0e..f2a68afbfcbe 100644
--- a/nfc-non-updatable/java/android/nfc/cardemulation/ApduServiceInfo.java
+++ b/nfc-non-updatable/java/android/nfc/cardemulation/ApduServiceInfo.java
@@ -70,6 +70,11 @@ import java.util.regex.Pattern;
public final class ApduServiceInfo implements Parcelable {
private static final String TAG = "ApduServiceInfo";
+ private static final Pattern PLPF_PATTERN =
+ Pattern.compile("[0-9A-Fa-f]{2,}[0-9A-Fa-f,\\?,\\*\\.]*");
+ private static final Pattern PLF_PATTERN =
+ Pattern.compile("[0-9A-Fa-f]{2,}");
+
/**
* Component level {@link android.content.pm.PackageManager.Property PackageManager
* .Property} for a system application to change its icon and label
@@ -472,7 +477,12 @@ public final class ApduServiceInfo implements Parcelable {
boolean autoTransact = a.getBoolean(
com.android.internal.R.styleable.PollingLoopFilter_autoTransact,
false);
- if (!mOnHost && !autoTransact) {
+ boolean isValidFilter = PLF_PATTERN.matcher(plf).matches()
+ && plf.length() % 2 == 0;
+ if (!isValidFilter) {
+ Log.e(TAG, "Ignoring polling-loop-filter " + plf
+ + " it is not a valid filter");
+ } else if (!mOnHost && !autoTransact) {
Log.e(TAG, "Ignoring polling-loop-filter " + plf
+ " for offhost service that isn't autoTransact");
} else {
@@ -489,8 +499,12 @@ public final class ApduServiceInfo implements Parcelable {
boolean autoTransact = a.getBoolean(
com.android.internal.R.styleable.PollingLoopFilter_autoTransact,
false);
- if (!mOnHost && !autoTransact) {
- Log.e(TAG, "Ignoring polling-loop-filter " + plf
+ boolean isValidFilter = PLPF_PATTERN.matcher(plf).matches();
+ if (!isValidFilter) {
+ Log.e(TAG, "Ignoring polling-loop-pattern-filter " + plf
+ + " it is not a valid pattern filter");
+ } else if (!mOnHost && !autoTransact) {
+ Log.e(TAG, "Ignoring polling-loop-pattern-filter " + plf
+ " for offhost service that isn't autoTransact");
} else {
mAutoTransactPatterns.put(Pattern.compile(plf), autoTransact);
@@ -814,6 +828,12 @@ public final class ApduServiceInfo implements Parcelable {
@FlaggedApi(Flags.FLAG_NFC_READ_POLLING_LOOP)
public void addPollingLoopFilter(@NonNull String pollingLoopFilter,
boolean autoTransact) {
+ if (!PLF_PATTERN.matcher(pollingLoopFilter).matches()
+ || pollingLoopFilter.length() % 2 != 0) {
+ throw new IllegalArgumentException(
+ "Polling loop filter must contain an even number of characters 0-9 or A-F"
+ );
+ }
if (!mOnHost && !autoTransact) {
return;
}
@@ -842,6 +862,11 @@ public final class ApduServiceInfo implements Parcelable {
@FlaggedApi(Flags.FLAG_NFC_READ_POLLING_LOOP)
public void addPollingLoopPatternFilter(@NonNull String pollingLoopPatternFilter,
boolean autoTransact) {
+ if (!PLPF_PATTERN.matcher(pollingLoopPatternFilter).matches()) {
+ throw new IllegalArgumentException(
+ "Polling loop pattern filter is invalid"
+ );
+ }
if (!mOnHost && !autoTransact) {
return;
}
diff --git a/omapi/OWNERS b/omapi/OWNERS
index 39c5c5bdeb09..375d643f7ed1 100644
--- a/omapi/OWNERS
+++ b/omapi/OWNERS
@@ -1,2 +1,2 @@
# Bug component: 456592
-include platform/packages/apps/Nfc:/OWNERS
+include platform/packages/modules/Nfc:/OWNERS
diff --git a/omapi/java/android/se/OWNERS b/omapi/java/android/se/OWNERS
index 39c5c5bdeb09..375d643f7ed1 100644
--- a/omapi/java/android/se/OWNERS
+++ b/omapi/java/android/se/OWNERS
@@ -1,2 +1,2 @@
# Bug component: 456592
-include platform/packages/apps/Nfc:/OWNERS
+include platform/packages/modules/Nfc:/OWNERS
diff --git a/omapi/java/android/se/omapi/OWNERS b/omapi/java/android/se/omapi/OWNERS
index 39c5c5bdeb09..375d643f7ed1 100644
--- a/omapi/java/android/se/omapi/OWNERS
+++ b/omapi/java/android/se/omapi/OWNERS
@@ -1,2 +1,2 @@
# Bug component: 456592
-include platform/packages/apps/Nfc:/OWNERS
+include platform/packages/modules/Nfc:/OWNERS
diff --git a/packages/SettingsLib/IntroPreference/res/layout/settingslib_expressive_preference_intro.xml b/packages/SettingsLib/IntroPreference/res/layout/settingslib_expressive_preference_intro.xml
index 7adcbf6c6601..cacd2740818c 100644
--- a/packages/SettingsLib/IntroPreference/res/layout/settingslib_expressive_preference_intro.xml
+++ b/packages/SettingsLib/IntroPreference/res/layout/settingslib_expressive_preference_intro.xml
@@ -17,6 +17,7 @@
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/entity_header"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@@ -38,7 +39,9 @@
android:id="@+id/collapsable_summary"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:gravity="center"/>
+ android:gravity="center"
+ android:minLines="1"
+ app:isCollapsable="true"/>
</LinearLayout>
diff --git a/packages/SettingsLib/IntroPreference/src/com/android/settingslib/widget/IntroPreference.kt b/packages/SettingsLib/IntroPreference/src/com/android/settingslib/widget/IntroPreference.kt
index f9931cf3238d..9d037e91a86f 100644
--- a/packages/SettingsLib/IntroPreference/src/com/android/settingslib/widget/IntroPreference.kt
+++ b/packages/SettingsLib/IntroPreference/src/com/android/settingslib/widget/IntroPreference.kt
@@ -33,8 +33,8 @@ class IntroPreference @JvmOverloads constructor(
defStyleRes: Int = 0
) : Preference(context, attrs, defStyleAttr, defStyleRes), GroupSectionDividerMixin {
- private var isCollapsable: Boolean = false
- private var minLines: Int = 2
+ private var isCollapsable: Boolean = true
+ private var minLines: Int = DEFAULT_MIN_LINES
private var hyperlinkListener: View.OnClickListener? = null
private var learnMoreListener: View.OnClickListener? = null
private var learnMoreText: CharSequence? = null
@@ -42,22 +42,6 @@ class IntroPreference @JvmOverloads constructor(
init {
layoutResource = R.layout.settingslib_expressive_preference_intro
isSelectable = false
-
- initAttributes(context, attrs, defStyleAttr)
- }
-
- private fun initAttributes(context: Context, attrs: AttributeSet?, defStyleAttr: Int) {
- context.obtainStyledAttributes(
- attrs,
- COLLAPSABLE_TEXT_VIEW_ATTRS, defStyleAttr, 0
- ).apply {
- isCollapsable = getBoolean(IS_COLLAPSABLE, false)
- minLines = getInt(
- MIN_LINES,
- if (isCollapsable) DEFAULT_MIN_LINES else DEFAULT_MAX_LINES
- ).coerceIn(1, DEFAULT_MAX_LINES)
- recycle()
- }
}
override fun onBindViewHolder(holder: PreferenceViewHolder) {
@@ -139,13 +123,6 @@ class IntroPreference @JvmOverloads constructor(
companion object {
private const val DEFAULT_MAX_LINES = 10
- private const val DEFAULT_MIN_LINES = 2
-
- private val COLLAPSABLE_TEXT_VIEW_ATTRS =
- com.android.settingslib.widget.theme.R.styleable.CollapsableTextView
- private val MIN_LINES =
- com.android.settingslib.widget.theme.R.styleable.CollapsableTextView_android_minLines
- private val IS_COLLAPSABLE =
- com.android.settingslib.widget.theme.R.styleable.CollapsableTextView_isCollapsable
+ private const val DEFAULT_MIN_LINES = 1
}
-} \ No newline at end of file
+}
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/CollapsableTextView.kt b/packages/SettingsLib/SettingsTheme/src/com/android/settingslib/widget/CollapsableTextView.kt
index 7eb9840e3e98..976711bdc5f3 100644
--- a/packages/SettingsLib/SettingsTheme/src/com/android/settingslib/widget/CollapsableTextView.kt
+++ b/packages/SettingsLib/SettingsTheme/src/com/android/settingslib/widget/CollapsableTextView.kt
@@ -40,7 +40,7 @@ class CollapsableTextView @JvmOverloads constructor(
defStyleAttr: Int = 0
) : ConstraintLayout(context, attrs, defStyleAttr) {
- private var isCollapsable: Boolean = false
+ private var isCollapsable: Boolean = DEFAULT_COLLAPSABLE
private var isCollapsed: Boolean = false
private var minLines: Int = DEFAULT_MIN_LINES
@@ -78,24 +78,38 @@ class CollapsableTextView @JvmOverloads constructor(
private fun initAttributes(context: Context, attrs: AttributeSet?, defStyleAttr: Int) {
context.obtainStyledAttributes(
- attrs, Attrs, defStyleAttr, 0
+ attrs, R.styleable.CollapsableTextView, defStyleAttr, 0
).apply {
- val gravity = getInt(GravityAttr, Gravity.START)
+ val gravity = getInt(gravityAttr, Gravity.START)
when (gravity) {
Gravity.CENTER_VERTICAL, Gravity.CENTER, Gravity.CENTER_HORIZONTAL -> {
centerHorizontally(titleTextView)
centerHorizontally(collapseButton)
}
}
+ isCollapsable = getBoolean(isCollapsableAttr, DEFAULT_COLLAPSABLE)
+ minLines = getInt(minLinesAttr, DEFAULT_MIN_LINES)
recycle()
}
}
private fun centerHorizontally(view: View) {
- (view.layoutParams as LayoutParams).apply {
- startToStart = LayoutParams.PARENT_ID
- endToEnd = LayoutParams.PARENT_ID
- horizontalBias = 0.5f
+ when (view) {
+ is MaterialButton -> {
+ (view.layoutParams as LayoutParams).apply {
+ startToStart = LayoutParams.PARENT_ID
+ endToEnd = LayoutParams.PARENT_ID
+ }
+ }
+ is TextView -> {
+ view.gravity = Gravity.CENTER
+ }
+ else -> {
+ (view.layoutParams as LayoutParams).apply {
+ startToStart = LayoutParams.PARENT_ID
+ endToEnd = LayoutParams.PARENT_ID
+ }
+ }
}
}
@@ -113,6 +127,8 @@ class CollapsableTextView @JvmOverloads constructor(
*/
fun setCollapsable(collapsable: Boolean) {
isCollapsable = collapsable
+ // Make is collapsed when it's collapsable
+ if (isCollapsable) isCollapsed = true
updateView()
}
@@ -120,8 +136,8 @@ class CollapsableTextView @JvmOverloads constructor(
* Sets the minimum number of lines to display when collapsed.
* @param lines The minimum number of lines.
*/
- fun setMinLines(line: Int) {
- minLines = line.coerceIn(1, DEFAULT_MAX_LINES)
+ fun setMinLines(lines: Int) {
+ minLines = lines.coerceIn(1, DEFAULT_MAX_LINES)
updateView()
}
@@ -198,7 +214,7 @@ class CollapsableTextView @JvmOverloads constructor(
}
learnMoreSpan = LearnMoreSpan(clickListener = learnMoreListener!!)
spannableLearnMoreText.setSpan(learnMoreSpan, 0, learnMoreText!!.length, 0)
- learnMoreTextView.setText(spannableLearnMoreText)
+ learnMoreTextView.text = spannableLearnMoreText
learnMoreTextView.visibility = VISIBLE
isLearnMoreEnabled = true
}
@@ -211,6 +227,8 @@ class CollapsableTextView @JvmOverloads constructor(
icon = collapseButtonResources.expandIcon
}
titleTextView.maxLines = minLines
+ titleTextView.ellipsize = null
+ titleTextView.scrollBarSize = 0
}
else -> {
@@ -219,6 +237,7 @@ class CollapsableTextView @JvmOverloads constructor(
icon = collapseButtonResources.collapseIcon
}
titleTextView.maxLines = DEFAULT_MAX_LINES
+ titleTextView.ellipsize = TextUtils.TruncateAt.END
}
}
collapseButton.visibility = if (isCollapsable) VISIBLE else GONE
@@ -235,17 +254,19 @@ class CollapsableTextView @JvmOverloads constructor(
companion object {
private const val DEFAULT_MAX_LINES = 10
private const val DEFAULT_MIN_LINES = 2
+ private const val DEFAULT_COLLAPSABLE = true
private const val LINK_BEGIN_MARKER = "LINK_BEGIN"
private const val LINK_END_MARKER = "LINK_END"
- private val Attrs = R.styleable.CollapsableTextView
- private val GravityAttr = R.styleable.CollapsableTextView_android_gravity
+ private val gravityAttr = R.styleable.CollapsableTextView_android_gravity
+ private val minLinesAttr = R.styleable.CollapsableTextView_android_minLines
+ private val isCollapsableAttr = R.styleable.CollapsableTextView_isCollapsable
}
}
internal class LearnMoreSpan(
- val url: String = "",
+ url: String = "",
val clickListener: View.OnClickListener) : URLSpan(url) {
override fun onClick(widget: View) {
clickListener.onClick(widget)
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index a3e42f1d1e51..4b0400fb3441 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -236,6 +236,14 @@
<string name="bluetooth_active_media_only_battery_level">Active (media only). <xliff:g id="battery_level_as_percentage">%1$s</xliff:g> battery.</string>
<!-- Connected devices settings. Message when Bluetooth is connected and active for media only, showing remote device status and battery level for untethered headset. [CHAR LIMIT=NONE] -->
<string name="bluetooth_active_media_only_battery_level_untethered">Active (media only). L: <xliff:g id="battery_level_as_percentage" example="25%">%1$s</xliff:g>, R: <xliff:g id="battery_level_as_percentage" example="25%">%2$s</xliff:g> battery.</string>
+ <!-- Connected devices settings. Message when Bluetooth is connected but not in use, showing remote device status and battery level for temporary bond device. [CHAR LIMIT=NONE] -->
+ <string name="bluetooth_guest_battery_level">Guest device. <xliff:g id="battery_level_as_percentage">%1$s</xliff:g> battery.</string>
+ <!-- Connected devices settings. Message when Bluetooth is connected but not in use, showing remote device status and battery level for temporary bond untethered headset. [CHAR LIMIT=NONE] -->
+ <string name="bluetooth_guest_battery_level_untethered">Guest device. L: <xliff:g id="battery_level_as_percentage" example="25%">%1$s</xliff:g>, R: <xliff:g id="battery_level_as_percentage" example="25%">%2$s</xliff:g> battery.</string>
+ <!-- Connected devices settings. Message when Bluetooth is connected and active for media only, showing remote device status and battery level for temporary bond device. [CHAR LIMIT=NONE] -->
+ <string name="bluetooth_guest_media_only_battery_level">Guest device (media only). <xliff:g id="battery_level_as_percentage">%1$s</xliff:g> battery.</string>
+ <!-- Connected devices settings. Message when Bluetooth is connected and active for media only, showing remote device status and battery level for temporary bond untethered headset. [CHAR LIMIT=NONE] -->
+ <string name="bluetooth_guest_media_only_battery_level_untethered">Guest device (media only). L: <xliff:g id="battery_level_as_percentage" example="25%">%1$s</xliff:g>, R: <xliff:g id="battery_level_as_percentage" example="25%">%2$s</xliff:g> battery.</string>
<!-- Connected devices settings. Message when Bluetooth is connected but not in use, showing remote device battery level, supports audio sharing. [CHAR LIMIT=NONE] -->
<string name="bluetooth_battery_level_lea_support">Connected (supports audio sharing). <xliff:g id="battery_level_as_percentage">%1$s</xliff:g> battery.</string>
<!-- Connected devices settings. Message when Bluetooth is connected but not in use, showing remote device battery level for untethered headset, supports audio sharing. [CHAR LIMIT=NONE] -->
@@ -246,10 +254,22 @@
<string name="bluetooth_battery_level_untethered_right_lea_support">Connected (supports audio sharing). Right: <xliff:g id="battery_level_as_percentage" example="25%">%1$s</xliff:g> battery.</string>
<!-- Connected devices settings. Message when Bluetooth is connected but not in use, showing no battery information, supports audio sharing. [CHAR LIMIT=NONE] -->
<string name="bluetooth_no_battery_level_lea_support">Connected (supports audio sharing)</string>
+ <!-- Connected devices settings. Message when Bluetooth is connected but not in use, showing remote device battery level, supports audio sharing. [CHAR LIMIT=NONE] -->
+ <string name="bluetooth_guest_battery_level_lea_support">Guest device (supports audio sharing). <xliff:g id="battery_level_as_percentage">%1$s</xliff:g> battery.</string>
+ <!-- Connected devices settings. Message when Bluetooth is connected but not in use, showing remote device battery level for untethered headset, supports audio sharing. [CHAR LIMIT=NONE] -->
+ <string name="bluetooth_guest_battery_level_untethered_lea_support">Guest device (supports audio sharing). L: <xliff:g id="battery_level_as_percentage" example="25%">%1$s</xliff:g>, R: <xliff:g id="battery_level_as_percentage" example="25%">%2$s</xliff:g> battery.</string>
+ <!-- Connected devices settings. Message when Bluetooth is connected but not in use, showing no battery information, supports audio sharing. [CHAR LIMIT=NONE] -->
+ <string name="bluetooth_guest_no_battery_level_lea_support">Guest device (supports audio sharing)</string>
<!-- Connected devices settings. Message when Bluetooth is connected and active for media only but no battery information, showing remote device status. [CHAR LIMIT=NONE] -->
<string name="bluetooth_active_media_only_no_battery_level">Active (media only)</string>
+ <!-- Connected devices settings. Message when Bluetooth is connected but not in use with no battery information, showing remote device status for temporary bond device. [CHAR LIMIT=NONE] -->
+ <string name="bluetooth_guest_no_battery_level">Guest device</string>
+ <!-- Connected devices settings. Message when Bluetooth is connected and active for media only but no battery information, showing remote device status for temporary bond device. [CHAR LIMIT=NONE] -->
+ <string name="bluetooth_guest_media_only_no_battery_level">Guest device (media only)</string>
<!-- Connected devices settings. Message shown when bluetooth device is disconnected but is a known, previously connected device, supports audio sharing [CHAR LIMIT=NONE] -->
<string name="bluetooth_saved_device_lea_support">Supports audio sharing</string>
+ <!-- Connected devices settings. Message shown when temporary bond bluetooth device is disconnected but is a known, previously connected device, supports audio sharing [CHAR LIMIT=NONE] -->
+ <string name="bluetooth_guest_saved_device_lea_support">Guest device. Supports audio sharing</string>
<!-- Connected device settings. Message when the left-side hearing aid device is active for media only. [CHAR LIMIT=NONE] -->
<string name="bluetooth_hearing_aid_media_only_left_active">Active (media only), left only</string>
@@ -1103,6 +1123,13 @@
<!-- Developer settings: text for the WebView provider selection toast shown if an invalid provider was chosen (i.e. the setting list was stale). [CHAR LIMIT=NONE] -->
<string name="select_webview_provider_toast_text">This choice is no longer valid. Try again.</string>
+ <!-- [CHAR LIMIT=50] Label for button to launch current WebView provider's DevTools (developer tools) UI -->
+ <string name="webview_launch_devtools_title">WebView DevTools</string>
+ <!-- [CHAR LIMIT=50] Error toast shown to user when no (current) WebView package is found (when trying to launch DevTools) -->
+ <string name="webview_launch_devtools_no_package">WebView package not found.</string>
+ <!-- [CHAR LIMIT=50] Error toast shown to user when DevTools (developer tools) activity could not be launched (despite there being a WebView package) -->
+ <string name="webview_launch_devtools_no_activity">Could not launch DevTools.</string>
+
<!-- Name of feature to change color setting for the display [CHAR LIMIT=60] -->
<string name="picture_color_mode">Picture color mode</string>
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
index 7374f80fd9db..97a345efd566 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,
diff --git a/packages/SettingsLib/src/com/android/settingslib/utils/CustomDialogHelper.java b/packages/SettingsLib/src/com/android/settingslib/utils/CustomDialogHelper.java
index 6e64c597f5cc..34e08af18f93 100644
--- a/packages/SettingsLib/src/com/android/settingslib/utils/CustomDialogHelper.java
+++ b/packages/SettingsLib/src/com/android/settingslib/utils/CustomDialogHelper.java
@@ -34,6 +34,8 @@ import com.android.settingslib.R;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import androidx.annotation.NonNull;
+
/**
* This class is used to create custom dialog with icon, title, message and custom view that are
* horizontally centered.
@@ -191,20 +193,52 @@ public class CustomDialogHelper {
}
/**
+ * Sets title of the dialog by string.
+ */
+ @NonNull public CustomDialogHelper setTitle(@NonNull CharSequence title) {
+ mDialogTitle.setText(title);
+ return this;
+ }
+
+ /**
+ * Sets title padding of the dialog.
+ */
+ @NonNull public CustomDialogHelper setTitlePadding(int left, int top, int right, int bottom) {
+ mDialogTitle.setPadding(left, top, right, bottom);
+ return this;
+ }
+
+ /**
* Sets message of the dialog.
*/
- public CustomDialogHelper setMessage(@StringRes int resid) {
+ @NonNull public CustomDialogHelper setMessage(@StringRes int resid) {
mDialogMessage.setText(resid);
return this;
}
/**
+ * Sets message of the dialog by string.
+ */
+ @NonNull public CustomDialogHelper setMessage(@NonNull CharSequence message) {
+ mDialogMessage.setText(message);
+ return this;
+ }
+
+ /**
* Sets message padding of the dialog.
*/
- public CustomDialogHelper setMessagePadding(int dp) {
+ @NonNull public CustomDialogHelper setMessagePadding(int dp) {
mDialogMessage.setPadding(dp, dp, dp, dp);
return this;
}
+ /**
+ * Sets message padding of the dialog.
+ */
+ @NonNull
+ public CustomDialogHelper setMessagePadding(int left, int top, int right, int bottom) {
+ mDialogMessage.setPadding(left, top, right, bottom);
+ return this;
+ }
/**
* Sets icon of the dialog.
@@ -215,6 +249,15 @@ public class CustomDialogHelper {
}
/**
+ * Sets icon padding of the dialog.
+ */
+ @NonNull
+ public CustomDialogHelper setIconPadding(int left, int top, int right, int bottom) {
+ mDialogIcon.setPadding(left, top, right, bottom);
+ return this;
+ }
+
+ /**
* Removes all views that were previously added to the custom layout part.
*/
public CustomDialogHelper clearCustomLayout() {
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/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java
index 4f5203136bbc..ed11e12c32ff 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java
@@ -209,7 +209,7 @@ public class SettingsHelper {
// Get datatype for B&R metrics logging.
String datatype = "";
- if (Flags.enableMetricsSettingsBackupAgents()) {
+ if (areAgentMetricsEnabled()) {
datatype = SettingsBackupRestoreKeys.getKeyFromUri(destination);
}
@@ -299,7 +299,7 @@ public class SettingsHelper {
contentValues.put(Settings.NameValueTable.NAME, name);
contentValues.put(Settings.NameValueTable.VALUE, value);
cr.insert(destination, contentValues);
- if (Flags.enableMetricsSettingsBackupAgents()) {
+ if (areAgentMetricsEnabled()) {
mBackupRestoreEventLogger.logItemsRestored(datatype, /* count= */ 1);
}
} catch (Exception e) {
@@ -308,7 +308,7 @@ public class SettingsHelper {
sendBroadcastSystemUI = false;
sendBroadcastAccessibility = false;
Log.e(TAG, "Failed to restore setting name: " + name + " + value: " + value, e);
- if (Flags.enableMetricsSettingsBackupAgents()) {
+ if (areAgentMetricsEnabled()) {
mBackupRestoreEventLogger.logItemsRestoreFailed(
datatype, /* count= */ 1, ERROR_FAILED_TO_RESTORE_SETTING);
}
@@ -785,12 +785,12 @@ public class SettingsHelper {
am.updatePersistentConfigurationWithAttribution(config, mContext.getOpPackageName(),
mContext.getAttributionTag());
- if (Flags.enableMetricsSettingsBackupAgents() && mBackupRestoreEventLogger != null) {
+ if (areAgentMetricsEnabled()) {
mBackupRestoreEventLogger
.logItemsRestored(SettingsBackupRestoreKeys.KEY_LOCALE, localeList.size());
}
} catch (RemoteException e) {
- if (Flags.enableMetricsSettingsBackupAgents() && mBackupRestoreEventLogger != null) {
+ if (areAgentMetricsEnabled()) {
mBackupRestoreEventLogger
.logItemsRestoreFailed(
SettingsBackupRestoreKeys.KEY_LOCALE,
@@ -817,4 +817,8 @@ public class SettingsHelper {
void setBackupRestoreEventLogger(BackupRestoreEventLogger backupRestoreEventLogger) {
mBackupRestoreEventLogger = backupRestoreEventLogger;
}
+
+ private boolean areAgentMetricsEnabled() {
+ return Flags.enableMetricsSettingsBackupAgents() && mBackupRestoreEventLogger != null;
+ }
}
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/WritableNamespacePrefixes.java b/packages/SettingsProvider/src/com/android/providers/settings/WritableNamespacePrefixes.java
index 74fd828f97ea..274fd3593918 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/WritableNamespacePrefixes.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/WritableNamespacePrefixes.java
@@ -124,6 +124,7 @@ final class WritableNamespacePrefixes {
"privacy",
"private_compute_services",
"profcollect_native_boot",
+ "profiling_testing",
"remote_auth",
"remote_key_provisioning_native",
"rollback",
diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsHelperRestoreTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsHelperRestoreTest.java
index 62c03ddc42b9..cb3f54e3c6be 100644
--- a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsHelperRestoreTest.java
+++ b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsHelperRestoreTest.java
@@ -20,6 +20,8 @@ import static com.google.common.truth.Truth.assertThat;
import static junit.framework.Assert.assertEquals;
+import android.app.backup.BackupAnnotations.OperationType;
+import android.app.backup.BackupRestoreEventLogger;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
@@ -60,11 +62,14 @@ public class SettingsHelperRestoreTest {
private static final float FLOAT_TOLERANCE = 0.01f;
private ContentResolver mContentResolver;
private SettingsHelper mSettingsHelper;
+ private BackupRestoreEventLogger mBackupRestoreEventLogger;
@Before
public void setUp() {
mContentResolver = mInterceptingContext.getContentResolver();
mSettingsHelper = new SettingsHelper(mInterceptingContext);
+ mBackupRestoreEventLogger = new BackupRestoreEventLogger(OperationType.RESTORE);
+ mSettingsHelper.setBackupRestoreEventLogger(mBackupRestoreEventLogger);
}
@After
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index 19806e7cdf64..49cdec11e104 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -60,6 +60,7 @@ filegroup {
"src-release/**/*.kt",
"src-release/**/*.java",
],
+ path: "src-release",
visibility: ["//visibility:private"],
}
@@ -69,6 +70,7 @@ filegroup {
"src-debug/**/*.kt",
"src-debug/**/*.java",
],
+ path: "src-debug",
visibility: ["//visibility:private"],
}
@@ -315,11 +317,11 @@ filegroup {
"tests/src/**/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt",
"tests/src/**/systemui/statusbar/policy/WalletControllerImplTest.kt",
"tests/src/**/keyguard/ClockEventControllerTest.kt",
- "tests/src/**/systemui/bluetooth/qsdialog/BluetoothStateInteractorTest.kt",
"tests/src/**/systemui/bluetooth/qsdialog/BluetoothDetailsContentManagerTest.kt",
+ "tests/src/**/systemui/bluetooth/qsdialog/BluetoothDetailsContentViewModelTest.kt",
+ "tests/src/**/systemui/bluetooth/qsdialog/BluetoothStateInteractorTest.kt",
"tests/src/**/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegateTest.kt",
"tests/src/**/systemui/bluetooth/qsdialog/BluetoothTileDialogRepositoryTest.kt",
- "tests/src/**/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModelTest.kt",
"tests/src/**/systemui/bluetooth/qsdialog/DeviceItemFactoryTest.kt",
"tests/src/**/systemui/bluetooth/qsdialog/DeviceItemInteractorTest.kt",
"tests/src/**/systemui/broadcast/UserBroadcastDispatcherTest.kt",
@@ -430,12 +432,21 @@ android_library {
],
}
-android_library {
- name: "SystemUI-core",
+filegroup {
+ name: "SystemUI-core-srcs",
srcs: [
"src/**/*.kt",
"src/**/*.java",
"src/**/I*.aidl",
+ ],
+ path: "src",
+ visibility: ["//visibility:private"],
+}
+
+android_library {
+ name: "SystemUI-core",
+ srcs: [
+ ":SystemUI-core-srcs",
":ReleaseJavaFiles",
"compose/features/src/**/*.kt",
"compose/facade/enabled/src/**/*.kt",
@@ -731,9 +742,7 @@ android_library {
srcs: [
"tests/src/**/*.kt",
"tests/src/**/*.java",
- "src/**/*.kt",
- "src/**/*.java",
- "src/**/I*.aidl",
+ ":SystemUI-core-srcs",
":ReleaseJavaFiles",
":SystemUI-tests-multivalent",
":SystemUI-tests-utils",
@@ -841,7 +850,7 @@ java_library {
"androidx.test.uiautomator_uiautomator",
"androidx.core_core-animation-testing",
"androidx.test.ext.junit",
- "inline-mockito-robolectric-prebuilt",
+ "inline-mockito5-robolectric-prebuilt",
"mockito-kotlin-nodeps",
"platform-parametric-runner-lib",
"SystemUICustomizationTestUtils",
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 910f71276376..0ccb20ce3e3f 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"
@@ -957,16 +964,6 @@ flag {
}
flag {
- name: "dedicated_notif_inflation_thread"
- namespace: "systemui"
- description: "Create a separate background thread for inflating notifications"
- bug: "308967184"
- metadata {
- purpose: PURPOSE_BUGFIX
- }
-}
-
-flag {
name: "bind_keyguard_media_visibility"
namespace: "systemui"
description: "Binds Keyguard Media Controller Visibility to MediaContainerView"
@@ -1258,13 +1255,6 @@ flag {
}
flag {
- name: "glanceable_hub_back_action"
- namespace: "systemui"
- description: "Support back action from glanceable hub"
- bug: "382771533"
-}
-
-flag {
name: "dream_overlay_updated_font"
namespace: "systemui"
description: "Flag to enable updated font settings for dream overlay"
@@ -1997,6 +1987,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."
@@ -2016,3 +2016,13 @@ 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
+ }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/Icon.kt b/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/Icon.kt
index 8b0c00535262..09db2d653326 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/Icon.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/Icon.kt
@@ -19,10 +19,12 @@ package com.android.systemui.common.ui.compose
import androidx.compose.material3.Icon
import androidx.compose.material3.LocalContentColor
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.res.painterResource
-import com.android.compose.ui.graphics.painter.rememberDrawablePainter
+import androidx.core.graphics.drawable.toBitmap
import com.android.systemui.common.shared.model.Icon
/**
@@ -35,7 +37,12 @@ fun Icon(icon: Icon, modifier: Modifier = Modifier, tint: Color = LocalContentCo
val contentDescription = icon.contentDescription?.load()
when (icon) {
is Icon.Loaded -> {
- Icon(rememberDrawablePainter(icon.drawable), contentDescription, modifier, tint)
+ Icon(
+ remember(icon.drawable) { icon.drawable.toBitmap().asImageBitmap() },
+ contentDescription,
+ modifier,
+ tint,
+ )
}
is Icon.Resource -> Icon(painterResource(icon.res), contentDescription, modifier, tint)
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenContent.kt
index ba85f9570d09..5806458da9b5 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenContent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenContent.kt
@@ -20,11 +20,8 @@ import android.view.View
import androidx.compose.foundation.layout.Box
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalView
-import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.compose.animation.scene.ContentScope
import com.android.internal.jank.Cuj
import com.android.internal.jank.Cuj.CujType
@@ -70,8 +67,7 @@ class LockscreenContent(
rememberViewModel("LockscreenContent-scrimViewModel") {
notificationScrimViewModelFactory.create()
}
- val isContentVisible: Boolean by viewModel.isContentVisible.collectAsStateWithLifecycle()
- if (!isContentVisible) {
+ if (!viewModel.isContentVisible) {
// If the content isn't supposed to be visible, show a large empty box as it's needed
// for scene transition animations (can't just skip rendering everything or shared
// elements won't have correct final/initial bounds from animating in and out of the
@@ -80,15 +76,13 @@ class LockscreenContent(
return
}
- val coroutineScope = rememberCoroutineScope()
- val blueprintId by viewModel.blueprintId(coroutineScope).collectAsStateWithLifecycle()
DisposableEffect(view) {
clockInteractor.clockEventController.registerListeners(view)
onDispose { clockInteractor.clockEventController.unregisterListeners() }
}
- val blueprint = blueprintByBlueprintId[blueprintId] ?: return
+ val blueprint = blueprintByBlueprintId[viewModel.blueprintId] ?: return
with(blueprint) {
Content(viewModel, modifier.sysuiResTag("keyguard_root_view"))
NotificationLockscreenScrim(notificationLockscreenScrimViewModel)
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt
index 6e25c8a5bc42..590a74ee2a0d 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt
@@ -23,7 +23,6 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.graphicsLayer
@@ -31,7 +30,7 @@ import androidx.compose.ui.layout.Layout
import androidx.compose.ui.res.dimensionResource
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.IntRect
-import androidx.lifecycle.compose.collectAsStateWithLifecycle
+import androidx.compose.ui.unit.dp
import com.android.compose.animation.scene.ContentScope
import com.android.compose.modifiers.padding
import com.android.systemui.compose.modifiers.sysuiResTag
@@ -44,6 +43,8 @@ import com.android.systemui.keyguard.ui.composable.section.SettingsMenuSection
import com.android.systemui.keyguard.ui.composable.section.StatusBarSection
import com.android.systemui.keyguard.ui.composable.section.TopAreaSection
import com.android.systemui.keyguard.ui.viewmodel.LockscreenContentViewModel
+import com.android.systemui.keyguard.ui.viewmodel.LockscreenContentViewModel.NotificationsPlacement.BelowClock
+import com.android.systemui.keyguard.ui.viewmodel.LockscreenContentViewModel.NotificationsPlacement.BesideClock
import com.android.systemui.res.R
import java.util.Optional
import javax.inject.Inject
@@ -70,11 +71,8 @@ constructor(
@Composable
override fun ContentScope.Content(viewModel: LockscreenContentViewModel, modifier: Modifier) {
val isUdfpsVisible = viewModel.isUdfpsVisible
- val isShadeLayoutWide by viewModel.isShadeLayoutWide.collectAsStateWithLifecycle()
- val unfoldTranslations by viewModel.unfoldTranslations.collectAsStateWithLifecycle()
- val areNotificationsVisible by
- viewModel.areNotificationsVisible().collectAsStateWithLifecycle(initialValue = false)
- val isBypassEnabled by viewModel.isBypassEnabled.collectAsStateWithLifecycle()
+ val isBypassEnabled = viewModel.isBypassEnabled
+ val notificationsPlacement = viewModel.notificationsPlacement
if (isBypassEnabled) {
with(notificationSection) { HeadsUpNotifications() }
@@ -91,7 +89,9 @@ constructor(
modifier =
Modifier.fillMaxWidth()
.padding(
- horizontal = { unfoldTranslations.start.roundToInt() }
+ horizontal = {
+ viewModel.unfoldTranslations.start.roundToInt()
+ }
)
)
}
@@ -100,29 +100,29 @@ constructor(
with(topAreaSection) {
DefaultClockLayout(
smartSpacePaddingTop = viewModel::getSmartSpacePaddingTop,
- isShadeLayoutWide = isShadeLayoutWide,
modifier =
Modifier.fillMaxWidth().graphicsLayer {
- translationX = unfoldTranslations.start
+ translationX = viewModel.unfoldTranslations.start
},
)
}
- if (isShadeLayoutWide && !isBypassEnabled) {
+ if (notificationsPlacement is BesideClock && !isBypassEnabled) {
with(notificationSection) {
Box(modifier = Modifier.fillMaxHeight()) {
AodPromotedNotificationArea(
modifier =
Modifier.fillMaxWidth(0.5f)
- .align(alignment = Alignment.TopEnd)
+ .align(notificationsPlacement.alignment)
)
Notifications(
- areNotificationsVisible = areNotificationsVisible,
- isShadeLayoutWide = true,
+ areNotificationsVisible =
+ viewModel.areNotificationsVisible,
burnInParams = null,
modifier =
Modifier.fillMaxWidth(0.5f)
.fillMaxHeight()
- .align(alignment = Alignment.TopEnd),
+ .align(notificationsPlacement.alignment)
+ .padding(top = 12.dp),
)
}
}
@@ -137,7 +137,7 @@ constructor(
dimensionResource(R.dimen.below_clock_padding_start_icons)
with(notificationSection) {
- if (!isShadeLayoutWide && !isBypassEnabled) {
+ if (notificationsPlacement is BelowClock && !isBypassEnabled) {
Box(modifier = Modifier.weight(weight = 1f)) {
Column(Modifier.align(alignment = Alignment.TopStart)) {
AodPromotedNotificationArea(
@@ -149,14 +149,13 @@ constructor(
)
}
Notifications(
- areNotificationsVisible = areNotificationsVisible,
- isShadeLayoutWide = false,
+ areNotificationsVisible = viewModel.areNotificationsVisible,
burnInParams = null,
)
}
} else {
Column {
- if (!isShadeLayoutWide) {
+ if (viewModel.notificationsPlacement is BelowClock) {
AodPromotedNotificationArea(
modifier =
Modifier.padding(top = aodPromotedNotifTopPadding)
@@ -204,13 +203,17 @@ constructor(
isStart = true,
applyPadding = true,
modifier =
- Modifier.graphicsLayer { translationX = unfoldTranslations.start },
+ Modifier.graphicsLayer {
+ translationX = viewModel.unfoldTranslations.start
+ },
)
Shortcut(
isStart = false,
applyPadding = true,
modifier =
- Modifier.graphicsLayer { translationX = unfoldTranslations.end },
+ Modifier.graphicsLayer {
+ translationX = viewModel.unfoldTranslations.end
+ },
)
}
with(settingsMenuSection) { SettingsMenu(onSettingsMenuPlaced) }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/MediaCarouselSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/MediaCarouselSection.kt
index d8b3f742b447..0876631cf5c1 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/MediaCarouselSection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/MediaCarouselSection.kt
@@ -41,16 +41,13 @@ constructor(
) {
@Composable
- fun ContentScope.KeyguardMediaCarousel(
- isShadeLayoutWide: Boolean,
- modifier: Modifier = Modifier,
- ) {
+ fun ContentScope.KeyguardMediaCarousel(modifier: Modifier = Modifier) {
val viewModel =
rememberViewModel(traceName = "KeyguardMediaCarousel") {
keyguardMediaViewModelFactory.create()
}
val horizontalPadding =
- if (isShadeLayoutWide) {
+ if (viewModel.isShadeLayoutWide) {
dimensionResource(id = R.dimen.notification_side_paddings)
} else {
dimensionResource(id = R.dimen.notification_side_paddings) +
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt
index f51049a10569..d903c3d16fdb 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt
@@ -18,12 +18,13 @@ package com.android.systemui.keyguard.ui.composable.section
import android.view.ViewGroup
import androidx.compose.animation.AnimatedVisibility
+import androidx.compose.animation.EnterTransition
+import androidx.compose.animation.ExitTransition
import androidx.compose.animation.core.MutableTransitionState
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
-import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
@@ -31,11 +32,9 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.dimensionResource
-import androidx.compose.ui.unit.dp
import androidx.compose.ui.viewinterop.AndroidView
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.compose.animation.scene.ContentScope
-import com.android.compose.modifiers.thenIf
import com.android.systemui.common.ui.ConfigurationState
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.ui.composable.blueprint.rememberBurnIn
@@ -118,12 +117,19 @@ constructor(
val isVisible by
keyguardRootViewModel.isAodPromotedNotifVisible.collectAsStateWithLifecycle()
+ val transitionState = remember { MutableTransitionState(isVisible.value) }
+ LaunchedEffect(key1 = isVisible, key2 = transitionState.isIdle) {
+ transitionState.targetState = isVisible.value
+ if (isVisible.isAnimating && transitionState.isIdle) {
+ isVisible.stopAnimating()
+ }
+ }
val burnIn = rememberBurnIn(keyguardClockViewModel)
AnimatedVisibility(
- visible = isVisible,
- enter = fadeIn(),
- exit = fadeOut(),
+ visibleState = transitionState,
+ enter = if (isVisible.isAnimating) fadeIn() else EnterTransition.None,
+ exit = if (isVisible.isAnimating) fadeOut() else ExitTransition.None,
modifier = modifier.burnInAware(aodBurnInViewModel, burnIn.parameters),
) {
AODPromotedNotification(aodPromotedNotificationViewModelFactory)
@@ -186,7 +192,6 @@ constructor(
@Composable
fun ContentScope.Notifications(
areNotificationsVisible: Boolean,
- isShadeLayoutWide: Boolean,
burnInParams: BurnInParameters?,
modifier: Modifier = Modifier,
) {
@@ -198,16 +203,13 @@ constructor(
stackScrollView = stackScrollView.get(),
viewModel = rememberViewModel("Notifications") { viewModelFactory.create() },
modifier =
- modifier
- .fillMaxWidth()
- .thenIf(isShadeLayoutWide) { Modifier.padding(top = 12.dp) }
- .let {
- if (burnInParams == null) {
- it
- } else {
- it.burnInAware(viewModel = aodBurnInViewModel, params = burnInParams)
- }
- },
+ modifier.fillMaxWidth().let {
+ if (burnInParams == null) {
+ it
+ } else {
+ it.burnInAware(viewModel = aodBurnInViewModel, params = burnInParams)
+ }
+ },
)
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/TopAreaSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/TopAreaSection.kt
index 6293fc26f96a..013424006668 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/TopAreaSection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/TopAreaSection.kt
@@ -60,7 +60,6 @@ constructor(
@Composable
fun ContentScope.DefaultClockLayout(
smartSpacePaddingTop: (Resources) -> Int,
- isShadeLayoutWide: Boolean,
modifier: Modifier = Modifier,
) {
val currentClockLayout by clockViewModel.currentClockLayout.collectAsStateWithLifecycle()
@@ -128,7 +127,7 @@ constructor(
)
}
}
- with(mediaCarouselSection) { KeyguardMediaCarousel(isShadeLayoutWide) }
+ with(mediaCarouselSection) { KeyguardMediaCarousel() }
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt
index 4d238ac3798d..8c5fad3906ed 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt
@@ -16,7 +16,6 @@
package com.android.systemui.back.domain.interactor
-import android.platform.test.annotations.EnableFlags
import android.platform.test.annotations.RequiresFlagsDisabled
import android.platform.test.annotations.RequiresFlagsEnabled
import android.platform.test.flag.junit.DeviceFlagsValueProvider
@@ -32,7 +31,6 @@ import androidx.test.filters.SmallTest
import com.android.internal.statusbar.IStatusBarService
import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
-import com.android.systemui.communal.domain.interactor.CommunalBackActionInteractor
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.testScope
@@ -93,7 +91,6 @@ class BackActionInteractorTest : SysuiTestCase() {
@Mock private lateinit var onBackInvokedDispatcher: WindowOnBackInvokedDispatcher
@Mock private lateinit var iStatusBarService: IStatusBarService
@Mock private lateinit var headsUpManager: HeadsUpManager
- @Mock private lateinit var communalBackActionInteractor: CommunalBackActionInteractor
private val keyguardRepository = FakeKeyguardRepository()
private val windowRootViewVisibilityInteractor: WindowRootViewVisibilityInteractor by lazy {
@@ -118,7 +115,6 @@ class BackActionInteractorTest : SysuiTestCase() {
windowRootViewVisibilityInteractor,
shadeBackActionInteractor,
qsController,
- communalBackActionInteractor,
)
}
@@ -297,19 +293,6 @@ class BackActionInteractorTest : SysuiTestCase() {
verify(shadeBackActionInteractor).onBackProgressed(0.4f)
}
- @Test
- @EnableFlags(Flags.FLAG_GLANCEABLE_HUB_BACK_ACTION)
- fun onBackAction_communalCanBeDismissed_communalBackActionInteractorCalled() {
- backActionInteractor.start()
- windowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(true)
- powerInteractor.setAwakeForTest()
- val callback = getBackInvokedCallback()
- whenever(communalBackActionInteractor.canBeDismissed()).thenReturn(true)
- callback.onBackInvoked()
-
- verify(communalBackActionInteractor).onBackPressed()
- }
-
private fun getBackInvokedCallback(): OnBackInvokedCallback {
testScope.runCurrent()
val captor = argumentCaptor<OnBackInvokedCallback>()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/common/ui/view/SeekBarWithIconButtonsViewTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/common/ui/view/SeekBarWithIconButtonsViewTest.java
index 01baadda7c87..c40c1a3b0e93 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/common/ui/view/SeekBarWithIconButtonsViewTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/common/ui/view/SeekBarWithIconButtonsViewTest.java
@@ -130,12 +130,17 @@ public class SeekBarWithIconButtonsViewTest extends SysuiTestCase {
@Test
public void setProgress_onProgressChangedAndOnUserInteractionFinalized() {
reset(mOnSeekBarChangeListener);
- mIconDiscreteSliderLinearLayout.setProgress(1);
+
+ // Trigger the progress changed listener with fromUser but without clicking.
+ // This is similar to what would happen if an accessibility service changed the
+ // progress.
+ mIconDiscreteSliderLinearLayout.getSeekBarChangeListener().onProgressChanged(
+ mIconDiscreteSliderLinearLayout.getSeekbar(), 1, /*fromUser=*/ true);
// If users are changing seekbar progress without touching the seekbar or clicking the
// buttons, trigger onUserInteractionFinalized.
verify(mOnSeekBarChangeListener).onProgressChanged(
- eq(mSeekbar), /* progress= */ eq(1), /* fromUser= */ eq(false));
+ eq(mSeekbar), /* progress= */ eq(1), /* fromUser= */ eq(true));
verify(mOnSeekBarChangeListener, never()).onStartTrackingTouch(/* seekBar= */ any());
verify(mOnSeekBarChangeListener, never()).onStopTrackingTouch(/* seekBar= */ any());
verify(mOnSeekBarChangeListener).onUserInteractionFinalized(
@@ -144,6 +149,22 @@ public class SeekBarWithIconButtonsViewTest extends SysuiTestCase {
}
@Test
+ public void setProgress_onProgressChangedWithoutUserInteractionFinalized() {
+ reset(mOnSeekBarChangeListener);
+ mIconDiscreteSliderLinearLayout.setProgress(1);
+
+ // If seekbar progress changes due to a non-user event, without touching the seekbar or
+ // clicking the buttons, do not trigger onUserInteractionFinalized.
+ verify(mOnSeekBarChangeListener).onProgressChanged(
+ eq(mSeekbar), /* progress= */ eq(1), /* fromUser= */ eq(false));
+ verify(mOnSeekBarChangeListener, never()).onStartTrackingTouch(/* seekBar= */ any());
+ verify(mOnSeekBarChangeListener, never()).onStopTrackingTouch(/* seekBar= */ any());
+ verify(mOnSeekBarChangeListener, never()).onUserInteractionFinalized(
+ /* seekBar= */ any(),
+ eq(OnSeekBarWithIconButtonsChangeListener.ControlUnitType.SLIDER));
+ }
+
+ @Test
public void setProgressToSeekBarByTouch_onUserInteractionFinalizedAfterTouchEnds() {
reset(mOnSeekBarChangeListener);
final SeekBarWithIconButtonsView.SeekBarChangeListener seekBarChangeListener =
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalBackActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalBackActionInteractorTest.kt
deleted file mode 100644
index 70f38f7bc94e..000000000000
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalBackActionInteractorTest.kt
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.communal.domain.interactor
-
-import android.platform.test.annotations.EnableFlags
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import com.android.systemui.Flags.FLAG_COMMUNAL_HUB
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.communal.data.repository.communalSceneRepository
-import com.android.systemui.communal.shared.model.CommunalScenes
-import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.kosmos.Kosmos.Fixture
-import com.android.systemui.kosmos.runCurrent
-import com.android.systemui.kosmos.runTest
-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 CommunalBackActionInteractorTest : SysuiTestCase() {
- private val kosmos = testKosmos()
-
- private var Kosmos.underTest by Fixture { communalBackActionInteractor }
-
- @Test
- @EnableFlags(FLAG_COMMUNAL_HUB)
- fun communalShowing_canBeDismissed() =
- kosmos.runTest {
- setCommunalAvailable(true)
- assertThat(underTest.canBeDismissed()).isEqualTo(false)
- communalInteractor.changeScene(CommunalScenes.Communal, "test")
- runCurrent()
- assertThat(underTest.canBeDismissed()).isEqualTo(true)
- }
-
- @Test
- @EnableFlags(FLAG_COMMUNAL_HUB)
- fun onBackPressed_invokesSceneChange() =
- kosmos.runTest {
- underTest.onBackPressed()
- runCurrent()
- assertThat(communalSceneRepository.currentScene.value).isEqualTo(CommunalScenes.Blank)
- }
-}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorTest.kt
index feee9e3d62d2..6eace1b50ea7 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorTest.kt
@@ -128,6 +128,19 @@ class CommunalTutorialInteractorTest : SysuiTestCase() {
}
@Test
+ fun tutorialState_startedAndCommunalSceneShowing_stateWillNotUpdate() =
+ testScope.runTest {
+ val tutorialSettingState by
+ collectLastValue(communalTutorialRepository.tutorialSettingState)
+
+ communalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_STARTED)
+
+ goToCommunal()
+
+ assertThat(tutorialSettingState).isEqualTo(HUB_MODE_TUTORIAL_STARTED)
+ }
+
+ @Test
fun tutorialState_completedAndCommunalSceneShowing_stateWillNotUpdate() =
testScope.runTest {
val tutorialSettingState by
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 da25bcac6c95..1a3606e413cc 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,7 +35,6 @@ 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
@@ -45,7 +44,6 @@ 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() {
@@ -67,7 +65,7 @@ class CommunalTransitionViewModelTest(flags: FlagsParameterization) : SysuiTestC
private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
private val communalSceneRepository = kosmos.fakeCommunalSceneRepository
- private val sceneInteractor by lazy { kosmos.sceneInteractor }
+ private val sceneInteractor = 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 e36d2455d316..329627af8ec2 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,7 +61,6 @@ 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
@@ -74,7 +73,6 @@ 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() {
@@ -82,26 +80,21 @@ 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 by lazy { kosmos.keyguardUpdateMonitor }
+ private val keyguardUpdateMonitor = kosmos.keyguardUpdateMonitor
private val faceWakeUpTriggersConfig = kosmos.fakeFaceWakeUpTriggersConfig
private val trustManager = kosmos.trustManager
-
- 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
- }
+ private val deviceEntryFaceAuthStatusInteractor = kosmos.deviceEntryFaceAuthStatusInteractor
@Before
fun setup() {
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 2feabf8221ad..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
@@ -34,23 +34,32 @@ package com.android.systemui.keyguard.domain.interactor
import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
-import androidx.test.ext.junit.runners.AndroidJUnit4
+import android.platform.test.flag.junit.FlagsParameterization
import androidx.test.filters.SmallTest
import com.android.systemui.Flags
+import com.android.systemui.Flags.FLAG_GLANCEABLE_HUB_V2
import com.android.systemui.SysuiTestCase
import com.android.systemui.bouncer.data.repository.fakeKeyguardBouncerRepository
+import com.android.systemui.communal.data.repository.fakeCommunalSceneRepository
import com.android.systemui.communal.domain.interactor.communalInteractor
+import com.android.systemui.communal.domain.interactor.communalSceneInteractor
+import com.android.systemui.communal.domain.interactor.setCommunalV2ConfigEnabled
+import com.android.systemui.communal.shared.model.CommunalScenes
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepositorySpy
import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.util.KeyguardTransitionRepositorySpySubject.Companion.assertThat
+import com.android.systemui.kosmos.collectLastValue
+import com.android.systemui.kosmos.runCurrent
+import com.android.systemui.kosmos.runTest
import com.android.systemui.kosmos.testScope
import com.android.systemui.power.data.repository.fakePowerRepository
import com.android.systemui.power.shared.model.WakeSleepReason
import com.android.systemui.power.shared.model.WakefulnessState
import com.android.systemui.testKosmos
+import com.google.common.truth.Truth
import kotlinx.coroutines.test.advanceTimeBy
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
@@ -58,10 +67,25 @@ import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito.reset
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
@SmallTest
-@RunWith(AndroidJUnit4::class)
-class FromAlternateBouncerTransitionInteractorTest : SysuiTestCase() {
+@RunWith(ParameterizedAndroidJunit4::class)
+class FromAlternateBouncerTransitionInteractorTest(flags: FlagsParameterization) : SysuiTestCase() {
+
+ companion object {
+ @JvmStatic
+ @Parameters(name = "{0}")
+ fun getParams(): List<FlagsParameterization> {
+ return FlagsParameterization.allCombinationsOf(FLAG_GLANCEABLE_HUB_V2)
+ }
+ }
+
+ init {
+ mSetFlagsRule.setFlagsParameterization(flags)
+ }
+
private val kosmos =
testKosmos().apply {
this.keyguardTransitionRepository = fakeKeyguardTransitionRepositorySpy
@@ -74,6 +98,7 @@ class FromAlternateBouncerTransitionInteractorTest : SysuiTestCase() {
fun setup() {
transitionRepository = kosmos.fakeKeyguardTransitionRepositorySpy
underTest = kosmos.fromAlternateBouncerTransitionInteractor
+ kosmos.setCommunalV2ConfigEnabled(true)
underTest.start()
}
@@ -171,6 +196,72 @@ class FromAlternateBouncerTransitionInteractorTest : SysuiTestCase() {
}
@Test
+ @EnableFlags(FLAG_GLANCEABLE_HUB_V2)
+ fun transitionToOccluded_glanceableHubShowing() =
+ kosmos.runTest {
+ val currentScene by collectLastValue(communalSceneInteractor.currentScene)
+
+ fakePowerRepository.updateWakefulness(
+ WakefulnessState.AWAKE,
+ WakeSleepReason.POWER_BUTTON,
+ WakeSleepReason.POWER_BUTTON,
+ false,
+ )
+ fakeKeyguardRepository.setKeyguardOccluded(false)
+ fakeKeyguardBouncerRepository.setAlternateVisible(true)
+ fakeCommunalSceneRepository.changeScene(CommunalScenes.Communal)
+ runCurrent()
+
+ Truth.assertThat(currentScene).isEqualTo(CommunalScenes.Communal)
+
+ transitionRepository.sendTransitionSteps(
+ from = KeyguardState.GLANCEABLE_HUB,
+ to = KeyguardState.ALTERNATE_BOUNCER,
+ testScope,
+ )
+ reset(transitionRepository)
+
+ fakeKeyguardRepository.setKeyguardOccluded(true)
+ fakeKeyguardBouncerRepository.setAlternateVisible(false)
+ testScope.advanceTimeBy(200) // advance past delay
+
+ Truth.assertThat(currentScene).isEqualTo(CommunalScenes.Blank)
+ }
+
+ @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/FromPrimaryBouncerTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorTest.kt
index 63bb1001c873..d9e76222e51f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorTest.kt
@@ -17,13 +17,17 @@
package com.android.systemui.keyguard.domain.interactor
import android.platform.test.annotations.EnableFlags
-import androidx.test.ext.junit.runners.AndroidJUnit4
+import android.platform.test.flag.junit.FlagsParameterization
import androidx.test.filters.SmallTest
import com.android.compose.animation.scene.ObservableTransitionState
+import com.android.systemui.Flags.FLAG_GLANCEABLE_HUB_V2
import com.android.systemui.Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR
import com.android.systemui.SysuiTestCase
import com.android.systemui.bouncer.data.repository.fakeKeyguardBouncerRepository
import com.android.systemui.communal.data.repository.fakeCommunalSceneRepository
+import com.android.systemui.communal.domain.interactor.communalSceneInteractor
+import com.android.systemui.communal.domain.interactor.setCommunalV2Available
+import com.android.systemui.communal.domain.interactor.setCommunalV2ConfigEnabled
import com.android.systemui.communal.shared.model.CommunalScenes
import com.android.systemui.coroutines.collectValues
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepositorySpy
@@ -33,30 +37,55 @@ import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
import com.android.systemui.keyguard.util.KeyguardTransitionRepositorySpySubject.Companion.assertThat
+import com.android.systemui.kosmos.collectLastValue
+import com.android.systemui.kosmos.runCurrent
+import com.android.systemui.kosmos.runTest
import com.android.systemui.kosmos.testScope
+import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest
+import com.android.systemui.power.domain.interactor.powerInteractor
import com.android.systemui.testKosmos
-import com.android.systemui.user.domain.interactor.selectedUserInteractor
+import com.google.common.truth.Truth
import junit.framework.Assert.assertEquals
import kotlinx.coroutines.flow.flowOf
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.Mockito.reset
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
@SmallTest
-@RunWith(AndroidJUnit4::class)
-class FromPrimaryBouncerTransitionInteractorTest : SysuiTestCase() {
+@RunWith(ParameterizedAndroidJunit4::class)
+class FromPrimaryBouncerTransitionInteractorTest(flags: FlagsParameterization) : SysuiTestCase() {
+
+ companion object {
+ @JvmStatic
+ @Parameters(name = "{0}")
+ fun getParams(): List<FlagsParameterization> {
+ return FlagsParameterization.allCombinationsOf(FLAG_GLANCEABLE_HUB_V2)
+ }
+ }
+
+ init {
+ mSetFlagsRule.setFlagsParameterization(flags)
+ }
+
private val kosmos =
testKosmos().apply {
this.keyguardTransitionRepository = fakeKeyguardTransitionRepositorySpy
}
val underTest = kosmos.fromPrimaryBouncerTransitionInteractor
val testScope = kosmos.testScope
- val selectedUserInteractor = kosmos.selectedUserInteractor
val transitionRepository = kosmos.fakeKeyguardTransitionRepositorySpy
val bouncerRepository = kosmos.fakeKeyguardBouncerRepository
+ @Before
+ fun setUp() {
+ kosmos.setCommunalV2ConfigEnabled(true)
+ }
+
@Test
fun testSurfaceBehindVisibility() =
testScope.runTest {
@@ -213,4 +242,33 @@ class FromPrimaryBouncerTransitionInteractorTest : SysuiTestCase() {
to = KeyguardState.OCCLUDED,
)
}
+
+ @Test
+ @EnableFlags(FLAG_GLANCEABLE_HUB_V2)
+ fun testTransitionToDozing_bouncerShowingOnTopOfGlanceableHub() =
+ kosmos.runTest {
+ underTest.start()
+ setCommunalV2Available(true)
+
+ val currentScene by collectLastValue(communalSceneInteractor.currentScene)
+ // Communal is showing.
+ fakeCommunalSceneRepository.changeScene(CommunalScenes.Communal)
+
+ Truth.assertThat(currentScene).isEqualTo(CommunalScenes.Communal)
+
+ // Bouncer is shown on top of the Glanceable Hub.
+ bouncerRepository.setPrimaryShow(true)
+ transitionRepository.sendTransitionSteps(
+ from = KeyguardState.GLANCEABLE_HUB,
+ to = KeyguardState.PRIMARY_BOUNCER,
+ testScope,
+ )
+
+ reset(transitionRepository)
+
+ powerInteractor.setAsleepForTest()
+ runCurrent()
+
+ Truth.assertThat(currentScene).isEqualTo(CommunalScenes.Blank)
+ }
}
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 fee2dfc88020..29e95cd911f8 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
@@ -46,7 +46,6 @@ 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
@@ -54,15 +53,13 @@ import org.junit.Assert.assertThrows
import org.junit.Test
import org.junit.runner.RunWith
-@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
class KeyguardTransitionInteractorTest : SysuiTestCase() {
val kosmos = testKosmos()
- val testScope = kosmos.testScope
+ val underTest = kosmos.keyguardTransitionInteractor
val repository = kosmos.fakeKeyguardTransitionRepository
-
- val underTest by lazy { kosmos.keyguardTransitionInteractor }
+ val testScope = kosmos.testScope
@Test
fun transitionCollectorsReceivesOnlyAppropriateEvents() =
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
index a090faba5bf7..8df70ef0fd2e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
@@ -17,6 +17,7 @@
package com.android.systemui.keyguard.domain.interactor
import android.app.StatusBarManager.CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP
+import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
import android.platform.test.flag.junit.FlagsParameterization
import androidx.test.filters.SmallTest
@@ -30,7 +31,6 @@ import com.android.systemui.Flags.glanceableHubV2
import com.android.systemui.SysuiTestCase
import com.android.systemui.bouncer.data.repository.fakeKeyguardBouncerRepository
import com.android.systemui.communal.domain.interactor.CommunalSceneTransitionInteractor
-import com.android.systemui.communal.domain.interactor.communalInteractor
import com.android.systemui.communal.domain.interactor.communalSceneInteractor
import com.android.systemui.communal.domain.interactor.communalSceneTransitionInteractor
import com.android.systemui.communal.domain.interactor.setCommunalAvailable
@@ -126,14 +126,14 @@ class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTest
}
private val powerInteractor by lazy { kosmos.powerInteractor }
- private val communalInteractor by lazy { kosmos.communalInteractor }
private val communalSceneInteractor by lazy { kosmos.communalSceneInteractor }
companion object {
@JvmStatic
@Parameters(name = "{0}")
fun getParams(): List<FlagsParameterization> {
- return FlagsParameterization.allCombinationsOf().andSceneContainer()
+ return FlagsParameterization.allCombinationsOf(FLAG_GLANCEABLE_HUB_V2)
+ .andSceneContainer()
}
}
@@ -902,6 +902,7 @@ class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTest
@Test
@DisableSceneContainer
+ @DisableFlags(FLAG_GLANCEABLE_HUB_V2)
fun primaryBouncerToGlanceableHubWhileDreaming() =
testScope.runTest {
// Setup - Move past initial delay with [KeyguardInteractor#isAbleToDream]
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt
index 3a016ff7152a..63770803ff48 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt
@@ -40,7 +40,6 @@ import com.android.systemui.keyguard.ui.view.layout.sections.DefaultStatusBarSec
import com.android.systemui.keyguard.ui.view.layout.sections.DefaultUdfpsAccessibilityOverlaySection
import com.android.systemui.keyguard.ui.view.layout.sections.KeyguardSliceViewSection
import com.android.systemui.keyguard.ui.view.layout.sections.SmartspaceSection
-import com.android.systemui.keyguard.ui.view.layout.sections.SplitShadeGuidelines
import com.android.systemui.util.mockito.whenever
import java.util.Optional
import org.junit.Before
@@ -66,7 +65,6 @@ class DefaultKeyguardBlueprintTest : SysuiTestCase() {
@Mock private lateinit var defaultSettingsPopupMenuSection: DefaultSettingsPopupMenuSection
@Mock private lateinit var defaultStatusBarViewSection: DefaultStatusBarSection
@Mock private lateinit var defaultNSSLSection: DefaultNotificationStackScrollLayoutSection
- @Mock private lateinit var splitShadeGuidelines: SplitShadeGuidelines
@Mock private lateinit var aodPromotedNotificationSection: AodPromotedNotificationSection
@Mock private lateinit var aodNotificationIconsSection: AodNotificationIconsSection
@Mock private lateinit var aodBurnInSection: AodBurnInSection
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
new file mode 100644
index 000000000000..052dfd52887f
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DozingToDreamingTransitionViewModelTest.kt
@@ -0,0 +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.
+ */
+
+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/KeyguardMediaViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardMediaViewModelTest.kt
index 38829da69c28..583fd1e03002 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardMediaViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardMediaViewModelTest.kt
@@ -26,6 +26,7 @@ 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.shade.data.repository.shadeRepository
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import org.junit.Before
@@ -81,4 +82,20 @@ class KeyguardMediaViewModelTest : SysuiTestCase() {
assertThat(underTest.isMediaVisible).isFalse()
}
+
+ @Test
+ fun isShadeLayoutWide_withConfigTrue_true() =
+ kosmos.runTest {
+ shadeRepository.setShadeLayoutWide(true)
+
+ assertThat(underTest.isShadeLayoutWide).isTrue()
+ }
+
+ @Test
+ fun isShadeLayoutWide_withConfigFalse_false() =
+ kosmos.runTest {
+ shadeRepository.setShadeLayoutWide(false)
+
+ assertThat(underTest.isShadeLayoutWide).isFalse()
+ }
}
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 af025273458f..25c157208513 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
@@ -17,6 +17,7 @@
package com.android.systemui.keyguard.ui.viewmodel
import android.platform.test.flag.junit.FlagsParameterization
+import androidx.compose.ui.Alignment
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.biometrics.authController
@@ -26,10 +27,13 @@ import com.android.systemui.flags.andSceneContainer
import com.android.systemui.keyguard.data.repository.fakeKeyguardClockRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.keyguard.data.repository.keyguardOcclusionRepository
+import com.android.systemui.keyguard.domain.interactor.keyguardClockInteractor
import com.android.systemui.keyguard.shared.model.ClockSize
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.transition.fakeKeyguardTransitionAnimationCallback
import com.android.systemui.keyguard.shared.transition.keyguardTransitionAnimationCallbackDelegator
+import com.android.systemui.keyguard.ui.viewmodel.LockscreenContentViewModel.NotificationsPlacement.BelowClock
+import com.android.systemui.keyguard.ui.viewmodel.LockscreenContentViewModel.NotificationsPlacement.BesideClock
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.collectLastValue
import com.android.systemui.kosmos.runCurrent
@@ -41,6 +45,9 @@ import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.shared.model.Scenes
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.shared.model.ShadeMode
import com.android.systemui.testKosmos
import com.android.systemui.unfold.fakeUnfoldTransitionProgressProvider
import com.android.systemui.util.mockito.whenever
@@ -99,136 +106,140 @@ class LockscreenContentViewModelTest(flags: FlagsParameterization) : SysuiTestCa
}
@Test
- @DisableSceneContainer
- fun clockSize_withLargeClock_true() =
+ fun notificationsPlacement_splitShade_topEnd() =
kosmos.runTest {
- val clockSize by collectLastValue(underTest.clockSize)
- fakeKeyguardClockRepository.setClockSize(ClockSize.LARGE)
- assertThat(clockSize).isEqualTo(ClockSize.LARGE)
+ setupState(shadeMode = ShadeMode.Split, clockSize = ClockSize.SMALL)
+
+ assertThat(underTest.notificationsPlacement)
+ .isEqualTo(BesideClock(alignment = Alignment.TopEnd))
}
@Test
- @DisableSceneContainer
- fun clockSize_withSmallClock_false() =
+ fun notificationsPlacement_singleShade_below() =
kosmos.runTest {
- val clockSize by collectLastValue(underTest.clockSize)
- fakeKeyguardClockRepository.setClockSize(ClockSize.SMALL)
- assertThat(clockSize).isEqualTo(ClockSize.SMALL)
+ setupState(shadeMode = ShadeMode.Single, clockSize = ClockSize.SMALL)
+
+ assertThat(underTest.notificationsPlacement).isEqualTo(BelowClock)
}
@Test
- fun areNotificationsVisible_splitShadeTrue_true() =
+ fun notificationsPlacement_dualShadeSmallClock_below() =
kosmos.runTest {
- val areNotificationsVisible by collectLastValue(underTest.areNotificationsVisible())
- shadeRepository.setShadeLayoutWide(true)
- fakeKeyguardClockRepository.setClockSize(ClockSize.LARGE)
+ setupState(
+ shadeMode = ShadeMode.Dual,
+ clockSize = ClockSize.SMALL,
+ shadeLayoutWide = true,
+ )
- assertThat(areNotificationsVisible).isTrue()
+ assertThat(underTest.notificationsPlacement).isEqualTo(BelowClock)
}
@Test
- fun areNotificationsVisible_dualShadeWideOnLockscreen_true() =
+ fun notificationsPlacement_dualShadeLargeClock_topStart() =
kosmos.runTest {
- val areNotificationsVisible by collectLastValue(underTest.areNotificationsVisible())
- kosmos.enableDualShade()
- shadeRepository.setShadeLayoutWide(true)
- fakeKeyguardClockRepository.setClockSize(ClockSize.LARGE)
+ setupState(
+ shadeMode = ShadeMode.Dual,
+ clockSize = ClockSize.LARGE,
+ shadeLayoutWide = true,
+ )
- assertThat(areNotificationsVisible).isTrue()
+ assertThat(underTest.notificationsPlacement)
+ .isEqualTo(BesideClock(alignment = Alignment.TopStart))
}
@Test
- @DisableSceneContainer
- fun areNotificationsVisible_withSmallClock_true() =
+ fun areNotificationsVisible_splitShadeTrue_true() =
kosmos.runTest {
- val areNotificationsVisible by collectLastValue(underTest.areNotificationsVisible())
- fakeKeyguardClockRepository.setClockSize(ClockSize.SMALL)
- assertThat(areNotificationsVisible).isTrue()
+ setupState(shadeMode = ShadeMode.Split, clockSize = ClockSize.LARGE)
+
+ assertThat(underTest.areNotificationsVisible).isTrue()
}
@Test
- @DisableSceneContainer
- fun areNotificationsVisible_withLargeClock_false() =
+ fun areNotificationsVisible_dualShadeWideOnLockscreen_true() =
kosmos.runTest {
- val areNotificationsVisible by collectLastValue(underTest.areNotificationsVisible())
- fakeKeyguardClockRepository.setClockSize(ClockSize.LARGE)
- assertThat(areNotificationsVisible).isFalse()
+ setupState(
+ shadeMode = ShadeMode.Dual,
+ clockSize = ClockSize.LARGE,
+ shadeLayoutWide = true,
+ )
+
+ assertThat(underTest.areNotificationsVisible).isTrue()
}
@Test
- fun isShadeLayoutWide_withConfigTrue_true() =
+ @DisableSceneContainer
+ fun areNotificationsVisible_withSmallClock_true() =
kosmos.runTest {
- val isShadeLayoutWide by collectLastValue(underTest.isShadeLayoutWide)
- shadeRepository.setShadeLayoutWide(true)
+ setupState(shadeMode = ShadeMode.Single, clockSize = ClockSize.SMALL)
- assertThat(isShadeLayoutWide).isTrue()
+ assertThat(underTest.areNotificationsVisible).isTrue()
}
@Test
- fun isShadeLayoutWide_withConfigFalse_false() =
+ @DisableSceneContainer
+ fun areNotificationsVisible_withLargeClock_false() =
kosmos.runTest {
- val isShadeLayoutWide by collectLastValue(underTest.isShadeLayoutWide)
- shadeRepository.setShadeLayoutWide(false)
+ setupState(shadeMode = ShadeMode.Single, clockSize = ClockSize.LARGE)
- assertThat(isShadeLayoutWide).isFalse()
+ assertThat(underTest.areNotificationsVisible).isFalse()
}
@Test
fun unfoldTranslations() =
kosmos.runTest {
val maxTranslation = prepareConfiguration()
- val translations by collectLastValue(underTest.unfoldTranslations)
val unfoldProvider = fakeUnfoldTransitionProgressProvider
unfoldProvider.onTransitionStarted()
- assertThat(translations?.start).isEqualTo(0f)
- assertThat(translations?.end).isEqualTo(-0f)
+ runCurrent()
+ assertThat(underTest.unfoldTranslations.start).isZero()
+ assertThat(underTest.unfoldTranslations.end).isZero()
repeat(10) { repetition ->
val transitionProgress = 0.1f * (repetition + 1)
unfoldProvider.onTransitionProgress(transitionProgress)
- assertThat(translations?.start).isEqualTo((1 - transitionProgress) * maxTranslation)
- assertThat(translations?.end).isEqualTo(-(1 - transitionProgress) * maxTranslation)
+ runCurrent()
+ assertThat(underTest.unfoldTranslations.start)
+ .isEqualTo((1 - transitionProgress) * maxTranslation)
+ assertThat(underTest.unfoldTranslations.end)
+ .isEqualTo(-(1 - transitionProgress) * maxTranslation)
}
unfoldProvider.onTransitionFinishing()
- assertThat(translations?.start).isEqualTo(0f)
- assertThat(translations?.end).isEqualTo(-0f)
+ runCurrent()
+ assertThat(underTest.unfoldTranslations.start).isZero()
+ assertThat(underTest.unfoldTranslations.end).isZero()
unfoldProvider.onTransitionFinished()
- assertThat(translations?.start).isEqualTo(0f)
- assertThat(translations?.end).isEqualTo(-0f)
+ runCurrent()
+ assertThat(underTest.unfoldTranslations.start).isZero()
+ assertThat(underTest.unfoldTranslations.end).isZero()
}
@Test
fun isContentVisible_whenNotOccluded_visible() =
kosmos.runTest {
- val isContentVisible by collectLastValue(underTest.isContentVisible)
-
keyguardOcclusionRepository.setShowWhenLockedActivityInfo(false, null)
runCurrent()
- assertThat(isContentVisible).isTrue()
+ assertThat(underTest.isContentVisible).isTrue()
}
@Test
fun isContentVisible_whenOccluded_notVisible() =
kosmos.runTest {
- val isContentVisible by collectLastValue(underTest.isContentVisible)
-
keyguardOcclusionRepository.setShowWhenLockedActivityInfo(true, null)
fakeKeyguardTransitionRepository.transitionTo(
KeyguardState.LOCKSCREEN,
KeyguardState.OCCLUDED,
)
runCurrent()
- assertThat(isContentVisible).isFalse()
+ assertThat(underTest.isContentVisible).isFalse()
}
@Test
fun isContentVisible_whenOccluded_notVisible_evenIfShadeShown() =
kosmos.runTest {
- val isContentVisible by collectLastValue(underTest.isContentVisible)
-
keyguardOcclusionRepository.setShowWhenLockedActivityInfo(true, null)
fakeKeyguardTransitionRepository.transitionTo(
KeyguardState.LOCKSCREEN,
@@ -238,7 +249,7 @@ class LockscreenContentViewModelTest(flags: FlagsParameterization) : SysuiTestCa
sceneInteractor.snapToScene(Scenes.Shade, "")
runCurrent()
- assertThat(isContentVisible).isFalse()
+ assertThat(underTest.isContentVisible).isFalse()
}
@Test
@@ -260,17 +271,16 @@ class LockscreenContentViewModelTest(flags: FlagsParameterization) : SysuiTestCa
@Test
fun isContentVisible_whenOccluded_notVisibleInOccluded_visibleInAod() =
kosmos.runTest {
- val isContentVisible by collectLastValue(underTest.isContentVisible)
keyguardOcclusionRepository.setShowWhenLockedActivityInfo(true, null)
fakeKeyguardTransitionRepository.transitionTo(
- KeyguardState.LOCKSCREEN,
- KeyguardState.OCCLUDED,
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.OCCLUDED,
)
runCurrent()
sceneInteractor.snapToScene(Scenes.Shade, "")
runCurrent()
- assertThat(isContentVisible).isFalse()
+ assertThat(underTest.isContentVisible).isFalse()
fakeKeyguardTransitionRepository.transitionTo(KeyguardState.OCCLUDED, KeyguardState.AOD)
runCurrent()
@@ -278,9 +288,30 @@ class LockscreenContentViewModelTest(flags: FlagsParameterization) : SysuiTestCa
sceneInteractor.snapToScene(Scenes.Lockscreen, "")
runCurrent()
- assertThat(isContentVisible).isTrue()
+ assertThat(underTest.isContentVisible).isTrue()
}
+ private fun Kosmos.setupState(
+ shadeMode: ShadeMode,
+ clockSize: ClockSize,
+ shadeLayoutWide: Boolean? = null,
+ ) {
+ val isShadeLayoutWide by collectLastValue(kosmos.shadeRepository.isShadeLayoutWide)
+ val collectedClockSize by collectLastValue(kosmos.keyguardClockInteractor.clockSize)
+ when (shadeMode) {
+ ShadeMode.Dual -> kosmos.enableDualShade(wideLayout = shadeLayoutWide)
+ ShadeMode.Single -> kosmos.enableSingleShade()
+ ShadeMode.Split -> kosmos.enableSplitShade()
+ }
+ fakeKeyguardClockRepository.setShouldForceSmallClock(clockSize == ClockSize.SMALL)
+ fakeKeyguardClockRepository.setClockSize(clockSize)
+ runCurrent()
+ if (shadeLayoutWide != null) {
+ assertThat(isShadeLayoutWide).isEqualTo(shadeLayoutWide)
+ }
+ assertThat(collectedClockSize).isEqualTo(clockSize)
+ }
+
private fun prepareConfiguration(): Int {
val configuration = context.resources.configuration
configuration.setLayoutDirection(Locale.US)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java
index 95d9c8f4e819..f5a71113235a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java
@@ -240,6 +240,8 @@ public class MediaOutputAdapterTest extends SysuiTestCase {
@DisableFlags(Flags.FLAG_ENABLE_OUTPUT_SWITCHER_DEVICE_GROUPING)
@Test
public void onBindViewHolder_bindConnectedRemoteDevice_verifyView() {
+ when(mMediaSwitchingController.getSelectedMediaDevice())
+ .thenReturn(ImmutableList.of(mMediaDevice1));
when(mMediaSwitchingController.getSelectableMediaDevice())
.thenReturn(ImmutableList.of(mMediaDevice2));
when(mMediaSwitchingController.isCurrentConnectedDeviceRemote()).thenReturn(true);
@@ -854,6 +856,8 @@ public class MediaOutputAdapterTest extends SysuiTestCase {
.thenReturn(ImmutableList.of(mMediaDevice2));
when(mMediaSwitchingController.getDeselectableMediaDevice())
.thenReturn(ImmutableList.of(mMediaDevice1));
+ when(mMediaSwitchingController.getSelectedMediaDevice())
+ .thenReturn(ImmutableList.of(mMediaDevice1));
when(mMediaSwitchingController.isCurrentConnectedDeviceRemote()).thenReturn(true);
mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter
.onCreateViewHolder(new LinearLayout(mContext), 0);
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 9e400a6c0a4c..c775bfd75f6e 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,7 +19,6 @@ 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
@@ -35,7 +34,6 @@ 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 9fe783b98046..2e7aeb433e04 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,7 +22,6 @@ 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
@@ -77,7 +76,6 @@ 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 d5e502e99de5..fdbf42c9afd8 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,7 +21,6 @@ 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
@@ -37,7 +36,6 @@ 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
@@ -45,7 +43,6 @@ 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() {
@@ -66,7 +63,6 @@ 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 4912c319bf2e..241cdbfbef83 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,7 +21,6 @@ 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
@@ -89,7 +88,6 @@ class QSColumnsViewModelTest : SysuiTestCase() {
}
@Test
- @EnableSceneContainer
fun mediaLocationNull_dualShade_alwaysDualShadeColumns() =
with(kosmos) {
testScope.runTest {
@@ -113,7 +111,6 @@ class QSColumnsViewModelTest : SysuiTestCase() {
}
@Test
- @EnableSceneContainer
fun mediaLocationQS_dualShade_alwaysDualShadeColumns() =
with(kosmos) {
testScope.runTest {
@@ -136,7 +133,6 @@ class QSColumnsViewModelTest : SysuiTestCase() {
}
@Test
- @EnableSceneContainer
fun mediaLocationQQS_dualShade_alwaysDualShadeColumns() =
with(kosmos) {
testScope.runTest {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/InternetTileNewImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/InternetTileNewImplTest.kt
index 64e6f4bd48b8..7ed3cb3e4586 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/InternetTileNewImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/InternetTileNewImplTest.kt
@@ -34,7 +34,6 @@ import com.android.systemui.qs.flags.QSComposeFragment
import com.android.systemui.qs.logging.QSLogger
import com.android.systemui.qs.tiles.dialog.InternetDetailsViewModel
import com.android.systemui.qs.tiles.dialog.InternetDialogManager
-import com.android.systemui.qs.tiles.dialog.WifiStateWorker
import com.android.systemui.res.R
import com.android.systemui.statusbar.connectivity.AccessPointController
import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
@@ -61,9 +60,6 @@ import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.MockitoAnnotations
-import org.mockito.kotlin.eq
-import org.mockito.kotlin.times
-import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever
import platform.test.runner.parameterized.ParameterizedAndroidJunit4
import platform.test.runner.parameterized.Parameters
@@ -99,7 +95,6 @@ class InternetTileNewImplTest(flags: FlagsParameterization) : SysuiTestCase() {
@Mock private lateinit var activityStarter: ActivityStarter
@Mock private lateinit var logger: QSLogger
@Mock private lateinit var dialogManager: InternetDialogManager
- @Mock private lateinit var wifiStateWorker: WifiStateWorker
@Mock private lateinit var accessPointController: AccessPointController
@Mock private lateinit var internetDetailsViewModelFactory: InternetDetailsViewModel.Factory
@@ -136,7 +131,6 @@ class InternetTileNewImplTest(flags: FlagsParameterization) : SysuiTestCase() {
logger,
viewModel,
dialogManager,
- wifiStateWorker,
accessPointController,
internetDetailsViewModelFactory,
)
@@ -245,26 +239,6 @@ class InternetTileNewImplTest(flags: FlagsParameterization) : SysuiTestCase() {
assertThat(underTest.state.secondaryLabel).isEqualTo(WIFI_SSID)
}
- @Test
- fun secondaryClick_turnsWifiOff() {
- whenever(wifiStateWorker.isWifiEnabled).thenReturn(true)
-
- underTest.secondaryClick(null)
- looper.processAllMessages()
-
- verify(wifiStateWorker, times(1)).isWifiEnabled = eq(false)
- }
-
- @Test
- fun secondaryClick_turnsWifiOn() {
- whenever(wifiStateWorker.isWifiEnabled).thenReturn(false)
-
- underTest.secondaryClick(null)
- looper.processAllMessages()
-
- verify(wifiStateWorker, times(1)).isWifiEnabled = eq(true)
- }
-
companion object {
const val WIFI_SSID = "test ssid"
val ACTIVE_WIFI =
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java
index 3a3f5371d195..61d0c8dbb087 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java
@@ -345,11 +345,21 @@ public class ScreenRecordTileTest extends SysuiTestCase {
public void testNotStartingAndRecording_returnDetailsViewModel() {
when(mController.isStarting()).thenReturn(false);
when(mController.isRecording()).thenReturn(false);
+ when(mController.isScreenCaptureDisabled()).thenReturn(false);
mTile.getDetailsViewModel(Assert::assertNotNull);
}
@Test
@EnableFlags(QsDetailedView.FLAG_NAME)
+ public void testRecordingDisabled_notReturnDetailsViewModel() {
+ when(mController.isStarting()).thenReturn(false);
+ when(mController.isRecording()).thenReturn(false);
+ when(mController.isScreenCaptureDisabled()).thenReturn(true);
+ mTile.getDetailsViewModel(Assert::assertNull);
+ }
+
+ @Test
+ @EnableFlags(QsDetailedView.FLAG_NAME)
public void testStarting_notReturnDetailsViewModel() {
when(mController.isStarting()).thenReturn(true);
when(mController.isRecording()).thenReturn(false);
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/InternetTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/InternetTileMapperTest.kt
index b087bbc29bf7..54a653df696f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/InternetTileMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/InternetTileMapperTest.kt
@@ -191,11 +191,7 @@ class InternetTileMapperTest : SysuiTestCase() {
label,
activationState,
secondaryLabel,
- setOf(
- QSTileState.UserAction.CLICK,
- QSTileState.UserAction.TOGGLE_CLICK,
- QSTileState.UserAction.LONG_CLICK,
- ),
+ setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK),
contentDescription,
null,
QSTileState.SideViewIcon.Chevron,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileUserActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileUserActionInteractorTest.kt
index ce4a3432a5b4..3db5efcb6eb8 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileUserActionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileUserActionInteractorTest.kt
@@ -29,7 +29,6 @@ import com.android.systemui.qs.tiles.base.interactor.QSTileInputTestKtx
import com.android.systemui.qs.tiles.dialog.InternetDetailsContentManager
import com.android.systemui.qs.tiles.dialog.InternetDetailsViewModel
import com.android.systemui.qs.tiles.dialog.InternetDialogManager
-import com.android.systemui.qs.tiles.dialog.WifiStateWorker
import com.android.systemui.qs.tiles.impl.internet.domain.model.InternetTileModel
import com.android.systemui.statusbar.connectivity.AccessPointController
import com.android.systemui.util.mockito.nullable
@@ -56,7 +55,6 @@ class InternetTileUserActionInteractorTest : SysuiTestCase() {
private lateinit var underTest: InternetTileUserActionInteractor
private lateinit var internetDialogManager: InternetDialogManager
- private lateinit var wifiStateWorker: WifiStateWorker
private lateinit var controller: AccessPointController
private lateinit var internetDetailsViewModelFactory: InternetDetailsViewModel.Factory
private lateinit var internetDetailsContentManagerFactory: InternetDetailsContentManager.Factory
@@ -65,7 +63,6 @@ class InternetTileUserActionInteractorTest : SysuiTestCase() {
@Before
fun setup() {
internetDialogManager = mock<InternetDialogManager>()
- wifiStateWorker = mock<WifiStateWorker>()
controller = mock<AccessPointController>()
internetDetailsViewModelFactory = mock<InternetDetailsViewModel.Factory>()
internetDetailsContentManagerFactory = mock<InternetDetailsContentManager.Factory>()
@@ -81,7 +78,6 @@ class InternetTileUserActionInteractorTest : SysuiTestCase() {
InternetTileUserActionInteractor(
kosmos.testScope.coroutineContext,
internetDialogManager,
- wifiStateWorker,
controller,
inputHandler,
internetDetailsViewModelFactory,
@@ -133,26 +129,6 @@ class InternetTileUserActionInteractorTest : SysuiTestCase() {
}
@Test
- fun handleSecondaryClickWhenWifiOn() =
- kosmos.testScope.runTest {
- whenever(wifiStateWorker.isWifiEnabled).thenReturn(true)
-
- underTest.handleInput(QSTileInputTestKtx.toggleClick(InternetTileModel.Active()))
-
- verify(wifiStateWorker, times(1)).isWifiEnabled = eq(false)
- }
-
- @Test
- fun handleSecondaryClickWhenWifiOff() =
- kosmos.testScope.runTest {
- whenever(wifiStateWorker.isWifiEnabled).thenReturn(false)
-
- underTest.handleInput(QSTileInputTestKtx.toggleClick(InternetTileModel.Inactive()))
-
- verify(wifiStateWorker, times(1)).isWifiEnabled = eq(true)
- }
-
- @Test
fun detailsViewModel() =
kosmos.testScope.runTest {
assertThat(underTest.detailsViewModel.getTitle()).isEqualTo("Internet")
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 d26e195d360a..668f568d7f46 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeModeInteractorImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeModeInteractorImplTest.kt
@@ -20,7 +20,6 @@ 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
@@ -32,7 +31,6 @@ import org.junit.runner.RunWith
@SmallTest
@RunWith(AndroidJUnit4::class)
-@EnableSceneContainer
class ShadeModeInteractorImplTest : SysuiTestCase() {
private val kosmos = testKosmos()
@@ -82,7 +80,7 @@ class ShadeModeInteractorImplTest : SysuiTestCase() {
}
@Test
- fun isDualShade_settingEnabledSceneContainerEnabled_returnsTrue() =
+ fun isDualShade_settingEnabled_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 dde867814159..b8f66acf6413 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,7 +48,6 @@ 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
@@ -60,7 +59,6 @@ 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() {
@@ -105,7 +103,6 @@ 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/system/QuickStepContractTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/system/QuickStepContractTest.kt
index d92781a5f3ce..ef03fab95778 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/system/QuickStepContractTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/system/QuickStepContractTest.kt
@@ -16,10 +16,8 @@
package com.android.systemui.shared.system
-import android.platform.test.annotations.DisableFlags
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
-import com.android.systemui.Flags.FLAG_GLANCEABLE_HUB_BACK_ACTION
import com.android.systemui.SysuiTestCase
import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BOUNCER_SHOWING
import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_COMMUNAL_HUB_SHOWING
@@ -32,7 +30,6 @@ import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class QuickStepContractTest : SysuiTestCase() {
@Test
- @DisableFlags(FLAG_GLANCEABLE_HUB_BACK_ACTION)
fun isBackGestureDisabled_hubShowing() {
val sysuiStateFlags = SYSUI_STATE_COMMUNAL_HUB_SHOWING
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt
index d2ea62da0940..83361dad9ff0 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt
@@ -76,7 +76,6 @@ import org.mockito.junit.MockitoJUnit
class NotificationShadeDepthControllerTest : SysuiTestCase() {
private val kosmos = testKosmos()
- private val applicationScope = kosmos.testScope.backgroundScope
@Mock private lateinit var windowRootViewBlurInteractor: WindowRootViewBlurInteractor
@Mock private lateinit var statusBarStateController: StatusBarStateController
@Mock private lateinit var blurUtils: BlurUtils
@@ -134,7 +133,6 @@ class NotificationShadeDepthControllerTest : SysuiTestCase() {
context,
ResourcesSplitShadeStateController(),
windowRootViewBlurInteractor,
- applicationScope,
appZoomOutOptional,
dumpManager,
configurationController,
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 05f2585cfaa5..cabe4afdea60 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
@@ -306,7 +306,7 @@ class SingleNotificationChipInteractorTest : SysuiTestCase() {
}
@Test
- fun notificationChip_appIsVisibleOnCreation_emitsNull() =
+ fun notificationChip_appIsVisibleOnCreation_emitsIsAppVisibleTrue() =
kosmos.runTest {
activityManagerRepository.fake.startingIsAppVisibleValue = true
@@ -323,11 +323,12 @@ class SingleNotificationChipInteractorTest : SysuiTestCase() {
val latest by collectLastValue(underTest.notificationChip)
- assertThat(latest).isNull()
+ assertThat(latest).isNotNull()
+ assertThat(latest!!.isAppVisible).isTrue()
}
@Test
- fun notificationChip_appNotVisibleOnCreation_emitsValue() =
+ fun notificationChip_appNotVisibleOnCreation_emitsIsAppVisibleFalse() =
kosmos.runTest {
activityManagerRepository.fake.startingIsAppVisibleValue = false
@@ -345,10 +346,11 @@ class SingleNotificationChipInteractorTest : SysuiTestCase() {
val latest by collectLastValue(underTest.notificationChip)
assertThat(latest).isNotNull()
+ assertThat(latest!!.isAppVisible).isFalse()
}
@Test
- fun notificationChip_hidesWhenAppIsVisible() =
+ fun notificationChip_updatesWhenAppIsVisible() =
kosmos.runTest {
val underTest =
factory.create(
@@ -364,13 +366,13 @@ class SingleNotificationChipInteractorTest : SysuiTestCase() {
val latest by collectLastValue(underTest.notificationChip)
activityManagerRepository.fake.setIsAppVisible(UID, false)
- assertThat(latest).isNotNull()
+ assertThat(latest!!.isAppVisible).isFalse()
activityManagerRepository.fake.setIsAppVisible(UID, true)
- assertThat(latest).isNull()
+ assertThat(latest!!.isAppVisible).isTrue()
activityManagerRepository.fake.setIsAppVisible(UID, false)
- assertThat(latest).isNotNull()
+ assertThat(latest!!.isAppVisible).isFalse()
}
// Note: This test is theoretically impossible because the notification key should contain the
@@ -396,6 +398,7 @@ class SingleNotificationChipInteractorTest : SysuiTestCase() {
)
val latest by collectLastValue(underTest.notificationChip)
assertThat(latest).isNotNull()
+ assertThat(latest!!.isAppVisible).isFalse()
// WHEN the notif gets a new UID that starts as visible
activityManagerRepository.fake.startingIsAppVisibleValue = true
@@ -408,9 +411,8 @@ class SingleNotificationChipInteractorTest : SysuiTestCase() {
)
)
- // THEN we re-fetch the app visibility state with the new UID, and since that UID is
- // visible, we hide the chip
- assertThat(latest).isNull()
+ // THEN we re-fetch the app visibility state with the new UID
+ assertThat(latest!!.isAppVisible).isTrue()
}
companion object {
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 e89c929a5827..d8e4cd927bec 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
@@ -21,8 +21,8 @@ import android.platform.test.annotations.EnableFlags
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.coroutines.collectValues
+import com.android.systemui.activity.data.repository.activityManagerRepository
+import com.android.systemui.activity.data.repository.fake
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.collectLastValue
import com.android.systemui.kosmos.collectValues
@@ -41,7 +41,6 @@ import com.android.systemui.testKosmos
import com.android.systemui.util.time.fakeSystemClock
import com.google.common.truth.Truth.assertThat
import kotlin.test.Test
-import kotlinx.coroutines.test.runTest
import org.junit.runner.RunWith
import org.mockito.kotlin.mock
@@ -55,9 +54,9 @@ class StatusBarNotificationChipsInteractorTest : SysuiTestCase() {
@Test
@DisableFlags(StatusBarNotifChips.FLAG_NAME)
- fun notificationChips_flagOff_noNotifs() =
+ fun shownNotificationChips_flagOff_noNotifs() =
kosmos.runTest {
- val latest by collectLastValue(underTest.notificationChips)
+ val latest by collectLastValue(underTest.shownNotificationChips)
setNotifs(
listOf(
@@ -74,9 +73,9 @@ class StatusBarNotificationChipsInteractorTest : SysuiTestCase() {
@Test
@EnableFlags(StatusBarNotifChips.FLAG_NAME)
- fun notificationChips_noNotifs_empty() =
+ fun shownNotificationChips_noNotifs_empty() =
kosmos.runTest {
- val latest by collectLastValue(underTest.notificationChips)
+ val latest by collectLastValue(underTest.shownNotificationChips)
setNotifs(emptyList())
@@ -86,9 +85,9 @@ class StatusBarNotificationChipsInteractorTest : SysuiTestCase() {
@Test
@EnableFlags(StatusBarNotifChips.FLAG_NAME)
@DisableFlags(StatusBarConnectedDisplays.FLAG_NAME)
- fun notificationChips_notifMissingStatusBarChipIconView_cdFlagOff_empty() =
+ fun shownNotificationChips_notifMissingStatusBarChipIconView_cdFlagOff_empty() =
kosmos.runTest {
- val latest by collectLastValue(underTest.notificationChips)
+ val latest by collectLastValue(underTest.shownNotificationChips)
setNotifs(
listOf(
@@ -105,9 +104,9 @@ class StatusBarNotificationChipsInteractorTest : SysuiTestCase() {
@Test
@EnableFlags(StatusBarNotifChips.FLAG_NAME, StatusBarConnectedDisplays.FLAG_NAME)
- fun notificationChips_notifMissingStatusBarChipIconView_cdFlagOn_notEmpty() =
+ fun shownNotificationChips_notifMissingStatusBarChipIconView_cdFlagOn_notEmpty() =
kosmos.runTest {
- val latest by collectLastValue(underTest.notificationChips)
+ val latest by collectLastValue(underTest.shownNotificationChips)
setNotifs(
listOf(
@@ -124,9 +123,9 @@ class StatusBarNotificationChipsInteractorTest : SysuiTestCase() {
@Test
@EnableFlags(StatusBarNotifChips.FLAG_NAME)
- fun notificationChips_onePromotedNotif_statusBarIconViewMatches() =
+ fun shownNotificationChips_onePromotedNotif_statusBarIconViewMatches() =
kosmos.runTest {
- val latest by collectLastValue(underTest.notificationChips)
+ val latest by collectLastValue(underTest.shownNotificationChips)
val icon = mock<StatusBarIconView>()
setNotifs(
@@ -146,9 +145,9 @@ class StatusBarNotificationChipsInteractorTest : SysuiTestCase() {
@Test
@EnableFlags(StatusBarNotifChips.FLAG_NAME)
- fun notificationChips_onlyForPromotedNotifs() =
+ fun shownNotificationChips_onlyForPromotedNotifs() =
kosmos.runTest {
- val latest by collectLastValue(underTest.notificationChips)
+ val latest by collectLastValue(underTest.shownNotificationChips)
val firstIcon = mock<StatusBarIconView>()
val secondIcon = mock<StatusBarIconView>()
@@ -179,12 +178,42 @@ class StatusBarNotificationChipsInteractorTest : SysuiTestCase() {
assertThat(latest!![1].statusBarChipIconView).isEqualTo(secondIcon)
}
+ @Test
+ @EnableFlags(StatusBarNotifChips.FLAG_NAME)
+ fun shownNotificationChips_onlyForNotVisibleApps() =
+ kosmos.runTest {
+ activityManagerRepository.fake.startingIsAppVisibleValue = false
+
+ val latest by collectLastValue(underTest.shownNotificationChips)
+
+ val uid = 433
+ setNotifs(
+ listOf(
+ activeNotificationModel(
+ key = "notif",
+ uid = uid,
+ statusBarChipIcon = mock<StatusBarIconView>(),
+ promotedContent = PromotedNotificationContentModel.Builder("notif1").build(),
+ )
+ )
+ )
+
+ activityManagerRepository.fake.setIsAppVisible(uid, isAppVisible = false)
+ assertThat(latest).hasSize(1)
+
+ activityManagerRepository.fake.setIsAppVisible(uid, isAppVisible = true)
+ assertThat(latest).isEmpty()
+
+ activityManagerRepository.fake.setIsAppVisible(uid, isAppVisible = false)
+ assertThat(latest).hasSize(1)
+ }
+
/** Regression test for b/388521980. */
@Test
@EnableFlags(StatusBarNotifChips.FLAG_NAME)
- fun notificationChips_callNotifIsAlsoPromoted_callNotifExcluded() =
+ fun shownNotificationChips_callNotifIsAlsoPromoted_callNotifExcluded() =
kosmos.runTest {
- val latest by collectLastValue(underTest.notificationChips)
+ val latest by collectLastValue(underTest.shownNotificationChips)
setNotifs(
listOf(
@@ -212,9 +241,9 @@ class StatusBarNotificationChipsInteractorTest : SysuiTestCase() {
@Test
@EnableFlags(StatusBarNotifChips.FLAG_NAME)
- fun notificationChips_notifUpdatesGoThrough() =
+ fun shownNotificationChips_notifUpdatesGoThrough() =
kosmos.runTest {
- val latest by collectLastValue(underTest.notificationChips)
+ val latest by collectLastValue(underTest.shownNotificationChips)
val firstIcon = mock<StatusBarIconView>()
val secondIcon = mock<StatusBarIconView>()
@@ -262,9 +291,9 @@ class StatusBarNotificationChipsInteractorTest : SysuiTestCase() {
@Test
@EnableFlags(StatusBarNotifChips.FLAG_NAME)
- fun notificationChips_promotedNotifDisappearsThenReappears() =
+ fun shownNotificationChips_promotedNotifDisappearsThenReappears() =
kosmos.runTest {
- val latest by collectLastValue(underTest.notificationChips)
+ val latest by collectLastValue(underTest.shownNotificationChips)
setNotifs(
listOf(
@@ -304,9 +333,9 @@ class StatusBarNotificationChipsInteractorTest : SysuiTestCase() {
@Test
@EnableFlags(StatusBarNotifChips.FLAG_NAME)
- fun notificationChips_sortedBasedOnFirstAppearanceTime() =
+ fun shownNotificationChips_sortedBasedOnFirstAppearanceTime() =
kosmos.runTest {
- val latest by collectLastValue(underTest.notificationChips)
+ val latest by collectLastValue(underTest.shownNotificationChips)
val firstIcon = mock<StatusBarIconView>()
val secondIcon = mock<StatusBarIconView>()
@@ -391,9 +420,9 @@ class StatusBarNotificationChipsInteractorTest : SysuiTestCase() {
@Test
@EnableFlags(StatusBarNotifChips.FLAG_NAME)
- fun notificationChips_notifChangesKey() =
+ fun shownNotificationChips_notifChangesKey() =
kosmos.runTest {
- val latest by collectLastValue(underTest.notificationChips)
+ val latest by collectLastValue(underTest.shownNotificationChips)
val firstIcon = mock<StatusBarIconView>()
val secondIcon = mock<StatusBarIconView>()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/TimeRemainingStateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/TimeRemainingStateTest.kt
new file mode 100644
index 000000000000..5dc59e893715
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/TimeRemainingStateTest.kt
@@ -0,0 +1,237 @@
+/*
+ * 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.chips.ui.viewmodel
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.internal.R.string.duration_hours_medium
+import com.android.internal.R.string.duration_minutes_medium
+import com.android.internal.R.string.now_string_shortest
+import com.android.systemui.SysuiTestCase
+import com.google.common.truth.Truth.assertThat
+import kotlin.time.Duration.Companion.hours
+import kotlin.time.Duration.Companion.minutes
+import kotlin.time.Duration.Companion.seconds
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.cancelAndJoin
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.advanceTimeBy
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class TimeRemainingStateTest : SysuiTestCase() {
+
+ private var fakeTimeSource: MutableTimeSource = MutableTimeSource()
+ // We need a non-zero start time to advance to. This is needed to ensure `TimeRemainingState` is
+ // updated at least once.
+ private val startTime = 1.seconds.inWholeMilliseconds
+
+ @Test
+ fun timeRemainingState_pastTime() = runTest {
+ val state = TimeRemainingState(fakeTimeSource, startTime - 62.seconds.inWholeMilliseconds)
+ val job = launch { state.run() }
+
+ fakeTimeSource.time = startTime
+ advanceTimeBy(startTime)
+ assertThat(state.timeRemainingData).isNull()
+ job.cancelAndJoin()
+ }
+
+ @Test
+ fun timeRemainingState_lessThanOneMinute() = runTest {
+ val state = TimeRemainingState(fakeTimeSource, startTime + 59.seconds.inWholeMilliseconds)
+ val job = launch { state.run() }
+
+ fakeTimeSource.time = startTime
+ advanceTimeBy(startTime)
+ assertThat(state.timeRemainingData!!.first).isEqualTo(now_string_shortest)
+ job.cancelAndJoin()
+ }
+
+ @Test
+ fun timeRemainingState_lessThanOneMinuteInThePast() = runTest {
+ val state = TimeRemainingState(fakeTimeSource, startTime - 59.seconds.inWholeMilliseconds)
+ val job = launch { state.run() }
+
+ fakeTimeSource.time = startTime
+ advanceTimeBy(startTime)
+ assertThat(state.timeRemainingData!!.first).isEqualTo(now_string_shortest)
+ job.cancelAndJoin()
+ }
+
+ @Test
+ fun timeRemainingState_oneMinute() = runTest {
+ val state = TimeRemainingState(fakeTimeSource, startTime + 60.seconds.inWholeMilliseconds)
+ val job = launch { state.run() }
+
+ fakeTimeSource.time = startTime
+ advanceTimeBy(startTime)
+ assertThat(state.timeRemainingData!!.first).isEqualTo(duration_minutes_medium)
+ assertThat(state.timeRemainingData!!.second).isEqualTo(1)
+ job.cancelAndJoin()
+ }
+
+ @Test
+ fun timeRemainingState_lessThanOneHour() = runTest {
+ val state = TimeRemainingState(fakeTimeSource, startTime + 59.minutes.inWholeMilliseconds)
+ val job = launch { state.run() }
+
+ fakeTimeSource.time = startTime
+ advanceTimeBy(startTime)
+ assertThat(state.timeRemainingData!!.first).isEqualTo(duration_minutes_medium)
+ assertThat(state.timeRemainingData!!.second).isEqualTo(59)
+ job.cancelAndJoin()
+ }
+
+ @Test
+ fun timeRemainingState_oneHour() = runTest {
+ val state = TimeRemainingState(fakeTimeSource, startTime + 60.minutes.inWholeMilliseconds)
+ val job = launch { state.run() }
+
+ fakeTimeSource.time = startTime
+ advanceTimeBy(startTime)
+ assertThat(state.timeRemainingData!!.first).isEqualTo(duration_hours_medium)
+ assertThat(state.timeRemainingData!!.second).isEqualTo(1)
+ job.cancelAndJoin()
+ }
+
+ @Test
+ fun timeRemainingState_betweenOneAndTwoHours() = runTest {
+ val state = TimeRemainingState(fakeTimeSource, startTime + 119.minutes.inWholeMilliseconds)
+ val job = launch { state.run() }
+
+ fakeTimeSource.time = startTime
+ advanceTimeBy(startTime)
+
+ assertThat(state.timeRemainingData).isNotNull()
+ assertThat(state.timeRemainingData!!.first).isEqualTo(duration_hours_medium)
+ assertThat(state.timeRemainingData!!.second).isEqualTo(1)
+ job.cancelAndJoin()
+ }
+
+ @Test
+ fun timeRemainingState_betweenFiveAndSixHours() = runTest {
+ val state = TimeRemainingState(fakeTimeSource, startTime + 320.minutes.inWholeMilliseconds)
+ val job = launch { state.run() }
+
+ fakeTimeSource.time = startTime
+ advanceTimeBy(startTime)
+ assertThat(state.timeRemainingData!!.first).isEqualTo(duration_hours_medium)
+ assertThat(state.timeRemainingData!!.second).isEqualTo(5)
+ job.cancelAndJoin()
+ }
+
+ fun timeRemainingState_moreThan24Hours() = runTest {
+ val state =
+ TimeRemainingState(fakeTimeSource, startTime + (25 * 60.minutes.inWholeMilliseconds))
+ val job = launch { state.run() }
+
+ fakeTimeSource.time = startTime
+ advanceTimeBy(startTime)
+ assertThat(state.timeRemainingData).isNull()
+
+ job.cancelAndJoin()
+ }
+
+ @Test
+ fun timeRemainingState_updateFromMinuteToNow() = runTest {
+ fakeTimeSource.time = startTime
+ val state = TimeRemainingState(fakeTimeSource, startTime + 119.seconds.inWholeMilliseconds)
+ val job = launch { state.run() }
+
+ advanceTimeBy(startTime)
+ assertThat(state.timeRemainingData!!.first).isEqualTo(duration_minutes_medium)
+ assertThat(state.timeRemainingData!!.second).isEqualTo(1)
+
+ fakeTimeSource.time += 59.seconds.inWholeMilliseconds
+ advanceTimeBy(59.seconds.inWholeMilliseconds)
+ assertThat(state.timeRemainingData!!.first).isEqualTo(duration_minutes_medium)
+ assertThat(state.timeRemainingData!!.second).isEqualTo(1)
+
+ fakeTimeSource.time += 1.seconds.inWholeMilliseconds
+ advanceTimeBy(1.seconds.inWholeMilliseconds)
+ assertThat(state.timeRemainingData!!.first).isEqualTo(now_string_shortest)
+
+ job.cancelAndJoin()
+ }
+
+ fun timeRemainingState_updateFromNowToEmpty() = runTest {
+ fakeTimeSource.time = startTime
+ val state = TimeRemainingState(fakeTimeSource, startTime)
+ val job = launch { state.run() }
+
+ advanceTimeBy(startTime)
+ assertThat(state.timeRemainingData!!.first).isEqualTo(now_string_shortest)
+
+ fakeTimeSource.time += 62.seconds.inWholeMilliseconds
+ advanceTimeBy(62.seconds.inWholeMilliseconds)
+ assertThat(state.timeRemainingData).isNull()
+
+ job.cancelAndJoin()
+ }
+
+ @Test
+ fun timeRemainingState_updateFromHourToMinutes() = runTest {
+ fakeTimeSource.time = startTime
+ val state = TimeRemainingState(fakeTimeSource, startTime + 119.minutes.inWholeMilliseconds)
+ val job = launch { state.run() }
+
+ advanceTimeBy(startTime)
+ assertThat(state.timeRemainingData!!.first).isEqualTo(duration_hours_medium)
+ assertThat(state.timeRemainingData!!.second).isEqualTo(1)
+
+ fakeTimeSource.time += 59.minutes.inWholeMilliseconds
+ advanceTimeBy(59.minutes.inWholeMilliseconds)
+ assertThat(state.timeRemainingData!!.first).isEqualTo(duration_hours_medium)
+ assertThat(state.timeRemainingData!!.second).isEqualTo(1)
+
+ fakeTimeSource.time += 1.seconds.inWholeMilliseconds
+ advanceTimeBy(1.seconds.inWholeMilliseconds)
+ assertThat(state.timeRemainingData!!.first).isEqualTo(duration_minutes_medium)
+ assertThat(state.timeRemainingData!!.second).isEqualTo(59)
+
+ job.cancelAndJoin()
+ }
+
+ @Test
+ fun timeRemainingState_showAfterLessThan24Hours() = runTest {
+ fakeTimeSource.time = startTime
+ val state = TimeRemainingState(fakeTimeSource, startTime + 25.hours.inWholeMilliseconds)
+ val job = launch { state.run() }
+
+ advanceTimeBy(startTime)
+ assertThat(state.timeRemainingData).isNull()
+
+ fakeTimeSource.time += 1.hours.inWholeMilliseconds + 1.seconds.inWholeMilliseconds
+ advanceTimeBy(1.hours.inWholeMilliseconds + 1.seconds.inWholeMilliseconds)
+ assertThat(state.timeRemainingData!!.first).isEqualTo(duration_hours_medium)
+ assertThat(state.timeRemainingData!!.second).isEqualTo(23)
+
+ job.cancelAndJoin()
+ }
+
+ /** A fake implementation of [TimeSource] that allows the caller to set the current time */
+ private class MutableTimeSource(var time: Long = 0L) : TimeSource {
+ override fun getCurrentTime(): Long {
+ return time
+ }
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/ColorizedFgsCoordinatorTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/ColorizedFgsCoordinatorTest.java
deleted file mode 100644
index 544d20145db3..000000000000
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/ColorizedFgsCoordinatorTest.java
+++ /dev/null
@@ -1,209 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.notification.collection.coordinator;
-
-import static android.app.Notification.FLAG_FOREGROUND_SERVICE;
-import static android.app.Notification.FLAG_PROMOTED_ONGOING;
-import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
-import static android.app.NotificationManager.IMPORTANCE_HIGH;
-import static android.app.NotificationManager.IMPORTANCE_MIN;
-
-import static junit.framework.Assert.assertFalse;
-import static junit.framework.Assert.assertTrue;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.verify;
-
-import android.app.Notification;
-import android.app.PendingIntent;
-import android.app.Person;
-import android.content.Intent;
-import android.graphics.Color;
-import android.os.UserHandle;
-import android.platform.test.annotations.DisableFlags;
-import android.platform.test.annotations.EnableFlags;
-import android.testing.TestableLooper;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
-
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.statusbar.notification.collection.NotifPipeline;
-import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
-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 org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-@TestableLooper.RunWithLooper
-public class ColorizedFgsCoordinatorTest extends SysuiTestCase {
-
- private static final int NOTIF_USER_ID = 0;
- @Mock private NotifPipeline mNotifPipeline;
-
- private NotificationEntryBuilder mEntryBuilder;
- private ColorizedFgsCoordinator mColorizedFgsCoordinator;
- private NotifSectioner mFgsSection;
-
- @Before
- public void setup() {
- MockitoAnnotations.initMocks(this);
- allowTestableLooperAsMainThread();
-
- mColorizedFgsCoordinator = new ColorizedFgsCoordinator();
-
- mEntryBuilder = new NotificationEntryBuilder()
- .setUser(new UserHandle(NOTIF_USER_ID));
-
- mColorizedFgsCoordinator.attach(mNotifPipeline);
-
- mFgsSection = mColorizedFgsCoordinator.getSectioner();
- }
-
- @Test
- public void testIncludeFGSInSection_importanceDefault() {
- // GIVEN the notification represents a colorized foreground service with > min importance
- mEntryBuilder
- .setFlag(mContext, FLAG_FOREGROUND_SERVICE, true)
- .setImportance(IMPORTANCE_DEFAULT)
- .modifyNotification(mContext)
- .setColorized(true).setColor(Color.WHITE);
-
- // THEN the entry is in the fgs section
- assertTrue(mFgsSection.isInSection(mEntryBuilder.build()));
- }
-
- @Test
- public void testDiscludeFGSInSection_importanceMin() {
- // GIVEN the notification represents a colorized foreground service with min importance
- mEntryBuilder
- .setFlag(mContext, FLAG_FOREGROUND_SERVICE, true)
- .setImportance(IMPORTANCE_MIN)
- .modifyNotification(mContext)
- .setColorized(true).setColor(Color.WHITE);
-
- // THEN the entry is NOT in the fgs section
- assertFalse(mFgsSection.isInSection(mEntryBuilder.build()));
- }
-
- @Test
- public void testDiscludeNonFGSInSection() {
- // GIVEN the notification represents a colorized notification with high importance that
- // is NOT a foreground service
- mEntryBuilder
- .setImportance(IMPORTANCE_HIGH)
- .setFlag(mContext, FLAG_FOREGROUND_SERVICE, false)
- .modifyNotification(mContext).setColorized(false);
-
- // THEN the entry is NOT in the fgs section
- assertFalse(mFgsSection.isInSection(mEntryBuilder.build()));
- }
-
- @Test
- public void testIncludeCallInSection_importanceDefault() {
- // GIVEN the notification represents a call with > min importance
- mEntryBuilder
- .setImportance(IMPORTANCE_DEFAULT)
- .modifyNotification(mContext)
- .setStyle(makeCallStyle());
-
- // THEN the entry is in the fgs section
- assertTrue(mFgsSection.isInSection(mEntryBuilder.build()));
- }
-
- @Test
- public void testDiscludeCallInSection_importanceMin() {
- // GIVEN the notification represents a call with min importance
- mEntryBuilder
- .setImportance(IMPORTANCE_MIN)
- .modifyNotification(mContext)
- .setStyle(makeCallStyle());
-
- // THEN the entry is NOT in the fgs section
- assertFalse(mFgsSection.isInSection(mEntryBuilder.build()));
- }
-
- @Test
- @EnableFlags(PromotedNotificationUi.FLAG_NAME)
- public void testIncludePromotedOngoingInSection_flagEnabled() {
- // GIVEN the notification has FLAG_PROMOTED_ONGOING
- mEntryBuilder.setFlag(mContext, FLAG_PROMOTED_ONGOING, true);
-
- // THEN the entry is in the fgs section
- assertTrue(mFgsSection.isInSection(mEntryBuilder.build()));
- }
-
- @Test
- @DisableFlags(PromotedNotificationUi.FLAG_NAME)
- public void testDiscludePromotedOngoingInSection_flagDisabled() {
- // GIVEN the notification has FLAG_PROMOTED_ONGOING
- mEntryBuilder.setFlag(mContext, FLAG_PROMOTED_ONGOING, true);
-
- // THEN the entry is NOT in the fgs section
- assertFalse(mFgsSection.isInSection(mEntryBuilder.build()));
- }
-
- @Test
- @EnableFlags(PromotedNotificationUi.FLAG_NAME)
- public void promoterSelectsPromotedOngoing_flagEnabled() {
- ArgumentCaptor<NotifPromoter> captor = ArgumentCaptor.forClass(NotifPromoter.class);
- verify(mNotifPipeline).addPromoter(captor.capture());
- NotifPromoter promoter = captor.getValue();
-
- // GIVEN the notification has FLAG_PROMOTED_ONGOING
- mEntryBuilder.setFlag(mContext, FLAG_PROMOTED_ONGOING, true);
-
- // THEN the entry is promoted to top level
- assertTrue(promoter.shouldPromoteToTopLevel(mEntryBuilder.build()));
- }
-
- @Test
- @EnableFlags(PromotedNotificationUi.FLAG_NAME)
- public void promoterIgnoresNonPromotedOngoing_flagEnabled() {
- ArgumentCaptor<NotifPromoter> captor = ArgumentCaptor.forClass(NotifPromoter.class);
- verify(mNotifPipeline).addPromoter(captor.capture());
- NotifPromoter promoter = captor.getValue();
-
- // GIVEN the notification does not have FLAG_PROMOTED_ONGOING
- mEntryBuilder.setFlag(mContext, FLAG_PROMOTED_ONGOING, false);
-
- // THEN the entry is NOT promoted to top level
- assertFalse(promoter.shouldPromoteToTopLevel(mEntryBuilder.build()));
- }
-
- @Test
- @DisableFlags(PromotedNotificationUi.FLAG_NAME)
- public void noPromoterAdded_flagDisabled() {
- verify(mNotifPipeline, never()).addPromoter(any());
- }
-
- private Notification.CallStyle makeCallStyle() {
- final PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, 0,
- new Intent("action"), PendingIntent.FLAG_IMMUTABLE);
- final Person person = new Person.Builder().setName("person").build();
- return Notification.CallStyle.forIncomingCall(person, pendingIntent, pendingIntent);
- }
-}
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
new file mode 100644
index 000000000000..e93c74252251
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/ColorizedFgsCoordinatorTest.kt
@@ -0,0 +1,191 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.statusbar.notification.collection.coordinator
+
+import android.app.Notification
+import android.app.NotificationManager
+import android.app.PendingIntent
+import android.app.Person
+import android.content.Intent
+import android.graphics.Color
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
+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.kosmos.useUnconfinedTestDispatcher
+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.promoted.PromotedNotificationUi
+import com.android.systemui.testKosmos
+import com.android.systemui.util.mockito.withArgCaptor
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.any
+import org.mockito.kotlin.never
+import org.mockito.kotlin.verify
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@RunWithLooper
+class ColorizedFgsCoordinatorTest : SysuiTestCase() {
+ private val kosmos = testKosmos().useUnconfinedTestDispatcher()
+ private val notifPipeline
+ get() = kosmos.notifPipeline
+
+ private lateinit var colorizedFgsCoordinator: ColorizedFgsCoordinator
+ private lateinit var sectioner: NotifSectioner
+
+ @Before
+ fun setup() {
+ allowTestableLooperAsMainThread()
+
+ colorizedFgsCoordinator = ColorizedFgsCoordinator()
+ colorizedFgsCoordinator.attach(notifPipeline)
+ sectioner = colorizedFgsCoordinator.sectioner
+ }
+
+ @Test
+ fun testIncludeFGSInSection_importanceDefault() {
+ // GIVEN the notification represents a colorized foreground service with > min importance
+ val entry = buildEntry {
+ setFlag(mContext, Notification.FLAG_FOREGROUND_SERVICE, true)
+ setImportance(NotificationManager.IMPORTANCE_DEFAULT)
+ modifyNotification(mContext).setColorized(true).setColor(Color.WHITE)
+ }
+
+ // THEN the entry is in the fgs section
+ assertTrue(sectioner.isInSection(entry))
+ }
+
+ @Test
+ fun testDiscludeFGSInSection_importanceMin() {
+ // GIVEN the notification represents a colorized foreground service with min importance
+ val entry = buildEntry {
+ setFlag(mContext, Notification.FLAG_FOREGROUND_SERVICE, true)
+ setImportance(NotificationManager.IMPORTANCE_MIN)
+ modifyNotification(mContext).setColorized(true).setColor(Color.WHITE)
+ }
+
+ // THEN the entry is NOT in the fgs section
+ assertFalse(sectioner.isInSection(entry))
+ }
+
+ @Test
+ fun testDiscludeNonFGSInSection() {
+ // GIVEN the notification represents a colorized notification with high importance that
+ // is NOT a foreground service
+ val entry = buildEntry {
+ setImportance(NotificationManager.IMPORTANCE_HIGH)
+ setFlag(mContext, Notification.FLAG_FOREGROUND_SERVICE, false)
+ modifyNotification(mContext).setColorized(false)
+ }
+
+ // THEN the entry is NOT in the fgs section
+ assertFalse(sectioner.isInSection(entry))
+ }
+
+ @Test
+ fun testIncludeCallInSection_importanceDefault() {
+ // GIVEN the notification represents a call with > min importance
+ val entry = buildEntry {
+ setImportance(NotificationManager.IMPORTANCE_DEFAULT)
+ modifyNotification(mContext).setStyle(makeCallStyle())
+ }
+
+ // THEN the entry is in the fgs section
+ assertTrue(sectioner.isInSection(entry))
+ }
+
+ @Test
+ fun testDiscludeCallInSection_importanceMin() {
+ // GIVEN the notification represents a call with min importance
+ val entry = buildEntry {
+ setImportance(NotificationManager.IMPORTANCE_MIN)
+ modifyNotification(mContext).setStyle(makeCallStyle())
+ }
+
+ // THEN the entry is NOT in the fgs section
+ assertFalse(sectioner.isInSection(entry))
+ }
+
+ @Test
+ @EnableFlags(PromotedNotificationUi.FLAG_NAME)
+ fun testIncludePromotedOngoingInSection_flagEnabled() {
+ // GIVEN the notification has FLAG_PROMOTED_ONGOING
+ val entry = buildEntry { setFlag(mContext, Notification.FLAG_PROMOTED_ONGOING, true) }
+
+ // THEN the entry is in the fgs section
+ assertTrue(sectioner.isInSection(entry))
+ }
+
+ @Test
+ @DisableFlags(PromotedNotificationUi.FLAG_NAME)
+ fun testDiscludePromotedOngoingInSection_flagDisabled() {
+ // GIVEN the notification has FLAG_PROMOTED_ONGOING
+ val entry = buildEntry { setFlag(mContext, Notification.FLAG_PROMOTED_ONGOING, true) }
+
+ // THEN the entry is NOT in the fgs section
+ assertFalse(sectioner.isInSection(entry))
+ }
+
+ @Test
+ @EnableFlags(PromotedNotificationUi.FLAG_NAME)
+ fun promoterSelectsPromotedOngoing_flagEnabled() {
+ val promoter: NotifPromoter = withArgCaptor { verify(notifPipeline).addPromoter(capture()) }
+
+ // GIVEN the notification has FLAG_PROMOTED_ONGOING
+ val entry = buildEntry { setFlag(mContext, Notification.FLAG_PROMOTED_ONGOING, true) }
+
+ // THEN the entry is promoted to top level
+ assertTrue(promoter.shouldPromoteToTopLevel(entry))
+ }
+
+ @Test
+ @EnableFlags(PromotedNotificationUi.FLAG_NAME)
+ fun promoterIgnoresNonPromotedOngoing_flagEnabled() {
+ val promoter: NotifPromoter = withArgCaptor { verify(notifPipeline).addPromoter(capture()) }
+
+ // GIVEN the notification does not have FLAG_PROMOTED_ONGOING
+ val entry = buildEntry { setFlag(mContext, Notification.FLAG_PROMOTED_ONGOING, false) }
+
+ // THEN the entry is NOT promoted to top level
+ assertFalse(promoter.shouldPromoteToTopLevel(entry))
+ }
+
+ @Test
+ @DisableFlags(PromotedNotificationUi.FLAG_NAME)
+ fun noPromoterAdded_flagDisabled() {
+ verify(notifPipeline, never()).addPromoter(any())
+ }
+
+ private fun makeCallStyle(): Notification.CallStyle {
+ val pendingIntent =
+ PendingIntent.getBroadcast(mContext, 0, Intent("action"), PendingIntent.FLAG_IMMUTABLE)
+ val person = Person.Builder().setName("person").build()
+ return Notification.CallStyle.forOngoingCall(person, pendingIntent)
+ }
+
+ companion object {
+ private const val NOTIF_USER_ID = 0
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractorTest.kt
index ee4d0990d38f..ee698ae20adb 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractorTest.kt
@@ -32,8 +32,12 @@ import com.android.systemui.statusbar.notification.data.repository.activeNotific
import com.android.systemui.statusbar.notification.data.repository.notificationsKeyguardViewStateRepository
import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor
import com.android.systemui.statusbar.notification.domain.interactor.headsUpNotificationIconInteractor
+import com.android.systemui.statusbar.notification.promoted.domain.interactor.aodPromotedNotificationInteractor
+import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel
+import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel.Style.Base
import com.android.systemui.statusbar.notification.shared.byIsAmbient
import com.android.systemui.statusbar.notification.shared.byIsLastMessageFromReply
+import com.android.systemui.statusbar.notification.shared.byIsPromoted
import com.android.systemui.statusbar.notification.shared.byIsPulsing
import com.android.systemui.statusbar.notification.shared.byIsRowDismissed
import com.android.systemui.statusbar.notification.shared.byIsSilent
@@ -58,12 +62,14 @@ class NotificationIconsInteractorTest : SysuiTestCase() {
private val testScope = kosmos.testScope
private val activeNotificationListRepository = kosmos.activeNotificationListRepository
private val notificationsKeyguardInteractor = kosmos.notificationsKeyguardInteractor
+ private val aodPromotedNotificationInteractor = kosmos.aodPromotedNotificationInteractor
private val underTest =
NotificationIconsInteractor(
kosmos.activeNotificationsInteractor,
kosmos.bubblesOptional,
kosmos.headsUpNotificationIconInteractor,
+ kosmos.aodPromotedNotificationInteractor,
kosmos.notificationsKeyguardViewStateRepository,
)
@@ -141,6 +147,22 @@ class NotificationIconsInteractorTest : SysuiTestCase() {
notificationsKeyguardInteractor.setNotificationsFullyHidden(true)
assertThat(filteredSet).comparingElementsUsing(byIsPulsing).contains(true)
}
+
+ @Test
+ fun filteredEntrySet_showAodPromoted() {
+ testScope.runTest {
+ val filteredSet by collectLastValue(underTest.filteredNotifSet(showAodPromoted = true))
+ assertThat(filteredSet).comparingElementsUsing(byIsPromoted).contains(true)
+ }
+ }
+
+ @Test
+ fun filteredEntrySet_noAodPromoted() {
+ testScope.runTest {
+ val filteredSet by collectLastValue(underTest.filteredNotifSet(showAodPromoted = false))
+ assertThat(filteredSet).comparingElementsUsing(byIsPromoted).doesNotContain(true)
+ }
+ }
}
@SmallTest
@@ -326,4 +348,12 @@ private val testIcons =
activeNotificationModel(key = "notif5", isLastMessageFromReply = true),
activeNotificationModel(key = "notif6", isSuppressedFromStatusBar = true),
activeNotificationModel(key = "notif7", isPulsing = true),
+ activeNotificationModel(key = "notif8", promotedContent = promotedContent("notif8", Base)),
)
+
+private fun promotedContent(
+ key: String,
+ style: PromotedNotificationContentModel.Style,
+): PromotedNotificationContentModel {
+ return PromotedNotificationContentModel.Builder(key).apply { this.style = style }.build()
+}
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/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/shared/TestActiveNotificationModel.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/shared/TestActiveNotificationModel.kt
index 16c5c8a98253..531b30b9547a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/shared/TestActiveNotificationModel.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/shared/TestActiveNotificationModel.kt
@@ -33,7 +33,12 @@ val byIsRowDismissed: Correspondence<ActiveNotificationModel, Boolean> =
val byIsLastMessageFromReply: Correspondence<ActiveNotificationModel, Boolean> =
Correspondence.transforming(
{ it?.isLastMessageFromReply },
- "has an isLastMessageFromReply value of"
+ "has an isLastMessageFromReply value of",
)
val byIsPulsing: Correspondence<ActiveNotificationModel, Boolean> =
Correspondence.transforming({ it?.isPulsing }, "has an isPulsing value of")
+val byIsPromoted: Correspondence<ActiveNotificationModel, Boolean> =
+ Correspondence.transforming(
+ { it?.promotedContent != null },
+ "has (or doesn't have) a promoted content model",
+ )
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 6381b4e0fef7..d570f18e35d8 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,12 +57,11 @@ 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/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
index 6f785a3731e1..dde6e2ee1866 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
@@ -65,11 +65,9 @@ import com.android.keyguard.KeyguardMessageArea;
import com.android.keyguard.KeyguardMessageAreaController;
import com.android.keyguard.KeyguardSecurityModel;
import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.keyguard.KeyguardUpdateMonitorCallback;
import com.android.keyguard.ViewMediatorCallback;
import com.android.systemui.Flags;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor;
import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor;
import com.android.systemui.bouncer.domain.interactor.BouncerInteractor;
import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerCallbackInteractor;
@@ -146,7 +144,6 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase {
@Mock private SysuiStatusBarStateController mStatusBarStateController;
@Mock private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
@Mock private View mNotificationContainer;
- @Mock private KeyguardBypassController mBypassController;
@Mock private KeyguardMessageAreaController.Factory mKeyguardMessageAreaFactory;
@Mock private KeyguardMessageAreaController mKeyguardMessageAreaController;
@Mock private KeyguardMessageArea mKeyguardMessageArea;
@@ -158,7 +155,6 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase {
@Mock private PrimaryBouncerCallbackInteractor mPrimaryBouncerCallbackInteractor;
@Mock private PrimaryBouncerInteractor mPrimaryBouncerInteractor;
@Mock private AlternateBouncerInteractor mAlternateBouncerInteractor;
- @Mock private UdfpsOverlayInteractor mUdfpsOverlayInteractor;
@Mock private ActivityStarter mActivityStarter;
@Mock private BouncerView mBouncerView;
@Mock private BouncerViewDelegate mBouncerViewDelegate;
@@ -167,7 +163,6 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase {
@Mock private NotificationShadeWindowView mNotificationShadeWindowView;
@Mock private WindowInsetsController mWindowInsetsController;
@Mock private TaskbarDelegate mTaskbarDelegate;
- @Mock private StatusBarKeyguardViewManager.KeyguardViewManagerCallback mCallback;
@Mock private SelectedUserInteractor mSelectedUserInteractor;
@Mock private DeviceEntryInteractor mDeviceEntryInteractor;
@Mock private SceneInteractor mSceneInteractor;
@@ -190,8 +185,6 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase {
private KeyguardTransitionInteractor mKeyguardTransitionInteractor;
@Captor
private ArgumentCaptor<OnBackInvokedCallback> mBackCallbackCaptor;
- @Captor
- private ArgumentCaptor<KeyguardUpdateMonitorCallback> mKeyguardUpdateMonitorCallback;
@Mock
private KeyguardDismissActionInteractor mKeyguardDismissActionInteractor;
@@ -227,7 +220,6 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase {
mock(DockManager.class),
mNotificationShadeWindowController,
mKeyguardStateController,
- mKeyguardMessageAreaFactory,
Optional.of(mSysUiUnfoldComponent),
() -> mShadeController,
mLatencyTracker,
@@ -236,7 +228,6 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase {
mPrimaryBouncerInteractor,
mBouncerView,
mAlternateBouncerInteractor,
- mUdfpsOverlayInteractor,
mActivityStarter,
mKeyguardTransitionInteractor,
mock(KeyguardDismissTransitionInteractor.class),
@@ -732,7 +723,6 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase {
mock(DockManager.class),
mock(NotificationShadeWindowController.class),
mKeyguardStateController,
- mKeyguardMessageAreaFactory,
Optional.of(mSysUiUnfoldComponent),
() -> mShadeController,
mLatencyTracker,
@@ -741,7 +731,6 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase {
mPrimaryBouncerInteractor,
mBouncerView,
mAlternateBouncerInteractor,
- mUdfpsOverlayInteractor,
mActivityStarter,
mock(KeyguardTransitionInteractor.class),
mock(KeyguardDismissTransitionInteractor.class),
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/window/ui/viewmodel/WindowRootViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/window/ui/viewmodel/WindowRootViewModelTest.kt
index a023b3c430ad..3da4f29a6fcb 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/window/ui/viewmodel/WindowRootViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/window/ui/viewmodel/WindowRootViewModelTest.kt
@@ -23,14 +23,12 @@ import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.kosmos.testScope
-import com.android.systemui.lifecycle.activateIn
import com.android.systemui.testKosmos
import com.android.systemui.window.data.repository.fakeWindowRootViewBlurRepository
import com.android.systemui.window.data.repository.windowRootViewBlurRepository
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
@@ -43,11 +41,6 @@ class WindowRootViewModelTest : SysuiTestCase() {
val underTest by lazy { kosmos.windowRootViewModel }
- @Before
- fun setup() {
- underTest.activateIn(testScope)
- }
-
@Test
fun bouncerTransitionChangesWindowBlurRadius() =
testScope.runTest {
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..61d6a9046144
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/drawable/pin_bouncer_confirm.xml
@@ -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.
+ -->
+
+<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="#F7DAEE"
+ android:fillType="evenOdd"
+ android:pathData="M20.76,19C21.65,19 22.096,17.924 21.467,17.294L19.284,15.105C18.895,14.716 18.895,14.085 19.285,13.695C19.674,13.306 20.306,13.306 20.695,13.695L26.293,19.293C26.683,19.683 26.683,20.317 26.293,20.707L20.705,26.295C20.315,26.685 19.683,26.686 19.292,26.298C18.9,25.907 18.898,25.272 19.29,24.88L21.463,22.707C22.093,22.077 21.647,21 20.756,21H10C9.448,21 9,20.552 9,20C9,19.448 9.448,19 10,19H20.76ZM32,26C32,26.552 31.552,27 31,27C30.448,27 30,26.552 30,26V14C30,13.448 30.448,13 31,13C31.552,13 32,13.448 32,14V26Z"
+ android:strokeColor="#F7DAEE"
+ android:strokeLineCap="round"
+ android:strokeLineJoin="round"
+ android:strokeWidth="2" />
+</vector>
diff --git a/packages/SystemUI/res-keyguard/drawable/pin_bouncer_delete.xml b/packages/SystemUI/res-keyguard/drawable/pin_bouncer_delete.xml
new file mode 100644
index 000000000000..044656d6fc7d
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/drawable/pin_bouncer_delete.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="#ECDFE5"
+ android:pathData="M18.792,26.5L23.333,21.958L27.875,26.5L29.875,24.542L25.292,20L29.792,15.458L27.833,13.5L23.333,18.042L18.792,13.5L16.792,15.458L21.375,20L16.792,24.542L18.792,26.5ZM14.708,33.333C14.292,33.333 13.875,33.236 13.458,33.042C13.069,32.847 12.75,32.569 12.5,32.208L3.333,20L12.458,7.792C12.708,7.431 13.028,7.153 13.417,6.958C13.833,6.764 14.264,6.667 14.708,6.667H33.917C34.694,6.667 35.347,6.944 35.875,7.5C36.431,8.028 36.708,8.681 36.708,9.458V30.542C36.708,31.319 36.431,31.986 35.875,32.542C35.347,33.069 34.694,33.333 33.917,33.333H14.708Z" />
+</vector>
diff --git a/packages/SystemUI/res/layout/battery_status_chip.xml b/packages/SystemUI/res/layout/battery_status_chip.xml
index 74371839e247..7399651d4248 100644
--- a/packages/SystemUI/res/layout/battery_status_chip.xml
+++ b/packages/SystemUI/res/layout/battery_status_chip.xml
@@ -24,21 +24,13 @@
<LinearLayout
android:id="@+id/rounded_container"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:minHeight="@dimen/ongoing_appops_chip_height"
- android:layout_gravity="center"
- android:background="@drawable/statusbar_chip_bg"
- android:clipToOutline="true"
- android:gravity="center"
- android:maxWidth="@dimen/ongoing_appops_chip_max_width"
- android:minWidth="@dimen/ongoing_appops_chip_min_width">
+ style="@style/StatusBar.EventChip">
<com.android.systemui.battery.BatteryMeterView
android:id="@+id/battery_meter_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_marginHorizontal="10dp" />
+ android:layout_marginHorizontal="@dimen/ongoing_appops_chip_content_horizontal_margin" />
</LinearLayout>
</merge> \ No newline at end of file
diff --git a/packages/SystemUI/res/layout/bindable_status_bar_compose_icon.xml b/packages/SystemUI/res/layout/bindable_status_bar_compose_icon.xml
new file mode 100644
index 000000000000..fa9318bc151c
--- /dev/null
+++ b/packages/SystemUI/res/layout/bindable_status_bar_compose_icon.xml
@@ -0,0 +1,33 @@
+<?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.
+ -->
+
+<!-- Base layout that provides a single bindable compose view -->
+<com.android.systemui.statusbar.pipeline.shared.ui.view.SingleBindableStatusBarComposeIconView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:gravity="center_vertical"
+ >
+
+ <androidx.compose.ui.platform.ComposeView
+ android:id="@+id/compose_view"
+ android:layout_height="@dimen/status_bar_bindable_icon_size"
+ android:layout_width="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:padding="4sp"
+ />
+
+</com.android.systemui.statusbar.pipeline.shared.ui.view.SingleBindableStatusBarComposeIconView>
diff --git a/packages/SystemUI/res/layout/magic_action_button.xml b/packages/SystemUI/res/layout/magic_action_button.xml
index 82d8d7043ff0..381d6b17dec5 100644
--- a/packages/SystemUI/res/layout/magic_action_button.xml
+++ b/packages/SystemUI/res/layout/magic_action_button.xml
@@ -1,4 +1,5 @@
-<Button xmlns:android="http://schemas.android.com/apk/res/android"
+<com.android.systemui.statusbar.notification.row.MagicActionButton
+ xmlns:android="http://schemas.android.com/apk/res/android"
style="@android:style/Widget.Material.Button"
android:layout_width="wrap_content"
android:layout_height="@dimen/magic_action_button_touch_target_height"
diff --git a/packages/SystemUI/res/layout/media_session_view.xml b/packages/SystemUI/res/layout/media_session_view.xml
index 109e63c6167a..4472373f99a6 100644
--- a/packages/SystemUI/res/layout/media_session_view.xml
+++ b/packages/SystemUI/res/layout/media_session_view.xml
@@ -93,7 +93,7 @@
android:layout_width="0dp"
android:layout_height="0dp"
android:orientation="vertical"
- app:layout_constraintGuide_end="@dimen/qs_media_session_collapsed_guideline" />
+ app:layout_constraintGuide_end="@dimen/qs_media_session_collapsed_legacy_guideline" />
<!-- App icon -->
<com.android.internal.widget.CachingIconView
diff --git a/packages/SystemUI/res/layout/promoted_notification_info.xml b/packages/SystemUI/res/layout/promoted_notification_info.xml
index 5d170a98a806..2e0a0ca1185c 100644
--- a/packages/SystemUI/res/layout/promoted_notification_info.xml
+++ b/packages/SystemUI/res/layout/promoted_notification_info.xml
@@ -323,31 +323,43 @@ asked for it -->
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="60dp"
+ android:layout_marginTop="@dimen/notification_importance_button_separation"
android:gravity="center_vertical"
- android:paddingStart="4dp"
- android:paddingEnd="4dp"
+ android:background="@drawable/rounded_corners"
+ android:backgroundTint="@androidprv:color/materialColorPrimaryContainer"
>
<TextView
- android:id="@+id/promoted_demote"
- android:text="@string/notification_inline_disable_promotion"
- android:layout_width="wrap_content"
+ android:id="@+id/promoted_explain_title"
+ android:text="@string/live_notifications_title"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentStart="true"
android:gravity="start|center_vertical"
- android:minWidth="@dimen/notification_importance_toggle_size"
- android:minHeight="@dimen/notification_importance_toggle_size"
- android:maxWidth="200dp"
+ android:padding="16dp"
+ android:color="@androidprv:color/materialColorOnPrimary"
style="@style/TextAppearance.NotificationInfo.Button"/>
<TextView
- android:id="@+id/promoted_dismiss"
- android:text="@string/notification_inline_dismiss"
+ android:id="@+id/promoted_explain"
+ android:text="@string/live_notifications_desc"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_below="@id/promoted_explain_title"
+ android:gravity="start|center_vertical"
+ android:maxWidth="200dp"
+ android:padding="16dp"
+ style="@style/TextAppearance.NotificationImportanceDetail"/>
+ <TextView
+ android:id="@+id/promoted_demote"
+ android:text="@string/notification_inline_disable_promotion"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
+ android:layout_below="@id/promoted_explain"
android:layout_alignParentEnd="true"
- android:gravity="end|center_vertical"
+ android:padding="16dp"
+ android:gravity="start|center_vertical"
android:minWidth="@dimen/notification_importance_toggle_size"
android:minHeight="@dimen/notification_importance_toggle_size"
- android:maxWidth="125dp"
+ android:maxWidth="200dp"
style="@style/TextAppearance.NotificationInfo.Button"/>
</RelativeLayout>
diff --git a/packages/SystemUI/res/layout/status_bar_event_chip_compose.xml b/packages/SystemUI/res/layout/status_bar_event_chip_compose.xml
new file mode 100644
index 000000000000..ff96ab15cd15
--- /dev/null
+++ b/packages/SystemUI/res/layout/status_bar_event_chip_compose.xml
@@ -0,0 +1,34 @@
+<?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.
+ -->
+
+<merge xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:layout_gravity="center_vertical|end">
+
+ <LinearLayout
+ android:id="@+id/rounded_container"
+ style="@style/StatusBar.EventChip">
+
+ <!-- Stub for the composable -->
+ <androidx.compose.ui.platform.ComposeView
+ android:id="@+id/compose_view"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginHorizontal="@dimen/ongoing_appops_chip_content_horizontal_margin" />
+
+ </LinearLayout>
+</merge>
diff --git a/packages/SystemUI/res/layout/volume_dialog.xml b/packages/SystemUI/res/layout/volume_dialog.xml
index 8ad99abccdfe..889aefed0c5c 100644
--- a/packages/SystemUI/res/layout/volume_dialog.xml
+++ b/packages/SystemUI/res/layout/volume_dialog.xml
@@ -14,7 +14,6 @@
limitations under the License.
-->
<androidx.constraintlayout.motion.widget.MotionLayout xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/volume_dialog"
android:layout_width="match_parent"
@@ -30,42 +29,47 @@
android:layout_marginTop="@dimen/volume_dialog_background_top_margin"
android:layout_marginBottom="@dimen/volume_dialog_background_vertical_margin"
android:background="@drawable/volume_dialog_background"
- app:layout_constraintBottom_toBottomOf="@id/volume_dialog_settings"
+ app:layout_constraintBottom_toBottomOf="@id/volume_dialog_bottom_section_container"
app:layout_constraintEnd_toEndOf="@id/volume_dialog_main_slider_container"
app:layout_constraintStart_toStartOf="@id/volume_dialog_main_slider_container"
app:layout_constraintTop_toTopOf="@id/volume_dialog_main_slider_container" />
- <include
- android:id="@id/volume_ringer_drawer"
- layout="@layout/volume_ringer_drawer"
+ <FrameLayout
+ android:id="@+id/volume_dialog_top_section_container"
android:layout_width="0dp"
android:layout_height="0dp"
- android:layout_marginEnd="@dimen/volume_dialog_ringer_drawer_diff_end_margin"
android:layout_marginBottom="@dimen/volume_dialog_components_spacing"
+ android:clipChildren="false"
app:layout_constraintBottom_toTopOf="@id/volume_dialog_main_slider_container"
app:layout_constraintEnd_toEndOf="@id/volume_dialog_main_slider_container"
+ app:layout_constraintHeight_default="spread"
app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintTop_toTopOf="parent" />
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintWidth_default="spread">
+
+ <include layout="@layout/volume_dialog_top_section" />
+ </FrameLayout>
<include
android:id="@+id/volume_dialog_main_slider_container"
layout="@layout/volume_dialog_slider" />
- <ImageButton
- android:id="@+id/volume_dialog_settings"
- android:layout_width="@dimen/volume_dialog_button_size"
- android:layout_height="@dimen/volume_dialog_button_size"
+ <FrameLayout
+ android:id="@+id/volume_dialog_bottom_section_container"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
android:layout_marginTop="@dimen/volume_dialog_components_spacing"
- android:background="@drawable/ripple_drawable_20dp"
- android:contentDescription="@string/accessibility_volume_settings"
- android:scaleType="centerInside"
- android:soundEffectsEnabled="false"
- android:tint="@androidprv:color/materialColorPrimary"
+ android:clipChildren="false"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="@id/volume_dialog_main_slider_container"
+ app:layout_constraintHeight_default="wrap"
app:layout_constraintStart_toStartOf="@id/volume_dialog_main_slider_container"
app:layout_constraintTop_toBottomOf="@id/volume_dialog_main_slider_container"
- app:layout_constraintVertical_bias="0" />
+ app:layout_constraintVertical_bias="0"
+ app:layout_constraintWidth_default="wrap">
+
+ <include layout="@layout/volume_dialog_bottom_section" />
+ </FrameLayout>
<LinearLayout
android:id="@+id/volume_dialog_floating_sliders_container"
diff --git a/packages/SystemUI/res/layout/volume_dialog_bottom_section.xml b/packages/SystemUI/res/layout/volume_dialog_bottom_section.xml
new file mode 100644
index 000000000000..b94c430e011c
--- /dev/null
+++ b/packages/SystemUI/res/layout/volume_dialog_bottom_section.xml
@@ -0,0 +1,26 @@
+<!--
+ 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.
+-->
+<ImageButton xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ android:id="@+id/volume_dialog_settings"
+ android:layout_width="@dimen/volume_dialog_button_size"
+ android:layout_height="@dimen/volume_dialog_button_size"
+ android:layout_gravity="center"
+ android:background="@drawable/ripple_drawable_20dp"
+ android:contentDescription="@string/accessibility_volume_settings"
+ android:scaleType="centerInside"
+ android:soundEffectsEnabled="false"
+ android:tint="@androidprv:color/materialColorPrimary" /> \ No newline at end of file
diff --git a/packages/SystemUI/res/layout/volume_ringer_drawer.xml b/packages/SystemUI/res/layout/volume_dialog_top_section.xml
index 8f51dbca2774..4fc20e218c5e 100644
--- a/packages/SystemUI/res/layout/volume_ringer_drawer.xml
+++ b/packages/SystemUI/res/layout/volume_dialog_top_section.xml
@@ -23,16 +23,17 @@
android:clipToPadding="false"
android:gravity="center"
android:layoutDirection="ltr"
+ android:paddingEnd="@dimen/volume_dialog_ringer_drawer_diff_end_margin"
app:layoutDescription="@xml/volume_dialog_ringer_drawer_motion_scene">
<View
android:id="@+id/ringer_buttons_background"
android:layout_width="@dimen/volume_dialog_width"
android:layout_height="0dp"
- android:visibility="gone"
android:layout_marginTop="@dimen/volume_dialog_background_vertical_margin"
android:layout_marginBottom="@dimen/volume_dialog_background_vertical_margin"
- android:background="@drawable/volume_dialog_ringer_background" />
+ android:background="@drawable/volume_dialog_ringer_background"
+ android:visibility="gone" />
<!-- add ringer buttons here -->
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 648e4c2e3ac7..2bac87d01bdd 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1242,8 +1242,11 @@
<dimen name="min_window_blur_radius">1px</dimen>
<dimen name="max_window_blur_radius">23px</dimen>
+ <!-- Blur radius of the Notification Shade content (notifications, footer, shelf) -->
+ <dimen name="max_shade_content_blur_radius">@dimen/max_window_blur_radius</dimen>
+
<!-- Blur radius behind Notification Shade -->
- <dimen name="max_shade_window_blur_radius">60dp</dimen>
+ <dimen name="max_shade_window_blur_radius">34dp</dimen>
<!-- How much into a DisplayCutout's bounds we can go, on each side -->
<dimen name="display_cutout_margin_consumption">0px</dimen>
@@ -1254,6 +1257,8 @@
<dimen name="ongoing_appops_chip_side_padding">8dp</dimen>
<!-- Margin between icons of Ongoing App Ops chip -->
<dimen name="ongoing_appops_chip_icon_margin">4dp</dimen>
+ <!-- Side margins for the content of an appops chip -->
+ <dimen name="ongoing_appops_chip_content_horizontal_margin">10dp</dimen>
<!-- Icon size of Ongoing App Ops chip -->
<dimen name="ongoing_appops_chip_icon_size">16sp</dimen>
<!-- Radius of Ongoing App Ops chip corners -->
@@ -1305,7 +1310,8 @@
<dimen name="qs_media_seekbar_progress_amplitude">1.5dp</dimen>
<dimen name="qs_media_seekbar_progress_phase">8dp</dimen>
<dimen name="qs_media_seekbar_progress_stroke_width">2dp</dimen>
- <dimen name="qs_media_session_collapsed_guideline">144dp</dimen>
+ <dimen name="qs_media_session_collapsed_legacy_guideline">144dp</dimen>
+ <dimen name="qs_media_session_collapsed_guideline">168dp</dimen>
<!-- Size of Smartspace media recommendations cards in the QSPanel carousel -->
<dimen name="qs_media_rec_default_width">380dp</dimen>
@@ -2136,18 +2142,25 @@
<dimen name="volume_dialog_width">60dp</dimen>
<dimen name="volume_dialog_background_corner_radius">30dp</dimen>
- <dimen name="volume_dialog_background_vertical_margin">-10dp</dimen>
+ <dimen name="volume_dialog_background_vertical_margin">
+ @dimen/volume_dialog_buttons_margin_negative
+ </dimen>
<!-- top margin covers half the ringer button + components spacing -->
<dimen name="volume_dialog_background_top_margin">-28dp</dimen>
+ <dimen name="volume_dialog_window_margin">14dp</dimen>
<dimen name="volume_dialog_components_spacing">8dp</dimen>
<dimen name="volume_dialog_floating_sliders_spacing">8dp</dimen>
<dimen name="volume_dialog_floating_sliders_vertical_padding">10dp</dimen>
- <dimen name="volume_dialog_floating_sliders_vertical_padding_negative">-10dp</dimen>
+ <dimen name="volume_dialog_floating_sliders_vertical_padding_negative">
+ @dimen/volume_dialog_buttons_margin_negative
+ </dimen>
<dimen name="volume_dialog_floating_sliders_horizontal_padding">4dp</dimen>
<dimen name="volume_dialog_button_size">40dp</dimen>
<dimen name="volume_dialog_slider_width">52dp</dimen>
<dimen name="volume_dialog_slider_height">254dp</dimen>
+ <dimen name="volume_dialog_buttons_margin">10dp</dimen>
+ <dimen name="volume_dialog_buttons_margin_negative">-10dp</dimen>
<!--
A primary goal of this margin is to vertically constraint slider height in the landscape
orientation when the vertical space is limited
@@ -2160,7 +2173,11 @@
<dimen name="volume_dialog_background_square_corner_radius">12dp</dimen>
- <dimen name="volume_dialog_ringer_drawer_margin">10dp</dimen>
+ <dimen name="volume_dialog_ringer_drawer_margin">@dimen/volume_dialog_buttons_margin</dimen>
+ <!--
+ (volume_dialog_slider_width - volume_dialog_button_size) / 2
+ This centers ringer drawer against the volume slider
+ -->
<dimen name="volume_dialog_ringer_drawer_diff_end_margin">6dp</dimen>
<dimen name="volume_dialog_ringer_drawer_button_size">@dimen/volume_dialog_button_size</dimen>
<dimen name="volume_dialog_ringer_drawer_button_icon_radius">10dp</dimen>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 86292039d93d..e077b41a6f59 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -2098,7 +2098,13 @@
<string name="notification_inline_dismiss">Dismiss</string>
<!-- [CHAR LIMIT=30] Text shown in button used to prevent app from showing Live Updates, for this notification and all future ones -->
- <string name="notification_inline_disable_promotion">Don\'t show again</string>
+ <string name="notification_inline_disable_promotion">Don\'t show as pinned</string>
+
+ <!-- Text accompanying the "Show live updates" switch explaining the purpose of the setting -->
+ <string name="live_notifications_title">Showing Live Updates</string>
+
+ <!-- Text accompanying the "Show live updates" switch explaining the purpose of the setting -->
+ <string name="live_notifications_desc">Pinned notifications display live info from apps, and always appear on the status bar and lock screen</string>
<!-- Notification: Control panel: Label that displays when the app's notifications cannot be blocked. -->
<string name="notification_unblockable_desc">These notifications can\'t be modified.</string>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 4961a7ece69a..8f808d389203 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -93,6 +93,19 @@
<item name="android:textColor">?android:attr/colorPrimary</item>
</style>
+ <style name="StatusBar.EventChip">
+ <item name="android:orientation">horizontal</item>
+ <item name="android:layout_width">wrap_content</item>
+ <item name="android:layout_height">wrap_content</item>
+ <item name="android:layout_gravity">center</item>
+ <item name="android:gravity">center</item>
+ <item name="android:clipToOutline">true</item>
+ <item name="android:background">@drawable/statusbar_chip_bg</item>
+ <item name="android:minHeight">@dimen/ongoing_appops_chip_height</item>
+ <item name="android:maxWidth">@dimen/ongoing_appops_chip_max_width</item>
+ <item name="android:minWidth">@dimen/ongoing_appops_chip_min_width</item>
+ </style>
+
<style name="Chipbar" />
<style name="Chipbar.Text" parent="@*android:style/TextAppearance.DeviceDefault.Notification.Title">
diff --git a/packages/SystemUI/res/xml/media_session_collapsed.xml b/packages/SystemUI/res/xml/media_session_collapsed.xml
index 66c54a389c8e..b5efd04eeba9 100644
--- a/packages/SystemUI/res/xml/media_session_collapsed.xml
+++ b/packages/SystemUI/res/xml/media_session_collapsed.xml
@@ -64,6 +64,13 @@
app:layout_constraintBottom_toBottomOf="@+id/album_art" />
<Constraint
+ android:id="@+id/action_button_guideline"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:orientation="vertical"
+ app:layout_constraintGuide_end="@dimen/qs_media_session_collapsed_legacy_guideline" />
+
+ <Constraint
android:id="@+id/header_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
diff --git a/packages/SystemUI/res/xml/volume_dialog_constraint_set.xml b/packages/SystemUI/res/xml/volume_dialog_constraint_set.xml
index a8f616c2427d..dcc5d4f6635f 100644
--- a/packages/SystemUI/res/xml/volume_dialog_constraint_set.xml
+++ b/packages/SystemUI/res/xml/volume_dialog_constraint_set.xml
@@ -8,7 +8,7 @@
android:layout_width="@dimen/volume_dialog_slider_width"
android:layout_height="0dp"
android:layout_marginTop="@dimen/volume_dialog_slider_vertical_margin"
- android:layout_marginEnd="@dimen/volume_dialog_components_spacing"
+ android:layout_marginEnd="@dimen/volume_dialog_window_margin"
android:layout_marginBottom="@dimen/volume_dialog_slider_vertical_margin"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
diff --git a/packages/SystemUI/res/xml/volume_dialog_half_folded_constraint_set.xml b/packages/SystemUI/res/xml/volume_dialog_half_folded_constraint_set.xml
index b4d8ae791f36..3a5e41d5781a 100644
--- a/packages/SystemUI/res/xml/volume_dialog_half_folded_constraint_set.xml
+++ b/packages/SystemUI/res/xml/volume_dialog_half_folded_constraint_set.xml
@@ -8,7 +8,7 @@
android:layout_width="@dimen/volume_dialog_slider_width"
android:layout_height="0dp"
android:layout_marginTop="@dimen/volume_dialog_slider_vertical_margin"
- android:layout_marginEnd="@dimen/volume_dialog_components_spacing"
+ android:layout_marginEnd="@dimen/volume_dialog_window_margin"
android:layout_marginBottom="@dimen/volume_dialog_slider_vertical_margin"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
diff --git a/packages/SystemUI/res/xml/volume_dialog_ringer_drawer_motion_scene.xml b/packages/SystemUI/res/xml/volume_dialog_ringer_drawer_motion_scene.xml
index 1607121f230f..4c3dbd78cc3e 100644
--- a/packages/SystemUI/res/xml/volume_dialog_ringer_drawer_motion_scene.xml
+++ b/packages/SystemUI/res/xml/volume_dialog_ringer_drawer_motion_scene.xml
@@ -20,30 +20,9 @@
android:id="@+id/close_to_open_transition"
app:constraintSetEnd="@+id/volume_dialog_ringer_drawer_open"
app:constraintSetStart="@+id/volume_dialog_ringer_drawer_close"
- app:transitionEasing="cubic(0.05, 0.7, 0.1, 1.0)"
- app:duration="400">
- </Transition>
-
- <ConstraintSet android:id="@+id/volume_dialog_ringer_drawer_close">
- <Constraint
- android:id="@+id/volume_ringer_drawer"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- app:layout_constraintTop_toTopOf="parent"
- app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintEnd_toEndOf="parent"/>
- </ConstraintSet>
-
- <ConstraintSet android:id="@+id/volume_dialog_ringer_drawer_open">
- <Constraint
- android:id="@+id/volume_ringer_drawer"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- app:layout_constraintTop_toTopOf="parent"
- app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintEnd_toEndOf="parent"/>
- </ConstraintSet>
+ app:duration="400"
+ app:transitionEasing="cubic(0.05, 0.7, 0.1, 1.0)" />
+ <ConstraintSet android:id="@+id/volume_dialog_ringer_drawer_close" />
+ <ConstraintSet android:id="@+id/volume_dialog_ringer_drawer_open" />
</MotionScene> \ No newline at end of file
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
index 82ac78c6db15..0372a6c6d2c5 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
@@ -20,7 +20,6 @@ import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_2BUTTON;
import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON;
import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL;
-import static com.android.systemui.Flags.glanceableHubBackAction;
import static com.android.systemui.shared.Flags.shadeAllowBackGesture;
import android.annotation.LongDef;
@@ -361,10 +360,6 @@ public class QuickStepContract {
}
// Disable back gesture on the hub, but not when the shade is showing.
if ((sysuiStateFlags & SYSUI_STATE_COMMUNAL_HUB_SHOWING) != 0) {
- // Allow back gesture on Glanceable Hub with back action support.
- if (glanceableHubBackAction()) {
- return false;
- }
// Use QS expanded signal as the notification panel is always considered visible
// expanded when on the lock screen and when opening hub over lock screen. This does
// mean that back gesture is disabled when opening shade over hub while in portrait
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationListener.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationListener.java
index ff6bcdb150f8..fcde508b07a8 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationListener.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationListener.java
@@ -16,6 +16,7 @@
package com.android.systemui.shared.system;
+import android.annotation.Nullable;
import android.graphics.Rect;
import android.os.Bundle;
import android.view.RemoteAnimationTarget;
@@ -42,5 +43,5 @@ public interface RecentsAnimationListener {
* Called when the task of an activity that has been started while the recents animation
* was running becomes ready for control.
*/
- void onTasksAppeared(RemoteAnimationTarget[] app);
+ void onTasksAppeared(RemoteAnimationTarget[] app, @Nullable TransitionInfo transitionInfo);
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
index fbe9edfd6680..245283da75ab 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,13 @@ 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.setImageResource(R.drawable.pin_bouncer_delete);
+ }
mDeleteButton.setVisibility(View.VISIBLE);
mButtons[0] = findViewById(R.id.key0);
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardViewController.java
index 5a9cbce73e4b..892851cd7056 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardViewController.java
@@ -173,6 +173,11 @@ public interface KeyguardViewController {
boolean isBouncerShowing();
/**
+ * Report when the UI is ready for dismissing the whole Keyguard.
+ */
+ void readyForKeyguardDone();
+
+ /**
* Stop showing the alternate bouncer, if showing.
*
* <p>Should be like calling {@link #hideAlternateBouncer(boolean, boolean)} with a {@code true}
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/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/CameraAvailabilityListener.kt b/packages/SystemUI/src/com/android/systemui/CameraAvailabilityListener.kt
index ca83724aaf07..7af37bd720e3 100644
--- a/packages/SystemUI/src/com/android/systemui/CameraAvailabilityListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/CameraAvailabilityListener.kt
@@ -129,6 +129,11 @@ class CameraAvailabilityListener(
listeners.remove(callback)
}
+ fun debugFaceAuth(id: Int) {
+ val info = cameraProtectionInfoList?.getOrNull(id)
+ if (info != null) notifyCameraActive(info)
+ }
+
private fun isExcluded(packageId: String): Boolean {
return excludedPackageIds.contains(packageId)
}
diff --git a/packages/SystemUI/src/com/android/systemui/FaceScanningOverlay.kt b/packages/SystemUI/src/com/android/systemui/FaceScanningOverlay.kt
index dbdf93d81eee..8bb236b91860 100644
--- a/packages/SystemUI/src/com/android/systemui/FaceScanningOverlay.kt
+++ b/packages/SystemUI/src/com/android/systemui/FaceScanningOverlay.kt
@@ -87,7 +87,7 @@ class FaceScanningOverlay(
override fun enableShowProtection(isCameraActive: Boolean) {
val scanningAnimationRequiredWhenCameraActive =
- keyguardUpdateMonitor.isFaceDetectionRunning || authController.isShowing
+ keyguardUpdateMonitor.isFaceDetectionRunning || authController.isShowing || mDebug
val faceAuthSucceeded = keyguardUpdateMonitor.isFaceAuthenticated
val showScanningAnimationNow = scanningAnimationRequiredWhenCameraActive && isCameraActive
if (showScanningAnimationNow == showScanningAnim) {
diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
index da206facd1fb..e7253533aa42 100644
--- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
+++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
@@ -189,7 +189,7 @@ public class ScreenDecorations implements
@VisibleForTesting
protected void showCameraProtection(@NonNull Path protectionPath, @NonNull Rect bounds) {
- if (mFaceScanningFactory.shouldShowFaceScanningAnim()) {
+ if (mDebug || mFaceScanningFactory.shouldShowFaceScanningAnim()) {
DisplayCutoutView overlay = (DisplayCutoutView) getOverlayView(
mFaceScanningViewId);
if (overlay != null) {
@@ -395,6 +395,12 @@ public class ScreenDecorations implements
setupDecorations();
});
}
+
+ if (cmd.getFaceAuthScreen() != null) {
+ mExecutor.execute(() -> {
+ debugTriggerFaceAuth(cmd.getFaceAuthScreen());
+ });
+ }
};
@Override
@@ -630,6 +636,15 @@ public class ScreenDecorations implements
}
}
+ private void debugTriggerFaceAuth(int screen) {
+ DisplayCutoutView overlay = (DisplayCutoutView) getOverlayView(
+ mFaceScanningViewId);
+ if (overlay != null) {
+ overlay.setDebug(true);
+ mCameraListener.debugFaceAuth(screen);
+ }
+ }
+
private void setupDecorations() {
Trace.beginSection("ScreenDecorations#setupDecorations");
setupDecorationsInner();
@@ -1360,6 +1375,7 @@ public class ScreenDecorations implements
final List<Rect> mBounds = new ArrayList();
final Rect mBoundingRect = new Rect();
Rect mTotalBounds = new Rect();
+ boolean mDebug = false;
private int mColor = Color.BLACK;
private int mRotation;
@@ -1378,6 +1394,10 @@ public class ScreenDecorations implements
}
}
+ public void setDebug(boolean debug) {
+ mDebug = debug;
+ }
+
public void setColor(int color) {
if (color == mColor) {
return;
diff --git a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
index e2065f175c79..85f18800f20b 100644
--- a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
@@ -126,7 +126,7 @@ public class SwipeHelper implements Gefingerpoken, Dumpable {
}
};
- private final int mFalsingThreshold;
+ private int mFalsingThreshold;
private boolean mTouchAboveFalsingThreshold;
private boolean mDisableHwLayers;
private final boolean mFadeDependingOnAmountSwiped;
@@ -149,8 +149,7 @@ public class SwipeHelper implements Gefingerpoken, Dumpable {
// Extra long-press!
mLongPressTimeout = (long) (ViewConfiguration.getLongPressTimeout() * 1.5f);
- mDensityScale = resources.getDisplayMetrics().density;
- mFalsingThreshold = resources.getDimensionPixelSize(R.dimen.swipe_helper_falsing_threshold);
+ updateResourceProperties(resources);
mFadeDependingOnAmountSwiped = resources.getBoolean(
R.bool.config_fadeDependingOnAmountSwiped);
mFalsingManager = falsingManager;
@@ -165,6 +164,14 @@ public class SwipeHelper implements Gefingerpoken, Dumpable {
getMaxEscapeAnimDuration() / 1000f);
}
+ /** Update ane properties that depend on Resources */
+ public void updateResourceProperties(Resources resources) {
+ float density = resources.getDisplayMetrics().density;
+ setDensityScale(density);
+ mCallback.onDensityScaleChange(density);
+ mFalsingThreshold = resources.getDimensionPixelSize(R.dimen.swipe_helper_falsing_threshold);
+ }
+
public void setDensityScale(float densityScale) {
mDensityScale = densityScale;
}
@@ -1001,5 +1008,8 @@ public class SwipeHelper implements Gefingerpoken, Dumpable {
* @return If true, the given view is draggable.
*/
default boolean canChildBeDragged(@NonNull View animView) { return true; }
+
+ /** The density scale has changed */
+ void onDensityScaleChange(float density);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/FullscreenMagnificationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/FullscreenMagnificationController.java
index b2f3df60c82b..d7bb667b02a9 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/FullscreenMagnificationController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/FullscreenMagnificationController.java
@@ -352,7 +352,13 @@ public class FullscreenMagnificationController implements ComponentCallbacks {
mTransaction
.addTransactionCommittedListener(
mExecutor,
- this::showBorder)
+ () -> {
+ if (getState() == ENABLING) {
+ // Ensure that we are in the ENABLING process to avoid performing
+ // animation on a null view.
+ mShowBorderRunnable.run();
+ }
+ })
.setPosition(mBorderSurfaceControl, -mBorderOffset, -mBorderOffset)
.setLayer(mBorderSurfaceControl, Integer.MAX_VALUE)
.show(mBorderSurfaceControl)
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java
index c14d28d1c08d..3b6f8f87a1a8 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java
@@ -174,13 +174,14 @@ class WindowMagnificationSettings implements MagnificationGestureDetector.OnGest
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
// Notify the service to update the magnifier scale only when the progress changed is
- // triggered by user interaction on seekbar
- if (fromUser) {
- final float scale = transformProgressToScale(progress);
- // We don't need to update the persisted scale when the seekbar progress is
- // changing. The update should be triggered when the changing is ended.
- mCallback.onMagnifierScale(scale, /* updatePersistence= */ false);
+ // triggered by user interaction on seekbar.
+ if (!fromUser) {
+ return;
}
+ final float scale = transformProgressToScale(progress);
+ // We don't need to update the persisted scale when the seekbar progress is
+ // changing. The update should be triggered when the changing is ended.
+ mCallback.onMagnifierScale(scale, /* updatePersistence= */ false);
}
@Override
@@ -195,7 +196,7 @@ class WindowMagnificationSettings implements MagnificationGestureDetector.OnGest
@Override
public void onUserInteractionFinalized(SeekBar seekBar, @ControlUnitType int control) {
- // Update the Settings persisted scale only when user interaction with seekbar ends
+ // Update the Settings persisted scale only when user interaction with seekbar ends.
final int progress = seekBar.getProgress();
final float scale = transformProgressToScale(progress);
mCallback.onMagnifierScale(scale, /* updatePersistence= */ true);
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/extradim/ExtraDimDialogManager.kt b/packages/SystemUI/src/com/android/systemui/accessibility/extradim/ExtraDimDialogManager.kt
index 3f717e282a61..f24c3be0004c 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/extradim/ExtraDimDialogManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/extradim/ExtraDimDialogManager.kt
@@ -20,6 +20,7 @@ import com.android.systemui.animation.DialogCuj
import com.android.systemui.animation.DialogTransitionAnimator
import com.android.systemui.animation.Expandable
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.statusbar.phone.SystemUIDialog
import javax.inject.Inject
@@ -33,7 +34,7 @@ constructor(
private val extraDimDialogDelegateProvider: Provider<ExtraDimDialogDelegate>,
private val mActivityStarter: ActivityStarter,
private val dialogTransitionAnimator: DialogTransitionAnimator,
- private val mainHandler: Handler,
+ @Main private val mainHandler: Handler,
) {
private var dialog: SystemUIDialog? = null
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuController.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuController.java
index 67aa4ff577b8..f8e4bda15d01 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuController.java
@@ -40,6 +40,7 @@ import com.android.systemui.accessibility.AccessibilityButtonModeObserver;
import com.android.systemui.accessibility.AccessibilityButtonModeObserver.AccessibilityButtonMode;
import com.android.systemui.accessibility.AccessibilityButtonTargetsObserver;
import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.navigationbar.NavigationModeController;
import com.android.systemui.settings.DisplayTracker;
import com.android.systemui.util.settings.SecureSettings;
@@ -114,7 +115,7 @@ public class AccessibilityFloatingMenuController implements
SecureSettings secureSettings,
DisplayTracker displayTracker,
NavigationModeController navigationModeController,
- Handler handler) {
+ @Main Handler handler) {
mContext = context;
mWindowManager = windowManager;
mViewCaptureAwareWindowManager = viewCaptureAwareWindowManager;
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/fontscaling/FontScalingDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/accessibility/fontscaling/FontScalingDialogDelegate.kt
index eaf541d7b559..76b5d823e0b6 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/fontscaling/FontScalingDialogDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/fontscaling/FontScalingDialogDelegate.kt
@@ -88,7 +88,7 @@ constructor(
dialog.setPositiveButton(
R.string.quick_settings_done,
/* onClick = */ null,
- /* dismissOnClick = */ true
+ /* dismissOnClick = */ true,
)
}
@@ -102,7 +102,7 @@ constructor(
labelArray[i] =
context.resources.getString(
com.android.settingslib.R.string.font_scale_percentage,
- (strEntryValues[i].toFloat() * 100).roundToInt()
+ (strEntryValues[i].toFloat() * 100).roundToInt(),
)
}
seekBarWithIconButtonsView.setProgressStateLabels(labelArray)
@@ -132,7 +132,7 @@ constructor(
override fun onUserInteractionFinalized(
seekBar: SeekBar,
- @ControlUnitType control: Int
+ @ControlUnitType control: Int,
) {
if (control == ControlUnitType.BUTTON) {
// The seekbar progress is changed by icon buttons
@@ -216,7 +216,7 @@ constructor(
!systemSettings.putStringForUser(
Settings.System.FONT_SCALE,
strEntryValues[lastProgress.get()],
- userTracker.userId
+ userTracker.userId,
)
) {
title.post { doneButton.isEnabled = true }
@@ -228,13 +228,13 @@ constructor(
if (
secureSettings.getStringForUser(
Settings.Secure.ACCESSIBILITY_FONT_SCALING_HAS_BEEN_CHANGED,
- userTracker.userId
+ userTracker.userId,
) != ON
) {
secureSettings.putStringForUser(
Settings.Secure.ACCESSIBILITY_FONT_SCALING_HAS_BEEN_CHANGED,
ON,
- userTracker.userId
+ userTracker.userId,
)
}
}
@@ -249,7 +249,7 @@ constructor(
title.setTextSize(
TypedValue.COMPLEX_UNIT_PX,
- previewConfigContext.resources.getDimension(R.dimen.dialog_title_text_size)
+ previewConfigContext.resources.getDimension(R.dimen.dialog_title_text_size),
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/back/domain/interactor/BackActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/back/domain/interactor/BackActionInteractor.kt
index 11a6cb9334ae..0b578c65e915 100644
--- a/packages/SystemUI/src/com/android/systemui/back/domain/interactor/BackActionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/back/domain/interactor/BackActionInteractor.kt
@@ -23,9 +23,7 @@ import android.window.OnBackInvokedDispatcher
import android.window.WindowOnBackInvokedDispatcher
import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.systemui.CoreStartable
-import com.android.systemui.Flags.glanceableHubBackAction
import com.android.systemui.Flags.predictiveBackAnimateShade
-import com.android.systemui.communal.domain.interactor.CommunalBackActionInteractor
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.plugins.statusbar.StatusBarStateController
@@ -52,7 +50,6 @@ constructor(
private val windowRootViewVisibilityInteractor: WindowRootViewVisibilityInteractor,
private val shadeBackActionInteractor: ShadeBackActionInteractor,
private val qsController: QuickSettingsController,
- private val communalBackActionInteractor: CommunalBackActionInteractor,
) : CoreStartable {
private var isCallbackRegistered = false
@@ -114,12 +111,6 @@ constructor(
shadeBackActionInteractor.animateCollapseQs(false)
return true
}
- if (glanceableHubBackAction()) {
- if (communalBackActionInteractor.canBeDismissed()) {
- communalBackActionInteractor.onBackPressed()
- return true
- }
- }
if (shouldBackBeHandled()) {
if (shadeBackActionInteractor.canBeCollapsed()) {
// this is the Shade dismiss animation, so make sure QQS closes when it ends.
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricNotificationService.java b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricNotificationService.java
index e5c22677dbcc..1ec7799a0b94 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricNotificationService.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricNotificationService.java
@@ -149,7 +149,7 @@ public class BiometricNotificationService implements CoreStartable {
public BiometricNotificationService(@NonNull @Main Context context,
@NonNull KeyguardUpdateMonitor keyguardUpdateMonitor,
@NonNull KeyguardStateController keyguardStateController,
- @NonNull Handler handler, @NonNull NotificationManager notificationManager,
+ @NonNull @Main Handler handler, @NonNull NotificationManager notificationManager,
@NonNull BiometricNotificationBroadcastReceiver biometricNotificationBroadcastReceiver,
@NonNull Optional<FingerprintReEnrollNotification> fingerprintReEnrollNotification,
@Nullable FingerprintManager fingerprintManager,
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothDetailsContent.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothDetailsContent.kt
index 710fde5c2130..c6644562a9cb 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothDetailsContent.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothDetailsContent.kt
@@ -24,7 +24,7 @@ import androidx.compose.ui.viewinterop.AndroidView
import com.android.systemui.res.R
@Composable
-fun BluetoothDetailsContent(detailsContentViewModel: BluetoothTileDialogViewModel) {
+fun BluetoothDetailsContent(detailsContentViewModel: BluetoothDetailsContentViewModel) {
AndroidView(
modifier = Modifier.fillMaxSize(),
factory = { context ->
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 d873f41309cc..eebcf0b0f0c1 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothDetailsContentManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothDetailsContentManager.kt
@@ -62,7 +62,7 @@ data class DeviceItemClick(val deviceItem: DeviceItem, val clickedView: View, va
class BluetoothDetailsContentManager
@AssistedInject
constructor(
- @Assisted private val initialUiProperties: BluetoothTileDialogViewModel.UiProperties,
+ @Assisted private val initialUiProperties: BluetoothDetailsContentViewModel.UiProperties,
@Assisted private val cachedContentHeight: Int,
@Assisted private val bluetoothTileDialogCallback: BluetoothTileDialogCallback,
@Assisted private val isInDialog: Boolean,
@@ -114,7 +114,7 @@ constructor(
@AssistedFactory
interface Factory {
fun create(
- initialUiProperties: BluetoothTileDialogViewModel.UiProperties,
+ initialUiProperties: BluetoothDetailsContentViewModel.UiProperties,
cachedContentHeight: Int,
dialogCallback: BluetoothTileDialogCallback,
isInDialog: Boolean,
@@ -226,7 +226,7 @@ constructor(
internal fun onBluetoothStateUpdated(
isEnabled: Boolean,
- uiProperties: BluetoothTileDialogViewModel.UiProperties,
+ uiProperties: BluetoothDetailsContentViewModel.UiProperties,
) {
bluetoothToggle.apply {
isChecked = isEnabled
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModel.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothDetailsContentViewModel.kt
index 308c9d10db93..ff2d9efa1b58 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothDetailsContentViewModel.kt
@@ -61,12 +61,9 @@ import kotlinx.coroutines.withContext
/**
* ViewModel for Bluetooth Dialog or Bluetooth Details View after clicking on the Bluetooth QS tile.
- *
- * TODO: b/378513956 Rename this class to BluetoothDetailsContentViewModel, since it's not only used
- * by the dialog view.
*/
@SysUISingleton
-class BluetoothTileDialogViewModel
+class BluetoothDetailsContentViewModel
@Inject
constructor(
private val deviceItemInteractor: DeviceItemInteractor,
@@ -312,7 +309,7 @@ constructor(
return bluetoothDialogDelegateFactory.create(
getUiProperties(),
getCachedContentHeight(),
- this@BluetoothTileDialogViewModel,
+ this@BluetoothDetailsContentViewModel,
{ cancelJob() },
)
}
@@ -321,7 +318,7 @@ constructor(
return bluetoothDetailsContentManagerFactory.create(
getUiProperties(),
getCachedContentHeight(),
- this@BluetoothTileDialogViewModel,
+ this@BluetoothDetailsContentViewModel,
/* isInDialog= */ false,
/* doneButtonCallback= */ fun() {},
)
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothDetailsViewModel.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothDetailsViewModel.kt
index 44475318a61e..5863a9385234 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothDetailsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothDetailsViewModel.kt
@@ -20,7 +20,7 @@ import com.android.systemui.plugins.qs.TileDetailsViewModel
class BluetoothDetailsViewModel(
private val onSettingsClick: () -> Unit,
- val detailsContentViewModel: BluetoothTileDialogViewModel,
+ val detailsContentViewModel: BluetoothDetailsContentViewModel,
) : TileDetailsViewModel() {
override fun clickOnSettingsButton() {
onSettingsClick()
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegate.kt
index 01be820a2fde..c55f60587527 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegate.kt
@@ -32,7 +32,7 @@ import dagger.assisted.AssistedInject
class BluetoothTileDialogDelegate
@AssistedInject
constructor(
- @Assisted private val initialUiProperties: BluetoothTileDialogViewModel.UiProperties,
+ @Assisted private val initialUiProperties: BluetoothDetailsContentViewModel.UiProperties,
@Assisted private val cachedContentHeight: Int,
@Assisted private val bluetoothTileDialogCallback: BluetoothTileDialogCallback,
@Assisted private val dismissListener: Runnable,
@@ -48,7 +48,7 @@ constructor(
@AssistedFactory
interface Factory {
fun create(
- initialUiProperties: BluetoothTileDialogViewModel.UiProperties,
+ initialUiProperties: BluetoothDetailsContentViewModel.UiProperties,
cachedContentHeight: Int,
dialogCallback: BluetoothTileDialogCallback,
dimissListener: Runnable,
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..149efcdcbb8a 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
@@ -126,5 +126,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/common/ui/view/SeekBarWithIconButtonsView.java b/packages/SystemUI/src/com/android/systemui/common/ui/view/SeekBarWithIconButtonsView.java
index 82bce0b5338a..257a5a4d9061 100644
--- a/packages/SystemUI/src/com/android/systemui/common/ui/view/SeekBarWithIconButtonsView.java
+++ b/packages/SystemUI/src/com/android/systemui/common/ui/view/SeekBarWithIconButtonsView.java
@@ -286,7 +286,8 @@ public class SeekBarWithIconButtonsView extends LinearLayout {
/**
* Notification that the user interaction with SeekBarWithIconButtonsView is finalized. This
- * would be triggered after user ends dragging on the slider or clicks icon buttons.
+ * would be triggered after user ends dragging on the slider or clicks icon buttons. This is
+ * not called if the progress change was not initiated by the user.
*
* @param seekBar The SeekBar in which the user ends interaction with
* @param control The last user interacted control unit. It would be
@@ -318,10 +319,14 @@ public class SeekBarWithIconButtonsView extends LinearLayout {
seekBar, OnSeekBarWithIconButtonsChangeListener.ControlUnitType.BUTTON);
} else {
mOnSeekBarChangeListener.onProgressChanged(seekBar, progress, fromUser);
- if (!mSeekByTouch) {
+ if (!mSeekByTouch && fromUser) {
// Accessibility users could change the progress of the seekbar without
- // touching the seekbar or clicking the buttons. We will consider the
- // interaction has finished in this case.
+ // touching the seekbar or clicking the buttons. In this, {@code fromUser}
+ // will be true, and we will consider the interaction to be finished.
+ // The seekbar progress could be changed when {@code fromUser} is false
+ // when magnification scale is set by pinch-to-zoom, keyboard control, or
+ // other services. In this case, we don't need to take finalized actions
+ // for the progress change.
mOnSeekBarChangeListener.onUserInteractionFinalized(
seekBar,
OnSeekBarWithIconButtonsChangeListener.ControlUnitType.SLIDER);
diff --git a/packages/SystemUI/src/com/android/systemui/communal/CommunalBackupRestoreStartable.kt b/packages/SystemUI/src/com/android/systemui/communal/CommunalBackupRestoreStartable.kt
index 7abad1448318..18d461add52b 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/CommunalBackupRestoreStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/CommunalBackupRestoreStartable.kt
@@ -29,6 +29,7 @@ import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.communal.domain.interactor.CommunalInteractor
import com.android.systemui.communal.widgets.CommunalWidgetModule
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.core.Logger
import com.android.systemui.log.dagger.CommunalLog
@@ -43,7 +44,7 @@ constructor(
private val communalInteractor: CommunalInteractor,
@CommunalLog logBuffer: LogBuffer,
private val secureSettings: SecureSettings,
- handler: Handler,
+ @Main handler: Handler,
) : CoreStartable, BroadcastReceiver() {
private val logger = Logger(logBuffer, TAG)
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalBackActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalBackActionInteractor.kt
deleted file mode 100644
index 2ccf96abff79..000000000000
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalBackActionInteractor.kt
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.communal.domain.interactor
-
-import com.android.systemui.communal.shared.model.CommunalScenes
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.scene.domain.interactor.SceneInteractor
-import com.android.systemui.scene.shared.flag.SceneContainerFlag
-import com.android.systemui.scene.shared.model.Scenes
-import javax.inject.Inject
-
-/**
- * {@link CommunalBackActionInteractor} is responsible for handling back gestures on the glanceable
- * hub. When invoked SystemUI should navigate back to the lockscreen.
- */
-@SysUISingleton
-class CommunalBackActionInteractor
-@Inject
-constructor(
- private val communalInteractor: CommunalInteractor,
- private val communalSceneInteractor: CommunalSceneInteractor,
- private val sceneInteractor: SceneInteractor,
-) {
- fun canBeDismissed(): Boolean {
- return communalInteractor.isCommunalShowing.value
- }
-
- fun onBackPressed() {
- if (SceneContainerFlag.isEnabled) {
- // TODO(b/384610333): Properly determine whether to go to dream or lockscreen on back.
- sceneInteractor.changeScene(
- toScene = Scenes.Lockscreen,
- loggingReason = "CommunalBackActionInteractor",
- )
- } else {
- communalSceneInteractor.changeScene(
- newScene = CommunalScenes.Blank,
- loggingReason = "CommunalBackActionInteractor",
- )
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
index 6dab32a66c94..564628d3f52f 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
@@ -327,7 +327,7 @@ constructor(
* use [isIdleOnCommunal].
*/
// TODO(b/323215860): rename to something more appropriate after cleaning up usages
- val isCommunalShowing: StateFlow<Boolean> =
+ val isCommunalShowing: Flow<Boolean> =
flow { emit(SceneContainerFlag.isEnabled) }
.flatMapLatest { sceneContainerEnabled ->
if (sceneContainerEnabled) {
@@ -345,10 +345,10 @@ constructor(
columnName = "isCommunalShowing",
initialValue = false,
)
- .stateIn(
+ .shareIn(
scope = applicationScope,
- started = SharingStarted.Eagerly,
- initialValue = false,
+ started = SharingStarted.WhileSubscribed(),
+ replay = 1,
)
/**
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneTransitionInteractor.kt
index 740555f40dea..477b87119563 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneTransitionInteractor.kt
@@ -32,6 +32,7 @@ import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionInfo
import com.android.systemui.keyguard.shared.model.TransitionModeOnCanceled
import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.util.kotlin.pairwise
import java.util.UUID
@@ -65,6 +66,7 @@ constructor(
@Application private val applicationScope: CoroutineScope,
private val sceneInteractor: CommunalSceneInteractor,
private val repository: CommunalSceneTransitionRepository,
+ private val powerInteractor: PowerInteractor,
keyguardInteractor: KeyguardInteractor,
) : CoreStartable, CommunalSceneInteractor.OnSceneAboutToChangeListener {
@@ -88,12 +90,15 @@ constructor(
combine(
// Don't use delayed dreaming signal as otherwise we might go to occluded or lock
// screen when closing hub if dream just started under the hub.
+ powerInteractor.isAsleep,
keyguardInteractor.isDreamingWithOverlay,
keyguardInteractor.isKeyguardOccluded,
keyguardInteractor.isKeyguardGoingAway,
keyguardInteractor.isKeyguardShowing,
- ) { dreaming, occluded, keyguardGoingAway, keyguardShowing ->
- if (keyguardGoingAway) {
+ ) { asleep, dreaming, occluded, keyguardGoingAway, keyguardShowing ->
+ if (asleep) {
+ KeyguardState.DOZING
+ } else if (keyguardGoingAway) {
KeyguardState.GONE
} else if (occluded && !dreaming) {
KeyguardState.OCCLUDED
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
index 8e0beda9eff7..7354f4096801 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
@@ -212,13 +212,6 @@ public class FrameworkServicesModule {
return new UserScopedServiceImpl<>(context, CaptioningManager.class);
}
- /** */
- @Provides
- @Singleton
- public Choreographer providesChoreographer() {
- return Choreographer.getInstance();
- }
-
@Provides
@Singleton
static ColorDisplayManager provideColorDisplayManager(Context context) {
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/decor/ScreenDecorCommand.kt b/packages/SystemUI/src/com/android/systemui/decor/ScreenDecorCommand.kt
index fa1d898de850..c5d5ca26b14c 100644
--- a/packages/SystemUI/src/com/android/systemui/decor/ScreenDecorCommand.kt
+++ b/packages/SystemUI/src/com/android/systemui/decor/ScreenDecorCommand.kt
@@ -53,6 +53,14 @@ class ScreenDecorCommand(
val roundedBottom: RoundedCornerSubCommand? by
subCommand(RoundedCornerSubCommand("rounded-bottom"))
+ val faceAuthScreen: Int? by
+ param(
+ longName = "faceAuthScreen",
+ description =
+ "Specify a screen to show face auth animation. 0:outer(default screen), 1:inner",
+ valueParser = Type.Int,
+ )
+
override fun execute(pw: PrintWriter) {
callback.onExecute(this, pw)
}
@@ -61,6 +69,7 @@ class ScreenDecorCommand(
return "ScreenDecorCommand(" +
"debug=$debug, " +
"color=$color, " +
+ "faceAuthScreen=$faceAuthScreen, " +
"roundedTop=$roundedTop, " +
"roundedBottom=$roundedBottom)"
}
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java
index abc810afca98..6b762bacb440 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java
@@ -40,6 +40,7 @@ import android.util.IndentingPrintWriter;
import android.view.Display;
import com.android.internal.R;
+import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.doze.dagger.BrightnessSensor;
import com.android.systemui.doze.dagger.DozeScope;
import com.android.systemui.doze.dagger.WrappedService;
@@ -118,7 +119,7 @@ public class DozeScreenBrightness extends BroadcastReceiver implements DozeMachi
@WrappedService DozeMachine.Service service,
AsyncSensorManager sensorManager,
@BrightnessSensor Optional<Sensor>[] lightSensorOptional,
- DozeHost host, Handler handler,
+ DozeHost host, @Main Handler handler,
AlwaysOnDisplayPolicy alwaysOnDisplayPolicy,
WakefulnessLifecycle wakefulnessLifecycle,
DozeParameters dozeParameters,
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
index a56a63c0b104..3132ec2b98e3 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
@@ -53,7 +53,6 @@ import com.android.internal.logging.UiEventLogger;
import com.android.internal.policy.PhoneWindow;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.KeyguardUpdateMonitorCallback;
-import com.android.systemui.Flags;
import com.android.systemui.ambient.touch.TouchHandler;
import com.android.systemui.ambient.touch.TouchMonitor;
import com.android.systemui.ambient.touch.dagger.AmbientTouchComponent;
@@ -211,7 +210,6 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ
mCommunalVisible = communalVisible;
updateLifecycleStateLocked();
- updateGestureBlockingLocked();
});
}
};
@@ -594,8 +592,7 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ
private void updateGestureBlockingLocked() {
final boolean shouldBlock = mStarted && !mShadeExpanded && !mBouncerShowing
- && !isDreamInPreviewMode()
- && !(Flags.glanceableHubBackAction() && mCommunalVisible);
+ && !isDreamInPreviewMode();
if (shouldBlock) {
mGestureInteractor.addGestureBlockedMatcher(DREAM_TYPE_MATCHER,
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/service/TaskFragmentComponent.kt b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/service/TaskFragmentComponent.kt
index 67de30c8fa5c..c81583864d45 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/service/TaskFragmentComponent.kt
+++ b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/service/TaskFragmentComponent.kt
@@ -83,7 +83,8 @@ constructor(
resultT.addTaskFragmentOperation(
fragmentToken,
TaskFragmentOperation.Builder(
- TaskFragmentOperation.OP_TYPE_REORDER_TO_TOP_OF_TASK
+ TaskFragmentOperation
+ .OP_TYPE_PRIVILEGED_REORDER_TO_TOP_OF_TASK
)
.build(),
)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index caf0fd4450fc..efa9c21f96b4 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -16,6 +16,7 @@
package com.android.systemui.keyguard;
+import static android.app.KeyguardManager.LOCK_ON_USER_SWITCH_CALLBACK;
import static android.app.StatusBarManager.SESSION_KEYGUARD;
import static android.provider.Settings.Secure.LOCK_SCREEN_LOCK_AFTER_TIMEOUT;
import static android.provider.Settings.System.LOCKSCREEN_SOUNDS_ENABLED;
@@ -75,6 +76,7 @@ import android.os.Bundle;
import android.os.DeadObjectException;
import android.os.Handler;
import android.os.IBinder;
+import android.os.IRemoteCallback;
import android.os.Looper;
import android.os.Message;
import android.os.PowerManager;
@@ -192,6 +194,8 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
import java.util.Objects;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
@@ -282,6 +286,9 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
private static final int SYSTEM_READY = 18;
private static final int CANCEL_KEYGUARD_EXIT_ANIM = 19;
private static final int BOOT_INTERACTOR = 20;
+ private static final int BEFORE_USER_SWITCHING = 21;
+ private static final int USER_SWITCHING = 22;
+ private static final int USER_SWITCH_COMPLETE = 23;
/** Enum for reasons behind updating wakeAndUnlock state. */
@Retention(RetentionPolicy.SOURCE)
@@ -299,6 +306,8 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
int WAKE_AND_UNLOCK = 3;
}
+ private final List<LockNowCallback> mLockNowCallbacks = new ArrayList<>();
+
/**
* The default amount of time we stay awake (used for all key input)
*/
@@ -357,13 +366,18 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
private final Lazy<NotificationShadeDepthController> mNotificationShadeDepthController;
private final Lazy<ShadeController> mShadeController;
private final Lazy<CommunalSceneInteractor> mCommunalSceneInteractor;
+ /*
+ * Records the user id on request to go away, for validation when WM calls back to start the
+ * exit animation.
+ */
+ private int mGoingAwayRequestedForUserId = -1;
+
private boolean mSystemReady;
private boolean mBootCompleted;
private boolean mBootSendUserPresent;
private boolean mShuttingDown;
private boolean mDozing;
private boolean mAnimatingScreenOff;
- private boolean mIgnoreDismiss;
private final Context mContext;
private final FalsingCollector mFalsingCollector;
@@ -626,41 +640,92 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
}
};
- KeyguardUpdateMonitorCallback mUpdateCallback = new KeyguardUpdateMonitorCallback() {
+ @VisibleForTesting
+ protected UserTracker.Callback mUserChangedCallback = new UserTracker.Callback() {
@Override
- public void onKeyguardVisibilityChanged(boolean visible) {
- synchronized (KeyguardViewMediator.this) {
- if (!visible && mPendingPinLock) {
- Log.i(TAG, "PIN lock requested, starting keyguard");
+ public void onBeforeUserSwitching(int newUser, @NonNull Runnable resultCallback) {
+ mHandler.sendMessage(mHandler.obtainMessage(BEFORE_USER_SWITCHING,
+ newUser, 0, resultCallback));
+ }
- // Bring the keyguard back in order to show the PIN lock
- mPendingPinLock = false;
- doKeyguardLocked(null);
- }
- }
+ @Override
+ public void onUserChanging(int newUser, @NonNull Context userContext,
+ @NonNull Runnable resultCallback) {
+ mHandler.sendMessage(mHandler.obtainMessage(USER_SWITCHING,
+ newUser, 0, resultCallback));
}
@Override
- public void onUserSwitching(int userId) {
- Log.d(TAG, String.format("onUserSwitching %d", userId));
- synchronized (KeyguardViewMediator.this) {
- mIgnoreDismiss = true;
- notifyTrustedChangedLocked(mUpdateMonitor.getUserHasTrust(userId));
- resetKeyguardDonePendingLocked();
+ public void onUserChanged(int newUser, Context userContext) {
+ mHandler.sendMessage(mHandler.obtainMessage(USER_SWITCH_COMPLETE,
+ newUser, 0));
+ }
+ };
+
+ /**
+ * Handle {@link #BEFORE_USER_SWITCHING}
+ */
+ @VisibleForTesting
+ void handleBeforeUserSwitching(int userId, Runnable resultCallback) {
+ Log.d(TAG, String.format("onBeforeUserSwitching %d", userId));
+ synchronized (KeyguardViewMediator.this) {
+ mHandler.removeMessages(DISMISS);
+ notifyTrustedChangedLocked(mUpdateMonitor.getUserHasTrust(userId));
+ resetKeyguardDonePendingLocked();
+ adjustStatusBarLocked();
+ mKeyguardStateController.notifyKeyguardGoingAway(false);
+ if (mLockPatternUtils.isSecure(userId) && !mShowing) {
+ doKeyguardLocked(null);
+ } else {
resetStateLocked();
- adjustStatusBarLocked();
}
+ resultCallback.run();
}
+ }
- @Override
- public void onUserSwitchComplete(int userId) {
- mIgnoreDismiss = false;
- Log.d(TAG, String.format("onUserSwitchComplete %d", userId));
+ /**
+ * Handle {@link #USER_SWITCHING}
+ */
+ @VisibleForTesting
+ void handleUserSwitching(int userId, Runnable resultCallback) {
+ Log.d(TAG, String.format("onUserSwitching %d", userId));
+ synchronized (KeyguardViewMediator.this) {
+ if (!mLockPatternUtils.isSecure(userId)) {
+ dismiss(null, null);
+ }
+ resultCallback.run();
+ }
+ }
+
+ /**
+ * Handle {@link #USER_SWITCH_COMPLETE}
+ */
+ @VisibleForTesting
+ void handleUserSwitchComplete(int userId) {
+ Log.d(TAG, String.format("onUserSwitchComplete %d", userId));
+ // Calling dismiss on a secure user will show the bouncer
+ if (mLockPatternUtils.isSecure(userId)) {
// We are calling dismiss with a delay as there are race conditions in some scenarios
// caused by async layout listeners
mHandler.postDelayed(() -> dismiss(null /* callback */, null /* message */), 500);
}
+ }
+
+ KeyguardUpdateMonitorCallback mUpdateCallback = new KeyguardUpdateMonitorCallback() {
+
+ @Override
+ public void onKeyguardVisibilityChanged(boolean visible) {
+ synchronized (KeyguardViewMediator.this) {
+ if (!visible && mPendingPinLock) {
+ Log.i(TAG, "PIN lock requested, starting keyguard");
+
+ // Bring the keyguard back in order to show the PIN lock
+ mPendingPinLock = false;
+ doKeyguardLocked(null);
+ }
+ }
+ }
@Override
public void onDeviceProvisioned() {
@@ -1671,7 +1736,13 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
com.android.internal.R.anim.lock_screen_behind_enter);
mWorkLockController = new WorkLockActivityController(mContext, mUserTracker);
-
+ mUserTracker.addCallback(mUserChangedCallback, mContext.getMainExecutor());
+ // start() can be invoked in the middle of user switching, so check for this state and issue
+ // the call manually as that important event was missed.
+ if (mUserTracker.isUserSwitching()) {
+ handleBeforeUserSwitching(mUserTracker.getUserId(), () -> {});
+ handleUserSwitching(mUserTracker.getUserId(), () -> {});
+ }
mJavaAdapter.alwaysCollectFlow(
mWallpaperRepository.getWallpaperSupportsAmbientMode(),
this::setWallpaperSupportsAmbientMode);
@@ -1720,7 +1791,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
// System ready can be invoked in the middle of user switching, so check for this state
// and issue the call manually as that important event was missed.
if (mUserTracker.isUserSwitching()) {
- mUpdateCallback.onUserSwitching(mUserTracker.getUserId());
+ mUserChangedCallback.onUserChanging(mUserTracker.getUserId(), mContext, () -> {});
}
}
// Most services aren't available until the system reaches the ready state, so we
@@ -2361,12 +2432,23 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
mCommunalSceneInteractor.get().showHubFromPowerButton();
}
+ int currentUserId = mSelectedUserInteractor.getSelectedUserId();
+ if (options != null && options.getBinder(LOCK_ON_USER_SWITCH_CALLBACK) != null) {
+ LockNowCallback callback = new LockNowCallback(currentUserId,
+ IRemoteCallback.Stub.asInterface(
+ options.getBinder(LOCK_ON_USER_SWITCH_CALLBACK)));
+ synchronized (mLockNowCallbacks) {
+ mLockNowCallbacks.add(callback);
+ }
+ Log.d(TAG, "LockNowCallback required for user: " + callback.mUserId);
+ }
+
// if another app is disabling us, don't show
if (!mExternallyEnabled
&& !mLockPatternUtils.isUserInLockdown(
mSelectedUserInteractor.getSelectedUserId())) {
if (DEBUG) Log.d(TAG, "doKeyguard: not showing because externally disabled");
-
+ notifyLockNowCallback();
mNeedToReshowWhenReenabled = true;
return;
}
@@ -2384,6 +2466,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
// We're removing "reset" in the refactor - "resetting" the views will happen
// as a reaction to the root cause of the "reset" signal.
if (KeyguardWmStateRefactor.isEnabled()) {
+ notifyLockNowCallback();
return;
}
@@ -2396,6 +2479,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
+ "previously hiding. It should be safe to short-circuit "
+ "here.");
resetStateLocked(/* hideBouncer= */ false);
+ notifyLockNowCallback();
return;
}
} else {
@@ -2422,6 +2506,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
Log.d(TAG, "doKeyguard: not showing because device isn't provisioned and the sim is"
+ " not locked or missing");
}
+ notifyLockNowCallback();
return;
}
@@ -2429,6 +2514,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
if (mLockPatternUtils.isLockScreenDisabled(mSelectedUserInteractor.getSelectedUserId())
&& !lockedOrMissing && !forceShow) {
if (DEBUG) Log.d(TAG, "doKeyguard: not showing because lockscreen is off");
+ notifyLockNowCallback();
return;
}
@@ -2476,11 +2562,6 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
}
public void dismiss(IKeyguardDismissCallback callback, CharSequence message) {
- if (mIgnoreDismiss) {
- android.util.Log.i(TAG, "Ignoring request to dismiss (user switch in progress?)");
- return;
- }
-
if (mKeyguardStateController.isKeyguardGoingAway()) {
Log.i(TAG, "Ignoring dismiss because we're already going away.");
return;
@@ -2498,7 +2579,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
}
private void resetStateLocked(boolean hideBouncer) {
- if (DEBUG) Log.e(TAG, "resetStateLocked");
+ if (DEBUG) Log.d(TAG, "resetStateLocked");
Message msg = mHandler.obtainMessage(RESET, hideBouncer ? 1 : 0, 0);
mHandler.sendMessage(msg);
}
@@ -2746,6 +2827,18 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
message = "BOOT_INTERACTOR";
handleBootInteractor();
break;
+ case BEFORE_USER_SWITCHING:
+ message = "BEFORE_USER_SWITCHING";
+ handleBeforeUserSwitching(msg.arg1, (Runnable) msg.obj);
+ break;
+ case USER_SWITCHING:
+ message = "USER_SWITCHING";
+ handleUserSwitching(msg.arg1, (Runnable) msg.obj);
+ break;
+ case USER_SWITCH_COMPLETE:
+ message = "USER_SWITCH_COMPLETE";
+ handleUserSwitchComplete(msg.arg1);
+ break;
}
Log.d(TAG, "KeyguardViewMediator queue processing message: " + message);
}
@@ -2887,6 +2980,9 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
mUiBgExecutor.execute(() -> {
Log.d(TAG, "updateActivityLockScreenState(" + showing + ", " + aodShowing + ", "
+ reason + ")");
+ if (showing) {
+ notifyLockNowCallback();
+ }
if (KeyguardWmStateRefactor.isEnabled()) {
// Handled in WmLockscreenVisibilityManager if flag is enabled.
@@ -2931,6 +3027,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
synchronized (KeyguardViewMediator.this) {
if (!mSystemReady) {
if (DEBUG) Log.d(TAG, "ignoring handleShow because system is not ready.");
+ notifyLockNowCallback();
return;
}
if (DEBUG) Log.d(TAG, "handleShow");
@@ -2989,12 +3086,11 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
}
}
- private final Runnable mKeyguardGoingAwayRunnable = new Runnable() {
+ final Runnable mKeyguardGoingAwayRunnable = new Runnable() {
@SuppressLint("MissingPermission")
@Override
public void run() {
Trace.beginSection("KeyguardViewMediator.mKeyGuardGoingAwayRunnable");
- Log.d(TAG, "keyguardGoingAwayRunnable");
mKeyguardViewControllerLazy.get().keyguardGoingAway();
int flags = 0;
@@ -3031,6 +3127,10 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
// Handled in WmLockscreenVisibilityManager if flag is enabled.
if (!KeyguardWmStateRefactor.isEnabled()) {
+ mGoingAwayRequestedForUserId = mSelectedUserInteractor.getSelectedUserId();
+ Log.d(TAG, "keyguardGoingAway requested for userId: "
+ + mGoingAwayRequestedForUserId);
+
// Don't actually hide the Keyguard at the moment, wait for window manager
// until it tells us it's safe to do so with startKeyguardExitAnimation.
// Posting to mUiOffloadThread to ensure that calls to ActivityTaskManager
@@ -3169,6 +3269,30 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
RemoteAnimationTarget[] nonApps, IRemoteAnimationFinishedCallback finishedCallback) {
Log.d(TAG, "handleStartKeyguardExitAnimation startTime=" + startTime
+ " fadeoutDuration=" + fadeoutDuration);
+ int currentUserId = mSelectedUserInteractor.getSelectedUserId();
+ if (!KeyguardWmStateRefactor.isEnabled() && mGoingAwayRequestedForUserId != currentUserId) {
+ Log.e(TAG, "Not executing handleStartKeyguardExitAnimationInner() due to userId "
+ + "mismatch. Requested: " + mGoingAwayRequestedForUserId + ", current: "
+ + currentUserId);
+ if (finishedCallback != null) {
+ // There will not execute animation, send a finish callback to ensure the remote
+ // animation won't hang there.
+ try {
+ finishedCallback.onAnimationFinished();
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Failed to call onAnimationFinished", e);
+ }
+ }
+ mHiding = false;
+ if (mLockPatternUtils.isSecure(currentUserId)) {
+ doKeyguardLocked(null);
+ } else {
+ resetStateLocked();
+ dismiss(null, null);
+ }
+ return;
+ }
+
synchronized (KeyguardViewMediator.this) {
mIsKeyguardExitAnimationCanceled = false;
// Tell ActivityManager that we canceled the keyguard animation if
@@ -3413,6 +3537,13 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
* app transition before finishing the current RemoteAnimation, or the keyguard being re-shown).
*/
private void handleCancelKeyguardExitAnimation() {
+ if (!KeyguardWmStateRefactor.isEnabled()
+ && mGoingAwayRequestedForUserId != mSelectedUserInteractor.getSelectedUserId()) {
+ Log.e(TAG, "Setting pendingLock = true due to userId mismatch. Requested: "
+ + mGoingAwayRequestedForUserId + ", current: "
+ + mSelectedUserInteractor.getSelectedUserId());
+ setPendingLock(true);
+ }
if (mPendingLock) {
Log.d(TAG, "#handleCancelKeyguardExitAnimation: keyguard exit animation cancelled. "
+ "There's a pending lock, so we were cancelled because the device was locked "
@@ -3513,6 +3644,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
mSurfaceBehindRemoteAnimationRequested = true;
if (ENABLE_NEW_KEYGUARD_SHELL_TRANSITIONS && !KeyguardWmStateRefactor.isEnabled()) {
+ mGoingAwayRequestedForUserId = mSelectedUserInteractor.getSelectedUserId();
startKeyguardTransition(false /* keyguardShowing */, false /* aodShowing */);
return;
}
@@ -3533,6 +3665,9 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
if (!KeyguardWmStateRefactor.isEnabled()) {
// Handled in WmLockscreenVisibilityManager.
+ mGoingAwayRequestedForUserId = mSelectedUserInteractor.getSelectedUserId();
+ Log.d(TAG, "keyguardGoingAway requested for userId: "
+ + mGoingAwayRequestedForUserId);
mActivityTaskManagerService.keyguardGoingAway(flags);
}
} catch (RemoteException e) {
@@ -3988,6 +4123,29 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
mUiBgExecutor.execute(mTrustManager::reportKeyguardShowingChanged);
}
+ private void notifyLockNowCallback() {
+ List<LockNowCallback> callbacks;
+ synchronized (mLockNowCallbacks) {
+ callbacks = new ArrayList<LockNowCallback>(mLockNowCallbacks);
+ mLockNowCallbacks.clear();
+ }
+ Iterator<LockNowCallback> iter = callbacks.listIterator();
+ while (iter.hasNext()) {
+ LockNowCallback callback = iter.next();
+ iter.remove();
+ if (callback.mUserId != mSelectedUserInteractor.getSelectedUserId()) {
+ Log.i(TAG, "Not notifying lockNowCallback due to user mismatch");
+ continue;
+ }
+ Log.i(TAG, "Notifying lockNowCallback");
+ try {
+ callback.mRemoteCallback.sendResult(null);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Could not issue LockNowCallback sendResult", e);
+ }
+ }
+ }
+
private void notifyTrustedChangedLocked(boolean trusted) {
int size = mKeyguardStateCallbacks.size();
for (int i = size - 1; i >= 0; i--) {
@@ -4152,4 +4310,14 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
}
};
}
+
+ private class LockNowCallback {
+ final int mUserId;
+ final IRemoteCallback mRemoteCallback;
+
+ LockNowCallback(int userId, IRemoteCallback remoteCallback) {
+ mUserId = userId;
+ mRemoteCallback = remoteCallback;
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt
index 2a1cb12c153e..f7ed10f40842 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt
@@ -24,6 +24,7 @@ import android.annotation.SuppressLint
import android.os.Trace
import android.util.Log
import com.android.app.animation.Interpolators
+import com.android.app.tracing.coroutines.flow.traceAs
import com.android.app.tracing.coroutines.withContextTraced as withContext
import com.android.systemui.Flags.transitionRaceCondition
import com.android.systemui.dagger.SysUISingleton
@@ -43,7 +44,6 @@ import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filter
@@ -143,11 +143,12 @@ constructor(
@SuppressLint("SharedFlowCreation")
private val _transitions =
MutableSharedFlow<TransitionStep>(
- replay = 2,
- extraBufferCapacity = 20,
- onBufferOverflow = BufferOverflow.DROP_OLDEST,
- )
- override val transitions = _transitions.asSharedFlow().distinctUntilChanged()
+ replay = 2,
+ extraBufferCapacity = 20,
+ onBufferOverflow = BufferOverflow.DROP_OLDEST,
+ )
+ .traceAs("KTR-transitions")
+ override val transitions = _transitions.distinctUntilChanged()
private var lastStep: TransitionStep = TransitionStep()
private var lastAnimator: ValueAnimator? = null
private var animatorListener: AnimatorListenerAdapter? = null
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 c755c4b02feb..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
@@ -17,8 +17,12 @@
package com.android.systemui.keyguard.domain.interactor
import android.animation.ValueAnimator
+import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
import com.android.systemui.communal.domain.interactor.CommunalInteractor
+import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
+import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor
+import com.android.systemui.communal.shared.model.CommunalScenes
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
@@ -46,7 +50,6 @@ import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.onStart
-import com.android.app.tracing.coroutines.launchTraced as launch
@SysUISingleton
class FromAlternateBouncerTransitionInteractor
@@ -60,6 +63,8 @@ constructor(
@Main mainDispatcher: CoroutineDispatcher,
keyguardInteractor: KeyguardInteractor,
private val communalInteractor: CommunalInteractor,
+ private val communalSettingsInteractor: CommunalSettingsInteractor,
+ private val communalSceneInteractor: CommunalSceneInteractor,
powerInteractor: PowerInteractor,
keyguardOcclusionInteractor: KeyguardOcclusionInteractor,
private val primaryBouncerInteractor: PrimaryBouncerInteractor,
@@ -86,7 +91,7 @@ constructor(
.transition(
edge = Edge.create(from = KeyguardState.ALTERNATE_BOUNCER, to = Scenes.Gone),
edgeWithoutSceneContainer =
- Edge.create(from = KeyguardState.ALTERNATE_BOUNCER, to = KeyguardState.GONE)
+ Edge.create(from = KeyguardState.ALTERNATE_BOUNCER, to = KeyguardState.GONE),
)
.map {
// The alt bouncer is pretty fast to hide, so start the surface behind animation
@@ -112,27 +117,21 @@ constructor(
keyguardInteractor.primaryBouncerShowing,
powerInteractor.isAwake,
keyguardInteractor.isAodAvailable,
- communalInteractor.isIdleOnCommunal,
- communalInteractor.editModeOpen,
+ communalSceneInteractor.isIdleOnCommunal,
+ 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) {
if (isAodAvailable) {
@@ -141,16 +140,32 @@ constructor(
KeyguardState.DOZING
}
} else {
- if (isIdleOnCommunal) {
+ 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
} else {
KeyguardState.LOCKSCREEN
}
}
- startTransitionTo(to)
+
+ if (hubV2 && to != KeyguardState.GLANCEABLE_HUB && isIdleOnCommunal) {
+ // If bouncer is showing over the hub, we need to make sure we
+ // properly dismiss the hub when transitioning away.
+ communalSceneInteractor.changeScene(
+ newScene = CommunalScenes.Blank,
+ loggingReason = "alternate bouncer no longer showing over GH",
+ keyguardState = to,
+ )
+ } else {
+ startTransitionTo(to)
+ }
}
}
}
@@ -173,7 +188,7 @@ constructor(
} else {
emptyFlow()
}
- }
+ },
)
.filterRelevantKeyguardState()
.collect { startTransitionTo(KeyguardState.GONE) }
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 6f5f662d6fa3..0700ec639153 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,6 +159,7 @@ 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) {
@@ -191,6 +192,13 @@ 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/FromPrimaryBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt
index 30c1aac7b2a7..75d6631008ca 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt
@@ -21,6 +21,7 @@ import android.util.Log
import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.keyguard.KeyguardSecurityModel
import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
+import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor
import com.android.systemui.communal.shared.model.CommunalScenes
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
@@ -35,6 +36,7 @@ import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.user.domain.interactor.SelectedUserInteractor
import com.android.systemui.util.kotlin.Utils.Companion.sample
+import com.android.systemui.util.kotlin.sample
import com.android.wm.shell.shared.animation.Interpolators
import javax.inject.Inject
import kotlin.time.Duration.Companion.milliseconds
@@ -42,6 +44,7 @@ import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onStart
@@ -57,6 +60,7 @@ constructor(
@Main mainDispatcher: CoroutineDispatcher,
keyguardInteractor: KeyguardInteractor,
private val communalSceneInteractor: CommunalSceneInteractor,
+ private val communalSettingsInteractor: CommunalSettingsInteractor,
private val keyguardSecurityModel: KeyguardSecurityModel,
private val selectedUserInteractor: SelectedUserInteractor,
powerInteractor: PowerInteractor,
@@ -140,14 +144,17 @@ constructor(
)
.collect { (_, isAwake, isDreaming, isIdleOnCommunal) ->
val isOccluded = keyguardInteractor.isKeyguardOccluded.value
+ val hubV2 = communalSettingsInteractor.isV2FlagEnabled()
val toState =
if (isAwake) {
if (isOccluded && !isDreaming) {
KeyguardState.OCCLUDED
- } else if (isIdleOnCommunal) {
+ } else if (!hubV2 && isIdleOnCommunal) {
KeyguardState.GLANCEABLE_HUB
} else if (isDreaming) {
KeyguardState.DREAMING
+ } else if (hubV2 && isIdleOnCommunal) {
+ KeyguardState.GLANCEABLE_HUB
} else {
KeyguardState.LOCKSCREEN
}
@@ -161,7 +168,17 @@ constructor(
)
keyguardInteractor.asleepKeyguardState.value
}
- startTransitionTo(toState)
+ if (hubV2 && toState != KeyguardState.GLANCEABLE_HUB && isIdleOnCommunal) {
+ // If bouncer is showing over the hub, we need to make sure we
+ // properly dismiss the hub when transitioning away.
+ communalSceneInteractor.changeScene(
+ newScene = CommunalScenes.Blank,
+ loggingReason = "bouncer no longer showing over GH",
+ keyguardState = toState,
+ )
+ } else {
+ startTransitionTo(toState)
+ }
}
}
}
@@ -184,7 +201,32 @@ constructor(
private fun listenForPrimaryBouncerToAsleep() {
if (SceneContainerFlag.isEnabled) return
- scope.launch { listenForSleepTransition() }
+ scope.launch {
+ if (communalSettingsInteractor.isV2FlagEnabled()) {
+ powerInteractor.isAsleep
+ .filter { isAsleep -> isAsleep }
+ .filterRelevantKeyguardState()
+ .sample(communalSceneInteractor.isIdleOnCommunal)
+ .collect { isIdleOnCommunal ->
+ if (isIdleOnCommunal) {
+ // If the bouncer is showing on top of the hub, then ensure we also
+ // hide the hub.
+ communalSceneInteractor.changeScene(
+ newScene = CommunalScenes.Blank,
+ loggingReason = "Sleep while primary bouncer showing over hub",
+ keyguardState = keyguardInteractor.asleepKeyguardState.value,
+ )
+ } else {
+ startTransitionTo(
+ toState = keyguardInteractor.asleepKeyguardState.value,
+ ownerReason = "Sleep transition triggered",
+ )
+ }
+ }
+ } else {
+ listenForSleepTransition()
+ }
+ }
}
private fun listenForPrimaryBouncerToGone() {
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 96e05cb7cd63..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,10 +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
@@ -45,6 +47,7 @@ import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
@@ -57,7 +60,8 @@ class KeyguardClockInteractor
constructor(
mediaCarouselInteractor: MediaCarouselInteractor,
activeNotificationsInteractor: ActiveNotificationsInteractor,
- shadeInteractor: ShadeInteractor,
+ aodPromotedNotificationInteractor: AODPromotedNotificationInteractor,
+ shadeModeInteractor: ShadeModeInteractor,
keyguardInteractor: KeyguardInteractor,
keyguardTransitionInteractor: KeyguardTransitionInteractor,
headsUpNotificationInteractor: HeadsUpNotificationInteractor,
@@ -66,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
@@ -80,53 +89,91 @@ constructor(
var clock: ClockController? by keyguardClockRepository.clockEventController::clock
- val clockSize: StateFlow<ClockSize> =
+ private val isAodPromotedNotificationPresent: Flow<Boolean> =
+ if (PromotedNotificationUiAod.isEnabled) {
+ aodPromotedNotificationInteractor.isPresent
+ } else {
+ flowOf(false)
+ }
+
+ private val areAnyNotificationsPresent: Flow<Boolean> =
+ if (PromotedNotificationUiAod.isEnabled) {
+ combine(
+ activeNotificationsInteractor.areAnyNotificationsPresent,
+ isAodPromotedNotificationPresent,
+ ) { areAnyNotificationsPresent, isAodPromotedNotificationPresent ->
+ areAnyNotificationsPresent || isAodPromotedNotificationPresent
+ }
+ } else {
+ activeNotificationsInteractor.areAnyNotificationsPresent
+ }
+
+ private val dynamicClockSize: Flow<ClockSize> =
if (SceneContainerFlag.isEnabled) {
combine(
- shadeInteractor.isShadeLayoutWide,
- activeNotificationsInteractor.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,
- activeNotificationsInteractor.areAnyNotificationsPresent,
+ shadeModeInteractor.isShadeLayoutWide,
+ areAnyNotificationsPresent,
+ isAodPromotedNotificationPresent,
isOnAod,
headsUpNotificationInteractor.isHeadsUpOrAnimatingAway,
keyguardInteractor.isDozing,
- ) { isShadeLayoutWide, areAnyNotificationsPresent, isOnAod, isHeadsUp, isDozing ->
+ ) {
+ isShadeLayoutWide,
+ areAnyNotificationsPresent,
+ isAodPromotedNotificationPresent,
+ isOnAod,
+ isHeadsUp,
+ isDozing ->
when {
!isShadeLayoutWide -> true
!areAnyNotificationsPresent -> true
// Pulsing notification appears on the right. Move clock left to avoid overlap.
isHeadsUp && isDozing -> false
+ isAodPromotedNotificationPresent -> false
else -> isOnAod
}
}
} else {
combine(
- shadeInteractor.isShadeLayoutWide,
- activeNotificationsInteractor.areAnyNotificationsPresent,
+ shadeModeInteractor.isShadeLayoutWide,
+ areAnyNotificationsPresent,
+ isAodPromotedNotificationPresent,
keyguardInteractor.dozeTransitionModel,
keyguardTransitionInteractor.startedKeyguardTransitionStep.map { it.to == AOD },
keyguardTransitionInteractor.startedKeyguardTransitionStep.map {
@@ -140,6 +187,7 @@ constructor(
) {
isShadeLayoutWide,
areAnyNotificationsPresent,
+ isAodPromotedNotificationPresent,
dozeTransitionModel,
startedToAod,
startedToLockScreen,
@@ -156,7 +204,7 @@ constructor(
// use null to skip emitting wrong value
startedToGone || startedToDoze -> null
startedToLockScreen -> !areAnyNotificationsPresent
- startedToAod -> !isPulsing
+ startedToAod -> !(isPulsing || isAodPromotedNotificationPresent)
else -> true
}
}
@@ -170,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/KeyguardTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
index 58fb4230ccf5..3b4a1488095a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
@@ -19,7 +19,10 @@ package com.android.systemui.keyguard.domain.interactor
import android.annotation.SuppressLint
import android.util.Log
+import com.android.app.tracing.coroutines.flow.filterTraced
+import com.android.app.tracing.coroutines.flow.traceAs
import com.android.app.tracing.coroutines.launchTraced as launch
+import com.android.app.tracing.coroutines.traceCoroutine
import com.android.compose.animation.scene.ObservableTransitionState
import com.android.compose.animation.scene.SceneKey
import com.android.systemui.Flags.keyguardTransitionForceFinishOnScreenOff
@@ -90,6 +93,7 @@ constructor(
onBufferOverflow = BufferOverflow.DROP_OLDEST,
)
.also { it.tryEmit(0f) }
+ .traceAs("KTF-${state.name}")
}
}
@@ -222,10 +226,11 @@ constructor(
val flow: Flow<TransitionStep> =
transitionMap.getOrPut(mappedEdge) {
- MutableSharedFlow(
- extraBufferCapacity = 10,
- onBufferOverflow = BufferOverflow.DROP_OLDEST,
- )
+ MutableSharedFlow<TransitionStep>(
+ extraBufferCapacity = 10,
+ onBufferOverflow = BufferOverflow.DROP_OLDEST,
+ )
+ .traceAs("KTF-${mappedEdge.from}-to-${mappedEdge.to}")
}
if (!SceneContainerFlag.isEnabled) {
@@ -234,7 +239,7 @@ constructor(
if (edge.isSceneWildcardEdge()) {
return simulateTransitionStepsForSceneTransitions(edge)
}
- return flow.filter { step ->
+ return flow.filterTraced("stl-filter") { step ->
val fromScene =
when (edge) {
is Edge.StateToState -> edge.from?.mapToSceneContainerScene()
@@ -273,7 +278,7 @@ constructor(
step.transitionState == TransitionState.CANCELED) &&
sceneTransitionPair.value.previousValue.isTransitioning(fromScene, toScene)
- return@filter isTransitioningBetweenLockscreenStates ||
+ return@filterTraced isTransitioningBetweenLockscreenStates ||
isTransitioningBetweenDesiredScenes ||
terminalStepBelongsToPreviousTransition ||
belongsToInstantReversedTransition
@@ -362,27 +367,27 @@ constructor(
coroutineScope {
collect { value ->
- job?.cancelAndJoin()
+ traceCoroutine("cancelAndJoin") { job?.cancelAndJoin() }
- job = launch {
+ job = launch("inner") {
val innerFlow = transform(value)
try {
innerFlow.collect { step ->
if (step.transitionState == TransitionState.STARTED) {
startedEmitted = true
}
- send(step)
+ traceCoroutine("send($step)") { send(step) }
}
} finally {
if (startedEmitted) {
- send(
+ val step =
TransitionStep(
from = UNDEFINED,
to = UNDEFINED,
value = 1f,
transitionState = TransitionState.FINISHED,
)
- )
+ traceCoroutine("send($step)") { send(step) }
startedEmitted = false
}
}
@@ -390,6 +395,7 @@ constructor(
}
}
}
+ .traceAs("flatMapLatestWithFinished")
/**
* Converts old KTF states to UNDEFINED when [SceneContainerFlag] is enabled.
@@ -545,6 +551,7 @@ constructor(
}
}
.onStart { emit(false) }
+ .traceAs("isInTransition-$edge-$edgeWithoutSceneContainer")
.distinctUntilChanged()
}
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 00710c97d00a..8e385385b8c4 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
@@ -300,7 +300,7 @@ object KeyguardRootViewBinder {
}
launch {
- viewModel.isNotifIconContainerVisible.collect { isVisible ->
+ viewModel.isAodPromotedNotifVisible.collect { isVisible ->
if (isVisible.value) {
blueprintViewModel.refreshBlueprint()
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/LightRevealScrimViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/LightRevealScrimViewBinder.kt
index 97fa3f19a82e..f0113a5b6020 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/LightRevealScrimViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/LightRevealScrimViewBinder.kt
@@ -26,7 +26,6 @@ import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.shared.Flags.ambientAod
import com.android.systemui.statusbar.LightRevealScrim
import com.android.systemui.wallpapers.ui.viewmodel.WallpaperViewModel
-import com.android.app.tracing.coroutines.launchTraced as launch
object LightRevealScrimViewBinder {
@JvmStatic
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 e6a85c6860c5..9018c58a7e36 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,4 +39,6 @@ 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/keyguard/ui/viewmodel/KeyguardMediaViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardMediaViewModel.kt
index ba03c48c65e9..e70d696a207f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardMediaViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardMediaViewModel.kt
@@ -21,6 +21,7 @@ import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.lifecycle.ExclusiveActivatable
import com.android.systemui.lifecycle.Hydrator
import com.android.systemui.media.controls.domain.pipeline.interactor.MediaCarouselInteractor
+import com.android.systemui.shade.domain.interactor.ShadeModeInteractor
import com.android.systemui.utils.coroutines.flow.flatMapLatestConflated
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
@@ -31,6 +32,7 @@ class KeyguardMediaViewModel
constructor(
mediaCarouselInteractor: MediaCarouselInteractor,
keyguardInteractor: KeyguardInteractor,
+ shadeModeInteractor: ShadeModeInteractor,
) : ExclusiveActivatable() {
private val hydrator = Hydrator("KeyguardMediaViewModel.hydrator")
@@ -54,6 +56,12 @@ constructor(
mediaCarouselInteractor.hasActiveMediaOrRecommendation.value,
)
+ val isShadeLayoutWide: Boolean by
+ hydrator.hydratedStateOf(
+ traceName = "isShadeLayoutWide",
+ source = shadeModeInteractor.isShadeLayoutWide,
+ )
+
override suspend fun onActivated(): Nothing {
hydrator.activate()
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
index 62a5ebab29e0..8e21745e1a31 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
@@ -25,6 +25,7 @@ import com.android.systemui.communal.domain.interactor.CommunalInteractor
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
+import com.android.systemui.dump.DumpManager
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.domain.interactor.PulseExpansionInteractor
@@ -44,9 +45,11 @@ import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.shade.ui.viewmodel.NotificationShadeWindowModel
import com.android.systemui.statusbar.notification.domain.interactor.NotificationsKeyguardInteractor
import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerAlwaysOnDisplayViewModel
+import com.android.systemui.statusbar.notification.promoted.domain.interactor.AODPromotedNotificationInteractor
import com.android.systemui.statusbar.phone.DozeParameters
import com.android.systemui.statusbar.phone.ScreenOffAnimationController
import com.android.systemui.util.kotlin.BooleanFlowOperators.anyOf
+import com.android.systemui.util.kotlin.FlowDumperImpl
import com.android.systemui.util.kotlin.pairwise
import com.android.systemui.util.kotlin.sample
import com.android.systemui.util.ui.AnimatableEvent
@@ -82,6 +85,7 @@ constructor(
private val notificationsKeyguardInteractor: NotificationsKeyguardInteractor,
private val pulseExpansionInteractor: PulseExpansionInteractor,
notificationShadeWindowModel: NotificationShadeWindowModel,
+ private val aodPromotedNotificationInteractor: AODPromotedNotificationInteractor,
private val aodNotificationIconViewModel: NotificationIconContainerAlwaysOnDisplayViewModel,
private val alternateBouncerToAodTransitionViewModel: AlternateBouncerToAodTransitionViewModel,
private val alternateBouncerToGoneTransitionViewModel:
@@ -134,17 +138,21 @@ constructor(
private val aodBurnInViewModel: AodBurnInViewModel,
private val shadeInteractor: ShadeInteractor,
wallpaperFocalAreaInteractor: WallpaperFocalAreaInteractor,
-) {
+ dumpManager: DumpManager,
+) : FlowDumperImpl(dumpManager) {
val burnInLayerVisibility: Flow<Int> =
keyguardTransitionInteractor.startedKeyguardTransitionStep
.filter { it.to == AOD || it.to == LOCKSCREEN }
.map { VISIBLE }
+ .dumpWhileCollecting("burnInLayerVisibility")
val goneToAodTransition =
- keyguardTransitionInteractor.transition(
- edge = Edge.create(Scenes.Gone, AOD),
- edgeWithoutSceneContainer = Edge.create(GONE, AOD),
- )
+ keyguardTransitionInteractor
+ .transition(
+ edge = Edge.create(Scenes.Gone, AOD),
+ edgeWithoutSceneContainer = Edge.create(GONE, AOD),
+ )
+ .dumpWhileCollecting("goneToAodTransition")
private val goneToAodTransitionRunning: Flow<Boolean> =
goneToAodTransition
@@ -222,13 +230,15 @@ constructor(
)
/** Last point that the root view was tapped */
- val lastRootViewTapPosition: Flow<Point?> = keyguardInteractor.lastRootViewTapPosition
+ val lastRootViewTapPosition: Flow<Point?> =
+ keyguardInteractor.lastRootViewTapPosition.dumpWhileCollecting("lastRootViewTapPosition")
/**
* The keyguard root view can be clipped as the shade is pulled down, typically only for
* non-split shade cases.
*/
- val topClippingBounds: Flow<Int?> = keyguardInteractor.topClippingBounds
+ val topClippingBounds: Flow<Int?> =
+ keyguardInteractor.topClippingBounds.dumpWhileCollecting("topClippingBounds")
/** An observable for the alpha level for the entire keyguard root view. */
fun alpha(viewState: ViewStateAccessor): Flow<Float> {
@@ -287,40 +297,56 @@ constructor(
}
}
.distinctUntilChanged()
+ .dumpWhileCollecting("alpha")
}
val scaleFromZoomOut: Flow<Float> =
- keyguardInteractor.zoomOut.map { 1 - it * PUSHBACK_SCALE_FOR_LOCKSCREEN }
+ keyguardInteractor.zoomOut
+ .map { 1 - it * PUSHBACK_SCALE_FOR_LOCKSCREEN }
+ .dumpWhileCollecting("scaleFromZoomOut")
- val translationY: Flow<Float> = aodBurnInViewModel.movement.map { it.translationY.toFloat() }
+ val translationY: Flow<Float> =
+ aodBurnInViewModel.movement
+ .map { it.translationY.toFloat() }
+ .dumpWhileCollecting("translationY")
val translationX: Flow<StateToValue> =
merge(
- aodBurnInViewModel.movement.map {
- StateToValue(to = AOD, value = it.translationX.toFloat())
- },
- lockscreenToGlanceableHubTransitionViewModel.keyguardTranslationX,
- glanceableHubToLockscreenTransitionViewModel.keyguardTranslationX,
- )
+ aodBurnInViewModel.movement.map {
+ StateToValue(to = AOD, value = it.translationX.toFloat())
+ },
+ lockscreenToGlanceableHubTransitionViewModel.keyguardTranslationX,
+ glanceableHubToLockscreenTransitionViewModel.keyguardTranslationX,
+ )
+ .dumpWhileCollecting("translationX")
fun updateBurnInParams(params: BurnInParameters) {
aodBurnInViewModel.updateBurnInParams(params)
}
val scale: Flow<BurnInScaleViewModel> =
- aodBurnInViewModel.movement.map {
- BurnInScaleViewModel(scale = it.scale, scaleClockOnly = it.scaleClockOnly)
- }
+ aodBurnInViewModel.movement
+ .map { BurnInScaleViewModel(scale = it.scale, scaleClockOnly = it.scaleClockOnly) }
+ .dumpWhileCollecting("scale")
- val isAodPromotedNotifVisible: StateFlow<Boolean> =
- keyguardTransitionInteractor
- .transitionValue(AOD)
- .map { it == 1f }
+ val isAodPromotedNotifVisible: StateFlow<AnimatedValue<Boolean>> =
+ combine(
+ areNotifsFullyHiddenAnimated(),
+ isPulseExpandingAnimated(),
+ aodPromotedNotificationInteractor.isPresent,
+ ) { notifsFullyHiddenAnimated, pulseExpandingAnimated, haveAodPromotedNotif ->
+ zip(notifsFullyHiddenAnimated, pulseExpandingAnimated) {
+ notifsFullyHidden,
+ pulseExpanding ->
+ notifsFullyHidden && !pulseExpanding && haveAodPromotedNotif
+ }
+ }
.stateIn(
scope = applicationScope,
started = SharingStarted.WhileSubscribed(),
- initialValue = false,
+ initialValue = AnimatedValue.NotAnimating(false),
)
+ .dumpValue("isAodPromotedNotifVisible")
/** Is the notification icon container visible? */
val isNotifIconContainerVisible: StateFlow<AnimatedValue<Boolean>> =
@@ -376,6 +402,7 @@ constructor(
started = SharingStarted.WhileSubscribed(),
initialValue = AnimatedValue.NotAnimating(false),
)
+ .dumpValue("isNotifIconContainerVisible")
fun onNotificationContainerBoundsChanged(top: Float, bottom: Float, animate: Boolean = false) {
keyguardInteractor.setNotificationContainerBounds(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt
index 3e3a89a55f66..ecebaee62862 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt
@@ -17,8 +17,9 @@
package com.android.systemui.keyguard.ui.viewmodel
import android.content.res.Resources
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.Alignment
import com.android.app.tracing.coroutines.launchTraced as launch
-import com.android.internal.annotations.VisibleForTesting
import com.android.systemui.biometrics.AuthController
import com.android.systemui.customization.R as customR
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
@@ -30,86 +31,121 @@ import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.transition.KeyguardTransitionAnimationCallback
import com.android.systemui.keyguard.shared.transition.KeyguardTransitionAnimationCallbackDelegator
import com.android.systemui.lifecycle.ExclusiveActivatable
+import com.android.systemui.lifecycle.Hydrator
import com.android.systemui.res.R
-import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.shade.domain.interactor.ShadeModeInteractor
+import com.android.systemui.shade.shared.model.ShadeMode
import com.android.systemui.unfold.domain.interactor.UnfoldTransitionInteractor
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
-import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.awaitCancellation
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.map
-import kotlinx.coroutines.flow.stateIn
class LockscreenContentViewModel
@AssistedInject
constructor(
- clockInteractor: KeyguardClockInteractor,
- private val interactor: KeyguardBlueprintInteractor,
+ private val clockInteractor: KeyguardClockInteractor,
+ interactor: KeyguardBlueprintInteractor,
private val authController: AuthController,
val touchHandling: KeyguardTouchHandlingViewModel,
- private val shadeInteractor: ShadeInteractor,
- private val unfoldTransitionInteractor: UnfoldTransitionInteractor,
- private val deviceEntryInteractor: DeviceEntryInteractor,
- private val transitionInteractor: KeyguardTransitionInteractor,
+ shadeModeInteractor: ShadeModeInteractor,
+ unfoldTransitionInteractor: UnfoldTransitionInteractor,
+ deviceEntryInteractor: DeviceEntryInteractor,
+ transitionInteractor: KeyguardTransitionInteractor,
private val keyguardTransitionAnimationCallbackDelegator:
KeyguardTransitionAnimationCallbackDelegator,
@Assisted private val keyguardTransitionAnimationCallback: KeyguardTransitionAnimationCallback,
) : ExclusiveActivatable() {
- @VisibleForTesting val clockSize = clockInteractor.clockSize
+
+ private val hydrator = Hydrator("LockscreenContentViewModel.hydrator")
val isUdfpsVisible: Boolean
get() = authController.isUdfpsSupported
- val isShadeLayoutWide: StateFlow<Boolean> = shadeInteractor.isShadeLayoutWide
+ /** Where to place the notifications stack on the lockscreen. */
+ val notificationsPlacement: NotificationsPlacement by
+ hydrator.hydratedStateOf(
+ traceName = "notificationsPlacement",
+ initialValue = NotificationsPlacement.BelowClock,
+ source =
+ combine(shadeModeInteractor.shadeMode, clockInteractor.clockSize) {
+ shadeMode,
+ clockSize ->
+ if (shadeMode is ShadeMode.Split) {
+ NotificationsPlacement.BesideClock(alignment = Alignment.TopEnd)
+ } else if (clockSize == ClockSize.SMALL) {
+ NotificationsPlacement.BelowClock
+ } else {
+ NotificationsPlacement.BesideClock(alignment = Alignment.TopStart)
+ }
+ },
+ )
- private val _unfoldTranslations = MutableStateFlow(UnfoldTranslations())
/** Amount of horizontal translation that should be applied to elements in the scene. */
- val unfoldTranslations: StateFlow<UnfoldTranslations> = _unfoldTranslations.asStateFlow()
+ val unfoldTranslations: UnfoldTranslations by
+ hydrator.hydratedStateOf(
+ traceName = "unfoldTranslations",
+ initialValue = UnfoldTranslations(),
+ source =
+ combine(
+ unfoldTransitionInteractor.unfoldTranslationX(isOnStartSide = true),
+ unfoldTransitionInteractor.unfoldTranslationX(isOnStartSide = false),
+ ::UnfoldTranslations,
+ ),
+ )
- private val _isContentVisible = MutableStateFlow(true)
/** Whether the content of the scene UI should be shown. */
- val isContentVisible: StateFlow<Boolean> = _isContentVisible.asStateFlow()
+ val isContentVisible: Boolean by
+ hydrator.hydratedStateOf(
+ traceName = "isContentVisible",
+ initialValue = true,
+ // Content is visible unless we're OCCLUDED. Currently, we don't have nice animations
+ // into and out of OCCLUDED, so the lockscreen/AOD content is hidden immediately upon
+ // entering/exiting OCCLUDED.
+ source = transitionInteractor.transitionValue(KeyguardState.OCCLUDED).map { it == 0f },
+ )
+
+ /** Indicates whether lockscreen notifications should be rendered. */
+ val areNotificationsVisible: Boolean by
+ hydrator.hydratedStateOf(
+ traceName = "areNotificationsVisible",
+ initialValue = false,
+ // Content is visible unless we're OCCLUDED. Currently, we don't have nice animations
+ // into and out of OCCLUDED, so the lockscreen/AOD content is hidden immediately upon
+ // entering/exiting OCCLUDED.
+ source =
+ combine(clockInteractor.clockSize, shadeModeInteractor.isShadeLayoutWide) {
+ clockSize,
+ isShadeLayoutWide ->
+ clockSize == ClockSize.SMALL || isShadeLayoutWide
+ },
+ )
/** @see DeviceEntryInteractor.isBypassEnabled */
- val isBypassEnabled: StateFlow<Boolean>
- get() = deviceEntryInteractor.isBypassEnabled
+ val isBypassEnabled: Boolean by
+ hydrator.hydratedStateOf(
+ traceName = "isBypassEnabled",
+ source = deviceEntryInteractor.isBypassEnabled,
+ )
+
+ val blueprintId: String by
+ hydrator.hydratedStateOf(
+ traceName = "blueprintId",
+ initialValue = interactor.getCurrentBlueprint().id,
+ source = interactor.blueprint.map { it.id }.distinctUntilChanged(),
+ )
override suspend fun onActivated(): Nothing {
coroutineScope {
try {
+ launch { hydrator.activate() }
+
keyguardTransitionAnimationCallbackDelegator.delegate =
keyguardTransitionAnimationCallback
- launch {
- combine(
- unfoldTransitionInteractor.unfoldTranslationX(isOnStartSide = true),
- unfoldTransitionInteractor.unfoldTranslationX(isOnStartSide = false),
- ) { start, end ->
- UnfoldTranslations(start = start, end = end)
- }
- .collect { _unfoldTranslations.value = it }
- }
-
- launch {
- transitionInteractor
- .transitionValue(KeyguardState.OCCLUDED)
- .map { it > 0f }
- .collect { fullyOrPartiallyOccluded ->
- // Content is visible unless we're OCCLUDED. Currently, we don't have
- // nice
- // animations into and out of OCCLUDED, so the lockscreen/AOD content is
- // hidden immediately upon entering/exiting OCCLUDED.
- _isContentVisible.value = !fullyOrPartiallyOccluded
- }
- }
awaitCancellation()
} finally {
@@ -118,16 +154,8 @@ constructor(
}
}
- /** Returns a flow that indicates whether lockscreen notifications should be rendered. */
- fun areNotificationsVisible(): Flow<Boolean> {
- return combine(clockSize, shadeInteractor.isShadeLayoutWide) { clockSize, isShadeLayoutWide
- ->
- clockSize == ClockSize.SMALL || isShadeLayoutWide
- }
- }
-
fun getSmartSpacePaddingTop(resources: Resources): Int {
- return if (clockSize.value == ClockSize.LARGE) {
+ return if (clockInteractor.clockSize.value == ClockSize.LARGE) {
resources.getDimensionPixelSize(customR.dimen.keyguard_smartspace_top_offset) +
resources.getDimensionPixelSize(R.dimen.keyguard_clock_top_margin)
} else {
@@ -135,17 +163,6 @@ constructor(
}
}
- fun blueprintId(scope: CoroutineScope): StateFlow<String> {
- return interactor.blueprint
- .map { it.id }
- .distinctUntilChanged()
- .stateIn(
- scope = scope,
- started = SharingStarted.WhileSubscribed(),
- initialValue = interactor.getCurrentBlueprint().id,
- )
- }
-
data class UnfoldTranslations(
/**
@@ -162,6 +179,15 @@ constructor(
val end: Float = 0f,
)
+ /** Where to place the notifications stack on the lockscreen. */
+ sealed interface NotificationsPlacement {
+ /** Show notifications below the lockscreen clock. */
+ data object BelowClock : NotificationsPlacement
+
+ /** Show notifications side-by-side with the clock. */
+ data class BesideClock(val alignment: Alignment) : NotificationsPlacement
+ }
+
@AssistedFactory
interface Factory {
fun create(
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewController.kt
index c1778119a3fd..2b36872dbe36 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewController.kt
@@ -1040,13 +1040,19 @@ constructor(
expandedLayout.load(context, R.xml.media_recommendations_expanded)
}
}
- readjustPlayPauseWidth()
+ readjustUIUpdateConstraints()
refreshState()
}
- private fun readjustPlayPauseWidth() {
+ private fun readjustUIUpdateConstraints() {
// TODO: move to xml file when flag is removed.
if (Flags.mediaControlsUiUpdate()) {
+ collapsedLayout.setGuidelineEnd(
+ R.id.action_button_guideline,
+ context.resources.getDimensionPixelSize(
+ R.dimen.qs_media_session_collapsed_guideline
+ ),
+ )
collapsedLayout.constrainWidth(
R.id.actionPlayPause,
context.resources.getDimensionPixelSize(R.dimen.qs_media_action_play_pause_width),
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
index 322c35116222..f5e62323e769 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
@@ -185,6 +185,7 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter {
}
boolean isDeviceGroup = false;
+ boolean hideGroupItem = false;
GroupStatus groupStatus = null;
OngoingSessionStatus ongoingSessionStatus = null;
ConnectionState connectionState = ConnectionState.DISCONNECTED;
@@ -216,68 +217,38 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter {
clickListener = v -> cancelMuteAwaitConnection();
} else if (device.getState() == MediaDeviceState.STATE_GROUPING) {
connectionState = ConnectionState.CONNECTING;
- } else if (mShouldGroupSelectedMediaItems
- && mController.getSelectedMediaDevice().size() > 1
- && isDeviceIncluded(mController.getSelectedMediaDevice(), device)) {
- if (!mediaItem.isFirstDeviceInGroup()) {
- mItemLayout.setVisibility(View.GONE);
- return;
- } else {
+ } else if (mShouldGroupSelectedMediaItems && hasMultipleSelectedDevices()
+ && isSelected) {
+ if (mediaItem.isFirstDeviceInGroup()) {
isDeviceGroup = true;
+ } else {
+ hideGroupItem = true;
}
- } else if (device.hasSubtext()) {
- subtitle = device.getSubtextString();
- boolean isActiveWithOngoingSession =
- device.hasOngoingSession() && (currentlyConnected || isSelected);
- if (isActiveWithOngoingSession) {
- ongoingSessionStatus = new OngoingSessionStatus(
- device.isHostForOngoingSession());
+ } else { // A connected or disconnected device.
+ subtitle = device.hasSubtext() ? device.getSubtextString() : null;
+ ongoingSessionStatus = getOngoingSessionStatus(device);
+ groupStatus = getGroupStatus(isSelected, isSelectable, isDeselectable);
+
+ if (device.getState() == MediaDeviceState.STATE_CONNECTING_FAILED) {
+ deviceStatusIcon = mContext.getDrawable(
+ R.drawable.media_output_status_failed);
+ subtitle = mContext.getString(R.string.media_output_dialog_connect_failed);
+ clickListener = v -> onItemClick(v, device);
+ } else if (currentlyConnected || isSelected) {
connectionState = ConnectionState.CONNECTED;
- } else {
- if (currentlyConnected) {
- connectionState = ConnectionState.CONNECTED;
+ } else { // disconnected
+ if (isSelectable) { // groupable device
+ if (!Flags.disableTransferWhenAppsDoNotSupport() || isTransferable
+ || hasRouteListingPreferenceItem) {
+ clickListener = v -> onItemClick(v, device);
+ }
+ } else {
+ deviceStatusIcon = getDeviceStatusIcon(device,
+ device.hasOngoingSession());
+ clickListener = getClickListenerBasedOnSelectionBehavior(device);
}
- clickListener = getClickListenerBasedOnSelectionBehavior(device);
deviceDisabled = clickListener == null;
- deviceStatusIcon = getDeviceStatusIcon(device, device.hasOngoingSession());
- }
- } else if (device.getState() == MediaDeviceState.STATE_CONNECTING_FAILED) {
- deviceStatusIcon = mContext.getDrawable(R.drawable.media_output_status_failed);
- subtitle = mContext.getString(R.string.media_output_dialog_connect_failed);
- clickListener = v -> onItemClick(v, device);
- } else if (mController.getSelectedMediaDevice().size() > 1 && isSelected) {
- // selected device in group
- groupStatus = new GroupStatus(
- true /* selected */,
- isDeselectable /* deselectable */);
- connectionState = ConnectionState.CONNECTED;
- } else if (currentlyConnected) {
- connectionState = ConnectionState.CONNECTED;
- // single selected device
- if (device.hasOngoingSession()) {
- ongoingSessionStatus = new OngoingSessionStatus(
- device.isHostForOngoingSession());
- } else if (mController.isCurrentConnectedDeviceRemote()
- && !mController.getSelectableMediaDevice().isEmpty()) {
- //If device is connected and there's other selectable devices, layout as
- // one of selected devices.
- groupStatus = new GroupStatus(
- true /* selected */,
- isDeselectable /* isDeselectable */);
- }
- } else if (isSelectable) {
- //groupable device
- groupStatus = new GroupStatus(false /* selected */, true /* deselectable */);
- if (!Flags.disableTransferWhenAppsDoNotSupport()
- || isTransferable
- || hasRouteListingPreferenceItem) {
- clickListener = v -> onItemClick(v, device);
}
- deviceDisabled = clickListener == null;
- } else {
- deviceStatusIcon = getDeviceStatusIcon(device, device.hasOngoingSession());
- clickListener = getClickListenerBasedOnSelectionBehavior(device);
- deviceDisabled = clickListener == null;
}
}
@@ -286,28 +257,71 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter {
}
if (isDeviceGroup) {
- String sessionName = mController.getSessionName() == null ? ""
- : mController.getSessionName().toString();
- updateTitle(sessionName);
- updateUnmutedVolumeIcon(null /* device */);
- updateGroupSeekBar(getGroupItemContentDescription(sessionName));
- updateEndAreaForDeviceGroup();
- updateItemBackground(ConnectionState.CONNECTED);
+ renderDeviceGroupItem();
} else {
- 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);
+ renderDeviceItem(hideGroupItem, device, connectionState, restrictVolumeAdjustment,
+ groupStatus, ongoingSessionStatus, clickListener, deviceDisabled, subtitle,
+ deviceStatusIcon);
}
}
+ private 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);
+ }
+
+ private void renderDeviceGroupItem() {
+ String sessionName = mController.getSessionName() == null ? ""
+ : mController.getSessionName().toString();
+ updateTitle(sessionName);
+ updateUnmutedVolumeIcon(null /* device */);
+ updateGroupSeekBar(getGroupItemContentDescription(sessionName));
+ updateEndAreaForDeviceGroup();
+ updateItemBackground(ConnectionState.CONNECTED);
+ }
+
+ private OngoingSessionStatus getOngoingSessionStatus(MediaDevice device) {
+ return device.hasOngoingSession() ? new OngoingSessionStatus(
+ device.isHostForOngoingSession()) : null;
+ }
+
+ private GroupStatus getGroupStatus(boolean isSelected, boolean isSelectable,
+ boolean isDeselectable) {
+ // A device should either be selectable or, when the device selected, the list should
+ // have other selectable or selected devices.
+ boolean selectedWithOtherGroupDevices =
+ isSelected && (hasMultipleSelectedDevices() || hasSelectableDevices());
+ if (isSelectable || selectedWithOtherGroupDevices) {
+ return new GroupStatus(isSelected, isDeselectable);
+ }
+ return null;
+ }
+
+ private boolean hasMultipleSelectedDevices() {
+ return mController.getSelectedMediaDevice().size() > 1;
+ }
+
+ private boolean hasSelectableDevices() {
+ return !mController.getSelectableMediaDevice().isEmpty();
+ }
+
/** Renders the right side round pill button / checkbox. */
private void updateEndArea(@NonNull MediaDevice device, ConnectionState connectionState,
@Nullable GroupStatus groupStatus,
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/tiles/BluetoothTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
index 973265c6c9b1..fd5861fed20c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
@@ -43,8 +43,8 @@ import com.android.settingslib.bluetooth.BluetoothUtils;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.settingslib.satellite.SatelliteDialogUtils;
import com.android.systemui.animation.Expandable;
+import com.android.systemui.bluetooth.qsdialog.BluetoothDetailsContentViewModel;
import com.android.systemui.bluetooth.qsdialog.BluetoothDetailsViewModel;
-import com.android.systemui.bluetooth.qsdialog.BluetoothTileDialogViewModel;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.flags.FeatureFlags;
@@ -84,7 +84,7 @@ public class BluetoothTile extends QSTileImpl<BooleanState> {
private final Executor mExecutor;
- private final BluetoothTileDialogViewModel mDialogViewModel;
+ private final BluetoothDetailsContentViewModel mDetailsContentViewModel;
private final FeatureFlags mFeatureFlags;
@Nullable
@@ -104,7 +104,7 @@ public class BluetoothTile extends QSTileImpl<BooleanState> {
QSLogger qsLogger,
BluetoothController bluetoothController,
FeatureFlags featureFlags,
- BluetoothTileDialogViewModel dialogViewModel
+ BluetoothDetailsContentViewModel detailsContentViewModel
) {
super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger,
statusBarStateController, activityStarter, qsLogger);
@@ -112,7 +112,7 @@ public class BluetoothTile extends QSTileImpl<BooleanState> {
mController.observe(getLifecycle(), mCallback);
mExecutor = new HandlerExecutor(mainHandler);
mFeatureFlags = featureFlags;
- mDialogViewModel = dialogViewModel;
+ mDetailsContentViewModel = detailsContentViewModel;
}
@Override
@@ -133,7 +133,7 @@ public class BluetoothTile extends QSTileImpl<BooleanState> {
callback.accept(new BluetoothDetailsViewModel(() -> {
longClick(null);
return null;
- }, mDialogViewModel))
+ }, mDetailsContentViewModel))
);
return true;
}
@@ -158,7 +158,7 @@ public class BluetoothTile extends QSTileImpl<BooleanState> {
private void handleClickEvent(@Nullable Expandable expandable) {
if (mFeatureFlags.isEnabled(Flags.BLUETOOTH_QS_TILE_DIALOG)) {
- mDialogViewModel.showDetailsContent(expandable, /* view= */ null);
+ mDetailsContentViewModel.showDetailsContent(expandable, /* view= */ null);
} else {
// Secondary clicks are header clicks, just toggle.
toggleBluetooth();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTileNewImpl.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTileNewImpl.kt
index 4d0e80854853..f80b8fb8cb1f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTileNewImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTileNewImpl.kt
@@ -36,7 +36,6 @@ import com.android.systemui.qs.logging.QSLogger
import com.android.systemui.qs.tileimpl.QSTileImpl
import com.android.systemui.qs.tiles.dialog.InternetDetailsViewModel
import com.android.systemui.qs.tiles.dialog.InternetDialogManager
-import com.android.systemui.qs.tiles.dialog.WifiStateWorker
import com.android.systemui.res.R
import com.android.systemui.statusbar.connectivity.AccessPointController
import com.android.systemui.statusbar.pipeline.shared.ui.binder.InternetTileBinder
@@ -58,7 +57,6 @@ constructor(
qsLogger: QSLogger,
viewModel: InternetTileViewModel,
private val internetDialogManager: InternetDialogManager,
- private val wifiStateWorker: WifiStateWorker,
private val accessPointController: AccessPointController,
private val internetDetailsViewModelFactory: InternetDetailsViewModel.Factory,
) :
@@ -86,10 +84,7 @@ constructor(
mContext.getString(R.string.quick_settings_internet_label)
override fun newTileState(): QSTile.BooleanState {
- return QSTile.BooleanState().also {
- it.forceExpandIcon = true
- it.handlesSecondaryClick = true
- }
+ return QSTile.BooleanState().also { it.forceExpandIcon = true }
}
override fun handleClick(expandable: Expandable?) {
@@ -107,12 +102,6 @@ constructor(
return internetDetailsViewModelFactory.create { longClick(null) }
}
- override fun handleSecondaryClick(expandable: Expandable?) {
- // TODO(b/358352265): Figure out the correct action for the secondary click
- // Toggle wifi
- wifiStateWorker.isWifiEnabled = !wifiStateWorker.isWifiEnabled
- }
-
override fun handleUpdateState(state: QSTile.BooleanState, arg: Any?) {
state.label = mContext.resources.getString(R.string.quick_settings_internet_label)
state.expandedAccessibilityClassName = Button::class.java.name
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java
index 42b35c736d42..7b88d6930279 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java
@@ -26,6 +26,7 @@ import android.text.TextUtils;
import android.util.Log;
import android.widget.Button;
import android.widget.Switch;
+import android.widget.Toast;
import androidx.annotation.Nullable;
@@ -192,8 +193,19 @@ public class ScreenRecordTile extends QSTileImpl<QSTile.BooleanState>
@Override
public boolean getDetailsViewModel(Consumer<TileDetailsViewModel> callback) {
handleClick(() -> executeWhenUnlockedKeyguard(
- () -> callback.accept(new ScreenRecordDetailsViewModel(mController,
- this::onStartRecordingClicked)))
+ () -> {
+ if (mController.isScreenCaptureDisabled()) {
+ // Close the panel first so that the toast can show up.
+ mDialogTransitionAnimator.disableAllCurrentDialogsExitAnimations();
+ mPanelInteractor.collapsePanels();
+
+ showDisabledByPolicyToast();
+ return;
+ }
+
+ callback.accept(new ScreenRecordDetailsViewModel(mController,
+ this::onStartRecordingClicked));
+ })
);
return true;
}
@@ -244,6 +256,12 @@ public class ScreenRecordTile extends QSTileImpl<QSTile.BooleanState>
return mContext.getString(R.string.quick_settings_screen_record_label);
}
+ void showDisabledByPolicyToast() {
+ Toast.makeText(mContext,
+ R.string.screen_capturing_disabled_by_policy_dialog_description, Toast.LENGTH_SHORT)
+ .show();
+ }
+
private void cancelCountdown() {
Log.d(TAG, "Cancelling countdown");
mController.cancelCountdown();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/ScreenRecordDetailsContent.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/ScreenRecordDetailsContent.kt
index bf1a51d8cd59..3eb73d83bc1a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/ScreenRecordDetailsContent.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/ScreenRecordDetailsContent.kt
@@ -28,13 +28,6 @@ import com.android.systemui.screenrecord.ScreenRecordPermissionViewBinder
@Composable
fun ScreenRecordDetailsContent(viewModel: ScreenRecordDetailsViewModel) {
- // TODO(b/378514312): Finish implementing this function.
-
- if (viewModel.recordingController.isScreenCaptureDisabled) {
- // TODO(b/388345506): Show disabled page here.
- return
- }
-
val viewBinder: ScreenRecordPermissionViewBinder = remember {
viewModel.recordingController.createScreenRecordPermissionViewBinder(
viewModel.onStartRecordingClicked
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/InternetTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/InternetTileMapper.kt
index 7ad01e463399..8d5880554277 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/InternetTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/InternetTileMapper.kt
@@ -92,10 +92,6 @@ constructor(
else QSTileState.ActivationState.INACTIVE
supportedActions =
- setOf(
- QSTileState.UserAction.CLICK,
- QSTileState.UserAction.TOGGLE_CLICK,
- QSTileState.UserAction.LONG_CLICK,
- )
+ setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileUserActionInteractor.kt
index 6e2c437b9c16..8e48fe492e13 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileUserActionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileUserActionInteractor.kt
@@ -26,7 +26,6 @@ import com.android.systemui.qs.tiles.base.interactor.QSTileInput
import com.android.systemui.qs.tiles.base.interactor.QSTileUserActionInteractor
import com.android.systemui.qs.tiles.dialog.InternetDetailsViewModel
import com.android.systemui.qs.tiles.dialog.InternetDialogManager
-import com.android.systemui.qs.tiles.dialog.WifiStateWorker
import com.android.systemui.qs.tiles.impl.internet.domain.model.InternetTileModel
import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction
import com.android.systemui.statusbar.connectivity.AccessPointController
@@ -40,7 +39,6 @@ class InternetTileUserActionInteractor
constructor(
@Main private val mainContext: CoroutineContext,
private val internetDialogManager: InternetDialogManager,
- private val wifiStateWorker: WifiStateWorker,
private val accessPointController: AccessPointController,
private val qsTileIntentUserActionHandler: QSTileIntentUserInputHandler,
private val internetDetailsViewModelFactory: InternetDetailsViewModel.Factory,
@@ -59,24 +57,17 @@ constructor(
)
}
}
- is QSTileUserAction.ToggleClick -> {
- // TODO(b/358352265): Figure out the correct action for the secondary click
- // Toggle Wifi
- wifiStateWorker.isWifiEnabled = !wifiStateWorker.isWifiEnabled
- }
is QSTileUserAction.LongClick -> {
handleLongClick(action.expandable)
}
+ else -> {}
}
}
override val detailsViewModel: TileDetailsViewModel =
internetDetailsViewModelFactory.create { handleLongClick(null) }
- private fun handleLongClick(expandable:Expandable?){
- qsTileIntentUserActionHandler.handle(
- expandable,
- Intent(Settings.ACTION_WIFI_SETTINGS)
- )
+ private fun handleLongClick(expandable: Expandable?) {
+ qsTileIntentUserActionHandler.handle(expandable, Intent(Settings.ACTION_WIFI_SETTINGS))
}
}
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 475c0794861f..e9e7deca0abf 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
@@ -16,6 +16,7 @@
package com.android.systemui.scene.domain.interactor
+import com.android.app.tracing.coroutines.flow.stateInTraced
import com.android.compose.animation.scene.ContentKey
import com.android.compose.animation.scene.ObservableTransitionState
import com.android.compose.animation.scene.OverlayKey
@@ -52,7 +53,6 @@ import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
-import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
@@ -124,7 +124,8 @@ constructor(
val transitionState: StateFlow<ObservableTransitionState> =
repository.transitionState
.onEach { logger.logSceneTransition(it) }
- .stateIn(
+ .stateInTraced(
+ name = "transitionState",
scope = applicationScope,
started = SharingStarted.Eagerly,
initialValue = repository.transitionState.value,
@@ -145,7 +146,8 @@ constructor(
is ObservableTransitionState.Transition -> state.toContent
}
}
- .stateIn(
+ .stateInTraced(
+ name = "transitioningTo",
scope = applicationScope,
started = SharingStarted.WhileSubscribed(),
initialValue = null,
@@ -164,7 +166,8 @@ constructor(
is ObservableTransitionState.Idle -> flowOf(false)
}
}
- .stateIn(
+ .stateInTraced(
+ name = "isTransitionUserInputOngoing",
scope = applicationScope,
started = SharingStarted.WhileSubscribed(),
initialValue = false,
@@ -183,7 +186,8 @@ constructor(
activeTransitionAnimationCount = activeTransitionAnimationCount,
)
}
- .stateIn(
+ .stateInTraced(
+ name = "isVisible",
scope = applicationScope,
started = SharingStarted.WhileSubscribed(),
initialValue = isVisibleInternal(),
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..16adf5ef976e 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
@@ -610,15 +611,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)",
+ )
+ }
}
+ }
}
}
@@ -1013,6 +1023,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/screenshot/scroll/ImageTileSet.java b/packages/SystemUI/src/com/android/systemui/screenshot/scroll/ImageTileSet.java
index 76a72f7e4adf..25c38050e95a 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/scroll/ImageTileSet.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/scroll/ImageTileSet.java
@@ -29,6 +29,7 @@ import androidx.annotation.UiThread;
import com.android.internal.util.CallbackRegistry;
import com.android.internal.util.CallbackRegistry.NotifierCallback;
+import com.android.systemui.dagger.qualifiers.Main;
import java.util.ArrayList;
import java.util.Iterator;
@@ -49,7 +50,7 @@ class ImageTileSet {
private CallbackRegistry<OnContentChangedListener, ImageTileSet, Rect> mContentListeners;
@Inject
- ImageTileSet(@UiThread Handler handler) {
+ ImageTileSet(@Main Handler handler) {
mHandler = handler;
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/BaseShadeControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/BaseShadeControllerImpl.kt
index b271c6979b31..71977ef1f234 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/BaseShadeControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/BaseShadeControllerImpl.kt
@@ -16,19 +16,19 @@
package com.android.systemui.shade
+import com.android.keyguard.KeyguardViewController
import com.android.systemui.assist.AssistManager
import com.android.systemui.statusbar.CommandQueue
import com.android.systemui.statusbar.NotificationPresenter
import com.android.systemui.statusbar.NotificationShadeWindowController
-import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
import dagger.Lazy
/** A base class for non-empty implementations of ShadeController. */
abstract class BaseShadeControllerImpl(
protected val commandQueue: CommandQueue,
- protected val statusBarKeyguardViewManager: StatusBarKeyguardViewManager,
+ protected val keyguardViewController: KeyguardViewController,
protected val notificationShadeWindowController: NotificationShadeWindowController,
- protected val assistManagerLazy: Lazy<AssistManager>
+ protected val assistManagerLazy: Lazy<AssistManager>,
) : ShadeController {
protected lateinit var notifPresenter: NotificationPresenter
/** Runnables to run after completing a collapse of the shade. */
@@ -66,7 +66,7 @@ abstract class BaseShadeControllerImpl(
for (r in clonedList) {
r.run()
}
- statusBarKeyguardViewManager.readyForKeyguardDone()
+ keyguardViewController.readyForKeyguardDone()
}
final override fun onLaunchAnimationEnd(launchIsFullScreen: Boolean) {
@@ -77,6 +77,7 @@ abstract class BaseShadeControllerImpl(
instantCollapseShade()
}
}
+
final override fun onLaunchAnimationCancelled(isLaunchForActivity: Boolean) {
if (
notifPresenter.isPresenterFullyCollapsed() &&
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java
index 0e30f2b4bb30..acae1bc81b97 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java
@@ -23,6 +23,7 @@ import android.view.MotionEvent;
import android.view.ViewTreeObserver;
import android.view.WindowManagerGlobal;
+import com.android.keyguard.KeyguardViewController;
import com.android.systemui.DejankUtils;
import com.android.systemui.assist.AssistManager;
import com.android.systemui.dagger.SysUISingleton;
@@ -35,7 +36,6 @@ import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
-import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.window.StatusBarWindowControllerStore;
@@ -61,7 +61,6 @@ public final class ShadeControllerImpl extends BaseShadeControllerImpl {
private final KeyguardStateController mKeyguardStateController;
private final NotificationShadeWindowController mNotificationShadeWindowController;
private final StatusBarStateController mStatusBarStateController;
- private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
private final StatusBarWindowControllerStore mStatusBarWindowControllerStore;
private final DeviceProvisionedController mDeviceProvisionedController;
@@ -82,7 +81,7 @@ public final class ShadeControllerImpl extends BaseShadeControllerImpl {
WindowRootViewVisibilityInteractor windowRootViewVisibilityInteractor,
KeyguardStateController keyguardStateController,
StatusBarStateController statusBarStateController,
- StatusBarKeyguardViewManager statusBarKeyguardViewManager,
+ KeyguardViewController keyguardViewController,
StatusBarWindowControllerStore statusBarWindowControllerStore,
DeviceProvisionedController deviceProvisionedController,
NotificationShadeWindowController notificationShadeWindowController,
@@ -93,7 +92,7 @@ public final class ShadeControllerImpl extends BaseShadeControllerImpl {
Lazy<NotificationGutsManager> gutsManager
) {
super(commandQueue,
- statusBarKeyguardViewManager,
+ keyguardViewController,
notificationShadeWindowController,
assistManagerLazy);
SceneContainerFlag.assertInLegacyMode();
@@ -107,7 +106,6 @@ public final class ShadeControllerImpl extends BaseShadeControllerImpl {
mGutsManager = gutsManager;
mNotificationShadeWindowController = notificationShadeWindowController;
mNotifShadeWindowViewController = notificationShadeWindowViewController;
- mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
mDisplayId = displayId;
mKeyguardStateController = keyguardStateController;
mAssistManagerLazy = assistManagerLazy;
@@ -396,7 +394,7 @@ public final class ShadeControllerImpl extends BaseShadeControllerImpl {
@Override
public void collapseShadeForActivityStart() {
- if (isExpandedVisible() && !mStatusBarKeyguardViewManager.isBouncerShowing()) {
+ if (isExpandedVisible() && !getKeyguardViewController().isBouncerShowing()) {
animateCollapseShadeForcedDelayed();
} else {
// Do it after DismissAction has been processed to conserve the
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/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 1ab0b93da175..8f4e8701cad8 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,7 +22,6 @@ 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
@@ -33,7 +32,6 @@ 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
/**
@@ -91,14 +89,10 @@ constructor(
) : ShadeModeInteractor {
private val isDualShadeEnabled: Flow<Boolean> =
- if (SceneContainerFlag.isEnabled) {
- secureSettingsRepository.boolSetting(
- Settings.Secure.DUAL_SHADE,
- defaultValue = DUAL_SHADE_ENABLED_DEFAULT,
- )
- } else {
- flowOf(false)
- }
+ secureSettingsRepository.boolSetting(
+ Settings.Secure.DUAL_SHADE,
+ defaultValue = DUAL_SHADE_ENABLED_DEFAULT,
+ )
override val isShadeLayoutWide: StateFlow<Boolean> = repository.isShadeLayoutWide
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BatteryStatusChip.kt b/packages/SystemUI/src/com/android/systemui/statusbar/BatteryStatusChip.kt
index a58ce4162ddc..02cec13d2ce8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/BatteryStatusChip.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/BatteryStatusChip.kt
@@ -26,6 +26,7 @@ import com.android.settingslib.flags.Flags.newStatusBarIcons
import com.android.systemui.battery.BatteryMeterView
import com.android.systemui.battery.unified.BatteryColors
import com.android.systemui.res.R
+import com.android.systemui.statusbar.core.NewStatusBarIcons
import com.android.systemui.statusbar.events.BackgroundAnimatableView
class BatteryStatusChip @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
@@ -37,6 +38,8 @@ class BatteryStatusChip @JvmOverloads constructor(context: Context, attrs: Attri
get() = batteryMeterView
init {
+ NewStatusBarIcons.assertInLegacyMode()
+
inflate(context, R.layout.battery_status_chip, this)
roundedContainer = requireViewById(R.id.rounded_container)
batteryMeterView = requireViewById(R.id.battery_meter_view)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
index 3e761079bc49..6aa2fe29e768 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
@@ -38,7 +38,6 @@ import com.android.systemui.Flags
import com.android.systemui.Flags.spatialModelAppPushback
import com.android.systemui.animation.ShadeInterpolation
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dump.DumpManager
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.plugins.statusbar.StatusBarStateController
@@ -61,8 +60,6 @@ import java.util.Optional
import javax.inject.Inject
import kotlin.math.max
import kotlin.math.sign
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.launch
/**
* Responsible for blurring the notification shade window, and applying a zoom effect to the
@@ -84,7 +81,6 @@ constructor(
@ShadeDisplayAware private val context: Context,
private val splitShadeStateController: SplitShadeStateController,
private val windowRootViewBlurInteractor: WindowRootViewBlurInteractor,
- @Application private val applicationScope: CoroutineScope,
private val appZoomOutOptional: Optional<AppZoomOut>,
dumpManager: DumpManager,
configurationController: ConfigurationController,
@@ -394,18 +390,15 @@ constructor(
private fun initBlurListeners() {
if (!Flags.bouncerUiRevamp()) return
- applicationScope.launch {
- Log.d(TAG, "Starting coroutines for window root view blur")
- windowRootViewBlurInteractor.onBlurAppliedEvent.collect { appliedBlurRadius ->
- if (updateScheduled) {
- // Process the blur applied event only if we scheduled the update
- TrackTracer.instantForGroup("shade", "shade_blur_radius", appliedBlurRadius)
- updateScheduled = false
- onBlurApplied(appliedBlurRadius, zoomOutCalculatedFromShadeRadius)
- } else {
- // Try scheduling an update now, maybe our blur request will be scheduled now.
- scheduleUpdate()
- }
+ windowRootViewBlurInteractor.registerBlurAppliedListener { appliedBlurRadius ->
+ if (updateScheduled) {
+ // Process the blur applied event only if we scheduled the update
+ TrackTracer.instantForGroup("shade", "shade_blur_radius", appliedBlurRadius)
+ updateScheduled = false
+ onBlurApplied(appliedBlurRadius, zoomOutCalculatedFromShadeRadius)
+ } else {
+ // Try scheduling an update now, maybe our blur request will be scheduled now.
+ scheduleUpdate()
}
}
}
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 a9338885d4c2..b1af811178e4 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
@@ -105,17 +105,12 @@ constructor(
*/
val notificationChip: Flow<NotificationChipModel?> =
combine(_notificationModel, isAppVisible) { notif, isAppVisible ->
- if (isAppVisible) {
- // If the app that posted this notification is visible, we want to hide the chip
- // because information between the status bar chip and the app itself could be
- // out-of-sync (like a timer that's slightly off)
- null
- } else {
- notif.toNotificationChipModel()
- }
+ notif.toNotificationChipModel(isAppVisible)
}
- private fun ActiveNotificationModel.toNotificationChipModel(): NotificationChipModel? {
+ private fun ActiveNotificationModel.toNotificationChipModel(
+ isVisible: Boolean
+ ): NotificationChipModel? {
val promotedContent = this.promotedContent
if (promotedContent == null) {
logger.w({
@@ -138,7 +133,13 @@ constructor(
}
}
- return NotificationChipModel(key, appName, statusBarChipIconView, promotedContent)
+ return NotificationChipModel(
+ key,
+ appName,
+ statusBarChipIconView,
+ promotedContent,
+ isVisible,
+ )
}
@AssistedFactory
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 9463db57585b..c26d10311f1e 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
@@ -142,10 +142,10 @@ constructor(
}
/**
- * A flow modeling the notifications that should be shown as chips in the status bar. Emits an
- * empty list if there are no notifications that should show a status bar chip.
+ * 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.
*/
- val notificationChips: Flow<List<NotificationChipModel>> =
+ private val allNotificationChips: Flow<List<NotificationChipModel>> =
if (StatusBarNotifChips.isEnabled) {
// For all our current interactors...
promotedNotificationInteractors.flatMapLatest { intrs ->
@@ -172,4 +172,13 @@ constructor(
} else {
flowOf(emptyList())
}
+
+ /** Emits the notifications that should actually be *shown* as chips in the status bar. */
+ val shownNotificationChips: Flow<List<NotificationChipModel>> =
+ allNotificationChips.map { chipsList ->
+ // If the app that posted this notification is visible, we want to hide the chip
+ // because information between the status bar chip and the app itself could be
+ // out-of-sync (like a timer that's slightly off)
+ chipsList.filter { !it.isAppVisible }
+ }
}
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 e7a90804a768..97c37628f2e1 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
@@ -22,8 +22,10 @@ import com.android.systemui.statusbar.notification.promoted.shared.model.Promote
/** Modeling all the data needed to render a status bar notification chip. */
data class NotificationChipModel(
val key: String,
- /** The user-readable name of the app that posted the call notification. */
+ /** The user-readable name of the app that posted this notification. */
val appName: String,
val statusBarChipIconView: StatusBarIconView?,
val promotedContent: PromotedNotificationContentModel,
+ /** True if the app managing this notification is currently visible to the user. */
+ val isAppVisible: Boolean,
)
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 2d6102e310f2..3ecbdf82f2cb 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
@@ -58,7 +58,7 @@ constructor(
*/
val chips: Flow<List<OngoingActivityChipModel.Active>> =
combine(
- notifChipsInteractor.notificationChips,
+ notifChipsInteractor.shownNotificationChips,
headsUpNotificationInteractor.statusBarHeadsUpState,
) { notifications, headsUpState ->
notifications.map { it.toActivityChipModel(headsUpState) }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/ChipContent.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/ChipContent.kt
index 8443d106dfb1..5242feac898b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/ChipContent.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/ChipContent.kt
@@ -19,8 +19,6 @@ package com.android.systemui.statusbar.chips.ui.compose
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.Measurable
@@ -39,7 +37,9 @@ import androidx.compose.ui.unit.constrain
import androidx.compose.ui.unit.dp
import com.android.systemui.res.R
import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
+import com.android.systemui.statusbar.chips.ui.viewmodel.formatTimeRemainingData
import com.android.systemui.statusbar.chips.ui.viewmodel.rememberChronometerState
+import com.android.systemui.statusbar.chips.ui.viewmodel.rememberTimeRemainingState
import kotlin.math.min
@Composable
@@ -121,7 +121,26 @@ fun ChipContent(viewModel: OngoingActivityChipModel.Active, modifier: Modifier =
}
is OngoingActivityChipModel.Active.ShortTimeDelta -> {
- // TODO(b/372657935): Implement ShortTimeDelta content in compose.
+ val timeRemainingState = rememberTimeRemainingState(futureTimeMillis = viewModel.time)
+
+ timeRemainingState.timeRemainingData?.let {
+ val text = formatTimeRemainingData(it)
+ Text(
+ text = text,
+ style = textStyle,
+ color = textColor,
+ softWrap = false,
+ modifier =
+ modifier.hideTextIfDoesNotFit(
+ text = text,
+ textStyle = textStyle,
+ textMeasurer = textMeasurer,
+ maxTextWidth = maxTextWidth,
+ startPadding = startPadding,
+ endPadding = endPadding,
+ ),
+ )
+ }
}
is OngoingActivityChipModel.Active.IconOnly -> {
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 1cdf6800fb97..4a999d5f5e0e 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
@@ -49,11 +49,18 @@ import com.android.systemui.animation.Expandable
import com.android.systemui.common.ui.compose.Icon
import com.android.systemui.common.ui.compose.load
import com.android.systemui.res.R
+import com.android.systemui.statusbar.StatusBarIconView
import com.android.systemui.statusbar.chips.ui.model.ColorsModel
import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
+import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
+import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerViewBinder
@Composable
-fun OngoingActivityChip(model: OngoingActivityChipModel.Active, modifier: Modifier = Modifier) {
+fun OngoingActivityChip(
+ model: OngoingActivityChipModel.Active,
+ iconViewStore: NotificationIconContainerViewBinder.IconViewStore?,
+ modifier: Modifier = Modifier,
+) {
when (val clickBehavior = model.clickBehavior) {
is OngoingActivityChipModel.ClickBehavior.ExpandAction -> {
// Wrap the chip in an Expandable so we can animate the expand transition.
@@ -65,15 +72,15 @@ fun OngoingActivityChip(model: OngoingActivityChipModel.Active, modifier: Modifi
),
modifier = modifier,
) { expandable ->
- ChipBody(model, onClick = { clickBehavior.onClick(expandable) })
+ ChipBody(model, iconViewStore, onClick = { clickBehavior.onClick(expandable) })
}
}
is OngoingActivityChipModel.ClickBehavior.ShowHeadsUpNotification -> {
- ChipBody(model, onClick = { clickBehavior.onClick() })
+ ChipBody(model, iconViewStore, onClick = { clickBehavior.onClick() })
}
is OngoingActivityChipModel.ClickBehavior.None -> {
- ChipBody(model, modifier = modifier)
+ ChipBody(model, iconViewStore, modifier = modifier)
}
}
}
@@ -81,12 +88,15 @@ fun OngoingActivityChip(model: OngoingActivityChipModel.Active, modifier: Modifi
@Composable
private fun ChipBody(
model: OngoingActivityChipModel.Active,
+ iconViewStore: NotificationIconContainerViewBinder.IconViewStore?,
modifier: Modifier = Modifier,
onClick: (() -> Unit)? = null,
) {
val context = LocalContext.current
val isClickable = onClick != null
- val hasEmbeddedIcon = model.icon is OngoingActivityChipModel.ChipIcon.StatusBarView
+ val hasEmbeddedIcon =
+ model.icon is OngoingActivityChipModel.ChipIcon.StatusBarView ||
+ model.icon is OngoingActivityChipModel.ChipIcon.StatusBarNotificationIcon
val contentDescription =
when (val icon = model.icon) {
is OngoingActivityChipModel.ChipIcon.StatusBarView -> icon.contentDescription.load()
@@ -156,7 +166,9 @@ private fun ChipBody(
}
),
) {
- model.icon?.let { ChipIcon(viewModel = it, colors = model.colors) }
+ model.icon?.let {
+ ChipIcon(viewModel = it, iconViewStore = iconViewStore, colors = model.colors)
+ }
val isIconOnly = model is OngoingActivityChipModel.Active.IconOnly
if (!isIconOnly) {
@@ -169,6 +181,7 @@ private fun ChipBody(
@Composable
private fun ChipIcon(
viewModel: OngoingActivityChipModel.ChipIcon,
+ iconViewStore: NotificationIconContainerViewBinder.IconViewStore?,
colors: ColorsModel,
modifier: Modifier = Modifier,
) {
@@ -176,22 +189,16 @@ private fun ChipIcon(
when (viewModel) {
is OngoingActivityChipModel.ChipIcon.StatusBarView -> {
- // TODO(b/364653005): If the notification updates their small icon, ensure it's updated
- // in the chip.
- val originalIcon = viewModel.impl
- val iconSizePx =
- context.resources.getDimensionPixelSize(
- R.dimen.ongoing_activity_chip_embedded_padding_icon_size
- )
- AndroidView(
- modifier = modifier,
- factory = { _ ->
- originalIcon.apply {
- layoutParams = ViewGroup.LayoutParams(iconSizePx, iconSizePx)
- imageTintList = ColorStateList.valueOf(colors.text(context))
- }
- },
- )
+ StatusBarConnectedDisplays.assertInLegacyMode()
+ StatusBarIcon(colors, viewModel.impl.notification?.key, modifier) { viewModel.impl }
+ }
+ is OngoingActivityChipModel.ChipIcon.StatusBarNotificationIcon -> {
+ StatusBarConnectedDisplays.assertInNewMode()
+ check(iconViewStore != null)
+
+ StatusBarIcon(colors, viewModel.notificationKey, modifier) {
+ iconViewStore.iconView(viewModel.notificationKey)
+ }
}
is OngoingActivityChipModel.ChipIcon.SingleColorIcon -> {
@@ -209,6 +216,31 @@ private fun ChipIcon(
}
}
+/** A Compose wrapper around [StatusBarIconView]. */
+@Composable
+private fun StatusBarIcon(
+ colors: ColorsModel,
+ notificationKey: String?,
+ modifier: Modifier = Modifier,
+ iconFactory: () -> StatusBarIconView?,
+) {
+ val context = LocalContext.current
+
+ val iconSizePx =
+ context.resources.getDimensionPixelSize(
+ R.dimen.ongoing_activity_chip_embedded_padding_icon_size
+ )
+ AndroidView(
+ modifier = modifier,
+ factory = { _ ->
+ iconFactory.invoke()?.apply {
+ layoutParams = ViewGroup.LayoutParams(iconSizePx, iconSizePx)
+ imageTintList = ColorStateList.valueOf(colors.text(context))
+ } ?: throw IllegalStateException("Missing StatusBarIconView for $notificationKey")
+ },
+ )
+}
+
@Composable
private fun ExpandableChip(
color: () -> Color,
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 64cad31f2150..4017c436151e 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
@@ -26,18 +26,24 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.android.systemui.statusbar.chips.ui.model.MultipleOngoingActivityChipsModel
+import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerViewBinder
@Composable
-fun OngoingActivityChips(chips: MultipleOngoingActivityChipsModel, modifier: Modifier = Modifier) {
+fun OngoingActivityChips(
+ chips: MultipleOngoingActivityChipsModel,
+ iconViewStore: NotificationIconContainerViewBinder.IconViewStore?,
+ modifier: Modifier = Modifier,
+) {
Row(
// TODO(b/372657935): Remove magic numbers for padding and spacing.
modifier = modifier.fillMaxHeight().padding(horizontal = 6.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(8.dp),
) {
- // TODO(b/372657935): Make sure chips are only shown when there is enough horizontal space.
chips.active
.filter { !it.isHidden }
- .forEach { key(it.key) { OngoingActivityChip(model = it) } }
+ .forEach {
+ key(it.key) { OngoingActivityChip(model = it, iconViewStore = iconViewStore) }
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/TimeRemainingState.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/TimeRemainingState.kt
new file mode 100644
index 000000000000..eb6ebcaa5796
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/TimeRemainingState.kt
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.chips.ui.viewmodel
+
+import android.os.SystemClock
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+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.res.stringResource
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.compose.LocalLifecycleOwner
+import androidx.lifecycle.repeatOnLifecycle
+import kotlin.time.Duration
+import kotlin.time.Duration.Companion.hours
+import kotlin.time.Duration.Companion.minutes
+import kotlin.time.DurationUnit
+import kotlin.time.toDuration
+import kotlinx.coroutines.delay
+
+/**
+ * Manages state and updates for the duration remaining between now and a given time in the future.
+ */
+class TimeRemainingState(private val timeSource: TimeSource, private val futureTimeMillis: Long) {
+ private var durationRemaining by mutableStateOf(Duration.ZERO)
+ private var startTimeMillis: Long = 0
+
+ /**
+ * [Pair] representing the time unit and its value.
+ *
+ * @property first the string resource ID corresponding to the time unit (e.g., minutes, hours).
+ * @property second the time value of the duration unit. Null if time is less than a minute or
+ * past.
+ */
+ val timeRemainingData by derivedStateOf { getTimeRemainingData(durationRemaining) }
+
+ suspend fun run() {
+ startTimeMillis = timeSource.getCurrentTime()
+ while (true) {
+ val currentTime = timeSource.getCurrentTime()
+ durationRemaining =
+ (futureTimeMillis - currentTime).toDuration(DurationUnit.MILLISECONDS)
+ // No need to update if duration is more than 1 minute in the past. Because, we will
+ // stop displaying anything.
+ if (durationRemaining.inWholeMilliseconds < -1.minutes.inWholeMilliseconds) {
+ break
+ }
+ val delaySkewMillis = (currentTime - startTimeMillis) % 1000L
+ delay(calculateNextUpdateDelay(durationRemaining) - delaySkewMillis)
+ }
+ }
+
+ private fun calculateNextUpdateDelay(duration: Duration): Long {
+ val durationAbsolute = duration.absoluteValue
+ return when {
+ durationAbsolute.inWholeHours < 1 -> {
+ 1000 + ((durationAbsolute.inWholeMilliseconds % 1.minutes.inWholeMilliseconds))
+ }
+ durationAbsolute.inWholeHours < 24 -> {
+ 1000 + (durationAbsolute.inWholeMilliseconds % 1.hours.inWholeMilliseconds)
+ }
+ else -> 1000 + (durationAbsolute.inWholeMilliseconds % 24.hours.inWholeMilliseconds)
+ }
+ }
+}
+
+/** Remember and manage the TimeRemainingState */
+@Composable
+fun rememberTimeRemainingState(
+ futureTimeMillis: Long,
+ timeSource: TimeSource = remember { TimeSource { SystemClock.elapsedRealtime() } },
+): TimeRemainingState {
+
+ val state =
+ remember(timeSource, futureTimeMillis) { TimeRemainingState(timeSource, futureTimeMillis) }
+ val lifecycleOwner = LocalLifecycleOwner.current
+ LaunchedEffect(lifecycleOwner, timeSource, futureTimeMillis) {
+ lifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { state.run() }
+ }
+
+ return state
+}
+
+private fun getTimeRemainingData(duration: Duration): Pair<Int, Long?>? {
+ return when {
+ duration.inWholeMinutes <= -1 -> null
+ duration.inWholeMinutes < 1 -> Pair(com.android.internal.R.string.now_string_shortest, null)
+ duration.inWholeHours < 1 ->
+ Pair(com.android.internal.R.string.duration_minutes_medium, duration.inWholeMinutes)
+ duration.inWholeDays < 1 ->
+ Pair(com.android.internal.R.string.duration_hours_medium, duration.inWholeHours)
+ else -> null
+ }
+}
+
+/** Formats the time remaining data into a user-readable string. */
+@Composable
+fun formatTimeRemainingData(resourcePair: Pair<Int, Long?>): String {
+ return resourcePair.let { (resourceId, time) ->
+ when (time) {
+ null -> stringResource(resourceId)
+ else -> stringResource(resourceId, time.toInt())
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/StatusEvent.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/StatusEvent.kt
index ea1d7820c79c..5887eb6ad865 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/events/StatusEvent.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/StatusEvent.kt
@@ -25,6 +25,8 @@ import com.android.systemui.privacy.OngoingPrivacyChip
import com.android.systemui.privacy.PrivacyItem
import com.android.systemui.statusbar.BatteryStatusChip
import com.android.systemui.statusbar.ConnectedDisplayChip
+import com.android.systemui.statusbar.core.NewStatusBarIcons
+import com.android.systemui.statusbar.events.ui.view.BatteryStatusEventComposeChip
typealias ViewCreator = (context: Context) -> BackgroundAnimatableView
@@ -53,9 +55,7 @@ interface StatusEvent {
}
}
-class BGView(
- context: Context
-) : View(context), BackgroundAnimatableView {
+class BGView(context: Context) : View(context), BackgroundAnimatableView {
override val view: View
get() = this
@@ -65,9 +65,7 @@ class BGView(
}
@SuppressLint("AppCompatCustomView")
-class BGImageView(
- context: Context
-) : ImageView(context), BackgroundAnimatableView {
+class BGImageView(context: Context) : ImageView(context), BackgroundAnimatableView {
override val view: View
get() = this
@@ -84,8 +82,10 @@ class BatteryEvent(@IntRange(from = 0, to = 100) val batteryLevel: Int) : Status
override val shouldAnnounceAccessibilityEvent: Boolean = false
override val viewCreator: ViewCreator = { context ->
- BatteryStatusChip(context).apply {
- setBatteryLevel(batteryLevel)
+ if (NewStatusBarIcons.isEnabled) {
+ BatteryStatusEventComposeChip(batteryLevel, context)
+ } else {
+ BatteryStatusChip(context).apply { setBatteryLevel(batteryLevel) }
}
}
@@ -103,9 +103,7 @@ class ConnectedDisplayEvent : StatusEvent {
override var contentDescription: String? = ""
override val shouldAnnounceAccessibilityEvent: Boolean = true
- override val viewCreator: ViewCreator = { context ->
- ConnectedDisplayChip(context)
- }
+ override val viewCreator: ViewCreator = { context -> ConnectedDisplayChip(context) }
override fun toString(): String {
return javaClass.simpleName
@@ -134,7 +132,8 @@ open class PrivacyEvent(override val showAnimation: Boolean = true) : StatusEven
}
override fun shouldUpdateFromEvent(other: StatusEvent?): Boolean {
- return other is PrivacyEvent && (other.privacyItems != privacyItems ||
+ return other is PrivacyEvent &&
+ (other.privacyItems != privacyItems ||
other.contentDescription != contentDescription ||
(other.forceVisible && !forceVisible))
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/ui/view/BatteryStatusEventComposeChip.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/ui/view/BatteryStatusEventComposeChip.kt
new file mode 100644
index 000000000000..a90e3ff4b2dc
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/ui/view/BatteryStatusEventComposeChip.kt
@@ -0,0 +1,100 @@
+/*
+ * 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.events.ui.view
+
+import android.annotation.SuppressLint
+import android.content.Context
+import android.util.AttributeSet
+import android.view.View
+import android.widget.FrameLayout
+import android.widget.LinearLayout
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.width
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.ComposeView
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.core.NewStatusBarIcons
+import com.android.systemui.statusbar.events.BackgroundAnimatableView
+import com.android.systemui.statusbar.pipeline.battery.domain.interactor.BatteryInteractor
+import com.android.systemui.statusbar.pipeline.battery.shared.ui.BatteryColors.LightThemeChargingColors
+import com.android.systemui.statusbar.pipeline.battery.shared.ui.BatteryFrame
+import com.android.systemui.statusbar.pipeline.battery.shared.ui.BatteryGlyph
+import com.android.systemui.statusbar.pipeline.battery.ui.composable.BatteryCanvas
+import com.android.systemui.statusbar.pipeline.battery.ui.viewmodel.BatteryViewModel
+import com.android.systemui.statusbar.pipeline.battery.ui.viewmodel.BatteryViewModel.Companion.glyphRepresentation
+
+/**
+ * [StatusEvent] chip for the battery plugged in status event. Shows the current battery level and
+ * charging state in the status bar via the system event animation.
+ *
+ * This chip will fully replace [BatteryStatusChip] when [NewStatusBarIcons] is rolled out
+ */
+@SuppressLint("ViewConstructor")
+class BatteryStatusEventComposeChip
+@JvmOverloads
+constructor(level: Int, context: Context, attrs: AttributeSet? = null) :
+ FrameLayout(context, attrs), BackgroundAnimatableView {
+ private val roundedContainer: LinearLayout
+ private val composeInner: ComposeView
+ override val contentView: View
+ get() = composeInner
+
+ init {
+ NewStatusBarIcons.assertInNewMode()
+
+ inflate(context, R.layout.status_bar_event_chip_compose, this)
+ roundedContainer = requireViewById(R.id.rounded_container)
+ composeInner = requireViewById(R.id.compose_view)
+ composeInner.apply {
+ setContent {
+ val isFull = BatteryInteractor.isBatteryFull(level)
+ BatteryCanvas(
+ modifier =
+ Modifier.width(BatteryViewModel.STATUS_BAR_BATTERY_WIDTH)
+ .height(BatteryViewModel.STATUS_BAR_BATTERY_HEIGHT),
+ path = BatteryFrame.pathSpec,
+ // TODO(b/394659067): get a content description for this chip
+ contentDescription = "",
+ innerWidth = BatteryFrame.innerWidth,
+ innerHeight = BatteryFrame.innerHeight,
+ // This event only happens when plugged in, so we always show it as charging
+ glyphs =
+ if (isFull) listOf(BatteryGlyph.Bolt)
+ else level.glyphRepresentation() + BatteryGlyph.Bolt,
+ level = level,
+ isFull = isFull,
+ colorsProvider = { LightThemeChargingColors },
+ )
+ }
+ }
+ updateResources()
+ }
+
+ /**
+ * When animating as a chip in the status bar, we want to animate the width for the rounded
+ * container. We have to subtract our own top and left offset because the bounds come to us as
+ * absolute on-screen bounds.
+ */
+ override fun setBoundsForAnimation(l: Int, t: Int, r: Int, b: Int) {
+ roundedContainer.setLeftTopRightBottom(l - left, t - top, r - left, b - top)
+ }
+
+ @SuppressLint("UseCompatLoadingForDrawables")
+ private fun updateResources() {
+ roundedContainer.background = mContext.getDrawable(R.drawable.statusbar_chip_bg)
+ }
+}
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/icon/IconManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt
index 31375cc4a03a..c512b43c91a3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt
@@ -222,10 +222,6 @@ constructor(
return@traceSection
}
- if (StatusBarConnectedDisplays.isEnabled) {
- onIconUpdateRequiredListeners.onEach { it.onIconUpdateRequired(entry) }
- }
-
if (usingCache && !Flags.notificationsBackgroundIcons()) {
Log.wtf(
TAG,
@@ -238,6 +234,10 @@ constructor(
entry.icons.peopleAvatarDescriptor = null
}
+ if (StatusBarConnectedDisplays.isEnabled) {
+ onIconUpdateRequiredListeners.onEach { it.onIconUpdateRequired(entry) }
+ }
+
val (normalIconDescriptor, sensitiveIconDescriptor) = getIconDescriptors(entry)
val notificationContentDescription =
entry.sbn.notification?.let { iconBuilder.getIconContentDescription(it) }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractor.kt
index f02edee399eb..18a1afa17720 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractor.kt
@@ -21,6 +21,7 @@ import com.android.systemui.statusbar.data.repository.NotificationListenerSettin
import com.android.systemui.statusbar.notification.data.repository.NotificationsKeyguardViewStateRepository
import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor
import com.android.systemui.statusbar.notification.domain.interactor.HeadsUpNotificationIconInteractor
+import com.android.systemui.statusbar.notification.promoted.domain.interactor.AODPromotedNotificationInteractor
import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel
import com.android.wm.shell.bubbles.Bubbles
import java.util.Optional
@@ -30,6 +31,7 @@ import kotlin.jvm.optionals.getOrNull
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.flowOn
/** Domain logic related to notification icons. */
@@ -39,8 +41,21 @@ constructor(
private val activeNotificationsInteractor: ActiveNotificationsInteractor,
private val bubbles: Optional<Bubbles>,
private val headsUpNotificationIconInteractor: HeadsUpNotificationIconInteractor,
+ private val aodPromotedNotificationInteractor: AODPromotedNotificationInteractor,
private val keyguardViewStateRepository: NotificationsKeyguardViewStateRepository,
) {
+ private val aodPromotedKeyToHide: Flow<String?> =
+ combine(
+ aodPromotedNotificationInteractor.content,
+ aodPromotedNotificationInteractor.isPresent,
+ ) { content, isPresent ->
+ when {
+ !isPresent -> null
+ content == null -> null
+ else -> content.identity.key
+ }
+ }
+
/** Returns a subset of all active notifications based on the supplied filtration parameters. */
fun filteredNotifSet(
forceShowHeadsUp: Boolean = false,
@@ -49,12 +64,14 @@ constructor(
showDismissed: Boolean = true,
showRepliedMessages: Boolean = true,
showPulsing: Boolean = true,
+ showAodPromoted: Boolean = true,
): Flow<Set<ActiveNotificationModel>> {
return combine(
activeNotificationsInteractor.topLevelRepresentativeNotifications,
headsUpNotificationIconInteractor.isolatedNotification,
+ if (showAodPromoted) flowOf(null) else aodPromotedKeyToHide,
keyguardViewStateRepository.areNotificationsFullyHidden,
- ) { notifications, isolatedNotifKey, notifsFullyHidden ->
+ ) { notifications, isolatedNotifKey, aodPromotedKeyToHide, notifsFullyHidden ->
notifications
.asSequence()
.filter { model: ActiveNotificationModel ->
@@ -67,6 +84,7 @@ constructor(
showRepliedMessages = showRepliedMessages,
showPulsing = showPulsing,
isolatedNotifKey = isolatedNotifKey,
+ aodPromotedKeyToHide = aodPromotedKeyToHide,
notifsFullyHidden = notifsFullyHidden,
)
}
@@ -83,6 +101,7 @@ constructor(
showRepliedMessages: Boolean,
showPulsing: Boolean,
isolatedNotifKey: String?,
+ aodPromotedKeyToHide: String?,
notifsFullyHidden: Boolean,
): Boolean {
return when {
@@ -93,6 +112,7 @@ constructor(
!showRepliedMessages && model.isLastMessageFromReply -> false
!showAmbient && model.isSuppressedFromStatusBar -> false
!showPulsing && model.isPulsing && !notifsFullyHidden -> false
+ model.key == aodPromotedKeyToHide -> false
bubbles.getOrNull()?.isBubbleExpanded(model.key) == true -> false
else -> true
}
@@ -115,6 +135,7 @@ constructor(
showDismissed = false,
showRepliedMessages = false,
showPulsing = !isBypassEnabled,
+ showAodPromoted = false,
)
}
.flowOn(bgContext)
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 777ffda8c87d..e5d2361e8524 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
@@ -23,6 +23,7 @@ import android.view.LayoutInflater
import android.view.View
import android.view.View.GONE
import android.view.View.VISIBLE
+import android.view.ViewGroup.MarginLayoutParams
import android.view.ViewStub
import android.widget.Chronometer
import android.widget.DateTimeView
@@ -38,7 +39,6 @@ import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.runtime.Composable
import androidx.compose.runtime.key
import androidx.compose.ui.Modifier
-import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.res.dimensionResource
import androidx.compose.ui.unit.dp
@@ -164,8 +164,8 @@ private class AODPromotedNotificationViewUpdater(root: View) {
private var chronometerStub: ViewStub? = root.findViewById(R.id.chronometer)
private var chronometer: Chronometer? = null
private val closeButton: View? = root.findViewById(R.id.close_button)
- private val conversationIconContainer: View? =
- root.findViewById(R.id.conversation_icon_container)
+ private val conversationIconBadge: View? = root.findViewById(R.id.conversation_icon_badge)
+ private val conversationIcon: CachingIconView? = root.findViewById(R.id.conversation_icon)
private val conversationText: TextView? = root.findViewById(R.id.conversation_text)
private val expandButton: NotificationExpandButton? = root.findViewById(R.id.expand_button)
private val headerText: TextView? = root.findViewById(R.id.header_text)
@@ -175,12 +175,13 @@ private class AODPromotedNotificationViewUpdater(root: View) {
root.findViewById(R.id.header_text_secondary_divider)
private val icon: NotificationRowIconView? = root.findViewById(R.id.icon)
private val leftIcon: ImageView? = root.findViewById(R.id.left_icon)
- private val rightIcon: ImageView? = root.findViewById(R.id.right_icon)
+ private val mainColumn: View? = root.findViewById(R.id.notification_main_column)
private val notificationProgressEndIcon: CachingIconView? =
root.findViewById(R.id.notification_progress_end_icon)
private val notificationProgressStartIcon: CachingIconView? =
root.findViewById(R.id.notification_progress_start_icon)
private val profileBadge: ImageView? = root.findViewById(R.id.profile_badge)
+ private val rightIcon: ImageView? = root.findViewById(R.id.right_icon)
private val text: ImageFloatingTextView? = root.findViewById(R.id.text)
private val time: DateTimeView? = root.findViewById(R.id.time)
private val timeDivider: View? = root.findViewById(R.id.time_divider)
@@ -198,7 +199,7 @@ private class AODPromotedNotificationViewUpdater(root: View) {
alternateExpandTarget?.visibility = GONE
bigPicture?.visibility = GONE
closeButton?.visibility = GONE
- conversationIconContainer?.visibility = GONE
+ conversationIconBadge?.visibility = GONE
expandButton?.visibility = GONE
leftIcon?.visibility = GONE
notificationProgressEndIcon?.visibility = GONE
@@ -209,6 +210,16 @@ private class AODPromotedNotificationViewUpdater(root: View) {
?.drawable
?.mutate()
?.setColorFilter(SecondaryText.colorInt, PorterDuff.Mode.SRC_IN)
+
+ if (Flags.notificationsRedesignTemplates()) {
+ (mainColumn?.layoutParams as? MarginLayoutParams)?.let { mainColumnMargins ->
+ mainColumnMargins.topMargin =
+ Notification.Builder.getContentMarginTop(
+ root.context,
+ R.dimen.notification_2025_content_margin_top,
+ )
+ }
+ }
}
fun update(content: PromotedNotificationContentModel, audiblyAlertedIconVisible: Boolean) {
@@ -229,16 +240,11 @@ private class AODPromotedNotificationViewUpdater(root: View) {
textView: ImageFloatingTextView? = null,
showOldProgress: Boolean = true,
) {
- // Icon binding must be called in this order
- updateImageView(icon, content.smallIcon)
- icon?.setImageLevel(content.iconLevel)
- icon?.setBackgroundColor(Background.colorInt)
- icon?.originalIconColor = PrimaryText.colorInt
-
updateHeader(content, hideTitle = true)
updateTitle(title, content)
updateText(textView ?: text, content)
+ updateSmallIcon(icon, content)
updateImageView(rightIcon, content.skeletonLargeIcon)
if (showOldProgress) {
@@ -341,6 +347,8 @@ private class AODPromotedNotificationViewUpdater(root: View) {
updateImageView(verificationIcon, content.verificationIcon)
updateTextView(verificationText, content.verificationText)
+
+ updateSmallIcon(conversationIcon, content)
}
private fun updateConversationHeaderDividers(
@@ -398,6 +406,19 @@ private class AODPromotedNotificationViewUpdater(root: View) {
}
}
+ private fun updateSmallIcon(
+ smallIconView: CachingIconView?,
+ content: PromotedNotificationContentModel,
+ ) {
+ smallIconView ?: return
+
+ // Icon binding must be called in this order
+ updateImageView(smallIconView, content.smallIcon)
+ smallIconView.setImageLevel(content.iconLevel)
+ smallIconView.setBackgroundColor(Background.colorInt)
+ smallIconView.originalIconColor = PrimaryText.colorInt
+ }
+
private fun inflateChronometer() {
if (chronometer != null) {
return
@@ -480,14 +501,12 @@ private fun Notification.ProgressStyle.Point.toSkeleton(): Notification.Progress
}
}
-private enum class AodPromotedNotificationColor(colorUInt: UInt) {
- Background(0xFF000000u),
- PrimaryText(0xFFFFFFFFu),
- SecondaryText(0xFFCCCCCCu);
+private enum class AodPromotedNotificationColor(val colorInt: Int) {
+ Background(android.graphics.Color.BLACK),
+ PrimaryText(android.graphics.Color.WHITE),
+ SecondaryText(android.graphics.Color.WHITE);
- val colorInt = colorUInt.toInt()
- val color = Color(colorInt)
- val brush = SolidColor(color)
+ val brush = SolidColor(androidx.compose.ui.graphics.Color(colorInt))
}
private val viewUpdaterTagId = systemuiR.id.aod_promoted_notification_view_updater_tag
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 0f21514fcc94..393f95d3ad77 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
@@ -17,8 +17,11 @@
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
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
@@ -26,9 +29,17 @@ import kotlinx.coroutines.flow.map
@SysUISingleton
class AODPromotedNotificationInteractor
@Inject
-constructor(activeNotificationsInteractor: ActiveNotificationsInteractor) {
+constructor(
+ activeNotificationsInteractor: ActiveNotificationsInteractor,
+ dumpManager: DumpManager,
+) : FlowDumperImpl(dumpManager) {
val content: Flow<PromotedNotificationContentModel?> =
activeNotificationsInteractor.topLevelRepresentativeNotifications.map { notifs ->
notifs.firstNotNullOfOrNull { it.promotedContent }
}
+
+ val isPresent: Flow<Boolean> =
+ content
+ .map { (it != null) && (it.style != Style.Ineligible) }
+ .dumpWhileCollecting("isPresent")
}
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..d1d1ea9b5ff4 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
@@ -2740,6 +2740,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) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/MagicActionBackgroundDrawable.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/MagicActionBackgroundDrawable.kt
index 6aa5e405f29c..77135802eced 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/MagicActionBackgroundDrawable.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/MagicActionBackgroundDrawable.kt
@@ -18,243 +18,126 @@ package com.android.systemui.statusbar.notification.row
import android.animation.ValueAnimator
import android.content.Context
-import android.graphics.BlendMode
import android.graphics.Canvas
-import android.graphics.Color
import android.graphics.ColorFilter
-import android.graphics.LinearGradient
import android.graphics.Paint
import android.graphics.Path
import android.graphics.PixelFormat
+import android.graphics.drawable.Drawable
+import android.graphics.LinearGradient
+import android.graphics.Matrix
import android.graphics.Rect
-import android.graphics.RuntimeShader
+import android.graphics.RectF
import android.graphics.Shader
-import android.graphics.drawable.Drawable
-import android.widget.FrameLayout
-import androidx.compose.foundation.layout.size
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.unit.dp
-import androidx.compose.ui.viewinterop.AndroidView
-import com.android.internal.graphics.ColorUtils
import com.android.systemui.res.R
-import com.android.systemui.surfaceeffects.shaderutil.ShaderUtilLibrary
import com.android.wm.shell.shared.animation.Interpolators
-import kotlin.math.min
-/**
- * A background style for smarter-smart-actions. The style is composed by a simplex3d noise,
- * overlaid with sparkles.
- */
class MagicActionBackgroundDrawable(
context: Context,
- primaryContainer: Int? = null,
- seed: Float = 0f,
) : Drawable() {
- private val pixelDensity = context.resources.displayMetrics.density
- private val cornerRadius =
- context.resources.getDimensionPixelSize(R.dimen.smart_reply_button_corner_radius).toFloat()
- private val outlineStrokeWidth =
- context.resources
- .getDimensionPixelSize(R.dimen.smart_action_button_outline_stroke_width)
- .toFloat()
- private val buttonShape = Path()
- private val paddingVertical =
- context.resources.getDimensionPixelSize(R.dimen.smart_action_button_icon_padding).toFloat()
+ private val cornerRadius = context.resources.getDimension(R.dimen.magic_action_button_corner_radius)
+ private val outlineStrokeWidth = context.resources.getDimension(R.dimen.magic_action_button_outline_stroke_width)
+ private val insetVertical = 8 * context.resources.displayMetrics.density
- /** The color of the button background. */
- private val mainColor =
- primaryContainer
- ?: context.getColor(com.android.internal.R.color.materialColorPrimaryContainer)
-
- /** Slightly brighter version of [mainColor] used on the simplex noise. */
- private val effectColor: Int
- get() {
- val labColor = arrayOf(0.0, 0.0, 0.0).toDoubleArray()
- ColorUtils.colorToLAB(mainColor, labColor)
- val camColor = ColorUtils.colorToCAM(mainColor)
- return ColorUtils.CAMToColor(
- camColor.hue,
- camColor.chroma,
- min(100f, (labColor[0] + 10).toFloat()),
+ private val buttonShape = Path()
+ // Color and style
+ private val bgPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
+ val bgColor =
+ context.getColor(
+ com.android.internal.R.color.materialColorPrimaryContainer
)
- }
+ color = bgColor
+ style = Paint.Style.FILL
+ }
+ private val outlinePaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
+ val outlineColor =
+ context.getColor(
+ com.android.internal.R.color.materialColorOutlineVariant
+ )
+ color = outlineColor
+ style = Paint.Style.STROKE
+ strokeWidth = outlineStrokeWidth
+ }
+ private val outlineStartColor =
+ context.getColor(
+ com.android.internal.R.color.materialColorTertiaryContainer
+ )
+ private val outlineMiddleColor =
+ context.getColor(
+ com.android.internal.R.color.materialColorPrimaryFixedDim
+ )
+ private val outlineEndColor =
+ context.getColor(
+ com.android.internal.R.color.materialColorPrimary
+ )
+ // Animation
+ private var gradientAnimator: ValueAnimator
+ private var rotationAngle = 20f // Start rotation at 20 degrees
- private val bgShader = MagicActionBackgroundShader()
- private val bgPaint = Paint()
- private val outlinePaint = Paint()
- private val gradientAnimator =
- ValueAnimator.ofFloat(0f, 1f).apply {
- duration = 2500
- interpolator = Interpolators.LINEAR
- addUpdateListener { invalidateSelf() }
- }
- private val turbulenceAnimator =
- ValueAnimator.ofFloat(seed, seed + TURBULENCE_MOVEMENT).apply {
- duration = ANIMATION_DURATION
+ init {
+ gradientAnimator = ValueAnimator.ofFloat(0f, 1f).apply {
+ duration = 5000 // 5 seconds
interpolator = Interpolators.LINEAR
- addUpdateListener { invalidateSelf() }
+ repeatCount = 1
+ addUpdateListener { animator ->
+ val animatedValue = animator.animatedValue as Float
+ rotationAngle = 20f + animatedValue * 360f // Rotate in a spiral
+ invalidateSelf()
+ }
+ // TODO: Reset the outline color when animation ends.
start()
}
- private val effectFadeAnimation =
- ValueAnimator.ofFloat(0f, 1f).apply {
- duration = 1000
- startDelay = ANIMATION_DURATION - 1000L
- interpolator = Interpolators.STANDARD_DECELERATE
- addUpdateListener { invalidateSelf() }
- }
-
- init {
- bgShader.setColorUniform("in_color", mainColor)
- bgShader.setColorUniform("in_effectColor", effectColor)
- bgPaint.shader = bgShader
- outlinePaint.style = Paint.Style.STROKE
- // Stroke is doubled in width and then clipped, to avoid anti-aliasing artifacts at the edge
- // of the rectangle.
- outlinePaint.strokeWidth = outlineStrokeWidth * 2
- outlinePaint.blendMode = BlendMode.SCREEN
- outlinePaint.alpha = OUTLINE_ALPHA
-
- animate()
- }
-
- private fun animate() {
- turbulenceAnimator.start()
- gradientAnimator.start()
- effectFadeAnimation.start()
}
override fun draw(canvas: Canvas) {
- updateShaders()
+ val boundsF = RectF(bounds)
+ boundsF.inset(0f, insetVertical)
+ buttonShape.reset()
+ buttonShape.addRoundRect(boundsF, cornerRadius, cornerRadius, Path.Direction.CW)
- // We clip instead of drawing 2 rounded rects, otherwise there will be artifacts where
- // around the button background and the outline.
canvas.save()
+ // Draw background
canvas.clipPath(buttonShape)
canvas.drawPath(buttonShape, bgPaint)
+ // Apply gradient to outline
canvas.drawPath(buttonShape, outlinePaint)
+ updateGradient(boundsF)
canvas.restore()
}
- private fun updateShaders() {
- val effectAlpha = 1f - effectFadeAnimation.animatedValue as Float
- val turbulenceZ = turbulenceAnimator.animatedValue as Float
- bgShader.setFloatUniform("in_sparkleMove", turbulenceZ * 1000)
- bgShader.setFloatUniform("in_noiseMove", 0f, 0f, turbulenceZ)
- bgShader.setFloatUniform("in_turbulenceAlpha", effectAlpha)
- bgShader.setFloatUniform("in_spkarkleAlpha", SPARKLE_ALPHA * effectAlpha)
- val gradientOffset = gradientAnimator.animatedValue as Float * bounds.width()
- val outlineGradient =
- LinearGradient(
- gradientOffset + bounds.left.toFloat(),
- 0f,
- gradientOffset + bounds.right.toFloat(),
- 0f,
- mainColor,
- ColorUtils.setAlphaComponent(mainColor, 0),
- Shader.TileMode.MIRROR,
- )
- outlinePaint.shader = outlineGradient
+ private fun updateGradient(boundsF: RectF) {
+ val gradient = LinearGradient(
+ boundsF.left, boundsF.top,
+ boundsF.right, boundsF.bottom,
+ intArrayOf(outlineStartColor, outlineMiddleColor, outlineEndColor),
+ null,
+ Shader.TileMode.CLAMP
+ )
+ // Create a rotation matrix for the spiral effect
+ val matrix = Matrix()
+ matrix.setRotate(rotationAngle, boundsF.centerX(), boundsF.centerY())
+ gradient.setLocalMatrix(matrix)
+
+ outlinePaint.shader = gradient
}
override fun onBoundsChange(bounds: Rect) {
super.onBoundsChange(bounds)
-
- val width = bounds.width().toFloat()
- val height = bounds.height().toFloat()
- if (width == 0f || height == 0f) return
-
- bgShader.setFloatUniform("in_gridNum", NOISE_SIZE)
- bgShader.setFloatUniform("in_size", width, height)
- bgShader.setFloatUniform("in_aspectRatio", width / height)
- bgShader.setFloatUniform("in_pixelDensity", pixelDensity)
-
- buttonShape.reset()
- buttonShape.addRoundRect(
- bounds.left.toFloat(),
- bounds.top + paddingVertical,
- bounds.right.toFloat(),
- bounds.bottom - paddingVertical,
- cornerRadius,
- cornerRadius,
- Path.Direction.CW,
- )
+ invalidateSelf() // Redraw when size changes
}
override fun setAlpha(alpha: Int) {
bgPaint.alpha = alpha
+ outlinePaint.alpha = alpha
invalidateSelf()
}
override fun setColorFilter(colorFilter: ColorFilter?) {
bgPaint.colorFilter = colorFilter
+ outlinePaint.colorFilter = colorFilter
invalidateSelf()
}
override fun getOpacity(): Int = PixelFormat.TRANSLUCENT
-
- companion object {
- /** Smoothness of the turbulence. Larger numbers yield more detail. */
- private const val NOISE_SIZE = 0.57f
- /** Strength of the sparkles overlaid on the turbulence. */
- private const val SPARKLE_ALPHA = 0.15f
- /** Alpha (0..255) of the button outline */
- private const val OUTLINE_ALPHA = 82
- /** Turbulence grid size */
- private const val TURBULENCE_MOVEMENT = 4.3f
- /** Total animation duration in millis */
- private const val ANIMATION_DURATION = 5000L
- }
-}
-
-private class MagicActionBackgroundShader : RuntimeShader(SHADER) {
-
- // language=AGSL
- companion object {
- private const val UNIFORMS =
- """
- uniform float in_gridNum;
- uniform vec3 in_noiseMove;
- uniform half in_sparkleMove;
- uniform vec2 in_size;
- uniform float in_aspectRatio;
- uniform half in_pixelDensity;
- uniform float in_turbulenceAlpha;
- uniform float in_spkarkleAlpha;
- layout(color) uniform vec4 in_color;
- layout(color) uniform vec4 in_effectColor;
- """
- private const val MAIN_SHADER =
- """vec4 main(vec2 p) {
- vec2 uv = p / in_size.xy;
- uv.x *= in_aspectRatio;
- vec3 noiseP = vec3(uv + in_noiseMove.xy, in_noiseMove.z) * in_gridNum;
- half luma = getLuminosity(half3(simplex3d(noiseP)));
- half4 turbulenceColor = mix(in_color, in_effectColor, luma * in_turbulenceAlpha);
- float sparkle = sparkles(p - mod(p, in_pixelDensity * 0.8), in_sparkleMove);
- sparkle = min(sparkle * in_spkarkleAlpha, in_spkarkleAlpha);
- return saturate(turbulenceColor + half4(sparkle));
- }
- """
- private const val SHADER = UNIFORMS + ShaderUtilLibrary.SHADER_LIB + MAIN_SHADER
- }
-}
-
-// @Preview
-@Composable
-fun DrawablePreview() {
- AndroidView(
- factory = { context ->
- FrameLayout(context).apply {
- background =
- MagicActionBackgroundDrawable(
- context = context,
- primaryContainer = Color.parseColor("#c5eae2"),
- seed = 0f,
- )
- }
- },
- modifier = Modifier.size(100.dp, 50.dp),
- )
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/MagicActionButton.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/MagicActionButton.kt
new file mode 100644
index 000000000000..d735360f1d4e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/MagicActionButton.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.row
+
+import android.annotation.SuppressLint
+import android.content.Context
+import android.util.AttributeSet
+import android.widget.Button
+
+/**
+ * Custom Button for Magic Action Button, which includes the custom background and foreground.
+ */
+@SuppressLint("AppCompatCustomView")
+class MagicActionButton @JvmOverloads constructor(
+ context: Context,
+ attrs: AttributeSet? = null,
+ defStyleAttr: Int = 0,
+) : Button(context, attrs, defStyleAttr) {
+ init {
+ background = MagicActionBackgroundDrawable(context)
+ }
+}
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/PromotedNotificationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PromotedNotificationInfo.java
index 93b2a2d41691..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
@@ -40,6 +42,7 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntry;
public class PromotedNotificationInfo extends NotificationInfo {
private static final String TAG = "PromotedNotifInfoGuts";
private INotificationManager mNotificationManager;
+ private NotificationGuts mGutsContainer;
public PromotedNotificationInfo(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -49,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,
@@ -63,40 +68,35 @@ 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;
- bindDismiss(entry.getSbn(), onCloseClick);
bindDemote(entry.getSbn(), pkg);
}
-
- protected void bindDismiss(StatusBarNotification sbn,
- View.OnClickListener onCloseClick) {
- View dismissButton = findViewById(R.id.promoted_dismiss);
-
- dismissButton.setOnClickListener(onCloseClick);
- dismissButton.setVisibility(!sbn.isNonDismissable()
- && dismissButton.hasOnClickListeners() ? VISIBLE : GONE);
-
- }
-
protected void bindDemote(StatusBarNotification sbn, String packageName) {
View demoteButton = findViewById(R.id.promoted_demote);
demoteButton.setOnClickListener(getDemoteClickListener(sbn, packageName));
demoteButton.setVisibility(demoteButton.hasOnClickListeners() ? VISIBLE : GONE);
}
+ @Override
+ public void setGutsParent(NotificationGuts guts) {
+ mGutsContainer = guts;
+ super.setGutsParent(guts);
+ }
+
private OnClickListener getDemoteClickListener(StatusBarNotification sbn, String packageName) {
- return ((View unusedView) -> {
+ return ((View v) -> {
try {
// TODO(b/391661009): Signal AutomaticPromotionCoordinator here
mNotificationManager.setCanBePromoted(packageName, sbn.getUid(), false, true);
+ mGutsContainer.closeControls(v, true);
} catch (RemoteException e) {
Log.e(TAG, "Couldn't revoke live update permission", e);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/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/row/wrapper/NotificationHeaderViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java
index e9eecdd8a26f..e8affaa4b60f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java
@@ -31,6 +31,7 @@ import android.view.ViewGroup;
import android.view.animation.Interpolator;
import android.view.animation.PathInterpolator;
import android.widget.DateTimeView;
+import android.widget.FrameLayout;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.TextView;
@@ -41,6 +42,7 @@ import com.android.app.animation.Interpolators;
import com.android.internal.widget.CachingIconView;
import com.android.internal.widget.NotificationCloseButton;
import com.android.internal.widget.NotificationExpandButton;
+import com.android.systemui.Flags;
import com.android.systemui.res.R;
import com.android.systemui.statusbar.TransformableView;
import com.android.systemui.statusbar.ViewTransformationHelper;
@@ -67,6 +69,7 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper imple
private CachingIconView mIcon;
private NotificationCloseButton mCloseButton;
private NotificationExpandButton mExpandButton;
+ private FrameLayout mExpandButtonSpacer;
private View mAltExpandTarget;
private View mIconContainer;
protected NotificationHeaderView mNotificationHeader;
@@ -154,6 +157,10 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper imple
mHeaderText = mView.findViewById(com.android.internal.R.id.header_text);
mAppNameText = mView.findViewById(com.android.internal.R.id.app_name_text);
mExpandButton = mView.findViewById(com.android.internal.R.id.expand_button);
+ if (Flags.uiRichOngoingForceExpanded()) {
+ mExpandButtonSpacer =
+ mView.findViewById(com.android.internal.R.id.expand_button_spacer);
+ }
mAltExpandTarget = mView.findViewById(com.android.internal.R.id.alternate_expand_target);
mIconContainer = mView.findViewById(com.android.internal.R.id.conversation_icon_container);
mWorkProfileImage = mView.findViewById(com.android.internal.R.id.profile_badge);
@@ -295,6 +302,9 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper imple
boolean expandable,
View.OnClickListener onClickListener,
boolean requestLayout) {
+ if (Flags.uiRichOngoingForceExpanded() && mExpandButtonSpacer != null) {
+ mExpandButtonSpacer.setVisibility(expandable ? GONE : VISIBLE);
+ }
mExpandButton.setVisibility(expandable ? VISIBLE : GONE);
mExpandButton.setOnClickListener(expandable ? onClickListener : null);
if (mAltExpandTarget != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MagneticNotificationRowManagerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MagneticNotificationRowManagerImpl.kt
index 5a29a699a7e6..a507c4ceecd2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MagneticNotificationRowManagerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MagneticNotificationRowManagerImpl.kt
@@ -190,7 +190,8 @@ constructor(
currentMagneticListeners.forEachIndexed { i, target ->
target?.let {
if (i != currentMagneticListeners.size / 2) {
- snapBack(it, velocity)
+ val velocityMultiplier = MAGNETIC_TRANSLATION_MULTIPLIERS[i]
+ snapBack(it, velocity?.times(velocityMultiplier))
}
}
}
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 42d02e10ab8d..b9352bf64be4 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
@@ -52,6 +52,9 @@ import android.graphics.Outline;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Rect;
+import android.graphics.RenderEffect;
+import android.graphics.RenderNode;
+import android.graphics.Shader;
import android.os.Bundle;
import android.os.SystemClock;
import android.os.Trace;
@@ -513,6 +516,13 @@ public class NotificationStackScrollLayout
/** The clip path defining where we are NOT allowed to draw. */
private final Path mNegativeRoundedClipPath = new Path();
+ /** RenderNode to blur notifications which will be reused (redrawn) whenever NSSL is drawn. */
+ private final RenderNode mBlurNode = new RenderNode("BlurNode");
+
+ /** Radius of the blur effect applied to the content of the NSSL. */
+ private float mBlurRadius = 0f;
+ @Nullable private RenderEffect mBlurEffect = null;
+
/**
* The clip Path used to clip the launching notification. This may be different
* from the normal path, as the views launch animation could start clipped.
@@ -896,6 +906,7 @@ public class NotificationStackScrollLayout
mOverflingDistance = configuration.getScaledOverflingDistance();
Resources res = context.getResources();
+ mSwipeHelper.updateResourceProperties(res);
final boolean isSmallScreenLandscape = res.getBoolean(R.bool.is_small_screen_landscape);
boolean useSmallLandscapeLockscreenResources = mIsSmallLandscapeLockscreenEnabled
&& isSmallScreenLandscape;
@@ -2073,8 +2084,6 @@ public class NotificationStackScrollLayout
Resources res = getResources();
updateSplitNotificationShade();
mStatusBarHeight = SystemBarUtils.getStatusBarHeight(mContext);
- float densityScale = res.getDisplayMetrics().density;
- mSwipeHelper.setDensityScale(densityScale);
float pagingTouchSlop = ViewConfiguration.get(getContext()).getScaledPagingTouchSlop();
mSwipeHelper.setPagingTouchSlop(pagingTouchSlop);
reinitView();
@@ -5974,6 +5983,24 @@ public class NotificationStackScrollLayout
invalidate();
}
+ @Override
+ public void setBlurRadius(float blurRadius) {
+ if (mBlurRadius != blurRadius) {
+ mBlurRadius = blurRadius;
+ updateBlurEffect();
+ invalidate();
+ }
+ }
+
+ private void updateBlurEffect() {
+ if (mBlurRadius > 0) {
+ mBlurEffect =
+ RenderEffect.createBlurEffect(mBlurRadius, mBlurRadius, Shader.TileMode.CLAMP);
+ } else {
+ mBlurEffect = null;
+ }
+ }
+
/**
* Set rounded rect clipping bounds on this view.
*/
@@ -6144,26 +6171,64 @@ public class NotificationStackScrollLayout
@Override
protected void dispatchDraw(@NonNull Canvas canvas) {
- if (!mLaunchingNotification) {
- // When launching notifications, we're clipping the children individually instead of in
- // dispatchDraw
- if (mShouldUseRoundedRectClipping) {
- // Let's clip rounded.
- canvas.clipPath(mRoundedClipPath);
+ if (mBlurEffect != null) {
+ // reuse the cached RenderNode to blur
+ mBlurNode.setPosition(0, 0, canvas.getWidth(), canvas.getHeight());
+ mBlurNode.setRenderEffect(mBlurEffect);
+ Canvas blurCanvas = mBlurNode.beginRecording();
+ // draw all the children (except HUNs) on the blurred canvas
+ super.dispatchDraw(blurCanvas);
+ mBlurNode.endRecording();
+ // apply clipping to the canvas
+ int saveCount = canvas.save();
+ applyClipToCanvas(canvas);
+ // draw the blurred content to the clipped canvas
+ canvas.drawRenderNode(mBlurNode);
+ // restore the canvas, so it doesn't clip anymore
+ canvas.restoreToCount(saveCount);
+ // draw the children that were left out during the dispatchDraw phase
+ for (int i = 0; i < getChildCount(); i++) {
+ // TODO(b/388469101) draw these children in z-order
+ ExpandableView child = getChildAtIndex(i);
+ if (shouldSkipBlurForChild(child)) {
+ super.drawChild(canvas, child, getDrawingTime());
+ }
}
- if (mShouldUseNegativeRoundedRectClipping) {
- // subtract the negative path if it is defined
- canvas.clipOutPath(mNegativeRoundedClipPath);
+ } else {
+ if (!mLaunchingNotification) {
+ // When launching notifications, we're clipping the children individually instead
+ // of in dispatchDraw
+ applyClipToCanvas(canvas);
}
+ super.dispatchDraw(canvas);
+ }
+ }
+
+ private void applyClipToCanvas(Canvas canvas) {
+ if (mShouldUseRoundedRectClipping) {
+ // clip by the positive path if it is defined
+ canvas.clipPath(mRoundedClipPath);
+ }
+ if (mShouldUseNegativeRoundedRectClipping) {
+ // subtract the negative path if it is defined
+ canvas.clipOutPath(mNegativeRoundedClipPath);
}
- super.dispatchDraw(canvas);
}
@Override
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
boolean shouldUseClipping =
mShouldUseRoundedRectClipping || mShouldUseNegativeRoundedRectClipping;
- if (mLaunchingNotification && shouldUseClipping) {
+ if (mBlurEffect != null) {
+ if (shouldSkipBlurForChild(child)) {
+ // skip drawing this child during the regular dispatchDraw pass
+ return false;
+ } else {
+ // draw the child as if nothing happened, non-blurred elements shouldn't be
+ // affected by clipping either
+ return super.drawChild(canvas, child, drawingTime);
+ }
+ } else if (mLaunchingNotification && shouldUseClipping) {
// Let's clip children individually during notification launch
canvas.save();
ExpandableView expandableView = (ExpandableView) child;
@@ -6194,6 +6259,14 @@ public class NotificationStackScrollLayout
}
}
+ private boolean shouldSkipBlurForChild(View child) {
+ if (child instanceof ExpandableView row) {
+ return row.isHeadsUpState();
+ } else {
+ return false;
+ }
+ }
+
/**
* Calculate the total translation needed when dismissing.
*/
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/NotificationSwipeHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java
index 6f4047f48205..c5a846e1da05 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java
@@ -534,12 +534,6 @@ class NotificationSwipeHelper extends SwipeHelper implements NotificationSwipeAc
}
@Override
- public void setDensityScale(float densityScale) {
- super.setDensityScale(densityScale);
- mCallback.onDensityScaleChange(densityScale);
- }
-
- @Override
public void resetTouchState() {
super.resetTouchState();
mCallback.resetMagneticStates();
@@ -565,8 +559,6 @@ class NotificationSwipeHelper extends SwipeHelper implements NotificationSwipeAc
*/
float getTotalTranslationLength(View animView);
- void onDensityScaleChange(float density);
-
boolean handleSwipeableViewTranslation(SwipeableView view, float translate);
// Reset any ongoing magnetic interactions
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationScrollView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationScrollView.kt
index 5fec0965f6a0..a7305f7f27ab 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationScrollView.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationScrollView.kt
@@ -58,6 +58,13 @@ interface NotificationScrollView {
*/
fun setNegativeClippingShape(shape: ShadeScrimShape?)
+ /**
+ * Sets a blur effect on the view. A radius of 0 means no blur.
+ *
+ * @param radius blur radius in pixels
+ */
+ fun setBlurRadius(radius: Float)
+
/** set the y position in px of the top of the stack in this view's coordinates */
fun setStackTop(stackTop: Float)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt
index 43a552b516ae..a4e39cbd8388 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt
@@ -94,6 +94,7 @@ constructor(
}
}
launch { viewModel.qsExpandFraction.collectTraced { view.setQsExpandFraction(it) } }
+ launch { viewModel.blurRadius(maxBlurRadius).collect(view::setBlurRadius) }
launch {
viewModel.isShowingStackOnLockscreen.collectTraced {
view.setShowingStackOnLockscreen(it)
@@ -146,6 +147,10 @@ constructor(
}
}
+ /** blur radius to be applied when the QS panel is fully expanded */
+ private val maxBlurRadius: Flow<Int> =
+ configuration.getDimensionPixelSize(R.dimen.max_shade_content_blur_radius)
+
/** flow of the scrim clipping radius */
private val scrimRadius: Flow<Int>
get() = configuration.getDimensionPixelOffset(R.dimen.notification_scrim_corner_radius)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt
index 7f016a1cbc2e..08d98a1d53e5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt
@@ -15,6 +15,8 @@
*
*/
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
package com.android.systemui.statusbar.notification.stack.ui.viewmodel
import com.android.compose.animation.scene.ContentKey
@@ -46,11 +48,14 @@ import com.android.systemui.util.kotlin.ActivatableFlowDumperImpl
import dagger.Lazy
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.combineTransform
import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.mapNotNull
@@ -193,6 +198,37 @@ constructor(
val qsExpandFraction: Flow<Float> =
shadeInteractor.qsExpansion.dumpWhileCollecting("qsExpandFraction")
+ /** Blur radius to be applied to Notifications. */
+ fun blurRadius(maxBlurRadius: Flow<Int>) =
+ combine(blurFraction, maxBlurRadius) { fraction, maxRadius -> fraction * maxRadius }
+
+ /**
+ * Scale of the blur effect that should be applied to Notifications.
+ *
+ * 0 -> don't blur (default, removes all blur render effects) 1 -> do the full blur (apply a
+ * render effect with the max blur radius)
+ */
+ private val blurFraction: Flow<Float> =
+ if (SceneContainerFlag.isEnabled) {
+ shadeModeInteractor.shadeMode.flatMapLatest { shadeMode ->
+ when (shadeMode) {
+ ShadeMode.Dual ->
+ combineTransform(
+ shadeInteractor.shadeExpansion,
+ shadeInteractor.qsExpansion,
+ ) { notificationShadeExpansion, qsExpansion ->
+ if (notificationShadeExpansion == 0f) {
+ // Blur out notifications as the QS overlay panel expands
+ emit(qsExpansion)
+ }
+ }
+ else -> flowOf(0f)
+ }
+ }
+ } else {
+ flowOf(0f)
+ }
+
/** Whether we should close any open notification guts. */
val shouldCloseGuts: Flow<Boolean> = stackAppearanceInteractor.shouldCloseGuts
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 54efa4a2bcf2..2c8c7a1bdd44 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,6 +48,7 @@ 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
@@ -136,6 +137,7 @@ 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,
@@ -572,6 +574,7 @@ 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/BiometricUnlockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
index 04dedb629a15..9aa4c54c4292 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
@@ -275,7 +275,8 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp
DozeScrimController dozeScrimController,
KeyguardViewMediator keyguardViewMediator,
NotificationShadeWindowController notificationShadeWindowController,
- KeyguardStateController keyguardStateController, Handler handler,
+ KeyguardStateController keyguardStateController,
+ @Main Handler handler,
KeyguardUpdateMonitor keyguardUpdateMonitor,
@Main Resources resources,
KeyguardBypassController keyguardBypassController,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
index 258b7cafcdcf..66f0d4ad692e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -168,6 +168,27 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump
*/
@FloatRange(from = 0, to = 1)
private float mBouncerHiddenFraction = KeyguardBouncerConstants.EXPANSION_HIDDEN;
+ private boolean mIsBlurSupported = false;
+
+ private float getDefaultScrimAlpha(boolean ignoreCurrentState) {
+ if (Flags.bouncerUiRevamp() && mIsBlurSupported) {
+ // Hack to not make the shade transparent when shade blur is not enabled.
+ if (!Flags.notificationShadeBlur() && !ignoreCurrentState) {
+ // When we expand directly to full quick settings, shade state is KEYGUARD
+ if (mState == ScrimState.SHADE_LOCKED || (mState == ScrimState.KEYGUARD
+ && mQsExpansion == 1)) {
+ return BUSY_SCRIM_ALPHA;
+ }
+ }
+ return TRANSPARENT_BOUNCER_SCRIM_ALPHA;
+ } else {
+ return BUSY_SCRIM_ALPHA;
+ }
+ }
+
+ private float getDefaultScrimAlpha() {
+ return getDefaultScrimAlpha(false);
+ }
@IntDef(prefix = {"VISIBILITY_"}, value = {
TRANSPARENT,
@@ -230,7 +251,6 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump
private float mScrimBehindAlphaKeyguard = KEYGUARD_SCRIM_ALPHA;
static final float TRANSPARENT_BOUNCER_SCRIM_ALPHA = 0.54f;
- private float mDefaultScrimAlpha;
private float mRawPanelExpansionFraction;
private float mPanelScrimMinFraction;
@@ -329,7 +349,7 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump
DozeParameters dozeParameters,
KeyguardStateController keyguardStateController,
DelayedWakeLock.Factory delayedWakeLockFactory,
- Handler handler,
+ @Main Handler handler,
KeyguardUpdateMonitor keyguardUpdateMonitor,
DockManager dockManager,
ConfigurationController configurationController,
@@ -350,7 +370,6 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump
mLargeScreenShadeInterpolator = largeScreenShadeInterpolator;
mBlurConfig = blurConfig;
mWindowRootViewBlurInteractor = windowRootViewBlurInteractor;
- mDefaultScrimAlpha = BUSY_SCRIM_ALPHA;
mKeyguardStateController = keyguardStateController;
mDarkenWhileDragging = !mKeyguardStateController.canDismissLockScreen();
@@ -414,7 +433,7 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump
states[i].init(mScrimInFront, mScrimBehind, mDozeParameters, mDockManager,
this::isBlurCurrentlySupported);
states[i].setScrimBehindAlphaKeyguard(mScrimBehindAlphaKeyguard);
- states[i].setDefaultScrimAlpha(mDefaultScrimAlpha);
+ states[i].setDefaultScrimAlpha(getDefaultScrimAlpha());
}
mTransparentScrimBackground = notificationsScrim.getResources()
@@ -498,10 +517,9 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump
}
}
- private void updateDefaultScrimAlpha(float alpha) {
- mDefaultScrimAlpha = alpha;
+ private void updateDefaultScrimAlphas() {
for (ScrimState state : ScrimState.values()) {
- state.setDefaultScrimAlpha(mDefaultScrimAlpha);
+ state.setDefaultScrimAlpha(getDefaultScrimAlpha(true));
}
applyAndDispatchState();
}
@@ -513,13 +531,13 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump
}
private void handleBlurSupportedChanged(boolean isBlurSupported) {
+ this.mIsBlurSupported = isBlurSupported;
if (Flags.bouncerUiRevamp()) {
+ updateDefaultScrimAlphas();
if (isBlurSupported) {
- updateDefaultScrimAlpha(TRANSPARENT_BOUNCER_SCRIM_ALPHA);
ScrimState.BOUNCER_SCRIMMED.setNotifBlurRadius(mBlurConfig.getMaxBlurRadiusPx());
} else {
ScrimState.BOUNCER_SCRIMMED.setNotifBlurRadius(0f);
- updateDefaultScrimAlpha(BUSY_SCRIM_ALPHA);
}
}
if (Flags.notificationShadeBlur()) {
@@ -1003,7 +1021,7 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump
float behindFraction = getInterpolatedFraction();
behindFraction = (float) Math.pow(behindFraction, 0.8f);
mBehindAlpha = 1;
- mNotificationsAlpha = behindFraction * mDefaultScrimAlpha;
+ mNotificationsAlpha = behindFraction * getDefaultScrimAlpha();
} else {
if (Flags.notificationShadeBlur()) {
// TODO (b/390730594): match any spec for controlling alpha based on shade
@@ -1014,7 +1032,7 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump
mNotificationsTint = mState.getNotifTint();
} else {
mBehindAlpha = mLargeScreenShadeInterpolator.getBehindScrimAlpha(
- mPanelExpansionFraction * mDefaultScrimAlpha);
+ mPanelExpansionFraction * getDefaultScrimAlpha());
mNotificationsAlpha =
mLargeScreenShadeInterpolator.getNotificationScrimAlpha(
mPanelExpansionFraction);
@@ -1030,7 +1048,7 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump
final float interpolatedFraction =
BouncerPanelExpansionCalculator.aboutToShowBouncerProgress(
mBouncerHiddenFraction);
- mBehindAlpha = MathUtils.lerp(mDefaultScrimAlpha, mBehindAlpha,
+ mBehindAlpha = MathUtils.lerp(getDefaultScrimAlpha(), mBehindAlpha,
interpolatedFraction);
mBehindTint = ColorUtils.blendARGB(ScrimState.BOUNCER.getBehindTint(),
mBehindTint,
@@ -1120,7 +1138,7 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump
float behindAlpha;
int behindTint = state.getBehindTint();
if (mDarkenWhileDragging) {
- behindAlpha = MathUtils.lerp(mDefaultScrimAlpha, stateBehind,
+ behindAlpha = MathUtils.lerp(getDefaultScrimAlpha(), stateBehind,
interpolatedFract);
} else {
behindAlpha = MathUtils.lerp(0 /* start */, stateBehind,
@@ -1136,7 +1154,7 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump
}
}
if (mQsExpansion > 0) {
- behindAlpha = MathUtils.lerp(behindAlpha, mDefaultScrimAlpha, mQsExpansion);
+ behindAlpha = MathUtils.lerp(behindAlpha, getDefaultScrimAlpha(), mQsExpansion);
float tintProgress = mQsExpansion;
if (mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit()) {
// this is case of - on lockscreen - going from expanded QS to bouncer.
@@ -1653,7 +1671,7 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump
pw.println(mTransitionToLockScreenFullShadeNotificationsProgress);
pw.print(" mDefaultScrimAlpha=");
- pw.println(mDefaultScrimAlpha);
+ pw.println(getDefaultScrimAlpha());
pw.print(" mPanelExpansionFraction=");
pw.println(mPanelExpansionFraction);
pw.print(" mExpansionAffectsAlpha=");
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
index d47c0f8d59d8..2282b9702ad5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
@@ -151,7 +151,9 @@ public enum ScrimState {
@Override
public void prepare(ScrimState previousState) {
if (Flags.bouncerUiRevamp()) {
- if (previousState == SHADE_LOCKED) {
+ // Add unlocked here because scrim state is unlocked when there is an app on top of
+ // the lockscreen and shade is pulled over it.
+ if (previousState == SHADE_LOCKED || previousState == UNLOCKED) {
mBehindAlpha = previousState.getBehindAlpha();
mNotifAlpha = previousState.getNotifAlpha();
} else {
@@ -195,6 +197,15 @@ public enum ScrimState {
mNotifAlpha = Color.alpha(mNotifTint) / 255.0f;
mFrontAlpha = 0.0f;
} else {
+ if (Flags.bouncerUiRevamp()) {
+ // This is only required until shade blur flag is fully enabled, shade is always
+ // opaque when shade blur is not enabled, and mClipQsScrim is always false.
+ mBehindAlpha = 1f;
+ mNotifAlpha = 1f;
+ mFrontAlpha = 0f;
+ mBehindTint = mBackgroundColor;
+ return;
+ }
mBehindAlpha = mClipQsScrim ? 1 : mDefaultScrimAlpha;
mNotifAlpha = 1f;
mFrontAlpha = 0f;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index 01de925f3d78..bc297699c41a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -46,7 +46,6 @@ import androidx.annotation.VisibleForTesting;
import com.android.internal.util.LatencyTracker;
import com.android.internal.widget.LockPatternUtils;
-import com.android.keyguard.KeyguardMessageAreaController;
import com.android.keyguard.KeyguardSecurityModel;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.KeyguardUpdateMonitorCallback;
@@ -56,7 +55,6 @@ import com.android.keyguard.ViewMediatorCallback;
import com.android.systemui.DejankUtils;
import com.android.systemui.Flags;
import com.android.systemui.animation.back.FlingOnBackAnimationCallback;
-import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor;
import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor;
import com.android.systemui.bouncer.domain.interactor.BouncerInteractor;
import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerCallbackInteractor;
@@ -158,7 +156,6 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
private final ConfigurationController mConfigurationController;
private final NavigationModeController mNavigationModeController;
private final NotificationShadeWindowController mNotificationShadeWindowController;
- private final KeyguardMessageAreaController.Factory mKeyguardMessageAreaFactory;
private final DreamOverlayStateController mDreamOverlayStateController;
@Nullable
private final FoldAodAnimationController mFoldAodAnimationController;
@@ -328,7 +325,6 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
private float mQsExpansion;
final Set<KeyguardViewManagerCallback> mCallbacks = new HashSet<>();
- private final UdfpsOverlayInteractor mUdfpsOverlayInteractor;
private final ActivityStarter mActivityStarter;
private OnDismissAction mAfterKeyguardGoneAction;
@@ -386,7 +382,6 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
DockManager dockManager,
NotificationShadeWindowController notificationShadeWindowController,
KeyguardStateController keyguardStateController,
- KeyguardMessageAreaController.Factory keyguardMessageAreaFactory,
Optional<SysUIUnfoldComponent> sysUIUnfoldComponent,
Lazy<ShadeController> shadeController,
LatencyTracker latencyTracker,
@@ -395,7 +390,6 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
PrimaryBouncerInteractor primaryBouncerInteractor,
BouncerView primaryBouncerView,
AlternateBouncerInteractor alternateBouncerInteractor,
- UdfpsOverlayInteractor udfpsOverlayInteractor,
ActivityStarter activityStarter,
KeyguardTransitionInteractor keyguardTransitionInteractor,
KeyguardDismissTransitionInteractor keyguardDismissTransitionInteractor,
@@ -423,7 +417,6 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
mKeyguardUpdateManager = keyguardUpdateMonitor;
mStatusBarStateController = sysuiStatusBarStateController;
mDockManager = dockManager;
- mKeyguardMessageAreaFactory = keyguardMessageAreaFactory;
mShadeController = shadeController;
mLatencyTracker = latencyTracker;
mKeyguardSecurityModel = keyguardSecurityModel;
@@ -434,7 +427,6 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
.map(SysUIUnfoldComponent::getFoldAodAnimationController).orElse(null);
mAlternateBouncerInteractor = alternateBouncerInteractor;
mBouncerInteractor = bouncerInteractor;
- mUdfpsOverlayInteractor = udfpsOverlayInteractor;
mActivityStarter = activityStarter;
mKeyguardTransitionInteractor = keyguardTransitionInteractor;
mKeyguardDismissTransitionInteractor = keyguardDismissTransitionInteractor;
@@ -1581,6 +1573,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
&& mPrimaryBouncerView.getDelegate().dispatchBackKeyEventPreIme();
}
+ @Override
public void readyForKeyguardDone() {
mViewMediatorCallback.readyForKeyguardDone();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
index ded964d8a1cc..6c8e1825ea0a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
@@ -57,6 +57,7 @@ import com.android.systemui.animation.ActivityTransitionAnimator;
import com.android.systemui.assist.AssistManager;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Background;
+import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.power.domain.interactor.PowerInteractor;
import com.android.systemui.settings.UserTracker;
@@ -155,7 +156,7 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
@Inject
StatusBarNotificationActivityStarter(
@ShadeDisplayAware Context context,
- Handler mainThreadHandler,
+ @Main Handler mainThreadHandler,
@Background Executor uiBgExecutor,
NotificationVisibilityProvider visibilityProvider,
HeadsUpManager headsUpManager,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
index 76f10b1c2a8b..0d43789e95a8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
@@ -20,6 +20,7 @@ import com.android.internal.jank.InteractionJankMonitor.CUJ_SCREEN_OFF_SHOW_AOD
import com.android.systemui.DejankUtils
import com.android.systemui.Flags.lightRevealMigration
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.keyguard.KeyguardViewMediator
import com.android.systemui.keyguard.WakefulnessLifecycle
import com.android.systemui.shade.ShadeViewController
@@ -71,7 +72,7 @@ constructor(
private val powerManager: PowerManager,
private val shadeLockscreenInteractorLazy: Lazy<ShadeLockscreenInteractor>,
private val panelExpansionInteractorLazy: Lazy<PanelExpansionInteractor>,
- private val handler: Handler = Handler(),
+ @Main private val handler: Handler,
) : WakefulnessLifecycle.Observer, ScreenOffAnimation {
private lateinit var centralSurfaces: CentralSurfaces
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/battery/domain/interactor/BatteryInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/battery/domain/interactor/BatteryInteractor.kt
index 8fdb6ee57587..d53cbabb1d19 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/battery/domain/interactor/BatteryInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/battery/domain/interactor/BatteryInteractor.kt
@@ -29,7 +29,7 @@ class BatteryInteractor @Inject constructor(repo: BatteryRepository) {
val level = repo.level.filterNotNull()
/** Whether the battery has been fully charged */
- val isFull = level.map { it >= 100 }
+ val isFull = level.map { isBatteryFull(it) }
/**
* For the sake of battery views, consider it to be "charging" if plugged in. This allows users
@@ -82,6 +82,8 @@ class BatteryInteractor @Inject constructor(repo: BatteryRepository) {
companion object {
/** Level below which we consider to be critically low */
private const val CRITICAL_LEVEL = 20
+
+ fun isBatteryFull(level: Int) = level >= 100
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/icons/shared/BindableIconsRegistry.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/icons/shared/BindableIconsRegistry.kt
index 8400fb08e147..701dae1594f5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/icons/shared/BindableIconsRegistry.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/icons/shared/BindableIconsRegistry.kt
@@ -18,6 +18,7 @@ package com.android.systemui.statusbar.pipeline.icons.shared
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.statusbar.pipeline.icons.shared.model.BindableIcon
+import com.android.systemui.statusbar.pipeline.mobile.ui.StackedMobileBindableIcon
import com.android.systemui.statusbar.pipeline.satellite.ui.DeviceBasedSatelliteBindableIcon
import javax.inject.Inject
@@ -40,11 +41,12 @@ class BindableIconsRegistryImpl
@Inject
constructor(
/** Bindables go here */
- oemSatellite: DeviceBasedSatelliteBindableIcon
+ oemSatellite: DeviceBasedSatelliteBindableIcon,
+ stackedMobile: StackedMobileBindableIcon,
) : BindableIconsRegistry {
/**
* Adding the injected bindables to this list will get them registered with
* StatusBarIconController
*/
- override val bindableIcons: List<BindableIcon> = listOf(oemSatellite)
+ override val bindableIcons: List<BindableIcon> = listOf(oemSatellite, stackedMobile)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt
index ac3728d9dcaf..c52536d2b312 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt
@@ -21,6 +21,7 @@ import android.telephony.CarrierConfigManager
import android.telephony.SubscriptionManager
import android.telephony.SubscriptionManager.PROFILE_CLASS_PROVISIONING
import com.android.settingslib.SignalIcon.MobileIconGroup
+import com.android.settingslib.flags.Flags
import com.android.settingslib.mobile.TelephonyIcons
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
@@ -28,6 +29,7 @@ import com.android.systemui.flags.FeatureFlagsClassic
import com.android.systemui.flags.Flags.FILTER_PROVISIONING_NETWORK_SUBSCRIPTIONS
import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.log.table.logDiffsForTable
+import com.android.systemui.statusbar.core.StatusBarRootModernization
import com.android.systemui.statusbar.pipeline.dagger.MobileSummaryLog
import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository
@@ -39,6 +41,7 @@ import com.android.systemui.util.CarrierConfigTracker
import java.lang.ref.WeakReference
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
@@ -79,6 +82,9 @@ interface MobileIconsInteractor {
*/
val icons: StateFlow<List<MobileIconInteractor>>
+ /** Whether the mobile icons can be stacked vertically. */
+ val isStackable: StateFlow<Boolean>
+
/** True if the active mobile data subscription has data enabled */
val activeDataConnectionHasDataEnabled: StateFlow<Boolean>
@@ -126,6 +132,7 @@ interface MobileIconsInteractor {
fun getMobileConnectionInteractorForSubId(subId: Int): MobileIconInteractor
}
+@OptIn(ExperimentalCoroutinesApi::class)
@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
@SysUISingleton
class MobileIconsInteractorImpl
@@ -290,6 +297,18 @@ constructor(
}
.stateIn(scope, SharingStarted.WhileSubscribed(), emptyList())
+ override val isStackable =
+ if (Flags.newStatusBarIcons() && StatusBarRootModernization.isEnabled) {
+ icons.flatMapLatest { icons ->
+ combine(icons.map { it.isNonTerrestrial }) {
+ it.size == 2 && it.none { isNonTerrestrial -> isNonTerrestrial }
+ }
+ }
+ } else {
+ flowOf(false)
+ }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), false)
+
/**
* Copied from the old pipeline. We maintain a 2s period of time where we will keep the
* validated bit from the old active network (A) while data is changing to the new one (B).
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileUiAdapter.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileUiAdapter.kt
index 30cc2c5da994..abd543d78687 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileUiAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileUiAdapter.kt
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.pipeline.mobile.ui
+import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.systemui.CoreStartable
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
@@ -27,7 +28,7 @@ import java.io.PrintWriter
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.collectLatest
-import com.android.app.tracing.coroutines.launchTraced as launch
+import kotlinx.coroutines.flow.combine
/**
* This class is intended to provide a context to collect on the
@@ -56,12 +57,23 @@ constructor(
// Start notifying the icon controller of subscriptions
scope.launch {
isCollecting = true
- mobileIconsViewModel.subscriptionIdsFlow.collectLatest {
- logger.logUiAdapterSubIdsSentToIconController(it)
- lastValue = it
- iconController.setNewMobileIconSubIds(it)
- shadeCarrierGroupController?.updateModernMobileIcons(it)
- }
+ combine(
+ mobileIconsViewModel.subscriptionIdsFlow,
+ mobileIconsViewModel.isStackable,
+ ::Pair,
+ )
+ .collectLatest { (subIds, isStackable) ->
+ logger.logUiAdapterSubIdsSentToIconController(subIds, isStackable)
+ lastValue = subIds
+ if (isStackable) {
+ // Passing an empty list to remove pre-existing mobile icons.
+ // StackedMobileBindableIcon will show the stacked icon instead.
+ iconController.setNewMobileIconSubIds(emptyList())
+ } else {
+ iconController.setNewMobileIconSubIds(subIds)
+ }
+ shadeCarrierGroupController?.updateModernMobileIcons(subIds)
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileViewLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileViewLogger.kt
index 2af6795b39c4..4c2849de34ee 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileViewLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileViewLogger.kt
@@ -31,22 +31,24 @@ import javax.inject.Inject
@SysUISingleton
class MobileViewLogger
@Inject
-constructor(
- @MobileViewLog private val buffer: LogBuffer,
- dumpManager: DumpManager,
-) : Dumpable {
+constructor(@MobileViewLog private val buffer: LogBuffer, dumpManager: DumpManager) : Dumpable {
init {
dumpManager.registerNormalDumpable(this)
}
private val collectionStatuses = mutableMapOf<String, Boolean>()
- fun logUiAdapterSubIdsSentToIconController(subs: List<Int>) {
+ fun logUiAdapterSubIdsSentToIconController(subs: List<Int>, isStackable: Boolean) {
buffer.log(
TAG,
LogLevel.INFO,
- { str1 = subs.toString() },
- { "Sub IDs in MobileUiAdapter being sent to icon controller: $str1" },
+ {
+ str1 = subs.toString()
+ bool1 = isStackable
+ },
+ {
+ "Sub IDs in MobileUiAdapter being sent to icon controller: $str1, isStackable=$bool1"
+ },
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/StackedMobileBindableIcon.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/StackedMobileBindableIcon.kt
new file mode 100644
index 000000000000..fa9fa4c1366f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/StackedMobileBindableIcon.kt
@@ -0,0 +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.
+ */
+
+package com.android.systemui.statusbar.pipeline.mobile.ui
+
+import android.content.Context
+import com.android.settingslib.flags.Flags
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.core.StatusBarRootModernization
+import com.android.systemui.statusbar.pipeline.icons.shared.model.BindableIcon
+import com.android.systemui.statusbar.pipeline.icons.shared.model.ModernStatusBarViewCreator
+import com.android.systemui.statusbar.pipeline.mobile.ui.binder.StackedMobileIconBinder
+import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsViewModel
+import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.StackedMobileIconViewModel
+import com.android.systemui.statusbar.pipeline.shared.ui.view.SingleBindableStatusBarComposeIconView
+import javax.inject.Inject
+
+@SysUISingleton
+class StackedMobileBindableIcon
+@Inject
+constructor(
+ context: Context,
+ mobileIconsViewModel: MobileIconsViewModel,
+ viewModelFactory: StackedMobileIconViewModel.Factory,
+) : BindableIcon {
+ override val slot: String =
+ context.getString(com.android.internal.R.string.status_bar_stacked_mobile)
+
+ override val initializer = ModernStatusBarViewCreator { context ->
+ SingleBindableStatusBarComposeIconView.createView(context).also { view ->
+ view.initView(slot) {
+ StackedMobileIconBinder.bind(view, mobileIconsViewModel, viewModelFactory)
+ }
+ }
+ }
+
+ override val shouldBindIcon: Boolean =
+ Flags.newStatusBarIcons() && StatusBarRootModernization.isEnabled
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/StackedMobileIconBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/StackedMobileIconBinder.kt
new file mode 100644
index 000000000000..c9fc53ecadc0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/StackedMobileIconBinder.kt
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.mobile.ui.binder
+
+import androidx.compose.material3.LocalContentColor
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.platform.ViewCompositionStrategy
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.systemui.lifecycle.rememberViewModel
+import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsViewModel
+import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.StackedMobileIconViewModel
+import com.android.systemui.statusbar.pipeline.shared.ui.binder.ModernStatusBarViewBinding
+import com.android.systemui.statusbar.pipeline.shared.ui.composable.StackedMobileIcon
+import com.android.systemui.statusbar.pipeline.shared.ui.view.SingleBindableStatusBarComposeIconView
+
+object StackedMobileIconBinder {
+ fun bind(
+ view: SingleBindableStatusBarComposeIconView,
+ mobileIconsViewModel: MobileIconsViewModel,
+ viewModelFactory: StackedMobileIconViewModel.Factory,
+ ): ModernStatusBarViewBinding {
+ return SingleBindableStatusBarComposeIconView.withDefaultBinding(
+ view = view,
+ shouldBeVisible = { mobileIconsViewModel.isStackable.value },
+ ) { _, tint ->
+ view.repeatWhenAttached {
+ repeatOnLifecycle(Lifecycle.State.STARTED) {
+ view.composeView.apply {
+ setViewCompositionStrategy(
+ ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed
+ )
+ setContent {
+ val viewModel =
+ rememberViewModel("StackedMobileIconBinder") {
+ viewModelFactory.create()
+ }
+ if (viewModel.isIconVisible) {
+ CompositionLocalProvider(LocalContentColor provides Color(tint())) {
+ StackedMobileIcon(viewModel)
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt
index 22feb7ce77c8..6176a3e9e281 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt
@@ -70,15 +70,20 @@ constructor(
}
.stateIn(scope, SharingStarted.WhileSubscribed(), listOf())
- private val firstMobileSubViewModel: StateFlow<MobileIconViewModelCommon?> =
+ val mobileSubViewModels: StateFlow<List<MobileIconViewModelCommon>> =
subscriptionIdsFlow
+ .map { ids -> ids.map { commonViewModelForSub(it) } }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), emptyList())
+
+ private val firstMobileSubViewModel: StateFlow<MobileIconViewModelCommon?> =
+ mobileSubViewModels
.map {
if (it.isEmpty()) {
null
} else {
// Mobile icons get reversed by [StatusBarIconController], so the last element
// in this list will show up visually first.
- commonViewModelForSub(it.last())
+ it.last()
}
}
.stateIn(scope, SharingStarted.WhileSubscribed(), null)
@@ -94,6 +99,8 @@ constructor(
}
.stateIn(scope, SharingStarted.WhileSubscribed(), false)
+ val isStackable: StateFlow<Boolean> = interactor.isStackable
+
init {
scope.launch { subscriptionIdsFlow.collect { invalidateCaches(it) } }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/StackedMobileIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/StackedMobileIconViewModel.kt
new file mode 100644
index 000000000000..a2c2a3cd1507
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/StackedMobileIconViewModel.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.pipeline.mobile.ui.viewmodel
+
+import androidx.compose.runtime.derivedStateOf
+import androidx.compose.runtime.getValue
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.lifecycle.ExclusiveActivatable
+import com.android.systemui.lifecycle.Hydrator
+import com.android.systemui.statusbar.pipeline.mobile.domain.model.SignalIconModel
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
+
+@OptIn(ExperimentalCoroutinesApi::class)
+class StackedMobileIconViewModel
+@AssistedInject
+constructor(mobileIconsViewModel: MobileIconsViewModel) : ExclusiveActivatable() {
+ private val hydrator = Hydrator("StackedMobileIconViewModel")
+
+ private val isStackable: Boolean by
+ hydrator.hydratedStateOf(
+ traceName = "isStackable",
+ source = mobileIconsViewModel.isStackable,
+ initialValue = false,
+ )
+
+ private val iconViewModelFlow: StateFlow<List<MobileIconViewModelCommon>> =
+ mobileIconsViewModel.mobileSubViewModels
+
+ val dualSim: DualSim? by
+ hydrator.hydratedStateOf(
+ traceName = "dualSim",
+ source =
+ iconViewModelFlow.flatMapLatest { viewModels ->
+ combine(viewModels.map { it.icon }) { icons ->
+ icons
+ .toList()
+ .filterIsInstance<SignalIconModel.Cellular>()
+ .takeIf { it.size == 2 }
+ ?.let { DualSim(it[0], it[1]) }
+ }
+ },
+ initialValue = null,
+ )
+
+ val networkTypeIcon: Icon.Resource? by
+ hydrator.hydratedStateOf(
+ traceName = "networkTypeIcon",
+ source =
+ iconViewModelFlow.flatMapLatest { viewModels ->
+ viewModels.firstOrNull()?.networkTypeIcon ?: flowOf(null)
+ },
+ initialValue = null,
+ )
+
+ val isIconVisible: Boolean by derivedStateOf { isStackable && dualSim != null }
+
+ override suspend fun onActivated(): Nothing {
+ hydrator.activate()
+ }
+
+ @AssistedFactory
+ interface Factory {
+ fun create(): StackedMobileIconViewModel
+ }
+
+ data class DualSim(
+ val primary: SignalIconModel.Cellular,
+ val secondary: SignalIconModel.Cellular,
+ )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/HomeStatusBarViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/HomeStatusBarViewBinder.kt
index 9c9d41e975e3..cd320a12d577 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/HomeStatusBarViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/HomeStatusBarViewBinder.kt
@@ -93,10 +93,11 @@ constructor(
// CollapsedStatusBarFragment doesn't need this
if (StatusBarRootModernization.isEnabled) {
- primaryChipView.isVisible = false
- systemInfoView.isVisible = false
- clockView.isVisible = false
- notificationIconsArea.isVisible = false
+ // GONE because this shouldn't take space in the layout
+ primaryChipView.hideInitially(state = View.GONE)
+ systemInfoView.hideInitially()
+ clockView.hideInitially()
+ notificationIconsArea.hideInitially()
}
view.repeatWhenAttached {
@@ -365,6 +366,17 @@ constructor(
}
}
+ /**
+ * Hide the view for initialization, but skip if it's already hidden and does not cancel
+ * animations.
+ */
+ private fun View.hideInitially(state: Int = View.INVISIBLE) {
+ if (visibility == View.INVISIBLE || visibility == View.GONE) {
+ return
+ }
+ visibility = state
+ }
+
// See CollapsedStatusBarFragment#hide.
private fun View.hide(state: Int = View.INVISIBLE, shouldAnimateChange: Boolean) {
if (visibility == View.INVISIBLE || visibility == View.GONE) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/composable/StackedMobileIcon.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/composable/StackedMobileIcon.kt
new file mode 100644
index 000000000000..465a43fbfb9e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/composable/StackedMobileIcon.kt
@@ -0,0 +1,180 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.shared.ui.composable
+
+import androidx.compose.foundation.Canvas
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.material3.LocalContentColor
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.CornerRadius
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.geometry.Size
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.drawscope.DrawScope
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.unit.TextUnit
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import com.android.compose.modifiers.height
+import com.android.compose.modifiers.width
+import com.android.systemui.common.ui.compose.Icon
+import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.StackedMobileIconViewModel
+import com.android.systemui.statusbar.pipeline.shared.ui.composable.StackedMobileIconDimensions.BarBaseHeightFiveBarsSp
+import com.android.systemui.statusbar.pipeline.shared.ui.composable.StackedMobileIconDimensions.BarBaseHeightFourBarsSp
+import com.android.systemui.statusbar.pipeline.shared.ui.composable.StackedMobileIconDimensions.BarsLevelIncrementSp
+import com.android.systemui.statusbar.pipeline.shared.ui.composable.StackedMobileIconDimensions.BarsVerticalPaddingSp
+import com.android.systemui.statusbar.pipeline.shared.ui.composable.StackedMobileIconDimensions.HorizontalPaddingFiveBarsSp
+import com.android.systemui.statusbar.pipeline.shared.ui.composable.StackedMobileIconDimensions.HorizontalPaddingFourBarsSp
+import com.android.systemui.statusbar.pipeline.shared.ui.composable.StackedMobileIconDimensions.IconHeightSp
+import com.android.systemui.statusbar.pipeline.shared.ui.composable.StackedMobileIconDimensions.IconWidthFiveBarsSp
+import com.android.systemui.statusbar.pipeline.shared.ui.composable.StackedMobileIconDimensions.IconWidthFourBarsSp
+import com.android.systemui.statusbar.pipeline.shared.ui.composable.StackedMobileIconDimensions.SecondaryBarHeightSp
+import kotlin.math.max
+
+/**
+ * The dual sim icon that shows both connections stacked vertically with the active connection on
+ * top
+ */
+@Composable
+fun StackedMobileIcon(viewModel: StackedMobileIconViewModel, modifier: Modifier = Modifier) {
+ val dualSim = viewModel.dualSim ?: return
+
+ val contentColor = LocalContentColor.current
+
+ Row(verticalAlignment = Alignment.CenterVertically, modifier = modifier) {
+ viewModel.networkTypeIcon?.let {
+ Icon(
+ it,
+ tint = contentColor,
+ modifier =
+ Modifier.height { IconHeightSp.roundToPx() }.padding(start = 1.dp, end = 2.dp),
+ )
+ }
+
+ StackedMobileIcon(dualSim, contentColor)
+ }
+}
+
+@Composable
+private fun StackedMobileIcon(
+ viewModel: StackedMobileIconViewModel.DualSim,
+ color: Color,
+ modifier: Modifier = Modifier,
+) {
+ val maxNumberOfLevels =
+ max(viewModel.primary.numberOfLevels, viewModel.secondary.numberOfLevels)
+ val dimensions = if (maxNumberOfLevels == 6) FiveBarsDimensions else FourBarsDimensions
+ val iconSize =
+ with(LocalDensity.current) { dimensions.totalWidth.toDp() to IconHeightSp.toDp() }
+
+ Canvas(modifier.size(width = iconSize.first, height = iconSize.second)) {
+ val verticalPaddingPx = BarsVerticalPaddingSp.roundToPx()
+ val horizontalPaddingPx = dimensions.barsHorizontalPadding.roundToPx()
+ val totalPaddingWidthPx = horizontalPaddingPx * (maxNumberOfLevels - 1)
+
+ val barWidthPx = (size.width - totalPaddingWidthPx) / maxNumberOfLevels
+ val dotHeightPx = SecondaryBarHeightSp.toPx()
+ val baseBarHeightPx = dimensions.barBaseHeight.toPx()
+
+ var xOffsetPx = 0f
+ for (bar in 1..maxNumberOfLevels) {
+ // Bottom dots representing secondary sim
+ val dotYOffsetPx = size.height - dotHeightPx
+ if (bar <= viewModel.secondary.numberOfLevels) {
+ drawMobileIconBar(
+ level = viewModel.secondary.level,
+ bar = bar,
+ topLeft = Offset(xOffsetPx, dotYOffsetPx),
+ size = Size(barWidthPx, dotHeightPx),
+ activeColor = color,
+ )
+ }
+
+ // Top bars representing primary sim
+ if (bar <= viewModel.primary.numberOfLevels) {
+ val barHeightPx = baseBarHeightPx + (BarsLevelIncrementSp.toPx() * (bar - 1))
+ val barYOffsetPx = dotYOffsetPx - verticalPaddingPx - barHeightPx
+ drawMobileIconBar(
+ level = viewModel.primary.level,
+ bar = bar,
+ topLeft = Offset(xOffsetPx, barYOffsetPx),
+ size = Size(barWidthPx, barHeightPx),
+ activeColor = color,
+ )
+ }
+
+ xOffsetPx += barWidthPx + horizontalPaddingPx
+ }
+ }
+}
+
+private fun DrawScope.drawMobileIconBar(
+ level: Int,
+ bar: Int,
+ topLeft: Offset,
+ size: Size,
+ activeColor: Color,
+ inactiveColor: Color = activeColor.copy(alpha = .3f),
+ cornerRadius: CornerRadius = CornerRadius(size.width / 2),
+) {
+ drawRoundRect(
+ color = if (level >= bar) activeColor else inactiveColor,
+ topLeft = topLeft,
+ size = size,
+ cornerRadius = cornerRadius,
+ )
+}
+
+private abstract class BarsDependentDimensions(
+ val totalWidth: TextUnit,
+ val barsHorizontalPadding: TextUnit,
+ val barBaseHeight: TextUnit,
+)
+
+private object FourBarsDimensions :
+ BarsDependentDimensions(
+ IconWidthFourBarsSp,
+ HorizontalPaddingFourBarsSp,
+ BarBaseHeightFourBarsSp,
+ )
+
+private object FiveBarsDimensions :
+ BarsDependentDimensions(
+ IconWidthFiveBarsSp,
+ HorizontalPaddingFiveBarsSp,
+ BarBaseHeightFiveBarsSp,
+ )
+
+private object StackedMobileIconDimensions {
+ // Common dimensions
+ val IconHeightSp = 12.sp
+ val BarsVerticalPaddingSp = 1.5.sp
+ val BarsLevelIncrementSp = 1.sp
+ val SecondaryBarHeightSp = 3.sp
+
+ // Dimensions dependant on the number of total bars
+ val IconWidthFiveBarsSp = 18.5.sp
+ val IconWidthFourBarsSp = 16.sp
+ val HorizontalPaddingFiveBarsSp = 1.5.sp
+ val HorizontalPaddingFourBarsSp = 2.sp
+ val BarBaseHeightFiveBarsSp = 3.5.sp
+ val BarBaseHeightFourBarsSp = 4.5.sp
+}
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 c34fa464cc3a..a961713230c8 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
@@ -41,12 +41,15 @@ import com.android.systemui.plugins.DarkIconDispatcher
import com.android.systemui.res.R
import com.android.systemui.statusbar.chips.ui.compose.OngoingActivityChips
import com.android.systemui.statusbar.core.NewStatusBarIcons
+import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
import com.android.systemui.statusbar.core.StatusBarRootModernization
import com.android.systemui.statusbar.data.repository.DarkIconDispatcherStore
import com.android.systemui.statusbar.events.domain.interactor.SystemStatusEventAnimationInteractor
import com.android.systemui.statusbar.featurepods.popups.StatusBarPopupChips
import com.android.systemui.statusbar.featurepods.popups.ui.compose.StatusBarPopupChipsContainer
+import com.android.systemui.statusbar.notification.icon.ui.viewbinder.ConnectedDisplaysStatusBarNotificationIconViewStore
import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerStatusBarViewBinder
+import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerViewBinder
import com.android.systemui.statusbar.phone.NotificationIconContainer
import com.android.systemui.statusbar.phone.PhoneStatusBarView
import com.android.systemui.statusbar.phone.StatusBarLocation
@@ -72,6 +75,7 @@ constructor(
private val homeStatusBarViewModelFactory: HomeStatusBarViewModelFactory,
private val homeStatusBarViewBinder: HomeStatusBarViewBinder,
private val notificationIconsBinder: NotificationIconContainerStatusBarViewBinder,
+ private val iconViewStoreFactory: ConnectedDisplaysStatusBarNotificationIconViewStore.Factory,
private val darkIconManagerFactory: DarkIconManager.Factory,
private val iconController: StatusBarIconController,
private val ongoingCallController: OngoingCallController,
@@ -89,6 +93,7 @@ constructor(
statusBarViewModelFactory = homeStatusBarViewModelFactory,
statusBarViewBinder = homeStatusBarViewBinder,
notificationIconsBinder = notificationIconsBinder,
+ iconViewStoreFactory = iconViewStoreFactory,
darkIconManagerFactory = darkIconManagerFactory,
iconController = iconController,
ongoingCallController = ongoingCallController,
@@ -119,6 +124,7 @@ fun StatusBarRoot(
statusBarViewModelFactory: HomeStatusBarViewModelFactory,
statusBarViewBinder: HomeStatusBarViewBinder,
notificationIconsBinder: NotificationIconContainerStatusBarViewBinder,
+ iconViewStoreFactory: ConnectedDisplaysStatusBarNotificationIconViewStore.Factory,
darkIconManagerFactory: DarkIconManager.Factory,
iconController: StatusBarIconController,
ongoingCallController: OngoingCallController,
@@ -129,6 +135,14 @@ fun StatusBarRoot(
val displayId = parent.context.displayId
val statusBarViewModel =
rememberViewModel("HomeStatusBar") { statusBarViewModelFactory.create(displayId) }
+ val iconViewStore: NotificationIconContainerViewBinder.IconViewStore? =
+ if (StatusBarConnectedDisplays.isEnabled) {
+ rememberViewModel("HomeStatusBar.IconViewStore[$displayId]") {
+ iconViewStoreFactory.create(displayId)
+ }
+ } else {
+ null
+ }
Box(Modifier.fillMaxSize()) {
// TODO(b/364360986): remove this before rolling the flag forward
@@ -174,7 +188,10 @@ fun StatusBarRoot(
val chips by
statusBarViewModel.ongoingActivityChips
.collectAsStateWithLifecycle()
- OngoingActivityChips(chips = chips)
+ OngoingActivityChips(
+ chips = chips,
+ iconViewStore = iconViewStore,
+ )
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/view/SingleBindableStatusBarComposeIconView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/view/SingleBindableStatusBarComposeIconView.kt
new file mode 100644
index 000000000000..8076040564fb
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/view/SingleBindableStatusBarComposeIconView.kt
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.shared.ui.view
+
+import android.content.Context
+import android.graphics.Color
+import android.util.AttributeSet
+import android.view.LayoutInflater
+import android.view.View
+import androidx.compose.ui.platform.ComposeView
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.lifecycleScope
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.app.tracing.coroutines.launchTraced as launch
+import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.StatusBarIconView
+import com.android.systemui.statusbar.StatusBarIconView.STATE_HIDDEN
+import com.android.systemui.statusbar.pipeline.shared.ui.binder.ModernStatusBarViewBinding
+import com.android.systemui.statusbar.pipeline.shared.ui.binder.ModernStatusBarViewVisibilityHelper
+import kotlinx.coroutines.awaitCancellation
+import kotlinx.coroutines.flow.MutableStateFlow
+
+/** Compose view that is bound to bindable_status_bar_compose_icon.xml */
+class SingleBindableStatusBarComposeIconView(context: Context, attrs: AttributeSet?) :
+ ModernStatusBarView(context, attrs) {
+
+ internal lateinit var composeView: ComposeView
+ internal lateinit var dotView: StatusBarIconView
+
+ override fun toString(): String {
+ return "SingleBindableStatusBarComposeIcon(" +
+ "slot='$slot', " +
+ "isCollecting=${binding.isCollecting()}, " +
+ "visibleState=${StatusBarIconView.getVisibleStateString(visibleState)}); " +
+ "viewString=${super.toString()}"
+ }
+
+ override fun initView(slot: String, bindingCreator: () -> ModernStatusBarViewBinding) {
+ super.initView(slot, bindingCreator)
+
+ composeView = requireViewById(R.id.compose_view)
+ dotView = requireViewById(R.id.status_bar_dot)
+ }
+
+ companion object {
+ fun createView(context: Context): SingleBindableStatusBarComposeIconView {
+ return LayoutInflater.from(context)
+ .inflate(R.layout.bindable_status_bar_compose_icon, null)
+ as SingleBindableStatusBarComposeIconView
+ }
+
+ /**
+ * Using a given binding [block], create the necessary scaffolding to handle the general
+ * case of a single status bar icon. This includes eliding into a dot view when there is not
+ * enough space, and handling tint.
+ *
+ * [block] should be a simple [launch] call that handles updating the single icon view with
+ * its new view. Currently there is no simple way to e.g., extend to handle multiple tints
+ * for dual-layered icons, and any more complex logic should probably find a way to return
+ * its own version of [ModernStatusBarViewBinding].
+ */
+ fun withDefaultBinding(
+ view: SingleBindableStatusBarComposeIconView,
+ shouldBeVisible: () -> Boolean,
+ block: suspend LifecycleOwner.(View, () -> Int) -> Unit,
+ ): ModernStatusBarViewBinding {
+ @StatusBarIconView.VisibleState
+ val visibilityState: MutableStateFlow<Int> = MutableStateFlow(STATE_HIDDEN)
+
+ val iconTint: MutableStateFlow<Int> = MutableStateFlow(Color.WHITE)
+ val decorTint: MutableStateFlow<Int> = MutableStateFlow(Color.WHITE)
+
+ var isCollecting: Boolean = false
+
+ view.repeatWhenAttached {
+ // Child binding
+ block(view) { iconTint.value }
+
+ lifecycleScope.launch {
+ repeatOnLifecycle(Lifecycle.State.STARTED) {
+ // isVisible controls the visibility state of the outer group, and thus it
+ // needs to run in the CREATED lifecycle so it can continue to watch while
+ // invisible. See (b/291031862) for details
+ launch {
+ visibilityState.collect { visibilityState ->
+ // for b/296864006, we can not hide all the child views if
+ // visibilityState is STATE_HIDDEN. Because hiding all child views
+ // would cause the getWidth() of this view return 0, and that would
+ // cause the translation calculation fails in StatusIconContainer.
+ // Therefore, like class MobileIconBinder, instead of set the child
+ // views visibility to View.GONE, we set their visibility to
+ // View.INVISIBLE to make them invisible but keep the width.
+ ModernStatusBarViewVisibilityHelper.setVisibilityState(
+ visibilityState,
+ view.composeView,
+ view.dotView,
+ )
+ }
+ }
+
+ launch { iconTint.collect { tint -> view.dotView.setDecorColor(tint) } }
+
+ launch {
+ decorTint.collect { decorTint -> view.dotView.setDecorColor(decorTint) }
+ }
+
+ try {
+ awaitCancellation()
+ } finally {
+ isCollecting = false
+ }
+ }
+ }
+ }
+
+ return object : ModernStatusBarViewBinding {
+ override fun getShouldIconBeVisible(): Boolean {
+ return shouldBeVisible()
+ }
+
+ override fun onVisibilityStateChanged(state: Int) {
+ visibilityState.value = state
+ }
+
+ override fun onIconTintChanged(newTint: Int, contrastTint: Int) {
+ iconTint.value = newTint
+ }
+
+ override fun onDecorTintChanged(newTint: Int) {
+ decorTint.value = newTint
+ }
+
+ override fun isCollecting(): Boolean {
+ return isCollecting
+ }
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java
index ccd75602aa13..dd742ff8f1cc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java
@@ -55,6 +55,7 @@ import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
import com.android.systemui.res.R;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.core.StatusBarRootModernization;
import com.android.systemui.statusbar.phone.ui.StatusBarIconController;
import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
import com.android.systemui.tuner.TunerService;
@@ -216,7 +217,9 @@ public class Clock extends TextView implements
// Make sure we update to the current time
updateClock();
- updateClockVisibility();
+ if (!StatusBarRootModernization.isEnabled()) {
+ updateClockVisibility();
+ }
updateShowSeconds();
}
@@ -275,19 +278,25 @@ public class Clock extends TextView implements
@Override
public void setVisibility(int visibility) {
- if (visibility == View.VISIBLE && !shouldBeVisible()) {
- return;
+ if (!StatusBarRootModernization.isEnabled()) {
+ if (visibility == View.VISIBLE && !shouldBeVisible()) {
+ return;
+ }
}
super.setVisibility(visibility);
}
- public void setClockVisibleByUser(boolean visible) {
+ private void setClockVisibleByUser(boolean visible) {
+ StatusBarRootModernization.assertInLegacyMode();
+
mClockVisibleByUser = visible;
updateClockVisibility();
}
- public void setClockVisibilityByPolicy(boolean visible) {
+ private void setClockVisibilityByPolicy(boolean visible) {
+ StatusBarRootModernization.assertInLegacyMode();
+
mClockVisibleByPolicy = visible;
updateClockVisibility();
}
@@ -297,6 +306,8 @@ public class Clock extends TextView implements
}
private void updateClockVisibility() {
+ StatusBarRootModernization.assertInLegacyMode();
+
boolean visible = shouldBeVisible();
int visibility = visible ? View.VISIBLE : View.GONE;
super.setVisibility(visibility);
@@ -346,15 +357,23 @@ public class Clock extends TextView implements
if (CLOCK_SECONDS.equals(key)) {
mShowSeconds = TunerService.parseIntegerSwitch(newValue, false);
updateShowSeconds();
- } else if (StatusBarIconController.ICON_HIDE_LIST.equals(key)) {
- setClockVisibleByUser(!StatusBarIconController.getIconHideList(getContext(), newValue)
- .contains("clock"));
- updateClockVisibility();
+ } else if (!StatusBarRootModernization.isEnabled()) {
+ if (StatusBarIconController.ICON_HIDE_LIST.equals(key)) {
+ setClockVisibleByUser(
+ !StatusBarIconController
+ .getIconHideList(getContext(), newValue)
+ .contains("clock"));
+ updateClockVisibility();
+ }
}
}
@Override
public void disable(int displayId, int state1, int state2, boolean animate) {
+ if (StatusBarRootModernization.isEnabled()) {
+ return;
+ }
+
if (displayId != getDisplay().getDisplayId()) {
return;
}
diff --git a/packages/SystemUI/src/com/android/systemui/util/concurrency/GlobalConcurrencyModule.java b/packages/SystemUI/src/com/android/systemui/util/concurrency/GlobalConcurrencyModule.java
index 70774f13fe6b..c43e7637998c 100644
--- a/packages/SystemUI/src/com/android/systemui/util/concurrency/GlobalConcurrencyModule.java
+++ b/packages/SystemUI/src/com/android/systemui/util/concurrency/GlobalConcurrencyModule.java
@@ -65,15 +65,6 @@ public abstract class GlobalConcurrencyModule {
}
/**
- * @deprecated Use @Main Handler.
- */
- @Deprecated
- @Provides
- public static Handler provideHandler() {
- return new Handler();
- }
-
- /**
* Provide an Executor specifically for running UI operations on a separate thread.
*
* Keep submitted runnables short and to the point, just as with any other UI code.
diff --git a/packages/SystemUI/src/com/android/systemui/util/concurrency/SysUIConcurrencyModule.kt b/packages/SystemUI/src/com/android/systemui/util/concurrency/SysUIConcurrencyModule.kt
index a7abb6b5f1d3..5b5f85c12efc 100644
--- a/packages/SystemUI/src/com/android/systemui/util/concurrency/SysUIConcurrencyModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/concurrency/SysUIConcurrencyModule.kt
@@ -53,6 +53,14 @@ object SysUIConcurrencyModule {
private const val NOTIFICATION_INFLATION_SLOW_DISPATCH_THRESHOLD = 1000L
private const val NOTIFICATION_INFLATION_SLOW_DELIVERY_THRESHOLD = 1000L
+ /**
+ * Choreographer instance for the main thread.
+ * TODO(b/395887935): Lets move this to @Main and provide thread-local references
+ */
+ @Provides
+ @SysUISingleton
+ fun providesChoreographer(): Choreographer = Choreographer.getInstance()
+
/** Background Looper */
@Provides
@SysUISingleton
@@ -99,10 +107,7 @@ object SysUIConcurrencyModule {
@Provides
@SysUISingleton
@NotifInflation
- fun provideNotifInflationLooper(@Background bgLooper: Looper): Looper {
- if (!Flags.dedicatedNotifInflationThread()) {
- return bgLooper
- }
+ fun provideNotifInflationLooper(): Looper {
val thread = HandlerThread("NotifInflation", Process.THREAD_PRIORITY_BACKGROUND)
thread.start()
val looper = thread.getLooper()
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/module/VolumeDialogModule.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/module/VolumeDialogModule.kt
index 025e269b70b6..7b08317d5265 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/module/VolumeDialogModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/module/VolumeDialogModule.kt
@@ -18,8 +18,13 @@ package com.android.systemui.volume.dialog.dagger.module
import com.android.systemui.volume.dialog.ringer.data.repository.VolumeDialogRingerFeedbackRepository
import com.android.systemui.volume.dialog.ringer.data.repository.VolumeDialogRingerFeedbackRepositoryImpl
+import com.android.systemui.volume.dialog.ringer.ui.binder.VolumeDialogRingerViewBinder
+import com.android.systemui.volume.dialog.settings.ui.binder.VolumeDialogSettingsButtonViewBinder
+import com.android.systemui.volume.dialog.sliders.ui.VolumeDialogSlidersViewBinder
+import com.android.systemui.volume.dialog.ui.binder.ViewBinder
import dagger.Binds
import dagger.Module
+import dagger.Provides
/** Dagger module for volume dialog code in the volume package */
@Module
@@ -29,4 +34,14 @@ interface VolumeDialogModule {
fun bindVolumeDialogRingerFeedbackRepository(
ringerFeedbackRepository: VolumeDialogRingerFeedbackRepositoryImpl
): VolumeDialogRingerFeedbackRepository
+
+ companion object {
+
+ @Provides
+ fun provideViewBinders(
+ slidersViewBinder: VolumeDialogSlidersViewBinder,
+ ringerViewBinder: VolumeDialogRingerViewBinder,
+ settingsButtonViewBinder: VolumeDialogSettingsButtonViewBinder,
+ ): List<ViewBinder> = listOf(slidersViewBinder, ringerViewBinder, settingsButtonViewBinder)
+ }
}
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 eb2b2f68a6e2..ef750574830a 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
@@ -39,6 +39,7 @@ import com.android.systemui.volume.dialog.ringer.ui.viewmodel.RingerDrawerState
import com.android.systemui.volume.dialog.ringer.ui.viewmodel.RingerViewModel
import com.android.systemui.volume.dialog.ringer.ui.viewmodel.RingerViewModelState
import com.android.systemui.volume.dialog.ringer.ui.viewmodel.VolumeDialogRingerDrawerViewModel
+import com.android.systemui.volume.dialog.ui.binder.ViewBinder
import com.android.systemui.volume.dialog.ui.utils.suspendAnimate
import com.android.systemui.volume.dialog.ui.viewmodel.VolumeDialogViewModel
import javax.inject.Inject
@@ -60,7 +61,7 @@ class VolumeDialogRingerViewBinder
constructor(
private val viewModel: VolumeDialogRingerDrawerViewModel,
private val dialogViewModel: VolumeDialogViewModel,
-) {
+) : ViewBinder {
private val roundnessSpringForce =
SpringForce(1F).apply {
stiffness = 800F
@@ -73,10 +74,11 @@ constructor(
}
private val rgbEvaluator = ArgbEvaluator()
- fun CoroutineScope.bind(view: View) {
+ override fun CoroutineScope.bind(view: View) {
val volumeDialogBackgroundView = view.requireViewById<View>(R.id.volume_dialog_background)
val ringerBackgroundView = view.requireViewById<View>(R.id.ringer_buttons_background)
val drawerContainer = view.requireViewById<MotionLayout>(R.id.volume_ringer_drawer)
+
val unselectedButtonUiModel = RingerButtonUiModel.getUnselectedButton(view.context)
val selectedButtonUiModel = RingerButtonUiModel.getSelectedButton(view.context)
val volumeDialogBgSmallRadius =
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/settings/ui/binder/VolumeDialogSettingsButtonViewBinder.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/settings/ui/binder/VolumeDialogSettingsButtonViewBinder.kt
index a330685fc6bb..54f04e274c03 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/settings/ui/binder/VolumeDialogSettingsButtonViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/settings/ui/binder/VolumeDialogSettingsButtonViewBinder.kt
@@ -21,6 +21,7 @@ import android.widget.ImageButton
import com.android.systemui.res.R
import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogScope
import com.android.systemui.volume.dialog.settings.ui.viewmodel.VolumeDialogSettingsButtonViewModel
+import com.android.systemui.volume.dialog.ui.binder.ViewBinder
import com.android.systemui.volume.dialog.ui.viewmodel.VolumeDialogViewModel
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
@@ -34,9 +35,9 @@ class VolumeDialogSettingsButtonViewBinder
constructor(
private val viewModel: VolumeDialogSettingsButtonViewModel,
private val dialogViewModel: VolumeDialogViewModel,
-) {
+) : ViewBinder {
- fun CoroutineScope.bind(view: View) {
+ override fun CoroutineScope.bind(view: View) {
val button = view.requireViewById<ImageButton>(R.id.volume_dialog_settings)
launch { dialogViewModel.addTouchableBounds(button) }
viewModel.isVisible
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSlidersViewBinder.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSlidersViewBinder.kt
index 7f58c835094f..0d970e5117c2 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSlidersViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSlidersViewBinder.kt
@@ -25,6 +25,7 @@ import com.android.systemui.res.R
import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogScope
import com.android.systemui.volume.dialog.sliders.dagger.VolumeDialogSliderComponent
import com.android.systemui.volume.dialog.sliders.ui.viewmodel.VolumeDialogSlidersViewModel
+import com.android.systemui.volume.dialog.ui.binder.ViewBinder
import com.android.systemui.volume.dialog.ui.viewmodel.VolumeDialogViewModel
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
@@ -38,16 +39,16 @@ class VolumeDialogSlidersViewBinder
constructor(
private val viewModel: VolumeDialogSlidersViewModel,
private val dialogViewModel: VolumeDialogViewModel,
-) {
+) : ViewBinder {
- fun CoroutineScope.bind(view: View) {
+ override fun CoroutineScope.bind(view: View) {
val floatingSlidersContainer: ViewGroup =
view.requireViewById(R.id.volume_dialog_floating_sliders_container)
val mainSliderContainer: View =
view.requireViewById(R.id.volume_dialog_main_slider_container)
val background: View = view.requireViewById(R.id.volume_dialog_background)
- val settingsButton: View = view.requireViewById(R.id.volume_dialog_settings)
- val ringerDrawer: View = view.requireViewById(R.id.volume_ringer_drawer)
+ val bottomSection: View = view.requireViewById(R.id.volume_dialog_bottom_section_container)
+ val topSection: View = view.requireViewById(R.id.volume_dialog_top_section_container)
launch { dialogViewModel.addTouchableBounds(mainSliderContainer, floatingSlidersContainer) }
viewModel.sliders
@@ -55,7 +56,7 @@ constructor(
bindSlider(
uiModel.sliderComponent,
mainSliderContainer,
- arrayOf(mainSliderContainer, background, settingsButton, ringerDrawer),
+ arrayOf(mainSliderContainer, background, bottomSection, topSection),
)
val floatingSliderViewBinders = uiModel.floatingSliderComponent
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/binder/ViewBinder.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/binder/ViewBinder.kt
new file mode 100644
index 000000000000..4d1af0d71108
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/binder/ViewBinder.kt
@@ -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.
+ */
+
+package com.android.systemui.volume.dialog.ui.binder
+
+import android.view.View
+import kotlinx.coroutines.CoroutineScope
+
+interface ViewBinder {
+
+ fun CoroutineScope.bind(view: View)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/binder/VolumeDialogViewBinder.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/binder/VolumeDialogViewBinder.kt
index 7cc4bcc4e11c..98042d5022f9 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/binder/VolumeDialogViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/binder/VolumeDialogViewBinder.kt
@@ -17,28 +17,28 @@
package com.android.systemui.volume.dialog.ui.binder
import android.app.Dialog
-import android.content.res.Resources
import android.view.View
import android.view.ViewTreeObserver
import android.view.WindowInsets
+import androidx.compose.ui.util.lerp
import androidx.constraintlayout.motion.widget.MotionLayout
import androidx.core.view.updatePadding
+import androidx.dynamicanimation.animation.DynamicAnimation
+import androidx.dynamicanimation.animation.FloatValueHolder
+import androidx.dynamicanimation.animation.SpringAnimation
+import androidx.dynamicanimation.animation.SpringForce
import com.android.internal.view.RotationPolicy
import com.android.systemui.common.ui.view.onApplyWindowInsets
-import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.res.R
import com.android.systemui.util.kotlin.awaitCancellationThenDispose
-import com.android.systemui.volume.SystemUIInterpolators
import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogScope
-import com.android.systemui.volume.dialog.ringer.ui.binder.VolumeDialogRingerViewBinder
-import com.android.systemui.volume.dialog.settings.ui.binder.VolumeDialogSettingsButtonViewBinder
import com.android.systemui.volume.dialog.shared.model.VolumeDialogVisibilityModel
-import com.android.systemui.volume.dialog.sliders.ui.VolumeDialogSlidersViewBinder
import com.android.systemui.volume.dialog.ui.utils.JankListenerFactory
import com.android.systemui.volume.dialog.ui.utils.suspendAnimate
import com.android.systemui.volume.dialog.ui.viewmodel.VolumeDialogViewModel
import com.android.systemui.volume.dialog.utils.VolumeTracer
import javax.inject.Inject
+import kotlin.math.ceil
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
@@ -50,26 +50,25 @@ import kotlinx.coroutines.flow.scan
import kotlinx.coroutines.launch
import kotlinx.coroutines.suspendCancellableCoroutine
+private const val SPRING_STIFFNESS = 700f
+private const val SPRING_DAMPING_RATIO = 0.9f
+
+private const val FRACTION_HIDE = 0f
+private const val FRACTION_SHOW = 1f
+private const val ANIMATION_MINIMUM_VISIBLE_CHANGE = 0.01f
+
/** Binds the root view of the Volume Dialog. */
@OptIn(ExperimentalCoroutinesApi::class)
@VolumeDialogScope
class VolumeDialogViewBinder
@Inject
constructor(
- @Main resources: Resources,
private val viewModel: VolumeDialogViewModel,
private val jankListenerFactory: JankListenerFactory,
private val tracer: VolumeTracer,
- private val volumeDialogRingerViewBinder: VolumeDialogRingerViewBinder,
- private val slidersViewBinder: VolumeDialogSlidersViewBinder,
- private val settingsButtonViewBinder: VolumeDialogSettingsButtonViewBinder,
+ private val viewBinders: List<@JvmSuppressWildcards ViewBinder>,
) {
- private val dialogShowAnimationDurationMs =
- resources.getInteger(R.integer.config_dialogShowAnimationDurationMs).toLong()
- private val dialogHideAnimationDurationMs =
- resources.getInteger(R.integer.config_dialogHideAnimationDurationMs).toLong()
-
fun CoroutineScope.bind(dialog: Dialog) {
val insets: MutableStateFlow<WindowInsets> =
MutableStateFlow(WindowInsets.Builder().build())
@@ -105,9 +104,9 @@ constructor(
.awaitCancellationThenDispose()
}
- with(volumeDialogRingerViewBinder) { bind(root) }
- with(slidersViewBinder) { bind(root) }
- with(settingsButtonViewBinder) { bind(root) }
+ for (viewBinder in viewBinders) {
+ with(viewBinder) { bind(root) }
+ }
}
private fun CoroutineScope.animateVisibility(
@@ -115,22 +114,35 @@ constructor(
dialog: Dialog,
visibilityModel: Flow<VolumeDialogVisibilityModel>,
) {
+ view.applyAnimationProgress(FRACTION_HIDE)
+ val animationValueHolder = FloatValueHolder(FRACTION_HIDE)
+ val animation: SpringAnimation =
+ SpringAnimation(animationValueHolder)
+ .setSpring(
+ SpringForce()
+ .setStiffness(SPRING_STIFFNESS)
+ .setDampingRatio(SPRING_DAMPING_RATIO)
+ )
+ .setMinimumVisibleChange(ANIMATION_MINIMUM_VISIBLE_CHANGE)
+ .addUpdateListener { _, value, _ -> view.applyAnimationProgress(value) }
+ var junkListener: DynamicAnimation.OnAnimationUpdateListener? = null
+
visibilityModel
.mapLatest {
when (it) {
is VolumeDialogVisibilityModel.Visible -> {
tracer.traceVisibilityEnd(it)
- view.animateShow(
- duration = dialogShowAnimationDurationMs,
- translationX = calculateTranslationX(view),
- )
+ junkListener?.let(animation::removeUpdateListener)
+ junkListener =
+ jankListenerFactory.show(view).also(animation::addUpdateListener)
+ animation.suspendAnimate(FRACTION_SHOW)
}
is VolumeDialogVisibilityModel.Dismissed -> {
tracer.traceVisibilityEnd(it)
- view.animateHide(
- duration = dialogHideAnimationDurationMs,
- translationX = calculateTranslationX(view),
- )
+ junkListener?.let(animation::removeUpdateListener)
+ junkListener =
+ jankListenerFactory.dismiss(view).also(animation::addUpdateListener)
+ animation.suspendAnimate(FRACTION_HIDE)
dialog.dismiss()
}
is VolumeDialogVisibilityModel.Invisible -> {
@@ -141,37 +153,21 @@ constructor(
.launchIn(this)
}
- private fun calculateTranslationX(view: View): Float? {
- return if (view.display.rotation == RotationPolicy.NATURAL_ROTATION) {
- if (view.isLayoutRtl) {
- -1
+ /**
+ * @param fraction in range [0, 1]. 0 corresponds to the dialog being hidden and 1 - visible.
+ */
+ private fun View.applyAnimationProgress(fraction: Float) {
+ alpha = ceil(fraction)
+ if (display.rotation == RotationPolicy.NATURAL_ROTATION) {
+ if (isLayoutRtl) {
+ -1
+ } else {
+ 1
+ } * width / 2f
} else {
- 1
- } * view.width / 2f
- } else {
- null
- }
- }
-
- private suspend fun View.animateShow(duration: Long, translationX: Float?) {
- translationX?.let { setTranslationX(translationX) }
- alpha = 0f
- animate()
- .alpha(1f)
- .translationX(0f)
- .setDuration(duration)
- .setInterpolator(SystemUIInterpolators.LogDecelerateInterpolator())
- .suspendAnimate(jankListenerFactory.show(this, duration))
- }
-
- private suspend fun View.animateHide(duration: Long, translationX: Float?) {
- val animator =
- animate()
- .alpha(0f)
- .setDuration(duration)
- .setInterpolator(SystemUIInterpolators.LogAccelerateInterpolator())
- translationX?.let { animator.translationX(it) }
- animator.suspendAnimate(jankListenerFactory.dismiss(this, duration))
+ null
+ }
+ ?.let { maxTranslationX -> translationX = lerp(maxTranslationX, 0f, fraction) }
}
private suspend fun ViewTreeObserver.listenToComputeInternalInsets() =
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/utils/JankListenerFactory.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/utils/JankListenerFactory.kt
index 9fcd77716fb6..803911a9cf77 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/utils/JankListenerFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/utils/JankListenerFactory.kt
@@ -17,8 +17,8 @@
package com.android.systemui.volume.dialog.ui.utils
import android.animation.Animator
-import android.animation.AnimatorListenerAdapter
import android.view.View
+import androidx.dynamicanimation.animation.DynamicAnimation
import com.android.internal.jank.Cuj
import com.android.internal.jank.InteractionJankMonitor
import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogPluginScope
@@ -30,35 +30,36 @@ class JankListenerFactory
@Inject
constructor(private val interactionJankMonitor: InteractionJankMonitor) {
- fun show(view: View, timeout: Long) = getJunkListener(view, "show", timeout)
-
- fun update(view: View, timeout: Long) = getJunkListener(view, "update", timeout)
+ fun show(view: View): DynamicAnimation.OnAnimationUpdateListener {
+ return createJunkListener(view, "show")
+ }
- fun dismiss(view: View, timeout: Long) = getJunkListener(view, "dismiss", timeout)
+ fun dismiss(view: View): DynamicAnimation.OnAnimationUpdateListener {
+ return createJunkListener(view, "dismiss")
+ }
- private fun getJunkListener(
+ private fun createJunkListener(
view: View,
type: String,
- timeout: Long,
- ): Animator.AnimatorListener {
- return object : AnimatorListenerAdapter() {
- override fun onAnimationStart(animation: Animator) {
+ ): DynamicAnimation.OnAnimationUpdateListener {
+ var trackedStart = false
+ return DynamicAnimation.OnAnimationUpdateListener { animation, _, _ ->
+ if (!trackedStart) {
+ trackedStart = true
interactionJankMonitor.begin(
InteractionJankMonitor.Configuration.Builder.withView(
Cuj.CUJ_VOLUME_CONTROL,
view,
)
.setTag(type)
- .setTimeout(timeout)
)
- }
-
- override fun onAnimationEnd(animation: Animator) {
- interactionJankMonitor.end(Cuj.CUJ_VOLUME_CONTROL)
- }
-
- override fun onAnimationCancel(animation: Animator) {
- interactionJankMonitor.cancel(Cuj.CUJ_VOLUME_CONTROL)
+ animation.addEndListener { _, canceled, _, _ ->
+ if (canceled) {
+ interactionJankMonitor.cancel(Cuj.CUJ_VOLUME_CONTROL)
+ } else {
+ interactionJankMonitor.end(Cuj.CUJ_VOLUME_CONTROL)
+ }
+ }
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/utils/SuspendAnimators.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/utils/SuspendAnimators.kt
index 52a19e0903e2..31e596f0278d 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/utils/SuspendAnimators.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/utils/SuspendAnimators.kt
@@ -96,21 +96,23 @@ suspend fun <T> ValueAnimator.suspendAnimate(onValueChanged: (T) -> Unit) {
* Starts spring animation and suspends until it's finished. Cancels the animation if the running
* coroutine is cancelled.
*/
-suspend fun SpringAnimation.suspendAnimate(onAnimationUpdate: (Float) -> Unit) =
- suspendCancellableCoroutine { continuation ->
- val updateListener =
- DynamicAnimation.OnAnimationUpdateListener { _, value, _ -> onAnimationUpdate(value) }
- val endListener =
- DynamicAnimation.OnAnimationEndListener { _, _, _, _ -> continuation.resumeIfCan(Unit) }
- addUpdateListener(updateListener)
- addEndListener(endListener)
- animateToFinalPosition(1F)
- continuation.invokeOnCancellation {
- removeUpdateListener(updateListener)
- removeEndListener(endListener)
- cancel()
- }
+suspend fun SpringAnimation.suspendAnimate(
+ finalPosition: Float = 1f,
+ onAnimationUpdate: (Float) -> Unit = {},
+) = suspendCancellableCoroutine { continuation ->
+ val updateListener =
+ DynamicAnimation.OnAnimationUpdateListener { _, value, _ -> onAnimationUpdate(value) }
+ val endListener =
+ DynamicAnimation.OnAnimationEndListener { _, _, _, _ -> continuation.resumeIfCan(Unit) }
+ addUpdateListener(updateListener)
+ addEndListener(endListener)
+ animateToFinalPosition(finalPosition)
+ continuation.invokeOnCancellation {
+ removeUpdateListener(updateListener)
+ removeEndListener(endListener)
+ cancel()
}
+}
/**
* Starts the animation and suspends until it's finished. Cancels the animation if the running
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/window/data/repository/NoopWindowRootViewBlurRepository.kt b/packages/SystemUI/src/com/android/systemui/window/data/repository/NoopWindowRootViewBlurRepository.kt
index 80aa11a71569..f1dd374d3e1d 100644
--- a/packages/SystemUI/src/com/android/systemui/window/data/repository/NoopWindowRootViewBlurRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/window/data/repository/NoopWindowRootViewBlurRepository.kt
@@ -25,4 +25,5 @@ class NoopWindowRootViewBlurRepository @Inject constructor() : WindowRootViewBlu
override val blurRadius: MutableStateFlow<Int> = MutableStateFlow(0)
override val isBlurOpaque: MutableStateFlow<Boolean> = MutableStateFlow(true)
override val isBlurSupported: StateFlow<Boolean> = MutableStateFlow(false)
-} \ No newline at end of file
+ override var blurAppliedListener: BlurAppliedListener? = null
+}
diff --git a/packages/SystemUI/src/com/android/systemui/window/data/repository/WindowRootViewBlurRepository.kt b/packages/SystemUI/src/com/android/systemui/window/data/repository/WindowRootViewBlurRepository.kt
index 41ceda033bdf..f98efe1e3c33 100644
--- a/packages/SystemUI/src/com/android/systemui/window/data/repository/WindowRootViewBlurRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/window/data/repository/WindowRootViewBlurRepository.kt
@@ -26,6 +26,7 @@ import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
import com.android.systemui.window.data.repository.WindowRootViewBlurRepository.Companion.isDisableBlurSysPropSet
import java.util.concurrent.Executor
+import java.util.function.Consumer
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.awaitClose
@@ -34,6 +35,8 @@ import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.stateIn
+typealias BlurAppliedListener = Consumer<Int>
+
/** Repository that maintains state for the window blur effect. */
interface WindowRootViewBlurRepository {
val blurRadius: MutableStateFlow<Int>
@@ -42,6 +45,8 @@ interface WindowRootViewBlurRepository {
/** Is blur supported based on settings toggle and battery power saver mode. */
val isBlurSupported: StateFlow<Boolean>
+ var blurAppliedListener: BlurAppliedListener?
+
companion object {
/**
* Whether the `persist.sysui.disableBlur` is set, this is used to disable blur for tests.
@@ -82,6 +87,8 @@ constructor(
} // stateIn because this is backed by a binder call.
.stateIn(scope, SharingStarted.WhileSubscribed(), false)
+ override var blurAppliedListener: BlurAppliedListener? = null
+
private fun isBlurAllowed(): Boolean {
return ActivityManager.isHighEndGfx() && !isDisableBlurSysPropSet()
}
diff --git a/packages/SystemUI/src/com/android/systemui/window/domain/interactor/WindowRootViewBlurInteractor.kt b/packages/SystemUI/src/com/android/systemui/window/domain/interactor/WindowRootViewBlurInteractor.kt
index 7a88a2ef966b..5197242e8079 100644
--- a/packages/SystemUI/src/com/android/systemui/window/domain/interactor/WindowRootViewBlurInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/window/domain/interactor/WindowRootViewBlurInteractor.kt
@@ -16,7 +16,6 @@
package com.android.systemui.window.domain.interactor
-import android.annotation.SuppressLint
import android.util.Log
import com.android.systemui.Flags
import com.android.systemui.communal.domain.interactor.CommunalInteractor
@@ -25,11 +24,11 @@ import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER
+import com.android.systemui.window.data.repository.BlurAppliedListener
import com.android.systemui.window.data.repository.WindowRootViewBlurRepository
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
@@ -54,8 +53,6 @@ constructor(
private val communalInteractor: CommunalInteractor,
private val repository: WindowRootViewBlurRepository,
) {
- @SuppressLint("SharedFlowCreation") private val _onBlurAppliedEvent = MutableSharedFlow<Int>()
-
private var isBouncerTransitionInProgress: StateFlow<Boolean> =
if (Flags.bouncerUiRevamp()) {
keyguardTransitionInteractor
@@ -71,8 +68,16 @@ constructor(
* Invoked by the view after blur of [appliedBlurRadius] was successfully applied on the window
* root view.
*/
- suspend fun onBlurApplied(appliedBlurRadius: Int) {
- _onBlurAppliedEvent.emit(appliedBlurRadius)
+ fun onBlurApplied(appliedBlurRadius: Int) {
+ repository.blurAppliedListener?.accept(appliedBlurRadius)
+ }
+
+ /**
+ * Register a listener that gets invoked whenever blur is applied, clears the listener if the
+ * passed in value is null
+ */
+ fun registerBlurAppliedListener(listener: BlurAppliedListener?) {
+ repository.blurAppliedListener = listener
}
/**
@@ -84,11 +89,6 @@ constructor(
/** Radius of blur to be applied on the window root view. */
val blurRadius: StateFlow<Int> = repository.blurRadius.asStateFlow()
- /**
- * Emits the applied blur radius whenever blur is successfully applied to the window root view.
- */
- val onBlurAppliedEvent: Flow<Int> = _onBlurAppliedEvent
-
/** Whether the blur applied is opaque or transparent. */
val isBlurOpaque: Flow<Boolean> =
combine(
diff --git a/packages/SystemUI/src/com/android/systemui/window/ui/WindowRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/window/ui/WindowRootViewBinder.kt
index 153df7f29737..06532bc0cc2a 100644
--- a/packages/SystemUI/src/com/android/systemui/window/ui/WindowRootViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/window/ui/WindowRootViewBinder.kt
@@ -84,9 +84,16 @@ object WindowRootViewBinder {
combine(viewModel.blurRadius, viewModel.isBlurOpaque, ::Pair)
.filter { it.first >= 0 }
.collect { (blurRadius, isOpaque) ->
- // Expectation is that we schedule only one blur radius value
- // per frame
+ val newBlurRadius = blurRadius.toInt()
+ // Expectation is that we schedule only one frame callback per frame
if (wasUpdateScheduledForThisFrame) {
+ // Update this value so that the frame callback picks up this
+ // value when it runs
+ if (lastScheduledBlurRadius != newBlurRadius) {
+ Log.w(TAG, "Multiple blur values emitted in the same frame")
+ }
+ lastScheduledBlurRadius = newBlurRadius
+ lastScheduleBlurOpaqueness = isOpaque
return@collect
}
TrackTracer.instantForGroup(
@@ -94,7 +101,7 @@ object WindowRootViewBinder {
"preparedBlurRadius",
blurRadius,
)
- lastScheduledBlurRadius = blurRadius.toInt()
+ lastScheduledBlurRadius = newBlurRadius
lastScheduleBlurOpaqueness = isOpaque
wasUpdateScheduledForThisFrame = true
blurUtils.prepareBlur(
diff --git a/packages/SystemUI/src/com/android/systemui/window/ui/viewmodel/WindowRootViewModel.kt b/packages/SystemUI/src/com/android/systemui/window/ui/viewmodel/WindowRootViewModel.kt
index 1b42352f1736..e60e85335f7a 100644
--- a/packages/SystemUI/src/com/android/systemui/window/ui/viewmodel/WindowRootViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/window/ui/viewmodel/WindowRootViewModel.kt
@@ -18,18 +18,13 @@ package com.android.systemui.window.ui.viewmodel
import android.os.Build
import android.util.Log
-import com.android.app.tracing.coroutines.launchTraced
import com.android.systemui.Flags
import com.android.systemui.keyguard.ui.transitions.GlanceableHubTransition
import com.android.systemui.keyguard.ui.transitions.PrimaryBouncerTransition
-import com.android.systemui.lifecycle.ExclusiveActivatable
import com.android.systemui.window.domain.interactor.WindowRootViewBlurInteractor
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.awaitCancellation
-import kotlinx.coroutines.channels.Channel
-import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flatMapLatest
@@ -38,8 +33,6 @@ import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.flow.onEach
-typealias BlurAppliedUiEvent = Int
-
/** View model for window root view. */
@OptIn(ExperimentalCoroutinesApi::class)
class WindowRootViewModel
@@ -48,9 +41,7 @@ constructor(
primaryBouncerTransitions: Set<@JvmSuppressWildcards PrimaryBouncerTransition>,
glanceableHubTransitions: Set<@JvmSuppressWildcards GlanceableHubTransition>,
private val blurInteractor: WindowRootViewBlurInteractor,
-) : ExclusiveActivatable() {
-
- private val blurEvents = Channel<BlurAppliedUiEvent>(Channel.BUFFERED)
+) {
private val bouncerBlurRadiusFlows =
if (Flags.bouncerUiRevamp())
@@ -88,22 +79,11 @@ constructor(
}
}
- override suspend fun onActivated(): Nothing {
- coroutineScope {
- launchTraced("WindowRootViewModel#blurEvents") {
- for (event in blurEvents) {
- if (isLoggable) {
- Log.d(TAG, "blur applied for $event")
- }
- blurInteractor.onBlurApplied(event)
- }
- }
- }
- awaitCancellation()
- }
-
fun onBlurApplied(blurRadius: Int) {
- blurEvents.trySend(blurRadius)
+ if (isLoggable) {
+ Log.d(TAG, "blur applied for radius $blurRadius")
+ }
+ blurInteractor.onBlurApplied(blurRadius)
}
@AssistedFactory
@@ -120,9 +100,3 @@ constructor(
}
}
}
-
-/**
- * @property radius Radius of blur to be applied on the window root view.
- * @property isOpaque Whether the blur applied is opaque or transparent.
- */
-data class BlurState(val radius: Int, val isOpaque: Boolean)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationSettingsTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationSettingsTest.java
index 4553f983b898..45b9f4ad2322 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationSettingsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationSettingsTest.java
@@ -552,12 +552,23 @@ public class WindowMagnificationSettingsTest extends SysuiTestCase {
mockSeekBar,
OnSeekBarWithIconButtonsChangeListener.ControlUnitType.SLIDER);
- // should trigger callback to update magnifier scale and persist the scale
+ // Should trigger callback to update magnifier scale and persist the scale.
verify(mWindowMagnificationSettingsCallback)
.onMagnifierScale(/* scale= */ eq(4f), /* updatePersistence= */ eq(true));
}
@Test
+ public void onSeekbarUserInteractionFinalized_notFromUser_persistedScaleNotUpdated() {
+ OnSeekBarWithIconButtonsChangeListener onChangeListener =
+ mZoomSeekbar.getOnSeekBarWithIconButtonsChangeListener();
+ onChangeListener.onProgressChanged(mZoomSeekbar.getSeekbar(), 30, false);
+
+ // Should not trigger callback to update magnifier scale and persist the scale.
+ verify(mWindowMagnificationSettingsCallback, never())
+ .onMagnifierScale(/* scale= */ anyFloat(), /* updatePersistence= */ eq(true));
+ }
+
+ @Test
public void seekbarProgress_scaleUpdatedAfterSettingPanelOpened_progressAlsoUpdated() {
setupMagnificationCapabilityAndMode(
/* capability= */ ACCESSIBILITY_MAGNIFICATION_MODE_ALL,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothDetailsContentManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothDetailsContentManagerTest.kt
index 6ed990d513cb..9c932695f295 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothDetailsContentManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothDetailsContentManagerTest.kt
@@ -87,7 +87,7 @@ class BluetoothDetailsContentManagerTest : SysuiTestCase() {
private val fakeSystemClock = FakeSystemClock()
private val uiProperties =
- BluetoothTileDialogViewModel.UiProperties.build(
+ BluetoothDetailsContentViewModel.UiProperties.build(
isBluetoothEnabled = ENABLED,
isAutoOnToggleFeatureAvailable = ENABLED,
)
@@ -314,7 +314,7 @@ class BluetoothDetailsContentManagerTest : SysuiTestCase() {
val cachedHeight = Int.MAX_VALUE
val contentManager =
BluetoothDetailsContentManager(
- BluetoothTileDialogViewModel.UiProperties.build(ENABLED, ENABLED),
+ BluetoothDetailsContentViewModel.UiProperties.build(ENABLED, ENABLED),
cachedHeight,
bluetoothTileDialogCallback,
/* isInDialog= */ true,
@@ -339,7 +339,7 @@ class BluetoothDetailsContentManagerTest : SysuiTestCase() {
testScope.runTest {
val contentManager =
BluetoothDetailsContentManager(
- BluetoothTileDialogViewModel.UiProperties.build(ENABLED, ENABLED),
+ BluetoothDetailsContentViewModel.UiProperties.build(ENABLED, ENABLED),
MATCH_PARENT,
bluetoothTileDialogCallback,
/* isInDialog= */ true,
@@ -364,7 +364,7 @@ class BluetoothDetailsContentManagerTest : SysuiTestCase() {
testScope.runTest {
val contentManager =
BluetoothDetailsContentManager(
- BluetoothTileDialogViewModel.UiProperties.build(ENABLED, ENABLED),
+ BluetoothDetailsContentViewModel.UiProperties.build(ENABLED, ENABLED),
MATCH_PARENT,
bluetoothTileDialogCallback,
/* isInDialog= */ true,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothDetailsContentViewModelTest.kt
index 47a834be9b9c..bfc5361b6129 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothDetailsContentViewModelTest.kt
@@ -67,14 +67,14 @@ import org.mockito.junit.MockitoRule
@RunWith(AndroidJUnit4::class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
@EnableFlags(Flags.FLAG_BLUETOOTH_QS_TILE_DIALOG_AUTO_ON_TOGGLE)
-class BluetoothTileDialogViewModelTest : SysuiTestCase() {
+class BluetoothDetailsContentViewModelTest : SysuiTestCase() {
@get:Rule val mockitoRule: MockitoRule = MockitoJUnit.rule()
private val kosmos = testKosmos()
private val fakeSystemClock = FakeSystemClock()
private val backgroundExecutor = FakeExecutor(fakeSystemClock)
- private lateinit var bluetoothTileDialogViewModel: BluetoothTileDialogViewModel
+ private lateinit var bluetoothDetailsContentViewModel: BluetoothDetailsContentViewModel
@Mock private lateinit var bluetoothDeviceMetadataInteractor: BluetoothDeviceMetadataInteractor
@@ -126,8 +126,8 @@ class BluetoothTileDialogViewModelTest : SysuiTestCase() {
testScope = kosmos.testScope
// TODO(b/364515243): use real object instead of mock
whenever(kosmos.deviceItemInteractor.deviceItemUpdate).thenReturn(MutableSharedFlow())
- bluetoothTileDialogViewModel =
- BluetoothTileDialogViewModel(
+ bluetoothDetailsContentViewModel =
+ BluetoothDetailsContentViewModel(
deviceItemInteractor,
deviceItemActionInteractor,
BluetoothStateInteractor(
@@ -194,7 +194,7 @@ class BluetoothTileDialogViewModelTest : SysuiTestCase() {
@Test
fun testShowDetailsContent_noAnimation() {
testScope.runTest {
- bluetoothTileDialogViewModel.showDetailsContent(null, null)
+ bluetoothDetailsContentViewModel.showDetailsContent(null, null)
runCurrent()
verify(mDialogTransitionAnimator, never()).show(any(), any(), any())
@@ -204,7 +204,7 @@ class BluetoothTileDialogViewModelTest : SysuiTestCase() {
@Test
fun testShowDetailsContent_animated() {
testScope.runTest {
- bluetoothTileDialogViewModel.showDetailsContent(expandable, null)
+ bluetoothDetailsContentViewModel.showDetailsContent(expandable, null)
runCurrent()
verify(mDialogTransitionAnimator).show(any(), any(), anyBoolean())
@@ -214,7 +214,7 @@ class BluetoothTileDialogViewModelTest : SysuiTestCase() {
@Test
fun testShowDetailsContent_animated_inDetailsView() {
testScope.runTest {
- bluetoothTileDialogViewModel.showDetailsContent(expandable, mockView)
+ bluetoothDetailsContentViewModel.showDetailsContent(expandable, mockView)
runCurrent()
verify(bluetoothDetailsContentManager).bind(mockView)
@@ -226,7 +226,7 @@ class BluetoothTileDialogViewModelTest : SysuiTestCase() {
fun testShowDetailsContent_animated_callInBackgroundThread() {
testScope.runTest {
backgroundExecutor.execute {
- bluetoothTileDialogViewModel.showDetailsContent(expandable, null)
+ bluetoothDetailsContentViewModel.showDetailsContent(expandable, null)
runCurrent()
verify(mDialogTransitionAnimator).show(any(), any(), anyBoolean())
@@ -238,7 +238,7 @@ class BluetoothTileDialogViewModelTest : SysuiTestCase() {
fun testShowDetailsContent_animated_callInBackgroundThread_inDetailsView() {
testScope.runTest {
backgroundExecutor.execute {
- bluetoothTileDialogViewModel.showDetailsContent(expandable, mockView)
+ bluetoothDetailsContentViewModel.showDetailsContent(expandable, mockView)
runCurrent()
verify(bluetoothDetailsContentManager).bind(mockView)
@@ -250,7 +250,7 @@ class BluetoothTileDialogViewModelTest : SysuiTestCase() {
@Test
fun testShowDetailsContent_fetchDeviceItem() {
testScope.runTest {
- bluetoothTileDialogViewModel.showDetailsContent(null, null)
+ bluetoothDetailsContentViewModel.showDetailsContent(null, null)
runCurrent()
verify(deviceItemInteractor).deviceItemUpdate
@@ -261,11 +261,11 @@ class BluetoothTileDialogViewModelTest : SysuiTestCase() {
fun testStartSettingsActivity_activityLaunched_dialogDismissed() {
testScope.runTest {
whenever(deviceItem.cachedBluetoothDevice).thenReturn(cachedBluetoothDevice)
- bluetoothTileDialogViewModel.showDetailsContent(null, null)
+ bluetoothDetailsContentViewModel.showDetailsContent(null, null)
runCurrent()
val clickedView = View(context)
- bluetoothTileDialogViewModel.onPairNewDeviceClicked(clickedView)
+ bluetoothDetailsContentViewModel.onPairNewDeviceClicked(clickedView)
verify(uiEventLogger).log(BluetoothTileDialogUiEvent.PAIR_NEW_DEVICE_CLICKED)
verify(activityStarter).postStartActivityDismissingKeyguard(any(), anyInt(), nullable())
@@ -276,7 +276,7 @@ class BluetoothTileDialogViewModelTest : SysuiTestCase() {
fun testBuildUiProperties_bluetoothOn_shouldHideAutoOn() {
testScope.runTest {
val actual =
- BluetoothTileDialogViewModel.UiProperties.build(
+ BluetoothDetailsContentViewModel.UiProperties.build(
isBluetoothEnabled = true,
isAutoOnToggleFeatureAvailable = true,
)
@@ -288,7 +288,7 @@ class BluetoothTileDialogViewModelTest : SysuiTestCase() {
fun testBuildUiProperties_bluetoothOff_shouldShowAutoOn() {
testScope.runTest {
val actual =
- BluetoothTileDialogViewModel.UiProperties.build(
+ BluetoothDetailsContentViewModel.UiProperties.build(
isBluetoothEnabled = false,
isAutoOnToggleFeatureAvailable = true,
)
@@ -300,7 +300,7 @@ class BluetoothTileDialogViewModelTest : SysuiTestCase() {
fun testBuildUiProperties_bluetoothOff_autoOnFeatureUnavailable_shouldHideAutoOn() {
testScope.runTest {
val actual =
- BluetoothTileDialogViewModel.UiProperties.build(
+ BluetoothDetailsContentViewModel.UiProperties.build(
isBluetoothEnabled = false,
isAutoOnToggleFeatureAvailable = false,
)
@@ -313,7 +313,7 @@ class BluetoothTileDialogViewModelTest : SysuiTestCase() {
testScope.runTest {
whenever(bluetoothAdapter.isAutoOnSupported).thenReturn(true)
- val actual = bluetoothTileDialogViewModel.isAutoOnToggleFeatureAvailable()
+ val actual = bluetoothDetailsContentViewModel.isAutoOnToggleFeatureAvailable()
assertThat(actual).isTrue()
}
}
@@ -323,7 +323,7 @@ class BluetoothTileDialogViewModelTest : SysuiTestCase() {
testScope.runTest {
whenever(bluetoothAdapter.isAutoOnSupported).thenReturn(false)
- val actual = bluetoothTileDialogViewModel.isAutoOnToggleFeatureAvailable()
+ val actual = bluetoothDetailsContentViewModel.isAutoOnToggleFeatureAvailable()
assertThat(actual).isFalse()
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegateTest.kt
index 2788f1d95382..7f75b8f03533 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegateTest.kt
@@ -80,7 +80,7 @@ class BluetoothTileDialogDelegateTest : SysuiTestCase() {
@Mock private lateinit var dialogTransitionAnimator: DialogTransitionAnimator
private val uiProperties =
- BluetoothTileDialogViewModel.UiProperties.build(
+ BluetoothDetailsContentViewModel.UiProperties.build(
isBluetoothEnabled = ENABLED,
isAutoOnToggleFeatureAvailable = ENABLED,
)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
index 4ccfa29d4ba0..e8054c07eac8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -206,6 +206,7 @@ public class KeyguardViewMediatorTest extends SysuiTestCase {
private @Mock ShadeInteractor mShadeInteractor;
private @Mock ShadeWindowLogger mShadeWindowLogger;
private @Mock SelectedUserInteractor mSelectedUserInteractor;
+ private @Mock UserTracker.Callback mUserTrackerCallback;
private @Mock KeyguardInteractor mKeyguardInteractor;
private @Mock KeyguardTransitionBootInteractor mKeyguardTransitionBootInteractor;
private @Captor ArgumentCaptor<KeyguardStateController.Callback>
@@ -280,7 +281,7 @@ public class KeyguardViewMediatorTest extends SysuiTestCase {
() -> mShadeInteractor,
mShadeWindowLogger,
() -> mSelectedUserInteractor,
- mUserTracker,
+ mock(UserTracker.class),
mKosmos.getNotificationShadeWindowModel(),
mSecureSettings,
mKosmos::getCommunalInteractor,
@@ -318,7 +319,7 @@ public class KeyguardViewMediatorTest extends SysuiTestCase {
} catch (Exception e) {
// Just so we don't have to add the exception signature to every test.
- fail();
+ fail(e.getMessage());
}
}
@@ -330,18 +331,156 @@ public class KeyguardViewMediatorTest extends SysuiTestCase {
/* First test the default behavior: handleUserSwitching() is not invoked */
when(mUserTracker.isUserSwitching()).thenReturn(false);
- mViewMediator.mUpdateCallback = mock(KeyguardUpdateMonitorCallback.class);
mViewMediator.onSystemReady();
TestableLooper.get(this).processAllMessages();
- verify(mViewMediator.mUpdateCallback, never()).onUserSwitching(userId);
+ verify(mUserTrackerCallback, never()).onUserChanging(eq(userId), eq(mContext),
+ any(Runnable.class));
/* Next test user switching is already in progress when started */
when(mUserTracker.isUserSwitching()).thenReturn(true);
mViewMediator.onSystemReady();
TestableLooper.get(this).processAllMessages();
- verify(mViewMediator.mUpdateCallback).onUserSwitching(userId);
+ verify(mUserTrackerCallback).onUserChanging(eq(userId), eq(mContext),
+ any(Runnable.class));
+ }
+
+ @Test
+ @TestableLooper.RunWithLooper(setAsMainLooper = true)
+ public void testGoingAwayFollowedByBeforeUserSwitchDoesNotHideKeyguard() {
+ setCurrentUser(/* userId= */1099, /* isSecure= */false);
+
+ // Setup keyguard
+ mViewMediator.onSystemReady();
+ processAllMessagesAndBgExecutorMessages();
+ mViewMediator.setShowingLocked(true, "");
+
+ // Request keyguard going away
+ when(mKeyguardStateController.isKeyguardGoingAway()).thenReturn(true);
+ mViewMediator.mKeyguardGoingAwayRunnable.run();
+
+ // After the request, begin a switch to a new secure user
+ int nextUserId = 500;
+ setCurrentUser(nextUserId, /* isSecure= */true);
+ Runnable result = mock(Runnable.class);
+ mViewMediator.handleBeforeUserSwitching(nextUserId, result);
+ processAllMessagesAndBgExecutorMessages();
+ verify(result).run();
+
+ // After that request has begun, have WM tell us to exit keyguard
+ RemoteAnimationTarget[] apps = new RemoteAnimationTarget[]{
+ mock(RemoteAnimationTarget.class)
+ };
+ RemoteAnimationTarget[] wallpapers = new RemoteAnimationTarget[]{
+ mock(RemoteAnimationTarget.class)
+ };
+ IRemoteAnimationFinishedCallback callback = mock(IRemoteAnimationFinishedCallback.class);
+ mViewMediator.startKeyguardExitAnimation(TRANSIT_OLD_KEYGUARD_GOING_AWAY, apps, wallpapers,
+ null, callback);
+ processAllMessagesAndBgExecutorMessages();
+
+ // The call to exit should be rejected, and keyguard should still be visible
+ verify(mKeyguardUnlockAnimationController, never()).notifyStartSurfaceBehindRemoteAnimation(
+ any(), any(), any(), anyLong(), anyBoolean());
+ try {
+ assertATMSLockScreenShowing(true);
+ } catch (Exception e) {
+ fail(e.getMessage());
+ }
+ assertTrue(mViewMediator.isShowingAndNotOccluded());
+ }
+
+ @Test
+ @TestableLooper.RunWithLooper(setAsMainLooper = true)
+ public void testUserSwitchToSecureUserShowsBouncer() {
+ setCurrentUser(/* userId= */1099, /* isSecure= */true);
+
+ // Setup keyguard
+ mViewMediator.onSystemReady();
+ processAllMessagesAndBgExecutorMessages();
+ mViewMediator.setShowingLocked(true, "");
+
+ // After the request, begin a switch to a new secure user
+ int nextUserId = 500;
+ setCurrentUser(nextUserId, /* isSecure= */true);
+
+ Runnable beforeResult = mock(Runnable.class);
+ mViewMediator.handleBeforeUserSwitching(nextUserId, beforeResult);
+ processAllMessagesAndBgExecutorMessages();
+ verify(beforeResult).run();
+
+ // Dismiss should not be called while user switch is in progress
+ Runnable onSwitchResult = mock(Runnable.class);
+ mViewMediator.handleUserSwitching(nextUserId, onSwitchResult);
+ processAllMessagesAndBgExecutorMessages();
+ verify(onSwitchResult).run();
+ verify(mStatusBarKeyguardViewManager, never()).dismissAndCollapse();
+
+ // The attempt to dismiss only comes on user switch complete, which will trigger a call to
+ // show the bouncer in StatusBarKeyguardViewManager
+ mViewMediator.handleUserSwitchComplete(nextUserId);
+ TestableLooper.get(this).moveTimeForward(600);
+ processAllMessagesAndBgExecutorMessages();
+
+ verify(mStatusBarKeyguardViewManager).dismissAndCollapse();
+ }
+
+ @Test
+ @TestableLooper.RunWithLooper(setAsMainLooper = true)
+ public void testUserSwitchToInsecureUserDismissesKeyguard() {
+ int userId = 1099;
+ when(mUserTracker.getUserId()).thenReturn(userId);
+
+ // Setup keyguard
+ mViewMediator.onSystemReady();
+ processAllMessagesAndBgExecutorMessages();
+ mViewMediator.setShowingLocked(true, "");
+
+ // After the request, begin a switch to an insecure user
+ int nextUserId = 500;
+ when(mLockPatternUtils.isSecure(nextUserId)).thenReturn(false);
+
+ Runnable beforeResult = mock(Runnable.class);
+ mViewMediator.handleBeforeUserSwitching(nextUserId, beforeResult);
+ processAllMessagesAndBgExecutorMessages();
+ verify(beforeResult).run();
+
+ // The call to dismiss comes during the user switch
+ Runnable onSwitchResult = mock(Runnable.class);
+ mViewMediator.handleUserSwitching(nextUserId, onSwitchResult);
+ processAllMessagesAndBgExecutorMessages();
+ verify(onSwitchResult).run();
+
+ verify(mStatusBarKeyguardViewManager).dismissAndCollapse();
+ }
+
+ @Test
+ @TestableLooper.RunWithLooper(setAsMainLooper = true)
+ public void testUserSwitchToSecureUserWhileKeyguardNotVisibleShowsKeyguard() {
+ setCurrentUser(/* userId= */1099, /* isSecure= */true);
+
+ // Setup keyguard as not visible
+ mViewMediator.onSystemReady();
+ processAllMessagesAndBgExecutorMessages();
+ mViewMediator.setShowingLocked(false, "");
+ processAllMessagesAndBgExecutorMessages();
+
+ // Begin a switch to a new secure user
+ int nextUserId = 500;
+ setCurrentUser(nextUserId, /* isSecure= */true);
+
+ Runnable beforeResult = mock(Runnable.class);
+ mViewMediator.handleBeforeUserSwitching(nextUserId, beforeResult);
+ processAllMessagesAndBgExecutorMessages();
+ verify(beforeResult).run();
+
+ try {
+ assertATMSLockScreenShowing(true);
+ } catch (Exception e) {
+ fail();
+ }
+ assertTrue(mViewMediator.isShowingAndNotOccluded());
}
@Test
@@ -1105,7 +1244,7 @@ public class KeyguardViewMediatorTest extends SysuiTestCase {
processAllMessagesAndBgExecutorMessages();
verify(mStatusBarKeyguardViewManager, never()).reset(anyBoolean());
- assertATMSAndKeyguardViewMediatorStatesMatch();
+
}
@Test
@@ -1149,6 +1288,7 @@ public class KeyguardViewMediatorTest extends SysuiTestCase {
IRemoteAnimationFinishedCallback callback = mock(IRemoteAnimationFinishedCallback.class);
when(mKeyguardStateController.isKeyguardGoingAway()).thenReturn(true);
+ mViewMediator.mKeyguardGoingAwayRunnable.run();
mViewMediator.startKeyguardExitAnimation(TRANSIT_OLD_KEYGUARD_GOING_AWAY, apps, wallpapers,
null, callback);
processAllMessagesAndBgExecutorMessages();
@@ -1203,13 +1343,6 @@ public class KeyguardViewMediatorTest extends SysuiTestCase {
// The captor will have the most recent setLockScreenShown call's value.
assertEquals(showing, showingCaptor.getValue());
-
- // We're now just after the last setLockScreenShown call. If we expect the lockscreen to be
- // showing, ensure that we didn't subsequently ask for it to go away.
- if (showing) {
- orderedSetLockScreenShownCalls.verify(mActivityTaskManagerService, never())
- .keyguardGoingAway(anyInt());
- }
}
/**
@@ -1371,6 +1504,7 @@ public class KeyguardViewMediatorTest extends SysuiTestCase {
mKeyguardTransitionBootInteractor,
mKosmos::getCommunalSceneInteractor,
mock(WindowManagerOcclusionManager.class));
+ mViewMediator.mUserChangedCallback = mUserTrackerCallback;
mViewMediator.start();
mViewMediator.registerCentralSurfaces(mCentralSurfaces, null, null, null, null);
@@ -1384,4 +1518,10 @@ public class KeyguardViewMediatorTest extends SysuiTestCase {
private void captureKeyguardUpdateMonitorCallback() {
verify(mUpdateMonitor).registerCallback(mKeyguardUpdateMonitorCallbackCaptor.capture());
}
+
+ private void setCurrentUser(int userId, boolean isSecure) {
+ when(mUserTracker.getUserId()).thenReturn(userId);
+ when(mSelectedUserInteractor.getSelectedUserId()).thenReturn(userId);
+ when(mLockPatternUtils.isSecure(userId)).thenReturn(isSecure);
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BluetoothTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BluetoothTileTest.kt
index 1305b0c4c499..cfe34f446f36 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BluetoothTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BluetoothTileTest.kt
@@ -16,7 +16,7 @@ import com.android.internal.telephony.flags.Flags
import com.android.settingslib.Utils
import com.android.settingslib.bluetooth.CachedBluetoothDevice
import com.android.systemui.SysuiTestCase
-import com.android.systemui.bluetooth.qsdialog.BluetoothTileDialogViewModel
+import com.android.systemui.bluetooth.qsdialog.BluetoothDetailsContentViewModel
import com.android.systemui.classifier.FalsingManagerFake
import com.android.systemui.flags.FeatureFlagsClassic
import com.android.systemui.plugins.ActivityStarter
@@ -71,7 +71,7 @@ class BluetoothTileTest(flags: FlagsParameterization) : SysuiTestCase() {
@Mock private lateinit var bluetoothController: BluetoothController
@Mock private lateinit var uiEventLogger: QsEventLogger
@Mock private lateinit var featureFlags: FeatureFlagsClassic
- @Mock private lateinit var bluetoothTileDialogViewModel: BluetoothTileDialogViewModel
+ @Mock private lateinit var bluetoothDetailsContentViewModel: BluetoothDetailsContentViewModel
@Mock private lateinit var clickJob: Job
private lateinit var testableLooper: TestableLooper
private lateinit var tile: FakeBluetoothTile
@@ -96,7 +96,7 @@ class BluetoothTileTest(flags: FlagsParameterization) : SysuiTestCase() {
qsLogger,
bluetoothController,
featureFlags,
- bluetoothTileDialogViewModel,
+ bluetoothDetailsContentViewModel,
)
tile.initialize()
@@ -238,7 +238,7 @@ class BluetoothTileTest(flags: FlagsParameterization) : SysuiTestCase() {
tile.handleClick(null)
- verify(bluetoothTileDialogViewModel)
+ verify(bluetoothDetailsContentViewModel)
.showDetailsContent(/* expandable= */ null, /* view= */ null)
}
@@ -308,7 +308,7 @@ class BluetoothTileTest(flags: FlagsParameterization) : SysuiTestCase() {
qsLogger: QSLogger,
bluetoothController: BluetoothController,
featureFlags: FeatureFlagsClassic,
- bluetoothTileDialogViewModel: BluetoothTileDialogViewModel,
+ bluetoothDetailsContentViewModel: BluetoothDetailsContentViewModel,
) :
BluetoothTile(
qsHost,
@@ -322,7 +322,7 @@ class BluetoothTileTest(flags: FlagsParameterization) : SysuiTestCase() {
qsLogger,
bluetoothController,
featureFlags,
- bluetoothTileDialogViewModel,
+ bluetoothDetailsContentViewModel,
) {
var restrictionChecked: String? = null
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/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
index 9137215f8c61..14a1233045bb 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,7 +63,6 @@ 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;
@@ -119,8 +118,10 @@ public class ScrimControllerTest extends SysuiTestCase {
@Rule public Expect mExpect = Expect.create();
private final KosmosJavaAdapter mKosmos = new KosmosJavaAdapter(this);
- private FakeConfigurationController mConfigurationController;
- private LargeScreenShadeInterpolator mLinearLargeScreenShadeInterpolator;
+ private final FakeConfigurationController mConfigurationController =
+ new FakeConfigurationController();
+ private final LargeScreenShadeInterpolator
+ mLinearLargeScreenShadeInterpolator = new LinearLargeScreenShadeInterpolator();
private final TestScope mTestScope = mKosmos.getTestScope();
private final JavaAdapter mJavaAdapter = new JavaAdapter(mTestScope.getBackgroundScope());
@@ -136,7 +137,6 @@ 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,11 +149,12 @@ 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;
@@ -237,9 +238,6 @@ 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);
@@ -272,9 +270,6 @@ public class ScrimControllerTest extends SysuiTestCase {
when(mAlternateBouncerToGoneTransitionViewModel.getScrimAlpha())
.thenReturn(emptyFlow());
- mKeyguardTransitionRepository = mKosmos.getKeyguardTransitionRepository();
- mKeyguardTransitionInteractor = mKosmos.getKeyguardTransitionInteractor();
-
mScrimController = new ScrimController(
mLightBarController,
mDozeParameters,
@@ -327,7 +322,6 @@ public class ScrimControllerTest extends SysuiTestCase {
}
@Test
- @DisableSceneContainer
public void transitionToKeyguard() {
mScrimController.legacyTransitionTo(ScrimState.KEYGUARD);
finishAnimationsImmediately();
@@ -343,7 +337,7 @@ public class ScrimControllerTest extends SysuiTestCase {
}
@Test
-@DisableSceneContainer void transitionToShadeLocked() {
+ public void transitionToShadeLocked() {
mScrimController.legacyTransitionTo(SHADE_LOCKED);
mScrimController.setQsPosition(1f, 0);
finishAnimationsImmediately();
@@ -379,7 +373,6 @@ public class ScrimControllerTest extends SysuiTestCase {
}
@Test
- @DisableSceneContainer
public void transitionToShadeLocked_clippingQs() {
mScrimController.setClipsQsScrim(true);
mScrimController.legacyTransitionTo(SHADE_LOCKED);
@@ -398,7 +391,6 @@ public class ScrimControllerTest extends SysuiTestCase {
}
@Test
- @DisableSceneContainer
public void transitionToOff() {
mScrimController.legacyTransitionTo(ScrimState.OFF);
finishAnimationsImmediately();
@@ -414,7 +406,6 @@ public class ScrimControllerTest extends SysuiTestCase {
}
@Test
- @DisableSceneContainer
public void transitionToAod_withRegularWallpaper() {
mScrimController.legacyTransitionTo(ScrimState.AOD);
finishAnimationsImmediately();
@@ -430,7 +421,6 @@ 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);
@@ -475,7 +465,6 @@ 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);
@@ -517,7 +506,6 @@ public class ScrimControllerTest extends SysuiTestCase {
}
@Test
- @DisableSceneContainer
public void transitionToPulsing_withFrontAlphaUpdates() {
// Pre-condition
// Need to go to AoD first because PULSING doesn't change
@@ -563,7 +551,6 @@ public class ScrimControllerTest extends SysuiTestCase {
}
@Test
- @DisableSceneContainer
public void transitionToKeyguardBouncer() {
mScrimController.legacyTransitionTo(BOUNCER);
finishAnimationsImmediately();
@@ -584,7 +571,6 @@ public class ScrimControllerTest extends SysuiTestCase {
}
@Test
- @DisableSceneContainer
public void lockscreenToHubTransition_setsBehindScrimAlpha() {
// Start on lockscreen.
mScrimController.legacyTransitionTo(ScrimState.KEYGUARD);
@@ -631,7 +617,6 @@ public class ScrimControllerTest extends SysuiTestCase {
}
@Test
- @DisableSceneContainer
public void hubToLockscreenTransition_setsViewAlpha() {
// Start on glanceable hub.
mScrimController.legacyTransitionTo(ScrimState.GLANCEABLE_HUB);
@@ -678,7 +663,6 @@ public class ScrimControllerTest extends SysuiTestCase {
}
@Test
- @DisableSceneContainer
public void transitionToHub() {
mScrimController.setRawPanelExpansionFraction(0f);
mScrimController.setBouncerHiddenFraction(KeyguardBouncerConstants.EXPANSION_HIDDEN);
@@ -693,7 +677,6 @@ public class ScrimControllerTest extends SysuiTestCase {
}
@Test
- @DisableSceneContainer
public void openBouncerOnHub() {
mScrimController.legacyTransitionTo(ScrimState.GLANCEABLE_HUB);
@@ -723,7 +706,6 @@ public class ScrimControllerTest extends SysuiTestCase {
}
@Test
- @DisableSceneContainer
public void openShadeOnHub() {
mScrimController.legacyTransitionTo(ScrimState.GLANCEABLE_HUB);
@@ -752,7 +734,6 @@ public class ScrimControllerTest extends SysuiTestCase {
}
@Test
- @DisableSceneContainer
public void transitionToHubOverDream() {
mScrimController.setRawPanelExpansionFraction(0f);
mScrimController.setBouncerHiddenFraction(KeyguardBouncerConstants.EXPANSION_HIDDEN);
@@ -767,7 +748,6 @@ public class ScrimControllerTest extends SysuiTestCase {
}
@Test
- @DisableSceneContainer
public void openBouncerOnHubOverDream() {
mScrimController.legacyTransitionTo(ScrimState.GLANCEABLE_HUB_OVER_DREAM);
@@ -797,7 +777,6 @@ public class ScrimControllerTest extends SysuiTestCase {
}
@Test
- @DisableSceneContainer
public void openShadeOnHubOverDream() {
mScrimController.legacyTransitionTo(ScrimState.GLANCEABLE_HUB_OVER_DREAM);
@@ -826,7 +805,6 @@ public class ScrimControllerTest extends SysuiTestCase {
}
@Test
- @DisableSceneContainer
public void onThemeChange_bouncerBehindTint_isUpdatedToSurfaceColor() {
assertEquals(BOUNCER.getBehindTint(), 0x112233);
mSurfaceColor = 0x223344;
@@ -835,7 +813,6 @@ public class ScrimControllerTest extends SysuiTestCase {
}
@Test
- @DisableSceneContainer
public void onThemeChangeWhileClipQsScrim_bouncerBehindTint_remainsBlack() {
mScrimController.setClipsQsScrim(true);
mScrimController.legacyTransitionTo(BOUNCER);
@@ -848,7 +825,6 @@ public class ScrimControllerTest extends SysuiTestCase {
}
@Test
- @DisableSceneContainer
public void transitionToKeyguardBouncer_clippingQs() {
mScrimController.setClipsQsScrim(true);
mScrimController.legacyTransitionTo(BOUNCER);
@@ -869,7 +845,6 @@ public class ScrimControllerTest extends SysuiTestCase {
}
@Test
- @DisableSceneContainer
public void disableClipQsScrimWithoutStateTransition_updatesTintAndAlpha() {
mScrimController.setClipsQsScrim(true);
mScrimController.legacyTransitionTo(BOUNCER);
@@ -892,7 +867,6 @@ public class ScrimControllerTest extends SysuiTestCase {
}
@Test
- @DisableSceneContainer
public void enableClipQsScrimWithoutStateTransition_updatesTintAndAlpha() {
mScrimController.setClipsQsScrim(false);
mScrimController.legacyTransitionTo(BOUNCER);
@@ -915,7 +889,6 @@ public class ScrimControllerTest extends SysuiTestCase {
}
@Test
- @DisableSceneContainer
public void transitionToBouncer() {
mScrimController.legacyTransitionTo(ScrimState.BOUNCER_SCRIMMED);
finishAnimationsImmediately();
@@ -929,7 +902,6 @@ public class ScrimControllerTest extends SysuiTestCase {
}
@Test
- @DisableSceneContainer
public void transitionToUnlocked_clippedQs() {
mScrimController.setClipsQsScrim(true);
mScrimController.setRawPanelExpansionFraction(0f);
@@ -988,7 +960,6 @@ public class ScrimControllerTest extends SysuiTestCase {
}
@Test
- @DisableSceneContainer
public void transitionToUnlocked_nonClippedQs_followsLargeScreensInterpolator() {
mScrimController.setClipsQsScrim(false);
mScrimController.setRawPanelExpansionFraction(0f);
@@ -1028,7 +999,6 @@ public class ScrimControllerTest extends SysuiTestCase {
}
@Test
- @DisableSceneContainer
public void scrimStateCallback() {
mScrimController.legacyTransitionTo(ScrimState.UNLOCKED);
finishAnimationsImmediately();
@@ -1044,7 +1014,6 @@ public class ScrimControllerTest extends SysuiTestCase {
}
@Test
- @DisableSceneContainer
public void panelExpansion() {
mScrimController.setRawPanelExpansionFraction(0f);
mScrimController.setRawPanelExpansionFraction(0.5f);
@@ -1067,7 +1036,6 @@ public class ScrimControllerTest extends SysuiTestCase {
}
@Test
- @DisableSceneContainer
public void qsExpansion() {
reset(mScrimBehind);
mScrimController.setQsPosition(1f, 999 /* value doesn't matter */);
@@ -1080,7 +1048,6 @@ public class ScrimControllerTest extends SysuiTestCase {
}
@Test
- @DisableSceneContainer
public void qsExpansion_clippingQs() {
reset(mScrimBehind);
mScrimController.setClipsQsScrim(true);
@@ -1094,7 +1061,6 @@ public class ScrimControllerTest extends SysuiTestCase {
}
@Test
- @DisableSceneContainer
public void qsExpansion_half_clippingQs() {
reset(mScrimBehind);
mScrimController.setClipsQsScrim(true);
@@ -1108,7 +1074,6 @@ public class ScrimControllerTest extends SysuiTestCase {
}
@Test
- @DisableSceneContainer
public void panelExpansionAffectsAlpha() {
mScrimController.setRawPanelExpansionFraction(0f);
mScrimController.setRawPanelExpansionFraction(0.5f);
@@ -1131,7 +1096,6 @@ public class ScrimControllerTest extends SysuiTestCase {
}
@Test
- @DisableSceneContainer
public void transitionToUnlockedFromOff() {
// Simulate unlock with fingerprint without AOD
mScrimController.legacyTransitionTo(ScrimState.OFF);
@@ -1154,7 +1118,6 @@ public class ScrimControllerTest extends SysuiTestCase {
}
@Test
- @DisableSceneContainer
public void transitionToUnlockedFromAod() {
// Simulate unlock with fingerprint
mScrimController.legacyTransitionTo(ScrimState.AOD);
@@ -1177,7 +1140,6 @@ public class ScrimControllerTest extends SysuiTestCase {
}
@Test
- @DisableSceneContainer
public void scrimBlanksBeforeLeavingAod() {
// Simulate unlock with fingerprint
mScrimController.legacyTransitionTo(ScrimState.AOD);
@@ -1201,7 +1163,6 @@ public class ScrimControllerTest extends SysuiTestCase {
}
@Test
- @DisableSceneContainer
public void scrimBlankCallbackWhenUnlockingFromPulse() {
boolean[] blanked = {false};
// Simulate unlock with fingerprint
@@ -1220,7 +1181,6 @@ public class ScrimControllerTest extends SysuiTestCase {
}
@Test
- @DisableSceneContainer
public void blankingNotRequired_leavingAoD() {
// GIVEN display does NOT need blanking
when(mDozeParameters.getDisplayNeedsBlanking()).thenReturn(false);
@@ -1276,7 +1236,6 @@ public class ScrimControllerTest extends SysuiTestCase {
}
@Test
- @DisableSceneContainer
public void testScrimCallback() {
int[] callOrder = {0, 0, 0};
int[] currentCall = {0};
@@ -1303,14 +1262,12 @@ 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() {
@@ -1324,7 +1281,6 @@ public class ScrimControllerTest extends SysuiTestCase {
}
@Test
- @DisableSceneContainer
public void testHoldsWakeLock_whenAOD() {
mScrimController.legacyTransitionTo(ScrimState.AOD);
verify(mWakeLock).acquire(anyString());
@@ -1334,7 +1290,6 @@ public class ScrimControllerTest extends SysuiTestCase {
}
@Test
- @DisableSceneContainer
public void testDoesNotHoldWakeLock_whenUnlocking() {
mScrimController.legacyTransitionTo(ScrimState.UNLOCKED);
finishAnimationsImmediately();
@@ -1342,7 +1297,6 @@ public class ScrimControllerTest extends SysuiTestCase {
}
@Test
- @DisableSceneContainer
public void testCallbackInvokedOnSameStateTransition() {
mScrimController.legacyTransitionTo(ScrimState.UNLOCKED);
finishAnimationsImmediately();
@@ -1352,7 +1306,6 @@ public class ScrimControllerTest extends SysuiTestCase {
}
@Test
- @DisableSceneContainer
public void testConservesExpansionOpacityAfterTransition() {
mScrimController.legacyTransitionTo(ScrimState.UNLOCKED);
mScrimController.setRawPanelExpansionFraction(0.5f);
@@ -1370,7 +1323,6 @@ public class ScrimControllerTest extends SysuiTestCase {
}
@Test
- @DisableSceneContainer
public void testCancelsOldAnimationBeforeBlanking() {
mScrimController.legacyTransitionTo(ScrimState.AOD);
finishAnimationsImmediately();
@@ -1383,7 +1335,6 @@ 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());
@@ -1392,7 +1343,6 @@ public class ScrimControllerTest extends SysuiTestCase {
}
@Test
- @DisableSceneContainer
public void testEatsTouchEvent() {
HashSet<ScrimState> eatsTouches =
new HashSet<>(Collections.singletonList(ScrimState.AOD));
@@ -1409,7 +1359,6 @@ public class ScrimControllerTest extends SysuiTestCase {
}
@Test
- @DisableSceneContainer
public void testAnimatesTransitionToAod() {
when(mDozeParameters.shouldControlScreenOff()).thenReturn(false);
ScrimState.AOD.prepare(ScrimState.KEYGUARD);
@@ -1424,7 +1373,6 @@ public class ScrimControllerTest extends SysuiTestCase {
}
@Test
- @DisableSceneContainer
public void testIsLowPowerMode() {
HashSet<ScrimState> lowPowerModeStates = new HashSet<>(Arrays.asList(
ScrimState.OFF, ScrimState.AOD, ScrimState.PULSING));
@@ -1442,7 +1390,6 @@ public class ScrimControllerTest extends SysuiTestCase {
}
@Test
- @DisableSceneContainer
public void testScrimsOpaque_whenShadeFullyExpanded() {
mScrimController.legacyTransitionTo(ScrimState.UNLOCKED);
mScrimController.setRawPanelExpansionFraction(1);
@@ -1457,7 +1404,6 @@ public class ScrimControllerTest extends SysuiTestCase {
}
@Test
- @DisableSceneContainer
public void testScrimsVisible_whenShadeVisible() {
mScrimController.setClipsQsScrim(true);
mScrimController.legacyTransitionTo(ScrimState.UNLOCKED);
@@ -1473,7 +1419,6 @@ 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);
@@ -1494,7 +1439,6 @@ public class ScrimControllerTest extends SysuiTestCase {
}
@Test
- @DisableSceneContainer
public void testScrimsVisible_whenShadeVisible_clippingQs() {
mScrimController.setClipsQsScrim(true);
mScrimController.legacyTransitionTo(ScrimState.UNLOCKED);
@@ -1510,7 +1454,6 @@ public class ScrimControllerTest extends SysuiTestCase {
}
@Test
- @DisableSceneContainer
public void testScrimsVisible_whenShadeVisibleOnLockscreen() {
mScrimController.legacyTransitionTo(ScrimState.KEYGUARD);
mScrimController.setQsPosition(0.25f, 300);
@@ -1522,7 +1465,6 @@ 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
@@ -1535,7 +1477,6 @@ public class ScrimControllerTest extends SysuiTestCase {
}
@Test
- @DisableSceneContainer
public void testNotificationScrimVisible_afterOpeningShadeFromLockscreen() {
mScrimController.setRawPanelExpansionFraction(1);
mScrimController.legacyTransitionTo(SHADE_LOCKED);
@@ -1547,7 +1488,6 @@ 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
@@ -1564,7 +1504,6 @@ public class ScrimControllerTest extends SysuiTestCase {
}
@Test
- @DisableSceneContainer
public void expansionNotificationAlpha_shadeLocked_bouncerActive_usesBouncerInterpolator() {
when(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit()).thenReturn(true);
@@ -1581,7 +1520,6 @@ public class ScrimControllerTest extends SysuiTestCase {
}
@Test
- @DisableSceneContainer
public void expansionNotificationAlpha_shadeLocked_bouncerNotActive_usesShadeInterpolator() {
when(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit()).thenReturn(false);
@@ -1597,7 +1535,6 @@ public class ScrimControllerTest extends SysuiTestCase {
}
@Test
- @DisableSceneContainer
public void notificationAlpha_unnocclusionAnimating_bouncerNotActive_usesKeyguardNotifAlpha() {
when(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit()).thenReturn(false);
@@ -1617,7 +1554,6 @@ public class ScrimControllerTest extends SysuiTestCase {
}
@Test
- @DisableSceneContainer
public void notificationAlpha_inKeyguardState_bouncerActive_usesInvertedBouncerInterpolator() {
when(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit()).thenReturn(true);
mScrimController.setClipsQsScrim(true);
@@ -1638,7 +1574,6 @@ public class ScrimControllerTest extends SysuiTestCase {
}
@Test
- @DisableSceneContainer
public void notificationAlpha_inKeyguardState_bouncerNotActive_usesInvertedShadeInterpolator() {
when(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit()).thenReturn(false);
mScrimController.setClipsQsScrim(true);
@@ -1659,7 +1594,6 @@ public class ScrimControllerTest extends SysuiTestCase {
}
@Test
- @DisableSceneContainer
public void behindTint_inKeyguardState_bouncerNotActive_usesKeyguardBehindTint() {
when(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit()).thenReturn(false);
mScrimController.setClipsQsScrim(false);
@@ -1671,7 +1605,6 @@ public class ScrimControllerTest extends SysuiTestCase {
}
@Test
- @DisableSceneContainer
public void testNotificationTransparency_followsTransitionToFullShade() {
mScrimController.setClipsQsScrim(true);
@@ -1713,7 +1646,6 @@ public class ScrimControllerTest extends SysuiTestCase {
}
@Test
- @DisableSceneContainer
public void notificationTransparency_followsNotificationScrimProgress() {
mScrimController.legacyTransitionTo(SHADE_LOCKED);
mScrimController.setRawPanelExpansionFraction(1.0f);
@@ -1730,7 +1662,6 @@ public class ScrimControllerTest extends SysuiTestCase {
}
@Test
- @DisableSceneContainer
public void notificationAlpha_qsNotClipped_alphaMatchesNotificationExpansionProgress() {
mScrimController.setClipsQsScrim(false);
mScrimController.legacyTransitionTo(ScrimState.KEYGUARD);
@@ -1766,7 +1697,6 @@ public class ScrimControllerTest extends SysuiTestCase {
}
@Test
- @DisableSceneContainer
public void setNotificationsOverScrollAmount_setsTranslationYOnNotificationsScrim() {
int overScrollAmount = 10;
@@ -1776,7 +1706,6 @@ public class ScrimControllerTest extends SysuiTestCase {
}
@Test
- @DisableSceneContainer
public void setNotificationsOverScrollAmount_doesNotSetTranslationYOnBehindScrim() {
int overScrollAmount = 10;
@@ -1786,7 +1715,6 @@ public class ScrimControllerTest extends SysuiTestCase {
}
@Test
- @DisableSceneContainer
public void setNotificationsOverScrollAmount_doesNotSetTranslationYOnFrontScrim() {
int overScrollAmount = 10;
@@ -1796,7 +1724,6 @@ public class ScrimControllerTest extends SysuiTestCase {
}
@Test
- @DisableSceneContainer
public void notificationBoundsTopGetsPassedToKeyguard() {
mScrimController.legacyTransitionTo(SHADE_LOCKED);
mScrimController.setQsPosition(1f, 0);
@@ -1807,7 +1734,6 @@ public class ScrimControllerTest extends SysuiTestCase {
}
@Test
- @DisableSceneContainer
public void notificationBoundsTopDoesNotGetPassedToKeyguardWhenNotifScrimIsNotVisible() {
mScrimController.setKeyguardOccluded(true);
mScrimController.legacyTransitionTo(ScrimState.KEYGUARD);
@@ -1818,7 +1744,6 @@ public class ScrimControllerTest extends SysuiTestCase {
}
@Test
- @DisableSceneContainer
public void transitionToDreaming() {
mScrimController.setRawPanelExpansionFraction(0f);
mScrimController.setBouncerHiddenFraction(KeyguardBouncerConstants.EXPANSION_HIDDEN);
@@ -1838,7 +1763,6 @@ public class ScrimControllerTest extends SysuiTestCase {
}
@Test
- @DisableSceneContainer
public void keyguardGoingAwayUpdateScrims() {
when(mKeyguardStateController.isKeyguardGoingAway()).thenReturn(true);
mScrimController.updateScrims();
@@ -1848,7 +1772,6 @@ public class ScrimControllerTest extends SysuiTestCase {
@Test
- @DisableSceneContainer
public void setUnOccludingAnimationKeyguard() {
mScrimController.legacyTransitionTo(ScrimState.KEYGUARD);
finishAnimationsImmediately();
@@ -1863,7 +1786,6 @@ public class ScrimControllerTest extends SysuiTestCase {
}
@Test
- @DisableSceneContainer
public void testHidesScrimFlickerInActivity() {
mScrimController.setKeyguardOccluded(true);
mScrimController.legacyTransitionTo(ScrimState.KEYGUARD);
@@ -1882,7 +1804,6 @@ public class ScrimControllerTest extends SysuiTestCase {
}
@Test
- @DisableSceneContainer
public void notificationAlpha_inKeyguardState_bouncerNotActive_clipsQsScrimFalse() {
mScrimController.setClipsQsScrim(false);
mScrimController.legacyTransitionTo(ScrimState.KEYGUARD);
@@ -1892,7 +1813,6 @@ public class ScrimControllerTest extends SysuiTestCase {
}
@Test
- @DisableSceneContainer
public void aodStateSetsFrontScrimToNotBlend() {
mScrimController.legacyTransitionTo(ScrimState.AOD);
assertFalse("Front scrim should not blend with main color",
@@ -1900,7 +1820,6 @@ public class ScrimControllerTest extends SysuiTestCase {
}
@Test
- @DisableSceneContainer
public void applyState_unlocked_bouncerShowing() {
mScrimController.legacyTransitionTo(ScrimState.UNLOCKED);
mScrimController.setBouncerHiddenFraction(0.99f);
@@ -1910,7 +1829,6 @@ public class ScrimControllerTest extends SysuiTestCase {
}
@Test
- @DisableSceneContainer
public void ignoreTransitionRequestWhileKeyguardTransitionRunning() {
mScrimController.legacyTransitionTo(ScrimState.UNLOCKED);
mScrimController.mBouncerToGoneTransition.accept(
@@ -1923,7 +1841,6 @@ public class ScrimControllerTest extends SysuiTestCase {
}
@Test
- @DisableSceneContainer
public void primaryBouncerToGoneOnFinishCallsKeyguardFadedAway() {
when(mKeyguardStateController.isKeyguardFadingAway()).thenReturn(true);
mScrimController.mBouncerToGoneTransition.accept(
@@ -1934,7 +1851,6 @@ public class ScrimControllerTest extends SysuiTestCase {
}
@Test
- @DisableSceneContainer
public void primaryBouncerToGoneOnFinishCallsLightBarController() {
reset(mLightBarController);
mScrimController.mBouncerToGoneTransition.accept(
@@ -1946,7 +1862,6 @@ public class ScrimControllerTest extends SysuiTestCase {
}
@Test
- @DisableSceneContainer
public void testDoNotAnimateChangeIfOccludeAnimationPlaying() {
mScrimController.setOccludeAnimationPlaying(true);
mScrimController.legacyTransitionTo(ScrimState.UNLOCKED);
@@ -1955,7 +1870,6 @@ public class ScrimControllerTest extends SysuiTestCase {
}
@Test
- @DisableSceneContainer
public void testNotifScrimAlpha_1f_afterUnlockFinishedAndExpanded() {
mScrimController.legacyTransitionTo(ScrimState.KEYGUARD);
when(mKeyguardUnlockAnimationController.isPlayingCannedUnlockAnimation()).thenReturn(true);
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalSceneTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalSceneTransitionInteractorKosmos.kt
index e6e59e1a523e..75c4b6f5366b 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalSceneTransitionInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalSceneTransitionInteractorKosmos.kt
@@ -22,6 +22,7 @@ import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.power.domain.interactor.powerInteractor
val Kosmos.communalSceneTransitionInteractor: CommunalSceneTransitionInteractor by
Kosmos.Fixture {
@@ -33,5 +34,6 @@ val Kosmos.communalSceneTransitionInteractor: CommunalSceneTransitionInteractor
sceneInteractor = communalSceneInteractor,
repository = communalSceneTransitionRepository,
keyguardInteractor = keyguardInteractor,
+ powerInteractor = powerInteractor,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractorKosmos.kt
index 7b0d208298d0..38372acff113 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractorKosmos.kt
@@ -18,6 +18,8 @@ package com.android.systemui.keyguard.domain.interactor
import com.android.systemui.bouncer.domain.interactor.primaryBouncerInteractor
import com.android.systemui.communal.domain.interactor.communalInteractor
+import com.android.systemui.communal.domain.interactor.communalSceneInteractor
+import com.android.systemui.communal.domain.interactor.communalSettingsInteractor
import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
@@ -39,5 +41,7 @@ val Kosmos.fromAlternateBouncerTransitionInteractor by
powerInteractor = powerInteractor,
keyguardOcclusionInteractor = keyguardOcclusionInteractor,
primaryBouncerInteractor = primaryBouncerInteractor,
+ communalSceneInteractor = communalSceneInteractor,
+ communalSettingsInteractor = communalSettingsInteractor,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorKosmos.kt
index 3c369d7d954f..6b240b5f36ac 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorKosmos.kt
@@ -18,6 +18,7 @@ package com.android.systemui.keyguard.domain.interactor
import com.android.keyguard.keyguardSecurityModel
import com.android.systemui.communal.domain.interactor.communalSceneInteractor
+import com.android.systemui.communal.domain.interactor.communalSettingsInteractor
import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
@@ -37,6 +38,7 @@ var Kosmos.fromPrimaryBouncerTransitionInteractor by
mainDispatcher = testDispatcher,
keyguardInteractor = keyguardInteractor,
communalSceneInteractor = communalSceneInteractor,
+ communalSettingsInteractor = communalSettingsInteractor,
keyguardSecurityModel = keyguardSecurityModel,
selectedUserInteractor = selectedUserInteractor,
powerInteractor = powerInteractor,
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 8844eb040f02..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,8 +20,9 @@ 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
import com.android.systemui.wallpapers.domain.interactor.wallpaperFocalAreaInteractor
@@ -30,7 +31,8 @@ val Kosmos.keyguardClockInteractor by
KeyguardClockInteractor(
mediaCarouselInteractor = mediaCarouselInteractor,
activeNotificationsInteractor = activeNotificationsInteractor,
- shadeInteractor = shadeInteractor,
+ aodPromotedNotificationInteractor = aodPromotedNotificationInteractor,
+ 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/keyguard/ui/viewmodel/KeyguardMediaViewModelFactoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardMediaViewModelFactoryKosmos.kt
index 16d3fdc26613..345d69aa8df0 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardMediaViewModelFactoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardMediaViewModelFactoryKosmos.kt
@@ -19,12 +19,17 @@ package com.android.systemui.keyguard.ui.viewmodel
import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.media.controls.domain.pipeline.interactor.mediaCarouselInteractor
+import com.android.systemui.shade.domain.interactor.shadeModeInteractor
val Kosmos.keyguardMediaViewModelFactory by
Kosmos.Fixture {
object : KeyguardMediaViewModel.Factory {
override fun create(): KeyguardMediaViewModel {
- return KeyguardMediaViewModel(mediaCarouselInteractor, keyguardInteractor)
+ return KeyguardMediaViewModel(
+ mediaCarouselInteractor = mediaCarouselInteractor,
+ keyguardInteractor = keyguardInteractor,
+ shadeModeInteractor = shadeModeInteractor,
+ )
}
}
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt
index ea3feeaf0854..78356318cbb4 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt
@@ -17,6 +17,7 @@ package com.android.systemui.keyguard.ui.viewmodel
import com.android.systemui.communal.domain.interactor.communalInteractor
import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
+import com.android.systemui.dump.dumpManager
import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
import com.android.systemui.keyguard.domain.interactor.pulseExpansionInteractor
@@ -26,6 +27,7 @@ import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.shade.ui.viewmodel.notificationShadeWindowModel
import com.android.systemui.statusbar.notification.icon.ui.viewmodel.notificationIconContainerAlwaysOnDisplayViewModel
+import com.android.systemui.statusbar.notification.promoted.domain.interactor.aodPromotedNotificationInteractor
import com.android.systemui.statusbar.notification.stack.domain.interactor.notificationsKeyguardInteractor
import com.android.systemui.statusbar.phone.dozeParameters
import com.android.systemui.statusbar.phone.screenOffAnimationController
@@ -41,6 +43,7 @@ val Kosmos.keyguardRootViewModel by Fixture {
keyguardTransitionInteractor = keyguardTransitionInteractor,
notificationsKeyguardInteractor = notificationsKeyguardInteractor,
pulseExpansionInteractor = pulseExpansionInteractor,
+ aodPromotedNotificationInteractor = aodPromotedNotificationInteractor,
aodNotificationIconViewModel = notificationIconContainerAlwaysOnDisplayViewModel,
notificationShadeWindowModel = notificationShadeWindowModel,
alternateBouncerToAodTransitionViewModel = alternateBouncerToAodTransitionViewModel,
@@ -90,5 +93,6 @@ val Kosmos.keyguardRootViewModel by Fixture {
aodBurnInViewModel = aodBurnInViewModel,
shadeInteractor = shadeInteractor,
wallpaperFocalAreaInteractor = wallpaperFocalAreaInteractor,
+ dumpManager = dumpManager,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelKosmos.kt
index dd13b8b143ae..b751e213152e 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelKosmos.kt
@@ -25,7 +25,7 @@ import com.android.systemui.keyguard.shared.transition.KeyguardTransitionAnimati
import com.android.systemui.keyguard.shared.transition.keyguardTransitionAnimationCallbackDelegator
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
-import com.android.systemui.shade.domain.interactor.shadeInteractor
+import com.android.systemui.shade.domain.interactor.shadeModeInteractor
import com.android.systemui.unfold.domain.interactor.unfoldTransitionInteractor
val Kosmos.lockscreenContentViewModelFactory by Fixture {
@@ -38,7 +38,7 @@ val Kosmos.lockscreenContentViewModelFactory by Fixture {
interactor = keyguardBlueprintInteractor,
authController = authController,
touchHandling = keyguardTouchHandlingViewModel,
- shadeInteractor = shadeInteractor,
+ shadeModeInteractor = shadeModeInteractor,
unfoldTransitionInteractor = unfoldTransitionInteractor,
deviceEntryInteractor = deviceEntryInteractor,
transitionInteractor = keyguardTransitionInteractor,
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/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/statusbar/notification/collection/EntryUtil.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/EntryUtil.kt
index 8b4de2bcc26f..05f1c0bac982 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/EntryUtil.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/EntryUtil.kt
@@ -33,3 +33,6 @@ inline fun NotificationEntry.modifyEntry(
fun getAttachState(entry: ListEntry): ListAttachState {
return entry.attachState
}
+
+fun buildEntry(block: NotificationEntryBuilder.() -> Unit) =
+ NotificationEntryBuilder().apply(block).build()
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractorKosmos.kt
index dc7595f7f2e4..87e0a0f0dda3 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractorKosmos.kt
@@ -24,6 +24,7 @@ import com.android.systemui.statusbar.data.repository.notificationListenerSettin
import com.android.systemui.statusbar.notification.data.repository.notificationsKeyguardViewStateRepository
import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor
import com.android.systemui.statusbar.notification.domain.interactor.headsUpNotificationIconInteractor
+import com.android.systemui.statusbar.notification.promoted.domain.interactor.aodPromotedNotificationInteractor
import com.android.wm.shell.bubbles.bubblesOptional
val Kosmos.alwaysOnDisplayNotificationIconsInteractor by Fixture {
@@ -47,6 +48,7 @@ val Kosmos.notificationIconsInteractor by Fixture {
activeNotificationsInteractor = activeNotificationsInteractor,
bubbles = bubblesOptional,
headsUpNotificationIconInteractor = headsUpNotificationIconInteractor,
+ aodPromotedNotificationInteractor = aodPromotedNotificationInteractor,
keyguardViewStateRepository = notificationsKeyguardViewStateRepository,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalBackActionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/promoted/domain/interactor/AODPromotedNotificationInteractorKosmos.kt
index 57c8fd066ea8..df1c82278bc2 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalBackActionInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/promoted/domain/interactor/AODPromotedNotificationInteractorKosmos.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2024 The Android Open Source Project
+ * Copyright (C) 2025 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,16 +14,16 @@
* limitations under the License.
*/
-package com.android.systemui.communal.domain.interactor
+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.scene.domain.interactor.sceneInteractor
+import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor
-val Kosmos.communalBackActionInteractor by
+val Kosmos.aodPromotedNotificationInteractor by
Kosmos.Fixture {
- CommunalBackActionInteractor(
- communalInteractor = communalInteractor,
- communalSceneInteractor = communalSceneInteractor,
- sceneInteractor = sceneInteractor,
+ AODPromotedNotificationInteractor(
+ activeNotificationsInteractor = activeNotificationsInteractor,
+ dumpManager = dumpManager,
)
}
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 7a2b7c24252b..047bd13f0c27 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,6 +29,7 @@ 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
@@ -81,6 +82,7 @@ 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/mobile/domain/interactor/FakeMobileIconsInteractor.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt
index 352f6cf011e1..9b6f205fba72 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt
@@ -26,6 +26,7 @@ import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy
import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
class FakeMobileIconsInteractor(
mobileMappings: MobileMappingsProxy,
@@ -73,6 +74,8 @@ class FakeMobileIconsInteractor(
override val icons: MutableStateFlow<List<MobileIconInteractor>> = MutableStateFlow(emptyList())
+ override val isStackable: StateFlow<Boolean> = MutableStateFlow(false)
+
private val _defaultMobileIconMapping = MutableStateFlow(TEST_MAPPING)
override val defaultMobileIconMapping = _defaultMobileIconMapping
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/ui/binder/VolumeDialogViewBinderKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/ui/binder/VolumeDialogViewBinderKosmos.kt
index dc09e3233b1e..386e0feb3b3a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/ui/binder/VolumeDialogViewBinderKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/ui/binder/VolumeDialogViewBinderKosmos.kt
@@ -16,7 +16,6 @@
package com.android.systemui.volume.dialog.ui.binder
-import android.content.applicationContext
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.volume.dialog.ringer.volumeDialogRingerViewBinder
import com.android.systemui.volume.dialog.settings.ui.binder.volumeDialogSettingsButtonViewBinder
@@ -28,12 +27,13 @@ import com.android.systemui.volume.dialog.utils.volumeTracer
val Kosmos.volumeDialogViewBinder by
Kosmos.Fixture {
VolumeDialogViewBinder(
- applicationContext.resources,
volumeDialogViewModel,
jankListenerFactory,
volumeTracer,
- volumeDialogRingerViewBinder,
- volumeDialogSlidersViewBinder,
- volumeDialogSettingsButtonViewBinder,
+ listOf(
+ volumeDialogSlidersViewBinder,
+ volumeDialogRingerViewBinder,
+ volumeDialogSettingsButtonViewBinder,
+ ),
)
}
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/window/data/repository/WindowRootViewBlurRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/window/data/repository/WindowRootViewBlurRepositoryKosmos.kt
index 96992233375d..b619e2d70724 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/window/data/repository/WindowRootViewBlurRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/window/data/repository/WindowRootViewBlurRepositoryKosmos.kt
@@ -29,4 +29,5 @@ class FakeWindowRootViewBlurRepository : WindowRootViewBlurRepository {
override val blurRadius: MutableStateFlow<Int> = MutableStateFlow(0)
override val isBlurOpaque: MutableStateFlow<Boolean> = MutableStateFlow(false)
override val isBlurSupported: MutableStateFlow<Boolean> = MutableStateFlow(false)
+ override var blurAppliedListener: BlurAppliedListener? = null
}
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/accessibility/java/com/android/server/accessibility/autoclick/AutoclickController.java b/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickController.java
index 5283df5ca7e1..4fa0d506f09e 100644
--- a/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickController.java
+++ b/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickController.java
@@ -538,6 +538,11 @@ public class AutoclickController extends BaseEventStreamTransformation {
return mActive;
}
+ @VisibleForTesting
+ long getScheduledClickTimeForTesting() {
+ return mScheduledClickTime;
+ }
+
/**
* Updates delay that should be used when scheduling clicks. The delay will be used only for
* clicks scheduled after this point (pending click tasks are not affected).
diff --git a/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickTypePanel.java b/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickTypePanel.java
index cf928e2f3fa4..614b2285d6e0 100644
--- a/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickTypePanel.java
+++ b/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickTypePanel.java
@@ -168,6 +168,10 @@ public class AutoclickTypePanel {
}
public void hide() {
+ // Sets the button background to unselected styling, this is necessary to make sure the
+ // button background styling is correct when the panel shows up next time.
+ toggleSelectedButtonStyle(mSelectedButton, /* isSelected= */ false);
+
mWindowManager.removeView(mContentView);
}
diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
index d47aab061788..e0f2939a2083 100644
--- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
+++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
@@ -16,7 +16,6 @@
package com.android.server.appwidget;
-import static android.appwidget.flags.Flags.checkRemoteViewsUriPermission;
import static android.appwidget.flags.Flags.remoteAdapterConversion;
import static android.appwidget.flags.Flags.remoteViewsProto;
import static android.appwidget.flags.Flags.removeAppWidgetServiceIoFromCriticalPath;
@@ -2560,9 +2559,7 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
// Make sure the package runs under the caller uid.
mSecurityPolicy.enforceCallFromPackage(callingPackage);
// Make sure RemoteViews do not contain URIs that the caller cannot access.
- if (checkRemoteViewsUriPermission()) {
- checkRemoteViewsUris(views);
- }
+ checkRemoteViewsUris(views);
synchronized (mLock) {
ensureGroupStateLoadedLocked(userId);
diff --git a/services/contextualsearch/java/com/android/server/contextualsearch/ContextualSearchManagerService.java b/services/contextualsearch/java/com/android/server/contextualsearch/ContextualSearchManagerService.java
index 89c9d690a82c..700a1624f7d4 100644
--- a/services/contextualsearch/java/com/android/server/contextualsearch/ContextualSearchManagerService.java
+++ b/services/contextualsearch/java/com/android/server/contextualsearch/ContextualSearchManagerService.java
@@ -34,6 +34,8 @@ import static com.android.server.wm.ActivityTaskManagerInternal.ASSIST_KEY_STRUC
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
+import android.app.ActivityManager;
+import android.app.ActivityManagerInternal;
import android.app.ActivityOptions;
import android.app.AppOpsManager;
import android.app.admin.DevicePolicyManagerInternal;
@@ -69,7 +71,6 @@ import android.provider.Settings;
import android.util.Log;
import android.util.Slog;
import android.view.IWindowManager;
-import android.window.ScreenCapture;
import android.window.ScreenCapture.ScreenshotHardwareBuffer;
import com.android.internal.R;
@@ -86,7 +87,6 @@ import java.io.FileDescriptor;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
-import java.util.Set;
public class ContextualSearchManagerService extends SystemService {
private static final String TAG = ContextualSearchManagerService.class.getSimpleName();
@@ -95,9 +95,20 @@ public class ContextualSearchManagerService extends SystemService {
private static final int MSG_INVALIDATE_TOKEN = 1;
private static final int MAX_TOKEN_VALID_DURATION_MS = 1_000 * 60 * 10; // 10 minutes
+ /**
+ * Below are internal entrypoints not supported by the
+ * {@link ContextualSearchManager#startContextualSearch(int entrypoint)} method.
+ *
+ * <p>These values should be negative to avoid conflicting with the system entrypoints.
+ */
+
+ /** Entrypoint to be used when a foreground app invokes Contextual Search. */
+ private static final int INTERNAL_ENTRYPOINT_APP = -1;
+
private static final boolean DEBUG = false;
private final Context mContext;
+ private final ActivityManagerInternal mActivityManagerInternal;
private final ActivityTaskManagerInternal mAtmInternal;
private final PackageManagerInternal mPackageManager;
private final WindowManagerInternal mWmInternal;
@@ -162,6 +173,8 @@ public class ContextualSearchManagerService extends SystemService {
super(context);
if (DEBUG) Log.d(TAG, "ContextualSearchManagerService created");
mContext = context;
+ mActivityManagerInternal = Objects.requireNonNull(
+ LocalServices.getService(ActivityManagerInternal.class));
mAtmInternal = Objects.requireNonNull(
LocalServices.getService(ActivityTaskManagerInternal.class));
mPackageManager = LocalServices.getService(PackageManagerInternal.class);
@@ -391,6 +404,20 @@ public class ContextualSearchManagerService extends SystemService {
}
}
+ private void enforceForegroundApp(@NonNull final String func) {
+ final int callingUid = Binder.getCallingUid();
+ final String callingPackage = mPackageManager.getNameForUid(Binder.getCallingUid());
+ if (mActivityManagerInternal.getUidProcessState(callingUid)
+ > ActivityManager.PROCESS_STATE_TOP) {
+ // The calling process must be displaying an activity in foreground to
+ // trigger contextual search.
+ String msg = "Permission Denial: Cannot call " + func + " from pid="
+ + Binder.getCallingPid() + ", uid=" + callingUid
+ + ", package=" + callingPackage + " without a foreground activity.";
+ throw new SecurityException(msg);
+ }
+ }
+
private void enforceOverridingPermission(@NonNull final String func) {
if (!(Binder.getCallingUid() == Process.SHELL_UID
|| Binder.getCallingUid() == Process.ROOT_UID
@@ -448,29 +475,43 @@ public class ContextualSearchManagerService extends SystemService {
}
@Override
+ public void startContextualSearchForForegroundApp() {
+ synchronized (this) {
+ if (DEBUG) {
+ Log.d(TAG, "Starting contextual search from: "
+ + mPackageManager.getNameForUid(Binder.getCallingUid()));
+ }
+ enforceForegroundApp("startContextualSearchForForegroundApp");
+ startContextualSearchInternal(INTERNAL_ENTRYPOINT_APP);
+ }
+ }
+
+ @Override
public void startContextualSearch(int entrypoint) {
synchronized (this) {
if (DEBUG) Log.d(TAG, "startContextualSearch entrypoint: " + entrypoint);
enforcePermission("startContextualSearch");
- final int callingUserId = Binder.getCallingUserHandle().getIdentifier();
-
- mAssistDataRequester.cancel();
- // Creates a new CallbackToken at mToken and an expiration handler.
- issueToken();
- // We get the launch intent with the system server's identity because the system
- // server has READ_FRAME_BUFFER permission to get the screenshot and because only
- // the system server can invoke non-exported activities.
- Binder.withCleanCallingIdentity(() -> {
- Intent launchIntent =
- getContextualSearchIntent(entrypoint, callingUserId, mToken);
- if (launchIntent != null) {
- int result = invokeContextualSearchIntent(launchIntent, callingUserId);
- if (DEBUG) Log.d(TAG, "Launch result: " + result);
- }
- });
+ startContextualSearchInternal(entrypoint);
}
}
+ private void startContextualSearchInternal(int entrypoint) {
+ final int callingUserId = Binder.getCallingUserHandle().getIdentifier();
+ mAssistDataRequester.cancel();
+ // Creates a new CallbackToken at mToken and an expiration handler.
+ issueToken();
+ // We get the launch intent with the system server's identity because the system
+ // server has READ_FRAME_BUFFER permission to get the screenshot and because only
+ // the system server can invoke non-exported activities.
+ Binder.withCleanCallingIdentity(() -> {
+ Intent launchIntent = getContextualSearchIntent(entrypoint, callingUserId, mToken);
+ if (launchIntent != null) {
+ int result = invokeContextualSearchIntent(launchIntent, callingUserId);
+ if (DEBUG) Log.d(TAG, "Launch result: " + result);
+ }
+ });
+ }
+
@Override
public void getContextualSearchState(
@NonNull IBinder token,
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index f34016905502..6e3d7bd19b41 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -8315,14 +8315,6 @@ public class ActivityManagerService extends IActivityManager.Stub
setThreadScheduler(proc.getRenderThreadTid(),
SCHED_FIFO | SCHED_RESET_ON_FORK, 1);
} else {
- if (Flags.resetOnForkEnabled()) {
- if (Process.getThreadScheduler(proc.getRenderThreadTid())
- == Process.SCHED_OTHER) {
- Process.setThreadScheduler(proc.getRenderThreadTid(),
- Process.SCHED_OTHER | Process.SCHED_RESET_ON_FORK,
- 0);
- }
- }
setThreadPriority(proc.getRenderThreadTid(),
THREAD_PRIORITY_TOP_APP_BOOST);
}
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index cd40905b01da..61c5501a7b5a 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -471,13 +471,6 @@ public class OomAdjuster {
}
void setThreadPriority(int tid, int priority) {
- if (Flags.resetOnForkEnabled()) {
- if (Process.getThreadScheduler(tid) == Process.SCHED_OTHER) {
- Process.setThreadScheduler(tid,
- Process.SCHED_OTHER | Process.SCHED_RESET_ON_FORK,
- 0);
- }
- }
Process.setThreadPriority(tid, priority);
}
}
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index 27e9e44f1090..e0fbaf43ea43 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -31,6 +31,7 @@ import static android.app.ActivityManagerInternal.ALLOW_FULL_ONLY;
import static android.app.ActivityManagerInternal.ALLOW_NON_FULL;
import static android.app.ActivityManagerInternal.ALLOW_NON_FULL_IN_PROFILE;
import static android.app.ActivityManagerInternal.ALLOW_PROFILES_OR_NON_FULL;
+import static android.app.KeyguardManager.LOCK_ON_USER_SWITCH_CALLBACK;
import static android.os.PowerWhitelistManager.REASON_BOOT_COMPLETED;
import static android.os.PowerWhitelistManager.REASON_LOCKED_BOOT_COMPLETED;
import static android.os.PowerWhitelistManager.TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED;
@@ -3904,10 +3905,6 @@ class UserController implements Handler.Callback {
return mService.mWindowManager;
}
- ActivityTaskManagerInternal getActivityTaskManagerInternal() {
- return mService.mAtmInternal;
- }
-
void activityManagerOnUserStopped(@UserIdInt int userId) {
LocalServices.getService(ActivityTaskManagerInternal.class).onUserStopped(userId);
}
@@ -4122,40 +4119,25 @@ class UserController implements Handler.Callback {
}
void lockDeviceNowAndWaitForKeyguardShown() {
- if (getWindowManager().isKeyguardLocked()) {
- Slogf.w(TAG, "Not locking the device since the keyguard is already locked");
- return;
- }
-
final TimingsTraceAndSlog t = new TimingsTraceAndSlog();
t.traceBegin("lockDeviceNowAndWaitForKeyguardShown");
final CountDownLatch latch = new CountDownLatch(1);
- ActivityTaskManagerInternal.ScreenObserver screenObserver =
- new ActivityTaskManagerInternal.ScreenObserver() {
- @Override
- public void onAwakeStateChanged(boolean isAwake) {
-
- }
-
- @Override
- public void onKeyguardStateChanged(boolean isShowing) {
- if (isShowing) {
- latch.countDown();
- }
- }
- };
-
- getActivityTaskManagerInternal().registerScreenObserver(screenObserver);
- getWindowManager().lockDeviceNow();
+ Bundle bundle = new Bundle();
+ bundle.putBinder(LOCK_ON_USER_SWITCH_CALLBACK, new IRemoteCallback.Stub() {
+ public void sendResult(Bundle data) {
+ latch.countDown();
+ }
+ });
+ getWindowManager().lockNow(bundle);
try {
if (!latch.await(20, TimeUnit.SECONDS)) {
- throw new RuntimeException("Keyguard is not shown in 20 seconds");
+ throw new RuntimeException("User controller expected a callback while waiting "
+ + "to show the keyguard. Timed out after 20 seconds.");
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
- getActivityTaskManagerInternal().unregisterScreenObserver(screenObserver);
t.traceEnd();
}
}
diff --git a/services/core/java/com/android/server/am/flags.aconfig b/services/core/java/com/android/server/am/flags.aconfig
index e89889e794ba..27c384a22fb6 100644
--- a/services/core/java/com/android/server/am/flags.aconfig
+++ b/services/core/java/com/android/server/am/flags.aconfig
@@ -205,13 +205,6 @@ flag {
}
flag {
- name: "reset_on_fork_enabled"
- namespace: "system_performance"
- description: "Set reset_on_fork flag."
- bug: "370988407"
-}
-
-flag {
name: "push_global_state_to_oomadjuster"
namespace: "backstage_power"
description: "Migrate OomAdjuster pulled device state to a push model"
@@ -319,3 +312,14 @@ flag {
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ name: "lower_sms_oom_importance"
+ namespace: "backstage_power"
+ description: "Lower messaging app process oom importance to PERCEPTIBLE_APP_ADJ + 1."
+ bug: "372511805"
+ is_fixed_read_only: true
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/services/core/java/com/android/server/appbinding/AppBindingConstants.java b/services/core/java/com/android/server/appbinding/AppBindingConstants.java
index 71847694b1d8..561cc0ef0a3b 100644
--- a/services/core/java/com/android/server/appbinding/AppBindingConstants.java
+++ b/services/core/java/com/android/server/appbinding/AppBindingConstants.java
@@ -102,9 +102,16 @@ public class AppBindingConstants {
boolean smsServiceEnabled = parser.getBoolean(SMS_SERVICE_ENABLED_KEY, true);
- int smsAppBindFlags = parser.getInt(
- SMS_APP_BIND_FLAGS_KEY,
- Context.BIND_NOT_VISIBLE | Context.BIND_FOREGROUND_SERVICE);
+ int smsAppBindFlags;
+ if (com.android.server.am.Flags.lowerSmsOomImportance()) {
+ smsAppBindFlags = parser.getInt(
+ SMS_APP_BIND_FLAGS_KEY,
+ Context.BIND_ALMOST_PERCEPTIBLE | Context.BIND_FOREGROUND_SERVICE);
+ } else {
+ smsAppBindFlags = parser.getInt(
+ SMS_APP_BIND_FLAGS_KEY,
+ Context.BIND_NOT_VISIBLE | Context.BIND_FOREGROUND_SERVICE);
+ }
long serviceStableConnectionThresholdSec = parser.getLong(
SERVICE_STABLE_CONNECTION_THRESHOLD_SEC_KEY, TimeUnit.MINUTES.toSeconds(2));
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index 4ec813827cbc..6d6e1fb6bfb3 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -1030,38 +1030,10 @@ public class AudioDeviceBroker {
}
}
- BtDeviceInfo createBtDeviceInfo(@NonNull BtDeviceChangedData d, @NonNull BluetoothDevice device,
- int state) {
- int audioDevice;
- int codec = AudioSystem.AUDIO_FORMAT_DEFAULT;
- switch (d.mInfo.getProfile()) {
- case BluetoothProfile.A2DP_SINK:
- audioDevice = AudioSystem.DEVICE_IN_BLUETOOTH_A2DP;
- break;
- case BluetoothProfile.A2DP:
- audioDevice = AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP;
- break;
- case BluetoothProfile.HEARING_AID:
- audioDevice = AudioSystem.DEVICE_OUT_HEARING_AID;
- break;
- case BluetoothProfile.LE_AUDIO:
- if (d.mInfo.isLeOutput()) {
- audioDevice = AudioSystem.DEVICE_OUT_BLE_HEADSET;
- } else {
- audioDevice = AudioSystem.DEVICE_IN_BLE_HEADSET;
- }
- break;
- case BluetoothProfile.LE_AUDIO_BROADCAST:
- audioDevice = AudioSystem.DEVICE_OUT_BLE_BROADCAST;
- break;
- case BluetoothProfile.HEADSET:
- // the actual device type is not important at this point and
- // will be set by BtHelper.handleBtScoActiveDeviceChange()
- audioDevice = AudioSystem.DEVICE_OUT_BLUETOOTH_SCO;
- break;
- default: throw new IllegalArgumentException("Invalid profile " + d.mInfo.getProfile());
- }
- return new BtDeviceInfo(d, device, state, audioDevice, codec);
+ /*package*/ static BtDeviceInfo createBtDeviceInfo(@NonNull BtDeviceChangedData d,
+ @NonNull BluetoothDevice device, int state) {
+ int audioDevice = BtHelper.getTypeFromProfile(d.mInfo.getProfile(), d.mInfo.isLeOutput());
+ return new BtDeviceInfo(d, device, state, audioDevice, AudioSystem.AUDIO_FORMAT_DEFAULT);
}
private void btMediaMetricRecord(@NonNull BluetoothDevice device, String state,
@@ -1728,8 +1700,8 @@ public class AudioDeviceBroker {
}
// must be called synchronized on mConnectedDevices
- /*package*/ boolean hasScheduledA2dpConnection(BluetoothDevice btDevice) {
- final BtDeviceInfo devInfoToCheck = new BtDeviceInfo(btDevice, BluetoothProfile.A2DP);
+ /*package*/ boolean hasScheduledA2dpConnection(BluetoothDevice btDevice, int profile) {
+ final BtDeviceInfo devInfoToCheck = new BtDeviceInfo(btDevice, profile);
return mBrokerHandler.hasEqualMessages(MSG_L_SET_BT_ACTIVE_DEVICE, devInfoToCheck);
}
diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
index 37ef9a0648a6..ef10793fd955 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
@@ -987,8 +987,10 @@ public class AudioDeviceInventory {
"onBluetoothDeviceConfigChange addr=" + address
+ " event=" + BtHelper.deviceEventToString(event)));
+ int deviceType = BtHelper.getTypeFromProfile(btInfo.mProfile, btInfo.mIsLeOutput);
+
synchronized (mDevicesLock) {
- if (mDeviceBroker.hasScheduledA2dpConnection(btDevice)) {
+ if (mDeviceBroker.hasScheduledA2dpConnection(btDevice, btInfo.mProfile)) {
AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
"A2dp config change ignored (scheduled connection change)")
.printSlog(EventLogger.Event.ALOGI, TAG));
@@ -996,8 +998,7 @@ public class AudioDeviceInventory {
.record();
return delayMs;
}
- final String key = DeviceInfo.makeDeviceListKey(
- AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address);
+ final String key = DeviceInfo.makeDeviceListKey(deviceType, address);
final DeviceInfo di = mConnectedDevices.get(key);
if (di == null) {
Log.e(TAG, "invalid null DeviceInfo in onBluetoothDeviceConfigChange");
@@ -1022,7 +1023,7 @@ public class AudioDeviceInventory {
BtHelper.getName(btDevice), codec);
if (res != AudioSystem.AUDIO_STATUS_OK) {
AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
- "APM handleDeviceConfigChange failed for A2DP device addr="
+ "APM handleDeviceConfigChange failed for device addr="
+ address + " codec="
+ AudioSystem.audioFormatToString(codec))
.printSlog(EventLogger.Event.ALOGE, TAG));
@@ -1033,7 +1034,7 @@ public class AudioDeviceInventory {
BluetoothProfile.STATE_DISCONNECTED));
} else {
AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
- "APM handleDeviceConfigChange success for A2DP device addr="
+ "APM handleDeviceConfigChange success for device addr="
+ address
+ " codec=" + AudioSystem.audioFormatToString(codec))
.printSlog(EventLogger.Event.ALOGI, TAG));
diff --git a/services/core/java/com/android/server/audio/BtHelper.java b/services/core/java/com/android/server/audio/BtHelper.java
index 0479c70656b7..922116999bc7 100644
--- a/services/core/java/com/android/server/audio/BtHelper.java
+++ b/services/core/java/com/android/server/audio/BtHelper.java
@@ -1291,6 +1291,29 @@ public class BtHelper {
return 0; // 0 is not a valid profile
}
+ /*package */ static int getTypeFromProfile(int profile, boolean isLeOutput) {
+ switch (profile) {
+ case BluetoothProfile.A2DP_SINK:
+ return AudioSystem.DEVICE_IN_BLUETOOTH_A2DP;
+ case BluetoothProfile.A2DP:
+ return AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP;
+ case BluetoothProfile.HEARING_AID:
+ return AudioSystem.DEVICE_OUT_HEARING_AID;
+ case BluetoothProfile.LE_AUDIO:
+ if (isLeOutput) {
+ return AudioSystem.DEVICE_OUT_BLE_HEADSET;
+ } else {
+ return AudioSystem.DEVICE_IN_BLE_HEADSET;
+ }
+ case BluetoothProfile.LE_AUDIO_BROADCAST:
+ return AudioSystem.DEVICE_OUT_BLE_BROADCAST;
+ case BluetoothProfile.HEADSET:
+ return AudioSystem.DEVICE_OUT_BLUETOOTH_SCO;
+ default:
+ throw new IllegalArgumentException("Invalid profile " + profile);
+ }
+ }
+
/*package */ static Bundle getPreferredAudioProfiles(String address) {
BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
return adapter.getPreferredAudioProfiles(adapter.getRemoteDevice(address));
diff --git a/services/core/java/com/android/server/backup/SystemBackupAgent.java b/services/core/java/com/android/server/backup/SystemBackupAgent.java
index b11267ef8634..79523bd02404 100644
--- a/services/core/java/com/android/server/backup/SystemBackupAgent.java
+++ b/services/core/java/com/android/server/backup/SystemBackupAgent.java
@@ -69,6 +69,7 @@ public class SystemBackupAgent extends BackupAgentHelper {
private static final String SYSTEM_GENDER_HELPER = "system_gender";
private static final String DISPLAY_HELPER = "display";
private static final String INPUT_HELPER = "input";
+ private static final String WEAR_BACKUP_HELPER = "wear";
// These paths must match what the WallpaperManagerService uses. The leaf *_FILENAME
// are also used in the full-backup file format, so must not change unless steps are
@@ -113,7 +114,7 @@ public class SystemBackupAgent extends BackupAgentHelper {
private static final Set<String> sEligibleHelpersForNonSystemUser =
SetUtils.union(sEligibleHelpersForProfileUser,
Sets.newArraySet(ACCOUNT_MANAGER_HELPER, USAGE_STATS_HELPER, PREFERRED_HELPER,
- SHORTCUT_MANAGER_HELPER, INPUT_HELPER));
+ SHORTCUT_MANAGER_HELPER, INPUT_HELPER, WEAR_BACKUP_HELPER));
private int mUserId = UserHandle.USER_SYSTEM;
private boolean mIsProfileUser = false;
@@ -153,6 +154,11 @@ public class SystemBackupAgent extends BackupAgentHelper {
if (com.android.hardware.input.Flags.enableBackupAndRestoreForInputGestures()) {
addHelperIfEligibleForUser(INPUT_HELPER, new InputBackupHelper(mUserId));
}
+
+ // Add Wear helper only if the device is a watch
+ if (getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH)) {
+ addHelperIfEligibleForUser(WEAR_BACKUP_HELPER, new WearBackupHelper());
+ }
}
@Override
diff --git a/services/core/java/com/android/server/backup/WearBackupHelper.java b/services/core/java/com/android/server/backup/WearBackupHelper.java
new file mode 100644
index 000000000000..27416b3eb2a6
--- /dev/null
+++ b/services/core/java/com/android/server/backup/WearBackupHelper.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.backup;
+
+import android.annotation.Nullable;
+import android.app.backup.BlobBackupHelper;
+
+import com.android.server.LocalServices;
+
+/** A {@link android.app.backup.BlobBackupHelper} for Wear */
+public class WearBackupHelper extends BlobBackupHelper {
+
+ private static final int BLOB_VERSION = 1;
+ private static final String KEY_WEAR_BACKUP = "wear";
+ @Nullable private final WearBackupInternal mWearBackupInternal;
+
+ public WearBackupHelper() {
+ super(BLOB_VERSION, KEY_WEAR_BACKUP);
+ mWearBackupInternal = LocalServices.getService(WearBackupInternal.class);
+ }
+
+ @Override
+ protected byte[] getBackupPayload(String key) {
+ return KEY_WEAR_BACKUP.equals(key) && mWearBackupInternal != null
+ ? mWearBackupInternal.getBackupPayload(getLogger())
+ : null;
+ }
+
+ @Override
+ protected void applyRestoredPayload(String key, byte[] payload) {
+ if (KEY_WEAR_BACKUP.equals(key) && mWearBackupInternal != null) {
+ mWearBackupInternal.applyRestoredPayload(payload);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/backup/WearBackupInternal.java b/services/core/java/com/android/server/backup/WearBackupInternal.java
new file mode 100644
index 000000000000..7b4847b51df6
--- /dev/null
+++ b/services/core/java/com/android/server/backup/WearBackupInternal.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.backup;
+
+import android.app.backup.BackupRestoreEventLogger;
+
+import com.android.internal.annotations.Keep;
+
+/** A local service internal for Wear OS handle backup/restore */
+@Keep
+public interface WearBackupInternal {
+
+ /** Gets the backup payload */
+ byte[] getBackupPayload(BackupRestoreEventLogger logger);
+
+ /** Applies the restored payload */
+ void applyRestoredPayload(byte[] payload);
+}
diff --git a/services/core/java/com/android/server/display/DisplayDevice.java b/services/core/java/com/android/server/display/DisplayDevice.java
index 2bdb5c25d0d5..a749a4013cdc 100644
--- a/services/core/java/com/android/server/display/DisplayDevice.java
+++ b/services/core/java/com/android/server/display/DisplayDevice.java
@@ -68,6 +68,8 @@ abstract class DisplayDevice {
private int mCurrentLayerStack = -1;
private int mCurrentFlags = 0;
private int mCurrentOrientation = -1;
+ private int mLastDisplayWidth;
+ private int mLastDisplayHeight;
private Rect mCurrentLayerStackRect;
private Rect mCurrentDisplayRect;
private final Context mContext;
@@ -216,9 +218,9 @@ abstract class DisplayDevice {
}
/**
- * Gives the display device a chance to update its properties while in a transaction.
+ * Updates the surface for the display.
*/
- public void performTraversalLocked(SurfaceControl.Transaction t) {
+ public void configureSurfaceLocked(SurfaceControl.Transaction t) {
}
/**
@@ -374,6 +376,29 @@ abstract class DisplayDevice {
}
/**
+ * Configure transaction with the display size.
+ */
+ public void configureDisplaySizeLocked(SurfaceControl.Transaction t) {
+ DisplayDeviceInfo info = getDisplayDeviceInfoLocked();
+ boolean isInstalledRotated = info.installOrientation == ROTATION_90
+ || info.installOrientation == ROTATION_270;
+ int displayWidth = isInstalledRotated ? info.height : info.width;
+ int displayHeight = isInstalledRotated ? info.width : info.height;
+ setDisplaySizeLocked(t, displayWidth, displayHeight);
+ }
+
+ /**
+ * Sets display size while in a transaction.
+ */
+ public final void setDisplaySizeLocked(SurfaceControl.Transaction t, int width, int height) {
+ if (width != mLastDisplayWidth && height != mLastDisplayHeight) {
+ mLastDisplayWidth = width;
+ mLastDisplayHeight = height;
+ t.setDisplaySize(mDisplayToken, width, height);
+ }
+ }
+
+ /**
* Sets the display surface while in a transaction.
*/
public final void setSurfaceLocked(SurfaceControl.Transaction t, Surface surface) {
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 854b0dd7676b..b6a3f4041b13 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -3122,7 +3122,6 @@ public final class DisplayManagerService extends SystemService {
displayTransactions.get(display.getDisplayIdLocked(), t);
if (device != null) {
configureDisplayLocked(displayTransaction, device);
- device.performTraversalLocked(displayTransaction);
}
});
diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
index 83ca563e0534..324f95a5974b 100644
--- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
@@ -35,6 +35,7 @@ import android.os.Looper;
import android.os.PowerManager;
import android.os.SystemProperties;
import android.os.Trace;
+import android.util.DisplayMetrics;
import android.util.DisplayUtils;
import android.util.LongSparseArray;
import android.util.Slog;
@@ -84,6 +85,10 @@ final class LocalDisplayAdapter extends DisplayAdapter {
private static final String PROPERTY_EMULATOR_CIRCULAR = "ro.boot.emulator.circular";
private static final double DEFAULT_DISPLAY_SIZE = 24.0;
+ // Touch target size 10.4mm in inches (divided by mm per inch 25.4)
+ private static final double EXTERNAL_DISPLAY_BASE_TOUCH_TARGET_SIZE_IN_INCHES = 10.4 / 25.4;
+
+ private static final double BASE_TOUCH_TARGET_SIZE_DP = 48.0;
private final LongSparseArray<LocalDisplayDevice> mDevices = new LongSparseArray<>();
@@ -530,17 +535,20 @@ final class LocalDisplayAdapter extends DisplayAdapter {
if (densityMapping == null) {
if (getFeatureFlags().isBaseDensityForExternalDisplaysEnabled()
&& !mStaticDisplayInfo.isInternal) {
- double dpi;
+ double ppi;
if (mActiveSfDisplayMode.xDpi > 0 && mActiveSfDisplayMode.yDpi > 0) {
- dpi = Math.sqrt((Math.pow(mActiveSfDisplayMode.xDpi, 2)
+ ppi = Math.sqrt((Math.pow(mActiveSfDisplayMode.xDpi, 2)
+ Math.pow(mActiveSfDisplayMode.yDpi, 2)) / 2);
} else {
// xDPI and yDPI is missing, calculate DPI from display resolution and
// default display size
- dpi = Math.sqrt(Math.pow(mInfo.width, 2) + Math.pow(mInfo.height, 2))
+ ppi = Math.sqrt(Math.pow(mInfo.width, 2) + Math.pow(mInfo.height, 2))
/ DEFAULT_DISPLAY_SIZE;
}
+ double pixels = ppi * EXTERNAL_DISPLAY_BASE_TOUCH_TARGET_SIZE_IN_INCHES;
+ double dpi =
+ pixels * DisplayMetrics.DENSITY_DEFAULT / BASE_TOUCH_TARGET_SIZE_DP;
return (int) (dpi + 0.5);
}
return (int) (mStaticDisplayInfo.density * 160 + 0.5);
diff --git a/services/core/java/com/android/server/display/LogicalDisplay.java b/services/core/java/com/android/server/display/LogicalDisplay.java
index f9d413732e3e..b2b9ef17ec8d 100644
--- a/services/core/java/com/android/server/display/LogicalDisplay.java
+++ b/services/core/java/com/android/server/display/LogicalDisplay.java
@@ -228,14 +228,17 @@ final class LogicalDisplay {
*/
private final boolean mIsAnisotropyCorrectionEnabled;
+ private final boolean mSyncedResolutionSwitchEnabled;
+
private boolean mCanHostTasks;
LogicalDisplay(int displayId, int layerStack, DisplayDevice primaryDisplayDevice) {
- this(displayId, layerStack, primaryDisplayDevice, false, false);
+ this(displayId, layerStack, primaryDisplayDevice, false, false, false);
}
LogicalDisplay(int displayId, int layerStack, DisplayDevice primaryDisplayDevice,
- boolean isAnisotropyCorrectionEnabled, boolean isAlwaysRotateDisplayDeviceEnabled) {
+ boolean isAnisotropyCorrectionEnabled, boolean isAlwaysRotateDisplayDeviceEnabled,
+ boolean isSyncedResolutionSwitchEnabled) {
mDisplayId = displayId;
mLayerStack = layerStack;
mPrimaryDisplayDevice = primaryDisplayDevice;
@@ -248,6 +251,7 @@ final class LogicalDisplay {
mBaseDisplayInfo.thermalBrightnessThrottlingDataId = mThermalBrightnessThrottlingDataId;
mIsAnisotropyCorrectionEnabled = isAnisotropyCorrectionEnabled;
mAlwaysRotateDisplayDeviceEnabled = isAlwaysRotateDisplayDeviceEnabled;
+ mSyncedResolutionSwitchEnabled = isSyncedResolutionSwitchEnabled;
mCanHostTasks = (mDisplayId == Display.DEFAULT_DISPLAY);
}
@@ -791,7 +795,12 @@ final class LogicalDisplay {
}
mDisplayPosition.set(mTempDisplayRect.left, mTempDisplayRect.top);
+
+ if (mSyncedResolutionSwitchEnabled || displayDeviceInfo.type == Display.TYPE_VIRTUAL) {
+ device.configureDisplaySizeLocked(t);
+ }
device.setProjectionLocked(t, orientation, mTempLayerStackRect, mTempDisplayRect);
+ device.configureSurfaceLocked(t);
}
/**
diff --git a/services/core/java/com/android/server/display/LogicalDisplayMapper.java b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
index ecc8896b69c6..02db051dff57 100644
--- a/services/core/java/com/android/server/display/LogicalDisplayMapper.java
+++ b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
@@ -1248,7 +1248,8 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
final int layerStack = assignLayerStackLocked(displayId);
final LogicalDisplay display = new LogicalDisplay(displayId, layerStack, device,
mFlags.isPixelAnisotropyCorrectionInLogicalDisplayEnabled(),
- mFlags.isAlwaysRotateDisplayDeviceEnabled());
+ mFlags.isAlwaysRotateDisplayDeviceEnabled(),
+ mFlags.isSyncedResolutionSwitchEnabled());
display.updateLocked(mDisplayDeviceRepo, mSyntheticModeManager);
final DisplayInfo info = display.getDisplayInfoLocked();
diff --git a/services/core/java/com/android/server/display/OverlayDisplayAdapter.java b/services/core/java/com/android/server/display/OverlayDisplayAdapter.java
index 382c88327523..b5a9b19bc5c5 100644
--- a/services/core/java/com/android/server/display/OverlayDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/OverlayDisplayAdapter.java
@@ -341,7 +341,7 @@ final class OverlayDisplayAdapter extends DisplayAdapter {
}
@Override
- public void performTraversalLocked(SurfaceControl.Transaction t) {
+ public void configureSurfaceLocked(SurfaceControl.Transaction t) {
if (mSurfaceTexture != null) {
if (mSurface == null) {
mSurface = new Surface(mSurfaceTexture);
diff --git a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
index abbdeb9da364..4779b690adfb 100644
--- a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
@@ -484,14 +484,19 @@ public class VirtualDisplayAdapter extends DisplayAdapter {
}
@Override
- public void performTraversalLocked(SurfaceControl.Transaction t) {
- if ((mPendingChanges & PENDING_RESIZE) != 0) {
- t.setDisplaySize(getDisplayTokenLocked(), mWidth, mHeight);
- }
+ public void configureSurfaceLocked(SurfaceControl.Transaction t) {
if ((mPendingChanges & PENDING_SURFACE_CHANGE) != 0) {
setSurfaceLocked(t, mSurface);
+ mPendingChanges &= ~PENDING_SURFACE_CHANGE;
+ }
+ }
+
+ @Override
+ public void configureDisplaySizeLocked(SurfaceControl.Transaction t) {
+ if ((mPendingChanges & PENDING_RESIZE) != 0) {
+ setDisplaySizeLocked(t, mWidth, mHeight);
+ mPendingChanges &= ~PENDING_RESIZE;
}
- mPendingChanges = 0;
}
@Override
diff --git a/services/core/java/com/android/server/display/WifiDisplayAdapter.java b/services/core/java/com/android/server/display/WifiDisplayAdapter.java
index 607c5d6a88bc..902eefa824b5 100644
--- a/services/core/java/com/android/server/display/WifiDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/WifiDisplayAdapter.java
@@ -640,7 +640,7 @@ final class WifiDisplayAdapter extends DisplayAdapter {
}
@Override
- public void performTraversalLocked(SurfaceControl.Transaction t) {
+ public void configureSurfaceLocked(SurfaceControl.Transaction t) {
if (mSurface != null) {
setSurfaceLocked(t, mSurface);
}
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 aab2760dbc66..bc5d90599b41 100644
--- a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
+++ b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
@@ -86,6 +86,11 @@ public class DisplayManagerFlags {
com.android.graphics.surfaceflinger.flags.Flags.FLAG_DISPLAY_CONFIG_ERROR_HAL,
com.android.graphics.surfaceflinger.flags.Flags::displayConfigErrorHal);
+ private final FlagState mSyncedResolutionSwitch = new FlagState(
+ com.android.graphics.surfaceflinger.flags.Flags.FLAG_SYNCED_RESOLUTION_SWITCH,
+ com.android.graphics.surfaceflinger.flags.Flags::syncedResolutionSwitch
+ );
+
private final FlagState mBrightnessIntRangeUserPerceptionFlagState = new FlagState(
Flags.FLAG_BRIGHTNESS_INT_RANGE_USER_PERCEPTION,
Flags::brightnessIntRangeUserPerception);
@@ -359,6 +364,10 @@ public class DisplayManagerFlags {
return mDisplayConfigErrorHalFlagState.isEnabled();
}
+ public boolean isSyncedResolutionSwitchEnabled() {
+ return mSyncedResolutionSwitch.isEnabled();
+ }
+
public boolean isBrightnessIntRangeUserPerceptionEnabled() {
return mBrightnessIntRangeUserPerceptionFlagState.isEnabled();
}
@@ -611,6 +620,7 @@ public class DisplayManagerFlags {
pw.println(" " + mEvenDimmerFlagState);
pw.println(" " + mSmallAreaDetectionFlagState);
pw.println(" " + mDisplayConfigErrorHalFlagState);
+ pw.println(" " + mSyncedResolutionSwitch);
pw.println(" " + mBrightnessIntRangeUserPerceptionFlagState);
pw.println(" " + mRestrictDisplayModes);
pw.println(" " + mBrightnessWearBedtimeModeClamperFlagState);
diff --git a/services/core/java/com/android/server/dreams/DreamManagerService.java b/services/core/java/com/android/server/dreams/DreamManagerService.java
index 7e8bb28b6a37..2af74f620c95 100644
--- a/services/core/java/com/android/server/dreams/DreamManagerService.java
+++ b/services/core/java/com/android/server/dreams/DreamManagerService.java
@@ -569,7 +569,8 @@ public final class DreamManagerService extends SystemService {
}
private void requestDreamInternal() {
- if (isDreamingInternal() && !dreamIsFrontmost() && mController.bringDreamToFront()) {
+ if (isDreamingInternal() && !dreamIsFrontmost() && mController.bringDreamToFront()
+ && !isDozingInternal()) {
return;
}
diff --git a/services/core/java/com/android/server/flags/people.aconfig b/services/core/java/com/android/server/flags/people.aconfig
new file mode 100644
index 000000000000..532e95848a06
--- /dev/null
+++ b/services/core/java/com/android/server/flags/people.aconfig
@@ -0,0 +1,12 @@
+package: "com.android.server.flags"
+container: "system"
+
+flag {
+ name: "early_data_manager_init"
+ namespace: "system_performance"
+ description: "Initialize DataManager earlier in boot"
+ bug: "394601872"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
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/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 484b47022f04..334e7b5240ce 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -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;
}
diff --git a/services/core/java/com/android/server/media/MediaRoute2ProviderWatcher.java b/services/core/java/com/android/server/media/MediaRoute2ProviderWatcher.java
index 42303e042561..b735b2447486 100644
--- a/services/core/java/com/android/server/media/MediaRoute2ProviderWatcher.java
+++ b/services/core/java/com/android/server/media/MediaRoute2ProviderWatcher.java
@@ -141,8 +141,7 @@ final class MediaRoute2ProviderWatcher {
isSelfScanOnlyProvider |=
MediaRoute2ProviderService.CATEGORY_SELF_SCAN_ONLY.equals(category);
supportsSystemMediaRouting |=
- MediaRoute2ProviderService.SERVICE_INTERFACE_SYSTEM_MEDIA.equals(
- category);
+ MediaRoute2ProviderService.CATEGORY_SYSTEM_MEDIA.equals(category);
}
}
int sourceIndex = findProvider(serviceInfo.packageName, serviceInfo.name);
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 0b8b115e65d0..4cf439611852 100644
--- a/services/core/java/com/android/server/media/quality/MediaQualityService.java
+++ b/services/core/java/com/android/server/media/quality/MediaQualityService.java
@@ -127,17 +127,17 @@ public class MediaQualityService extends SystemService {
super(context);
mContext = context;
mHalAmbientBacklightCallback = new HalAmbientBacklightCallback();
- mPictureProfileAdjListener = new PictureProfileAdjustmentListenerImpl(mContext);
- mSoundProfileAdjListener = new SoundProfileAdjustmentListenerImpl(mContext);
mPackageManager = mContext.getPackageManager();
mPictureProfileTempIdMap = new BiMap<>();
mSoundProfileTempIdMap = new BiMap<>();
mMediaQualityDbHelper = new MediaQualityDbHelper(mContext);
- mMqDatabaseUtils = new MqDatabaseUtils(mContext);
mMediaQualityDbHelper.setWriteAheadLoggingEnabled(true);
mMediaQualityDbHelper.setIdleConnectionTimeout(30);
- mHalNotifier = new HalNotifier();
mMqManagerNotifier = new MqManagerNotifier();
+ mMqDatabaseUtils = new MqDatabaseUtils();
+ mHalNotifier = new HalNotifier();
+ mPictureProfileAdjListener = new PictureProfileAdjustmentListenerImpl();
+ mSoundProfileAdjListener = new SoundProfileAdjustmentListenerImpl();
// The package info in the context isn't initialized in the way it is for normal apps,
// so the standard, name-based context.getSharedPreferences doesn't work. Instead, we
@@ -166,16 +166,18 @@ public class MediaQualityService extends SystemService {
if (mMediaQuality != null) {
try {
mMediaQuality.setAmbientBacklightCallback(mHalAmbientBacklightCallback);
+
+ mPpChangedListener = mMediaQuality.getPictureProfileListener();
+ mSpChangedListener = mMediaQuality.getSoundProfileListener();
+
mMediaQuality.setPictureProfileAdjustmentListener(mPictureProfileAdjListener);
mMediaQuality.setSoundProfileAdjustmentListener(mSoundProfileAdjListener);
+
} catch (RemoteException e) {
Slog.e(TAG, "Failed to set ambient backlight detector callback", e);
}
}
- mPpChangedListener = IPictureProfileChangedListener.Stub.asInterface(binder);
- mSpChangedListener = ISoundProfileChangedListener.Stub.asInterface(binder);
-
publishBinderService(Context.MEDIA_QUALITY_SERVICE, new BinderService());
}
@@ -225,7 +227,6 @@ public class MediaQualityService extends SystemService {
PictureProfile.ERROR_NO_PERMISSION,
Binder.getCallingUid(), Binder.getCallingPid());
}
-
synchronized (mPictureProfileLock) {
ContentValues values = MediaQualityUtils.getContentValues(dbId,
pp.getProfileType(),
@@ -233,7 +234,6 @@ public class MediaQualityService extends SystemService {
pp.getPackageName(),
pp.getInputId(),
pp.getParameters());
-
updateDatabaseOnPictureProfileAndNotifyManagerAndHal(values, pp.getParameters());
}
}
@@ -792,7 +792,6 @@ public class MediaQualityService extends SystemService {
}
}
- //TODO: do I need a lock here?
@Override
public List<ParameterCapability> getParameterCapabilities(
List<String> names, UserHandle user) {
@@ -809,14 +808,20 @@ public class MediaQualityService extends SystemService {
private List<ParameterCapability> getListParameterCapability(ParamCapability[] caps) {
List<ParameterCapability> pcList = new ArrayList<>();
- for (ParamCapability pcHal : caps) {
- String name = MediaQualityUtils.getParameterName(pcHal.name);
- boolean isSupported = pcHal.isSupported;
- int type = pcHal.defaultValue == null ? 0 : pcHal.defaultValue.getTag() + 1;
- Bundle bundle = MediaQualityUtils.convertToCaps(type, pcHal.range);
- pcList.add(new ParameterCapability(name, isSupported, type, bundle));
+ if (caps != null) {
+ for (ParamCapability pcHal : caps) {
+ if (pcHal != null) {
+ String name = MediaQualityUtils.getParameterName(pcHal.name);
+ boolean isSupported = pcHal.isSupported;
+ int type = pcHal.defaultValue == null ? 0 : pcHal.defaultValue.getTag() + 1;
+ Bundle bundle = MediaQualityUtils.convertToCaps(type, pcHal.range);
+
+ pcList.add(new ParameterCapability(name, isSupported, type, bundle));
+ }
+ }
}
+
return pcList;
}
@@ -1112,8 +1117,6 @@ public class MediaQualityService extends SystemService {
private final class MqDatabaseUtils {
- MediaQualityDbHelper mMediaQualityDbHelper;
-
private PictureProfile getPictureProfile(Long dbId) {
String selection = BaseParameters.PARAMETER_ID + " = ?";
String[] selectionArguments = {Long.toString(dbId)};
@@ -1205,8 +1208,7 @@ public class MediaQualityService extends SystemService {
/*groupBy=*/ null, /*having=*/ null, /*orderBy=*/ null);
}
- private MqDatabaseUtils(Context context) {
- mMediaQualityDbHelper = new MediaQualityDbHelper(context);
+ private MqDatabaseUtils() {
}
}
@@ -1404,11 +1406,13 @@ public class MediaQualityService extends SystemService {
private void notifyHalOnPictureProfileChange(Long dbId, PersistableBundle params) {
// TODO: only notify HAL when the profile is active / being used
- try {
- mPpChangedListener.onPictureProfileChanged(convertToHalPictureProfile(dbId,
- params));
- } catch (RemoteException e) {
- Slog.e(TAG, "Failed to notify HAL on picture profile change.", e);
+ if (mPpChangedListener != null) {
+ try {
+ mPpChangedListener.onPictureProfileChanged(convertToHalPictureProfile(dbId,
+ params));
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to notify HAL on picture profile change.", e);
+ }
}
}
@@ -1429,10 +1433,13 @@ public class MediaQualityService extends SystemService {
private void notifyHalOnSoundProfileChange(Long dbId, PersistableBundle params) {
// TODO: only notify HAL when the profile is active / being used
- try {
- mSpChangedListener.onSoundProfileChanged(convertToHalSoundProfile(dbId, params));
- } catch (RemoteException e) {
- Slog.e(TAG, "Failed to notify HAL on sound profile change.", e);
+ if (mSpChangedListener != null) {
+ try {
+ mSpChangedListener
+ .onSoundProfileChanged(convertToHalSoundProfile(dbId, params));
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to notify HAL on sound profile change.", e);
+ }
}
}
@@ -1488,9 +1495,6 @@ public class MediaQualityService extends SystemService {
private final class PictureProfileAdjustmentListenerImpl extends
IPictureProfileAdjustmentListener.Stub {
- MqDatabaseUtils mMqDatabaseUtils;
- MqManagerNotifier mMqManagerNotifier;
- HalNotifier mHalNotifier;
@Override
public void onPictureProfileAdjusted(
@@ -1542,18 +1546,13 @@ public class MediaQualityService extends SystemService {
return null;
}
- private PictureProfileAdjustmentListenerImpl(Context context) {
- mMqDatabaseUtils = new MqDatabaseUtils(context);
- mMqManagerNotifier = new MqManagerNotifier();
- mHalNotifier = new HalNotifier();
+ private PictureProfileAdjustmentListenerImpl() {
+
}
}
private final class SoundProfileAdjustmentListenerImpl extends
ISoundProfileAdjustmentListener.Stub {
- MqDatabaseUtils mMqDatabaseUtils;
- MqManagerNotifier mMqManagerNotifier;
- HalNotifier mHalNotifier;
@Override
public void onSoundProfileAdjusted(
@@ -1598,10 +1597,8 @@ public class MediaQualityService extends SystemService {
return null;
}
- private SoundProfileAdjustmentListenerImpl(Context context) {
- mMqDatabaseUtils = new MqDatabaseUtils(context);
- mMqManagerNotifier = new MqManagerNotifier();
- mHalNotifier = new HalNotifier();
+ private SoundProfileAdjustmentListenerImpl() {
+
}
}
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 3a3deb00562e..09c8b5ba823e 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;
@@ -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)) {
@@ -5244,6 +5248,21 @@ public class NotificationManagerService extends SystemService {
}
@Override
+ @FlaggedApi(android.app.Flags.FLAG_NM_BINDER_PERF_GET_APPS_WITH_CHANNELS)
+ public List<String> getPackagesWithAnyChannels(int userId) throws RemoteException {
+ checkCallerIsSystem();
+ UserHandle user = UserHandle.of(userId);
+ List<String> packages = mPreferencesHelper.getPackagesWithAnyChannels(userId);
+ for (int i = packages.size() - 1; i >= 0; i--) {
+ String pkg = packages.get(i);
+ if (!areNotificationsEnabledForPackage(pkg, getUidForPackageAndUser(pkg, user))) {
+ packages.remove(i);
+ }
+ }
+ return packages;
+ }
+
+ @Override
public void clearData(String packageName, int uid, boolean fromApp) throws RemoteException {
boolean packagesChanged = false;
checkCallerIsSystem();
@@ -5715,12 +5734,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);
@@ -5747,16 +5767,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 {
@@ -6534,6 +6554,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)
@@ -6938,7 +6965,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
@@ -13150,7 +13178,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.
@@ -13790,7 +13819,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;
}
@@ -13818,7 +13847,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;
}
@@ -13874,7 +13903,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/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
index a171ffc2ed98..3974c839fd38 100644
--- a/services/core/java/com/android/server/notification/PreferencesHelper.java
+++ b/services/core/java/com/android/server/notification/PreferencesHelper.java
@@ -2006,6 +2006,29 @@ public class PreferencesHelper implements RankingConfig {
}
/**
+ * Gets all apps for this user that have a nonzero number of channels. This count does not
+ * include deleted channels.
+ */
+ @FlaggedApi(android.app.Flags.FLAG_NM_BINDER_PERF_GET_APPS_WITH_CHANNELS)
+ public @NonNull List<String> getPackagesWithAnyChannels(@UserIdInt int userId) {
+ List<String> pkgs = new ArrayList<>();
+ synchronized (mLock) {
+ for (PackagePreferences p : mPackagePreferences.values()) {
+ if (UserHandle.getUserId(p.uid) != userId) {
+ continue;
+ }
+ for (NotificationChannel c : p.channels.values()) {
+ if (!c.isDeleted()) {
+ pkgs.add(p.pkg);
+ break;
+ }
+ }
+ }
+ }
+ return pkgs;
+ }
+
+ /**
* True for pre-O apps that only have the default channel, or pre O apps that have no
* channels yet. This method will create the default channel for pre-O apps that don't have it.
* Should never be true for O+ targeting apps, but that's enforced on boot/when an app
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/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 55d2508d085e..15688c0f7366 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -1021,7 +1021,7 @@ final class InstallPackageHelper {
*
* Failure at any phase will result in a full failure to install all packages.
*/
- void installPackagesTraced(List<InstallRequest> requests) {
+ void installPackagesTraced(List<InstallRequest> requests, MoveInfo moveInfo) {
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "installPackages");
boolean success = false;
final Map<String, Boolean> createdAppId = new ArrayMap<>(requests.size());
@@ -1049,10 +1049,37 @@ final class InstallPackageHelper {
} finally {
completeInstallProcess(requests, createdAppId, success);
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
+ doPostInstall(requests, moveInfo);
releaseWakeLock(acquireTime, requests.size());
}
}
+ private void doPostInstall(List<InstallRequest> requests, MoveInfo moveInfo) {
+ for (InstallRequest request : requests) {
+ doPostInstallCleanUp(request, moveInfo);
+ }
+
+ for (InstallRequest request : requests) {
+ restoreAndPostInstall(request);
+ }
+ }
+
+ private void doPostInstallCleanUp(InstallRequest request, MoveInfo moveInfo) {
+ if (moveInfo != null) {
+ if (request.getReturnCode() == PackageManager.INSTALL_SUCCEEDED) {
+ mRemovePackageHelper.cleanUpForMoveInstall(moveInfo.mFromUuid,
+ moveInfo.mPackageName, moveInfo.mFromCodePath);
+ } else {
+ mRemovePackageHelper.cleanUpForMoveInstall(moveInfo.mToUuid,
+ moveInfo.mPackageName, moveInfo.mFromCodePath);
+ }
+ } else {
+ if (request.getReturnCode() != PackageManager.INSTALL_SUCCEEDED) {
+ mRemovePackageHelper.removeCodePath(request.getCodeFile());
+ }
+ }
+ }
+
private long acquireWakeLock(int count) {
if (!mPm.isSystemReady()) {
return -1;
diff --git a/services/core/java/com/android/server/pm/InstallingSession.java b/services/core/java/com/android/server/pm/InstallingSession.java
index 6a2bf83ba368..3d8f2bbf4e7f 100644
--- a/services/core/java/com/android/server/pm/InstallingSession.java
+++ b/services/core/java/com/android/server/pm/InstallingSession.java
@@ -550,31 +550,11 @@ class InstallingSession {
cleanUpForFailedInstall(request);
}
}
- } else {
- mPm.installPackagesTraced(installRequests);
-
for (InstallRequest request : installRequests) {
- doPostInstall(request);
- }
- }
- for (InstallRequest request : installRequests) {
- mPm.restoreAndPostInstall(request);
- }
- }
-
- private void doPostInstall(InstallRequest request) {
- if (mMoveInfo != null) {
- if (request.getReturnCode() == PackageManager.INSTALL_SUCCEEDED) {
- mPm.cleanUpForMoveInstall(mMoveInfo.mFromUuid,
- mMoveInfo.mPackageName, mMoveInfo.mFromCodePath);
- } else {
- mPm.cleanUpForMoveInstall(mMoveInfo.mToUuid,
- mMoveInfo.mPackageName, mMoveInfo.mFromCodePath);
+ mPm.restoreAndPostInstall(request);
}
} else {
- if (request.getReturnCode() != PackageManager.INSTALL_SUCCEEDED) {
- mPm.removeCodePath(request.getCodeFile());
- }
+ mPm.installPackagesTraced(installRequests, mMoveInfo);
}
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 8343935425cd..2464a291b4dd 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -8214,8 +8214,8 @@ public class PackageManagerService implements PackageSender, TestUtilityService
return mInstallPackageHelper.enableCompressedPackage(stubPkg, stubPs);
}
- void installPackagesTraced(List<InstallRequest> requests) {
- mInstallPackageHelper.installPackagesTraced(requests);
+ void installPackagesTraced(List<InstallRequest> requests, MoveInfo moveInfo) {
+ mInstallPackageHelper.installPackagesTraced(requests, moveInfo);
}
void restoreAndPostInstall(InstallRequest request) {
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 76c5240ab623..4153cd1be0a6 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -1163,6 +1163,15 @@ 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
@@ -1261,9 +1270,10 @@ public class PhoneWindowManager implements WindowManagerPolicy {
// show hub.
boolean keyguardAvailable = !mLockPatternUtils.isLockScreenDisabled(
mCurrentUserId);
- if (mUserManagerInternal.isUserUnlocked(mCurrentUserId) && hubEnabled
- && keyguardAvailable && mDreamManagerInternal.dreamConditionActive()) {
- // If the hub can be launched, send a message to keyguard.
+ 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.
Bundle options = new Bundle();
options.putBoolean(EXTRA_TRIGGER_HUB, true);
lockNow(options);
@@ -1324,14 +1334,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 void attemptToDreamFromShortPowerButtonPress(
+ private boolean 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;
+ return false;
}
final DreamManagerInternal dreamManagerInternal = getDreamManagerInternal();
@@ -1339,7 +1349,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;
+ return false;
}
synchronized (mLock) {
@@ -1350,6 +1360,8 @@ public class PhoneWindowManager implements WindowManagerPolicy {
}
dreamManagerInternal.requestDream();
+
+ return true;
}
/**
@@ -6398,6 +6410,17 @@ 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/policy/keyguard/KeyguardServiceDelegate.java b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java
index 587447b8af26..9d7e9f53a1aa 100644
--- a/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java
+++ b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java
@@ -92,6 +92,8 @@ public class KeyguardServiceDelegate {
public boolean bootCompleted;
public int screenState;
public int interactiveState;
+ boolean doKeyguardTimeoutRequested;
+ Bundle doKeyguardTimeoutRequestedOptions;
private void reset() {
// Assume keyguard is showing and secure until we know for sure. This is here in
@@ -225,6 +227,12 @@ public class KeyguardServiceDelegate {
if (mKeyguardState.dreaming) {
mKeyguardService.onDreamingStarted();
}
+ if (mKeyguardState.doKeyguardTimeoutRequested) {
+ mKeyguardService.doKeyguardTimeout(
+ mKeyguardState.doKeyguardTimeoutRequestedOptions);
+ mKeyguardState.doKeyguardTimeoutRequested = false;
+ mKeyguardState.doKeyguardTimeoutRequestedOptions = null;
+ }
}
@Override
@@ -410,6 +418,11 @@ public class KeyguardServiceDelegate {
public void doKeyguardTimeout(Bundle options) {
if (mKeyguardService != null) {
mKeyguardService.doKeyguardTimeout(options);
+ } else {
+ mKeyguardState.doKeyguardTimeoutRequested = true;
+ if (options != null) {
+ mKeyguardState.doKeyguardTimeoutRequestedOptions = options;
+ }
}
}
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/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/security/intrusiondetection/DataAggregator.java b/services/core/java/com/android/server/security/intrusiondetection/DataAggregator.java
index 687442b47fb3..cdeacaa2e43a 100644
--- a/services/core/java/com/android/server/security/intrusiondetection/DataAggregator.java
+++ b/services/core/java/com/android/server/security/intrusiondetection/DataAggregator.java
@@ -62,7 +62,7 @@ public class DataAggregator {
/** Initialize DataSources */
private void initialize() {
mDataSources.add(new SecurityLogSource(mContext, this));
- mDataSources.add(new NetworkLogSource(mContext, this));
+ mDataSources.add(new NetworkLogSource(this));
}
/**
diff --git a/services/core/java/com/android/server/security/intrusiondetection/NetworkLogSource.java b/services/core/java/com/android/server/security/intrusiondetection/NetworkLogSource.java
index f303a588d30c..fe0cf80a48f2 100644
--- a/services/core/java/com/android/server/security/intrusiondetection/NetworkLogSource.java
+++ b/services/core/java/com/android/server/security/intrusiondetection/NetworkLogSource.java
@@ -18,7 +18,6 @@ package com.android.server.security.intrusiondetection;
import android.app.admin.ConnectEvent;
import android.app.admin.DnsEvent;
-import android.content.Context;
import android.content.pm.PackageManagerInternal;
import android.net.IIpConnectivityMetrics;
import android.net.INetdEventCallback;
@@ -44,8 +43,7 @@ public class NetworkLogSource implements DataSource {
private IIpConnectivityMetrics mIpConnectivityMetrics;
private long mId;
- public NetworkLogSource(Context context, DataAggregator dataAggregator)
- throws SecurityException {
+ public NetworkLogSource(DataAggregator dataAggregator) throws SecurityException {
mDataAggregator = dataAggregator;
mPm = LocalServices.getService(PackageManagerInternal.class);
mId = 0;
diff --git a/services/core/java/com/android/server/security/intrusiondetection/SecurityLogSource.java b/services/core/java/com/android/server/security/intrusiondetection/SecurityLogSource.java
index 142094c9d9f4..7501799198e8 100644
--- a/services/core/java/com/android/server/security/intrusiondetection/SecurityLogSource.java
+++ b/services/core/java/com/android/server/security/intrusiondetection/SecurityLogSource.java
@@ -19,14 +19,15 @@ package com.android.server.security.intrusiondetection;
import android.Manifest.permission;
import android.annotation.RequiresPermission;
import android.app.admin.DevicePolicyManager;
+import android.app.admin.DevicePolicyManagerInternal;
import android.app.admin.SecurityLog.SecurityEvent;
import android.content.Context;
import android.security.intrusiondetection.IntrusionDetectionEvent;
import android.util.Slog;
+import com.android.server.LocalServices;
+
import java.util.List;
-import java.util.concurrent.Executor;
-import java.util.concurrent.Executors;
import java.util.function.Consumer;
import java.util.stream.Collectors;
@@ -36,13 +37,13 @@ public class SecurityLogSource implements DataSource {
private SecurityEventCallback mEventCallback;
private DevicePolicyManager mDpm;
- private Executor mExecutor;
+ private DevicePolicyManagerInternal mDpmInternal;
private DataAggregator mDataAggregator;
public SecurityLogSource(Context context, DataAggregator dataAggregator) {
mDataAggregator = dataAggregator;
mDpm = context.getSystemService(DevicePolicyManager.class);
- mExecutor = Executors.newSingleThreadExecutor();
+ mDpmInternal = LocalServices.getService(DevicePolicyManagerInternal.class);
mEventCallback = new SecurityEventCallback();
}
@@ -50,12 +51,13 @@ public class SecurityLogSource implements DataSource {
@RequiresPermission(permission.MANAGE_DEVICE_POLICY_AUDIT_LOGGING)
public void enable() {
enableAuditLog();
- mDpm.setAuditLogEventCallback(mExecutor, mEventCallback);
+ mDpmInternal.setInternalEventsCallback(mEventCallback);
}
@Override
@RequiresPermission(permission.MANAGE_DEVICE_POLICY_AUDIT_LOGGING)
public void disable() {
+ mDpmInternal.setInternalEventsCallback(null);
disableAuditLog();
}
@@ -82,10 +84,11 @@ public class SecurityLogSource implements DataSource {
@Override
public void accept(List<SecurityEvent> events) {
- if (events.size() == 0) {
+ if (events == null || events.size() == 0) {
Slog.w(TAG, "No events received; caller may not be authorized");
return;
}
+
List<IntrusionDetectionEvent> intrusionDetectionEvents =
events.stream()
.filter(event -> event != null)
diff --git a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
index 7f2c68ff60b1..889b494ef538 100644
--- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
+++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
@@ -3661,16 +3661,17 @@ public class StatsPullAtomService extends SystemService {
if (!packageNames.isEmpty()) {
for (String packageName : packageNames) {
- PackageInfo pkg;
+ int uid = INVALID_UID;
try {
- pkg = pm.getPackageInfoAsUser(packageName, 0, userId);
+ PackageInfo pkg = pm.getPackageInfoAsUser(packageName, 0, userId);
+ uid = pkg.applicationInfo.uid;
} catch (PackageManager.NameNotFoundException e) {
- Slog.w(TAG, "Role holder " + packageName + " not found");
- return StatsManager.PULL_SKIP;
+ Slog.w(TAG, "Role holder " + packageName + " not found for user "
+ + userId);
}
pulledData.add(FrameworkStatsLog.buildStatsEvent(
- atomTag, pkg.applicationInfo.uid, packageName, roleName));
+ atomTag, uid, packageName, roleName));
}
} else {
// Ensure that roles set to None are logged with an empty state.
@@ -3679,6 +3680,9 @@ public class StatsPullAtomService extends SystemService {
}
}
}
+ } catch (Throwable t) {
+ Log.e(TAG, "Could not read role holders", t);
+ return StatsManager.PULL_SKIP;
} finally {
Binder.restoreCallingIdentity(callingToken);
}
diff --git a/services/core/java/com/android/server/updates/CertPinInstallReceiver.java b/services/core/java/com/android/server/updates/CertPinInstallReceiver.java
index c03fbc3d0aed..250e99b47b1a 100644
--- a/services/core/java/com/android/server/updates/CertPinInstallReceiver.java
+++ b/services/core/java/com/android/server/updates/CertPinInstallReceiver.java
@@ -16,9 +16,19 @@
package com.android.server.updates;
+import android.content.Context;
+import android.content.Intent;
+
public class CertPinInstallReceiver extends ConfigUpdateInstallReceiver {
public CertPinInstallReceiver() {
super("/data/misc/keychain/", "pins", "metadata/", "version");
}
+
+ @Override
+ public void onReceive(final Context context, final Intent intent) {
+ if (!com.android.server.flags.Flags.certpininstallerRemoval()) {
+ super.onReceive(context, intent);
+ }
+ }
}
diff --git a/services/core/java/com/android/server/wearable/WearableSensingManagerPerUserService.java b/services/core/java/com/android/server/wearable/WearableSensingManagerPerUserService.java
index 395816902592..d06827ab0529 100644
--- a/services/core/java/com/android/server/wearable/WearableSensingManagerPerUserService.java
+++ b/services/core/java/com/android/server/wearable/WearableSensingManagerPerUserService.java
@@ -273,6 +273,9 @@ final class WearableSensingManagerPerUserService
@Override
public void onError() {
+ synchronized (mLock) {
+ ensureRemoteServiceInitiated();
+ }
synchronized (mSecureChannelLock) {
if (mSecureChannel != null
&& mSecureChannel
diff --git a/services/core/java/com/android/server/wearable/WearableSensingSecureChannel.java b/services/core/java/com/android/server/wearable/WearableSensingSecureChannel.java
index a16ff51e2d20..9f14ab7a70d3 100644
--- a/services/core/java/com/android/server/wearable/WearableSensingSecureChannel.java
+++ b/services/core/java/com/android/server/wearable/WearableSensingSecureChannel.java
@@ -156,6 +156,7 @@ final class WearableSensingSecureChannel {
new AssociationRequest.Builder()
.setDisplayName(CDM_ASSOCIATION_DISPLAY_NAME)
.setSelfManaged(true)
+ .setDeviceProfile(AssociationRequest.DEVICE_PROFILE_WEARABLE_SENSING)
.build(),
mLightWeightExecutor,
new CompanionDeviceManager.Callback() {
@@ -195,7 +196,8 @@ final class WearableSensingSecureChannel {
mCompanionDeviceManager.attachSystemDataTransport(
associationId,
new AutoCloseInputStream(mUnderlyingTransport),
- new AutoCloseOutputStream(mUnderlyingTransport));
+ new AutoCloseOutputStream(mUnderlyingTransport),
+ CompanionDeviceManager.TRANSPORT_FLAG_EXTEND_PATCH_DIFF);
}
}
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index cf9c57aa634a..c37b5a055140 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -46,9 +46,6 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.app.WindowConfiguration.activityTypeToString;
-import static android.app.admin.DevicePolicyResources.Drawables.Source.PROFILE_SWITCH_ANIMATION;
-import static android.app.admin.DevicePolicyResources.Drawables.Style.OUTLINE;
-import static android.app.admin.DevicePolicyResources.Drawables.WORK_PROFILE_ICON;
import static android.content.Context.CONTEXT_RESTRICTED;
import static android.content.Intent.ACTION_MAIN;
import static android.content.Intent.CATEGORY_HOME;
@@ -189,7 +186,6 @@ import static com.android.server.wm.ActivityRecordProto.STARTING_DISPLAYED;
import static com.android.server.wm.ActivityRecordProto.STARTING_MOVED;
import static com.android.server.wm.ActivityRecordProto.STARTING_WINDOW;
import static com.android.server.wm.ActivityRecordProto.STATE;
-import static com.android.server.wm.ActivityRecordProto.THUMBNAIL;
import static com.android.server.wm.ActivityRecordProto.TRANSLUCENT;
import static com.android.server.wm.ActivityRecordProto.VISIBLE;
import static com.android.server.wm.ActivityRecordProto.VISIBLE_REQUESTED;
@@ -265,7 +261,6 @@ import android.app.PictureInPictureParams;
import android.app.ResultInfo;
import android.app.WaitResult;
import android.app.WindowConfiguration;
-import android.app.admin.DevicePolicyManager;
import android.app.assist.ActivityId;
import android.app.compat.CompatChanges;
import android.app.servertransaction.ActivityConfigurationChangeItem;
@@ -300,7 +295,6 @@ import android.graphics.Insets;
import android.graphics.PixelFormat;
import android.graphics.Point;
import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
import android.gui.DropInputMode;
import android.hardware.HardwareBuffer;
import android.net.Uri;
@@ -333,7 +327,6 @@ import android.view.IAppTransitionAnimationSpecsFuture;
import android.view.InputApplicationHandle;
import android.view.RemoteAnimationAdapter;
import android.view.RemoteAnimationDefinition;
-import android.view.RemoteAnimationTarget;
import android.view.SurfaceControl;
import android.view.SurfaceControl.Transaction;
import android.view.WindowInsets;
@@ -341,7 +334,6 @@ import android.view.WindowInsets.Type;
import android.view.WindowManager;
import android.view.WindowManager.LayoutParams;
import android.view.WindowManager.TransitionOldType;
-import android.view.animation.Animation;
import android.window.ActivityWindowInfo;
import android.window.ITaskFragmentOrganizer;
import android.window.RemoteTransition;
@@ -380,7 +372,6 @@ import com.android.server.uri.UriPermissionOwner;
import com.android.server.wm.ActivityMetricsLogger.TransitionInfoSnapshot;
import com.android.server.wm.SurfaceAnimator.AnimationType;
import com.android.server.wm.WindowManagerService.H;
-import com.android.server.wm.utils.InsetUtils;
import com.android.window.flags.Flags;
import dalvik.annotation.optimization.NeverCompile;
@@ -4701,8 +4692,6 @@ final class ActivityRecord extends WindowToken {
return true;
}
- // TODO: Transfer thumbnail
-
return false;
}
@@ -5707,7 +5696,21 @@ final class ActivityRecord extends WindowToken {
displayContent.getInputMonitor().updateInputWindowsLw(false /*force*/);
mTransitionChangeFlags = 0;
- postApplyAnimation(visible, fromTransition);
+ // Set client visibility if:
+ // 1. The activity is becoming visible. This is usually no-op because assume that
+ // setVisibility(true) should have been called. Just in case if that was missed.
+ // 2. The activity is becoming invisible and not RESUMED state (it is usually PAUSED unless
+ // the activity is transient-hide). If the state is RESUMED, setVisibility(false) will be
+ // called until activityStopped. This is to avoid crashing apps that assume its view root
+ // won't be invisible before the activity is paused.
+ if (visible || mState != RESUMED) {
+ setClientVisible(visible);
+ }
+ // Notify the visibility change outside of transition in case onTransitionFinish is not
+ // called for updating snapshot states.
+ if (!fromTransition) {
+ mWmService.mSnapshotController.notifyAppVisibilityChanged(this, visible);
+ }
}
void commitVisibility(boolean visible, boolean performLayout) {
@@ -5726,75 +5729,6 @@ final class ActivityRecord extends WindowToken {
return mNeedsLetterboxedAnimation && isAnimating();
}
- /**
- * Post process after applying an app transition animation.
- *
- * <p class="note"><strong>Note: </strong> This function must be called after the animations
- * have been applied and {@link #commitVisibility}.</p>
- *
- * @param visible {@code true} if this {@link ActivityRecord} has become visible, otherwise
- * this has become invisible.
- * @param fromTransition {@code true} if this call is part of finishing a transition. This is
- * needed because the shell transition is no-longer active by the time
- * commitVisibility is called.
- */
- private void postApplyAnimation(boolean visible, boolean fromTransition) {
- final boolean usingShellTransitions = mTransitionController.isShellTransitionsEnabled();
- final boolean delayed = !usingShellTransitions && isAnimating(PARENTS | CHILDREN,
- ANIMATION_TYPE_APP_TRANSITION | ANIMATION_TYPE_WINDOW_ANIMATION);
- if (!delayed && !usingShellTransitions) {
- // We aren't delayed anything, but exiting windows rely on the animation finished
- // callback being called in case the ActivityRecord was pretending to be delayed,
- // which we might have done because we were in closing/opening apps list.
- onAnimationFinished(ANIMATION_TYPE_APP_TRANSITION, null /* AnimationAdapter */);
- if (visible) {
- // The token was made immediately visible, there will be no entrance animation.
- // We need to inform the client the enter animation was finished.
- mEnteringAnimation = true;
- mWmService.mActivityManagerAppTransitionNotifier.onAppTransitionFinishedLocked(
- token);
- }
- }
-
- // If we're becoming visible, immediately change client visibility as well. there seem
- // to be some edge cases where we change our visibility but client visibility never gets
- // updated.
- // If we're becoming invisible, update the client visibility if we are not running an
- // animation and aren't in RESUMED state. Otherwise, we'll update client visibility in
- // onAnimationFinished or activityStopped.
- if (visible || (mState != RESUMED && (usingShellTransitions || !isAnimating(
- PARENTS, ANIMATION_TYPE_APP_TRANSITION)))) {
- setClientVisible(visible);
- }
-
- final DisplayContent displayContent = getDisplayContent();
- if (!displayContent.mClosingApps.contains(this)
- && !displayContent.mOpeningApps.contains(this)
- && !fromTransition) {
- // Take the screenshot before possibly hiding the WSA, otherwise the screenshot
- // will not be taken.
- mWmService.mSnapshotController.notifyAppVisibilityChanged(this, visible);
- }
-
- // If we are hidden but there is no delay needed we immediately
- // apply the Surface transaction so that the ActivityManager
- // can have some guarantee on the Surface state following
- // setting the visibility. This captures cases like dismissing
- // the docked or root pinned task where there is no app transition.
- //
- // In the case of a "Null" animation, there will be
- // no animation but there will still be a transition set.
- // We still need to delay hiding the surface such that it
- // can be synchronized with showing the next surface in the transition.
- if (!usingShellTransitions && !isVisible() && !delayed
- && !displayContent.mAppTransition.isTransitionSet()) {
- forAllWindows(win -> {
- win.mWinAnimator.hide(getPendingTransaction(), "immediately hidden");
- }, true);
- scheduleAnimation();
- }
- }
-
/** Updates draw state and shows drawn windows. */
void commitFinishDrawing(SurfaceControl.Transaction t) {
boolean committed = false;
@@ -7597,9 +7531,6 @@ final class ActivityRecord extends WindowToken {
mActivityRecordInputSink.applyChangesToSurfaceIfChanged(getPendingTransaction());
}
}
- if (mThumbnail != null) {
- mThumbnail.setShowing(getPendingTransaction(), show);
- }
mLastSurfaceShowing = show;
super.prepareSurfaces();
}
@@ -7611,84 +7542,6 @@ final class ActivityRecord extends WindowToken {
return mLastSurfaceShowing;
}
- void attachThumbnailAnimation() {
- if (!isAnimating(PARENTS, ANIMATION_TYPE_APP_TRANSITION)) {
- return;
- }
- final HardwareBuffer thumbnailHeader =
- getDisplayContent().mAppTransition.getAppTransitionThumbnailHeader(task);
- if (thumbnailHeader == null) {
- ProtoLog.d(WM_DEBUG_APP_TRANSITIONS, "No thumbnail header bitmap for: %s", task);
- return;
- }
- clearThumbnail();
- final Transaction transaction = getAnimatingContainer().getPendingTransaction();
- mThumbnail = new WindowContainerThumbnail(transaction, getAnimatingContainer(),
- thumbnailHeader);
- mThumbnail.startAnimation(transaction, loadThumbnailAnimation(thumbnailHeader));
- }
-
- /**
- * Attaches a surface with a thumbnail for the
- * {@link android.app.ActivityOptions#ANIM_OPEN_CROSS_PROFILE_APPS} animation.
- */
- void attachCrossProfileAppsThumbnailAnimation() {
- if (!isAnimating(PARENTS, ANIMATION_TYPE_APP_TRANSITION)) {
- return;
- }
- clearThumbnail();
-
- final WindowState win = findMainWindow();
- if (win == null) {
- return;
- }
- final Rect frame = win.getRelativeFrame();
- final Context context = mAtmService.getUiContext();
- final Drawable thumbnailDrawable;
- if (task.mUserId == mWmService.mCurrentUserId) {
- thumbnailDrawable = context.getDrawable(R.drawable.ic_account_circle);
- } else {
- final DevicePolicyManager dpm = context.getSystemService(DevicePolicyManager.class);
- thumbnailDrawable = dpm.getResources().getDrawable(
- WORK_PROFILE_ICON, OUTLINE, PROFILE_SWITCH_ANIMATION,
- () -> context.getDrawable(R.drawable.ic_corp_badge));
- }
- final HardwareBuffer thumbnail = getDisplayContent().mAppTransition
- .createCrossProfileAppsThumbnail(thumbnailDrawable, frame);
- if (thumbnail == null) {
- return;
- }
- final Transaction transaction = getPendingTransaction();
- mThumbnail = new WindowContainerThumbnail(transaction, getTask(), thumbnail);
- final Animation animation =
- getDisplayContent().mAppTransition.createCrossProfileAppsThumbnailAnimationLocked(
- frame);
- mThumbnail.startAnimation(transaction, animation, new Point(frame.left, frame.top));
- }
-
- private Animation loadThumbnailAnimation(HardwareBuffer thumbnailHeader) {
- final DisplayInfo displayInfo = mDisplayContent.getDisplayInfo();
-
- // If this is a multi-window scenario, we use the windows frame as
- // destination of the thumbnail header animation. If this is a full screen
- // window scenario, we use the whole display as the target.
- WindowState win = findMainWindow();
- Rect insets;
- Rect appRect;
- if (win != null) {
- insets = win.getInsetsStateWithVisibilityOverride().calculateInsets(
- win.getFrame(), Type.systemBars(), false /* ignoreVisibility */).toRect();
- appRect = new Rect(win.getFrame());
- appRect.inset(insets);
- } else {
- insets = null;
- appRect = new Rect(0, 0, displayInfo.appWidth, displayInfo.appHeight);
- }
- final Configuration displayConfig = mDisplayContent.getConfiguration();
- return getDisplayContent().mAppTransition.createThumbnailAspectScaleAnimationLocked(
- appRect, insets, thumbnailHeader, task, displayConfig.orientation);
- }
-
@Override
public void onAnimationLeashLost(Transaction t) {
super.onAnimationLeashLost(t);
@@ -7715,7 +7568,6 @@ final class ActivityRecord extends WindowToken {
setAppLayoutChanges(FINISH_LAYOUT_REDO_ANIM | FINISH_LAYOUT_REDO_WALLPAPER,
"ActivityRecord");
- clearThumbnail();
setClientVisible(isVisible() || mVisibleRequested);
getDisplayContent().computeImeTargetIfNeeded(this);
@@ -7725,12 +7577,6 @@ final class ActivityRecord extends WindowToken {
this, reportedVisible, okToDisplay(), okToAnimate(),
isStartingWindowDisplayed());
- // clean up thumbnail window
- if (mThumbnail != null) {
- mThumbnail.destroy();
- mThumbnail = null;
- }
-
// WindowState.onExitAnimationDone might modify the children list, so make a copy and then
// traverse the copy.
final ArrayList<WindowState> children = new ArrayList<>(mChildren);
@@ -7769,20 +7615,6 @@ final class ActivityRecord extends WindowToken {
}
}
- @Override
- void cancelAnimation() {
- super.cancelAnimation();
- clearThumbnail();
- }
-
- private void clearThumbnail() {
- if (mThumbnail == null) {
- return;
- }
- mThumbnail.destroy();
- mThumbnail = null;
- }
-
public @TransitionOldType int getTransit() {
return mTransit;
}
@@ -8219,6 +8051,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.
@@ -8247,6 +8080,9 @@ final class ActivityRecord extends WindowToken {
resolvedConfig.windowConfiguration.setMaxBounds(mTmpBounds);
}
+ mAppCompatController.getSandboxingPolicy().sandboxBoundsIfNeeded(resolvedConfig,
+ parentWindowingMode);
+
applySizeOverrideIfNeeded(
mDisplayContent,
info.applicationInfo,
@@ -9728,9 +9564,6 @@ final class ActivityRecord extends WindowToken {
proto.write(IS_WAITING_FOR_TRANSITION_START, isWaitingForTransitionStart());
proto.write(IS_ANIMATING, isAnimating(TRANSITION | PARENTS | CHILDREN,
ANIMATION_TYPE_APP_TRANSITION | ANIMATION_TYPE_WINDOW_ANIMATION));
- if (mThumbnail != null){
- mThumbnail.dumpDebug(proto, THUMBNAIL);
- }
proto.write(FILLS_PARENT, fillsParent());
proto.write(APP_STOPPED, mAppStopped);
proto.write(TRANSLUCENT, !occludesParent());
@@ -9834,31 +9667,6 @@ final class ActivityRecord extends WindowToken {
}
@Override
- RemoteAnimationTarget createRemoteAnimationTarget(
- RemoteAnimationController.RemoteAnimationRecord record) {
- final WindowState mainWindow = findMainWindow();
- if (task == null || mainWindow == null) {
- return null;
- }
- final Rect insets = mainWindow.getInsetsStateWithVisibilityOverride().calculateInsets(
- task.getBounds(), Type.systemBars(), false /* ignoreVisibility */).toRect();
- InsetUtils.addInsets(insets, getLetterboxInsets());
-
- final RemoteAnimationTarget target = new RemoteAnimationTarget(task.mTaskId,
- record.getMode(), record.mAdapter.mCapturedLeash, !fillsParent(),
- new Rect(), insets,
- getPrefixOrderIndex(), record.mAdapter.mPosition, record.mAdapter.mLocalBounds,
- record.mAdapter.mEndBounds, task.getWindowConfiguration(),
- false /*isNotInRecents*/,
- record.mThumbnailAdapter != null ? record.mThumbnailAdapter.mCapturedLeash : null,
- record.mStartBounds, task.getTaskInfo(), checkEnterPictureInPictureAppOpsState());
- target.setShowBackdrop(record.mShowBackdrop);
- target.setWillShowImeOnTarget(mStartingData != null && mStartingData.hasImeSurface());
- target.hasAnimatingParent = record.hasAnimatingParent();
- return target;
- }
-
- @Override
boolean canCreateRemoteAnimationTarget() {
return true;
}
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/AppCompatSandboxingPolicy.java b/services/core/java/com/android/server/wm/AppCompatSandboxingPolicy.java
new file mode 100644
index 000000000000..26cf32b12d4f
--- /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 com.android.server.wm.AppCompatUtils.isInDesktopMode;
+
+import android.annotation.NonNull;
+import android.app.WindowConfiguration.WindowingMode;
+import android.content.res.Configuration;
+import android.graphics.Rect;
+
+import com.android.window.flags.Flags;
+
+/**
+ * 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 (!Flags.excludeCaptionFromAppBounds()) {
+ 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 9c4b722feb47..d98ad8bb9e05 100644
--- a/services/core/java/com/android/server/wm/AppTransition.java
+++ b/services/core/java/com/android/server/wm/AppTransition.java
@@ -231,8 +231,6 @@ public class AppTransition implements Dump {
private final int mDefaultWindowAnimationStyleResId;
private boolean mOverrideTaskTransition;
- private RemoteAnimationController mRemoteAnimationController;
-
final Handler mHandler;
final Runnable mHandleAppTransitionTimeoutRunnable = () -> handleAppTransitionTimeout();
@@ -398,9 +396,7 @@ public class AppTransition implements Dump {
: SystemClock.uptimeMillis(),
AnimationAdapter.STATUS_BAR_TRANSITION_DURATION);
- if (mRemoteAnimationController != null) {
- mRemoteAnimationController.goodToGo(transit);
- } else if ((isTaskOpenTransitOld(transit) || transit == TRANSIT_OLD_WALLPAPER_CLOSE)
+ if ((isTaskOpenTransitOld(transit) || transit == TRANSIT_OLD_WALLPAPER_CLOSE)
&& topOpeningAnim != null) {
if (mDisplayContent.getDisplayPolicy().shouldAttachNavBarToAppDuringTransition()) {
final NavBarFadeAnimationController controller =
@@ -424,7 +420,6 @@ public class AppTransition implements Dump {
mNextAppTransitionType = NEXT_TRANSIT_TYPE_NONE;
mNextAppTransitionOverrideRequested = false;
mNextAppTransitionAnimationsSpecs.clear();
- mRemoteAnimationController = null;
mNextAppTransitionAnimationsSpecsFuture = null;
mDefaultNextAppTransitionAnimationSpec = null;
mAnimationFinishedCallback = null;
@@ -442,13 +437,6 @@ public class AppTransition implements Dump {
final boolean keyguardGoingAwayCancelled = mNextAppTransitionRequests.contains(
TRANSIT_KEYGUARD_GOING_AWAY);
- // The RemoteAnimationControl didn't register AppTransitionListener and
- // only initialized the finish and timeout callback when goodToGo().
- // So cancel the remote animation here to prevent the animation can't do
- // finish after transition state cleared.
- if (mRemoteAnimationController != null) {
- mRemoteAnimationController.cancelAnimation("freeze");
- }
mNextAppTransitionRequests.clear();
clear();
setReady();
@@ -719,10 +707,6 @@ public class AppTransition implements Dump {
&& !mNextAppTransitionRequests.contains(TRANSIT_KEYGUARD_GOING_AWAY);
}
- RemoteAnimationController getRemoteAnimationController() {
- return mRemoteAnimationController;
- }
-
/**
*
* @param frame These are the bounds of the window when it finishes the animation. This is where
@@ -1082,17 +1066,6 @@ public class AppTransition implements Dump {
void overridePendingAppTransitionRemote(RemoteAnimationAdapter remoteAnimationAdapter,
boolean sync, boolean isActivityEmbedding) {
- ProtoLog.i(WM_DEBUG_APP_TRANSITIONS, "Override pending remote transitionSet=%b adapter=%s",
- isTransitionSet(), remoteAnimationAdapter);
- if (isTransitionSet() && !mNextAppTransitionIsSync) {
- // ActivityEmbedding animation will run by the app process for which we want to respect
- // the app override for whether or not to show background color.
- clear(!isActivityEmbedding /* clearAppOverride */);
- mNextAppTransitionType = NEXT_TRANSIT_TYPE_REMOTE;
- mRemoteAnimationController = new RemoteAnimationController(mService, mDisplayContent,
- remoteAnimationAdapter, mHandler, isActivityEmbedding);
- mNextAppTransitionIsSync = sync;
- }
}
void overrideInPlaceAppTransition(String packageName, int anim) {
diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java
index e76a83453a9d..094ad187686c 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.");
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/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 682f3d8cf1e5..703ce7d24468 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -3239,25 +3239,43 @@ 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 */);
}
}
+ private 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;
@@ -6578,22 +6596,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/DragState.java b/services/core/java/com/android/server/wm/DragState.java
index 69f32cb7b8ea..84281b8fbecf 100644
--- a/services/core/java/com/android/server/wm/DragState.java
+++ b/services/core/java/com/android/server/wm/DragState.java
@@ -122,7 +122,7 @@ class DragState {
float mThumbOffsetX, mThumbOffsetY;
InputInterceptor mInputInterceptor;
ArrayList<WindowState> mNotifiedWindows;
- boolean mDragInProgress;
+ private boolean mDragInProgress;
// Set to non -1 value if a valid app requests DRAG_FLAG_HIDE_CALLING_TASK_ON_DRAG_START
int mCallingTaskIdToHide;
/**
@@ -161,7 +161,7 @@ class DragState {
private boolean mIsClosing;
// Stores the last drop event which was reported to a valid drop target window, or null
- // otherwise. This drop event will contain private info and should only be consumed by the
+ // otherwise. This drop event will contain private info and should only be consumed by the
// unhandled drag listener.
DragEvent mUnhandledDropEvent;
@@ -243,7 +243,7 @@ class DragState {
for (WindowState ws : mNotifiedWindows) {
float inWindowX = 0;
float inWindowY = 0;
- SurfaceControl dragSurface = null;
+ boolean includeDragSurface = false;
if (!mDragResult && (ws.mSession.mPid == mPid)) {
// Report unconsumed drop location back to the app that started the drag.
inWindowX = ws.translateToWindowX(mCurrentDisplayX);
@@ -251,13 +251,10 @@ class DragState {
if (relinquishDragSurfaceToDragSource()) {
// If requested (and allowed), report the drag surface back to the app
// starting the drag to handle the return animation
- dragSurface = mSurfaceControl;
+ includeDragSurface = true;
}
}
- DragEvent event = DragEvent.obtain(DragEvent.ACTION_DRAG_ENDED, inWindowX,
- inWindowY, mThumbOffsetX, mThumbOffsetY,
- mCurrentDisplayContent.getDisplayId(), mFlags, null, null, null,
- dragSurface, null, mDragResult);
+ DragEvent event = obtainDragEndedEvent(inWindowX, inWindowY, includeDragSurface);
try {
if (DEBUG_DRAG) Slog.d(TAG_WM, "Sending DRAG_ENDED to " + ws);
ws.mClient.dispatchDragEvent(event);
@@ -310,10 +307,10 @@ class DragState {
/**
* Creates the drop event for dispatching to the unhandled drag.
- * TODO(b/384841906): Update `inWindowX` and `inWindowY` to be display-coordinate.
*/
- private DragEvent createUnhandledDropEvent(float inWindowX, float inWindowY) {
- return obtainDragEvent(DragEvent.ACTION_DROP, inWindowX, inWindowY, mDataDescription, mData,
+ private DragEvent createUnhandledDropEvent(float inDisplayX, float inDisplayY) {
+ return obtainDragEvent(DragEvent.ACTION_DROP, inDisplayX, inDisplayY, mDataDescription,
+ mData,
/* includeDragSurface= */ true,
/* includeDragFlags= */ true, null /* dragAndDropPermissions */);
}
@@ -370,11 +367,8 @@ class DragState {
}
final WindowState touchedWin = mService.mInputToWindowMap.get(token);
- // TODO(b/384841906): The x, y here when sent to a window and unhandled, will still be
- // relative to the window it was originally sent to. Need to update this to actually be
- // display-coordinate.
- final DragEvent unhandledDropEvent = createUnhandledDropEvent(inWindowX, inWindowY);
if (!isWindowNotified(touchedWin)) {
+ final DragEvent unhandledDropEvent = createUnhandledDropEvent(inWindowX, inWindowY);
// Delegate to the unhandled drag listener as a first pass
if (mDragDropController.notifyUnhandledDrop(unhandledDropEvent, "unhandled-drop")) {
// The unhandled drag listener will call back to notify whether it has consumed
@@ -392,6 +386,8 @@ class DragState {
}
if (DEBUG_DRAG) Slog.d(TAG_WM, "Sending DROP to " + touchedWin);
+ final DragEvent unhandledDropEvent = createUnhandledDropEvent(
+ touchedWin.getBounds().left + inWindowX, touchedWin.getBounds().top + inWindowY);
final IBinder clientToken = touchedWin.mClient.asBinder();
final DragEvent event = createDropEvent(inWindowX, inWindowY, touchedWin);
@@ -776,28 +772,37 @@ class DragState {
displayId, (int) (displayX - mThumbOffsetX), (int) (displayY - mThumbOffsetY));
}
- /**
- * Returns true if it has sent DRAG_STARTED broadcast out but has not been sent DRAG_END
- * broadcast.
- */
- boolean isInProgress() {
- return mDragInProgress;
+ private DragEvent obtainDragEndedEvent(float x, float y, boolean includeDragSurface) {
+ return obtainDragEvent(DragEvent.ACTION_DRAG_ENDED, x, y, /* description= */
+ null, /* data= */ null, includeDragSurface, /* includeDragFlags= */
+ true, /* dragAndDropPermissions= */ null, mDragResult);
+ }
+
+ private DragEvent obtainDragEvent(int action, float x, float y, ClipDescription description,
+ ClipData data, boolean includeDragSurface, boolean includeDragFlags,
+ IDragAndDropPermissions dragAndDropPermissions) {
+ return obtainDragEvent(action, x, y, description, data, includeDragSurface,
+ includeDragFlags, dragAndDropPermissions, /* dragResult= */ false);
}
/**
* `x` and `y` here varies between local window coordinate, relative coordinate to another
* window and local display coordinate, all depending on the `action`. Please take a look
* at the callers to determine the type.
- * TODO(b/384845022): Properly document the events sent based on the event type.
+ * - ACTION_DRAG_STARTED: (x, y) is relative coordinate to the target window's origin
+ * (possible to have negative values).
+ * - ACTION_DROP:
+ * --- UnhandledDropEvent: (x, y) is in display space coordinate.
+ * --- DropEvent: (x, y) is in local window coordinate where event is targeted to.
+ * - ACTION_DRAG_ENDED: (x, y) is in local window coordinate where event is targeted to.
*/
private DragEvent obtainDragEvent(int action, float x, float y, ClipDescription description,
ClipData data, boolean includeDragSurface, boolean includeDragFlags,
- IDragAndDropPermissions dragAndDropPermissions) {
+ IDragAndDropPermissions dragAndDropPermissions, boolean dragResult) {
return DragEvent.obtain(action, x, y, mThumbOffsetX, mThumbOffsetY,
mCurrentDisplayContent.getDisplayId(), includeDragFlags ? mFlags : 0,
null /* localState */, description, data,
- includeDragSurface ? mSurfaceControl : null, dragAndDropPermissions,
- false /* result */);
+ includeDragSurface ? mSurfaceControl : null, dragAndDropPermissions, dragResult);
}
private ValueAnimator createReturnAnimationLocked() {
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/NonAppWindowAnimationAdapter.java b/services/core/java/com/android/server/wm/NonAppWindowAnimationAdapter.java
deleted file mode 100644
index 91598c5cc27a..000000000000
--- a/services/core/java/com/android/server/wm/NonAppWindowAnimationAdapter.java
+++ /dev/null
@@ -1,227 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wm;
-
-import static android.view.WindowManager.TRANSIT_OLD_KEYGUARD_GOING_AWAY;
-import static android.view.WindowManager.TRANSIT_OLD_KEYGUARD_GOING_AWAY_ON_WALLPAPER;
-import static android.view.WindowManager.TRANSIT_OLD_TASK_OPEN;
-import static android.view.WindowManager.TRANSIT_OLD_TASK_TO_FRONT;
-import static android.view.WindowManager.TRANSIT_OLD_WALLPAPER_CLOSE;
-
-import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_REMOTE_ANIMATIONS;
-import static com.android.server.wm.AnimationAdapterProto.REMOTE;
-import static com.android.server.wm.RemoteAnimationAdapterWrapperProto.TARGET;
-import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_WINDOW_ANIMATION;
-
-import android.annotation.NonNull;
-import android.graphics.Rect;
-import android.os.SystemClock;
-import android.util.proto.ProtoOutputStream;
-import android.view.RemoteAnimationTarget;
-import android.view.SurfaceControl;
-import android.view.WindowManager;
-
-import com.android.internal.protolog.ProtoLog;
-import com.android.server.policy.WindowManagerPolicy;
-
-import java.io.PrintWriter;
-import java.util.ArrayList;
-
-class NonAppWindowAnimationAdapter implements AnimationAdapter {
-
- private final WindowContainer mWindowContainer;
- private RemoteAnimationTarget mTarget;
- private SurfaceControl mCapturedLeash;
- private SurfaceAnimator.OnAnimationFinishedCallback mCapturedLeashFinishCallback;
- private @SurfaceAnimator.AnimationType int mLastAnimationType;
-
- private long mDurationHint;
- private long mStatusBarTransitionDelay;
-
- @Override
- public boolean getShowWallpaper() {
- return false;
- }
-
- NonAppWindowAnimationAdapter(WindowContainer w, long durationHint,
- long statusBarTransitionDelay) {
- mWindowContainer = w;
- mDurationHint = durationHint;
- mStatusBarTransitionDelay = statusBarTransitionDelay;
- }
-
- static RemoteAnimationTarget[] startNonAppWindowAnimations(WindowManagerService service,
- DisplayContent displayContent, @WindowManager.TransitionOldType int transit,
- long durationHint, long statusBarTransitionDelay,
- ArrayList<NonAppWindowAnimationAdapter> adaptersOut) {
- final ArrayList<RemoteAnimationTarget> targets = new ArrayList<>();
- if (shouldStartNonAppWindowAnimationsForKeyguardExit(transit)) {
- startNonAppWindowAnimationsForKeyguardExit(
- service, durationHint, statusBarTransitionDelay, targets, adaptersOut);
- } else if (shouldAttachNavBarToApp(service, displayContent, transit)) {
- startNavigationBarWindowAnimation(
- displayContent, durationHint, statusBarTransitionDelay, targets,
- adaptersOut);
- }
- return targets.toArray(new RemoteAnimationTarget[targets.size()]);
- }
-
- static boolean shouldStartNonAppWindowAnimationsForKeyguardExit(
- @WindowManager.TransitionOldType int transit) {
- return transit == TRANSIT_OLD_KEYGUARD_GOING_AWAY
- || transit == TRANSIT_OLD_KEYGUARD_GOING_AWAY_ON_WALLPAPER;
- }
-
- static boolean shouldAttachNavBarToApp(WindowManagerService service,
- DisplayContent displayContent, @WindowManager.TransitionOldType int transit) {
- return (transit == TRANSIT_OLD_TASK_OPEN || transit == TRANSIT_OLD_TASK_TO_FRONT
- || transit == TRANSIT_OLD_WALLPAPER_CLOSE)
- && displayContent.getDisplayPolicy().shouldAttachNavBarToAppDuringTransition()
- && displayContent.getAsyncRotationController() == null;
- }
-
- /**
- * Creates and starts remote animations for all the visible non app windows.
- *
- * @return RemoteAnimationTarget[] targets for all the visible non app windows
- */
- private static void startNonAppWindowAnimationsForKeyguardExit(WindowManagerService service,
- long durationHint, long statusBarTransitionDelay,
- ArrayList<RemoteAnimationTarget> targets,
- ArrayList<NonAppWindowAnimationAdapter> adaptersOut) {
-
- final WindowManagerPolicy policy = service.mPolicy;
- service.mRoot.forAllWindows(nonAppWindow -> {
- // Animation on the IME window is controlled via Insets.
- if (nonAppWindow.mActivityRecord == null && nonAppWindow.canBeHiddenByKeyguard()
- && nonAppWindow.wouldBeVisibleIfPolicyIgnored() && !nonAppWindow.isVisible()
- && nonAppWindow != service.mRoot.getCurrentInputMethodWindow()) {
- final NonAppWindowAnimationAdapter nonAppAdapter = new NonAppWindowAnimationAdapter(
- nonAppWindow, durationHint, statusBarTransitionDelay);
- adaptersOut.add(nonAppAdapter);
- nonAppWindow.startAnimation(nonAppWindow.getPendingTransaction(),
- nonAppAdapter, false /* hidden */, ANIMATION_TYPE_WINDOW_ANIMATION);
- targets.add(nonAppAdapter.createRemoteAnimationTarget());
- }
- }, true /* traverseTopToBottom */);
- }
-
- /**
- * Creates and starts remote animation for the navigation bar windows.
- *
- * @return RemoteAnimationTarget[] targets for all the visible non app windows
- */
- private static void startNavigationBarWindowAnimation(DisplayContent displayContent,
- long durationHint, long statusBarTransitionDelay,
- ArrayList<RemoteAnimationTarget> targets,
- ArrayList<NonAppWindowAnimationAdapter> adaptersOut) {
- final WindowState navWindow = displayContent.getDisplayPolicy().getNavigationBar();
- final NonAppWindowAnimationAdapter nonAppAdapter = new NonAppWindowAnimationAdapter(
- navWindow.mToken, durationHint, statusBarTransitionDelay);
- adaptersOut.add(nonAppAdapter);
- navWindow.mToken.startAnimation(navWindow.mToken.getPendingTransaction(),
- nonAppAdapter, false /* hidden */, ANIMATION_TYPE_WINDOW_ANIMATION);
- targets.add(nonAppAdapter.createRemoteAnimationTarget());
- }
-
- /**
- * Create a remote animation target for this animation adapter.
- */
- RemoteAnimationTarget createRemoteAnimationTarget() {
- mTarget = new RemoteAnimationTarget(-1, -1, getLeash(), false,
- new Rect(), null, mWindowContainer.getPrefixOrderIndex(),
- mWindowContainer.getLastSurfacePosition(), mWindowContainer.getBounds(), null,
- mWindowContainer.getWindowConfiguration(), true, null, null, null, false,
- mWindowContainer.getWindowType());
- return mTarget;
- }
-
- @Override
- public void startAnimation(SurfaceControl animationLeash, SurfaceControl.Transaction t,
- int type, @NonNull SurfaceAnimator.OnAnimationFinishedCallback finishCallback) {
- ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS, "startAnimation");
- mCapturedLeash = animationLeash;
- mCapturedLeashFinishCallback = finishCallback;
- mLastAnimationType = type;
- }
-
- /**
- * @return the callback to call to clean up when the animation has finished.
- */
- SurfaceAnimator.OnAnimationFinishedCallback getLeashFinishedCallback() {
- return mCapturedLeashFinishCallback;
- }
-
- /**
- * @return the type of animation.
- */
- @SurfaceAnimator.AnimationType
- int getLastAnimationType() {
- return mLastAnimationType;
- }
-
- WindowContainer getWindowContainer() {
- return mWindowContainer;
- }
-
- @Override
- public long getDurationHint() {
- return mDurationHint;
- }
-
- @Override
- public long getStatusBarTransitionsStartTime() {
- return SystemClock.uptimeMillis() + mStatusBarTransitionDelay;
- }
-
- /**
- * @return the leash for this animation (only valid after the non app window surface animation
- * has started).
- */
- SurfaceControl getLeash() {
- return mCapturedLeash;
- }
-
- @Override
- public void onAnimationCancelled(SurfaceControl animationLeash) {
- ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS, "onAnimationCancelled");
- }
-
- @Override
- public void dump(PrintWriter pw, String prefix) {
- pw.print(prefix);
- pw.print("windowContainer=");
- pw.println(mWindowContainer);
- if (mTarget != null) {
- pw.print(prefix);
- pw.println("Target:");
- mTarget.dump(pw, prefix + " ");
- } else {
- pw.print(prefix);
- pw.println("Target: null");
- }
- }
-
- @Override
- public void dumpDebug(ProtoOutputStream proto) {
- final long token = proto.start(REMOTE);
- if (mTarget != null) {
- mTarget.dumpDebug(proto, TARGET);
- }
- proto.end(token);
- }
-}
diff --git a/services/core/java/com/android/server/wm/RemoteAnimationController.java b/services/core/java/com/android/server/wm/RemoteAnimationController.java
deleted file mode 100644
index b3b2c57550e4..000000000000
--- a/services/core/java/com/android/server/wm/RemoteAnimationController.java
+++ /dev/null
@@ -1,636 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.server.wm;
-
-import static android.view.WindowManager.TRANSIT_OLD_KEYGUARD_UNOCCLUDE;
-
-import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_REMOTE_ANIMATIONS;
-import static com.android.server.wm.AnimationAdapterProto.REMOTE;
-import static com.android.server.wm.RemoteAnimationAdapterWrapperProto.TARGET;
-import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
-import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
-
-import android.annotation.ColorInt;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.graphics.Point;
-import android.graphics.Rect;
-import android.os.Binder;
-import android.os.Handler;
-import android.os.IBinder.DeathRecipient;
-import android.os.RemoteException;
-import android.os.SystemClock;
-import android.util.Slog;
-import android.util.proto.ProtoOutputStream;
-import android.view.IRemoteAnimationFinishedCallback;
-import android.view.RemoteAnimationAdapter;
-import android.view.RemoteAnimationTarget;
-import android.view.SurfaceControl;
-import android.view.SurfaceControl.Transaction;
-import android.view.WindowManager;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.protolog.ProtoLog;
-import com.android.internal.protolog.common.LogLevel;
-import com.android.internal.util.FastPrintWriter;
-import com.android.server.wm.SurfaceAnimator.AnimationType;
-import com.android.server.wm.SurfaceAnimator.OnAnimationFinishedCallback;
-
-import java.io.PrintWriter;
-import java.io.StringWriter;
-import java.util.ArrayList;
-import java.util.function.Consumer;
-
-/**
- * Helper class to run app animations in a remote process.
- */
-class RemoteAnimationController implements DeathRecipient {
- private static final String TAG = TAG_WITH_CLASS_NAME
- ? "RemoteAnimationController" : TAG_WM;
- private static final long TIMEOUT_MS = 10000;
-
- private final WindowManagerService mService;
- private final DisplayContent mDisplayContent;
- private final RemoteAnimationAdapter mRemoteAnimationAdapter;
- private final ArrayList<RemoteAnimationRecord> mPendingAnimations = new ArrayList<>();
- private final ArrayList<WallpaperAnimationAdapter> mPendingWallpaperAnimations =
- new ArrayList<>();
- @VisibleForTesting
- final ArrayList<NonAppWindowAnimationAdapter> mPendingNonAppAnimations = new ArrayList<>();
- private final Handler mHandler;
- private final Runnable mTimeoutRunnable = () -> cancelAnimation("timeoutRunnable");
- private boolean mIsFinishing;
-
- private FinishedCallback mFinishedCallback;
- private final boolean mIsActivityEmbedding;
- private boolean mCanceled;
- private boolean mLinkedToDeathOfRunner;
- @Nullable
- private Runnable mOnRemoteAnimationReady;
-
- RemoteAnimationController(WindowManagerService service, DisplayContent displayContent,
- RemoteAnimationAdapter remoteAnimationAdapter, Handler handler,
- boolean isActivityEmbedding) {
- mService = service;
- mDisplayContent = displayContent;
- mRemoteAnimationAdapter = remoteAnimationAdapter;
- mHandler = handler;
- mIsActivityEmbedding = isActivityEmbedding;
- }
-
- /**
- * Creates an animation record for each individual {@link WindowContainer}.
- *
- * @param windowContainer The windows to animate.
- * @param position The position app bounds relative to its parent.
- * @param localBounds The bounds of the app relative to its parent.
- * @param endBounds The end bounds after the transition, in screen coordinates.
- * @param startBounds The start bounds before the transition, in screen coordinates.
- * @param showBackdrop To show background behind a window during animation.
- * @return The record representing animation(s) to run on the app.
- */
- RemoteAnimationRecord createRemoteAnimationRecord(WindowContainer windowContainer,
- Point position, Rect localBounds, Rect endBounds, Rect startBounds,
- boolean showBackdrop) {
- return createRemoteAnimationRecord(windowContainer, position, localBounds, endBounds,
- startBounds, showBackdrop, startBounds != null /* shouldCreateSnapshot */);
- }
-
- /**
- * Creates an animation record for each individual {@link WindowContainer}.
- *
- * @param windowContainer The windows to animate.
- * @param position The position app bounds relative to its parent.
- * @param localBounds The bounds of the app relative to its parent.
- * @param endBounds The end bounds after the transition, in screen coordinates.
- * @param startBounds The start bounds before the transition, in screen coordinates.
- * @param showBackdrop To show background behind a window during animation.
- * @param shouldCreateSnapshot Whether this target should create a snapshot animation.
- * @return The record representing animation(s) to run on the app.
- */
- RemoteAnimationRecord createRemoteAnimationRecord(WindowContainer windowContainer,
- Point position, Rect localBounds, Rect endBounds, Rect startBounds,
- boolean showBackdrop, boolean shouldCreateSnapshot) {
- ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS, "createAnimationAdapter(): container=%s",
- windowContainer);
- final RemoteAnimationRecord adapters = new RemoteAnimationRecord(windowContainer, position,
- localBounds, endBounds, startBounds, showBackdrop, shouldCreateSnapshot);
- mPendingAnimations.add(adapters);
- return adapters;
- }
-
- /** Sets callback to run before starting remote animation. */
- void setOnRemoteAnimationReady(@Nullable Runnable onRemoteAnimationReady) {
- mOnRemoteAnimationReady = onRemoteAnimationReady;
- }
-
- /**
- * We use isFromActivityEmbedding() in the server process to tell if we're running an
- * Activity Embedding type remote animation, where animations are driven by the client.
- * This is currently supporting features like showBackdrop where we need to load App XML.
- */
- public boolean isFromActivityEmbedding() {
- return mIsActivityEmbedding;
- }
-
- /**
- * Called when the transition is ready to be started, and all leashes have been set up.
- */
- void goodToGo(@WindowManager.TransitionOldType int transit) {
- ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS, "goodToGo()");
- if (mCanceled) {
- ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS,
- "goodToGo(): Animation canceled already");
- onAnimationFinished();
- invokeAnimationCancelled("already_cancelled");
- return;
- }
-
- // Scale the timeout with the animator scale the controlling app is using.
- mHandler.postDelayed(mTimeoutRunnable,
- (long) (TIMEOUT_MS * mService.getCurrentAnimatorScale()));
- mFinishedCallback = new FinishedCallback(this);
-
- // Create the app targets
- final RemoteAnimationTarget[] appTargets = createAppAnimations();
- if (appTargets.length == 0 && !AppTransition.isKeyguardOccludeTransitOld(transit)) {
- // Keyguard occlude transition can be executed before the occluding activity becomes
- // visible. Even in this case, KeyguardService expects to receive binder call, so we
- // don't cancel remote animation.
- ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS,
- "goodToGo(): No apps to animate, mPendingAnimations=%d",
- mPendingAnimations.size());
- onAnimationFinished();
- invokeAnimationCancelled("no_app_targets");
- return;
- }
-
- if (mOnRemoteAnimationReady != null) {
- mOnRemoteAnimationReady.run();
- mOnRemoteAnimationReady = null;
- }
-
- // Create the remote wallpaper animation targets (if any)
- final RemoteAnimationTarget[] wallpaperTargets = createWallpaperAnimations();
-
- // Create the remote non app animation targets (if any)
- final RemoteAnimationTarget[] nonAppTargets = createNonAppWindowAnimations(transit);
-
- mService.mAnimator.addAfterPrepareSurfacesRunnable(() -> {
- try {
- linkToDeathOfRunner();
- ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS, "goodToGo(): onAnimationStart,"
- + " transit=%s, apps=%d, wallpapers=%d, nonApps=%d",
- AppTransition.appTransitionOldToString(transit), appTargets.length,
- wallpaperTargets.length, nonAppTargets.length);
- if (AppTransition.isKeyguardOccludeTransitOld(transit)) {
- EventLogTags.writeWmSetKeyguardOccluded(
- transit == TRANSIT_OLD_KEYGUARD_UNOCCLUDE ? 0 : 1,
- 1 /* animate */,
- transit,
- "onAnimationStart");
- }
- mRemoteAnimationAdapter.getRunner().onAnimationStart(transit, appTargets,
- wallpaperTargets, nonAppTargets, mFinishedCallback);
- } catch (RemoteException e) {
- Slog.e(TAG, "Failed to start remote animation", e);
- onAnimationFinished();
- }
- if (ProtoLog.isEnabled(WM_DEBUG_REMOTE_ANIMATIONS, LogLevel.DEBUG)) {
- ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS, "startAnimation(): Notify animation start:");
- writeStartDebugStatement();
- }
- });
- setRunningRemoteAnimation(true);
- }
-
- void cancelAnimation(String reason) {
- ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS, "cancelAnimation(): reason=%s", reason);
- synchronized (mService.getWindowManagerLock()) {
- if (mCanceled) {
- return;
- }
- mCanceled = true;
- }
- onAnimationFinished();
- invokeAnimationCancelled(reason);
- }
-
- private void writeStartDebugStatement() {
- ProtoLog.i(WM_DEBUG_REMOTE_ANIMATIONS, "Starting remote animation");
- final StringWriter sw = new StringWriter();
- final FastPrintWriter pw = new FastPrintWriter(sw);
- for (int i = mPendingAnimations.size() - 1; i >= 0; i--) {
- mPendingAnimations.get(i).mAdapter.dump(pw, "");
- }
- pw.close();
- ProtoLog.i(WM_DEBUG_REMOTE_ANIMATIONS, "%s", sw.toString());
- }
-
- private RemoteAnimationTarget[] createAppAnimations() {
- ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS, "createAppAnimations()");
- final ArrayList<RemoteAnimationTarget> targets = new ArrayList<>();
- for (int i = mPendingAnimations.size() - 1; i >= 0; i--) {
- final RemoteAnimationRecord wrappers = mPendingAnimations.get(i);
- final RemoteAnimationTarget target = wrappers.createRemoteAnimationTarget();
- if (target != null) {
- ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS, "\tAdd container=%s",
- wrappers.mWindowContainer);
- targets.add(target);
- } else {
- ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS, "\tRemove container=%s",
- wrappers.mWindowContainer);
-
- // We can't really start an animation but we still need to make sure to finish the
- // pending animation that was started by SurfaceAnimator
- if (wrappers.mAdapter != null
- && wrappers.mAdapter.mCapturedFinishCallback != null) {
- wrappers.mAdapter.mCapturedFinishCallback
- .onAnimationFinished(wrappers.mAdapter.mAnimationType,
- wrappers.mAdapter);
- }
- if (wrappers.mThumbnailAdapter != null
- && wrappers.mThumbnailAdapter.mCapturedFinishCallback != null) {
- wrappers.mThumbnailAdapter.mCapturedFinishCallback
- .onAnimationFinished(wrappers.mThumbnailAdapter.mAnimationType,
- wrappers.mThumbnailAdapter);
- }
- mPendingAnimations.remove(i);
- }
- }
- return targets.toArray(new RemoteAnimationTarget[targets.size()]);
- }
-
- private RemoteAnimationTarget[] createWallpaperAnimations() {
- ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS, "createWallpaperAnimations()");
- return WallpaperAnimationAdapter.startWallpaperAnimations(mDisplayContent,
- mRemoteAnimationAdapter.getDuration(),
- mRemoteAnimationAdapter.getStatusBarTransitionDelay(),
- adapter -> {
- synchronized (mService.mGlobalLock) {
- // If the wallpaper animation is canceled, continue with the app animation
- mPendingWallpaperAnimations.remove(adapter);
- }
- }, mPendingWallpaperAnimations);
- }
-
- private RemoteAnimationTarget[] createNonAppWindowAnimations(
- @WindowManager.TransitionOldType int transit) {
- ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS, "createNonAppWindowAnimations()");
- return NonAppWindowAnimationAdapter.startNonAppWindowAnimations(mService,
- mDisplayContent,
- transit,
- mRemoteAnimationAdapter.getDuration(),
- mRemoteAnimationAdapter.getStatusBarTransitionDelay(),
- mPendingNonAppAnimations);
- }
-
- private void onAnimationFinished() {
- ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS, "onAnimationFinished(): mPendingAnimations=%d",
- mPendingAnimations.size());
- mHandler.removeCallbacks(mTimeoutRunnable);
- synchronized (mService.mGlobalLock) {
- mIsFinishing = true;
- unlinkToDeathOfRunner();
- releaseFinishedCallback();
- try {
- ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS,
- "onAnimationFinished(): Notify animation finished:");
- for (int i = mPendingAnimations.size() - 1; i >= 0; i--) {
- final RemoteAnimationRecord adapters = mPendingAnimations.get(i);
- if (adapters.mAdapter != null) {
- adapters.mAdapter.mCapturedFinishCallback
- .onAnimationFinished(adapters.mAdapter.mAnimationType,
- adapters.mAdapter);
- }
- if (adapters.mThumbnailAdapter != null) {
- adapters.mThumbnailAdapter.mCapturedFinishCallback
- .onAnimationFinished(adapters.mThumbnailAdapter.mAnimationType,
- adapters.mThumbnailAdapter);
- }
- mPendingAnimations.remove(i);
- ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS, "\tcontainer=%s",
- adapters.mWindowContainer);
- }
-
- for (int i = mPendingWallpaperAnimations.size() - 1; i >= 0; i--) {
- final WallpaperAnimationAdapter adapter = mPendingWallpaperAnimations.get(i);
- adapter.getLeashFinishedCallback().onAnimationFinished(
- adapter.getLastAnimationType(), adapter);
- mPendingWallpaperAnimations.remove(i);
- ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS, "\twallpaper=%s", adapter.getToken());
- }
-
- for (int i = mPendingNonAppAnimations.size() - 1; i >= 0; i--) {
- final NonAppWindowAnimationAdapter adapter = mPendingNonAppAnimations.get(i);
- adapter.getLeashFinishedCallback().onAnimationFinished(
- adapter.getLastAnimationType(), adapter);
- mPendingNonAppAnimations.remove(i);
- ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS, "\tnonApp=%s",
- adapter.getWindowContainer());
- }
- } catch (Exception e) {
- Slog.e(TAG, "Failed to finish remote animation", e);
- throw e;
- } finally {
- mIsFinishing = false;
- }
- // Reset input for all activities when the remote animation is finished.
- final Consumer<ActivityRecord> updateActivities =
- activity -> activity.setDropInputForAnimation(false);
- mDisplayContent.forAllActivities(updateActivities);
- }
- setRunningRemoteAnimation(false);
- ProtoLog.i(WM_DEBUG_REMOTE_ANIMATIONS, "Finishing remote animation");
- }
-
- private void invokeAnimationCancelled(String reason) {
- ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS, "cancelAnimation(): reason=%s", reason);
- try {
- mRemoteAnimationAdapter.getRunner().onAnimationCancelled();
- } catch (RemoteException e) {
- Slog.e(TAG, "Failed to notify cancel", e);
- }
- mOnRemoteAnimationReady = null;
- }
-
- private void releaseFinishedCallback() {
- if (mFinishedCallback != null) {
- mFinishedCallback.release();
- mFinishedCallback = null;
- }
- }
-
- private void setRunningRemoteAnimation(boolean running) {
- final int pid = mRemoteAnimationAdapter.getCallingPid();
- final int uid = mRemoteAnimationAdapter.getCallingUid();
-
- if (pid == 0) {
- throw new RuntimeException("Calling pid of remote animation was null");
- }
- final WindowProcessController wpc = mService.mAtmService.getProcessController(pid, uid);
- if (wpc == null) {
- Slog.w(TAG, "Unable to find process with pid=" + pid + " uid=" + uid);
- return;
- }
- wpc.setRunningRemoteAnimation(running);
- }
-
- private void linkToDeathOfRunner() throws RemoteException {
- if (!mLinkedToDeathOfRunner) {
- mRemoteAnimationAdapter.getRunner().asBinder().linkToDeath(this, 0);
- mLinkedToDeathOfRunner = true;
- }
- }
-
- private void unlinkToDeathOfRunner() {
- if (mLinkedToDeathOfRunner) {
- mRemoteAnimationAdapter.getRunner().asBinder().unlinkToDeath(this, 0);
- mLinkedToDeathOfRunner = false;
- }
- }
-
- @Override
- public void binderDied() {
- cancelAnimation("binderDied");
- }
-
- private static final class FinishedCallback extends IRemoteAnimationFinishedCallback.Stub {
-
- RemoteAnimationController mOuter;
-
- FinishedCallback(RemoteAnimationController outer) {
- mOuter = outer;
- }
-
- @Override
- public void onAnimationFinished() throws RemoteException {
- ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS, "app-onAnimationFinished(): mOuter=%s", mOuter);
- final long token = Binder.clearCallingIdentity();
- try {
- if (mOuter != null) {
- mOuter.onAnimationFinished();
-
- // In case the client holds on to the finish callback, make sure we don't leak
- // RemoteAnimationController which in turn would leak the runner on the client.
- mOuter = null;
- }
- } finally {
- Binder.restoreCallingIdentity(token);
- }
- }
-
- /**
- * Marks this callback as not be used anymore by releasing the reference to the outer class
- * to prevent memory leak.
- */
- void release() {
- ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS, "app-release(): mOuter=%s", mOuter);
- mOuter = null;
- }
- };
-
- /**
- * Contains information about a remote-animation for one WindowContainer. This keeps track of,
- * potentially, multiple animating surfaces (AdapterWrappers) associated with one
- * Window/Transition. For example, a change transition has an adapter controller for the
- * main window and an adapter controlling the start-state snapshot.
- * <p>
- * This can be thought of as a bridge between the information that the remote animator sees (via
- * {@link RemoteAnimationTarget}) and what the server sees (the
- * {@link RemoteAnimationAdapterWrapper}(s) interfacing with the moving surfaces).
- */
- public class RemoteAnimationRecord {
- RemoteAnimationAdapterWrapper mAdapter;
- RemoteAnimationAdapterWrapper mThumbnailAdapter = null;
- RemoteAnimationTarget mTarget;
- final WindowContainer mWindowContainer;
- final Rect mStartBounds;
- final boolean mShowBackdrop;
- @ColorInt int mBackdropColor = 0;
- private @RemoteAnimationTarget.Mode int mMode = RemoteAnimationTarget.MODE_CHANGING;
-
- RemoteAnimationRecord(WindowContainer windowContainer, Point endPos, Rect localBounds,
- Rect endBounds, @Nullable Rect startBounds, boolean showBackdrop,
- boolean shouldCreateSnapshot) {
- mWindowContainer = windowContainer;
- mShowBackdrop = showBackdrop;
- if (startBounds != null) {
- mStartBounds = new Rect(startBounds);
- mAdapter = new RemoteAnimationAdapterWrapper(this, endPos, localBounds, endBounds,
- mStartBounds, mShowBackdrop);
- if (shouldCreateSnapshot && mRemoteAnimationAdapter.getChangeNeedsSnapshot()) {
- final Rect thumbnailLocalBounds = new Rect(startBounds);
- thumbnailLocalBounds.offsetTo(0, 0);
- // Snapshot is located at (0,0) of the animation leash. It doesn't have size
- // change, so the startBounds is its end bounds, and no start bounds for it.
- mThumbnailAdapter = new RemoteAnimationAdapterWrapper(this, new Point(0, 0),
- thumbnailLocalBounds, startBounds, new Rect(), mShowBackdrop);
- }
- } else {
- mAdapter = new RemoteAnimationAdapterWrapper(this, endPos, localBounds, endBounds,
- new Rect(), mShowBackdrop);
- mStartBounds = null;
- }
- }
-
- void setBackDropColor(@ColorInt int backdropColor) {
- mBackdropColor = backdropColor;
- }
-
- RemoteAnimationTarget createRemoteAnimationTarget() {
- if (mAdapter == null
- || mAdapter.mCapturedFinishCallback == null
- || mAdapter.mCapturedLeash == null) {
- return null;
- }
- mTarget = mWindowContainer.createRemoteAnimationTarget(this);
- return mTarget;
- }
-
- void setMode(@RemoteAnimationTarget.Mode int mode) {
- mMode = mode;
- }
-
- int getMode() {
- return mMode;
- }
-
- /** Whether its parent is also an animation target in the same transition. */
- boolean hasAnimatingParent() {
- // mOpeningApps and mClosingApps are only activities, so only need to check
- // mChangingContainers.
- for (int i = mDisplayContent.mChangingContainers.size() - 1; i >= 0; i--) {
- if (mWindowContainer.isDescendantOf(
- mDisplayContent.mChangingContainers.valueAt(i))) {
- return true;
- }
- }
- return false;
- }
- }
-
- class RemoteAnimationAdapterWrapper implements AnimationAdapter {
- private final RemoteAnimationRecord mRecord;
- SurfaceControl mCapturedLeash;
- private OnAnimationFinishedCallback mCapturedFinishCallback;
- private @AnimationType int mAnimationType;
- final Point mPosition = new Point();
- final Rect mLocalBounds;
- final Rect mEndBounds = new Rect();
- final Rect mStartBounds = new Rect();
- final boolean mShowBackdrop;
-
- RemoteAnimationAdapterWrapper(RemoteAnimationRecord record, Point position,
- Rect localBounds, Rect endBounds, Rect startBounds, boolean showBackdrop) {
- mRecord = record;
- mPosition.set(position.x, position.y);
- mLocalBounds = localBounds;
- mEndBounds.set(endBounds);
- mStartBounds.set(startBounds);
- mShowBackdrop = showBackdrop;
- }
-
- @Override
- @ColorInt
- public int getBackgroundColor() {
- return mRecord.mBackdropColor;
- }
-
- @Override
- public boolean getShowBackground() {
- return mShowBackdrop;
- }
-
- @Override
- public boolean getShowWallpaper() {
- return false;
- }
-
- @Override
- public void startAnimation(SurfaceControl animationLeash, Transaction t,
- @AnimationType int type, @NonNull OnAnimationFinishedCallback finishCallback) {
- ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS, "startAnimation");
-
- if (mStartBounds.isEmpty()) {
- // Restore position and stack crop until client has a chance to modify it.
- t.setPosition(animationLeash, mPosition.x, mPosition.y);
- t.setWindowCrop(animationLeash, mEndBounds.width(), mEndBounds.height());
- } else {
- // Offset the change animation leash to the relative start position in parent.
- // (mPosition) is the relative end position in parent container.
- // (mStartBounds - mEndBounds) is the position difference between start and end.
- // (mPosition + mStartBounds - mEndBounds) will be the relative start position.
- t.setPosition(animationLeash, mPosition.x + mStartBounds.left - mEndBounds.left,
- mPosition.y + mStartBounds.top - mEndBounds.top);
- t.setWindowCrop(animationLeash, mStartBounds.width(), mStartBounds.height());
- }
- mCapturedLeash = animationLeash;
- mCapturedFinishCallback = finishCallback;
- mAnimationType = type;
- }
-
- @Override
- public void onAnimationCancelled(SurfaceControl animationLeash) {
- if (mIsFinishing) {
- return;
- }
- if (mRecord.mAdapter == this) {
- mRecord.mAdapter = null;
- } else {
- mRecord.mThumbnailAdapter = null;
- }
- if (mRecord.mAdapter == null && mRecord.mThumbnailAdapter == null) {
- mPendingAnimations.remove(mRecord);
- }
- if (mPendingAnimations.isEmpty()) {
- cancelAnimation("allAppAnimationsCanceled");
- }
- }
-
- @Override
- public long getDurationHint() {
- return mRemoteAnimationAdapter.getDuration();
- }
-
- @Override
- public long getStatusBarTransitionsStartTime() {
- return SystemClock.uptimeMillis()
- + mRemoteAnimationAdapter.getStatusBarTransitionDelay();
- }
-
- @Override
- public void dump(PrintWriter pw, String prefix) {
- pw.print(prefix); pw.print("container="); pw.println(mRecord.mWindowContainer);
- if (mRecord.mTarget != null) {
- pw.print(prefix); pw.println("Target:");
- mRecord.mTarget.dump(pw, prefix + " ");
- } else {
- pw.print(prefix); pw.println("Target: null");
- }
- }
-
- @Override
- public void dumpDebug(ProtoOutputStream proto) {
- final long token = proto.start(REMOTE);
- if (mRecord.mTarget != null) {
- mRecord.mTarget.dumpDebug(proto, TARGET);
- }
- proto.end(token);
- }
- }
-}
diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java
index ae3a015a690d..1966ecf57c73 100644
--- a/services/core/java/com/android/server/wm/TaskDisplayArea.java
+++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java
@@ -50,7 +50,6 @@ import android.graphics.Color;
import android.os.UserHandle;
import android.util.IntArray;
import android.util.Slog;
-import android.view.RemoteAnimationTarget;
import android.view.SurfaceControl;
import android.view.WindowManager;
@@ -776,13 +775,6 @@ final class TaskDisplayArea extends DisplayArea<WindowContainer> {
return needsZBoost[0];
}
- @Override
- RemoteAnimationTarget createRemoteAnimationTarget(
- RemoteAnimationController.RemoteAnimationRecord record) {
- final ActivityRecord activity = getTopMostActivity();
- return activity != null ? activity.createRemoteAnimationTarget(record) : null;
- }
-
void setBackgroundColor(@ColorInt int colorInt) {
setBackgroundColor(colorInt, false /* restore */);
}
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 97a1a34336e9..74059c1cc9b1 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -99,7 +99,6 @@ import android.util.DisplayMetrics;
import android.util.Slog;
import android.util.proto.ProtoOutputStream;
import android.view.DisplayInfo;
-import android.view.RemoteAnimationTarget;
import android.view.SurfaceControl;
import android.window.ITaskFragmentOrganizer;
import android.window.TaskFragmentAnimationParams;
@@ -2306,18 +2305,6 @@ class TaskFragment extends WindowContainer<WindowContainer> {
}
@Override
- RemoteAnimationTarget createRemoteAnimationTarget(
- RemoteAnimationController.RemoteAnimationRecord record) {
- final ActivityRecord activity = record.getMode() == RemoteAnimationTarget.MODE_OPENING
- // There may be a launching (e.g. trampoline or embedded) activity without a window
- // on top of the existing task which is moving to front. Exclude finishing activity
- // so the window of next activity can be chosen to create the animation target.
- ? getActivity(r -> !r.finishing && r.hasChild())
- : getTopMostActivity();
- return activity != null ? activity.createRemoteAnimationTarget(record) : null;
- }
-
- @Override
boolean canCreateRemoteAnimationTarget() {
return true;
}
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/WallpaperAnimationAdapter.java b/services/core/java/com/android/server/wm/WallpaperAnimationAdapter.java
deleted file mode 100644
index c3e85b1f8662..000000000000
--- a/services/core/java/com/android/server/wm/WallpaperAnimationAdapter.java
+++ /dev/null
@@ -1,191 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.server.wm;
-
-import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_REMOTE_ANIMATIONS;
-import static com.android.server.wm.AnimationAdapterProto.REMOTE;
-import static com.android.server.wm.RemoteAnimationAdapterWrapperProto.TARGET;
-import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_WINDOW_ANIMATION;
-
-import android.annotation.NonNull;
-import android.graphics.Point;
-import android.os.SystemClock;
-import android.util.proto.ProtoOutputStream;
-import android.view.RemoteAnimationTarget;
-import android.view.SurfaceControl;
-
-import com.android.internal.protolog.ProtoLog;
-import com.android.server.wm.SurfaceAnimator.AnimationType;
-
-import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.function.Consumer;
-
-/**
- * An animation adapter for wallpaper windows.
- */
-class WallpaperAnimationAdapter implements AnimationAdapter {
- private static final String TAG = "WallpaperAnimationAdapter";
-
- private final WallpaperWindowToken mWallpaperToken;
- private SurfaceControl mCapturedLeash;
- private SurfaceAnimator.OnAnimationFinishedCallback mCapturedLeashFinishCallback;
- private @AnimationType int mLastAnimationType;
-
- private long mDurationHint;
- private long mStatusBarTransitionDelay;
-
- private Consumer<WallpaperAnimationAdapter> mAnimationCanceledRunnable;
- private RemoteAnimationTarget mTarget;
-
- WallpaperAnimationAdapter(WallpaperWindowToken wallpaperToken,
- long durationHint, long statusBarTransitionDelay,
- Consumer<WallpaperAnimationAdapter> animationCanceledRunnable) {
- mWallpaperToken = wallpaperToken;
- mDurationHint = durationHint;
- mStatusBarTransitionDelay = statusBarTransitionDelay;
- mAnimationCanceledRunnable = animationCanceledRunnable;
- }
-
- /**
- * Creates and starts remote animations for all the visible wallpaper windows.
- *
- * @return RemoteAnimationTarget[] targets for all the visible wallpaper windows
- */
- public static RemoteAnimationTarget[] startWallpaperAnimations(DisplayContent displayContent,
- long durationHint, long statusBarTransitionDelay,
- Consumer<WallpaperAnimationAdapter> animationCanceledRunnable,
- ArrayList<WallpaperAnimationAdapter> adaptersOut) {
- if (!shouldStartWallpaperAnimation(displayContent)) {
- ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS,
- "\tWallpaper of display=%s is not visible", displayContent);
- return new RemoteAnimationTarget[0];
- }
- final ArrayList<RemoteAnimationTarget> targets = new ArrayList<>();
- displayContent.forAllWallpaperWindows(wallpaperWindow -> {
- final WallpaperAnimationAdapter wallpaperAdapter = new WallpaperAnimationAdapter(
- wallpaperWindow, durationHint, statusBarTransitionDelay,
- animationCanceledRunnable);
- wallpaperWindow.startAnimation(wallpaperWindow.getPendingTransaction(),
- wallpaperAdapter, false /* hidden */, ANIMATION_TYPE_WINDOW_ANIMATION);
- targets.add(wallpaperAdapter.createRemoteAnimationTarget());
- adaptersOut.add(wallpaperAdapter);
- });
- return targets.toArray(new RemoteAnimationTarget[targets.size()]);
- }
-
- static boolean shouldStartWallpaperAnimation(DisplayContent displayContent) {
- return displayContent.mWallpaperController.isWallpaperVisible();
- }
-
- /**
- * Create a remote animation target for this animation adapter.
- */
- RemoteAnimationTarget createRemoteAnimationTarget() {
- mTarget = new RemoteAnimationTarget(-1, -1, getLeash(), false, null, null,
- mWallpaperToken.getPrefixOrderIndex(), new Point(), null, null,
- mWallpaperToken.getWindowConfiguration(), true, null, null, null, false);
- return mTarget;
- }
-
- /**
- * @return the leash for this animation (only valid after the wallpaper window surface animation
- * has started).
- */
- SurfaceControl getLeash() {
- return mCapturedLeash;
- }
-
- /**
- * @return the callback to call to clean up when the animation has finished.
- */
- SurfaceAnimator.OnAnimationFinishedCallback getLeashFinishedCallback() {
- return mCapturedLeashFinishCallback;
- }
-
- /**
- * @return the type of animation.
- */
- @AnimationType int getLastAnimationType() {
- return mLastAnimationType;
- }
-
- /**
- * @return the wallpaper window
- */
- WallpaperWindowToken getToken() {
- return mWallpaperToken;
- }
-
- @Override
- public boolean getShowWallpaper() {
- // Not used
- return false;
- }
-
- @Override
- public void startAnimation(SurfaceControl animationLeash, SurfaceControl.Transaction t,
- @AnimationType int type,
- @NonNull SurfaceAnimator.OnAnimationFinishedCallback finishCallback) {
- ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS, "startAnimation");
-
- // Restore z-layering until client has a chance to modify it.
- t.setLayer(animationLeash, mWallpaperToken.getPrefixOrderIndex());
- mCapturedLeash = animationLeash;
- mCapturedLeashFinishCallback = finishCallback;
- mLastAnimationType = type;
- }
-
- @Override
- public void onAnimationCancelled(SurfaceControl animationLeash) {
- ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS, "onAnimationCancelled");
- mAnimationCanceledRunnable.accept(this);
- }
-
- @Override
- public long getDurationHint() {
- return mDurationHint;
- }
-
- @Override
- public long getStatusBarTransitionsStartTime() {
- return SystemClock.uptimeMillis() + mStatusBarTransitionDelay;
- }
-
- @Override
- public void dump(PrintWriter pw, String prefix) {
- pw.print(prefix);
- pw.print("token=");
- pw.println(mWallpaperToken);
- if (mTarget != null) {
- pw.print(prefix);
- pw.println("Target:");
- mTarget.dump(pw, prefix + " ");
- } else {
- pw.print(prefix);
- pw.println("Target: null");
- }
- }
-
- @Override
- public void dumpDebug(ProtoOutputStream proto) {
- final long token = proto.start(REMOTE);
- if (mTarget != null) {
- mTarget.dumpDebug(proto, TARGET);
- }
- proto.end(token);
- }
-}
diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java
index 70948e1264c4..c1ef208d1d4d 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
@@ -692,8 +684,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 +727,7 @@ class WallpaperController {
if (mFindResults.wallpaperTarget == null && mFindResults.useTopWallpaperAsTarget) {
mFindResults.setWallpaperTarget(
- mFindResults.getTopWallpaper(mService.mFlags.mAodTransition
- ? mDisplayContent.isKeyguardLockedOrAodShowing()
- : mDisplayContent.isKeyguardLocked()));
+ mFindResults.getTopWallpaper(mDisplayContent.isKeyguardLocked()));
}
}
@@ -910,17 +899,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/WallpaperWindowToken.java b/services/core/java/com/android/server/wm/WallpaperWindowToken.java
index 3b79c54f1c73..7c88abcec7ec 100644
--- a/services/core/java/com/android/server/wm/WallpaperWindowToken.java
+++ b/services/core/java/com/android/server/wm/WallpaperWindowToken.java
@@ -34,7 +34,6 @@ import android.util.SparseArray;
import com.android.internal.protolog.ProtoLog;
import java.io.PrintWriter;
-import java.util.function.Consumer;
/**
* A token that represents a set of wallpaper windows.
@@ -250,11 +249,6 @@ class WallpaperWindowToken extends WindowToken {
}
@Override
- void forAllWallpaperWindows(Consumer<WallpaperWindowToken> callback) {
- callback.accept(this);
- }
-
- @Override
boolean fillsParent() {
return true;
}
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 7af542f10127..95cdf46ea488 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -97,7 +97,6 @@ import android.view.InsetsSource;
import android.view.InsetsState;
import android.view.MagnificationSpec;
import android.view.RemoteAnimationDefinition;
-import android.view.RemoteAnimationTarget;
import android.view.Surface;
import android.view.SurfaceControl;
import android.view.SurfaceControl.Builder;
@@ -303,7 +302,6 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
* This gets used during some open/close transitions as well as during a change transition
* where it represents the starting-state snapshot.
*/
- WindowContainerThumbnail mThumbnail;
final Point mTmpPoint = new Point();
protected final Rect mTmpRect = new Rect();
final Rect mTmpPrevBounds = new Rect();
@@ -2097,12 +2095,6 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
return getActivity((r) -> !r.finishing && !r.isTaskOverlay());
}
- void forAllWallpaperWindows(Consumer<WallpaperWindowToken> callback) {
- for (int i = mChildren.size() - 1; i >= 0; --i) {
- mChildren.get(i).forAllWallpaperWindows(callback);
- }
- }
-
/**
* Calls the given {@param callback} for all tasks in depth-first top-down z-order at or below
* this container.
@@ -3174,61 +3166,10 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
getAnimationPosition(mTmpPoint);
mTmpRect.offsetTo(0, 0);
- final AppTransition appTransition = getDisplayContent().mAppTransition;
- final RemoteAnimationController controller = appTransition.getRemoteAnimationController();
final boolean isChanging = AppTransition.isChangeTransitOld(transit) && enter
&& isChangingAppTransition();
- if (controller != null) {
- // Here we load App XML in order to read com.android.R.styleable#Animation_showBackdrop.
- boolean showBackdrop = false;
- // Optionally set backdrop color if App explicitly provides it through
- // {@link Activity#overridePendingTransition(int, int, int)}.
- @ColorInt int backdropColor = 0;
- if (controller.isFromActivityEmbedding()) {
- if (isChanging) {
- // When there are more than one changing containers, it may leave part of the
- // screen empty. Show background color to cover that.
- showBackdrop = getDisplayContent().mChangingContainers.size() > 1;
- backdropColor = appTransition.getNextAppTransitionBackgroundColor();
- } else {
- // Check whether the app has requested to show backdrop for open/close
- // transition.
- final Animation a = appTransition.getNextAppRequestedAnimation(enter);
- if (a != null) {
- showBackdrop = a.getShowBackdrop();
- backdropColor = a.getBackdropColor();
- }
- }
- }
- final Rect localBounds = new Rect(mTmpRect);
- localBounds.offsetTo(mTmpPoint.x, mTmpPoint.y);
- final RemoteAnimationController.RemoteAnimationRecord adapters;
- if (!isChanging && !enter && isClosingWhenResizing()) {
- // Container that is closing while resizing. Pass in the closing start bounds, so
- // the animation can start with the correct bounds, there won't be a snapshot.
- // Cleanup the mClosingChangingContainers so that when the animation is finished, it
- // will reset the surface.
- final Rect closingStartBounds = getDisplayContent().mClosingChangingContainers
- .remove(this);
- adapters = controller.createRemoteAnimationRecord(
- this, mTmpPoint, localBounds, screenBounds, closingStartBounds,
- showBackdrop, false /* shouldCreateSnapshot */);
- } else {
- final Rect startBounds = null;
- adapters = controller.createRemoteAnimationRecord(
- this, mTmpPoint, localBounds, screenBounds, startBounds, showBackdrop);
- }
- if (backdropColor != 0) {
- adapters.setBackDropColor(backdropColor);
- }
- if (!isChanging) {
- adapters.setMode(enter
- ? RemoteAnimationTarget.MODE_OPENING
- : RemoteAnimationTarget.MODE_CLOSING);
- }
- resultAdapters = new Pair<>(adapters.mAdapter, adapters.mThumbnailAdapter);
- } else if (isChanging) {
+ if (isChanging) {
final float durationScale = mWmService.getTransitionAnimationScaleLocked();
final DisplayInfo displayInfo = getDisplayContent().getDisplayInfo();
mTmpRect.offsetTo(mTmpPoint.x, mTmpPoint.y);
@@ -3417,11 +3358,6 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
return a;
}
- RemoteAnimationTarget createRemoteAnimationTarget(
- RemoteAnimationController.RemoteAnimationRecord record) {
- return null;
- }
-
boolean canCreateRemoteAnimationTarget() {
return false;
}
diff --git a/services/core/java/com/android/server/wm/WindowContainerThumbnail.java b/services/core/java/com/android/server/wm/WindowContainerThumbnail.java
deleted file mode 100644
index ae477e1e22f0..000000000000
--- a/services/core/java/com/android/server/wm/WindowContainerThumbnail.java
+++ /dev/null
@@ -1,218 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wm;
-
-import static android.view.SurfaceControl.METADATA_OWNER_UID;
-import static android.view.SurfaceControl.METADATA_WINDOW_TYPE;
-
-import static com.android.internal.protolog.WmProtoLogGroups.WM_SHOW_TRANSACTIONS;
-import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION;
-import static com.android.server.wm.WindowContainerThumbnailProto.HEIGHT;
-import static com.android.server.wm.WindowContainerThumbnailProto.SURFACE_ANIMATOR;
-import static com.android.server.wm.WindowContainerThumbnailProto.WIDTH;
-import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
-import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
-import static com.android.server.wm.WindowManagerService.MAX_ANIMATION_DURATION;
-
-import android.graphics.ColorSpace;
-import android.graphics.GraphicBuffer;
-import android.graphics.PixelFormat;
-import android.graphics.Point;
-import android.hardware.HardwareBuffer;
-import android.util.proto.ProtoOutputStream;
-import android.view.SurfaceControl;
-import android.view.SurfaceControl.Builder;
-import android.view.SurfaceControl.Transaction;
-import android.view.animation.Animation;
-
-import com.android.internal.protolog.ProtoLog;
-import com.android.server.wm.SurfaceAnimator.Animatable;
-import com.android.server.wm.SurfaceAnimator.AnimationType;
-
-/**
- * Represents a surface that is displayed over a subclass of {@link WindowContainer}
- */
-class WindowContainerThumbnail implements Animatable {
-
- private static final String TAG = TAG_WITH_CLASS_NAME ? "WindowContainerThumbnail" : TAG_WM;
-
- private final WindowContainer mWindowContainer;
- private SurfaceControl mSurfaceControl;
- private final SurfaceAnimator mSurfaceAnimator;
- private final int mWidth;
- private final int mHeight;
-
- /**
- * @param t Transaction to create the thumbnail in.
- * @param container The sub-class of {@link WindowContainer} to associate this thumbnail with.
- * @param thumbnailHeader A thumbnail or placeholder for thumbnail to initialize with.
- */
- WindowContainerThumbnail(Transaction t, WindowContainer container,
- HardwareBuffer thumbnailHeader) {
- this(t, container, thumbnailHeader, null /* animator */);
- }
-
- WindowContainerThumbnail(Transaction t, WindowContainer container,
- HardwareBuffer thumbnailHeader, SurfaceAnimator animator) {
- mWindowContainer = container;
- if (animator != null) {
- mSurfaceAnimator = animator;
- } else {
- // We can't use a delegating constructor since we need to
- // reference this::onAnimationFinished
- mSurfaceAnimator =
- new SurfaceAnimator(this, this::onAnimationFinished /* animationFinishedCallback */,
- container.mWmService);
- }
- mWidth = thumbnailHeader.getWidth();
- mHeight = thumbnailHeader.getHeight();
-
- // Create a new surface for the thumbnail
- // TODO: This should be attached as a child to the app token, once the thumbnail animations
- // use relative coordinates. Once we start animating task we can also consider attaching
- // this to the task.
- mSurfaceControl = mWindowContainer.makeChildSurface(mWindowContainer.getTopChild())
- .setName("thumbnail anim: " + mWindowContainer.toString())
- .setBLASTLayer()
- .setFormat(PixelFormat.TRANSLUCENT)
- .setMetadata(METADATA_WINDOW_TYPE, mWindowContainer.getWindowingMode())
- .setMetadata(METADATA_OWNER_UID, WindowManagerService.MY_UID)
- .setCallsite("WindowContainerThumbnail")
- .build();
-
- ProtoLog.i(WM_SHOW_TRANSACTIONS, " THUMBNAIL %s: CREATE", mSurfaceControl);
-
- GraphicBuffer graphicBuffer = GraphicBuffer.createFromHardwareBuffer(thumbnailHeader);
- t.setBuffer(mSurfaceControl, graphicBuffer);
- t.setColorSpace(mSurfaceControl, ColorSpace.get(ColorSpace.Named.SRGB));
- t.show(mSurfaceControl);
-
- // We parent the thumbnail to the container, and just place it on top of anything else in
- // the container.
- t.setLayer(mSurfaceControl, Integer.MAX_VALUE);
- }
-
- void startAnimation(Transaction t, Animation anim) {
- startAnimation(t, anim, null /* position */);
- }
-
- void startAnimation(Transaction t, Animation anim, Point position) {
- anim.restrictDuration(MAX_ANIMATION_DURATION);
- anim.scaleCurrentDuration(mWindowContainer.mWmService.getTransitionAnimationScaleLocked());
- mSurfaceAnimator.startAnimation(t, new LocalAnimationAdapter(
- new WindowAnimationSpec(anim, position,
- mWindowContainer.getDisplayContent().mAppTransition.canSkipFirstFrame(),
- mWindowContainer.getDisplayContent().getWindowCornerRadius()),
- mWindowContainer.mWmService.mSurfaceAnimationRunner), false /* hidden */,
- ANIMATION_TYPE_APP_TRANSITION);
- }
-
- private void onAnimationFinished(@AnimationType int type, AnimationAdapter anim) {
- }
-
- void setShowing(Transaction pendingTransaction, boolean show) {
- // TODO: Not needed anymore once thumbnail is attached to the app.
- if (show) {
- pendingTransaction.show(mSurfaceControl);
- } else {
- pendingTransaction.hide(mSurfaceControl);
- }
- }
-
- void destroy() {
- mSurfaceAnimator.cancelAnimation();
- getPendingTransaction().remove(mSurfaceControl);
- mSurfaceControl = null;
- }
-
- /**
- * Write to a protocol buffer output stream. Protocol buffer message definition is at {@link
- * com.android.server.wm.WindowContainerThumbnailProto}.
- *
- * @param proto Stream to write the WindowContainerThumbnailProto object to.
- * @param fieldId Field Id of the WindowContainerThumbnailProto as defined in the parent
- * message.
- * @hide
- */
- void dumpDebug(ProtoOutputStream proto, long fieldId) {
- final long token = proto.start(fieldId);
- proto.write(WIDTH, mWidth);
- proto.write(HEIGHT, mHeight);
- if (mSurfaceAnimator.isAnimating()) {
- mSurfaceAnimator.dumpDebug(proto, SURFACE_ANIMATOR);
- }
- proto.end(token);
- }
-
- @Override
- public Transaction getSyncTransaction() {
- return mWindowContainer.getSyncTransaction();
- }
-
- @Override
- public Transaction getPendingTransaction() {
- return mWindowContainer.getPendingTransaction();
- }
-
- @Override
- public void commitPendingTransaction() {
- mWindowContainer.commitPendingTransaction();
- }
-
- @Override
- public void onAnimationLeashCreated(Transaction t, SurfaceControl leash) {
- t.setLayer(leash, Integer.MAX_VALUE);
- }
-
- @Override
- public void onAnimationLeashLost(Transaction t) {
-
- // TODO: Once attached to app token, we don't need to hide it immediately if thumbnail
- // became visible.
- t.hide(mSurfaceControl);
- }
-
- @Override
- public Builder makeAnimationLeash() {
- return mWindowContainer.makeChildSurface(mWindowContainer.getTopChild());
- }
-
- @Override
- public SurfaceControl getSurfaceControl() {
- return mSurfaceControl;
- }
-
- @Override
- public SurfaceControl getAnimationLeashParent() {
- return mWindowContainer.getAnimationLeashParent();
- }
-
- @Override
- public SurfaceControl getParentSurfaceControl() {
- return mWindowContainer.getParentSurfaceControl();
- }
-
- @Override
- public int getSurfaceWidth() {
- return mWidth;
- }
-
- @Override
- public int getSurfaceHeight() {
- return mHeight;
- }
-}
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 3b6a4dc6e1b0..4c53ba53a506 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -30,24 +30,25 @@ import static android.window.TaskFragmentOperation.OP_TYPE_CLEAR_ADJACENT_TASK_F
import static android.window.TaskFragmentOperation.OP_TYPE_CREATE_OR_MOVE_TASK_FRAGMENT_DECOR_SURFACE;
import static android.window.TaskFragmentOperation.OP_TYPE_CREATE_TASK_FRAGMENT;
import static android.window.TaskFragmentOperation.OP_TYPE_DELETE_TASK_FRAGMENT;
+import static android.window.TaskFragmentOperation.OP_TYPE_PRIVILEGED_REORDER_TO_BOTTOM_OF_TASK;
+import static android.window.TaskFragmentOperation.OP_TYPE_PRIVILEGED_REORDER_TO_TOP_OF_TASK;
+import static android.window.TaskFragmentOperation.OP_TYPE_PRIVILEGED_SET_CAN_AFFECT_SYSTEM_UI_FLAGS;
+import static android.window.TaskFragmentOperation.OP_TYPE_PRIVILEGED_SET_MOVE_TO_BOTTOM_IF_CLEAR_WHEN_LAUNCH;
import static android.window.TaskFragmentOperation.OP_TYPE_REMOVE_TASK_FRAGMENT_DECOR_SURFACE;
-import static android.window.TaskFragmentOperation.OP_TYPE_REORDER_TO_BOTTOM_OF_TASK;
import static android.window.TaskFragmentOperation.OP_TYPE_REORDER_TO_FRONT;
-import static android.window.TaskFragmentOperation.OP_TYPE_REORDER_TO_TOP_OF_TASK;
import static android.window.TaskFragmentOperation.OP_TYPE_REPARENT_ACTIVITY_TO_TASK_FRAGMENT;
import static android.window.TaskFragmentOperation.OP_TYPE_REQUEST_FOCUS_ON_TASK_FRAGMENT;
import static android.window.TaskFragmentOperation.OP_TYPE_SET_ADJACENT_TASK_FRAGMENTS;
import static android.window.TaskFragmentOperation.OP_TYPE_SET_ANIMATION_PARAMS;
-import static android.window.TaskFragmentOperation.OP_TYPE_SET_CAN_AFFECT_SYSTEM_UI_FLAGS;
import static android.window.TaskFragmentOperation.OP_TYPE_SET_COMPANION_TASK_FRAGMENT;
import static android.window.TaskFragmentOperation.OP_TYPE_SET_DECOR_SURFACE_BOOSTED;
import static android.window.TaskFragmentOperation.OP_TYPE_SET_DIM_ON_TASK;
import static android.window.TaskFragmentOperation.OP_TYPE_SET_ISOLATED_NAVIGATION;
-import static android.window.TaskFragmentOperation.OP_TYPE_SET_MOVE_TO_BOTTOM_IF_CLEAR_WHEN_LAUNCH;
import static android.window.TaskFragmentOperation.OP_TYPE_SET_PINNED;
import static android.window.TaskFragmentOperation.OP_TYPE_SET_RELATIVE_BOUNDS;
import static android.window.TaskFragmentOperation.OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT;
import static android.window.TaskFragmentOperation.OP_TYPE_UNKNOWN;
+import static android.window.TaskFragmentOperation.PRIVILEGED_OP_START;
import static android.window.WindowContainerTransaction.Change.CHANGE_FOCUSABLE;
import static android.window.WindowContainerTransaction.Change.CHANGE_FORCE_TRANSLUCENT;
import static android.window.WindowContainerTransaction.Change.CHANGE_HIDDEN;
@@ -1786,7 +1787,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
taskFragment.setIsolatedNav(isolatedNav);
break;
}
- case OP_TYPE_REORDER_TO_BOTTOM_OF_TASK: {
+ case OP_TYPE_PRIVILEGED_REORDER_TO_BOTTOM_OF_TASK: {
final Task task = taskFragment.getTask();
if (task != null) {
if (task.getBottomChild() != taskFragment) {
@@ -1802,7 +1803,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
}
break;
}
- case OP_TYPE_REORDER_TO_TOP_OF_TASK: {
+ case OP_TYPE_PRIVILEGED_REORDER_TO_TOP_OF_TASK: {
final Task task = taskFragment.getTask();
if (task != null) {
if (task.getTopChild() != taskFragment) {
@@ -1852,7 +1853,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
: EMBEDDED_DIM_AREA_TASK_FRAGMENT);
break;
}
- case OP_TYPE_SET_MOVE_TO_BOTTOM_IF_CLEAR_WHEN_LAUNCH: {
+ case OP_TYPE_PRIVILEGED_SET_MOVE_TO_BOTTOM_IF_CLEAR_WHEN_LAUNCH: {
taskFragment.setMoveToBottomIfClearWhenLaunch(operation.getBooleanValue());
break;
}
@@ -1888,7 +1889,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
taskFragment.setPinned(pinned);
break;
}
- case OP_TYPE_SET_CAN_AFFECT_SYSTEM_UI_FLAGS: {
+ case OP_TYPE_PRIVILEGED_SET_CAN_AFFECT_SYSTEM_UI_FLAGS: {
taskFragment.setCanAffectSystemUiFlags(operation.getBooleanValue());
// Request to apply the flags.
@@ -1938,45 +1939,23 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
return false;
}
final int opType = operation.getOpType();
- if (opType == OP_TYPE_CREATE_TASK_FRAGMENT) {
- // No need to check TaskFragment.
- return true;
- }
-
- if (!validateTaskFragment(taskFragment, opType, errorCallbackToken, organizer)) {
- return false;
- }
- if ((opType == OP_TYPE_REORDER_TO_BOTTOM_OF_TASK
- || opType == OP_TYPE_REORDER_TO_TOP_OF_TASK)
+ if (opType >= PRIVILEGED_OP_START
&& !mTaskFragmentOrganizerController.isSystemOrganizer(organizer.asBinder())) {
final Throwable exception = new SecurityException(
- "Only a system organizer can perform OP_TYPE_REORDER_TO_BOTTOM_OF_TASK or "
- + "OP_TYPE_REORDER_TO_TOP_OF_TASK."
+ "Only a system organizer can perform privileged operations. opType=" + opType
);
sendTaskFragmentOperationFailure(organizer, errorCallbackToken, taskFragment,
opType, exception);
return false;
}
- if ((opType == OP_TYPE_SET_MOVE_TO_BOTTOM_IF_CLEAR_WHEN_LAUNCH)
- && !mTaskFragmentOrganizerController.isSystemOrganizer(organizer.asBinder())) {
- final Throwable exception = new SecurityException(
- "Only a system organizer can perform "
- + "OP_TYPE_SET_MOVE_TO_BOTTOM_IF_CLEAR_WHEN_LAUNCH."
- );
- sendTaskFragmentOperationFailure(organizer, errorCallbackToken, taskFragment,
- opType, exception);
- return false;
+ if (opType == OP_TYPE_CREATE_TASK_FRAGMENT) {
+ // No need to check TaskFragment.
+ return true;
}
- if ((opType == OP_TYPE_SET_CAN_AFFECT_SYSTEM_UI_FLAGS)
- && !mTaskFragmentOrganizerController.isSystemOrganizer(organizer.asBinder())) {
- final Throwable exception = new SecurityException(
- "Only a system organizer can perform OP_TYPE_SET_CAN_AFFECT_SYSTEM_UI_FLAGS."
- );
- sendTaskFragmentOperationFailure(organizer, errorCallbackToken, taskFragment,
- opType, exception);
+ if (!validateTaskFragment(taskFragment, opType, errorCallbackToken, organizer)) {
return false;
}
diff --git a/services/people/java/com/android/server/people/PeopleService.java b/services/people/java/com/android/server/people/PeopleService.java
index b9f00d7979c2..44a83541f0b9 100644
--- a/services/people/java/com/android/server/people/PeopleService.java
+++ b/services/people/java/com/android/server/people/PeopleService.java
@@ -16,6 +16,8 @@
package com.android.server.people;
+import static com.android.server.flags.Flags.earlyDataManagerInit;
+
import android.Manifest;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -117,6 +119,18 @@ public class PeopleService extends SystemService {
}
@Override
+ public void onBootPhase(int phase) {
+ if (phase == PHASE_BOOT_COMPLETED) {
+ if (earlyDataManagerInit()) {
+ // Force initialization of DataManager before onUserUnlocked
+ // to avoid blocking the ActivityManagerService on
+ // shortcutHandleUnlockUser.
+ getDataManager();
+ }
+ }
+ }
+
+ @Override
public void onUserUnlocked(@NonNull TargetUser user) {
getDataManager().onUserUnlocked(user.getUserIdentifier());
}
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/AndroidManifest.xml b/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/AndroidManifest.xml
index b7de74987eb8..45523268c966 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/AndroidManifest.xml
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/AndroidManifest.xml
@@ -32,8 +32,8 @@
<uses-library android:name="android.test.runner" />
</application>
- <!-- The "targetPackage" reference the instruments APK package, which is the FakeImeApk, while
- the test package is "com.android.inputmethod.imetests" (FrameworksImeTests.apk).-->
+ <!-- The "targetPackage" reference the instruments APK package, which is the SimpleTestIme.apk,
+ while the test package is "com.android.inputmethod.imetests" (FrameworksImeTests.apk).-->
<instrumentation
android:name="androidx.test.runner.AndroidJUnitRunner"
android:targetPackage="com.android.apps.inputmethod.simpleime"
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/InputMethodServiceTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/InputMethodServiceTest.java
index 72e9cc566497..5d64cb638702 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/InputMethodServiceTest.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/InputMethodServiceTest.java
@@ -92,6 +92,9 @@ public class InputMethodServiceTest {
private static final String DISABLE_SHOW_IME_WITH_HARD_KEYBOARD_CMD =
"settings put secure " + Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD + " 0";
+ /** 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 GestureNavSwitchHelper mGestureNavSwitchHelper = new GestureNavSwitchHelper();
@@ -173,15 +176,14 @@ public class InputMethodServiceTest {
Log.i(TAG, "Click on EditText");
verifyInputViewStatus(
() -> clickOnViewCenter(mActivity.getEditText()),
- EVENT_SHOW,
- true /* expected */,
- true /* inputViewStarted */);
- assertWithMessage("IME is shown").that(mInputMethodService.isInputViewShown()).isTrue();
+ EVENT_SHOW, true /* eventExpected */, true /* shown */, "IME is shown");
// Press home key to hide IME.
Log.i(TAG, "Press home");
if (mFlagsValueProvider.getBoolean(Flags.FLAG_REFACTOR_INSETS_CONTROLLER)) {
assertWithMessage("Home key press was handled").that(mUiDevice.pressHome()).isTrue();
+ // This doesn't call verifyInputViewStatus, as the refactor delays the events such that
+ // getCurrentInputStarted() would be false, as we would already be in launcher.
// The IME visibility is only sent at the end of the animation. Therefore, we have to
// wait until the visibility was sent to the server and the IME window hidden.
eventually(() -> assertWithMessage("IME is not shown")
@@ -190,11 +192,7 @@ public class InputMethodServiceTest {
verifyInputViewStatus(
() -> assertWithMessage("Home key press was handled")
.that(mUiDevice.pressHome()).isTrue(),
- EVENT_HIDE,
- true /* expected */,
- false /* inputViewStarted */);
- assertWithMessage("IME is not shown")
- .that(mInputMethodService.isInputViewShown()).isFalse();
+ EVENT_HIDE, true /* eventExpected */, false /* shown */, "IME is not shown");
}
}
@@ -208,26 +206,12 @@ public class InputMethodServiceTest {
// Triggers to show IME via public API.
verifyInputViewStatusOnMainSync(
() -> assertThat(mActivity.showImeWithInputMethodManager(0 /* flags */)).isTrue(),
- EVENT_SHOW,
- true /* expected */,
- true /* inputViewStarted */);
- assertWithMessage("IME is shown").that(mInputMethodService.isInputViewShown()).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 /* expected */,
- false /* inputViewStarted */);
- if (mFlagsValueProvider.getBoolean(Flags.FLAG_REFACTOR_INSETS_CONTROLLER)) {
- // The IME visibility is only sent at the end of the animation. Therefore, we have to
- // wait until the visibility was sent to the server and the IME window hidden.
- eventually(() -> assertWithMessage("IME is not shown")
- .that(mInputMethodService.isInputViewShown()).isFalse());
- } else {
- assertWithMessage("IME is not shown")
- .that(mInputMethodService.isInputViewShown()).isFalse();
- }
+ EVENT_HIDE, true /* eventExpected */, false /* shown */, "IME is not shown");
}
/**
@@ -240,26 +224,12 @@ public class InputMethodServiceTest {
// Triggers to show IME via public API.
verifyInputViewStatusOnMainSync(
() -> mActivity.showImeWithWindowInsetsController(),
- EVENT_SHOW,
- true /* expected */,
- true /* inputViewStarted */);
- assertWithMessage("IME is shown").that(mInputMethodService.isInputViewShown()).isTrue();
+ EVENT_SHOW, true /* eventExpected */, true /* shown */, "IME is shown");
// Triggers to hide IME via public API.
verifyInputViewStatusOnMainSync(
() -> mActivity.hideImeWithWindowInsetsController(),
- EVENT_HIDE,
- true /* expected */,
- false /* inputViewStarted */);
- if (mFlagsValueProvider.getBoolean(Flags.FLAG_REFACTOR_INSETS_CONTROLLER)) {
- // The IME visibility is only sent at the end of the animation. Therefore, we have to
- // wait until the visibility was sent to the server and the IME window hidden.
- eventually(() -> assertWithMessage("IME is not shown")
- .that(mInputMethodService.isInputViewShown()).isFalse());
- } else {
- assertWithMessage("IME is not shown")
- .that(mInputMethodService.isInputViewShown()).isFalse();
- }
+ EVENT_HIDE, true /* eventExpected */, false /* shown */, "IME is not shown");
}
/**
@@ -277,10 +247,7 @@ public class InputMethodServiceTest {
Log.i(TAG, "Call IMS#requestShowSelf(0)");
verifyInputViewStatusOnMainSync(
() -> mInputMethodService.requestShowSelf(0 /* flags */),
- EVENT_SHOW,
- true /* expected */,
- true /* inputViewStarted */);
- assertWithMessage("IME is shown").that(mInputMethodService.isInputViewShown()).isTrue();
+ 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).
@@ -288,51 +255,31 @@ public class InputMethodServiceTest {
verifyInputViewStatusOnMainSync(
() -> mInputMethodService.requestHideSelf(
InputMethodManager.HIDE_IMPLICIT_ONLY),
- EVENT_HIDE,
- false /* expected */,
- true /* inputViewStarted */);
- assertWithMessage("IME is still shown after HIDE_IMPLICIT_ONLY")
- .that(mInputMethodService.isInputViewShown()).isTrue();
+ 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 /* expected */,
- false /* inputViewStarted */);
- if (mFlagsValueProvider.getBoolean(Flags.FLAG_REFACTOR_INSETS_CONTROLLER)) {
- // The IME visibility is only sent at the end of the animation. Therefore, we have to
- // wait until the visibility was sent to the server and the IME window hidden.
- eventually(() -> assertWithMessage("IME is not shown")
- .that(mInputMethodService.isInputViewShown()).isFalse());
- } else {
- assertWithMessage("IME is not shown")
- .that(mInputMethodService.isInputViewShown()).isFalse();
- }
+ 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 /* expected */,
- true /* inputViewStarted */);
- assertWithMessage("IME is shown with SHOW_IMPLICIT")
- .that(mInputMethodService.isInputViewShown()).isTrue();
+ 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 /* expected */,
- false /* inputViewStarted */);
- assertWithMessage("IME is not shown after HIDE_IMPLICIT_ONLY")
- .that(mInputMethodService.isInputViewShown()).isFalse();
+ EVENT_HIDE, true /* eventExpected */, false /* shown */,
+ "IME is not shown after HIDE_IMPLICIT_ONLY");
}
}
@@ -424,20 +371,14 @@ public class InputMethodServiceTest {
verifyInputViewStatusOnMainSync(() -> assertThat(
mActivity.showImeWithInputMethodManager(
InputMethodManager.SHOW_IMPLICIT)).isTrue(),
- EVENT_SHOW,
- false /* expected */,
- false /* inputViewStarted */);
- assertWithMessage("IME is not shown after SHOW_IMPLICIT")
- .that(mInputMethodService.isInputViewShown()).isFalse();
+ EVENT_SHOW, false /* eventExpected */, false /* shown */,
+ "IME is not shown after SHOW_IMPLICIT");
verifyInputViewStatusOnMainSync(
() -> assertThat(mActivity.showImeWithInputMethodManager(0 /* flags */))
.isTrue(),
- EVENT_SHOW,
- false /* expected */,
- false /* inputViewStarted */);
- assertWithMessage("IME is not shown after SHOW_EXPLICIT")
- .that(mInputMethodService.isInputViewShown()).isFalse();
+ EVENT_SHOW, false /* eventExpected */, false /* shown */,
+ "IME is not shown after SHOW_EXPLICIT");
} finally {
mInputMethodService.getResources()
.updateConfiguration(initialConfig, null /* metrics */, null /* compat */);
@@ -455,10 +396,7 @@ public class InputMethodServiceTest {
// IME should be shown.
verifyInputViewStatusOnMainSync(
() -> assertThat(mActivity.showImeWithInputMethodManager(0 /* flags */)).isTrue(),
- EVENT_SHOW,
- true /* expected */,
- true /* inputViewStarted */);
- assertWithMessage("IME is shown").that(mInputMethodService.isInputViewShown()).isTrue();
+ EVENT_SHOW, true /* eventExpected */, true /* shown */, "IME is shown");
}
/**
@@ -472,10 +410,7 @@ public class InputMethodServiceTest {
// the IME should be shown.
verifyInputViewStatusOnMainSync(() -> assertThat(
mActivity.showImeWithInputMethodManager(InputMethodManager.SHOW_IMPLICIT)).isTrue(),
- EVENT_SHOW,
- true /* expected */,
- true /* inputViewStarted */);
- assertWithMessage("IME is shown").that(mInputMethodService.isInputViewShown()).isTrue();
+ EVENT_SHOW, true /* eventExpected */, true /* shown */, "IME is shown");
}
/**
@@ -492,9 +427,9 @@ public class InputMethodServiceTest {
.that(mUiDevice.isNaturalOrientation()).isFalse());
// Wait for the TestActivity to be recreated.
eventually(() -> assertWithMessage("Activity was re-created after rotation")
- .that(TestActivity.getLastCreatedInstance()).isNotEqualTo(mActivity));
+ .that(TestActivity.getInstance()).isNotEqualTo(mActivity));
// Get the new TestActivity.
- mActivity = TestActivity.getLastCreatedInstance();
+ 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")
@@ -502,10 +437,7 @@ public class InputMethodServiceTest {
verifyInputViewStatusOnMainSync(() -> assertThat(
mActivity.showImeWithInputMethodManager(0 /* flags */)).isTrue(),
- EVENT_SHOW,
- true /* expected */,
- true /* inputViewStarted */);
- assertWithMessage("IME is shown").that(mInputMethodService.isInputViewShown()).isTrue();
+ EVENT_SHOW, true /* eventExpected */, true /* shown */, "IME is shown");
}
/**
@@ -527,9 +459,9 @@ public class InputMethodServiceTest {
.that(mUiDevice.isNaturalOrientation()).isFalse());
// Wait for the TestActivity to be recreated.
eventually(() -> assertWithMessage("Activity was re-created after rotation")
- .that(TestActivity.getLastCreatedInstance()).isNotEqualTo(mActivity));
+ .that(TestActivity.getInstance()).isNotEqualTo(mActivity));
// Get the new TestActivity.
- mActivity = TestActivity.getLastCreatedInstance();
+ 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")
@@ -537,11 +469,7 @@ public class InputMethodServiceTest {
verifyInputViewStatusOnMainSync(() -> assertThat(
mActivity.showImeWithInputMethodManager(InputMethodManager.SHOW_IMPLICIT)).isTrue(),
- EVENT_SHOW,
- false /* expected */,
- false /* inputViewStarted */);
- assertWithMessage("IME is not shown")
- .that(mInputMethodService.isInputViewShown()).isFalse();
+ EVENT_SHOW, false /* eventExpected */, false /* shown */, "IME is not shown");
}
/**
@@ -561,10 +489,7 @@ public class InputMethodServiceTest {
verifyInputViewStatusOnMainSync(() -> assertThat(
mActivity.showImeWithInputMethodManager(0 /* flags */)).isTrue(),
- EVENT_SHOW,
- true /* expected */,
- true /* inputViewStarted */);
- assertWithMessage("IME is shown").that(mInputMethodService.isInputViewShown()).isTrue();
+ EVENT_SHOW, true /* eventExpected */, true /* shown */, "IME is shown");
} finally {
mInputMethodService.getResources()
.updateConfiguration(initialConfig, null /* metrics */, null /* compat */);
@@ -594,11 +519,7 @@ public class InputMethodServiceTest {
verifyInputViewStatusOnMainSync(() ->assertThat(
mActivity.showImeWithInputMethodManager(InputMethodManager.SHOW_IMPLICIT))
.isTrue(),
- EVENT_SHOW,
- false /* expected */,
- false /* inputViewStarted */);
- assertWithMessage("IME is not shown")
- .that(mInputMethodService.isInputViewShown()).isFalse();
+ EVENT_SHOW, false /* eventExpected */, false /* shown */, "IME is not shown");
} finally {
mInputMethodService.getResources()
.updateConfiguration(initialConfig, null /* metrics */, null /* compat */);
@@ -623,10 +544,7 @@ public class InputMethodServiceTest {
verifyInputViewStatusOnMainSync(
() -> assertThat(mActivity.showImeWithInputMethodManager(0 /* flags */))
.isTrue(),
- EVENT_SHOW,
- true /* expected */,
- true /* inputViewStarted */);
- assertWithMessage("IME is shown").that(mInputMethodService.isInputViewShown()).isTrue();
+ EVENT_SHOW, true /* eventExpected */, true /* shown */, "IME is shown");
// Simulate connecting a hardware keyboard.
config.keyboard = Configuration.KEYBOARD_QWERTY;
@@ -637,11 +555,8 @@ public class InputMethodServiceTest {
verifyInputViewStatusOnMainSync(
() -> mInputMethodService.onConfigurationChanged(config),
- EVENT_CONFIG,
- true /* expected */,
- true /* inputViewStarted */);
- assertWithMessage("IME is still shown after a configuration change")
- .that(mInputMethodService.isInputViewShown()).isTrue();
+ EVENT_CONFIG, true /* eventExpected */, true /* shown */,
+ "IME is still shown after a configuration change");
} finally {
mInputMethodService.getResources()
.updateConfiguration(initialConfig, null /* metrics */, null /* compat */);
@@ -672,10 +587,7 @@ public class InputMethodServiceTest {
verifyInputViewStatusOnMainSync(() -> assertThat(
mActivity.showImeWithInputMethodManager(
InputMethodManager.SHOW_IMPLICIT)).isTrue(),
- EVENT_SHOW,
- true /* expected */,
- true /* inputViewStarted */);
- assertWithMessage("IME is shown").that(mInputMethodService.isInputViewShown()).isTrue();
+ EVENT_SHOW, true /* eventExpected */, true /* shown */, "IME is shown");
// Simulate connecting a hardware keyboard.
config.keyboard = Configuration.KEYBOARD_QWERTY;
@@ -692,11 +604,8 @@ public class InputMethodServiceTest {
// still alive.
verifyInputViewStatusOnMainSync(
() -> mInputMethodService.onConfigurationChanged(config),
- EVENT_CONFIG,
- true /* expected */,
- true /* inputViewStarted */);
- assertWithMessage("IME is not shown after a configuration change")
- .that(mInputMethodService.isInputViewShown()).isFalse();
+ 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 */);
@@ -722,31 +631,21 @@ public class InputMethodServiceTest {
// Explicit show request.
verifyInputViewStatusOnMainSync(() -> assertThat(
mActivity.showImeWithInputMethodManager(0 /* flags */)).isTrue(),
- EVENT_SHOW,
- true /* expected */,
- true /* inputViewStarted */);
- assertWithMessage("IME is shown").that(mInputMethodService.isInputViewShown()).isTrue();
+ EVENT_SHOW, true /* eventExpected */, true /* shown */, "IME is shown");
// Implicit show request.
verifyInputViewStatusOnMainSync(() -> assertThat(
mActivity.showImeWithInputMethodManager(
InputMethodManager.SHOW_IMPLICIT)).isTrue(),
- EVENT_SHOW,
- false /* expected */,
- true /* inputViewStarted */);
- assertWithMessage("IME is still shown")
- .that(mInputMethodService.isInputViewShown()).isTrue();
+ EVENT_SHOW, false /* eventExpected */, true /* shown */, "IME is still shown");
// Simulate a fake configuration change to avoid the recreation of TestActivity.
// This should now consider the implicit show request, but keep the state from the
// explicit show request, and thus not hide the IME.
verifyInputViewStatusOnMainSync(
() -> mInputMethodService.onConfigurationChanged(config),
- EVENT_CONFIG,
- true /* expected */,
- true /* inputViewStarted */);
- assertWithMessage("IME is still shown after a configuration change")
- .that(mInputMethodService.isInputViewShown()).isTrue();
+ EVENT_CONFIG, true /* eventExpected */, true /* shown */,
+ "IME is still shown after a configuration change");
} finally {
mInputMethodService.getResources()
.updateConfiguration(initialConfig, null /* metrics */, null /* compat */);
@@ -769,27 +668,17 @@ public class InputMethodServiceTest {
verifyInputViewStatusOnMainSync(() -> assertThat(
mActivity.showImeWithInputMethodManager(InputMethodManager.SHOW_FORCED)).isTrue(),
- EVENT_SHOW,
- true /* expected */,
- true /* inputViewStarted */);
- assertWithMessage("IME is shown").that(mInputMethodService.isInputViewShown()).isTrue();
+ EVENT_SHOW, true /* eventExpected */, true /* shown */, "IME is shown");
verifyInputViewStatusOnMainSync(() -> assertThat(
mActivity.showImeWithInputMethodManager(0 /* flags */)).isTrue(),
- EVENT_SHOW,
- false /* expected */,
- true /* inputViewStarted */);
- assertWithMessage("IME is still shown")
- .that(mInputMethodService.isInputViewShown()).isTrue();
+ EVENT_SHOW, false /* eventExpected */, true /* shown */, "IME is still shown");
verifyInputViewStatusOnMainSync(() -> assertThat(
mActivity.hideImeWithInputMethodManager(InputMethodManager.HIDE_NOT_ALWAYS))
.isTrue(),
- EVENT_HIDE,
- false /* expected */,
- true /* inputViewStarted */);
- assertWithMessage("IME is still shown after HIDE_NOT_ALWAYS")
- .that(mInputMethodService.isInputViewShown()).isTrue();
+ EVENT_HIDE, false /* eventExpected */, true /* shown */,
+ "IME is still shown after HIDE_NOT_ALWAYS");
}
/**
@@ -800,18 +689,15 @@ public class InputMethodServiceTest {
setShowImeWithHardKeyboard(true /* enabled */);
Log.i(TAG, "Set orientation natural");
- verifyFullscreenMode(() -> setOrientation(0),
- false /* expected */,
+ verifyFullscreenMode(() -> setOrientation(0), false /* eventExpected */,
true /* orientationPortrait */);
Log.i(TAG, "Set orientation left");
- verifyFullscreenMode(() -> setOrientation(1),
- true /* expected */,
+ verifyFullscreenMode(() -> setOrientation(1), true /* eventExpected */,
false /* orientationPortrait */);
Log.i(TAG, "Set orientation right");
- verifyFullscreenMode(() -> setOrientation(2),
- false /* expected */,
+ verifyFullscreenMode(() -> setOrientation(2), false /* eventExpected */,
false /* orientationPortrait */);
}
@@ -845,10 +731,7 @@ public class InputMethodServiceTest {
setDrawsImeNavBarAndSwitcherButton(true /* enabled */);
mActivity.showImeWithWindowInsetsController();
},
- EVENT_SHOW,
- true /* expected */,
- true /* inputViewStarted */);
- assertWithMessage("IME is shown").that(mInputMethodService.isInputViewShown()).isTrue();
+ EVENT_SHOW, true /* eventExpected */, true /* shown */, "IME is shown");
assertWithMessage("IME navigation bar is initially shown")
.that(mInputMethodService.isImeNavigationBarShownForTesting()).isTrue();
@@ -883,10 +766,7 @@ public class InputMethodServiceTest {
setDrawsImeNavBarAndSwitcherButton(false /* enabled */);
mActivity.showImeWithWindowInsetsController();
},
- EVENT_SHOW,
- true /* expected */,
- true /* inputViewStarted */);
- assertWithMessage("IME is shown").that(mInputMethodService.isInputViewShown()).isTrue();
+ EVENT_SHOW, true /* eventExpected */, true /* shown */, "IME is shown");
assertWithMessage("IME navigation bar is initially not shown")
.that(mInputMethodService.isImeNavigationBarShownForTesting()).isFalse();
@@ -917,24 +797,15 @@ public class InputMethodServiceTest {
try (var ignored = mGestureNavSwitchHelper.withGestureNavigationMode()) {
verifyInputViewStatusOnMainSync(
() -> mActivity.showImeWithWindowInsetsController(),
- EVENT_SHOW,
- true /* expected */,
- true /* inputViewStarted */);
- assertWithMessage("IME is shown").that(mInputMethodService.isInputViewShown()).isTrue();
+ EVENT_SHOW, true /* eventExpected */, true /* shown */, "IME is shown");
final var backButton = getUiObject(By.res(INPUT_METHOD_NAV_BACK_ID));
- backButton.click();
- mInstrumentation.waitForIdleSync();
-
- if (mFlagsValueProvider.getBoolean(Flags.FLAG_REFACTOR_INSETS_CONTROLLER)) {
- // The IME visibility is only sent at the end of the animation. Therefore, we have
- // to wait until the visibility was sent to the server and the IME window hidden.
- eventually(() -> assertWithMessage("IME is not shown")
- .that(mInputMethodService.isInputViewShown()).isFalse());
- } else {
- assertWithMessage("IME is not shown")
- .that(mInputMethodService.isInputViewShown()).isFalse();
- }
+ verifyInputViewStatus(
+ () -> {
+ backButton.click();
+ mInstrumentation.waitForIdleSync();
+ },
+ EVENT_HIDE, true /* eventExpected */, false /* shown */, "IME is not shown");
}
}
@@ -952,30 +823,20 @@ public class InputMethodServiceTest {
try (var ignored = mGestureNavSwitchHelper.withGestureNavigationMode()) {
verifyInputViewStatusOnMainSync(
() -> mActivity.showImeWithWindowInsetsController(),
- EVENT_SHOW,
- true /* expected */,
- true /* inputViewStarted */);
- assertWithMessage("IME is shown").that(mInputMethodService.isInputViewShown()).isTrue();
+ EVENT_SHOW, true /* eventExpected */, true /* shown */, "IME is shown");
final var backButton = getUiObject(By.res(INPUT_METHOD_NAV_BACK_ID));
- backButton.longClick();
- mInstrumentation.waitForIdleSync();
-
- if (mFlagsValueProvider.getBoolean(Flags.FLAG_REFACTOR_INSETS_CONTROLLER)) {
- // The IME visibility is only sent at the end of the animation. Therefore, we have
- // to wait until the visibility was sent to the server and the IME window hidden.
- eventually(() -> assertWithMessage("IME is not shown")
- .that(mInputMethodService.isInputViewShown()).isFalse());
- } else {
- assertWithMessage("IME is not shown")
- .that(mInputMethodService.isInputViewShown()).isFalse();
- }
+ verifyInputViewStatus(
+ () -> {
+ backButton.longClick();
+ mInstrumentation.waitForIdleSync();
+ },
+ EVENT_HIDE, true /* eventExpected */, false /* shown */, "IME is not shown");
}
}
/**
- * Verifies that clicking on the IME switch button either shows the Input Method Switcher Menu,
- * or switches the input method.
+ * Verifies that clicking on the IME switch button switches the input method subtype.
*/
@Test
public void testImeSwitchButtonClick() throws Exception {
@@ -985,34 +846,32 @@ public class InputMethodServiceTest {
setShowImeWithHardKeyboard(true /* enabled */);
+ final var info = mImm.getCurrentInputMethodInfo();
+ assertWithMessage("InputMethodInfo is not null").that(info).isNotNull();
+ mImm.setExplicitlyEnabledInputMethodSubtypes(info.getId(), SUBTYPE_IDS);
+
+ final var initialSubtype = mImm.getCurrentInputMethodSubtype();
+
try (var ignored = mGestureNavSwitchHelper.withGestureNavigationMode()) {
verifyInputViewStatusOnMainSync(
() -> {
setDrawsImeNavBarAndSwitcherButton(true /* enabled */);
mActivity.showImeWithWindowInsetsController();
},
- EVENT_SHOW,
- true /* expected */,
- true /* inputViewStarted */);
- assertWithMessage("IME is shown").that(mInputMethodService.isInputViewShown()).isTrue();
-
- final var initialInfo = mImm.getCurrentInputMethodInfo();
+ EVENT_SHOW, true /* eventExpected */, true /* shown */, "IME is shown");
final var imeSwitcherButton = getUiObject(By.res(INPUT_METHOD_NAV_IME_SWITCHER_ID));
imeSwitcherButton.click();
mInstrumentation.waitForIdleSync();
- final var newInfo = mImm.getCurrentInputMethodInfo();
+ final var newSubtype = mImm.getCurrentInputMethodSubtype();
- assertWithMessage("Input Method Switcher Menu is shown or input method was switched")
- .that(isInputMethodPickerShown(mImm) || !Objects.equals(initialInfo, newInfo))
+ assertWithMessage("Input method subtype was switched")
+ .that(!Objects.equals(initialSubtype, newSubtype))
.isTrue();
assertWithMessage("IME is still shown after IME Switcher button was clicked")
.that(mInputMethodService.isInputViewShown()).isTrue();
-
- // Hide the IME Switcher Menu before finishing.
- mUiDevice.pressBack();
}
}
@@ -1027,16 +886,17 @@ public class InputMethodServiceTest {
setShowImeWithHardKeyboard(true /* enabled */);
+ final var info = mImm.getCurrentInputMethodInfo();
+ assertWithMessage("InputMethodInfo is not null").that(info).isNotNull();
+ mImm.setExplicitlyEnabledInputMethodSubtypes(info.getId(), SUBTYPE_IDS);
+
try (var ignored = mGestureNavSwitchHelper.withGestureNavigationMode()) {
verifyInputViewStatusOnMainSync(
() -> {
setDrawsImeNavBarAndSwitcherButton(true /* enabled */);
mActivity.showImeWithWindowInsetsController();
},
- EVENT_SHOW,
- true /* expected */,
- true /* inputViewStarted */);
- assertWithMessage("IME is shown").that(mInputMethodService.isInputViewShown()).isTrue();
+ EVENT_SHOW, true /* eventExpected */, true /* shown */, "IME is shown");
final var imeSwitcherButton = getUiObject(By.res(INPUT_METHOD_NAV_IME_SWITCHER_ID));
imeSwitcherButton.longClick();
@@ -1052,58 +912,78 @@ public class InputMethodServiceTest {
}
}
- private void verifyInputViewStatus(@NonNull Runnable runnable, @Event int event,
- boolean expected, boolean inputViewStarted) {
- verifyInputViewStatusInternal(runnable, event, expected, inputViewStarted,
- false /* runOnMainSync */);
+ private void verifyInputViewStatus(@NonNull Runnable runnable, @Event int eventType,
+ boolean eventExpected, boolean shown, @NonNull String message) {
+ verifyInputViewStatusInternal(runnable, eventType, eventExpected,
+ shown /* inputViewStarted */, shown, message, false /* runOnMainSync */);
+ }
+
+ private void verifyInputViewStatusOnMainSync(@NonNull Runnable runnable, @Event int eventType,
+ boolean eventExpected, boolean shown, @NonNull String message) {
+ verifyInputViewStatusInternal(runnable, eventType, eventExpected,
+ shown /* inputViewStarted */, shown, message, true /* runOnMainSync */);
}
- private void verifyInputViewStatusOnMainSync(@NonNull Runnable runnable, @Event int event,
- boolean expected, boolean inputViewStarted) {
- verifyInputViewStatusInternal(runnable, event, expected, inputViewStarted,
- true /* runOnMainSync */);
+ private void verifyInputViewStatusOnMainSync(@NonNull Runnable runnable, @Event int eventType,
+ boolean eventExpected, boolean inputViewStarted, boolean shown,
+ @NonNull String message) {
+ verifyInputViewStatusInternal(runnable, eventType, eventExpected, inputViewStarted, shown,
+ message, true /* runOnMainSync */);
}
/**
* Verifies the status of the Input View after executing the given runnable, and waiting that
- * the event was either triggered or not, based on the given expectation.
+ * the event was either called or not.
*
- * @param runnable the runnable to trigger the event
- * @param event the event to await.
- * @param expected whether the event is expected to be triggered.
- * @param inputViewStarted the expected state of the Input View after executing the runnable.
+ * @param runnable the runnable to call the event.
+ * @param eventType the event type to wait for.
+ * @param eventExpected whether the event is expected to be called.
+ * @param inputViewStarted whether the input view is expected to be started.
+ * @param shown whether the input view is expected to be shown.
+ * @param message the message for the input view shown assertion.
* @param runOnMainSync whether to execute the runnable on the main thread.
*/
- private void verifyInputViewStatusInternal(@NonNull Runnable runnable, @Event int event,
- boolean expected, boolean inputViewStarted, boolean runOnMainSync) {
- final boolean completed;
+ private void verifyInputViewStatusInternal(@NonNull Runnable runnable, @Event int eventType,
+ boolean eventExpected, boolean inputViewStarted, boolean shown, @NonNull String message,
+ boolean runOnMainSync) {
+ final boolean eventCalled;
try {
final var latch = new CountDownLatch(1);
- mInputMethodService.setCountDownLatchForTesting(latch, event);
+ mInputMethodService.setCountDownLatchForTesting(latch, eventType);
if (runOnMainSync) {
mInstrumentation.runOnMainSync(runnable);
} else {
runnable.run();
}
mInstrumentation.waitForIdleSync();
- completed = latch.await(TIMEOUT_IN_SECONDS, TimeUnit.SECONDS);
+ eventCalled = latch.await(TIMEOUT_IN_SECONDS, TimeUnit.SECONDS);
} catch (InterruptedException e) {
fail("Interrupted while waiting for latch: " + e.getMessage());
return;
} finally {
- mInputMethodService.setCountDownLatchForTesting(null /* latch */, event);
+ mInputMethodService.setCountDownLatchForTesting(null /* latch */, eventType);
}
- if (expected && !completed) {
- fail("Timed out waiting for " + eventToString(event));
- } else if (!expected && completed) {
- fail("Unexpected call " + eventToString(event));
+ if (eventExpected && !eventCalled) {
+ fail("Timed out waiting for " + eventToString(eventType));
+ } else if (!eventExpected && eventCalled) {
+ fail("Unexpected call " + eventToString(eventType));
}
- // Input is not finished.
+ // Input connection is not finished.
assertWithMessage("Input connection is still started")
.that(mInputMethodService.getCurrentInputStarted()).isTrue();
- assertWithMessage("IME visibility matches expected")
+ assertWithMessage("Input view started matches expected")
.that(mInputMethodService.getCurrentInputViewStarted()).isEqualTo(inputViewStarted);
+
+ if (mFlagsValueProvider.getBoolean(Flags.FLAG_REFACTOR_INSETS_CONTROLLER)) {
+ // The IME visibility is only sent at the end of the hide animation. Therefore, we have
+ // to wait until the visibility was sent to the server and the IME window hidden.
+ eventually(() -> assertWithMessage(message).that(mInputMethodService.isInputViewShown())
+ .isEqualTo(shown));
+ } else {
+ assertWithMessage(message).that(mInputMethodService.isInputViewShown())
+ .isEqualTo(shown);
+ }
}
private void setOrientation(int orientation) {
@@ -1127,31 +1007,28 @@ public class InputMethodServiceTest {
/**
* Verifies the IME fullscreen mode state after executing the given runnable.
*
- * @param runnable the runnable to execute for setting the orientation.
- * @param expected whether the runnable is expected to trigger the signal.
+ * @param runnable the runnable to set the orientation.
+ * @param eventExpected whether the event is expected to be called.
* @param orientationPortrait whether the orientation is expected to be portrait.
*/
- private void verifyFullscreenMode(@NonNull Runnable runnable, boolean expected,
+ private void verifyFullscreenMode(@NonNull Runnable runnable, boolean eventExpected,
boolean orientationPortrait) {
- verifyInputViewStatus(runnable, EVENT_CONFIG, expected, false /* inputViewStarted */);
- if (expected) {
+ 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.getLastCreatedInstance()).isNotEqualTo(mActivity));
+ .that(TestActivity.getInstance()).isNotEqualTo(mActivity));
// Get the new TestActivity.
- mActivity = TestActivity.getLastCreatedInstance();
+ 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(
- () -> mActivity.showImeWithWindowInsetsController(),
- EVENT_SHOW,
- true /* expected */,
- true /* inputViewStarted */);
- assertWithMessage("IME is shown").that(mInputMethodService.isInputViewShown()).isTrue();
+ verifyInputViewStatusOnMainSync(() -> mActivity.showImeWithWindowInsetsController(),
+ EVENT_SHOW, true /* eventExpected */, true /* shown */, "IME is shown");
assertWithMessage("IME orientation matches expected")
.that(mInputMethodService.getResources().getConfiguration().orientation)
@@ -1172,21 +1049,8 @@ public class InputMethodServiceTest {
.that(mInputMethodService.isFullscreenMode()).isEqualTo(!orientationPortrait);
// Hide IME before finishing the run.
- verifyInputViewStatusOnMainSync(
- () -> mActivity.hideImeWithWindowInsetsController(),
- EVENT_HIDE,
- true /* expected */,
- false /* inputViewStarted */);
-
- if (mFlagsValueProvider.getBoolean(Flags.FLAG_REFACTOR_INSETS_CONTROLLER)) {
- // The IME visibility is only sent at the end of the animation. Therefore, we have to
- // wait until the visibility was sent to the server and the IME window hidden.
- eventually(() -> assertWithMessage("IME is not shown")
- .that(mInputMethodService.isInputViewShown()).isFalse());
- } else {
- assertWithMessage("IME is not shown")
- .that(mInputMethodService.isInputViewShown()).isFalse();
- }
+ verifyInputViewStatusOnMainSync(() -> mActivity.hideImeWithWindowInsetsController(),
+ EVENT_HIDE, true /* eventExpected */, false /* shown */, "IME is not shown");
}
private void prepareIme() {
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java
index 3af5db9841ea..6af4064b1288 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java
@@ -66,6 +66,7 @@ import com.android.internal.inputmethod.IInputMethodSession;
import com.android.internal.inputmethod.IRemoteAccessibilityInputConnection;
import com.android.internal.inputmethod.IRemoteInputConnection;
import com.android.internal.inputmethod.InputBindResult;
+import com.android.internal.protolog.IProtoLogConfigurationService;
import com.android.internal.view.IInputMethodManager;
import com.android.server.LocalServices;
import com.android.server.ServiceThread;
@@ -124,6 +125,7 @@ public class InputMethodManagerServiceTestBase {
@Mock protected IBinder mMockInputMethodBinder;
@Mock protected IInputManager mMockIInputManager;
@Mock protected ImeTargetVisibilityPolicy mMockImeTargetVisibilityPolicy;
+ @Mock protected IProtoLogConfigurationService.Stub mMockProtoLogConfigurationService;
protected Context mContext;
protected MockitoSession mMockingSession;
@@ -186,6 +188,9 @@ public class InputMethodManagerServiceTestBase {
.when(() -> ServiceManager.getServiceOrThrow(Context.INPUT_METHOD_SERVICE));
doReturn(mMockIPlatformCompat)
.when(() -> ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE));
+ doReturn(mMockProtoLogConfigurationService)
+ .when(() -> ServiceManager.getServiceOrThrow(
+ Context.PROTOLOG_CONFIGURATION_SERVICE));
// Stubbing out context related methods to avoid the system holding strong references to
// InputMethodManagerService.
diff --git a/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/res/xml/method.xml b/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/res/xml/method.xml
index 872b0688814a..3dd4a97f464f 100644
--- a/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/res/xml/method.xml
+++ b/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/res/xml/method.xml
@@ -17,7 +17,14 @@
<input-method xmlns:android="http://schemas.android.com/apk/res/android">
<subtype
- android:label="FakeIme"
+ android:label="SimpleIme"
android:imeSubtypeLocale="en_US"
- android:imeSubtypeMode="keyboard"/>
+ android:imeSubtypeMode="keyboard"
+ android:subtypeId="1" />
+
+ <subtype
+ android:label="SimpleIme French"
+ android:imeSubtypeLocale="fr_FR"
+ android:imeSubtypeMode="keyboard"
+ android:subtypeId="2" />
</input-method> \ No newline at end of file
diff --git a/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/src/com/android/apps/inputmethod/simpleime/SimpleKeyboardView.java b/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/src/com/android/apps/inputmethod/simpleime/SimpleKeyboardView.java
index 55d1b451c6b0..1840cdea4c20 100644
--- a/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/src/com/android/apps/inputmethod/simpleime/SimpleKeyboardView.java
+++ b/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/src/com/android/apps/inputmethod/simpleime/SimpleKeyboardView.java
@@ -23,6 +23,7 @@ import android.util.Log;
import android.util.SparseArray;
import android.view.KeyEvent;
import android.view.LayoutInflater;
+import android.view.WindowInsets;
import android.widget.FrameLayout;
import android.widget.TextView;
@@ -107,6 +108,15 @@ final class SimpleKeyboardView extends FrameLayout {
mapSoftKeys();
}
+ @Override
+ public WindowInsets onApplyWindowInsets(WindowInsets insets) {
+ // Handle edge to edge for navigationBars insets (system nav bar)
+ // and captionBars insets (IME navigation bar).
+ final int insetBottom = insets.getInsets(WindowInsets.Type.systemBars()).bottom;
+ setPadding(getPaddingLeft(), getPaddingTop(), getPaddingRight(), insetBottom);
+ return insets.inset(0, 0, 0, insetBottom);
+ }
+
/**
* Sets the key press listener.
*
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 3a7abbb167ec..558d1a7c4e8b 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
@@ -27,6 +27,7 @@ import androidx.annotation.Nullable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.lang.ref.WeakReference;
import java.util.concurrent.CountDownLatch;
/** Wrapper of {@link InputMethodService} to expose interfaces for testing purpose. */
@@ -35,8 +36,8 @@ public class InputMethodServiceWrapper extends InputMethodService {
private static final String TAG = "InputMethodServiceWrapper";
/** Last created instance of this wrapper. */
- @Nullable
- private static InputMethodServiceWrapper sInstance;
+ @NonNull
+ private static WeakReference<InputMethodServiceWrapper> sInstance = new WeakReference<>(null);
/** IME show event ({@link #onStartInputView}). */
public static final int EVENT_SHOW = 0;
@@ -68,32 +69,11 @@ public class InputMethodServiceWrapper extends InputMethodService {
@Nullable
private CountDownLatch mCountDownLatch;
- /** Gets the last created instance of this wrapper, if available. */
- @Nullable
- public static InputMethodServiceWrapper getInstance() {
- return sInstance;
- }
-
- public boolean getCurrentInputViewStarted() {
- return mInputViewStarted;
- }
-
- /**
- * Sets the latch used to wait for the IME event.
- *
- * @param latch the latch to wait on.
- * @param latchEvent the event to set the latch on.
- */
- public void setCountDownLatchForTesting(@Nullable CountDownLatch latch, @Event int latchEvent) {
- mCountDownLatch = latch;
- mLatchEvent = latchEvent;
- }
-
@Override
public void onCreate() {
Log.i(TAG, "onCreate()");
super.onCreate();
- sInstance = this;
+ sInstance = new WeakReference<>(this);
}
@Override
@@ -103,6 +83,12 @@ public class InputMethodServiceWrapper extends InputMethodService {
}
@Override
+ public void onFinishInput() {
+ Log.i(TAG, "onFinishInput()");
+ super.onFinishInput();
+ }
+
+ @Override
public void onStartInputView(EditorInfo info, boolean restarting) {
Log.i(TAG, "onStartInputView() editor=" + dumpEditorInfo(info)
+ ", restarting=" + restarting);
@@ -114,12 +100,6 @@ public class InputMethodServiceWrapper extends InputMethodService {
}
@Override
- public void onFinishInput() {
- Log.i(TAG, "onFinishInput()");
- super.onFinishInput();
- }
-
- @Override
public void onFinishInputView(boolean finishingInput) {
Log.i(TAG, "onFinishInputView()");
super.onFinishInputView(finishingInput);
@@ -146,14 +126,35 @@ public class InputMethodServiceWrapper extends InputMethodService {
}
}
+ public boolean getCurrentInputViewStarted() {
+ return mInputViewStarted;
+ }
+
+ /**
+ * Sets the latch used to wait for the IME event.
+ *
+ * @param latch the latch to wait on.
+ * @param latchEvent the event to set the latch on.
+ */
+ public void setCountDownLatchForTesting(@Nullable CountDownLatch latch, @Event int latchEvent) {
+ mCountDownLatch = latch;
+ mLatchEvent = latchEvent;
+ }
+
+ /** Gets the last created instance of this wrapper, if available. */
+ @Nullable
+ public static InputMethodServiceWrapper getInstance() {
+ return sInstance.get();
+ }
+
/**
* Gets the string representation of the IME event that is being waited on.
*
- * @param event the IME event.
+ * @param eventType the IME event type.
*/
@NonNull
- public static String eventToString(@Event int event) {
- return switch (event) {
+ public static String eventToString(@Event int eventType) {
+ return switch (eventType) {
case EVENT_SHOW -> "onStartInputView";
case EVENT_HIDE -> "onFinishInputView";
case EVENT_CONFIG -> "onConfigurationChanged";
diff --git a/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/src/com/android/apps/inputmethod/simpleime/testing/TestActivity.java b/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/src/com/android/apps/inputmethod/simpleime/testing/TestActivity.java
index eadbac3ca74b..ad90b32848b0 100644
--- a/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/src/com/android/apps/inputmethod/simpleime/testing/TestActivity.java
+++ b/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/src/com/android/apps/inputmethod/simpleime/testing/TestActivity.java
@@ -45,30 +45,13 @@ import java.lang.ref.WeakReference;
public final class TestActivity extends Activity {
private static final String TAG = "TestActivity";
- private static WeakReference<TestActivity> sLastCreatedInstance = new WeakReference<>(null);
- /**
- * Start a new test activity with an editor and wait for it to begin running before returning.
- *
- * @param instrumentation application instrumentation
- * @return the newly started activity
- */
+ /** Last created instance of this activity. */
@NonNull
- public static TestActivity startSync(@NonNull Instrumentation instrumentation) {
- final var intent = new Intent()
- .setAction(Intent.ACTION_MAIN)
- .setClass(instrumentation.getTargetContext(), TestActivity.class)
- .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
- .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
- return (TestActivity) instrumentation.startActivitySync(intent);
- }
+ private static WeakReference<TestActivity> sInstance = new WeakReference<>(null);
private EditText mEditText;
- public EditText getEditText() {
- return mEditText;
- }
-
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -80,13 +63,11 @@ public final class TestActivity extends Activity {
rootView.setFitsSystemWindows(true);
setContentView(rootView);
mEditText.requestFocus();
- sLastCreatedInstance = new WeakReference<>(this);
+ sInstance = new WeakReference<>(this);
}
- /** Get the last created TestActivity instance, if available. */
- @Nullable
- public static TestActivity getLastCreatedInstance() {
- return sLastCreatedInstance.get();
+ public EditText getEditText() {
+ return mEditText;
}
/** Shows soft keyboard via InputMethodManager. */
@@ -118,4 +99,26 @@ public final class TestActivity extends Activity {
controller.hide(WindowInsets.Type.ime());
Log.i(TAG, "hideIme() via WindowInsetsController");
}
+
+ /** Gets the last created instance of this activity, if available. */
+ @Nullable
+ public static TestActivity getInstance() {
+ return sInstance.get();
+ }
+
+ /**
+ * Start a new test activity with an editor and wait for it to begin running before returning.
+ *
+ * @param instrumentation application instrumentation.
+ * @return the newly started activity.
+ */
+ @NonNull
+ public static TestActivity startSync(@NonNull Instrumentation instrumentation) {
+ final var intent = new Intent()
+ .setAction(Intent.ACTION_MAIN)
+ .setClass(instrumentation.getTargetContext(), TestActivity.class)
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ return (TestActivity) instrumentation.startActivitySync(intent);
+ }
}
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageParserTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageParserTest.java
index f5c0de034483..e1c65d27459e 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageParserTest.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageParserTest.java
@@ -65,10 +65,13 @@ import android.content.pm.SigningDetails;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
import android.platform.test.annotations.Presubmit;
import android.platform.test.annotations.RequiresFlagsEnabled;
import android.platform.test.flag.junit.CheckFlagsRule;
import android.platform.test.flag.junit.DeviceFlagsValueProvider;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.util.ArraySet;
import androidx.annotation.Nullable;
@@ -150,6 +153,9 @@ public class PackageParserTest {
@Rule
public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
private File mTmpDir;
private static final File FRAMEWORK = new File("/system/framework/framework-res.apk");
private static final String TEST_APP1_APK = "PackageParserTestApp1.apk";
@@ -846,7 +852,42 @@ public class PackageParserTest {
@Test
@RequiresFlagsEnabled(android.content.res.Flags.FLAG_MANIFEST_FLAGGING)
- public void testParseWithFeatureFlagAttributes() throws Exception {
+ @DisableFlags(android.content.res.Flags.FLAG_USE_NEW_ACONFIG_STORAGE)
+ public void testParseWithFeatureFlagAttributes_oldStorage() throws Exception {
+ final File testFile = extractFile(TEST_APP8_APK);
+ try (PackageParser2 parser = new TestPackageParser2()) {
+ Map<String, Boolean> flagValues = new HashMap<>();
+ flagValues.put("my.flag1", true);
+ flagValues.put("my.flag2", false);
+ flagValues.put("my.flag3", false);
+ flagValues.put("my.flag4", true);
+ ParsingPackageUtils.getAconfigFlags().addFlagValuesForTesting(flagValues);
+
+ // The manifest has:
+ // <permission android:name="PERM1" android:featureFlag="my.flag1 " />
+ // <permission android:name="PERM2" android:featureFlag=" !my.flag2" />
+ // <permission android:name="PERM3" android:featureFlag="my.flag3" />
+ // <permission android:name="PERM4" android:featureFlag="!my.flag4" />
+ // <permission android:name="PERM5" android:featureFlag="unknown.flag" />
+ // Therefore with the above flag values, only PERM1 and PERM2 should be present.
+
+ final ParsedPackage pkg = parser.parsePackage(testFile, 0, false);
+ List<String> permissionNames =
+ pkg.getPermissions().stream().map(ParsedComponent::getName).toList();
+ assertThat(permissionNames).contains(PACKAGE_NAME + ".PERM1");
+ assertThat(permissionNames).contains(PACKAGE_NAME + ".PERM2");
+ assertThat(permissionNames).doesNotContain(PACKAGE_NAME + ".PERM3");
+ assertThat(permissionNames).doesNotContain(PACKAGE_NAME + ".PERM4");
+ assertThat(permissionNames).doesNotContain(PACKAGE_NAME + ".PERM5");
+ } finally {
+ testFile.delete();
+ }
+ }
+
+ @Test
+ @RequiresFlagsEnabled(android.content.res.Flags.FLAG_MANIFEST_FLAGGING)
+ @EnableFlags(android.content.res.Flags.FLAG_USE_NEW_ACONFIG_STORAGE)
+ public void testParseWithFeatureFlagAttributes_newStorage() throws Exception {
final File testFile = extractFile(TEST_APP8_APK);
try (PackageParser2 parser = new TestPackageParser2()) {
Map<String, Boolean> flagValues = new HashMap<>();
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceTest.java
index 00296745a81f..38586155993e 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceTest.java
@@ -23,10 +23,17 @@ import static android.view.Surface.ROTATION_90;
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.isNull;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
import android.graphics.Point;
import android.graphics.Rect;
import android.platform.test.annotations.Presubmit;
import android.view.Display;
+import android.view.Surface;
import android.view.SurfaceControl;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -37,6 +44,7 @@ import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
+import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
/**
@@ -154,6 +162,65 @@ public class DisplayDeviceTest {
assertThat(displayDevice.getDisplaySurfaceDefaultSizeLocked()).isEqualTo(LANDSCAPE_SIZE);
}
+ @Test
+ public void testSetDisplaySize_landscapeInstallRotation() {
+ DisplayDevice displayDevice = new FakeDisplayDevice(mDisplayDeviceInfo,
+ mMockDisplayAdapter);
+ mDisplayDeviceInfo.installOrientation = Surface.ROTATION_0;
+ mDisplayDeviceInfo.width = 100;
+ mDisplayDeviceInfo.height = 200;
+ displayDevice.configureDisplaySizeLocked(mMockTransaction);
+ verify(mMockTransaction).setDisplaySize(isNull(), eq(100), eq(200));
+
+ Mockito.clearInvocations(mMockTransaction);
+
+ mDisplayDeviceInfo.installOrientation = Surface.ROTATION_180;
+ mDisplayDeviceInfo.width = 300;
+ mDisplayDeviceInfo.height = 400;
+ displayDevice.configureDisplaySizeLocked(mMockTransaction);
+ verify(mMockTransaction).setDisplaySize(isNull(), eq(300), eq(400));
+ }
+
+ @Test
+ public void testSetDisplaySize_portraitInstallRotation() {
+ DisplayDevice displayDevice = new FakeDisplayDevice(mDisplayDeviceInfo,
+ mMockDisplayAdapter);
+ mDisplayDeviceInfo.installOrientation = Surface.ROTATION_90;
+ mDisplayDeviceInfo.width = 100;
+ mDisplayDeviceInfo.height = 200;
+ displayDevice.configureDisplaySizeLocked(mMockTransaction);
+ verify(mMockTransaction).setDisplaySize(isNull(), eq(200), eq(100));
+
+ Mockito.clearInvocations(mMockTransaction);
+
+ mDisplayDeviceInfo.installOrientation = Surface.ROTATION_270;
+ mDisplayDeviceInfo.width = 300;
+ mDisplayDeviceInfo.height = 400;
+ displayDevice.configureDisplaySizeLocked(mMockTransaction);
+ verify(mMockTransaction).setDisplaySize(isNull(), eq(400), eq(300));
+ }
+
+ @Test
+ public void testSetDisplaySize_invokedOnlyAfterResize() {
+ DisplayDevice displayDevice = new FakeDisplayDevice(mDisplayDeviceInfo,
+ mMockDisplayAdapter);
+ mDisplayDeviceInfo.installOrientation = Surface.ROTATION_90;
+ mDisplayDeviceInfo.width = 100;
+ mDisplayDeviceInfo.height = 200;
+ displayDevice.configureDisplaySizeLocked(mMockTransaction);
+ verify(mMockTransaction).setDisplaySize(isNull(), eq(200), eq(100));
+
+ Mockito.clearInvocations(mMockTransaction);
+
+ displayDevice.configureDisplaySizeLocked(mMockTransaction);
+ verify(mMockTransaction, never()).setDisplaySize(isNull(), anyInt(), anyInt());
+
+ mDisplayDeviceInfo.width = 300;
+ mDisplayDeviceInfo.height = 400;
+ displayDevice.configureDisplaySizeLocked(mMockTransaction);
+ verify(mMockTransaction).setDisplaySize(isNull(), eq(400), eq(300));
+ }
+
private static class FakeDisplayDevice extends DisplayDevice {
private final DisplayDeviceInfo mDisplayDeviceInfo;
diff --git a/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java b/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java
index b9cea0c72306..f8b4113a3c04 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java
@@ -550,10 +550,10 @@ public class LocalDisplayAdapterTest {
assertDisplayDpi(
mListener.addedDisplays.get(0).getDisplayDeviceInfoLocked(), PORT_A, 100, 100,
- 100);
+ 136);
assertDisplayDpi(
mListener.addedDisplays.get(1).getDisplayDeviceInfoLocked(), PORT_B, 100, 100,
- 100);
+ 136);
}
private static class DisplayModeWrapper {
diff --git a/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayTest.java b/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayTest.java
index 1a0ab252f128..37f8aba1fb6c 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayTest.java
@@ -159,7 +159,8 @@ public class LogicalDisplayTest {
mDisplayDeviceInfo.type = Display.TYPE_INTERNAL;
mLogicalDisplay = new LogicalDisplay(DISPLAY_ID, LAYER_STACK, mDisplayDevice,
/*isAnisotropyCorrectionEnabled=*/ true,
- /*isAlwaysRotateDisplayDeviceEnabled=*/ true);
+ /*isAlwaysRotateDisplayDeviceEnabled=*/ true,
+ /*isSyncedResolutionSwitchEnabled=*/ true);
// In case of Anisotropy of pixels, then the content should be rescaled so it would adjust
// to using the whole screen. This is because display will rescale it back to fill the
@@ -188,7 +189,8 @@ public class LogicalDisplayTest {
mDisplayDeviceInfo.type = Display.TYPE_EXTERNAL;
mLogicalDisplay = new LogicalDisplay(DISPLAY_ID, LAYER_STACK, mDisplayDevice,
/*isAnisotropyCorrectionEnabled=*/ true,
- /*isAlwaysRotateDisplayDeviceEnabled=*/ true);
+ /*isAlwaysRotateDisplayDeviceEnabled=*/ true,
+ /*isSyncedResolutionSwitchEnabled=*/ true);
// In case of Anisotropy of pixels, then the content should be rescaled so it would adjust
// to using the whole screen. This is because display will rescale it back to fill the
@@ -217,7 +219,8 @@ public class LogicalDisplayTest {
mDisplayDeviceInfo.type = Display.TYPE_EXTERNAL;
mLogicalDisplay = new LogicalDisplay(DISPLAY_ID, LAYER_STACK, mDisplayDevice,
/*isAnisotropyCorrectionEnabled=*/ true,
- /*isAlwaysRotateDisplayDeviceEnabled=*/ true);
+ /*isAlwaysRotateDisplayDeviceEnabled=*/ true,
+ /*isSyncedResolutionSwitchEnabled=*/ true);
DisplayInfo displayInfo = new DisplayInfo();
displayInfo.logicalWidth = DISPLAY_WIDTH;
@@ -275,7 +278,8 @@ public class LogicalDisplayTest {
mDisplayDeviceInfo.type = Display.TYPE_EXTERNAL;
mLogicalDisplay = new LogicalDisplay(DISPLAY_ID, LAYER_STACK, mDisplayDevice,
/*isAnisotropyCorrectionEnabled=*/ true,
- /*isAlwaysRotateDisplayDeviceEnabled=*/ true);
+ /*isAlwaysRotateDisplayDeviceEnabled=*/ true,
+ /*isSyncedResolutionSwitchEnabled=*/ true);
DisplayInfo displayInfo = new DisplayInfo();
displayInfo.logicalWidth = DISPLAY_WIDTH;
@@ -304,7 +308,8 @@ public class LogicalDisplayTest {
mDisplayDeviceInfo.type = Display.TYPE_EXTERNAL;
mLogicalDisplay = new LogicalDisplay(DISPLAY_ID, LAYER_STACK, mDisplayDevice,
/*isAnisotropyCorrectionEnabled=*/ true,
- /*isAlwaysRotateDisplayDeviceEnabled=*/ true);
+ /*isAlwaysRotateDisplayDeviceEnabled=*/ true,
+ /*isSyncedResolutionSwitchEnabled=*/ true);
// In case of Anisotropy of pixels, then the content should be rescaled so it would adjust
// to using the whole screen. This is because display will rescale it back to fill the
@@ -379,11 +384,24 @@ public class LogicalDisplayTest {
}
@Test
+ public void testSetDisplaySizeIsCalledDuringConfigureDisplayLocked() {
+ mLogicalDisplay = new LogicalDisplay(DISPLAY_ID, LAYER_STACK, mDisplayDevice,
+ /*isAnisotropyCorrectionEnabled=*/ true,
+ /*isAlwaysRotateDisplayDeviceEnabled=*/ true,
+ /*isSyncedResolutionSwitchEnabled=*/ true);
+ mLogicalDisplay.updateLocked(mDeviceRepo, mSyntheticModeManager);
+ SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class);
+ mLogicalDisplay.configureDisplayLocked(t, mDisplayDevice, false);
+ verify(mDisplayDevice).configureDisplaySizeLocked(eq(t));
+ }
+
+ @Test
public void testGetDisplayPositionAlwaysRotateDisplayEnabled() {
mDisplayDeviceInfo.type = Display.TYPE_EXTERNAL;
mLogicalDisplay = new LogicalDisplay(DISPLAY_ID, LAYER_STACK, mDisplayDevice,
/*isAnisotropyCorrectionEnabled=*/ true,
- /*isAlwaysRotateDisplayDeviceEnabled=*/ true);
+ /*isAlwaysRotateDisplayDeviceEnabled=*/ true,
+ /*isSyncedResolutionSwitchEnabled=*/ true);
mLogicalDisplay.updateLocked(mDeviceRepo, mSyntheticModeManager);
Point expectedPosition = new Point();
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java
index 2cd105ba5317..67b26c1c0b00 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java
@@ -60,6 +60,8 @@ import android.content.ComponentName;
import android.content.pm.PackageManagerInternal;
import android.net.Uri;
import android.os.SystemClock;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.provider.MediaStore;
import android.util.SparseIntArray;
@@ -71,6 +73,7 @@ import com.android.server.job.JobSchedulerService;
import org.junit.After;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@@ -92,6 +95,9 @@ public class JobStatusTest {
private static final Uri IMAGES_MEDIA_URI = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
private static final Uri VIDEO_MEDIA_URI = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
@Mock
private JobSchedulerInternal mJobSchedulerInternal;
private MockitoSession mMockingSession;
@@ -1373,6 +1379,86 @@ public class JobStatusTest {
assertEquals("@TestNamespace@TestTag:foo", jobStatus.getBatteryName());
}
+ @Test
+ @EnableFlags({
+ com.android.server.job.Flags.FLAG_INCLUDE_TRACE_TAG_IN_JOB_NAME,
+ android.app.job.Flags.FLAG_JOB_DEBUG_INFO_APIS
+ })
+ public void testJobName_NotTagNoNamespace_IncludeTraceTagInJobNameEnabled() {
+ JobInfo jobInfo = new JobInfo.Builder(101, new ComponentName("foo", "bar"))
+ .setTraceTag("TestTraceTag")
+ .build();
+ JobStatus jobStatus = createJobStatus(jobInfo, null, -1, null, null);
+ assertEquals("#TestTraceTag#foo/bar", jobStatus.getBatteryName());
+ }
+
+ @Test
+ @EnableFlags({
+ com.android.server.job.Flags.FLAG_INCLUDE_TRACE_TAG_IN_JOB_NAME,
+ android.app.job.Flags.FLAG_JOB_DEBUG_INFO_APIS
+ })
+ public void testJobName_NoTagWithNamespace_IncludeTraceTagInJobNameEnabled() {
+ JobInfo jobInfo = new JobInfo.Builder(101, new ComponentName("foo", "bar"))
+ .setTraceTag("TestTraceTag")
+ .build();
+ JobStatus jobStatus = createJobStatus(jobInfo, null, -1, "TestNamespace", null);
+ assertEquals("#TestTraceTag#@TestNamespace@foo/bar", jobStatus.getBatteryName());
+ }
+
+ @Test
+ @EnableFlags({
+ com.android.server.job.Flags.FLAG_INCLUDE_TRACE_TAG_IN_JOB_NAME,
+ android.app.job.Flags.FLAG_JOB_DEBUG_INFO_APIS
+ })
+ public void testJobName_WithTagNoNamespace_IncludeTraceTagInJobNameEnabled() {
+ JobInfo jobInfo = new JobInfo.Builder(101, new ComponentName("foo", "bar"))
+ .setTraceTag("TestTraceTag")
+ .build();
+ JobStatus jobStatus = createJobStatus(jobInfo, SOURCE_PACKAGE, 0, null, "TestTag");
+ assertEquals("#TestTraceTag#TestTag:foo", jobStatus.getBatteryName());
+ }
+
+ @Test
+ @EnableFlags({
+ com.android.server.job.Flags.FLAG_INCLUDE_TRACE_TAG_IN_JOB_NAME,
+ android.app.job.Flags.FLAG_JOB_DEBUG_INFO_APIS
+ })
+ public void testJobName_FilteredTraceTagEmail_IncludeTraceTagInJobNameEnabled() {
+ JobInfo jobInfo = new JobInfo.Builder(101, new ComponentName("foo", "bar"))
+ .setTraceTag("test@email.com")
+ .build();
+ JobStatus jobStatus = createJobStatus(jobInfo, SOURCE_PACKAGE, 0, null, "TestTag");
+ assertEquals("#[EMAIL]#TestTag:foo", jobStatus.getBatteryName());
+ }
+
+ @Test
+ @EnableFlags({
+ com.android.server.job.Flags.FLAG_INCLUDE_TRACE_TAG_IN_JOB_NAME,
+ android.app.job.Flags.FLAG_JOB_DEBUG_INFO_APIS
+ })
+ public void testJobName_FilteredTraceTagPhone_IncludeTraceTagInJobNameEnabled() {
+ JobInfo jobInfo = new JobInfo.Builder(101, new ComponentName("foo", "bar"))
+ .setTraceTag("123-456-7890")
+ .build();
+ JobStatus jobStatus = createJobStatus(jobInfo, SOURCE_PACKAGE, 0, null, "TestTag");
+ assertEquals("#[PHONE]#TestTag:foo", jobStatus.getBatteryName());
+ }
+
+ @Test
+ @EnableFlags({
+ com.android.server.job.Flags.FLAG_INCLUDE_TRACE_TAG_IN_JOB_NAME,
+ android.app.job.Flags.FLAG_JOB_DEBUG_INFO_APIS
+ })
+ public void testJobName_WithTagAndNamespace_IncludeTraceTagInJobNameEnabled() {
+ JobInfo jobInfo =
+ new JobInfo.Builder(101, new ComponentName("foo", "bar"))
+ .setTraceTag("TestTraceTag")
+ .build();
+ JobStatus jobStatus =
+ createJobStatus(jobInfo, SOURCE_PACKAGE, 0, "TestNamespace", "TestTag");
+ assertEquals("#TestTraceTag#@TestNamespace@TestTag:foo", jobStatus.getBatteryName());
+ }
+
private void markExpeditedQuotaApproved(JobStatus job, boolean isApproved) {
if (job.isRequestedExpeditedJob()) {
job.setExpeditedJobQuotaApproved(sElapsedRealtimeClock.millis(), isApproved);
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/security/intrusiondetection/src/com/android/server/security/intrusiondetection/IntrusionDetectionServiceTest.java b/services/tests/security/intrusiondetection/src/com/android/server/security/intrusiondetection/IntrusionDetectionServiceTest.java
index 298d27e2e8c4..879aa4893802 100644
--- a/services/tests/security/intrusiondetection/src/com/android/server/security/intrusiondetection/IntrusionDetectionServiceTest.java
+++ b/services/tests/security/intrusiondetection/src/com/android/server/security/intrusiondetection/IntrusionDetectionServiceTest.java
@@ -17,7 +17,6 @@
package com.android.server.security.intrusiondetection;
import static android.Manifest.permission.BIND_INTRUSION_DETECTION_EVENT_TRANSPORT_SERVICE;
-import static android.Manifest.permission.INTERNET;
import static android.Manifest.permission.MANAGE_INTRUSION_DETECTION_STATE;
import static android.Manifest.permission.READ_INTRUSION_DETECTION_STATE;
@@ -28,7 +27,6 @@ import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.spy;
@@ -37,8 +35,8 @@ import static org.mockito.Mockito.verify;
import android.annotation.SuppressLint;
import android.app.admin.ConnectEvent;
+import android.app.admin.DevicePolicyManagerInternal;
import android.app.admin.DnsEvent;
-import android.app.admin.SecurityLog;
import android.app.admin.SecurityLog.SecurityEvent;
import android.content.ComponentName;
import android.content.Context;
@@ -53,37 +51,22 @@ import android.os.test.TestLooper;
import android.security.intrusiondetection.IIntrusionDetectionServiceCommandCallback;
import android.security.intrusiondetection.IIntrusionDetectionServiceStateCallback;
import android.security.intrusiondetection.IntrusionDetectionEvent;
-import android.security.keystore.KeyGenParameterSpec;
-import android.security.keystore.KeyProperties;
import android.util.Log;
import androidx.test.core.app.ApplicationProvider;
import com.android.bedstead.harrier.BedsteadJUnit4;
import com.android.bedstead.multiuser.annotations.RequireRunOnSystemUser;
-import com.android.bedstead.nene.TestApis;
-import com.android.bedstead.nene.devicepolicy.DeviceOwner;
import com.android.bedstead.permissions.CommonPermissions;
-import com.android.bedstead.permissions.PermissionContext;
import com.android.bedstead.permissions.annotations.EnsureHasPermission;
import com.android.coretests.apps.testapp.LocalIntrusionDetectionEventTransport;
import com.android.server.ServiceThread;
import org.junit.Before;
-import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
-import org.mockito.invocation.InvocationOnMock;
-import org.mockito.stubbing.Answer;
-
-import java.io.IOException;
-import java.net.HttpURLConnection;
-import java.net.URL;
-import java.security.GeneralSecurityException;
-import java.security.KeyPair;
-import java.security.KeyPairGenerator;
-import java.security.KeyStore;
+
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
@@ -107,7 +90,6 @@ public class IntrusionDetectionServiceTest {
private static final int ERROR_DATA_SOURCE_UNAVAILABLE =
IIntrusionDetectionServiceCommandCallback.ErrorCode.DATA_SOURCE_UNAVAILABLE;
- private static DeviceOwner sDeviceOwner;
private Context mContext;
private IntrusionDetectionEventTransportConnection mIntrusionDetectionEventTransportConnection;
@@ -124,6 +106,8 @@ public class IntrusionDetectionServiceTest {
"com.android.coretests.apps.testapp";
private static final String TEST_SERVICE = TEST_PKG + ".TestLoggingService";
+ DevicePolicyManagerInternal mDevicePolicyManagerInternal;
+
@SuppressLint("VisibleForTests")
@Before
public void setUp() throws Exception {
@@ -189,6 +173,7 @@ public class IntrusionDetectionServiceTest {
}
@Test
+ @EnsureHasPermission(CommonPermissions.MANAGE_DEVICE_POLICY_AUDIT_LOGGING)
public void testAddStateCallback_Disabled_TwoStateCallbacks() throws RemoteException {
StateCallback scb1 = new StateCallback();
assertEquals(STATE_UNKNOWN, scb1.mState);
@@ -204,7 +189,7 @@ public class IntrusionDetectionServiceTest {
}
@Test
- @Ignore("Unit test does not run as system service UID")
+ @EnsureHasPermission(CommonPermissions.MANAGE_DEVICE_POLICY_AUDIT_LOGGING)
public void testRemoveStateCallback() throws RemoteException {
mIntrusionDetectionService.setState(STATE_DISABLED);
StateCallback scb1 = new StateCallback();
@@ -220,15 +205,19 @@ public class IntrusionDetectionServiceTest {
mIntrusionDetectionService.getBinderService().removeStateCallback(scb2);
CommandCallback ccb = new CommandCallback();
+
+ // Enable will fail; caller does not run as system server.
+ doNothing().when(mDataAggregator).enable();
mIntrusionDetectionService.getBinderService().enable(ccb);
+
mTestLooper.dispatchAll();
assertEquals(STATE_ENABLED, scb1.mState);
assertEquals(STATE_DISABLED, scb2.mState);
assertNull(ccb.mErrorCode);
}
- @Ignore("Unit test does not run as system service UID")
@Test
+ @EnsureHasPermission(CommonPermissions.MANAGE_DEVICE_POLICY_AUDIT_LOGGING)
public void testEnable_FromDisabled_TwoStateCallbacks() throws RemoteException {
mIntrusionDetectionService.setState(STATE_DISABLED);
StateCallback scb1 = new StateCallback();
@@ -243,6 +232,9 @@ public class IntrusionDetectionServiceTest {
CommandCallback ccb = new CommandCallback();
mIntrusionDetectionService.getBinderService().enable(ccb);
+
+ // Enable will fail; caller does not run as system server.
+ doNothing().when(mDataAggregator).enable();
mTestLooper.dispatchAll();
verify(mDataAggregator, times(1)).enable();
@@ -319,7 +311,7 @@ public class IntrusionDetectionServiceTest {
assertNull(ccb.mErrorCode);
}
- @Ignore("Enable once the IntrusionDetectionEventTransportConnection is ready")
+ @EnsureHasPermission(CommonPermissions.MANAGE_DEVICE_POLICY_AUDIT_LOGGING)
@Test
public void testEnable_FromDisable_TwoStateCallbacks_TransportUnavailable()
throws RemoteException {
@@ -390,146 +382,6 @@ public class IntrusionDetectionServiceTest {
}
@Test
- @Ignore("Unit test does not run as system service UID")
- @RequireRunOnSystemUser
- @EnsureHasPermission(CommonPermissions.MANAGE_DEVICE_POLICY_AUDIT_LOGGING)
- public void testDataAggregator_AddSecurityEvent() throws Exception {
- mIntrusionDetectionService.setState(STATE_ENABLED);
- ServiceThread mockThread = spy(ServiceThread.class);
- mDataAggregator.setHandler(mLooperOfDataAggregator, mockThread);
-
- // SecurityLogging generates a number of events and callbacks, so create a latch to wait for
- // the given event.
- String eventString = this.getClass().getName() + ".testSecurityEvent";
-
- final CountDownLatch latch = new CountDownLatch(1);
- // TODO: Replace this mock when the IntrusionDetectionEventTransportConnection is ready.
- doAnswer(
- new Answer<Boolean>() {
- @Override
- public Boolean answer(InvocationOnMock input) {
- List<IntrusionDetectionEvent> receivedEvents =
- (List<IntrusionDetectionEvent>) input.getArguments()[0];
- for (IntrusionDetectionEvent event : receivedEvents) {
- if (event.getType() == IntrusionDetectionEvent.SECURITY_EVENT) {
- SecurityEvent securityEvent = event.getSecurityEvent();
- Object[] eventData = (Object[]) securityEvent.getData();
- if (securityEvent.getTag() == SecurityLog.TAG_KEY_GENERATED
- && eventData[1].equals(eventString)) {
- latch.countDown();
- }
- }
- }
- return true;
- }
- })
- .when(mIntrusionDetectionEventTransportConnection).addData(any());
- mDataAggregator.enable();
-
- // Generate the security event.
- generateSecurityEvent(eventString);
- TestApis.devicePolicy().forceSecurityLogs();
-
- // Verify the event is received.
- mTestLooper.startAutoDispatch();
- assertTrue(latch.await(1, TimeUnit.SECONDS));
- mTestLooper.stopAutoDispatch();
-
- mDataAggregator.disable();
- }
-
- @Test
- @RequireRunOnSystemUser
- @Ignore("Unit test does not run as system service UID")
- @EnsureHasPermission(CommonPermissions.MANAGE_DEVICE_POLICY_AUDIT_LOGGING)
- public void testDataAggregator_AddNetworkEvent() throws Exception {
- mIntrusionDetectionService.setState(STATE_ENABLED);
- ServiceThread mockThread = spy(ServiceThread.class);
- mDataAggregator.setHandler(mLooperOfDataAggregator, mockThread);
-
- // Network logging may log multiple and callbacks, so create a latch to wait for
- // the given event.
- // eventServer must be a valid domain to generate a network log event.
- String eventServer = "google.com";
- final CountDownLatch latch = new CountDownLatch(1);
- // TODO: Replace this mock when the IntrusionDetectionEventTransportConnection is ready.
- doAnswer(
- new Answer<Boolean>() {
- @Override
- public Boolean answer(InvocationOnMock input) {
- List<IntrusionDetectionEvent> receivedEvents =
- (List<IntrusionDetectionEvent>) input.getArguments()[0];
- for (IntrusionDetectionEvent event : receivedEvents) {
- if (event.getType()
- == IntrusionDetectionEvent.NETWORK_EVENT_DNS) {
- DnsEvent dnsEvent = event.getDnsEvent();
- if (dnsEvent.getHostname().equals(eventServer)) {
- latch.countDown();
- }
- }
- }
- return true;
- }
- })
- .when(mIntrusionDetectionEventTransportConnection).addData(any());
- mDataAggregator.enable();
-
- // Generate the network event.
- generateNetworkEvent(eventServer);
- TestApis.devicePolicy().forceNetworkLogs();
-
- // Verify the event is received.
- mTestLooper.startAutoDispatch();
- assertTrue(latch.await(1, TimeUnit.SECONDS));
- mTestLooper.stopAutoDispatch();
-
- mDataAggregator.disable();
- }
-
- /** Emits a given string into security log (if enabled). */
- private void generateSecurityEvent(String eventString)
- throws IllegalArgumentException, GeneralSecurityException, IOException {
- if (eventString == null || eventString.isEmpty()) {
- throw new IllegalArgumentException(
- "Error generating security event: eventString must not be empty");
- }
-
- final KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA", "AndroidKeyStore");
- keyGen.initialize(
- new KeyGenParameterSpec.Builder(eventString, KeyProperties.PURPOSE_SIGN).build());
- // Emit key generation event.
- final KeyPair keyPair = keyGen.generateKeyPair();
- assertNotNull(keyPair);
-
- final KeyStore ks = KeyStore.getInstance("AndroidKeyStore");
- ks.load(null);
- // Emit key destruction event.
- ks.deleteEntry(eventString);
- }
-
- /** Emits a given string into network log (if enabled). */
- private void generateNetworkEvent(String server) throws IllegalArgumentException, IOException {
- if (server == null || server.isEmpty()) {
- throw new IllegalArgumentException(
- "Error generating network event: server must not be empty");
- }
-
- HttpURLConnection urlConnection = null;
- int connectionTimeoutMS = 2_000;
- try (PermissionContext p = TestApis.permissions().withPermission(INTERNET)) {
- final URL url = new URL("http://" + server);
- urlConnection = (HttpURLConnection) url.openConnection();
- urlConnection.setConnectTimeout(connectionTimeoutMS);
- urlConnection.setReadTimeout(connectionTimeoutMS);
- urlConnection.getResponseCode();
- } finally {
- if (urlConnection != null) {
- urlConnection.disconnect();
- }
- }
- }
-
- @Test
@RequireRunOnSystemUser
@EnsureHasPermission(
android.Manifest.permission.BIND_INTRUSION_DETECTION_EVENT_TRANSPORT_SERVICE)
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickControllerTest.java
index 0227ef1d2dc0..7f60caaa569b 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickControllerTest.java
@@ -326,6 +326,80 @@ public class AutoclickControllerTest {
assertThat(mController.mClickScheduler.getIsActiveForTesting()).isTrue();
}
+ @Test
+ public void smallJitteryMovement_doesNotTriggerClick() {
+ // Initial hover move to set an anchor point.
+ MotionEvent initialHoverMove = MotionEvent.obtain(
+ /* downTime= */ 0,
+ /* eventTime= */ 100,
+ /* action= */ MotionEvent.ACTION_HOVER_MOVE,
+ /* x= */ 30f,
+ /* y= */ 40f,
+ /* metaState= */ 0);
+ initialHoverMove.setSource(InputDevice.SOURCE_MOUSE);
+ mController.onMotionEvent(initialHoverMove, initialHoverMove, /* policyFlags= */ 0);
+
+ // Get the initial scheduled click time.
+ long initialScheduledTime = mController.mClickScheduler.getScheduledClickTimeForTesting();
+
+ // Simulate small, jittery movements (all within the default slop).
+ MotionEvent jitteryMove1 = MotionEvent.obtain(
+ /* downTime= */ 0,
+ /* eventTime= */ 150,
+ /* action= */ MotionEvent.ACTION_HOVER_MOVE,
+ /* x= */ 31f, // Small change in x
+ /* y= */ 41f, // Small change in y
+ /* metaState= */ 0);
+ jitteryMove1.setSource(InputDevice.SOURCE_MOUSE);
+ mController.onMotionEvent(jitteryMove1, jitteryMove1, /* policyFlags= */ 0);
+
+ MotionEvent jitteryMove2 = MotionEvent.obtain(
+ /* downTime= */ 0,
+ /* eventTime= */ 200,
+ /* action= */ MotionEvent.ACTION_HOVER_MOVE,
+ /* x= */ 30.5f, // Small change in x
+ /* y= */ 39.8f, // Small change in y
+ /* metaState= */ 0);
+ jitteryMove2.setSource(InputDevice.SOURCE_MOUSE);
+ mController.onMotionEvent(jitteryMove2, jitteryMove2, /* policyFlags= */ 0);
+
+ // Verify that the scheduled click time has NOT changed.
+ assertThat(mController.mClickScheduler.getScheduledClickTimeForTesting())
+ .isEqualTo(initialScheduledTime);
+ }
+
+ @Test
+ public void singleSignificantMovement_triggersClick() {
+ // Initial hover move to set an anchor point.
+ MotionEvent initialHoverMove = MotionEvent.obtain(
+ /* downTime= */ 0,
+ /* eventTime= */ 100,
+ /* action= */ MotionEvent.ACTION_HOVER_MOVE,
+ /* x= */ 30f,
+ /* y= */ 40f,
+ /* metaState= */ 0);
+ initialHoverMove.setSource(InputDevice.SOURCE_MOUSE);
+ mController.onMotionEvent(initialHoverMove, initialHoverMove, /* policyFlags= */ 0);
+
+ // Get the initial scheduled click time.
+ long initialScheduledTime = mController.mClickScheduler.getScheduledClickTimeForTesting();
+
+ // Simulate a single, significant movement (greater than the default slop).
+ MotionEvent significantMove = MotionEvent.obtain(
+ /* downTime= */ 0,
+ /* eventTime= */ 150,
+ /* action= */ MotionEvent.ACTION_HOVER_MOVE,
+ /* x= */ 60f, // Significant change in x (30f difference)
+ /* y= */ 70f, // Significant change in y (30f difference)
+ /* metaState= */ 0);
+ significantMove.setSource(InputDevice.SOURCE_MOUSE);
+ mController.onMotionEvent(significantMove, significantMove, /* policyFlags= */ 0);
+
+ // Verify that the scheduled click time has changed (click was rescheduled).
+ assertThat(mController.mClickScheduler.getScheduledClickTimeForTesting())
+ .isNotEqualTo(initialScheduledTime);
+ }
+
private void injectFakeMouseActionHoverMoveEvent() {
MotionEvent event = getFakeMotionHoverMoveEvent();
event.setSource(InputDevice.SOURCE_MOUSE);
diff --git a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
index 06958b81d846..1627f683cd3e 100644
--- a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
@@ -25,6 +25,7 @@ import static android.app.ActivityManagerInternal.ALLOW_FULL_ONLY;
import static android.app.ActivityManagerInternal.ALLOW_NON_FULL;
import static android.app.ActivityManagerInternal.ALLOW_NON_FULL_IN_PROFILE;
import static android.app.ActivityManagerInternal.ALLOW_PROFILES_OR_NON_FULL;
+import static android.app.KeyguardManager.LOCK_ON_USER_SWITCH_CALLBACK;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.testing.DexmakerShareClassLoaderRule.runWithDexmakerShareClassLoader;
@@ -115,7 +116,6 @@ import com.android.server.pm.UserManagerInternal;
import com.android.server.pm.UserManagerService;
import com.android.server.pm.UserTypeDetails;
import com.android.server.pm.UserTypeFactory;
-import com.android.server.wm.ActivityTaskManagerInternal;
import com.android.server.wm.WindowManagerService;
import com.google.common.collect.Range;
@@ -1563,11 +1563,11 @@ public class UserControllerTest {
// and the thread is still alive
assertTrue(threadStartUser.isAlive());
- // mock send the keyguard shown event
- ArgumentCaptor<ActivityTaskManagerInternal.ScreenObserver> captor = ArgumentCaptor.forClass(
- ActivityTaskManagerInternal.ScreenObserver.class);
- verify(mInjector.mActivityTaskManagerInternal).registerScreenObserver(captor.capture());
- captor.getValue().onKeyguardStateChanged(true);
+ // mock the binder response for the user switch completion
+ ArgumentCaptor<Bundle> captor = ArgumentCaptor.forClass(Bundle.class);
+ verify(mInjector.mWindowManagerMock).lockNow(captor.capture());
+ IRemoteCallback.Stub.asInterface(captor.getValue().getBinder(
+ LOCK_ON_USER_SWITCH_CALLBACK)).sendResult(null);
// verify the switch now moves on...
Thread.sleep(1000);
@@ -1757,7 +1757,6 @@ public class UserControllerTest {
private final IStorageManager mStorageManagerMock;
private final UserManagerInternal mUserManagerInternalMock;
private final WindowManagerService mWindowManagerMock;
- private final ActivityTaskManagerInternal mActivityTaskManagerInternal;
private final PowerManagerInternal mPowerManagerInternal;
private final AlarmManagerInternal mAlarmManagerInternal;
private final KeyguardManager mKeyguardManagerMock;
@@ -1779,7 +1778,6 @@ public class UserControllerTest {
mUserManagerMock = mock(UserManagerService.class);
mUserManagerInternalMock = mock(UserManagerInternal.class);
mWindowManagerMock = mock(WindowManagerService.class);
- mActivityTaskManagerInternal = mock(ActivityTaskManagerInternal.class);
mStorageManagerMock = mock(IStorageManager.class);
mPowerManagerInternal = mock(PowerManagerInternal.class);
mAlarmManagerInternal = mock(AlarmManagerInternal.class);
@@ -1843,11 +1841,6 @@ public class UserControllerTest {
}
@Override
- ActivityTaskManagerInternal getActivityTaskManagerInternal() {
- return mActivityTaskManagerInternal;
- }
-
- @Override
PowerManagerInternal getPowerManagerInternal() {
return mPowerManagerInternal;
}
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/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/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/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 0373eb6e9318..858dd3a605d8 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
@@ -5383,6 +5385,42 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
}
@Test
+ public void testGetPackagesWithChannels_blocked() throws Exception {
+ // While we mostly rely on the PreferencesHelper implementation of channels, we filter in
+ // NMS so that we do not return blocked packages.
+ // Three packages; all under user 1.
+ // pkg2 is blocked, but pkg1 and pkg3 are not.
+ String pkg1 = "com.package.one", pkg2 = "com.package.two", pkg3 = "com.package.three";
+ int uid1 = UserHandle.getUid(1, 111);
+ int uid2 = UserHandle.getUid(1, 222);
+ int uid3 = UserHandle.getUid(1, 333);
+
+ when(mPackageManager.getPackageUid(eq(pkg1), anyLong(), anyInt())).thenReturn(uid1);
+ when(mPackageManager.getPackageUid(eq(pkg2), anyLong(), anyInt())).thenReturn(uid2);
+ when(mPackageManager.getPackageUid(eq(pkg3), anyLong(), anyInt())).thenReturn(uid3);
+ when(mPermissionHelper.hasPermission(uid1)).thenReturn(true);
+ when(mPermissionHelper.hasPermission(uid2)).thenReturn(false);
+ when(mPermissionHelper.hasPermission(uid3)).thenReturn(true);
+
+ NotificationChannel channel1 = new NotificationChannel("id1", "name1",
+ NotificationManager.IMPORTANCE_DEFAULT);
+ NotificationChannel channel2 = new NotificationChannel("id3", "name3",
+ NotificationManager.IMPORTANCE_DEFAULT);
+ NotificationChannel channel3 = new NotificationChannel("id4", "name3",
+ NotificationManager.IMPORTANCE_DEFAULT);
+ mService.mPreferencesHelper.createNotificationChannel(pkg1, uid1, channel1, true, false,
+ uid1, false);
+ mService.mPreferencesHelper.createNotificationChannel(pkg2, uid2, channel2, true, false,
+ uid2, false);
+ mService.mPreferencesHelper.createNotificationChannel(pkg3, uid3, channel3, true, false,
+ uid3, false);
+
+ // Output should contain only the package with notification permissions (1, 3).
+ enableInteractAcrossUsers();
+ assertThat(mBinderService.getPackagesWithAnyChannels(1)).containsExactly(pkg1, pkg3);
+ }
+
+ @Test
public void testHasCompanionDevice_failure() throws Exception {
when(mCompanionMgr.getAssociations(anyString(), anyInt())).thenThrow(
new IllegalArgumentException());
@@ -16287,6 +16325,20 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
}
@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;
@@ -16303,6 +16355,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;
@@ -16321,6 +16392,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;
@@ -16339,6 +16431,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;
@@ -16356,6 +16469,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;
@@ -16372,10 +16505,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;
@@ -16394,7 +16547,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;
@@ -16411,6 +16589,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);
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 3f26cd9258af..640de174ba20 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
@@ -3096,6 +3096,67 @@ public class PreferencesHelperTest extends UiServiceTestCase {
}
@Test
+ public void getPackagesWithAnyChannels_noChannels() {
+ assertThat(mHelper.getPackagesWithAnyChannels(UserHandle.getUserId(UID_O))).isEmpty();
+ }
+
+ @Test
+ public void getPackagesWithAnyChannels_someChannels() {
+ // 2 channels under PKG_N_MR1, 1 under PKG_O
+ NotificationChannel channel1 = new NotificationChannel("1", "something",
+ IMPORTANCE_DEFAULT);
+ mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel1, true, false, UID_N_MR1,
+ false);
+ NotificationChannel channel2 = new NotificationChannel("2", "another", IMPORTANCE_DEFAULT);
+ mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel2, true, false, UID_N_MR1,
+ false);
+
+ NotificationChannel other = new NotificationChannel("3", "still another",
+ IMPORTANCE_DEFAULT);
+ mHelper.createNotificationChannel(PKG_O, UID_O, other, true, false, UID_O, false);
+
+ assertThat(mHelper.getPackagesWithAnyChannels(USER.getIdentifier())).containsExactly(
+ PKG_N_MR1, PKG_O);
+ }
+
+ @Test
+ public void getPackagesWithAnyChannels_onlyDeleted() {
+ NotificationChannel channel1 = new NotificationChannel("1", "something",
+ IMPORTANCE_DEFAULT);
+ channel1.setDeleted(true);
+ mHelper.createNotificationChannel(PKG_O, UID_O, channel1, true, false, UID_O,
+ false);
+ NotificationChannel channel2 = new NotificationChannel("2", "another", IMPORTANCE_DEFAULT);
+ channel2.setDeleted(true);
+ mHelper.createNotificationChannel(PKG_O, UID_O, channel2, true, false, UID_O,
+ false);
+
+ assertThat(mHelper.getPackagesWithAnyChannels(UserHandle.getUserId(UID_O))).isEmpty();
+ }
+
+ @Test
+ public void getPackagesWithAnyChannels_distinguishesUsers() throws Exception {
+ // Set a package up for both users 0 and 10
+ String pkgName = "test.package";
+ int uid0 = UserHandle.getUid(0, 1234);
+ int uid10 = UserHandle.getUid(10, 1234);
+ setUpPackageWithUid(pkgName, uid0);
+ setUpPackageWithUid(pkgName, uid10);
+
+ // but only user 10 has channels
+ NotificationChannel channel1 = new NotificationChannel("1", "something",
+ IMPORTANCE_DEFAULT);
+ mHelper.createNotificationChannel(pkgName, uid10, channel1, true, false, uid10,
+ false);
+ NotificationChannel channel2 = new NotificationChannel("2", "another", IMPORTANCE_DEFAULT);
+ mHelper.createNotificationChannel(pkgName, uid10, channel2, true, false, uid10,
+ false);
+
+ assertThat(mHelper.getPackagesWithAnyChannels(0)).isEmpty();
+ assertThat(mHelper.getPackagesWithAnyChannels(10)).containsExactly(pkgName);
+ }
+
+ @Test
public void testOnlyHasDefaultChannel() throws Exception {
assertTrue(mHelper.onlyHasDefaultChannel(PKG_N_MR1, UID_N_MR1));
assertFalse(mHelper.onlyHasDefaultChannel(PKG_O, UID_O));
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..e6c3fb369b91 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DesktopModeHelperTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DesktopModeHelperTest.java
@@ -177,22 +177,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 +221,7 @@ public class DesktopModeHelperTest {
eq(R.bool.config_isDesktopModeDevOptionSupported)
);
- assertThat(DesktopModeHelper.isInternalDisplayEligibleToHostDesktops(mMockContext)).isTrue();
+ assertThat(DesktopModeHelper.isDeviceEligibleForDesktopMode(mMockContext)).isTrue();
}
private void resetEnforceDeviceRestriction() throws Exception {
diff --git a/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java
deleted file mode 100644
index 20381ba21758..000000000000
--- a/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java
+++ /dev/null
@@ -1,757 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wm;
-
-import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
-import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
-import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR;
-import static android.view.WindowManager.TRANSIT_OLD_ACTIVITY_OPEN;
-import static android.view.WindowManager.TRANSIT_OLD_KEYGUARD_GOING_AWAY;
-import static android.view.WindowManager.TRANSIT_OLD_KEYGUARD_GOING_AWAY_ON_WALLPAPER;
-import static android.view.WindowManager.TRANSIT_OLD_NONE;
-import static android.view.WindowManager.TRANSIT_OLD_TASK_CHANGE_WINDOWING_MODE;
-import static android.view.WindowManager.TRANSIT_OLD_TASK_OPEN;
-
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.atLeast;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.never;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.verifyNoMoreInteractions;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
-import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION;
-import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_WINDOW_ANIMATION;
-
-import static junit.framework.Assert.fail;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.mock;
-
-import android.graphics.Point;
-import android.graphics.Rect;
-import android.os.Binder;
-import android.os.IBinder;
-import android.os.IInterface;
-import android.os.RemoteException;
-import android.platform.test.annotations.Presubmit;
-import android.view.IRemoteAnimationFinishedCallback;
-import android.view.IRemoteAnimationRunner;
-import android.view.RemoteAnimationAdapter;
-import android.view.RemoteAnimationTarget;
-import android.view.SurfaceControl;
-import android.view.SurfaceControl.Transaction;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.server.testutils.OffsettableClock;
-import com.android.server.testutils.TestHandler;
-import com.android.server.wm.RemoteAnimationController.RemoteAnimationRecord;
-import com.android.server.wm.SurfaceAnimator.OnAnimationFinishedCallback;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-/**
- * Build/Install/Run:
- * atest WmTests:RemoteAnimationControllerTest
- */
-@SmallTest
-@Presubmit
-@RunWith(WindowTestRunner.class)
-public class RemoteAnimationControllerTest extends WindowTestsBase {
-
- @Mock
- SurfaceControl mMockLeash;
- @Mock
- SurfaceControl mMockThumbnailLeash;
- @Mock
- Transaction mMockTransaction;
- @Mock
- OnAnimationFinishedCallback mFinishedCallback;
- @Mock
- OnAnimationFinishedCallback mThumbnailFinishedCallback;
- @Mock
- IRemoteAnimationRunner mMockRunner;
- private RemoteAnimationAdapter mAdapter;
- private RemoteAnimationController mController;
- private final OffsettableClock mClock = new OffsettableClock.Stopped();
- private TestHandler mHandler;
-
- @Before
- public void setUp() throws Exception {
- MockitoAnnotations.initMocks(this);
-
- when(mMockRunner.asBinder()).thenReturn(new Binder());
- mAdapter = new RemoteAnimationAdapter(mMockRunner, 100, 50, true /* changeNeedsSnapshot */);
- mAdapter.setCallingPidUid(123, 456);
- runWithScissors(mWm.mH, () -> mHandler = new TestHandler(null, mClock), 0);
- mController = new RemoteAnimationController(mWm, mDisplayContent, mAdapter,
- mHandler, false /*isActivityEmbedding*/);
- mWm.mAnimator.ready();
- }
-
- private WindowState createAppOverlayWindow() {
- final WindowState win = newWindowBuilder("testOverlayWindow",
- TYPE_APPLICATION_OVERLAY).build();
- win.mActivityRecord = null;
- win.mHasSurface = true;
- return win;
- }
-
- @Test
- public void testForwardsShowBackdrop() throws Exception {
- final WindowState win = createTestWindow();
- mDisplayContent.mOpeningApps.add(win.mActivityRecord);
- final WindowState overlayWin = createAppOverlayWindow();
- try {
- final AnimationAdapter adapter = mController.createRemoteAnimationRecord(
- win.mActivityRecord,
- new Point(50, 100), null, new Rect(50, 100, 150, 150), null,
- true /* showBackdrop */).mAdapter;
- adapter.startAnimation(mMockLeash, mMockTransaction, ANIMATION_TYPE_APP_TRANSITION,
- mFinishedCallback);
- mController.goodToGo(TRANSIT_OLD_ACTIVITY_OPEN);
- waitUntilWindowAnimatorIdle();
- final ArgumentCaptor<RemoteAnimationTarget[]> appsCaptor =
- ArgumentCaptor.forClass(RemoteAnimationTarget[].class);
- final ArgumentCaptor<RemoteAnimationTarget[]> wallpapersCaptor =
- ArgumentCaptor.forClass(RemoteAnimationTarget[].class);
- final ArgumentCaptor<RemoteAnimationTarget[]> nonAppsCaptor =
- ArgumentCaptor.forClass(RemoteAnimationTarget[].class);
- final ArgumentCaptor<IRemoteAnimationFinishedCallback> finishedCaptor =
- ArgumentCaptor.forClass(IRemoteAnimationFinishedCallback.class);
- verify(mMockRunner).onAnimationStart(eq(TRANSIT_OLD_ACTIVITY_OPEN),
- appsCaptor.capture(), wallpapersCaptor.capture(), nonAppsCaptor.capture(),
- finishedCaptor.capture());
- assertEquals(1, appsCaptor.getValue().length);
- final RemoteAnimationTarget app = appsCaptor.getValue()[0];
- assertTrue(app.showBackdrop);
- } finally {
- mDisplayContent.mOpeningApps.clear();
- }
- }
-
- @Test
- public void testRun() throws Exception {
- final WindowState win = createTestWindow();
- mDisplayContent.mOpeningApps.add(win.mActivityRecord);
- final WindowState overlayWin = createAppOverlayWindow();
- try {
- final AnimationAdapter adapter = mController.createRemoteAnimationRecord(
- win.mActivityRecord,
- new Point(50, 100), null, new Rect(50, 100, 150, 150), null, false).mAdapter;
- adapter.startAnimation(mMockLeash, mMockTransaction, ANIMATION_TYPE_APP_TRANSITION,
- mFinishedCallback);
- mController.goodToGo(TRANSIT_OLD_ACTIVITY_OPEN);
- waitUntilWindowAnimatorIdle();
- final ArgumentCaptor<RemoteAnimationTarget[]> appsCaptor =
- ArgumentCaptor.forClass(RemoteAnimationTarget[].class);
- final ArgumentCaptor<RemoteAnimationTarget[]> wallpapersCaptor =
- ArgumentCaptor.forClass(RemoteAnimationTarget[].class);
- final ArgumentCaptor<RemoteAnimationTarget[]> nonAppsCaptor =
- ArgumentCaptor.forClass(RemoteAnimationTarget[].class);
- final ArgumentCaptor<IRemoteAnimationFinishedCallback> finishedCaptor =
- ArgumentCaptor.forClass(IRemoteAnimationFinishedCallback.class);
- verify(mMockRunner).onAnimationStart(eq(TRANSIT_OLD_ACTIVITY_OPEN),
- appsCaptor.capture(), wallpapersCaptor.capture(), nonAppsCaptor.capture(),
- finishedCaptor.capture());
- assertEquals(1, appsCaptor.getValue().length);
- final RemoteAnimationTarget app = appsCaptor.getValue()[0];
- assertEquals(new Point(50, 100), app.position);
- assertEquals(new Rect(50, 100, 150, 150), app.sourceContainerBounds);
- assertEquals(win.mActivityRecord.getPrefixOrderIndex(), app.prefixOrderIndex);
- assertEquals(win.mActivityRecord.getTask().mTaskId, app.taskId);
- assertEquals(mMockLeash, app.leash);
- assertEquals(false, app.isTranslucent);
- verify(mMockTransaction).setPosition(mMockLeash, app.position.x, app.position.y);
- verify(mMockTransaction).setWindowCrop(mMockLeash, 100, 50);
-
- finishedCaptor.getValue().onAnimationFinished();
- verify(mFinishedCallback).onAnimationFinished(eq(ANIMATION_TYPE_APP_TRANSITION),
- eq(adapter));
- assertEquals(0, nonAppsCaptor.getValue().length);
- } finally {
- mDisplayContent.mOpeningApps.clear();
- }
- }
-
- @Test
- public void testCancel() throws Exception {
- final WindowState win = createTestWindow();
- final AnimationAdapter adapter = mController.createRemoteAnimationRecord(
- win.mActivityRecord,
- new Point(50, 100), null, new Rect(50, 100, 150, 150), null, false).mAdapter;
- adapter.startAnimation(mMockLeash, mMockTransaction, ANIMATION_TYPE_APP_TRANSITION,
- mFinishedCallback);
- mController.goodToGo(TRANSIT_OLD_ACTIVITY_OPEN);
-
- adapter.onAnimationCancelled(mMockLeash);
- verify(mMockRunner).onAnimationCancelled();
- }
-
- @Test
- public void testTimeout() throws Exception {
- final WindowState win = createTestWindow();
- final AnimationAdapter adapter = mController.createRemoteAnimationRecord(
- win.mActivityRecord,
- new Point(50, 100), null, new Rect(50, 100, 150, 150), null, false).mAdapter;
- adapter.startAnimation(mMockLeash, mMockTransaction, ANIMATION_TYPE_APP_TRANSITION,
- mFinishedCallback);
- mController.goodToGo(TRANSIT_OLD_ACTIVITY_OPEN);
-
- mClock.fastForward(10500);
- mHandler.timeAdvance();
-
- verify(mMockRunner).onAnimationCancelled();
- verify(mFinishedCallback).onAnimationFinished(eq(ANIMATION_TYPE_APP_TRANSITION),
- eq(adapter));
- }
-
- @Test
- public void testTimeout_scaled() throws Exception {
- try {
- mWm.setAnimationScale(2, 5.0f);
- final WindowState win = createTestWindow();
- final AnimationAdapter adapter = mController.createRemoteAnimationRecord(
- win.mActivityRecord, new Point(50, 100), null, new Rect(50, 100, 150, 150),
- null, false).mAdapter;
- adapter.startAnimation(mMockLeash, mMockTransaction, ANIMATION_TYPE_APP_TRANSITION,
- mFinishedCallback);
- mController.goodToGo(TRANSIT_OLD_ACTIVITY_OPEN);
-
- mClock.fastForward(10500);
- mHandler.timeAdvance();
-
- verify(mMockRunner, never()).onAnimationCancelled();
-
- mClock.fastForward(52500);
- mHandler.timeAdvance();
-
- verify(mMockRunner).onAnimationCancelled();
- verify(mFinishedCallback).onAnimationFinished(eq(ANIMATION_TYPE_APP_TRANSITION),
- eq(adapter));
- } finally {
- mWm.setAnimationScale(2, 1.0f);
- }
- }
-
- @Test
- public void testZeroAnimations() throws Exception {
- mController.goodToGo(TRANSIT_OLD_NONE);
- verify(mMockRunner, never()).onAnimationStart(anyInt(), any(), any(), any(), any());
- verify(mMockRunner).onAnimationCancelled();
- }
-
- @Test
- public void testNotReallyStarted() throws Exception {
- final WindowState win = createTestWindow();
- mController.createRemoteAnimationRecord(win.mActivityRecord,
- new Point(50, 100), null, new Rect(50, 100, 150, 150), null, false);
- mController.goodToGo(TRANSIT_OLD_ACTIVITY_OPEN);
- verify(mMockRunner, never()).onAnimationStart(anyInt(), any(), any(), any(), any());
- verify(mMockRunner).onAnimationCancelled();
- }
-
- @Test
- public void testOneNotStarted() throws Exception {
- final WindowState win1 = newWindowBuilder("testWin1", TYPE_BASE_APPLICATION).build();
- final WindowState win2 = newWindowBuilder("testWin2", TYPE_BASE_APPLICATION).build();
- mController.createRemoteAnimationRecord(win1.mActivityRecord,
- new Point(50, 100), null, new Rect(50, 100, 150, 150), null, false);
- final AnimationAdapter adapter = mController.createRemoteAnimationRecord(
- win2.mActivityRecord,
- new Point(50, 100), null, new Rect(50, 100, 150, 150), null, false).mAdapter;
- adapter.startAnimation(mMockLeash, mMockTransaction, ANIMATION_TYPE_APP_TRANSITION,
- mFinishedCallback);
- mController.goodToGo(TRANSIT_OLD_ACTIVITY_OPEN);
- waitUntilWindowAnimatorIdle();
- final ArgumentCaptor<RemoteAnimationTarget[]> appsCaptor =
- ArgumentCaptor.forClass(RemoteAnimationTarget[].class);
- final ArgumentCaptor<RemoteAnimationTarget[]> wallpapersCaptor =
- ArgumentCaptor.forClass(RemoteAnimationTarget[].class);
- final ArgumentCaptor<RemoteAnimationTarget[]> nonAppsCaptor =
- ArgumentCaptor.forClass(RemoteAnimationTarget[].class);
- final ArgumentCaptor<IRemoteAnimationFinishedCallback> finishedCaptor =
- ArgumentCaptor.forClass(IRemoteAnimationFinishedCallback.class);
- verify(mMockRunner).onAnimationStart(eq(TRANSIT_OLD_ACTIVITY_OPEN),
- appsCaptor.capture(), wallpapersCaptor.capture(), nonAppsCaptor.capture(),
- finishedCaptor.capture());
- assertEquals(1, appsCaptor.getValue().length);
- assertEquals(mMockLeash, appsCaptor.getValue()[0].leash);
- }
-
- @Test
- public void testRemovedBeforeStarted() throws Exception {
- final WindowState win = createTestWindow();
- final AnimationAdapter adapter = mController.createRemoteAnimationRecord(
- win.mActivityRecord,
- new Point(50, 100), null, new Rect(50, 100, 150, 150), null, false).mAdapter;
- adapter.startAnimation(mMockLeash, mMockTransaction, ANIMATION_TYPE_APP_TRANSITION,
- mFinishedCallback);
- win.mActivityRecord.removeImmediately();
- mController.goodToGo(TRANSIT_OLD_ACTIVITY_OPEN);
- verify(mMockRunner, never()).onAnimationStart(anyInt(), any(), any(), any(), any());
- verify(mMockRunner).onAnimationCancelled();
- verify(mFinishedCallback).onAnimationFinished(eq(ANIMATION_TYPE_APP_TRANSITION),
- eq(adapter));
- }
-
- @Test
- public void testOpeningTaskWithTopFinishingActivity() {
- final WindowState win = createTestWindow();
- final Task task = win.getTask();
- final ActivityRecord topFinishing = new ActivityBuilder(mAtm).setTask(task).build();
- // Now the task contains:
- // - Activity[1] (top, finishing, no window)
- // - Activity[0] (has window)
- topFinishing.finishing = true;
- spyOn(mDisplayContent.mAppTransition);
- doReturn(mController).when(mDisplayContent.mAppTransition).getRemoteAnimationController();
- task.applyAnimationUnchecked(null /* lp */, true /* enter */, TRANSIT_OLD_TASK_OPEN,
- false /* isVoiceInteraction */, null /* sources */);
- mController.goodToGo(TRANSIT_OLD_TASK_OPEN);
- waitUntilWindowAnimatorIdle();
- final ArgumentCaptor<RemoteAnimationTarget[]> appsCaptor =
- ArgumentCaptor.forClass(RemoteAnimationTarget[].class);
- try {
- verify(mMockRunner).onAnimationStart(eq(TRANSIT_OLD_TASK_OPEN),
- appsCaptor.capture(), any(), any(), any());
- } catch (RemoteException ignored) {
- }
- assertEquals(1, appsCaptor.getValue().length);
- assertEquals(RemoteAnimationTarget.MODE_OPENING, appsCaptor.getValue()[0].mode);
- }
-
- @Test
- public void testChangeToSmallerSize() throws Exception {
- final WindowState win = createTestWindow();
- mDisplayContent.mChangingContainers.add(win.mActivityRecord);
- try {
- final RemoteAnimationRecord record = mController.createRemoteAnimationRecord(
- win.mActivityRecord, new Point(50, 100), null, new Rect(50, 100, 150, 150),
- new Rect(0, 0, 200, 200), false);
- assertNotNull(record.mThumbnailAdapter);
- ((AnimationAdapter) record.mAdapter)
- .startAnimation(mMockLeash, mMockTransaction, ANIMATION_TYPE_WINDOW_ANIMATION,
- mFinishedCallback);
- ((AnimationAdapter) record.mThumbnailAdapter).startAnimation(mMockThumbnailLeash,
- mMockTransaction, ANIMATION_TYPE_WINDOW_ANIMATION, mThumbnailFinishedCallback);
- mController.goodToGo(TRANSIT_OLD_TASK_CHANGE_WINDOWING_MODE);
- waitUntilWindowAnimatorIdle();
- final ArgumentCaptor<RemoteAnimationTarget[]> appsCaptor =
- ArgumentCaptor.forClass(RemoteAnimationTarget[].class);
- final ArgumentCaptor<RemoteAnimationTarget[]> wallpapersCaptor =
- ArgumentCaptor.forClass(RemoteAnimationTarget[].class);
- final ArgumentCaptor<RemoteAnimationTarget[]> nonAppsCaptor =
- ArgumentCaptor.forClass(RemoteAnimationTarget[].class);
- final ArgumentCaptor<IRemoteAnimationFinishedCallback> finishedCaptor =
- ArgumentCaptor.forClass(IRemoteAnimationFinishedCallback.class);
- verify(mMockRunner).onAnimationStart(eq(TRANSIT_OLD_TASK_CHANGE_WINDOWING_MODE),
- appsCaptor.capture(), wallpapersCaptor.capture(), nonAppsCaptor.capture(),
- finishedCaptor.capture());
- assertEquals(1, appsCaptor.getValue().length);
- final RemoteAnimationTarget app = appsCaptor.getValue()[0];
- assertEquals(RemoteAnimationTarget.MODE_CHANGING, app.mode);
- assertEquals(new Point(50, 100), app.position);
- assertEquals(new Rect(50, 100, 150, 150), app.sourceContainerBounds);
- assertEquals(new Rect(0, 0, 200, 200), app.startBounds);
- assertEquals(mMockLeash, app.leash);
- assertEquals(mMockThumbnailLeash, app.startLeash);
- assertEquals(false, app.isTranslucent);
- verify(mMockTransaction).setPosition(
- mMockLeash, app.startBounds.left, app.startBounds.top);
- verify(mMockTransaction).setWindowCrop(
- mMockLeash, app.startBounds.width(), app.startBounds.height());
- verify(mMockTransaction).setPosition(mMockThumbnailLeash, 0, 0);
- verify(mMockTransaction).setWindowCrop(mMockThumbnailLeash, app.startBounds.width(),
- app.startBounds.height());
-
- finishedCaptor.getValue().onAnimationFinished();
- verify(mFinishedCallback).onAnimationFinished(eq(ANIMATION_TYPE_WINDOW_ANIMATION),
- eq(record.mAdapter));
- verify(mThumbnailFinishedCallback).onAnimationFinished(
- eq(ANIMATION_TYPE_WINDOW_ANIMATION), eq(record.mThumbnailAdapter));
- } finally {
- mDisplayContent.mChangingContainers.clear();
- }
- }
-
- @Test
- public void testChangeTolargerSize() throws Exception {
- final WindowState win = createTestWindow();
- mDisplayContent.mChangingContainers.add(win.mActivityRecord);
- try {
- final RemoteAnimationRecord record = mController.createRemoteAnimationRecord(
- win.mActivityRecord, new Point(0, 0), null, new Rect(0, 0, 200, 200),
- new Rect(50, 100, 150, 150), false);
- assertNotNull(record.mThumbnailAdapter);
- ((AnimationAdapter) record.mAdapter)
- .startAnimation(mMockLeash, mMockTransaction, ANIMATION_TYPE_WINDOW_ANIMATION,
- mFinishedCallback);
- ((AnimationAdapter) record.mThumbnailAdapter).startAnimation(mMockThumbnailLeash,
- mMockTransaction, ANIMATION_TYPE_WINDOW_ANIMATION, mThumbnailFinishedCallback);
- mController.goodToGo(TRANSIT_OLD_TASK_CHANGE_WINDOWING_MODE);
- waitUntilWindowAnimatorIdle();
- final ArgumentCaptor<RemoteAnimationTarget[]> appsCaptor =
- ArgumentCaptor.forClass(RemoteAnimationTarget[].class);
- final ArgumentCaptor<RemoteAnimationTarget[]> wallpapersCaptor =
- ArgumentCaptor.forClass(RemoteAnimationTarget[].class);
- final ArgumentCaptor<RemoteAnimationTarget[]> nonAppsCaptor =
- ArgumentCaptor.forClass(RemoteAnimationTarget[].class);
- final ArgumentCaptor<IRemoteAnimationFinishedCallback> finishedCaptor =
- ArgumentCaptor.forClass(IRemoteAnimationFinishedCallback.class);
- verify(mMockRunner).onAnimationStart(eq(TRANSIT_OLD_TASK_CHANGE_WINDOWING_MODE),
- appsCaptor.capture(), wallpapersCaptor.capture(), nonAppsCaptor.capture(),
- finishedCaptor.capture());
- assertEquals(1, appsCaptor.getValue().length);
- final RemoteAnimationTarget app = appsCaptor.getValue()[0];
- assertEquals(RemoteAnimationTarget.MODE_CHANGING, app.mode);
- assertEquals(new Point(0, 0), app.position);
- assertEquals(new Rect(0, 0, 200, 200), app.sourceContainerBounds);
- assertEquals(new Rect(50, 100, 150, 150), app.startBounds);
- assertEquals(mMockLeash, app.leash);
- assertEquals(mMockThumbnailLeash, app.startLeash);
- assertEquals(false, app.isTranslucent);
- verify(mMockTransaction).setPosition(
- mMockLeash, app.startBounds.left, app.startBounds.top);
- verify(mMockTransaction).setWindowCrop(
- mMockLeash, app.startBounds.width(), app.startBounds.height());
- verify(mMockTransaction).setPosition(mMockThumbnailLeash, 0, 0);
- verify(mMockTransaction).setWindowCrop(mMockThumbnailLeash, app.startBounds.width(),
- app.startBounds.height());
-
- finishedCaptor.getValue().onAnimationFinished();
- verify(mFinishedCallback).onAnimationFinished(eq(ANIMATION_TYPE_WINDOW_ANIMATION),
- eq(record.mAdapter));
- verify(mThumbnailFinishedCallback).onAnimationFinished(
- eq(ANIMATION_TYPE_WINDOW_ANIMATION), eq(record.mThumbnailAdapter));
- } finally {
- mDisplayContent.mChangingContainers.clear();
- }
- }
-
- @Test
- public void testChangeToDifferentPosition() throws Exception {
- final WindowState win = createTestWindow();
- mDisplayContent.mChangingContainers.add(win.mActivityRecord);
- try {
- final RemoteAnimationRecord record = mController.createRemoteAnimationRecord(
- win.mActivityRecord, new Point(100, 100), null, new Rect(150, 150, 400, 400),
- new Rect(50, 100, 150, 150), false);
- assertNotNull(record.mThumbnailAdapter);
- ((AnimationAdapter) record.mAdapter)
- .startAnimation(mMockLeash, mMockTransaction, ANIMATION_TYPE_WINDOW_ANIMATION,
- mFinishedCallback);
- ((AnimationAdapter) record.mThumbnailAdapter).startAnimation(mMockThumbnailLeash,
- mMockTransaction, ANIMATION_TYPE_WINDOW_ANIMATION, mThumbnailFinishedCallback);
- mController.goodToGo(TRANSIT_OLD_TASK_CHANGE_WINDOWING_MODE);
- waitUntilWindowAnimatorIdle();
- final ArgumentCaptor<RemoteAnimationTarget[]> appsCaptor =
- ArgumentCaptor.forClass(RemoteAnimationTarget[].class);
- final ArgumentCaptor<RemoteAnimationTarget[]> wallpapersCaptor =
- ArgumentCaptor.forClass(RemoteAnimationTarget[].class);
- final ArgumentCaptor<RemoteAnimationTarget[]> nonAppsCaptor =
- ArgumentCaptor.forClass(RemoteAnimationTarget[].class);
- final ArgumentCaptor<IRemoteAnimationFinishedCallback> finishedCaptor =
- ArgumentCaptor.forClass(IRemoteAnimationFinishedCallback.class);
- verify(mMockRunner).onAnimationStart(eq(TRANSIT_OLD_TASK_CHANGE_WINDOWING_MODE),
- appsCaptor.capture(), wallpapersCaptor.capture(), nonAppsCaptor.capture(),
- finishedCaptor.capture());
- assertEquals(1, appsCaptor.getValue().length);
- final RemoteAnimationTarget app = appsCaptor.getValue()[0];
- assertEquals(RemoteAnimationTarget.MODE_CHANGING, app.mode);
- assertEquals(new Point(100, 100), app.position);
- assertEquals(new Rect(150, 150, 400, 400), app.sourceContainerBounds);
- assertEquals(new Rect(50, 100, 150, 150), app.startBounds);
- assertEquals(mMockLeash, app.leash);
- assertEquals(mMockThumbnailLeash, app.startLeash);
- assertEquals(false, app.isTranslucent);
- verify(mMockTransaction).setPosition(
- mMockLeash, app.position.x + app.startBounds.left - app.screenSpaceBounds.left,
- app.position.y + app.startBounds.top - app.screenSpaceBounds.top);
- verify(mMockTransaction).setWindowCrop(
- mMockLeash, app.startBounds.width(), app.startBounds.height());
- verify(mMockTransaction).setPosition(mMockThumbnailLeash, 0, 0);
- verify(mMockTransaction).setWindowCrop(mMockThumbnailLeash, app.startBounds.width(),
- app.startBounds.height());
-
- finishedCaptor.getValue().onAnimationFinished();
- verify(mFinishedCallback).onAnimationFinished(eq(ANIMATION_TYPE_WINDOW_ANIMATION),
- eq(record.mAdapter));
- verify(mThumbnailFinishedCallback).onAnimationFinished(
- eq(ANIMATION_TYPE_WINDOW_ANIMATION), eq(record.mThumbnailAdapter));
- } finally {
- mDisplayContent.mChangingContainers.clear();
- }
- }
-
- @Test
- public void testWallpaperIncluded_expectTarget() throws Exception {
- final WindowToken wallpaperWindowToken = new WallpaperWindowToken(mWm, mock(IBinder.class),
- true, mDisplayContent, true /* ownerCanManageAppTokens */);
- spyOn(mDisplayContent.mWallpaperController);
- doReturn(true).when(mDisplayContent.mWallpaperController).isWallpaperVisible();
- final WindowState win = createTestWindow();
- mDisplayContent.mOpeningApps.add(win.mActivityRecord);
- try {
- final AnimationAdapter adapter = mController.createRemoteAnimationRecord(
- win.mActivityRecord,
- new Point(50, 100), null, new Rect(50, 100, 150, 150), null, false).mAdapter;
- adapter.startAnimation(mMockLeash, mMockTransaction, ANIMATION_TYPE_APP_TRANSITION,
- mFinishedCallback);
- mController.goodToGo(TRANSIT_OLD_ACTIVITY_OPEN);
- waitUntilWindowAnimatorIdle();
- final ArgumentCaptor<RemoteAnimationTarget[]> appsCaptor =
- ArgumentCaptor.forClass(RemoteAnimationTarget[].class);
- final ArgumentCaptor<RemoteAnimationTarget[]> wallpapersCaptor =
- ArgumentCaptor.forClass(RemoteAnimationTarget[].class);
- final ArgumentCaptor<RemoteAnimationTarget[]> nonAppsCaptor =
- ArgumentCaptor.forClass(RemoteAnimationTarget[].class);
- final ArgumentCaptor<IRemoteAnimationFinishedCallback> finishedCaptor =
- ArgumentCaptor.forClass(IRemoteAnimationFinishedCallback.class);
- verify(mMockRunner).onAnimationStart(eq(TRANSIT_OLD_ACTIVITY_OPEN),
- appsCaptor.capture(), wallpapersCaptor.capture(), nonAppsCaptor.capture(),
- finishedCaptor.capture());
- assertEquals(1, wallpapersCaptor.getValue().length);
- } finally {
- mDisplayContent.mOpeningApps.clear();
- }
- }
-
- @Test
- public void testWallpaperAnimatorCanceled_expectAnimationKeepsRunning() throws Exception {
- final WindowToken wallpaperWindowToken = new WallpaperWindowToken(mWm, mock(IBinder.class),
- true, mDisplayContent, true /* ownerCanManageAppTokens */);
- spyOn(mDisplayContent.mWallpaperController);
- doReturn(true).when(mDisplayContent.mWallpaperController).isWallpaperVisible();
- final WindowState win = createTestWindow();
- mDisplayContent.mOpeningApps.add(win.mActivityRecord);
- try {
- final AnimationAdapter adapter = mController.createRemoteAnimationRecord(
- win.mActivityRecord,
- new Point(50, 100), null, new Rect(50, 100, 150, 150), null, false).mAdapter;
- adapter.startAnimation(mMockLeash, mMockTransaction, ANIMATION_TYPE_APP_TRANSITION,
- mFinishedCallback);
- mController.goodToGo(TRANSIT_OLD_ACTIVITY_OPEN);
- waitUntilWindowAnimatorIdle();
- final ArgumentCaptor<RemoteAnimationTarget[]> appsCaptor =
- ArgumentCaptor.forClass(RemoteAnimationTarget[].class);
- final ArgumentCaptor<RemoteAnimationTarget[]> wallpapersCaptor =
- ArgumentCaptor.forClass(RemoteAnimationTarget[].class);
- final ArgumentCaptor<RemoteAnimationTarget[]> nonAPpsCaptor =
- ArgumentCaptor.forClass(RemoteAnimationTarget[].class);
- final ArgumentCaptor<IRemoteAnimationFinishedCallback> finishedCaptor =
- ArgumentCaptor.forClass(IRemoteAnimationFinishedCallback.class);
- verify(mMockRunner).onAnimationStart(eq(TRANSIT_OLD_ACTIVITY_OPEN),
- appsCaptor.capture(), wallpapersCaptor.capture(), nonAPpsCaptor.capture(),
- finishedCaptor.capture());
- assertEquals(1, wallpapersCaptor.getValue().length);
-
- // Cancel the wallpaper window animator and ensure the runner is not canceled
- wallpaperWindowToken.cancelAnimation();
- verify(mMockRunner, never()).onAnimationCancelled();
- } finally {
- mDisplayContent.mOpeningApps.clear();
- }
- }
-
- @Test
- public void testNonAppIncluded_keygaurdGoingAway() throws Exception {
- final WindowState win = createTestWindow();
- mDisplayContent.mOpeningApps.add(win.mActivityRecord);
- // Add overlay window hidden by the keyguard.
- final WindowState overlayWin = createAppOverlayWindow();
- overlayWin.hide(false /* doAnimation */, false /* requestAnim */);
- try {
- final AnimationAdapter adapter = mController.createRemoteAnimationRecord(
- win.mActivityRecord, new Point(50, 100), null,
- new Rect(50, 100, 150, 150), null, false).mAdapter;
- adapter.startAnimation(mMockLeash, mMockTransaction, ANIMATION_TYPE_APP_TRANSITION,
- mFinishedCallback);
- mController.goodToGo(TRANSIT_OLD_KEYGUARD_GOING_AWAY);
- waitUntilWindowAnimatorIdle();
- final ArgumentCaptor<RemoteAnimationTarget[]> appsCaptor =
- ArgumentCaptor.forClass(RemoteAnimationTarget[].class);
- final ArgumentCaptor<RemoteAnimationTarget[]> wallpapersCaptor =
- ArgumentCaptor.forClass(RemoteAnimationTarget[].class);
- final ArgumentCaptor<RemoteAnimationTarget[]> nonAppsCaptor =
- ArgumentCaptor.forClass(RemoteAnimationTarget[].class);
- final ArgumentCaptor<IRemoteAnimationFinishedCallback> finishedCaptor =
- ArgumentCaptor.forClass(IRemoteAnimationFinishedCallback.class);
- verify(mMockRunner).onAnimationStart(eq(TRANSIT_OLD_KEYGUARD_GOING_AWAY),
- appsCaptor.capture(), wallpapersCaptor.capture(), nonAppsCaptor.capture(),
- finishedCaptor.capture());
- assertEquals(1, appsCaptor.getValue().length);
- final RemoteAnimationTarget app = appsCaptor.getValue()[0];
- assertEquals(new Point(50, 100), app.position);
- assertEquals(new Rect(50, 100, 150, 150), app.sourceContainerBounds);
- assertEquals(win.mActivityRecord.getPrefixOrderIndex(), app.prefixOrderIndex);
- assertEquals(win.mActivityRecord.getTask().mTaskId, app.taskId);
- assertEquals(mMockLeash, app.leash);
- assertEquals(false, app.isTranslucent);
- verify(mMockTransaction).setPosition(mMockLeash, app.position.x, app.position.y);
- verify(mMockTransaction).setWindowCrop(mMockLeash, 100, 50);
-
- finishedCaptor.getValue().onAnimationFinished();
- verify(mFinishedCallback).onAnimationFinished(eq(ANIMATION_TYPE_APP_TRANSITION),
- eq(adapter));
- assertEquals(1, nonAppsCaptor.getValue().length);
- } finally {
- mDisplayContent.mOpeningApps.clear();
- }
- }
-
- @Test
- public void testNonAppIncluded_keygaurdGoingAwayToWallpaper() throws Exception {
- final WindowToken wallpaperWindowToken = new WallpaperWindowToken(mWm, mock(IBinder.class),
- true, mDisplayContent, true /* ownerCanManageAppTokens */);
- spyOn(mDisplayContent.mWallpaperController);
- doReturn(true).when(mDisplayContent.mWallpaperController).isWallpaperVisible();
- final WindowState win = createTestWindow();
- mDisplayContent.mOpeningApps.add(win.mActivityRecord);
- // Add overlay window hidden by the keyguard.
- final WindowState overlayWin = createAppOverlayWindow();
- overlayWin.hide(false /* doAnimation */, false /* requestAnim */);
- try {
- final AnimationAdapter adapter = mController.createRemoteAnimationRecord(
- win.mActivityRecord, new Point(50, 100), null,
- new Rect(50, 100, 150, 150), null, false).mAdapter;
- adapter.startAnimation(mMockLeash, mMockTransaction, ANIMATION_TYPE_APP_TRANSITION,
- mFinishedCallback);
- mController.goodToGo(TRANSIT_OLD_KEYGUARD_GOING_AWAY_ON_WALLPAPER);
- waitUntilWindowAnimatorIdle();
- final ArgumentCaptor<RemoteAnimationTarget[]> appsCaptor =
- ArgumentCaptor.forClass(RemoteAnimationTarget[].class);
- final ArgumentCaptor<RemoteAnimationTarget[]> wallpapersCaptor =
- ArgumentCaptor.forClass(RemoteAnimationTarget[].class);
- final ArgumentCaptor<RemoteAnimationTarget[]> nonAppsCaptor =
- ArgumentCaptor.forClass(RemoteAnimationTarget[].class);
- final ArgumentCaptor<IRemoteAnimationFinishedCallback> finishedCaptor =
- ArgumentCaptor.forClass(IRemoteAnimationFinishedCallback.class);
- verify(mMockRunner).onAnimationStart(eq(TRANSIT_OLD_KEYGUARD_GOING_AWAY_ON_WALLPAPER),
- appsCaptor.capture(), wallpapersCaptor.capture(), nonAppsCaptor.capture(),
- finishedCaptor.capture());
- assertEquals(1, wallpapersCaptor.getValue().length);
- assertEquals(1, nonAppsCaptor.getValue().length);
- } finally {
- mDisplayContent.mOpeningApps.clear();
- }
- }
-
- @Test
- public void testNonAppTarget_sendNavBar() throws Exception {
- final int transit = TRANSIT_OLD_TASK_OPEN;
- final AnimationAdapter adapter = setupForNonAppTargetNavBar(transit, true);
-
- final ArgumentCaptor<RemoteAnimationTarget[]> nonAppsCaptor =
- ArgumentCaptor.forClass(RemoteAnimationTarget[].class);
- final ArgumentCaptor<IRemoteAnimationFinishedCallback> finishedCaptor =
- ArgumentCaptor.forClass(IRemoteAnimationFinishedCallback.class);
- verify(mMockRunner).onAnimationStart(eq(transit), any(), any(),
- nonAppsCaptor.capture(), finishedCaptor.capture());
- boolean containNavTarget = false;
- for (int i = 0; i < nonAppsCaptor.getValue().length; i++) {
- if (nonAppsCaptor.getValue()[0].windowType == TYPE_NAVIGATION_BAR) {
- containNavTarget = true;
- break;
- }
- }
- assertTrue(containNavTarget);
- assertEquals(1, mController.mPendingNonAppAnimations.size());
- final NonAppWindowAnimationAdapter nonAppAdapter =
- mController.mPendingNonAppAnimations.get(0);
- spyOn(nonAppAdapter.getLeashFinishedCallback());
-
- finishedCaptor.getValue().onAnimationFinished();
- verify(mFinishedCallback).onAnimationFinished(eq(ANIMATION_TYPE_APP_TRANSITION),
- eq(adapter));
- verify(nonAppAdapter.getLeashFinishedCallback())
- .onAnimationFinished(nonAppAdapter.getLastAnimationType(), nonAppAdapter);
- }
-
- @Test
- public void testNonAppTarget_notSendNavBar_notAttachToApp() throws Exception {
- final int transit = TRANSIT_OLD_TASK_OPEN;
- setupForNonAppTargetNavBar(transit, false);
-
- final ArgumentCaptor<RemoteAnimationTarget[]> nonAppsCaptor =
- ArgumentCaptor.forClass(RemoteAnimationTarget[].class);
- verify(mMockRunner).onAnimationStart(eq(transit),
- any(), any(), nonAppsCaptor.capture(), any());
- for (int i = 0; i < nonAppsCaptor.getValue().length; i++) {
- if (nonAppsCaptor.getValue()[0].windowType == TYPE_NAVIGATION_BAR) {
- fail("Non-app animation target must not contain navbar");
- }
- }
- }
-
- @Test
- public void testNonAppTarget_notSendNavBar_controlledByFadeRotation() throws Exception {
- final AsyncRotationController mockController =
- mock(AsyncRotationController.class);
- doReturn(mockController).when(mDisplayContent).getAsyncRotationController();
- final int transit = TRANSIT_OLD_TASK_OPEN;
- setupForNonAppTargetNavBar(transit, true);
-
- final ArgumentCaptor<RemoteAnimationTarget[]> nonAppsCaptor =
- ArgumentCaptor.forClass(RemoteAnimationTarget[].class);
- verify(mMockRunner).onAnimationStart(eq(transit),
- any(), any(), nonAppsCaptor.capture(), any());
- for (int i = 0; i < nonAppsCaptor.getValue().length; i++) {
- if (nonAppsCaptor.getValue()[0].windowType == TYPE_NAVIGATION_BAR) {
- fail("Non-app animation target must not contain navbar");
- }
- }
- }
-
- private AnimationAdapter setupForNonAppTargetNavBar(int transit, boolean shouldAttachNavBar) {
- final WindowState win = createTestWindow();
- mDisplayContent.mOpeningApps.add(win.mActivityRecord);
- final WindowState navBar = newWindowBuilder("NavigationBar", TYPE_NAVIGATION_BAR).build();
- mDisplayContent.getDisplayPolicy().addWindowLw(navBar, navBar.mAttrs);
- final DisplayPolicy policy = mDisplayContent.getDisplayPolicy();
- spyOn(policy);
- doReturn(shouldAttachNavBar).when(policy).shouldAttachNavBarToAppDuringTransition();
-
- final AnimationAdapter adapter = mController.createRemoteAnimationRecord(
- win.mActivityRecord, new Point(50, 100), null,
- new Rect(50, 100, 150, 150), null, false).mAdapter;
- adapter.startAnimation(mMockLeash, mMockTransaction, ANIMATION_TYPE_APP_TRANSITION,
- mFinishedCallback);
- mController.goodToGo(transit);
- waitUntilWindowAnimatorIdle();
- return adapter;
- }
-
- private static void verifyNoMoreInteractionsExceptAsBinder(IInterface binder) {
- verify(binder, atLeast(0)).asBinder();
- verifyNoMoreInteractions(binder);
- }
-
- private WindowState createTestWindow() {
- return newWindowBuilder("testWin", TYPE_BASE_APPLICATION).build();
- }
-}
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/TaskFragmentOrganizerControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
index 546ecc6e38a5..ab76ae8e378a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
@@ -29,14 +29,14 @@ import static android.view.WindowManager.TRANSIT_OPEN;
import static android.window.TaskFragmentOperation.OP_TYPE_CREATE_OR_MOVE_TASK_FRAGMENT_DECOR_SURFACE;
import static android.window.TaskFragmentOperation.OP_TYPE_CREATE_TASK_FRAGMENT;
import static android.window.TaskFragmentOperation.OP_TYPE_DELETE_TASK_FRAGMENT;
+import static android.window.TaskFragmentOperation.OP_TYPE_PRIVILEGED_REORDER_TO_BOTTOM_OF_TASK;
+import static android.window.TaskFragmentOperation.OP_TYPE_PRIVILEGED_REORDER_TO_TOP_OF_TASK;
+import static android.window.TaskFragmentOperation.OP_TYPE_PRIVILEGED_SET_CAN_AFFECT_SYSTEM_UI_FLAGS;
import static android.window.TaskFragmentOperation.OP_TYPE_REMOVE_TASK_FRAGMENT_DECOR_SURFACE;
-import static android.window.TaskFragmentOperation.OP_TYPE_REORDER_TO_BOTTOM_OF_TASK;
import static android.window.TaskFragmentOperation.OP_TYPE_REORDER_TO_FRONT;
-import static android.window.TaskFragmentOperation.OP_TYPE_REORDER_TO_TOP_OF_TASK;
import static android.window.TaskFragmentOperation.OP_TYPE_REPARENT_ACTIVITY_TO_TASK_FRAGMENT;
import static android.window.TaskFragmentOperation.OP_TYPE_SET_ADJACENT_TASK_FRAGMENTS;
import static android.window.TaskFragmentOperation.OP_TYPE_SET_ANIMATION_PARAMS;
-import static android.window.TaskFragmentOperation.OP_TYPE_SET_CAN_AFFECT_SYSTEM_UI_FLAGS;
import static android.window.TaskFragmentOperation.OP_TYPE_SET_DIM_ON_TASK;
import static android.window.TaskFragmentOperation.OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT;
import static android.window.TaskFragmentOrganizer.KEY_ERROR_CALLBACK_OP_TYPE;
@@ -1821,7 +1821,7 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
// Reorder TaskFragment to bottom
final TaskFragmentOperation operation = new TaskFragmentOperation.Builder(
- OP_TYPE_REORDER_TO_BOTTOM_OF_TASK).build();
+ OP_TYPE_PRIVILEGED_REORDER_TO_BOTTOM_OF_TASK).build();
mTransaction.addTaskFragmentOperation(tf1.getFragmentToken(), operation);
assertApplyTransactionAllowed(mTransaction);
@@ -1858,7 +1858,7 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
// Reorder TaskFragment to top
final TaskFragmentOperation operation = new TaskFragmentOperation.Builder(
- OP_TYPE_REORDER_TO_TOP_OF_TASK).build();
+ OP_TYPE_PRIVILEGED_REORDER_TO_TOP_OF_TASK).build();
mTransaction.addTaskFragmentOperation(tf0.getFragmentToken(), operation);
assertApplyTransactionAllowed(mTransaction);
@@ -1903,13 +1903,13 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
@Test
public void testApplyTransaction_reorderToBottomOfTask_failsIfNotSystemOrganizer() {
testApplyTransaction_reorder_failsIfNotSystemOrganizer_common(
- OP_TYPE_REORDER_TO_BOTTOM_OF_TASK);
+ OP_TYPE_PRIVILEGED_REORDER_TO_BOTTOM_OF_TASK);
}
@Test
public void testApplyTransaction_reorderToTopOfTask_failsIfNotSystemOrganizer() {
testApplyTransaction_reorder_failsIfNotSystemOrganizer_common(
- OP_TYPE_REORDER_TO_TOP_OF_TASK);
+ OP_TYPE_PRIVILEGED_REORDER_TO_TOP_OF_TASK);
}
@Test
@@ -1922,7 +1922,7 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
// Setting the flag to false.
TaskFragmentOperation operation = new TaskFragmentOperation.Builder(
- OP_TYPE_SET_CAN_AFFECT_SYSTEM_UI_FLAGS).setBooleanValue(false).build();
+ OP_TYPE_PRIVILEGED_SET_CAN_AFFECT_SYSTEM_UI_FLAGS).setBooleanValue(false).build();
mTransaction.addTaskFragmentOperation(tf.getFragmentToken(), operation);
assertApplyTransactionAllowed(mTransaction);
@@ -1931,7 +1931,7 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
// Setting the flag back to true.
operation = new TaskFragmentOperation.Builder(
- OP_TYPE_SET_CAN_AFFECT_SYSTEM_UI_FLAGS).setBooleanValue(true).build();
+ OP_TYPE_PRIVILEGED_SET_CAN_AFFECT_SYSTEM_UI_FLAGS).setBooleanValue(true).build();
mTransaction.addTaskFragmentOperation(tf.getFragmentToken(), operation);
assertApplyTransactionAllowed(mTransaction);
@@ -1945,7 +1945,7 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
final TaskFragment tf = createTaskFragment(task);
TaskFragmentOperation operation = new TaskFragmentOperation.Builder(
- OP_TYPE_SET_CAN_AFFECT_SYSTEM_UI_FLAGS).setBooleanValue(false).build();
+ OP_TYPE_PRIVILEGED_SET_CAN_AFFECT_SYSTEM_UI_FLAGS).setBooleanValue(false).build();
mTransaction
.addTaskFragmentOperation(tf.getFragmentToken(), operation)
.setErrorCallbackToken(mErrorToken);
@@ -1955,7 +1955,7 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
// The pending event will be dispatched on the handler (from requestTraversal).
waitHandlerIdle(mWm.mAnimationHandler);
- assertTaskFragmentErrorTransaction(OP_TYPE_SET_CAN_AFFECT_SYSTEM_UI_FLAGS,
+ assertTaskFragmentErrorTransaction(OP_TYPE_PRIVILEGED_SET_CAN_AFFECT_SYSTEM_UI_FLAGS,
SecurityException.class);
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowContainerThumbnailTest.java b/services/tests/wmtests/src/com/android/server/wm/WindowContainerThumbnailTest.java
deleted file mode 100644
index 849072e133ae..000000000000
--- a/services/tests/wmtests/src/com/android/server/wm/WindowContainerThumbnailTest.java
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wm;
-
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
-
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.mockito.ArgumentMatchers.any;
-
-import android.hardware.HardwareBuffer;
-import android.platform.test.annotations.Presubmit;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.server.testutils.StubTransaction;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-/**
- * Test class for {@link TaskSnapshotSurface}.
- *
- * Build/Install/Run:
- * atest WmTests:WindowContainerThumbnailTest
- *
- */
-@SmallTest
-@Presubmit
-@RunWith(WindowTestRunner.class)
-public class WindowContainerThumbnailTest extends WindowTestsBase {
- private WindowContainerThumbnail buildThumbnail() {
- final HardwareBuffer buffer = HardwareBuffer.create(1, 1, HardwareBuffer.RGBA_8888,
- 1, HardwareBuffer.USAGE_CPU_READ_RARELY);
- final ActivityRecord mockAr = mock(ActivityRecord.class);
- when(mockAr.getPendingTransaction()).thenReturn(new StubTransaction());
- when(mockAr.makeChildSurface(any())).thenReturn(new MockSurfaceControlBuilder());
- when(mockAr.makeSurface()).thenReturn(new MockSurfaceControlBuilder());
- return new WindowContainerThumbnail(new StubTransaction(), mockAr, buffer,
- mock(SurfaceAnimator.class));
- }
-
- @Test
- public void testDestroy_nullsSurface() {
- final WindowContainerThumbnail t = buildThumbnail();
- assertNotNull(t.getSurfaceControl());
- t.destroy();
- assertNull(t.getSurfaceControl());
- }
-}
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
index a718c06cc2fa..59ee2f5c8e9f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
@@ -1281,6 +1281,7 @@ public class WindowStateTests extends WindowTestsBase {
// Simulate app plays closing transition to app2.
app.mActivityRecord.commitVisibility(false, false);
+ mDisplayContent.computeImeTarget(true /* updateImeTarget */);
assertTrue(app.mActivityRecord.mLastImeShown);
// Verify the IME insets is visible on app, but not for app2 during app task switching.
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/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/Input/src/com/android/server/input/InputManagerServiceTests.kt b/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt
index b22e42d1ab89..a2f6f0051116 100644
--- a/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt
+++ b/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt
@@ -333,7 +333,7 @@ class InputManagerServiceTests {
fun testKeyActivenessNotifyEventsLifecycle() {
service.systemRunning()
- fakePermissionEnforcer.grant(android.Manifest.permission.LISTEN_FOR_KEY_ACTIVITY);
+ fakePermissionEnforcer.grant(android.Manifest.permission.LISTEN_FOR_KEY_ACTIVITY)
val inputManager = context.getSystemService(InputManager::class.java)
@@ -358,6 +358,34 @@ class InputManagerServiceTests {
verifyNoMoreInteractions(listener)
}
+ @Test
+ fun testKeyEventsForwardedToFocusedWindow_whenWmAllows() {
+ service.systemRunning()
+ overrideSendActionKeyEventsToFocusedWindow(
+ /* hasPermission = */false,
+ /* hasPrivateFlag = */false
+ )
+ whenever(wmCallbacks.interceptKeyBeforeDispatching(any(), any(), anyInt())).thenReturn(0)
+
+ val event = KeyEvent( /* downTime= */0, /* eventTime= */0, KeyEvent.ACTION_DOWN,
+ KeyEvent.KEYCODE_SPACE, /* repeat= */0, KeyEvent.META_CTRL_ON)
+ assertEquals(0, service.interceptKeyBeforeDispatching(null, event, 0))
+ }
+
+ @Test
+ fun testKeyEventsNotForwardedToFocusedWindow_whenWmConsumes() {
+ service.systemRunning()
+ overrideSendActionKeyEventsToFocusedWindow(
+ /* hasPermission = */false,
+ /* hasPrivateFlag = */false
+ )
+ whenever(wmCallbacks.interceptKeyBeforeDispatching(any(), any(), anyInt())).thenReturn(-1)
+
+ val event = KeyEvent( /* downTime= */0, /* eventTime= */0, KeyEvent.ACTION_DOWN,
+ KeyEvent.KEYCODE_SPACE, /* repeat= */0, KeyEvent.META_CTRL_ON)
+ assertEquals(-1, service.interceptKeyBeforeDispatching(null, event, 0))
+ }
+
private class AutoClosingVirtualDisplays(val displays: List<VirtualDisplay>) : AutoCloseable {
operator fun get(i: Int): VirtualDisplay = displays[i]
diff --git a/tests/Tracing/src/com/android/internal/protolog/ProtoLogConfigurationServiceTest.java b/tests/Tracing/src/com/android/internal/protolog/ProtoLogConfigurationServiceTest.java
index a3d03a8278ed..3be725101252 100644
--- a/tests/Tracing/src/com/android/internal/protolog/ProtoLogConfigurationServiceTest.java
+++ b/tests/Tracing/src/com/android/internal/protolog/ProtoLogConfigurationServiceTest.java
@@ -38,6 +38,8 @@ import android.tools.traces.io.ResultReader;
import android.tools.traces.io.ResultWriter;
import android.tools.traces.monitors.PerfettoTraceMonitor;
+import com.android.internal.protolog.IProtoLogConfigurationService.RegisterClientArgs;
+
import com.google.common.truth.Truth;
import com.google.protobuf.InvalidProtocolBufferException;
@@ -152,10 +154,9 @@ public class ProtoLogConfigurationServiceTest {
public void canRegisterClientWithGroupsOnly() throws RemoteException {
final ProtoLogConfigurationService service = new ProtoLogConfigurationServiceImpl();
- final ProtoLogConfigurationServiceImpl.RegisterClientArgs args =
- new ProtoLogConfigurationServiceImpl.RegisterClientArgs()
- .setGroups(new ProtoLogConfigurationServiceImpl.RegisterClientArgs
- .GroupConfig(TEST_GROUP, true));
+ final RegisterClientArgs args = new RegisterClientArgs();
+ args.groups = new String[] { TEST_GROUP };
+ args.groupsDefaultLogcatStatus = new boolean[] { true };
service.registerClient(mMockClient, args);
Truth.assertThat(service.isLoggingToLogcat(TEST_GROUP)).isTrue();
@@ -167,11 +168,11 @@ public class ProtoLogConfigurationServiceTest {
throws RemoteException, InvalidProtocolBufferException {
final ProtoLogConfigurationService service = new ProtoLogConfigurationServiceImpl();
- final ProtoLogConfigurationServiceImpl.RegisterClientArgs args =
- new ProtoLogConfigurationServiceImpl.RegisterClientArgs()
- .setGroups(new ProtoLogConfigurationServiceImpl.RegisterClientArgs
- .GroupConfig(TEST_GROUP, true))
- .setViewerConfigFile(mViewerConfigFile.getAbsolutePath());
+ final RegisterClientArgs args = new RegisterClientArgs();
+ args.groups = new String[] { TEST_GROUP };
+ args.groupsDefaultLogcatStatus = new boolean[] { true };
+ args.viewerConfigFile = mViewerConfigFile.getAbsolutePath();
+
service.registerClient(mMockClient, args);
service.registerClient(mSecondMockClient, args);
@@ -204,11 +205,11 @@ public class ProtoLogConfigurationServiceTest {
Mockito.mock(ProtoLogConfigurationServiceImpl.ViewerConfigFileTracer.class);
final ProtoLogConfigurationService service = new ProtoLogConfigurationServiceImpl(tracer);
- final ProtoLogConfigurationServiceImpl.RegisterClientArgs args =
- new ProtoLogConfigurationServiceImpl.RegisterClientArgs()
- .setGroups(new ProtoLogConfigurationServiceImpl.RegisterClientArgs
- .GroupConfig(TEST_GROUP, true))
- .setViewerConfigFile(mViewerConfigFile.getAbsolutePath());
+ final RegisterClientArgs args = new RegisterClientArgs();
+ args.groups = new String[] { TEST_GROUP };
+ args.groupsDefaultLogcatStatus = new boolean[] { true };
+ args.viewerConfigFile = mViewerConfigFile.getAbsolutePath();
+
service.registerClient(mMockClient, args);
service.registerClient(mSecondMockClient, args);
@@ -227,9 +228,9 @@ public class ProtoLogConfigurationServiceTest {
public void sendEnableLoggingToLogcatToClient() throws RemoteException {
final var service = new ProtoLogConfigurationServiceImpl();
- final var args = new ProtoLogConfigurationServiceImpl.RegisterClientArgs()
- .setGroups(new ProtoLogConfigurationServiceImpl.RegisterClientArgs
- .GroupConfig(TEST_GROUP, false));
+ final RegisterClientArgs args = new RegisterClientArgs();
+ args.groups = new String[] { TEST_GROUP };
+ args.groupsDefaultLogcatStatus = new boolean[] { false };
service.registerClient(mMockClient, args);
Truth.assertThat(service.isLoggingToLogcat(TEST_GROUP)).isFalse();
@@ -244,10 +245,9 @@ public class ProtoLogConfigurationServiceTest {
public void sendDisableLoggingToLogcatToClient() throws RemoteException {
final ProtoLogConfigurationService service = new ProtoLogConfigurationServiceImpl();
- final ProtoLogConfigurationServiceImpl.RegisterClientArgs args =
- new ProtoLogConfigurationServiceImpl.RegisterClientArgs()
- .setGroups(new ProtoLogConfigurationServiceImpl.RegisterClientArgs
- .GroupConfig(TEST_GROUP, true));
+ final RegisterClientArgs args = new RegisterClientArgs();
+ args.groups = new String[] { TEST_GROUP };
+ args.groupsDefaultLogcatStatus = new boolean[] { true };
service.registerClient(mMockClient, args);
Truth.assertThat(service.isLoggingToLogcat(TEST_GROUP)).isTrue();
@@ -262,10 +262,10 @@ public class ProtoLogConfigurationServiceTest {
public void doNotSendLoggingToLogcatToClientWithoutRegisteredGroup() throws RemoteException {
final ProtoLogConfigurationService service = new ProtoLogConfigurationServiceImpl();
- final ProtoLogConfigurationServiceImpl.RegisterClientArgs args =
- new ProtoLogConfigurationServiceImpl.RegisterClientArgs()
- .setGroups(new ProtoLogConfigurationServiceImpl.RegisterClientArgs
- .GroupConfig(TEST_GROUP, false));
+ final RegisterClientArgs args = new RegisterClientArgs();
+ args.groups = new String[] { TEST_GROUP };
+ args.groupsDefaultLogcatStatus = new boolean[] { false };
+
service.registerClient(mMockClient, args);
Truth.assertThat(service.isLoggingToLogcat(TEST_GROUP)).isFalse();
@@ -283,10 +283,10 @@ public class ProtoLogConfigurationServiceTest {
service.enableProtoLogToLogcat(TEST_GROUP);
Truth.assertThat(service.isLoggingToLogcat(TEST_GROUP)).isTrue();
- final ProtoLogConfigurationServiceImpl.RegisterClientArgs args =
- new ProtoLogConfigurationServiceImpl.RegisterClientArgs()
- .setGroups(new ProtoLogConfigurationServiceImpl.RegisterClientArgs
- .GroupConfig(TEST_GROUP, false));
+ final RegisterClientArgs args = new RegisterClientArgs();
+ args.groups = new String[] { TEST_GROUP };
+ args.groupsDefaultLogcatStatus = new boolean[] { false };
+
service.registerClient(mMockClient, args);
Mockito.verify(mMockClient).toggleLogcat(eq(true),
diff --git a/tests/testables/tests/src/android/animation/AnimatorTestRuleToolkitTest.kt b/tests/testables/tests/src/android/animation/AnimatorTestRuleToolkitTest.kt
index 993c3fed9d59..2eb8ba1be811 100644
--- a/tests/testables/tests/src/android/animation/AnimatorTestRuleToolkitTest.kt
+++ b/tests/testables/tests/src/android/animation/AnimatorTestRuleToolkitTest.kt
@@ -16,6 +16,7 @@
package android.animation
+import android.content.pm.PackageManager
import android.graphics.Color
import android.platform.test.annotations.MotionTest
import android.view.ViewGroup
@@ -23,11 +24,14 @@ import android.widget.FrameLayout
import androidx.test.ext.junit.rules.ActivityScenarioRule
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
import com.android.internal.dynamicanimation.animation.DynamicAnimation
import com.android.internal.dynamicanimation.animation.SpringAnimation
import com.android.internal.dynamicanimation.animation.SpringForce
import kotlinx.coroutines.test.TestScope
+import org.junit.Assume.assumeFalse
+import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@@ -65,6 +69,16 @@ class AnimatorTestRuleToolkitTest {
bitmapDiffer = screenshotRule,
)
+ @Before
+ fun setUp() {
+ // Do not run on Automotive.
+ assumeFalse(
+ InstrumentationRegistry.getInstrumentation().context.packageManager.hasSystemFeature(
+ PackageManager.FEATURE_AUTOMOTIVE
+ )
+ )
+ }
+
@Test
fun recordFilmstrip_withAnimator() {
val animatedBox = createScene()
@@ -188,12 +202,7 @@ class AnimatorTestRuleToolkitTest {
null
}
return motionRule.recordMotion(
- AnimatorRuleRecordingSpec(
- container,
- motionControl,
- sampleIntervalMs,
- visualCapture,
- ) {
+ AnimatorRuleRecordingSpec(container, motionControl, sampleIntervalMs, visualCapture) {
feature(ViewFeatureCaptures.alpha, "alpha")
}
)
diff --git a/tools/aapt2/cmd/Diff.cpp b/tools/aapt2/cmd/Diff.cpp
index d3750a6100d3..b2b9179d4ffe 100644
--- a/tools/aapt2/cmd/Diff.cpp
+++ b/tools/aapt2/cmd/Diff.cpp
@@ -29,7 +29,8 @@ namespace aapt {
class DiffContext : public IAaptContext {
public:
- DiffContext() : name_mangler_({}), symbol_table_(&name_mangler_) {
+ explicit DiffContext(bool ignore_id_shift)
+ : ignore_id_shift(ignore_id_shift), name_mangler_({}), symbol_table_(&name_mangler_) {
}
PackageType GetPackageType() override {
@@ -71,6 +72,8 @@ class DiffContext : public IAaptContext {
return empty;
}
+ const bool ignore_id_shift;
+
private:
std::string empty_;
StdErrDiagnostics diagnostics_;
@@ -79,7 +82,7 @@ class DiffContext : public IAaptContext {
};
static void EmitDiffLine(const android::Source& source, StringPiece message) {
- std::cerr << source << ": " << message << "\n";
+ std::cout << source << ": " << message << "\n";
}
static bool IsSymbolVisibilityDifferent(const Visibility& vis_a, const Visibility& vis_b) {
@@ -95,8 +98,27 @@ static bool IsIdDiff(const Visibility::Level& level_a, const std::optional<Id>&
return false;
}
+class ZeroingIdVisitor : public DescendingValueVisitor {
+ public:
+ using DescendingValueVisitor::Visit;
+
+ void Visit(Reference* ref) override {
+ if (ref->name) {
+ ref->id.reset();
+ }
+ }
+};
+
+static std::unique_ptr<Value> CloneAndClearIds(const Value* value, LoadedApk* apk) {
+ CloningValueTransformer cloner(&apk->GetResourceTable()->string_pool);
+ auto res = value->Transform(cloner);
+ ZeroingIdVisitor visitor;
+ res->Accept(&visitor);
+ return res;
+}
+
static bool EmitResourceConfigValueDiff(
- IAaptContext* context, LoadedApk* apk_a, const ResourceTablePackageView& pkg_a,
+ DiffContext* context, LoadedApk* apk_a, const ResourceTablePackageView& pkg_a,
const ResourceTableTypeView& type_a, const ResourceTableEntryView& entry_a,
const ResourceConfigValue* config_value_a, LoadedApk* apk_b,
const ResourceTablePackageView& pkg_b, const ResourceTableTypeView& type_b,
@@ -104,6 +126,14 @@ static bool EmitResourceConfigValueDiff(
Value* value_a = config_value_a->value.get();
Value* value_b = config_value_b->value.get();
if (!value_a->Equals(value_b)) {
+ if (context->ignore_id_shift) {
+ // Check if the values are only different because of their IDs
+ auto cleared_a = CloneAndClearIds(value_a, apk_a);
+ auto cleared_b = CloneAndClearIds(value_b, apk_b);
+ if (cleared_a->Equals(cleared_b.get())) {
+ return false;
+ }
+ }
std::stringstream str_stream;
str_stream << "value " << pkg_a.name << ":" << type_a.named_type << "/" << entry_a.name
<< " config='" << config_value_a->config << "' does not match:\n";
@@ -116,7 +146,7 @@ static bool EmitResourceConfigValueDiff(
return false;
}
-static bool EmitResourceEntryDiff(IAaptContext* context, LoadedApk* apk_a,
+static bool EmitResourceEntryDiff(DiffContext* context, LoadedApk* apk_a,
const ResourceTablePackageView& pkg_a,
const ResourceTableTypeView& type_a,
const ResourceTableEntryView& entry_a, LoadedApk* apk_b,
@@ -180,90 +210,96 @@ static bool EmitResourceEntryDiff(IAaptContext* context, LoadedApk* apk_a,
return diff;
}
-static bool EmitResourceTypeDiff(IAaptContext* context, LoadedApk* apk_a,
+static const ResourceTableEntryView* findEntry(const ResourceTableTypeView& type,
+ const ResourceTableEntryView& entry) {
+ auto it = std::ranges::find_if(type.entries, [&](const ResourceTableEntryView& other_entry) {
+ return other_entry.name == entry.name;
+ });
+ return it != std::end(type.entries) ? &*it : nullptr;
+}
+
+static bool EmitResourceTypeDiff(DiffContext* context, LoadedApk* apk_a,
const ResourceTablePackageView& pkg_a,
const ResourceTableTypeView& type_a, LoadedApk* apk_b,
const ResourceTablePackageView& pkg_b,
const ResourceTableTypeView& type_b) {
bool diff = false;
- auto entry_a_iter = type_a.entries.begin();
- auto entry_b_iter = type_b.entries.begin();
- while (entry_a_iter != type_a.entries.end() || entry_b_iter != type_b.entries.end()) {
- if (entry_b_iter == type_b.entries.end()) {
+ std::unordered_set<const ResourceTableEntryView*> found_b_entries;
+ for (auto&& entry_a : type_a.entries) {
+ auto entry_b_iter = findEntry(type_b, entry_a);
+ if (!entry_b_iter) {
// Type A contains a type that type B does not have.
std::stringstream str_stream;
- str_stream << "missing " << pkg_a.name << ":" << type_a.named_type << "/"
- << entry_a_iter->name;
+ str_stream << "missing " << pkg_a.name << ":" << type_a.named_type << "/" << entry_a.name;
EmitDiffLine(apk_a->GetSource(), str_stream.str());
diff = true;
- } else if (entry_a_iter == type_a.entries.end()) {
- // Type B contains a type that type A does not have.
+ continue;
+ }
+ const auto& entry_b = *entry_b_iter;
+ found_b_entries.insert(&entry_b);
+ if (IsSymbolVisibilityDifferent(entry_a.visibility, entry_b.visibility)) {
std::stringstream str_stream;
- str_stream << "new entry " << pkg_b.name << ":" << type_b.named_type << "/"
- << entry_b_iter->name;
+ str_stream << pkg_a.name << ":" << type_a.named_type << "/" << entry_a.name
+ << " has different visibility (";
+ if (entry_b.visibility.staged_api) {
+ str_stream << "STAGED ";
+ }
+ if (entry_b.visibility.level == Visibility::Level::kPublic) {
+ str_stream << "PUBLIC";
+ } else {
+ str_stream << "PRIVATE";
+ }
+ str_stream << " vs ";
+ if (entry_a.visibility.staged_api) {
+ str_stream << "STAGED ";
+ }
+ if (entry_a.visibility.level == Visibility::Level::kPublic) {
+ str_stream << "PUBLIC";
+ } else {
+ str_stream << "PRIVATE";
+ }
+ str_stream << ")";
EmitDiffLine(apk_b->GetSource(), str_stream.str());
diff = true;
- } else {
- const auto& entry_a = *entry_a_iter;
- const auto& entry_b = *entry_b_iter;
- if (IsSymbolVisibilityDifferent(entry_a.visibility, entry_b.visibility)) {
- std::stringstream str_stream;
- str_stream << pkg_a.name << ":" << type_a.named_type << "/" << entry_a.name
- << " has different visibility (";
- if (entry_b.visibility.staged_api) {
- str_stream << "STAGED ";
- }
- if (entry_b.visibility.level == Visibility::Level::kPublic) {
- str_stream << "PUBLIC";
- } else {
- str_stream << "PRIVATE";
- }
- str_stream << " vs ";
- if (entry_a.visibility.staged_api) {
- str_stream << "STAGED ";
- }
- if (entry_a.visibility.level == Visibility::Level::kPublic) {
- str_stream << "PUBLIC";
- } else {
- str_stream << "PRIVATE";
- }
- str_stream << ")";
- EmitDiffLine(apk_b->GetSource(), str_stream.str());
- diff = true;
- } else if (IsIdDiff(entry_a.visibility.level, entry_a.id, entry_b.visibility.level,
- entry_b.id)) {
- std::stringstream str_stream;
- str_stream << pkg_a.name << ":" << type_a.named_type << "/" << entry_a.name
- << " has different public ID (";
- if (entry_b.id) {
- str_stream << "0x" << std::hex << entry_b.id.value();
- } else {
- str_stream << "none";
- }
- str_stream << " vs ";
- if (entry_a.id) {
- str_stream << "0x " << std::hex << entry_a.id.value();
- } else {
- str_stream << "none";
- }
- str_stream << ")";
- EmitDiffLine(apk_b->GetSource(), str_stream.str());
- diff = true;
+ } else if (!context->ignore_id_shift && IsIdDiff(entry_a.visibility.level, entry_a.id,
+ entry_b.visibility.level, entry_b.id)) {
+ std::stringstream str_stream;
+ str_stream << pkg_a.name << ":" << type_a.named_type << "/" << entry_a.name
+ << " has different public ID (";
+ if (entry_b.id) {
+ str_stream << "0x" << std::hex << entry_b.id.value();
+ } else {
+ str_stream << "none";
}
- diff |= EmitResourceEntryDiff(context, apk_a, pkg_a, type_a, entry_a, apk_b, pkg_b, type_b,
- entry_b);
- }
- if (entry_a_iter != type_a.entries.end()) {
- ++entry_a_iter;
+ str_stream << " vs ";
+ if (entry_a.id) {
+ str_stream << "0x " << std::hex << entry_a.id.value();
+ } else {
+ str_stream << "none";
+ }
+ str_stream << ")";
+ EmitDiffLine(apk_b->GetSource(), str_stream.str());
+ diff = true;
}
- if (entry_b_iter != type_b.entries.end()) {
- ++entry_b_iter;
+ diff |= EmitResourceEntryDiff(context, apk_a, pkg_a, type_a, entry_a, apk_b, pkg_b, type_b,
+ entry_b);
+ }
+ if (found_b_entries.size() < type_b.entries.size()) {
+ diff = true;
+ for (auto&& entry_b : type_b.entries) {
+ if (found_b_entries.contains(&entry_b)) {
+ continue;
+ }
+ // Type B contains a type that type A does not have.
+ std::stringstream str_stream;
+ str_stream << "new entry " << pkg_b.name << ":" << type_b.named_type << "/" << entry_b.name;
+ EmitDiffLine(apk_b->GetSource(), str_stream.str());
}
}
return diff;
}
-static bool EmitResourcePackageDiff(IAaptContext* context, LoadedApk* apk_a,
+static bool EmitResourcePackageDiff(DiffContext* context, LoadedApk* apk_a,
const ResourceTablePackageView& pkg_a, LoadedApk* apk_b,
const ResourceTablePackageView& pkg_b) {
bool diff = false;
@@ -302,7 +338,8 @@ static bool EmitResourcePackageDiff(IAaptContext* context, LoadedApk* apk_a,
str_stream << ")";
EmitDiffLine(apk_b->GetSource(), str_stream.str());
diff = true;
- } else if (IsIdDiff(type_a.visibility_level, type_a.id, type_b.visibility_level, type_b.id)) {
+ } else if (!context->ignore_id_shift &&
+ IsIdDiff(type_a.visibility_level, type_a.id, type_b.visibility_level, type_b.id)) {
std::stringstream str_stream;
str_stream << pkg_a.name << ":" << type_a.named_type << " has different public ID (";
if (type_b.id) {
@@ -332,7 +369,7 @@ static bool EmitResourcePackageDiff(IAaptContext* context, LoadedApk* apk_a,
return diff;
}
-static bool EmitResourceTableDiff(IAaptContext* context, LoadedApk* apk_a, LoadedApk* apk_b) {
+static bool EmitResourceTableDiff(DiffContext* context, LoadedApk* apk_a, LoadedApk* apk_b) {
const auto table_a = apk_a->GetResourceTable()->GetPartitionedView();
const auto table_b = apk_b->GetResourceTable()->GetPartitionedView();
@@ -355,7 +392,7 @@ static bool EmitResourceTableDiff(IAaptContext* context, LoadedApk* apk_a, Loade
} else {
const auto& package_a = *package_a_iter;
const auto& package_b = *package_b_iter;
- if (package_a.id != package_b.id) {
+ if (package_a.id != package_b.id && !context->ignore_id_shift) {
std::stringstream str_stream;
str_stream << "package '" << package_a.name << "' has different id (";
if (package_b.id) {
@@ -405,7 +442,7 @@ static void ZeroOutAppReferences(ResourceTable* table) {
}
int DiffCommand::Action(const std::vector<std::string>& args) {
- DiffContext context;
+ DiffContext context(ignore_id_shift_);
if (args.size() != 2u) {
std::cerr << "must have two apks as arguments.\n\n";
diff --git a/tools/aapt2/cmd/Diff.h b/tools/aapt2/cmd/Diff.h
index c38888863be9..32e394416880 100644
--- a/tools/aapt2/cmd/Diff.h
+++ b/tools/aapt2/cmd/Diff.h
@@ -14,9 +14,7 @@
* limitations under the License.
*/
-#ifndef AAPT2_DIFF_H
-#define AAPT2_DIFF_H
-
+#pragma once
#include "Command.h"
namespace aapt {
@@ -25,11 +23,16 @@ class DiffCommand : public Command {
public:
explicit DiffCommand() : Command("diff") {
SetDescription("Prints the differences in resources of two apks.");
+ AddOptionalSwitch("--ignore-id-shift",
+ "Match the resources when their IDs shift, e.g. because of the added\n"
+ "or deleted entries.",
+ &ignore_id_shift_);
}
int Action(const std::vector<std::string>& args) override;
-};
-}// namespace aapt
+ private:
+ bool ignore_id_shift_ = false;
+};
-#endif //AAPT2_DIFF_H
+} // namespace aapt