summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--AconfigFlags.bp45
-rw-r--r--OWNERS1
-rw-r--r--core/api/system-current.txt8
-rw-r--r--core/java/android/app/INotificationManager.aidl7
-rw-r--r--core/java/android/app/Notification.java27
-rw-r--r--core/java/android/app/NotificationManager.java25
-rw-r--r--core/java/android/app/TaskInfo.java8
-rw-r--r--core/java/android/app/notification.aconfig10
-rw-r--r--core/java/android/app/supervision/SupervisionManager.java29
-rw-r--r--core/java/android/app/supervision/flags.aconfig8
-rw-r--r--core/java/android/appwidget/AppWidgetManager.java18
-rw-r--r--core/java/android/companion/AssociationInfo.java20
-rw-r--r--core/java/android/companion/AssociationRequest.java9
-rw-r--r--core/java/android/companion/CompanionDeviceManager.java51
-rw-r--r--core/java/android/companion/virtual/VirtualDeviceManager.java5
-rw-r--r--core/java/android/companion/virtual/VirtualDeviceParams.java2
-rw-r--r--core/java/android/content/pm/RegisteredServicesCache.java125
-rw-r--r--core/java/android/hardware/input/InputSettings.java37
-rw-r--r--core/java/android/hardware/location/ContextHubManager.java17
-rw-r--r--core/java/android/hardware/location/IContextHubService.aidl4
-rw-r--r--core/java/android/os/AppZygote.java87
-rw-r--r--core/java/android/os/ChildZygoteProcess.java38
-rw-r--r--core/java/android/os/Parcel.java16
-rw-r--r--core/java/android/os/PerfettoTrace.java170
-rw-r--r--core/java/android/os/PerfettoTrackEventExtra.java481
-rw-r--r--core/java/android/os/ZygoteProcess.java2
-rw-r--r--core/java/android/os/flags.aconfig7
-rw-r--r--core/java/android/permission/flags.aconfig6
-rw-r--r--core/java/android/provider/Settings.java40
-rw-r--r--core/java/android/security/flags.aconfig2
-rw-r--r--core/java/android/service/quickaccesswallet/QuickAccessWalletClient.java10
-rw-r--r--core/java/android/service/quickaccesswallet/QuickAccessWalletClientImpl.java18
-rw-r--r--core/java/android/service/quickaccesswallet/QuickAccessWalletServiceInfo.java55
-rw-r--r--core/java/android/timezone/MobileCountries.java68
-rw-r--r--core/java/android/timezone/TelephonyNetworkFinder.java17
-rw-r--r--core/java/android/util/proto/ProtoFieldFilter.java335
-rw-r--r--core/java/android/view/Choreographer.java9
-rw-r--r--core/java/android/view/SurfaceView.java5
-rw-r--r--core/java/android/view/TextureView.java5
-rw-r--r--core/java/android/view/View.java6
-rw-r--r--core/java/android/view/ViewRootImpl.java14
-rw-r--r--core/java/android/view/accessibility/AccessibilityManager.java3
-rw-r--r--core/java/android/view/contentcapture/ChildContentCaptureSession.java4
-rw-r--r--core/java/android/view/contentcapture/ContentCaptureManager.java2
-rw-r--r--core/java/android/view/contentcapture/ContentCaptureSession.java2
-rw-r--r--core/java/android/view/contentcapture/MainContentCaptureSession.java17
-rw-r--r--core/java/android/view/inputmethod/InputMethodInfo.java1
-rw-r--r--core/java/android/widget/CompoundButton.java13
-rw-r--r--core/java/android/widget/EditText.java4
-rw-r--r--core/java/android/window/ITaskOrganizerController.aidl7
-rw-r--r--core/java/android/window/WindowContainerTransaction.java37
-rw-r--r--core/java/android/window/flags/windowing_sdk.aconfig7
-rw-r--r--core/java/com/android/internal/jank/Cuj.java17
-rw-r--r--core/java/com/android/internal/widget/NotificationProgressBar.java180
-rw-r--r--core/java/com/android/internal/widget/NotificationProgressModel.java40
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/CoreDocument.java2
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/Operations.java12
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/RemoteComposeBuffer.java144
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/RemoteComposeState.java96
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/RemoteContext.java67
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/ClipPath.java6
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/DataListFloat.java7
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/DataListIds.java7
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/DataMapIds.java9
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase2.java11
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase3.java7
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase4.java7
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase6.java21
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmap.java11
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmapInt.java15
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmapScaled.java17
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/DrawPath.java6
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/DrawTextOnPath.java9
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/DrawTweenPath.java10
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/MatrixRestore.java5
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/MatrixSave.java5
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/PaintData.java7
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/PathAppend.java20
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/PathCreate.java14
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/PathData.java7
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/TextData.java7
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/TextLength.java5
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/TextMeasure.java5
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/LayoutComponent.java15
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/CollapsibleColumnLayout.java175
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/CollapsibleRowLayout.java175
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/ColumnLayout.java95
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/LayoutManager.java32
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/RowLayout.java100
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HeightInModifierOperation.java124
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HeightModifierOperation.java19
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/WidthInModifierOperation.java124
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/WidthModifierOperation.java19
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/utilities/AnimatedFloatExpression.java126
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/Easing.java38
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/FloatAnimation.java39
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/GeneralEasing.java7
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/MonotonicCurveFit.java42
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/MonotonicSpline.java14
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/SpringStopEngine.java65
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/StepCurve.java7
-rw-r--r--core/java/com/android/internal/widget/remotecompose/player/RemoteComposePlayer.java22
-rw-r--r--core/java/com/android/internal/widget/remotecompose/player/platform/RemoteComposeCanvas.java15
-rw-r--r--core/jni/android_graphics_BLASTBufferQueue.cpp11
-rw-r--r--core/jni/android_os_PerfettoTrace.cpp49
-rw-r--r--core/jni/android_os_PerfettoTrackEventExtra.cpp54
-rw-r--r--core/proto/android/providers/settings/secure.proto1
-rw-r--r--core/proto/android/providers/settings/system.proto3
-rw-r--r--core/res/AndroidManifest.xml7
-rw-r--r--core/res/res/layout/preference_list_fragment.xml1
-rw-r--r--core/res/res/layout/preference_list_fragment_material.xml1
-rw-r--r--core/res/res/values/attrs.xml2
-rw-r--r--core/res/res/values/public-staging.xml2
-rw-r--r--core/tests/InputMethodCoreTests/src/android/view/inputmethod/InputMethodInfoTest.java2
-rw-r--r--core/tests/coretests/src/android/app/NotificationManagerTest.java85
-rw-r--r--core/tests/coretests/src/android/content/pm/RegisteredServicesCacheTest.java16
-rw-r--r--core/tests/coretests/src/android/os/PerfettoTraceTest.java226
-rw-r--r--core/tests/coretests/src/android/view/contentcapture/ContentCaptureSessionTest.java2
-rw-r--r--core/tests/coretests/src/android/view/contentcapture/MainContentCaptureSessionTest.java10
-rw-r--r--core/tests/coretests/src/com/android/internal/widget/NotificationProgressBarTest.java380
-rw-r--r--core/tests/coretests/src/com/android/internal/widget/NotificationProgressModelTest.java48
-rw-r--r--core/tests/utiltests/src/android/util/proto/ProtoFieldFilterTest.java230
-rw-r--r--data/etc/privapp-permissions-platform.xml4
-rw-r--r--graphics/java/android/graphics/BLASTBufferQueue.java4
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java8
-rw-r--r--libs/WindowManager/Shell/aconfig/multitasking.aconfig10
-rw-r--r--libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleControllerBubbleBarTest.kt2
-rw-r--r--libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt162
-rw-r--r--libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleViewInfoTaskTest.kt2
-rw-r--r--libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/FakeBubbleTaskViewFactory.kt3
-rw-r--r--libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelperTest.kt22
-rw-r--r--libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewTest.kt3
-rw-r--r--libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerViewTest.kt2
-rw-r--r--libs/WindowManager/Shell/res/drawable/decor_desktop_mode_maximize_button_dark.xml3
-rw-r--r--libs/WindowManager/Shell/shared/Android.bp14
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java33
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java6
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java53
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java36
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/SyncTransactionQueue.java19
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/IPip.aidl6
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipMenuController.java3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitWindowManager.java3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java15
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/CloseDesktopTaskTransitionHandler.kt29
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt6
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandler.kt5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt35
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt728
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt78
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt36
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java10
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipAlphaAnimator.java46
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java24
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java22
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java235
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java23
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/recents/TaskStackTransitionObserver.kt18
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java8
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskView.java21
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewController.java133
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewFactoryController.java12
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java321
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java259
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/MixedTransitionHelper.java30
-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/CarWindowDecorViewModel.java15
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CarWindowDecoration.java43
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopHeaderManageWindowsMenu.kt3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java8
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/ReusableWindowDecorViewHost.kt3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDividerWindowManager.kt2
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java15
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleViewInfoTest.kt2
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/CloseDesktopTaskTransitionHandlerTest.kt3
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandlerTest.kt3
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt40
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt4
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt44
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt126
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipAlphaAnimatorTest.java109
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipSchedulerTest.java2
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java234
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTest.java57
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTransitionsTest.java20
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt3
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTestsBase.kt3
-rw-r--r--media/java/android/media/MediaCodecInfo.java8
-rw-r--r--media/java/android/media/flags/media_better_together.aconfig12
-rw-r--r--media/java/android/media/tv/TvInputService.java21
-rw-r--r--media/java/android/media/tv/TvInputServiceExtensionManager.java88
-rw-r--r--packages/SettingsLib/ActionButtonsPreference/res/color/settingslib_expressive_actionbutton_background.xml24
-rw-r--r--packages/SettingsLib/ActionButtonsPreference/res/color/settingslib_expressive_actionbutton_content_color.xml25
-rw-r--r--packages/SettingsLib/ActionButtonsPreference/res/values-v35/styles_expressive.xml11
-rw-r--r--packages/SettingsLib/ActionButtonsPreference/src/com/android/settingslib/widget/ActionButtonsPreference.java2
-rw-r--r--packages/SettingsLib/Android.bp3
-rw-r--r--packages/SettingsLib/BannerMessagePreference/Android.bp8
-rw-r--r--packages/SettingsLib/BannerMessagePreference/res/color/settingslib_banner_button_background_high.xml24
-rw-r--r--packages/SettingsLib/BannerMessagePreference/res/color/settingslib_banner_button_background_low.xml24
-rw-r--r--packages/SettingsLib/BannerMessagePreference/res/color/settingslib_banner_button_background_medium.xml24
-rw-r--r--packages/SettingsLib/BannerMessagePreference/res/color/settingslib_banner_button_background_normal.xml24
-rw-r--r--packages/SettingsLib/BannerMessagePreference/res/drawable-v31/settingslib_card_background.xml2
-rw-r--r--packages/SettingsLib/BannerMessagePreference/res/drawable-v35/settingslib_expressive_card_background.xml21
-rw-r--r--packages/SettingsLib/BannerMessagePreference/res/layout-v31/settingslib_banner_message.xml139
-rw-r--r--packages/SettingsLib/BannerMessagePreference/res/layout-v35/settingslib_expressive_banner_message.xml108
-rw-r--r--packages/SettingsLib/BannerMessagePreference/res/layout/settingslib_banner_message_preference_group.xml27
-rw-r--r--packages/SettingsLib/BannerMessagePreference/res/values-v31/styles.xml2
-rw-r--r--packages/SettingsLib/BannerMessagePreference/res/values-v35/styles_expressive.xml76
-rw-r--r--packages/SettingsLib/BannerMessagePreference/res/values/attrs.xml11
-rw-r--r--packages/SettingsLib/BannerMessagePreference/res/values/colors.xml2
-rw-r--r--packages/SettingsLib/BannerMessagePreference/src/com/android/settingslib/widget/BannerMessagePreference.java150
-rw-r--r--packages/SettingsLib/BannerMessagePreference/src/com/android/settingslib/widget/BannerMessagePreferenceGroup.kt149
-rw-r--r--packages/SettingsLib/ButtonPreference/Android.bp7
-rw-r--r--packages/SettingsLib/ButtonPreference/res/color/settingslib_section_button_background.xml22
-rw-r--r--packages/SettingsLib/ButtonPreference/res/drawable/settingslib_number_button_background.xml21
-rw-r--r--packages/SettingsLib/ButtonPreference/res/drawable/settingslib_number_count_background.xml21
-rw-r--r--packages/SettingsLib/ButtonPreference/res/layout/settingslib_number_button.xml45
-rw-r--r--packages/SettingsLib/ButtonPreference/res/layout/settingslib_section_button.xml30
-rw-r--r--packages/SettingsLib/ButtonPreference/res/values/styles.xml29
-rw-r--r--packages/SettingsLib/ButtonPreference/src/com/android/settingslib/widget/NumberButtonPreference.kt70
-rw-r--r--packages/SettingsLib/ButtonPreference/src/com/android/settingslib/widget/SectionButtonPreference.kt71
-rw-r--r--packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGraphBuilder.kt7
-rw-r--r--packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PersistentPreference.kt17
-rw-r--r--packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceTypes.kt18
-rw-r--r--packages/SettingsLib/OWNERS1
-rw-r--r--packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBinding.kt2
-rw-r--r--packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBindings.kt8
-rw-r--r--packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceFragment.kt16
-rw-r--r--packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceScreenBindingHelper.kt6
-rw-r--r--packages/SettingsLib/SettingsTheme/Android.bp5
-rw-r--r--packages/SettingsLib/SettingsTheme/aconfig/settingstheme.aconfig10
-rw-r--r--packages/SettingsLib/SettingsTheme/res/drawable/settingslib_expressive_icon_chevron.xml (renamed from packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_expressive_icon_chevron.xml)0
-rw-r--r--packages/SettingsLib/SettingsTheme/res/drawable/settingslib_expressive_icon_collapse.xml (renamed from packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_expressive_icon_collapse.xml)0
-rw-r--r--packages/SettingsLib/SettingsTheme/res/drawable/settingslib_expressive_icon_cross.xml36
-rw-r--r--packages/SettingsLib/SettingsTheme/res/drawable/settingslib_expressive_icon_expand.xml (renamed from packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_expressive_icon_expand.xml)0
-rw-r--r--packages/SettingsLib/SettingsTheme/res/drawable/settingslib_expressive_icon_level_high.xml41
-rw-r--r--packages/SettingsLib/SettingsTheme/res/drawable/settingslib_expressive_icon_level_low.xml41
-rw-r--r--packages/SettingsLib/SettingsTheme/res/drawable/settingslib_expressive_icon_level_medium.xml41
-rw-r--r--packages/SettingsLib/SettingsTheme/res/drawable/settingslib_expressive_icon_level_normal.xml41
-rw-r--r--packages/SettingsLib/SettingsTheme/res/drawable/settingslib_expressive_icon_up.xml25
-rw-r--r--packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_preference.xml2
-rw-r--r--packages/SettingsLib/SettingsTheme/res/layout/settingslib_expressive_collapsable_textview.xml (renamed from packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_collapsable_textview.xml)1
-rw-r--r--packages/SettingsLib/SettingsTheme/res/values-night/colors.xml5
-rw-r--r--packages/SettingsLib/SettingsTheme/res/values-v31/styles_expressive.xml5
-rw-r--r--packages/SettingsLib/SettingsTheme/res/values-v35/styles_expressive.xml5
-rw-r--r--packages/SettingsLib/SettingsTheme/res/values/colors.xml7
-rw-r--r--packages/SettingsLib/SettingsTheme/src/com/android/settingslib/widget/SettingsThemeHelper.kt4
-rw-r--r--packages/SettingsLib/Spa/.gitignore1
-rw-r--r--packages/SettingsLib/Spa/build.gradle.kts2
-rw-r--r--packages/SettingsLib/Spa/gradle/libs.versions.toml4
-rw-r--r--packages/SettingsLib/Spa/spa/build.gradle.kts4
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/DropdownTextBox.kt4
-rw-r--r--packages/SettingsLib/Spa/testutils/build.gradle.kts2
-rw-r--r--packages/SettingsLib/TopIntroPreference/res/layout-v33/top_intro_preference.xml41
-rw-r--r--packages/SettingsLib/TopIntroPreference/res/layout/settingslib_expressive_top_intro.xml (renamed from packages/SettingsLib/TopIntroPreference/res/layout-v35/settingslib_expressive_top_intro.xml)2
-rw-r--r--packages/SettingsLib/TopIntroPreference/res/layout/top_intro_preference.xml39
-rw-r--r--packages/SettingsLib/TopIntroPreference/src/com/android/settingslib/widget/TopIntroPreference.kt43
-rw-r--r--packages/SettingsLib/res/values/config.xml11
-rw-r--r--packages/SettingsLib/res/values/strings.xml2
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/PreferenceBindings.kt37
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/mobile/MobileMappings.java55
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/notification/modes/DndDurationDialogFactory.java (renamed from packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenDurationDialog.java)9
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/notification/modes/EnableDndDialogFactory.java (renamed from packages/SettingsLib/src/com/android/settingslib/notification/modes/EnableZenModeDialog.java)23
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/notification/modes/EnableDndDialogMetricsLogger.java (renamed from packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenModeDialogMetricsLogger.java)6
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/modes/DndDurationDialogFactoryTest.java (renamed from packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/modes/ZenDurationDialogTest.java)92
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/modes/EnableDndDialogFactoryTest.java (renamed from packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/modes/EnableZenModeDialogTest.java)69
-rw-r--r--packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java2
-rw-r--r--packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java1
-rw-r--r--packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java3
-rw-r--r--packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java1
-rw-r--r--packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java3
-rw-r--r--packages/Shell/AndroidManifest.xml5
-rw-r--r--packages/SystemUI/Android.bp16
-rw-r--r--packages/SystemUI/OWNERS1
-rw-r--r--packages/SystemUI/aconfig/systemui.aconfig21
-rw-r--r--packages/SystemUI/compose/core/src/com/android/compose/gesture/effect/OffsetOverscrollEffect.kt16
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt4
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt7
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt3
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/TopAreaSection.kt11
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt52
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt4
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneRevealScrim.kt45
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToSplitShadeTransition.kt7
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToSplitShadeTransition.kt7
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromNotificationsShadeToQuickSettingsShadeTransition.kt8
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToNotificationsShadeTransition.kt8
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToQuickSettingsShadeTransition.kt8
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToShadeTransition.kt7
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/spatialaudio/ui/composable/SpatialAudioPopup.kt9
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateContent.kt6
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt3
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt65
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt27
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeAnimation.kt8
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt17
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt9
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Content.kt17
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/state/TransitionState.kt18
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/reveal/ContainerReveal.kt9
-rw-r--r--packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/AnimatedSharedAsStateTest.kt2
-rw-r--r--packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ContentTest.kt2
-rw-r--r--packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt12
-rw-r--r--packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt53
-rw-r--r--packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/InterruptionHandlerTest.kt10
-rw-r--r--packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MovableElementTest.kt4
-rw-r--r--packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MutableSceneTransitionLayoutStateForTests.kt47
-rw-r--r--packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ObservableTransitionStateTest.kt8
-rw-r--r--packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/OverlayTest.kt14
-rw-r--r--packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/PredictiveBackHandlerTest.kt10
-rw-r--r--packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt44
-rw-r--r--packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt85
-rw-r--r--packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt24
-rw-r--r--packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt42
-rw-r--r--packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/NestedElementTransformationTest.kt5
-rw-r--r--packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/NestedSharedElementTest.kt5
-rw-r--r--packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestContentScope.kt2
-rw-r--r--packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestTransition.kt84
-rw-r--r--packages/SystemUI/log/src/com/android/systemui/log/LogBuffer.kt10
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/fontscaling/FontScalingDialogDelegateTest.kt2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalDreamStartableTest.kt74
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt32
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfigTest.kt10
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt20
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodToPrimaryBouncerTransitionViewModelTest.kt43
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DozingToPrimaryBouncerTransitionViewModelTest.kt20
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/ModesTileTest.kt8
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/logging/QSTileLoggerTest.kt2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractorTest.kt8
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelTest.kt109
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModelTest.kt136
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModelTest.kt95
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModelTest.kt123
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/ChipTransitionHelperTest.kt12
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipViewModelTest.kt32
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelTest.kt35
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithNotifsViewModelTest.kt19
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java1
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractorImplTest.kt6
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeHomeStatusBarViewModel.kt23
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelImplTest.kt8
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegateTest.kt3
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/wmshell/TestableBubbleController.java4
-rw-r--r--packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java5
-rw-r--r--packages/SystemUI/res/layout/contextual_edu_dialog.xml2
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl12
-rw-r--r--packages/SystemUI/src/com/android/keyguard/ClockEventController.kt25
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/FullscreenMagnificationController.java26
-rw-r--r--packages/SystemUI/src/com/android/systemui/common/ui/view/ViewExt.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/CommunalDreamStartable.kt22
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutCustomizer.kt17
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt1110
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/Surfaces.kt125
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerLockscreenVisibilityManager.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfig.kt21
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToPrimaryBouncerTransitionViewModel.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToPrimaryBouncerTransitionViewModel.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/log/LogBufferFactory.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImpl.kt101
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessor.kt122
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewController.kt12
-rw-r--r--packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java18
-rw-r--r--packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogV2.kt51
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java12
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/QSEnableDndDialogMetricsLogger.java (renamed from packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/QSZenModeDialogMetricsLogger.java)6
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractor.kt13
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/ShadeStateTraceLogger.kt12
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt42
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModel.kt33
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModel.kt60
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModel.kt26
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModel.kt29
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModel.kt57
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/binder/OngoingActivityChipBinder.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/OngoingActivityChip.kt52
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/OngoingActivityChipModel.kt44
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipViewModel.kt26
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/dagger/NotificationsLogModule.kt48
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationUiAod.kt60
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationUiForceExpanded.kt61
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java17
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt19
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java17
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java69
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/StatusBarChipsModernization.kt37
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/HomeStatusBarViewBinder.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/composable/StatusBarRoot.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/model/SystemInfoCombinedVisibilityModel.kt44
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/model/VisibilityModel.kt49
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModel.kt226
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegate.kt26
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModel.kt23
-rw-r--r--packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderIconProvider.kt28
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderViewModel.kt28
-rw-r--r--packages/SystemUI/src/com/android/systemui/wallet/controller/QuickAccessWalletController.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/wallet/ui/WalletActivity.java11
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt3
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt1
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt1
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorSceneContainerTest.kt1
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt1
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImplTest.kt300
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorTest.kt292
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java77
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/wallet/controller/QuickAccessWalletControllerTest.java7
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java8
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LightRevealScrimViewModelKosmos.kt25
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorKosmos.kt4
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractorKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneKosmos.kt4
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt4
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegateKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/wallpapers/data/repository/WallpaperRepositoryKosmos.kt4
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/wallpapers/ui/viewmodel/WallpaperViewModelKosmos.kt23
-rw-r--r--packages/SystemUI/utils/Android.bp1
-rw-r--r--packages/SystemUI/utils/src/com/android/systemui/utils/coroutines/flow/LatestConflated.kt3
-rw-r--r--ravenwood/texts/ravenwood-annotation-allowed-classes.txt1
-rw-r--r--ravenwood/texts/ravenwood-standard-options.txt2
-rw-r--r--ravenwood/tools/hoststubgen/hoststubgen-standard-options.txt2
-rw-r--r--ravenwood/tools/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt2
-rw-r--r--ravenwood/tools/hoststubgen/src/com/android/hoststubgen/HostStubGenOptions.kt5
-rw-r--r--ravenwood/tools/hoststubgen/src/com/android/hoststubgen/visitors/BaseAdapter.kt35
-rw-r--r--ravenwood/tools/hoststubgen/src/com/android/hoststubgen/visitors/ImplGeneratingAdapter.kt3
-rw-r--r--ravenwood/tools/hoststubgen/test-tiny-framework/golden-output.RELEASE_TARGET_JAVA_21/01-hoststubgen-test-tiny-framework-orig-dump.txt172
-rw-r--r--ravenwood/tools/hoststubgen/test-tiny-framework/golden-output/01-hoststubgen-test-tiny-framework-orig-dump.txt172
-rw-r--r--ravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotations.java8
-rw-r--r--services/accessibility/java/com/android/server/accessibility/AutoclickController.java18
-rw-r--r--services/accessibility/java/com/android/server/accessibility/AutoclickIndicatorView.java9
-rw-r--r--services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java41
-rw-r--r--services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java26
-rw-r--r--services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java10
-rw-r--r--services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java2
-rw-r--r--services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java4
-rw-r--r--services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java13
-rw-r--r--services/contextualsearch/java/com/android/server/contextualsearch/ContextualSearchManagerService.java35
-rw-r--r--services/core/java/com/android/server/BootReceiver.java129
-rw-r--r--services/core/java/com/android/server/am/ProcessList.java11
-rw-r--r--services/core/java/com/android/server/am/SettingsToPropertiesMapper.java33
-rw-r--r--services/core/java/com/android/server/am/UidObserverController.java56
-rw-r--r--services/core/java/com/android/server/appop/AppOpsService.java34
-rw-r--r--services/core/java/com/android/server/appop/DiscreteOpsDbHelper.java23
-rw-r--r--services/core/java/com/android/server/appop/HistoricalRegistry.java23
-rw-r--r--services/core/java/com/android/server/audio/AudioService.java2
-rw-r--r--services/core/java/com/android/server/display/BrightnessTracker.java11
-rw-r--r--services/core/java/com/android/server/display/DisplayManagerService.java70
-rw-r--r--services/core/java/com/android/server/display/DisplayManagerShellCommand.java19
-rw-r--r--services/core/java/com/android/server/display/feature/DisplayManagerFlags.java28
-rw-r--r--services/core/java/com/android/server/display/feature/display_flags.aconfig22
-rw-r--r--services/core/java/com/android/server/input/InputSettingsObserver.java7
-rw-r--r--services/core/java/com/android/server/input/NativeInputManagerService.java5
-rw-r--r--services/core/java/com/android/server/inputmethod/InputMethodManagerService.java2
-rw-r--r--services/core/java/com/android/server/location/contexthub/ContextHubService.java9
-rw-r--r--services/core/java/com/android/server/location/contexthub/HubInfoRegistry.java119
-rw-r--r--services/core/java/com/android/server/media/quality/MediaQualityService.java44
-rw-r--r--services/core/java/com/android/server/notification/NotificationManagerService.java210
-rw-r--r--services/core/java/com/android/server/notification/ZenLog.java36
-rw-r--r--services/core/java/com/android/server/notification/ZenModeHelper.java54
-rw-r--r--services/core/java/com/android/server/notification/flags.aconfig10
-rw-r--r--services/core/java/com/android/server/os/NativeTombstoneManager.java22
-rw-r--r--services/core/java/com/android/server/os/core_os_flags.aconfig2
-rw-r--r--services/core/java/com/android/server/pm/InstallPackageHelper.java79
-rw-r--r--services/core/java/com/android/server/pm/InstallRequest.java12
-rw-r--r--services/core/java/com/android/server/pm/PackageHandler.java1
-rw-r--r--services/core/java/com/android/server/pm/PackageMetrics.java4
-rw-r--r--services/core/java/com/android/server/pm/permission/PermissionManagerService.java18
-rw-r--r--services/core/java/com/android/server/power/OWNERS2
-rw-r--r--services/core/java/com/android/server/stats/pull/StatsPullAtomService.java2
-rw-r--r--services/core/java/com/android/server/statusbar/StatusBarManagerService.java85
-rw-r--r--services/core/java/com/android/server/wm/ActivityRecord.java271
-rw-r--r--services/core/java/com/android/server/wm/ActivityRefresher.java12
-rw-r--r--services/core/java/com/android/server/wm/ActivityStarter.java9
-rw-r--r--services/core/java/com/android/server/wm/ActivityTaskManagerService.java3
-rw-r--r--services/core/java/com/android/server/wm/AppCompatAspectRatioOverrides.java2
-rw-r--r--services/core/java/com/android/server/wm/AppCompatAspectRatioPolicy.java46
-rw-r--r--services/core/java/com/android/server/wm/AppCompatCameraPolicy.java2
-rw-r--r--services/core/java/com/android/server/wm/AppCompatController.java4
-rw-r--r--services/core/java/com/android/server/wm/AppCompatLetterboxPolicy.java50
-rw-r--r--services/core/java/com/android/server/wm/AppCompatOrientationPolicy.java2
-rw-r--r--services/core/java/com/android/server/wm/AppCompatOverrides.java10
-rw-r--r--services/core/java/com/android/server/wm/AppCompatUtils.java10
-rw-r--r--services/core/java/com/android/server/wm/AppTransitionController.java21
-rw-r--r--services/core/java/com/android/server/wm/AppWarnings.java14
-rw-r--r--services/core/java/com/android/server/wm/AsyncRotationController.java3
-rw-r--r--services/core/java/com/android/server/wm/BackgroundActivityStartController.java4
-rw-r--r--services/core/java/com/android/server/wm/CameraCompatFreeformPolicy.java8
-rw-r--r--services/core/java/com/android/server/wm/DisplayContent.java112
-rw-r--r--services/core/java/com/android/server/wm/DisplayRotation.java108
-rw-r--r--services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java12
-rw-r--r--services/core/java/com/android/server/wm/DragDropController.java12
-rw-r--r--services/core/java/com/android/server/wm/PageSizeMismatchDialog.java12
-rw-r--r--services/core/java/com/android/server/wm/RootWindowContainer.java47
-rw-r--r--services/core/java/com/android/server/wm/ScreenRotationAnimation.java825
-rw-r--r--services/core/java/com/android/server/wm/SimpleSurfaceAnimatable.java330
-rw-r--r--services/core/java/com/android/server/wm/Task.java2
-rw-r--r--services/core/java/com/android/server/wm/TaskFragment.java6
-rw-r--r--services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java1
-rw-r--r--services/core/java/com/android/server/wm/Transition.java5
-rw-r--r--services/core/java/com/android/server/wm/WindowAnimator.java4
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerService.java279
-rw-r--r--services/core/java/com/android/server/wm/WindowOrganizerController.java22
-rw-r--r--services/core/java/com/android/server/wm/WindowProcessController.java8
-rw-r--r--services/core/java/com/android/server/wm/WindowState.java53
-rw-r--r--services/core/java/com/android/server/wm/WindowStateAnimator.java2
-rw-r--r--services/core/jni/com_android_server_input_InputManagerService.cpp29
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java24
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyData.java31
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java196
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/EnforcingAdmin.java55
-rw-r--r--services/java/com/android/server/SystemServer.java4
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java65
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/power/ScreenUndimDetectorTest.java2
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/AutoclickControllerTest.java242
-rw-r--r--services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java8
-rw-r--r--services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest3.java11
-rw-r--r--services/tests/servicestests/src/com/android/server/statusbar/StatusBarManagerServiceTest.java50
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java214
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java31
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java112
-rw-r--r--services/tests/wmtests/Android.bp1
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java19
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/ActivityRefresherTests.java12
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/AppCompatCameraOverridesTest.java2
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/CameraCompatFreeformPolicyTests.java10
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java21
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java14
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java32
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java30
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java1
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TaskTests.java38
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowContainerTransactionTests.java28
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java6
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java14
-rw-r--r--telecomm/java/android/telecom/Call.java7
-rw-r--r--telephony/java/android/telephony/TelephonyManager.java37
559 files changed, 13116 insertions, 8213 deletions
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index 0a61df71cc29..302168d845fa 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -102,6 +102,7 @@ aconfig_declarations_group {
"com.android.media.flags.projection-aconfig-java",
"com.android.net.http.flags-aconfig-exported-java",
"com.android.net.thread.platform.flags-aconfig-java",
+ "com.android.permission.flags-aconfig-java-export",
"com.android.ranging.flags.ranging-aconfig-java-export",
"com.android.server.contextualsearch.flags-java",
"com.android.server.flags.services-aconfig-java",
@@ -115,6 +116,7 @@ aconfig_declarations_group {
"framework-jobscheduler-job.flags-aconfig-java",
"framework_graphics_flags_java_lib",
"hwui_flags_java_lib",
+ "icu_exported_aconfig_flags_lib",
"interaction_jank_monitor_flags_lib",
"keystore2_flags_java-framework",
"libcore_exported_aconfig_flags_lib",
@@ -163,6 +165,14 @@ java_aconfig_library {
defaults: ["framework-minus-apex-aconfig-java-defaults"],
}
+// ICU
+java_aconfig_library {
+ name: "icu_exported_aconfig_flags_lib",
+ aconfig_declarations: "icu_aconfig_flags",
+ mode: "exported",
+ defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
+
// Camera
java_aconfig_library {
name: "camera_platform_flags_core_java_lib",
@@ -1846,6 +1856,41 @@ java_aconfig_library {
defaults: ["framework-minus-apex-aconfig-java-defaults"],
}
+// SettingsTheme Lib
+aconfig_declarations {
+ name: "aconfig_settings_theme_flags",
+ package: "com.android.settingslib.widget.theme.flags",
+ container: "system",
+ exportable: true,
+ srcs: [
+ "packages/SettingsLib/SettingsTheme/aconfig/settingstheme.aconfig",
+ ],
+}
+
+java_aconfig_library {
+ name: "aconfig_settingstheme_exported_flags_java_lib",
+ aconfig_declarations: "aconfig_settings_theme_flags",
+ defaults: ["framework-minus-apex-aconfig-java-defaults"],
+ mode: "exported",
+ min_sdk_version: "21",
+ apex_available: [
+ "//apex_available:platform",
+ "com.android.adservices",
+ "com.android.cellbroadcast",
+ "com.android.devicelock",
+ "com.android.extservices",
+ "com.android.healthfitness",
+ "com.android.mediaprovider",
+ "com.android.permission",
+ ],
+}
+
+java_aconfig_library {
+ name: "aconfig_settingstheme_flags_java_lib",
+ aconfig_declarations: "aconfig_settings_theme_flags",
+ defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
+
// Quick Access Wallet
aconfig_declarations {
name: "android.service.quickaccesswallet.flags-aconfig",
diff --git a/OWNERS b/OWNERS
index 058ea3619a58..aa93a275100a 100644
--- a/OWNERS
+++ b/OWNERS
@@ -16,6 +16,7 @@ omakoto@google.com #{LAST_RESORT_SUGGESTION}
roosa@google.com #{LAST_RESORT_SUGGESTION}
smoreland@google.com #{LAST_RESORT_SUGGESTION}
yamasani@google.com #{LAST_RESORT_SUGGESTION}
+timmurray@google.com #{LAST_RESORT_SUGGESTION}
# API changes are already covered by API-Review+1 (http://mdb/android-api-council)
# via https://android.git.corp.google.com/All-Projects/+/refs/meta/config/rules.pl.
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 6e0defed1e27..15ae79e34061 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -3462,7 +3462,7 @@ package android.companion.virtual {
method public void removeSoundEffectListener(@NonNull android.companion.virtual.VirtualDeviceManager.SoundEffectListener);
method public void setDevicePolicy(int, int);
method @FlaggedApi("android.companion.virtualdevice.flags.activity_control_api") public void setDevicePolicy(int, int, int);
- method @FlaggedApi("android.companion.virtual.flags.vdm_custom_ime") public void setDisplayImePolicy(int, int);
+ method public void setDisplayImePolicy(int, int);
method public void setShowPointerIcon(boolean);
method public void unregisterIntentInterceptor(@NonNull android.companion.virtual.VirtualDeviceManager.IntentInterceptorCallback);
method @FlaggedApi("android.companion.virtualdevice.flags.device_aware_display_power") public void wakeUp();
@@ -3481,7 +3481,7 @@ package android.companion.virtual {
method public int getDevicePolicy(int);
method @FlaggedApi("android.companion.virtualdevice.flags.device_aware_display_power") @NonNull public java.time.Duration getDimDuration();
method @Nullable public android.content.ComponentName getHomeComponent();
- method @FlaggedApi("android.companion.virtual.flags.vdm_custom_ime") @Nullable public android.content.ComponentName getInputMethodComponent();
+ method @Nullable public android.content.ComponentName getInputMethodComponent();
method public int getLockState();
method @Nullable public String getName();
method @FlaggedApi("android.companion.virtualdevice.flags.device_aware_display_power") @NonNull public java.time.Duration getScreenOffTimeout();
@@ -3520,7 +3520,7 @@ package android.companion.virtual {
method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setDevicePolicy(int, int);
method @FlaggedApi("android.companion.virtualdevice.flags.device_aware_display_power") @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setDimDuration(@NonNull java.time.Duration);
method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setHomeComponent(@Nullable android.content.ComponentName);
- method @FlaggedApi("android.companion.virtual.flags.vdm_custom_ime") @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setInputMethodComponent(@Nullable android.content.ComponentName);
+ method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setInputMethodComponent(@Nullable android.content.ComponentName);
method @NonNull @RequiresPermission(value=android.Manifest.permission.ADD_ALWAYS_UNLOCKED_DISPLAY, conditional=true) public android.companion.virtual.VirtualDeviceParams.Builder setLockState(int);
method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setName(@NonNull String);
method @FlaggedApi("android.companion.virtualdevice.flags.device_aware_display_power") @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setScreenOffTimeout(@NonNull java.time.Duration);
@@ -19043,7 +19043,7 @@ package android.view.displayhash {
package android.view.inputmethod {
public final class InputMethodInfo implements android.os.Parcelable {
- method @FlaggedApi("android.companion.virtual.flags.vdm_custom_ime") public boolean isVirtualDeviceOnly();
+ method public boolean isVirtualDeviceOnly();
}
public final class InputMethodManager {
diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl
index 1738a92b7672..8fa2362139a1 100644
--- a/core/java/android/app/INotificationManager.aidl
+++ b/core/java/android/app/INotificationManager.aidl
@@ -271,6 +271,9 @@ interface INotificationManager
int[] getAllowedAdjustmentKeyTypes();
void setAssistantAdjustmentKeyTypeState(int type, boolean enabled);
- int[] getAllowedAdjustmentKeyTypesForPackage(String pkg);
- void setAssistantAdjustmentKeyTypeStateForPackage(String pkg, int type, boolean enabled);
+ String[] getTypeAdjustmentDeniedPackages();
+ void setTypeAdjustmentForPackageState(String pkg, boolean enabled);
+
+ // TODO: b/389918945 - Remove once nm_binder_perf flags are going to Nextfood.
+ void incrementCounter(String metricId);
}
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 93d751cb9402..fbe5b9449b00 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -11854,7 +11854,7 @@ public class Notification implements Parcelable
// If segment limit is exceeded. All segments will be replaced
// with a single segment
boolean allSameColor = true;
- int firstSegmentColor = segments.get(0).getColor();
+ int firstSegmentColor = segments.getFirst().getColor();
for (int i = 1; i < segments.size(); i++) {
if (segments.get(i).getColor() != firstSegmentColor) {
@@ -11887,8 +11887,31 @@ public class Notification implements Parcelable
}
}
+ // If the segments and points can't all fit inside the progress drawable, the
+ // view will replace all segments with a single segment.
+ final int segmentsFallbackColor;
+ if (segments.size() <= 1) {
+ segmentsFallbackColor = NotificationProgressModel.INVALID_COLOR;
+ } else {
+
+ boolean allSameColor = true;
+ int firstSegmentColor = segments.getFirst().getColor();
+ for (int i = 1; i < segments.size(); i++) {
+ if (segments.get(i).getColor() != firstSegmentColor) {
+ allSameColor = false;
+ break;
+ }
+ }
+ // If the segments are of the same color, the view can just use that color.
+ // In that case there is no need to send the fallback color.
+ segmentsFallbackColor = allSameColor ? NotificationProgressModel.INVALID_COLOR
+ : sanitizeProgressColor(Notification.COLOR_DEFAULT, backgroundColor,
+ defaultProgressColor);
+ }
+
model = new NotificationProgressModel(segments, points,
- Math.clamp(mProgress, 0, totalLength), mIsStyledByProgress);
+ Math.clamp(mProgress, 0, totalLength), mIsStyledByProgress,
+ segmentsFallbackColor);
}
return model;
}
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index 24f2495d8f09..1e1ec602d0a2 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -665,8 +665,10 @@ public class NotificationManager {
private final InstantSource mClock;
private final RateLimiter mUpdateRateLimiter = new RateLimiter("notify (update)",
+ "notifications.value_client_throttled_notify_update",
MAX_NOTIFICATION_UPDATE_RATE);
private final RateLimiter mUnnecessaryCancelRateLimiter = new RateLimiter("cancel (dupe)",
+ "notifications.value_client_throttled_cancel_duplicate",
MAX_NOTIFICATION_UNNECESSARY_CANCEL_RATE);
// Value is KNOWN_STATUS_ENQUEUED/_CANCELLED
private final LruCache<NotificationKey, Integer> mKnownNotifications = new LruCache<>(100);
@@ -848,19 +850,21 @@ public class NotificationManager {
/** Helper class to rate-limit Binder calls. */
private class RateLimiter {
- private static final Duration RATE_LIMITER_LOG_INTERVAL = Duration.ofSeconds(5);
+ private static final Duration RATE_LIMITER_LOG_INTERVAL = Duration.ofSeconds(1);
private final RateEstimator mInputRateEstimator;
private final RateEstimator mOutputRateEstimator;
private final String mName;
+ private final String mCounterName;
private final float mLimitRate;
private Instant mLogSilencedUntil;
- private RateLimiter(String name, float limitRate) {
+ private RateLimiter(String name, String counterName, float limitRate) {
mInputRateEstimator = new RateEstimator();
mOutputRateEstimator = new RateEstimator();
mName = name;
+ mCounterName = counterName;
mLimitRate = limitRate;
}
@@ -880,6 +884,14 @@ public class NotificationManager {
return;
}
+ if (Flags.nmBinderPerfLogNmThrottling()) {
+ try {
+ service().incrementCounter(mCounterName);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Ignoring error while trying to log " + mCounterName, e);
+ }
+ }
+
long nowMillis = now.toEpochMilli();
Slog.w(TAG, TextUtils.formatSimple(
"Shedding %s of %s, rate limit (%s) exceeded: input %s, output would be %s",
@@ -1644,7 +1656,8 @@ public class NotificationManager {
if (Flags.modesApi() && Flags.modesUi()) {
PackageManager pm = mContext.getPackageManager();
return !pm.hasSystemFeature(PackageManager.FEATURE_WATCH)
- && !pm.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE);
+ && !pm.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)
+ && !pm.hasSystemFeature(PackageManager.FEATURE_LEANBACK);
} else {
return false;
}
@@ -2163,12 +2176,10 @@ public class NotificationManager {
* @hide
*/
@FlaggedApi(android.app.Flags.FLAG_NOTIFICATION_CLASSIFICATION_UI)
- public void setAssistantAdjustmentKeyTypeStateForPackage(@NonNull String pkg,
- @Adjustment.Types int type,
- boolean enabled) {
+ public void setTypeAdjustmentForPackageState(@NonNull String pkg, boolean enabled) {
INotificationManager service = service();
try {
- service.setAssistantAdjustmentKeyTypeStateForPackage(pkg, type, enabled);
+ service.setTypeAdjustmentForPackageState(pkg, enabled);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/android/app/TaskInfo.java b/core/java/android/app/TaskInfo.java
index 76705dcdd3d2..6936ddc5eeac 100644
--- a/core/java/android/app/TaskInfo.java
+++ b/core/java/android/app/TaskInfo.java
@@ -376,6 +376,14 @@ public class TaskInfo {
}
/**
+ * Returns the task id.
+ * @hide
+ */
+ public int getTaskId() {
+ return taskId;
+ }
+
+ /**
* Whether this task is visible.
*/
public boolean isVisible() {
diff --git a/core/java/android/app/notification.aconfig b/core/java/android/app/notification.aconfig
index edd17e8bb552..914ca73f1ce4 100644
--- a/core/java/android/app/notification.aconfig
+++ b/core/java/android/app/notification.aconfig
@@ -295,6 +295,16 @@ flag {
}
flag {
+ name: "nm_binder_perf_log_nm_throttling"
+ namespace: "systemui"
+ description: "Log throttled operations (notify, cancel) to statsd. This flag will NOT be pushed past Trunkfood."
+ bug: "389918945"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "no_sbnholder"
namespace: "systemui"
description: "removes sbnholder from NLS"
diff --git a/core/java/android/app/supervision/SupervisionManager.java b/core/java/android/app/supervision/SupervisionManager.java
index a5b58f968c27..92241f3634e8 100644
--- a/core/java/android/app/supervision/SupervisionManager.java
+++ b/core/java/android/app/supervision/SupervisionManager.java
@@ -34,6 +34,35 @@ public class SupervisionManager {
private final Context mContext;
private final ISupervisionManager mService;
+ /**
+ * Activity action: ask the human user to enable supervision for this user. Only the app that
+ * holds the {@code SYSTEM_SUPERVISION} role can launch this intent.
+ *
+ * <p>The intent must be invoked via {@link Activity#startActivityForResult} to receive the
+ * result of whether or not the user approved the action. If approved, the result will be {@link
+ * Activity#RESULT_OK}.
+ *
+ * <p>If supervision is already enabled, the operation will return a failure result.
+ *
+ * @hide
+ */
+ public static final String ACTION_ENABLE_SUPERVISION = "android.app.action.ENABLE_SUPERVISION";
+
+ /**
+ * Activity action: ask the human user to disable supervision for this user. Only the app that
+ * holds the {@code SYSTEM_SUPERVISION} role can launch this intent.
+ *
+ * <p>The intent must be invoked via {@link Activity#startActivityForResult} to receive the
+ * result of whether or not the user approved the action. If approved, the result will be {@link
+ * Activity#RESULT_OK}.
+ *
+ * <p>If supervision is not enabled, the operation will return a failure result.
+ *
+ * @hide
+ */
+ public static final String ACTION_DISABLE_SUPERVISION =
+ "android.app.action.DISABLE_SUPERVISION";
+
/** @hide */
@UnsupportedAppUsage
public SupervisionManager(Context context, ISupervisionManager service) {
diff --git a/core/java/android/app/supervision/flags.aconfig b/core/java/android/app/supervision/flags.aconfig
index 4ee3a0360b43..18182b804627 100644
--- a/core/java/android/app/supervision/flags.aconfig
+++ b/core/java/android/app/supervision/flags.aconfig
@@ -40,3 +40,11 @@ flag {
description: "Flag to enable the SupervisionAppService"
bug: "389123070"
}
+
+flag {
+ name: "enable_supervision_settings_screen"
+ is_exported: true
+ namespace: "supervision"
+ description: "Flag that enables the Supervision settings screen with top-level Android settings entry point"
+ bug: "383404606"
+}
diff --git a/core/java/android/appwidget/AppWidgetManager.java b/core/java/android/appwidget/AppWidgetManager.java
index 67ad4594599f..b54e17beb100 100644
--- a/core/java/android/appwidget/AppWidgetManager.java
+++ b/core/java/android/appwidget/AppWidgetManager.java
@@ -1684,10 +1684,14 @@ public class AppWidgetManager {
private IBinder mIBinder;
ConnectionTask(@NonNull FilterComparison filter) {
- mContext.bindService(filter.getIntent(),
- Context.BindServiceFlags.of(Context.BIND_AUTO_CREATE),
- mHandler::post,
- this);
+ try {
+ mContext.bindService(filter.getIntent(),
+ Context.BindServiceFlags.of(Context.BIND_AUTO_CREATE),
+ mHandler::post,
+ this);
+ } catch (Exception e) {
+ Log.e(TAG, "Error connecting to service in connection cache", e);
+ }
}
@Override
@@ -1737,7 +1741,11 @@ public class AppWidgetManager {
handleNext();
return;
}
- mContext.unbindService(this);
+ try {
+ mContext.unbindService(this);
+ } catch (Exception e) {
+ Log.e(TAG, "Error unbinding the cached connection", e);
+ }
mActiveConnections.values().remove(this);
}
}
diff --git a/core/java/android/companion/AssociationInfo.java b/core/java/android/companion/AssociationInfo.java
index 2f161150a89b..ceafce2bdbb7 100644
--- a/core/java/android/companion/AssociationInfo.java
+++ b/core/java/android/companion/AssociationInfo.java
@@ -287,8 +287,8 @@ public final class AssociationInfo implements Parcelable {
/**
* Get the device icon of the associated device. The device icon represents the device type.
*
- * @return the device icon, or {@code null} if no device icon has been set for the
- * associated device.
+ * @return the device icon with size 24dp x 24dp.
+ * If the associated device has no icon set, it returns {@code null}.
*
* @see AssociationRequest.Builder#setDeviceIcon(Icon)
*/
@@ -377,6 +377,7 @@ public final class AssociationInfo implements Parcelable {
if (this == o) return true;
if (!(o instanceof AssociationInfo)) return false;
final AssociationInfo that = (AssociationInfo) o;
+
return mId == that.mId
&& mUserId == that.mUserId
&& mSelfManaged == that.mSelfManaged
@@ -391,11 +392,17 @@ public final class AssociationInfo implements Parcelable {
&& Objects.equals(mDeviceProfile, that.mDeviceProfile)
&& Objects.equals(mAssociatedDevice, that.mAssociatedDevice)
&& mSystemDataSyncFlags == that.mSystemDataSyncFlags
- && (mDeviceIcon == null ? that.mDeviceIcon == null
- : mDeviceIcon.sameAs(that.mDeviceIcon))
+ && isSameIcon(mDeviceIcon, that.mDeviceIcon)
&& Objects.equals(mDeviceId, that.mDeviceId);
}
+ private boolean isSameIcon(Icon iconA, Icon iconB) {
+ // Because we've already rescaled and converted both icons to bitmaps,
+ // we can now directly compare them by bitmap.
+ return (iconA == null && iconB == null)
+ || (iconA != null && iconB != null && iconA.getBitmap().sameAs(iconB.getBitmap()));
+ }
+
@Override
public int hashCode() {
return Objects.hash(mId, mUserId, mPackageName, mDeviceMacAddress, mDisplayName,
@@ -425,7 +432,7 @@ public final class AssociationInfo implements Parcelable {
dest.writeLong(mTimeApprovedMs);
dest.writeLong(mLastTimeConnectedMs);
dest.writeInt(mSystemDataSyncFlags);
- if (mDeviceIcon != null) {
+ if (Flags.associationDeviceIcon() && mDeviceIcon != null) {
dest.writeInt(1);
mDeviceIcon.writeToParcel(dest, flags);
} else {
@@ -455,7 +462,8 @@ public final class AssociationInfo implements Parcelable {
mTimeApprovedMs = in.readLong();
mLastTimeConnectedMs = in.readLong();
mSystemDataSyncFlags = in.readInt();
- if (in.readInt() == 1) {
+ int deviceIcon = in.readInt();
+ if (Flags.associationDeviceIcon() && deviceIcon == 1) {
mDeviceIcon = Icon.CREATOR.createFromParcel(in);
} else {
mDeviceIcon = null;
diff --git a/core/java/android/companion/AssociationRequest.java b/core/java/android/companion/AssociationRequest.java
index 32cbf326c923..a098a6067491 100644
--- a/core/java/android/companion/AssociationRequest.java
+++ b/core/java/android/companion/AssociationRequest.java
@@ -385,6 +385,10 @@ public final class AssociationRequest implements Parcelable {
public void setAssociatedDevice(AssociatedDevice associatedDevice) {
mAssociatedDevice = associatedDevice;
}
+ /** @hide */
+ public void setDeviceIcon(Icon deviceIcon) {
+ mDeviceIcon = deviceIcon;
+ }
/** @hide */
@NonNull
@@ -492,9 +496,10 @@ public final class AssociationRequest implements Parcelable {
/**
* Set the device icon for the self-managed device and to display the icon in the
* self-managed association dialog.
+ * <p>The given device icon will be resized to 24dp x 24dp.
*
- * @throws IllegalArgumentException if the icon is not exactly 24dp by 24dp
- * or if it is {@link Icon#TYPE_URI} or {@link Icon#TYPE_URI_ADAPTIVE_BITMAP}.
+ * @throws IllegalArgumentException if the icon is
+ * {@link Icon#TYPE_URI} or {@link Icon#TYPE_URI_ADAPTIVE_BITMAP}.
* @see #setSelfManaged(boolean)
*/
@NonNull
diff --git a/core/java/android/companion/CompanionDeviceManager.java b/core/java/android/companion/CompanionDeviceManager.java
index a96ba11eb482..566e78a8de35 100644
--- a/core/java/android/companion/CompanionDeviceManager.java
+++ b/core/java/android/companion/CompanionDeviceManager.java
@@ -23,7 +23,6 @@ import static android.Manifest.permission.REQUEST_COMPANION_PROFILE_WATCH;
import static android.graphics.drawable.Icon.TYPE_URI;
import static android.graphics.drawable.Icon.TYPE_URI_ADAPTIVE_BITMAP;
-
import android.annotation.CallbackExecutor;
import android.annotation.FlaggedApi;
import android.annotation.IntDef;
@@ -52,10 +51,10 @@ import android.content.Intent;
import android.content.IntentSender;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
+import android.graphics.Canvas;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.Icon;
-import android.graphics.drawable.VectorDrawable;
import android.net.MacAddress;
import android.os.Binder;
import android.os.Handler;
@@ -110,6 +109,7 @@ import java.util.function.Consumer;
@RequiresFeature(PackageManager.FEATURE_COMPANION_DEVICE_SETUP)
public final class CompanionDeviceManager {
private static final String TAG = "CDM_CompanionDeviceManager";
+ private static final int ICON_TARGET_SIZE = 24;
/** @hide */
@IntDef(prefix = {"RESULT_"}, value = {
@@ -474,10 +474,8 @@ public final class CompanionDeviceManager {
if (Flags.associationDeviceIcon()) {
final Icon deviceIcon = request.getDeviceIcon();
-
- if (deviceIcon != null && !isValidIcon(deviceIcon, mContext)) {
- throw new IllegalArgumentException("The size of the device icon must be "
- + "24dp x 24dp to ensure proper display");
+ if (deviceIcon != null) {
+ request.setDeviceIcon(scaleIcon(deviceIcon, mContext));
}
}
@@ -547,10 +545,8 @@ public final class CompanionDeviceManager {
if (Flags.associationDeviceIcon()) {
final Icon deviceIcon = request.getDeviceIcon();
-
- if (deviceIcon != null && !isValidIcon(deviceIcon, mContext)) {
- throw new IllegalArgumentException("The size of the device icon must be "
- + "24dp x 24dp to ensure proper display");
+ if (deviceIcon != null) {
+ request.setDeviceIcon(scaleIcon(deviceIcon, mContext));
}
}
@@ -2024,33 +2020,26 @@ public final class CompanionDeviceManager {
}
}
- private boolean isValidIcon(Icon icon, Context context) {
+ private Icon scaleIcon(Icon icon, Context context) {
+ if (icon == null) return null;
if (icon.getType() == TYPE_URI_ADAPTIVE_BITMAP || icon.getType() == TYPE_URI) {
throw new IllegalArgumentException("The URI based Icon is not supported.");
}
- Drawable drawable = icon.loadDrawable(context);
- float density = context.getResources().getDisplayMetrics().density;
+ Bitmap bitmap;
+ Drawable drawable = icon.loadDrawable(context);
if (drawable instanceof BitmapDrawable) {
- Bitmap bitmap = ((BitmapDrawable) drawable).getBitmap();
-
- float widthDp = bitmap.getWidth() / density;
- float heightDp = bitmap.getHeight() / density;
-
- if (widthDp != 24 || heightDp != 24) {
- return false;
- }
- } else if (drawable instanceof VectorDrawable) {
- VectorDrawable vectorDrawable = (VectorDrawable) drawable;
- float widthDp = vectorDrawable.getIntrinsicWidth() / density;
- float heightDp = vectorDrawable.getIntrinsicHeight() / density;
-
- if (widthDp != 24 || heightDp != 24) {
- return false;
- }
+ bitmap = Bitmap.createScaledBitmap(
+ ((BitmapDrawable) drawable).getBitmap(), ICON_TARGET_SIZE, ICON_TARGET_SIZE,
+ false);
} else {
- throw new IllegalArgumentException("The format of the device icon is unsupported.");
+ bitmap = Bitmap.createBitmap(context.getResources().getDisplayMetrics(),
+ ICON_TARGET_SIZE, ICON_TARGET_SIZE, Bitmap.Config.ARGB_8888);
+ Canvas canvas = new Canvas(bitmap);
+ drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
+ drawable.draw(canvas);
}
- return true;
+
+ return Icon.createWithBitmap(bitmap);
}
}
diff --git a/core/java/android/companion/virtual/VirtualDeviceManager.java b/core/java/android/companion/virtual/VirtualDeviceManager.java
index 99794d7f49fe..252db824c69f 100644
--- a/core/java/android/companion/virtual/VirtualDeviceManager.java
+++ b/core/java/android/companion/virtual/VirtualDeviceManager.java
@@ -1116,11 +1116,8 @@ public final class VirtualDeviceManager {
* @throws SecurityException if the display is not owned by this device or is not
* {@link DisplayManager#VIRTUAL_DISPLAY_FLAG_TRUSTED trusted}
*/
- @FlaggedApi(Flags.FLAG_VDM_CUSTOM_IME)
public void setDisplayImePolicy(int displayId, @WindowManager.DisplayImePolicy int policy) {
- if (Flags.vdmCustomIme()) {
- mVirtualDeviceInternal.setDisplayImePolicy(displayId, policy);
- }
+ mVirtualDeviceInternal.setDisplayImePolicy(displayId, policy);
}
/**
diff --git a/core/java/android/companion/virtual/VirtualDeviceParams.java b/core/java/android/companion/virtual/VirtualDeviceParams.java
index 761e75bd9076..95dee9b72a88 100644
--- a/core/java/android/companion/virtual/VirtualDeviceParams.java
+++ b/core/java/android/companion/virtual/VirtualDeviceParams.java
@@ -440,7 +440,6 @@ public final class VirtualDeviceParams implements Parcelable {
*
* @see Builder#setInputMethodComponent
*/
- @FlaggedApi(Flags.FLAG_VDM_CUSTOM_IME)
@Nullable
public ComponentName getInputMethodComponent() {
return mInputMethodComponent;
@@ -945,7 +944,6 @@ public final class VirtualDeviceParams implements Parcelable {
* @attr ref android.R.styleable#InputMethod_isVirtualDeviceOnly
* @attr ref android.R.styleable#InputMethod_showInInputMethodPicker
*/
- @FlaggedApi(Flags.FLAG_VDM_CUSTOM_IME)
@NonNull
public Builder setInputMethodComponent(@Nullable ComponentName inputMethodComponent) {
mInputMethodComponent = inputMethodComponent;
diff --git a/core/java/android/content/pm/RegisteredServicesCache.java b/core/java/android/content/pm/RegisteredServicesCache.java
index 9d11710a2cad..82663849f316 100644
--- a/core/java/android/content/pm/RegisteredServicesCache.java
+++ b/core/java/android/content/pm/RegisteredServicesCache.java
@@ -85,6 +85,8 @@ public abstract class RegisteredServicesCache<V> {
private static final boolean DEBUG = false;
protected static final String REGISTERED_SERVICES_DIR = "registered_services";
+ static final long SERVICE_INFO_CACHES_TIMEOUT_MILLIS = 30000; // 30 seconds
+
public final Context mContext;
private final String mInterfaceName;
private final String mMetaDataName;
@@ -96,8 +98,18 @@ public abstract class RegisteredServicesCache<V> {
@GuardedBy("mServicesLock")
private final SparseArray<UserServices<V>> mUserServices = new SparseArray<UserServices<V>>(2);
- @GuardedBy("mServicesLock")
- private final ArrayMap<String, ServiceInfo<V>> mServiceInfoCaches = new ArrayMap<>();
+ @GuardedBy("mServiceInfoCaches")
+ private final ArrayMap<ComponentName, ServiceInfo<V>> mServiceInfoCaches = new ArrayMap<>();
+
+ private final Handler mBackgroundHandler;
+
+ private final Runnable mClearServiceInfoCachesRunnable = new Runnable() {
+ public void run() {
+ synchronized (mServiceInfoCaches) {
+ mServiceInfoCaches.clear();
+ }
+ }
+ };
private static class UserServices<V> {
@GuardedBy("mServicesLock")
@@ -172,9 +184,9 @@ public abstract class RegisteredServicesCache<V> {
if (isCore) {
intentFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
}
- Handler handler = BackgroundThread.getHandler();
+ mBackgroundHandler = BackgroundThread.getHandler();
mContext.registerReceiverAsUser(
- mPackageReceiver, UserHandle.ALL, intentFilter, null, handler);
+ mPackageReceiver, UserHandle.ALL, intentFilter, null, mBackgroundHandler);
// Register for events related to sdcard installation.
IntentFilter sdFilter = new IntentFilter();
@@ -183,7 +195,7 @@ public abstract class RegisteredServicesCache<V> {
if (isCore) {
sdFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
}
- mContext.registerReceiver(mExternalReceiver, sdFilter, null, handler);
+ mContext.registerReceiver(mExternalReceiver, sdFilter, null, mBackgroundHandler);
// Register for user-related events
IntentFilter userFilter = new IntentFilter();
@@ -191,7 +203,7 @@ public abstract class RegisteredServicesCache<V> {
if (isCore) {
userFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
}
- mContext.registerReceiver(mUserRemovedReceiver, userFilter, null, handler);
+ mContext.registerReceiver(mUserRemovedReceiver, userFilter, null, mBackgroundHandler);
}
private void handlePackageEvent(Intent intent, int userId) {
@@ -328,6 +340,10 @@ public abstract class RegisteredServicesCache<V> {
public final ComponentName componentName;
@UnsupportedAppUsage
public final int uid;
+ /**
+ * The last update time of the package that contains the service.
+ * It's from {@link PackageInfo#lastUpdateTime}.
+ */
public final long lastUpdateTime;
/** @hide */
@@ -342,7 +358,8 @@ public abstract class RegisteredServicesCache<V> {
@Override
public String toString() {
- return "ServiceInfo: " + type + ", " + componentName + ", uid " + uid;
+ return "ServiceInfo: " + type + ", " + componentName + ", uid " + uid
+ + ", lastUpdateTime " + lastUpdateTime;
}
}
@@ -496,19 +513,56 @@ public abstract class RegisteredServicesCache<V> {
final ArrayList<ServiceInfo<V>> serviceInfos = new ArrayList<>();
final List<ResolveInfo> resolveInfos = queryIntentServices(userId);
+ final PackageManager pm = mContext.getPackageManager();
for (ResolveInfo resolveInfo : resolveInfos) {
+ // Check if the service has been in the service cache.
+ long lastUpdateTime = -1;
+ final android.content.pm.ServiceInfo si = resolveInfo.serviceInfo;
+ final ComponentName componentName = si.getComponentName();
+ if (Flags.optimizeParsingInRegisteredServicesCache()) {
+ try {
+ PackageInfo packageInfo = pm.getPackageInfoAsUser(si.packageName,
+ PackageManager.MATCH_DIRECT_BOOT_AWARE
+ | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, userId);
+ lastUpdateTime = packageInfo.lastUpdateTime;
+ } catch (NameNotFoundException | SecurityException e) {
+ Slog.d(TAG, "Fail to get the PackageInfo in generateServicesMap: " + e);
+ continue;
+ }
+ ServiceInfo<V> serviceInfo = getServiceInfoFromServiceCache(componentName,
+ lastUpdateTime);
+ if (serviceInfo != null) {
+ serviceInfos.add(serviceInfo);
+ continue;
+ }
+ }
try {
- ServiceInfo<V> info = parseServiceInfo(resolveInfo, userId);
+ ServiceInfo<V> info = parseServiceInfo(resolveInfo, lastUpdateTime);
if (info == null) {
Log.w(TAG, "Unable to load service info " + resolveInfo.toString());
continue;
}
serviceInfos.add(info);
+ if (Flags.optimizeParsingInRegisteredServicesCache()) {
+ synchronized (mServiceInfoCaches) {
+ mServiceInfoCaches.put(componentName, info);
+ }
+ }
} catch (XmlPullParserException | IOException e) {
Log.w(TAG, "Unable to load service info " + resolveInfo.toString(), e);
}
}
+ if (Flags.optimizeParsingInRegisteredServicesCache()) {
+ synchronized (mServiceInfoCaches) {
+ if (!mServiceInfoCaches.isEmpty()) {
+ mBackgroundHandler.removeCallbacks(mClearServiceInfoCachesRunnable);
+ mBackgroundHandler.postDelayed(mClearServiceInfoCachesRunnable,
+ SERVICE_INFO_CACHES_TIMEOUT_MILLIS);
+ }
+ }
+ }
+
synchronized (mServicesLock) {
final UserServices<V> user = findOrCreateUserLocked(userId);
final boolean firstScan = user.services == null;
@@ -645,32 +699,18 @@ public abstract class RegisteredServicesCache<V> {
return false;
}
+ /**
+ * If the service has already existed in the caches, this method will not be called to parse
+ * the service.
+ */
@VisibleForTesting
- protected ServiceInfo<V> parseServiceInfo(ResolveInfo service, int userId)
+ protected ServiceInfo<V> parseServiceInfo(ResolveInfo service, long lastUpdateTime)
throws XmlPullParserException, IOException {
android.content.pm.ServiceInfo si = service.serviceInfo;
ComponentName componentName = new ComponentName(si.packageName, si.name);
PackageManager pm = mContext.getPackageManager();
- // Check if the service has been in the service cache.
- long lastUpdateTime = -1;
- if (Flags.optimizeParsingInRegisteredServicesCache()) {
- try {
- PackageInfo packageInfo = pm.getPackageInfoAsUser(si.packageName,
- PackageManager.MATCH_DIRECT_BOOT_AWARE
- | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, userId);
- lastUpdateTime = packageInfo.lastUpdateTime;
-
- ServiceInfo<V> serviceInfo = getServiceInfoFromServiceCache(si, lastUpdateTime);
- if (serviceInfo != null) {
- return serviceInfo;
- }
- } catch (NameNotFoundException | SecurityException e) {
- Slog.d(TAG, "Fail to get the PackageInfo in parseServiceInfo: " + e);
- }
- }
-
XmlResourceParser parser = null;
try {
parser = si.loadXmlMetaData(pm, mMetaDataName);
@@ -696,13 +736,7 @@ public abstract class RegisteredServicesCache<V> {
if (v == null) {
return null;
}
- ServiceInfo<V> serviceInfo = new ServiceInfo<V>(v, si, componentName, lastUpdateTime);
- if (Flags.optimizeParsingInRegisteredServicesCache()) {
- synchronized (mServicesLock) {
- mServiceInfoCaches.put(getServiceCacheKey(si), serviceInfo);
- }
- }
- return serviceInfo;
+ return new ServiceInfo<V>(v, si, componentName, lastUpdateTime);
} catch (NameNotFoundException e) {
throw new XmlPullParserException(
"Unable to load resources for pacakge " + si.packageName);
@@ -873,26 +907,13 @@ public abstract class RegisteredServicesCache<V> {
mContext.unregisterReceiver(mUserRemovedReceiver);
}
- private static String getServiceCacheKey(@NonNull android.content.pm.ServiceInfo serviceInfo) {
- StringBuilder sb = new StringBuilder(serviceInfo.packageName);
- sb.append('-');
- sb.append(serviceInfo.name);
- return sb.toString();
- }
-
- private ServiceInfo<V> getServiceInfoFromServiceCache(
- @NonNull android.content.pm.ServiceInfo serviceInfo, long lastUpdateTime) {
- String serviceCacheKey = getServiceCacheKey(serviceInfo);
- synchronized (mServicesLock) {
- ServiceInfo<V> serviceCache = mServiceInfoCaches.get(serviceCacheKey);
- if (serviceCache == null) {
- return null;
- }
- if (serviceCache.lastUpdateTime == lastUpdateTime) {
+ private ServiceInfo<V> getServiceInfoFromServiceCache(@NonNull ComponentName componentName,
+ long lastUpdateTime) {
+ synchronized (mServiceInfoCaches) {
+ ServiceInfo<V> serviceCache = mServiceInfoCaches.get(componentName);
+ if (serviceCache != null && serviceCache.lastUpdateTime == lastUpdateTime) {
return serviceCache;
}
- // The service is not latest, remove it from the cache.
- mServiceInfoCaches.remove(serviceCacheKey);
return null;
}
}
diff --git a/core/java/android/hardware/input/InputSettings.java b/core/java/android/hardware/input/InputSettings.java
index b380e259577c..cd48047bccb4 100644
--- a/core/java/android/hardware/input/InputSettings.java
+++ b/core/java/android/hardware/input/InputSettings.java
@@ -385,6 +385,42 @@ public class InputSettings {
}
/**
+ * Whether touchpad acceleration is enabled or not.
+ *
+ * @param context The application context.
+ *
+ * @hide
+ */
+ public static boolean isTouchpadAccelerationEnabled(@NonNull Context context) {
+ if (!isPointerAccelerationFeatureFlagEnabled()) {
+ return false;
+ }
+
+ return Settings.System.getIntForUser(context.getContentResolver(),
+ Settings.System.TOUCHPAD_ACCELERATION_ENABLED, 1, UserHandle.USER_CURRENT)
+ == 1;
+ }
+
+ /**
+ * Enables or disables touchpad acceleration.
+ *
+ * @param context The application context.
+ * @param enabled Will enable touchpad acceleration if true, disable it if
+ * false.
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.WRITE_SETTINGS)
+ public static void setTouchpadAccelerationEnabled(@NonNull Context context,
+ boolean enabled) {
+ if (!isPointerAccelerationFeatureFlagEnabled()) {
+ return;
+ }
+ Settings.System.putIntForUser(context.getContentResolver(),
+ Settings.System.TOUCHPAD_ACCELERATION_ENABLED, enabled ? 1 : 0,
+ UserHandle.USER_CURRENT);
+ }
+
+ /**
* Returns true if the feature flag for disabling system gestures on touchpads is enabled.
*
* @hide
@@ -835,7 +871,6 @@ public class InputSettings {
UserHandle.USER_CURRENT);
}
-
/**
* Whether Accessibility bounce keys feature is enabled.
*
diff --git a/core/java/android/hardware/location/ContextHubManager.java b/core/java/android/hardware/location/ContextHubManager.java
index 9181bd0cb2ed..953ee08800cf 100644
--- a/core/java/android/hardware/location/ContextHubManager.java
+++ b/core/java/android/hardware/location/ContextHubManager.java
@@ -771,6 +771,7 @@ public final class ContextHubManager {
*/
@FlaggedApi(Flags.FLAG_OFFLOAD_API)
private IContextHubEndpointDiscoveryCallback createDiscoveryCallback(
+ IContextHubService service,
Executor executor,
HubEndpointDiscoveryCallback callback,
@Nullable String serviceDescriptor) {
@@ -779,6 +780,7 @@ public final class ContextHubManager {
public void onEndpointsStarted(HubEndpointInfo[] hubEndpointInfoList) {
if (hubEndpointInfoList.length == 0) {
Log.w(TAG, "onEndpointsStarted: received empty discovery list");
+ invokeCallbackFinished(service);
return;
}
executor.execute(
@@ -791,6 +793,7 @@ public final class ContextHubManager {
} else {
callback.onEndpointsStarted(discoveryList);
}
+ invokeCallbackFinished(service);
});
}
@@ -798,6 +801,7 @@ public final class ContextHubManager {
public void onEndpointsStopped(HubEndpointInfo[] hubEndpointInfoList, int reason) {
if (hubEndpointInfoList.length == 0) {
Log.w(TAG, "onEndpointsStopped: received empty discovery list");
+ invokeCallbackFinished(service);
return;
}
executor.execute(
@@ -810,8 +814,17 @@ public final class ContextHubManager {
} else {
callback.onEndpointsStopped(discoveryList, reason);
}
+ invokeCallbackFinished(service);
});
}
+
+ private void invokeCallbackFinished(IContextHubService service) {
+ try {
+ service.onDiscoveryCallbackFinished();
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
};
}
@@ -873,7 +886,7 @@ public final class ContextHubManager {
Objects.requireNonNull(executor, "executor cannot be null");
Objects.requireNonNull(callback, "callback cannot be null");
IContextHubEndpointDiscoveryCallback iCallback =
- createDiscoveryCallback(executor, callback, null);
+ createDiscoveryCallback(mService, executor, callback, null);
try {
mService.registerEndpointDiscoveryCallbackId(endpointId, iCallback);
} catch (RemoteException e) {
@@ -919,7 +932,7 @@ public final class ContextHubManager {
}
IContextHubEndpointDiscoveryCallback iCallback =
- createDiscoveryCallback(executor, callback, serviceDescriptor);
+ createDiscoveryCallback(mService, executor, callback, serviceDescriptor);
try {
mService.registerEndpointDiscoveryCallbackDescriptor(serviceDescriptor, iCallback);
} catch (RemoteException e) {
diff --git a/core/java/android/hardware/location/IContextHubService.aidl b/core/java/android/hardware/location/IContextHubService.aidl
index d5b3fa251e82..bb5491d98cf9 100644
--- a/core/java/android/hardware/location/IContextHubService.aidl
+++ b/core/java/android/hardware/location/IContextHubService.aidl
@@ -150,4 +150,8 @@ interface IContextHubService {
// Unregister an endpoint with the context hub
@EnforcePermission("ACCESS_CONTEXT_HUB")
void unregisterEndpointDiscoveryCallback(in IContextHubEndpointDiscoveryCallback callback);
+
+ // Called when a discovery callback is finished executing
+ @EnforcePermission("ACCESS_CONTEXT_HUB")
+ void onDiscoveryCallbackFinished();
}
diff --git a/core/java/android/os/AppZygote.java b/core/java/android/os/AppZygote.java
index 0541a96e990e..69b6597d717a 100644
--- a/core/java/android/os/AppZygote.java
+++ b/core/java/android/os/AppZygote.java
@@ -16,15 +16,22 @@
package android.os;
+import static android.os.Process.ZYGOTE_POLICY_FLAG_EMPTY;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.content.pm.ApplicationInfo;
import android.content.pm.ProcessInfo;
import android.util.Log;
+import android.util.Pair;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.os.Zygote;
import dalvik.system.VMRuntime;
+import java.util.Map;
+
/**
* AppZygote is responsible for interfacing with an application-specific zygote.
*
@@ -94,12 +101,90 @@ public class AppZygote {
return mAppInfo;
}
+ /**
+ * Start a new process.
+ *
+ * <p>Wrap ZygoteProcess.start with retry logic.
+ *
+ * @param processClass The class to use as the process's main entry
+ * point.
+ * @param niceName A more readable name to use for the process.
+ * @param uid The user-id under which the process will run.
+ * @param gids Additional group-ids associated with the process.
+ * @param runtimeFlags Additional flags.
+ * @param targetSdkVersion The target SDK version for the app.
+ * @param seInfo null-ok SELinux information for the new process.
+ * @param abi non-null the ABI this app should be started with.
+ * @param instructionSet null-ok the instruction set to use.
+ * @param appDataDir null-ok the data directory of the app.
+ * @param packageName null-ok the name of the package this process belongs to.
+ * @param isTopApp Whether the process starts for high priority application.
+ * @param disabledCompatChanges null-ok list of disabled compat changes for the process being
+ * started.
+ * @param pkgDataInfoMap Map from related package names to private data directory
+ * volume UUID and inode number.
+ * @param allowlistedDataInfoList Map from allowlisted package names to private data directory
+ * volume UUID and inode number.
+ * @param zygoteArgs Additional arguments to supply to the Zygote process.
+ * @return An object that describes the result of the attempt to start the process.
+ * @throws RuntimeException on fatal start failure
+ */
+ public final Process.ProcessStartResult startProcess(@NonNull final String processClass,
+ final String niceName,
+ int uid, @Nullable int[] gids,
+ int runtimeFlags, int mountExternal,
+ int targetSdkVersion,
+ @Nullable String seInfo,
+ @NonNull String abi,
+ @Nullable String instructionSet,
+ @Nullable String appDataDir,
+ @Nullable String packageName,
+ boolean isTopApp,
+ @Nullable long[] disabledCompatChanges,
+ @Nullable Map<String, Pair<String, Long>>
+ pkgDataInfoMap,
+ @Nullable Map<String, Pair<String, Long>>
+ allowlistedDataInfoList,
+ @Nullable String[] zygoteArgs) {
+ try {
+ return getProcess().start(processClass,
+ niceName, uid, uid, gids, runtimeFlags, mountExternal,
+ targetSdkVersion, seInfo, abi, instructionSet,
+ appDataDir, null, packageName,
+ /*zygotePolicyFlags=*/ ZYGOTE_POLICY_FLAG_EMPTY, isTopApp,
+ disabledCompatChanges, pkgDataInfoMap, allowlistedDataInfoList,
+ false, false, false,
+ zygoteArgs);
+ } catch (RuntimeException e) {
+ if (!Flags.appZygoteRetryStart()) {
+ throw e;
+ }
+ final boolean zygote_dead = getProcess().isDead();
+ if (!zygote_dead) {
+ throw e; // Zygote process is alive. Do nothing.
+ }
+ }
+ // Retry here if the previous start fails.
+ Log.w(LOG_TAG, "retry starting process " + niceName);
+ stopZygote();
+ return getProcess().start(processClass,
+ niceName, uid, uid, gids, runtimeFlags, mountExternal,
+ targetSdkVersion, seInfo, abi, instructionSet,
+ appDataDir, null, packageName,
+ /*zygotePolicyFlags=*/ ZYGOTE_POLICY_FLAG_EMPTY, isTopApp,
+ disabledCompatChanges, pkgDataInfoMap, allowlistedDataInfoList,
+ false, false, false,
+ zygoteArgs);
+ }
+
@GuardedBy("mLock")
private void stopZygoteLocked() {
if (mZygote != null) {
mZygote.close();
// use killProcessGroup() here, so we kill all untracked children as well.
- Process.killProcessGroup(mZygoteUid, mZygote.getPid());
+ if (!mZygote.isDead()) {
+ Process.killProcessGroup(mZygoteUid, mZygote.getPid());
+ }
mZygote = null;
}
}
diff --git a/core/java/android/os/ChildZygoteProcess.java b/core/java/android/os/ChildZygoteProcess.java
index 337a3e279a1a..d8f825a2ee60 100644
--- a/core/java/android/os/ChildZygoteProcess.java
+++ b/core/java/android/os/ChildZygoteProcess.java
@@ -17,6 +17,10 @@
package android.os;
import android.net.LocalSocketAddress;
+import android.system.ErrnoException;
+import android.system.Os;
+
+import java.util.concurrent.atomic.AtomicBoolean;
/**
* Represents a connection to a child-zygote process. A child-zygote is spawend from another
@@ -30,9 +34,23 @@ public class ChildZygoteProcess extends ZygoteProcess {
*/
private final int mPid;
- ChildZygoteProcess(LocalSocketAddress socketAddress, int pid) {
+ /**
+ * The UID of the child zygote process.
+ */
+ private final int mUid;
+
+
+ /**
+ * If this zygote process was dead;
+ */
+ private AtomicBoolean mDead;
+
+
+ ChildZygoteProcess(LocalSocketAddress socketAddress, int pid, int uid) {
super(socketAddress, null);
mPid = pid;
+ mUid = uid;
+ mDead = new AtomicBoolean(false);
}
/**
@@ -41,4 +59,22 @@ public class ChildZygoteProcess extends ZygoteProcess {
public int getPid() {
return mPid;
}
+
+ /**
+ * Check if child-zygote process is dead
+ */
+ public boolean isDead() {
+ if (mDead.get()) {
+ return true;
+ }
+ try {
+ if (Os.stat("/proc/" + mPid).st_uid == mUid) {
+ return false;
+ }
+ } catch (ErrnoException e) {
+ // Do nothing, it's dead.
+ }
+ mDead.set(true);
+ return true;
+ }
}
diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java
index 4aa74621bd62..c6a63a7ef238 100644
--- a/core/java/android/os/Parcel.java
+++ b/core/java/android/os/Parcel.java
@@ -50,6 +50,7 @@ import com.android.internal.util.ArrayUtils;
import dalvik.annotation.optimization.CriticalNative;
import dalvik.annotation.optimization.FastNative;
+import dalvik.annotation.optimization.NeverInline;
import libcore.util.SneakyThrow;
@@ -628,6 +629,19 @@ public final class Parcel {
}
}
+ @NeverInline
+ private void errorUsedWhileRecycling() {
+ String error = "Parcel used while recycled. "
+ + Log.getStackTraceString(new Throwable())
+ + " Original recycle call (if DEBUG_RECYCLE): ", mStack;
+ Log.wtf(TAG, error);
+ // TODO(b/381155347): harder error
+ }
+
+ private void assertNotRecycled() {
+ if (mRecycled) errorUsedWhileRecycling();
+ }
+
/**
* Set a {@link ReadWriteHelper}, which can be used to avoid having duplicate strings, for
* example.
@@ -1180,6 +1194,7 @@ public final class Parcel {
* growing dataCapacity() if needed.
*/
public final void writeInt(int val) {
+ assertNotRecycled();
int err = nativeWriteInt(mNativePtr, val);
if (err != OK) {
nativeSignalExceptionForError(err);
@@ -3282,6 +3297,7 @@ public final class Parcel {
* Read an integer value from the parcel at the current dataPosition().
*/
public final int readInt() {
+ assertNotRecycled();
return nativeReadInt(mNativePtr);
}
diff --git a/core/java/android/os/PerfettoTrace.java b/core/java/android/os/PerfettoTrace.java
index 164561acac32..e3f251e34b45 100644
--- a/core/java/android/os/PerfettoTrace.java
+++ b/core/java/android/os/PerfettoTrace.java
@@ -22,7 +22,6 @@ import dalvik.annotation.optimization.FastNative;
import libcore.util.NativeAllocationRegistry;
import java.util.concurrent.atomic.AtomicInteger;
-import java.util.function.Consumer;
/**
* Writes trace events to the perfetto trace buffer. These trace events can be
@@ -72,7 +71,7 @@ public final class PerfettoTrace {
* @param name The category name.
*/
public Category(String name) {
- this(name, null, null);
+ this(name, "", "");
}
/**
@@ -82,7 +81,7 @@ public final class PerfettoTrace {
* @param tag An atrace tag name that this category maps to.
*/
public Category(String name, String tag) {
- this(name, tag, null);
+ this(name, tag, "");
}
/**
@@ -155,9 +154,6 @@ public final class PerfettoTrace {
}
}
- @FastNative
- private static native void native_event(int type, long tag, String name, long ptr);
-
@CriticalNative
private static native long native_get_process_track_uuid();
@@ -170,176 +166,98 @@ public final class PerfettoTrace {
/**
* Writes a trace message to indicate a given section of code was invoked.
*
- * @param category The perfetto category pointer.
+ * @param category The perfetto category.
* @param eventName The event name to appear in the trace.
- * @param extra The extra arguments.
*/
- public static void instant(Category category, String eventName, PerfettoTrackEventExtra extra) {
+ public static PerfettoTrackEventExtra.Builder instant(Category category, String eventName) {
if (!category.isEnabled()) {
- return;
+ return PerfettoTrackEventExtra.noOpBuilder();
}
- native_event(PERFETTO_TE_TYPE_INSTANT, category.getPtr(), eventName, extra.getPtr());
- extra.reset();
- }
-
- /**
- * Writes a trace message to indicate a given section of code was invoked.
- *
- * @param category The perfetto category.
- * @param eventName The event name to appear in the trace.
- * @param extraConfig Consumer for the extra arguments.
- */
- public static void instant(Category category, String eventName,
- Consumer<PerfettoTrackEventExtra.Builder> extraConfig) {
- PerfettoTrackEventExtra.Builder extra = PerfettoTrackEventExtra.builder();
- extraConfig.accept(extra);
- instant(category, eventName, extra.build());
- }
-
- /**
- * Writes a trace message to indicate a given section of code was invoked.
- *
- * @param category The perfetto category.
- * @param eventName The event name to appear in the trace.
- */
- public static void instant(Category category, String eventName) {
- instant(category, eventName, PerfettoTrackEventExtra.builder().build());
+ return PerfettoTrackEventExtra.builder().init(PERFETTO_TE_TYPE_INSTANT, category)
+ .setEventName(eventName);
}
/**
* Writes a trace message to indicate the start of a given section of code.
*
- * @param category The perfetto category pointer.
+ * @param category The perfetto category.
* @param eventName The event name to appear in the trace.
- * @param extra The extra arguments.
*/
- public static void begin(Category category, String eventName, PerfettoTrackEventExtra extra) {
+ public static PerfettoTrackEventExtra.Builder begin(Category category, String eventName) {
if (!category.isEnabled()) {
- return;
+ return PerfettoTrackEventExtra.noOpBuilder();
}
- native_event(PERFETTO_TE_TYPE_SLICE_BEGIN, category.getPtr(), eventName, extra.getPtr());
- extra.reset();
- }
-
- /**
- * Writes a trace message to indicate the start of a given section of code.
- *
- * @param category The perfetto category pointer.
- * @param eventName The event name to appear in the trace.
- * @param extraConfig Consumer for the extra arguments.
- */
- public static void begin(Category category, String eventName,
- Consumer<PerfettoTrackEventExtra.Builder> extraConfig) {
- PerfettoTrackEventExtra.Builder extra = PerfettoTrackEventExtra.builder();
- extraConfig.accept(extra);
- begin(category, eventName, extra.build());
- }
-
- /**
- * Writes a trace message to indicate the start of a given section of code.
- *
- * @param category The perfetto category pointer.
- * @param eventName The event name to appear in the trace.
- */
- public static void begin(Category category, String eventName) {
- begin(category, eventName, PerfettoTrackEventExtra.builder().build());
+ return PerfettoTrackEventExtra.builder().init(PERFETTO_TE_TYPE_SLICE_BEGIN, category)
+ .setEventName(eventName);
}
/**
* Writes a trace message to indicate the end of a given section of code.
*
- * @param category The perfetto category pointer.
- * @param extra The extra arguments.
+ * @param category The perfetto category.
*/
- public static void end(Category category, PerfettoTrackEventExtra extra) {
+ public static PerfettoTrackEventExtra.Builder end(Category category) {
if (!category.isEnabled()) {
- return;
+ return PerfettoTrackEventExtra.noOpBuilder();
}
- native_event(PERFETTO_TE_TYPE_SLICE_END, category.getPtr(), "", extra.getPtr());
- extra.reset();
- }
-
- /**
- * Writes a trace message to indicate the end of a given section of code.
- *
- * @param category The perfetto category pointer.
- * @param extraConfig Consumer for the extra arguments.
- */
- public static void end(Category category,
- Consumer<PerfettoTrackEventExtra.Builder> extraConfig) {
- PerfettoTrackEventExtra.Builder extra = PerfettoTrackEventExtra.builder();
- extraConfig.accept(extra);
- end(category, extra.build());
- }
-
- /**
- * Writes a trace message to indicate the end of a given section of code.
- *
- * @param category The perfetto category pointer.
- */
- public static void end(Category category) {
- end(category, PerfettoTrackEventExtra.builder().build());
+ return PerfettoTrackEventExtra.builder().init(PERFETTO_TE_TYPE_SLICE_END, category);
}
/**
* Writes a trace message to indicate the value of a given section of code.
*
- * @param category The perfetto category pointer.
- * @param extra The extra arguments.
+ * @param category The perfetto category.
+ * @param value The value of the counter.
*/
- public static void counter(Category category, PerfettoTrackEventExtra extra) {
+ public static PerfettoTrackEventExtra.Builder counter(Category category, long value) {
if (!category.isEnabled()) {
- return;
+ return PerfettoTrackEventExtra.noOpBuilder();
}
- native_event(PERFETTO_TE_TYPE_COUNTER, category.getPtr(), "", extra.getPtr());
- extra.reset();
+ return PerfettoTrackEventExtra.builder().init(PERFETTO_TE_TYPE_COUNTER, category)
+ .setCounter(value);
}
/**
* Writes a trace message to indicate the value of a given section of code.
*
- * @param category The perfetto category pointer.
- * @param extraConfig Consumer for the extra arguments.
+ * @param category The perfetto category.
+ * @param value The value of the counter.
+ * @param trackName The trackName for the event.
*/
- public static void counter(Category category,
- Consumer<PerfettoTrackEventExtra.Builder> extraConfig) {
- PerfettoTrackEventExtra.Builder extra = PerfettoTrackEventExtra.builder();
- extraConfig.accept(extra);
- counter(category, extra.build());
+ public static PerfettoTrackEventExtra.Builder counter(
+ Category category, long value, String trackName) {
+ return counter(category, value).usingProcessCounterTrack(trackName);
}
/**
* Writes a trace message to indicate the value of a given section of code.
*
- * @param category The perfetto category pointer.
- * @param trackName The trackName for the event.
+ * @param category The perfetto category.
* @param value The value of the counter.
*/
- public static void counter(Category category, String trackName, long value) {
- PerfettoTrackEventExtra extra = PerfettoTrackEventExtra.builder()
- .usingCounterTrack(trackName, PerfettoTrace.getProcessTrackUuid())
- .setCounter(value)
- .build();
- counter(category, extra);
+ public static PerfettoTrackEventExtra.Builder counter(Category category, double value) {
+ if (!category.isEnabled()) {
+ return PerfettoTrackEventExtra.noOpBuilder();
+ }
+
+ return PerfettoTrackEventExtra.builder().init(PERFETTO_TE_TYPE_COUNTER, category)
+ .setCounter(value);
}
/**
* Writes a trace message to indicate the value of a given section of code.
*
- * @param category The perfetto category pointer.
- * @param trackName The trackName for the event.
+ * @param category The perfetto category.
* @param value The value of the counter.
+ * @param trackName The trackName for the event.
*/
- public static void counter(Category category, String trackName, double value) {
- PerfettoTrackEventExtra extra = PerfettoTrackEventExtra.builder()
- .usingCounterTrack(trackName, PerfettoTrace.getProcessTrackUuid())
- .setCounter(value)
- .build();
- counter(category, extra);
+ public static PerfettoTrackEventExtra.Builder counter(
+ Category category, double value, String trackName) {
+ return counter(category, value).usingProcessCounterTrack(trackName);
}
/**
@@ -360,7 +278,7 @@ public final class PerfettoTrace {
* Returns the process track uuid that can be used as a parent track uuid.
*/
public static long getProcessTrackUuid() {
- if (IS_FLAG_ENABLED) {
+ if (!IS_FLAG_ENABLED) {
return 0;
}
return native_get_process_track_uuid();
@@ -370,7 +288,7 @@ public final class PerfettoTrace {
* Given a thread tid, returns the thread track uuid that can be used as a parent track uuid.
*/
public static long getThreadTrackUuid(long tid) {
- if (IS_FLAG_ENABLED) {
+ if (!IS_FLAG_ENABLED) {
return 0;
}
return native_get_thread_track_uuid(tid);
@@ -380,7 +298,7 @@ public final class PerfettoTrace {
* Activates a trigger by name {@code triggerName} with expiry in {@code ttlMs}.
*/
public static void activateTrigger(String triggerName, int ttlMs) {
- if (IS_FLAG_ENABLED) {
+ if (!IS_FLAG_ENABLED) {
return;
}
native_activate_trigger(triggerName, ttlMs);
diff --git a/core/java/android/os/PerfettoTrackEventExtra.java b/core/java/android/os/PerfettoTrackEventExtra.java
index a219b3b5678b..e034fb3726e3 100644
--- a/core/java/android/os/PerfettoTrackEventExtra.java
+++ b/core/java/android/os/PerfettoTrackEventExtra.java
@@ -31,6 +31,7 @@ import java.util.function.Supplier;
*/
public final class PerfettoTrackEventExtra {
private static final int DEFAULT_EXTRA_CACHE_SIZE = 5;
+ private static final Builder NO_OP_BUILDER = new NoOpBuilder();
private static final ThreadLocal<PerfettoTrackEventExtra> sTrackEventExtra =
new ThreadLocal<PerfettoTrackEventExtra>() {
@Override
@@ -40,7 +41,6 @@ public final class PerfettoTrackEventExtra {
};
private static final AtomicLong sNamedTrackId = new AtomicLong();
- private boolean mIsInUse;
private CounterInt64 mCounterInt64;
private CounterDouble mCounterDouble;
private Proto mProto;
@@ -123,15 +123,299 @@ public final class PerfettoTrackEventExtra {
}
}
+ public interface Builder {
+ /**
+ * Emits the track event.
+ */
+ void emit();
+
+ /**
+ * Initialize the builder for a new trace event.
+ */
+ Builder init(int traceType, PerfettoTrace.Category category);
+
+ /**
+ * Sets the event name for the track event.
+ *
+ * @param eventName can contain a format string specifier, in which case, the
+ * {@code args} are the format arguments. If no {@code args} are provided,
+ * the {@code eventName} should be the string itself.
+ * @param args format arguments if {@code eventName} is specified.
+ */
+ Builder setEventName(String eventName, Object... args);
+
+ /**
+ * Adds a debug arg with key {@code name} and value {@code val}.
+ */
+ Builder addArg(String name, long val);
+
+ /**
+ * Adds a debug arg with key {@code name} and value {@code val}.
+ */
+ Builder addArg(String name, boolean val);
+
+ /**
+ * Adds a debug arg with key {@code name} and value {@code val}.
+ */
+ Builder addArg(String name, double val);
+
+ /**
+ * Adds a debug arg with key {@code name} and value {@code val}.
+ *
+ * @param val can contain a format string specifier, in which case, the
+ * {@code args} are the format arguments. If no {@code args} are provided,
+ * the {@code val} should be the string itself.
+ * @param args format arguments if {@code val} is specified.
+ */
+ Builder addArg(String name, String val, Object... args);
+
+ /**
+ * Adds a flow with {@code id}.
+ */
+ Builder addFlow(int id);
+
+ /**
+ * Adds a terminating flow with {@code id}.
+ */
+ Builder addTerminatingFlow(int id);
+
+ /**
+ * Adds the events to a named track instead of the thread track where the
+ * event occurred.
+ *
+ * @param name can contain a format string specifier, in which case, the
+ * {@code args} are the format arguments. If no {@code args} are provided,
+ * the {@code name} should be the string itself.
+ * @param args format arguments if {@code name} is specified.
+ */
+ Builder usingNamedTrack(long parentUuid, String name, Object... args);
+
+ /**
+ * Adds the events to a process scoped named track instead of the thread track where the
+ * event occurred.
+ *
+ * @param name can contain a format string specifier, in which case, the
+ * {@code args} are the format arguments. If no {@code args} are provided,
+ * the {@code name} should be the string itself.
+ * @param args format arguments if {@code name} is specified.
+ */
+ Builder usingProcessNamedTrack(String name, Object... args);
+
+ /**
+ * Adds the events to a thread scoped named track instead of the thread track where the
+ * event occurred.
+ *
+ * @param name can contain a format string specifier, in which case, the
+ * {@code args} are the format arguments. If no {@code args} are provided,
+ * the {@code name} should be the string itself.
+ * @param args format arguments if {@code name} is specified.
+ */
+ Builder usingThreadNamedTrack(long tid, String name, Object... args);
+
+ /**
+ * Adds the events to a counter track instead. This is required for
+ * setting counter values.
+ *
+ * @param name can contain a format string specifier, in which case, the
+ * {@code args} are the format arguments. If no {@code args} are provided,
+ * the {@code name} should be the string itself.
+ * @param args format arguments if {@code name} is specified.
+ */
+ Builder usingCounterTrack(long parentUuid, String name, Object... args);
+
+ /**
+ * Adds the events to a process scoped counter track instead. This is required for
+ * setting counter values.
+ *
+ * @param name can contain a format string specifier, in which case, the
+ * {@code args} are the format arguments. If no {@code args} are provided,
+ * the {@code name} should be the string itself.
+ * @param args format arguments if {@code eventName} is specified.
+ */
+ Builder usingProcessCounterTrack(String name, Object... args);
+
+ /**
+ * Adds the events to a thread scoped counter track instead. This is required for
+ * setting counter values.
+ *
+ * @param name can contain a format string specifier, in which case, the
+ * {@code args} are the format arguments. If no {@code args} are provided,
+ * the {@code name} should be the string itself.
+ * @param args format arguments if {@code name} is specified.
+ */
+ Builder usingThreadCounterTrack(long tid, String name, Object... args);
+
+ /**
+ * Sets a long counter value on the event.
+ *
+ */
+ Builder setCounter(long val);
+
+ /**
+ * Sets a double counter value on the event.
+ *
+ */
+ Builder setCounter(double val);
+
+ /**
+ * Adds a proto field with field id {@code id} and value {@code val}.
+ */
+ Builder addField(long id, long val);
+
+ /**
+ * Adds a proto field with field id {@code id} and value {@code val}.
+ */
+ Builder addField(long id, double val);
+
+ /**
+ * Adds a proto field with field id {@code id} and value {@code val}.
+ *
+ * @param val can contain a format string specifier, in which case, the
+ * {@code args} are the format arguments. If no {@code args} are provided,
+ * the {@code val} should be the string itself.
+ * @param args format arguments if {@code val} is specified.
+ */
+ Builder addField(long id, String val, Object... args);
+
+ /**
+ * Begins a proto field with field
+ * Fields can be added from this point and there must be a corresponding
+ * {@link endProto}.
+ *
+ * The proto field is a singleton and all proto fields get added inside the
+ * one {@link beginProto} and {@link endProto} within the {@link Builder}.
+ */
+ Builder beginProto();
+
+ /**
+ * Ends a proto field.
+ */
+ Builder endProto();
+
+ /**
+ * Begins a nested proto field with field id {@code id}.
+ * Fields can be added from this point and there must be a corresponding
+ * {@link endNested}.
+ */
+ Builder beginNested(long id);
+
+ /**
+ * Ends a nested proto field.
+ */
+ Builder endNested();
+ }
+
+ public static final class NoOpBuilder implements Builder {
+ @Override
+ public void emit() {}
+ @Override
+ public Builder init(int traceType, PerfettoTrace.Category category) {
+ return this;
+ }
+ @Override
+ public Builder setEventName(String eventName, Object... args) {
+ return this;
+ }
+ @Override
+ public Builder addArg(String name, long val) {
+ return this;
+ }
+ @Override
+ public Builder addArg(String name, boolean val) {
+ return this;
+ }
+ @Override
+ public Builder addArg(String name, double val) {
+ return this;
+ }
+ @Override
+ public Builder addArg(String name, String val, Object... args) {
+ return this;
+ }
+ @Override
+ public Builder addFlow(int id) {
+ return this;
+ }
+ @Override
+ public Builder addTerminatingFlow(int id) {
+ return this;
+ }
+ @Override
+ public Builder usingNamedTrack(long parentUuid, String name, Object... args) {
+ return this;
+ }
+ @Override
+ public Builder usingProcessNamedTrack(String name, Object... args) {
+ return this;
+ }
+ @Override
+ public Builder usingThreadNamedTrack(long tid, String name, Object... args) {
+ return this;
+ }
+ @Override
+ public Builder usingCounterTrack(long parentUuid, String name, Object... args) {
+ return this;
+ }
+ @Override
+ public Builder usingProcessCounterTrack(String name, Object... args) {
+ return this;
+ }
+ @Override
+ public Builder usingThreadCounterTrack(long tid, String name, Object... args) {
+ return this;
+ }
+ @Override
+ public Builder setCounter(long val) {
+ return this;
+ }
+ @Override
+ public Builder setCounter(double val) {
+ return this;
+ }
+ @Override
+ public Builder addField(long id, long val) {
+ return this;
+ }
+ @Override
+ public Builder addField(long id, double val) {
+ return this;
+ }
+ @Override
+ public Builder addField(long id, String val, Object... args) {
+ return this;
+ }
+ @Override
+ public Builder beginProto() {
+ return this;
+ }
+ @Override
+ public Builder endProto() {
+ return this;
+ }
+ @Override
+ public Builder beginNested(long id) {
+ return this;
+ }
+ @Override
+ public Builder endNested() {
+ return this;
+ }
+ }
+
/**
* Builder for Perfetto track event extras.
*/
- public static final class Builder {
+ public static final class BuilderImpl implements Builder {
// For performance reasons, we hold a reference to mExtra as a holder for
// perfetto pointers being added. This way, we avoid an additional list to hold
// the pointers in Java and we can pass them down directly to native code.
private final PerfettoTrackEventExtra mExtra;
+
+ private int mTraceType;
+ private PerfettoTrace.Category mCategory;
+ private String mEventName;
private boolean mIsBuilt;
+
private Builder mParent;
private FieldContainer mCurrentContainer;
@@ -151,16 +435,10 @@ public final class PerfettoTrackEventExtra {
private final Pool<FieldString> mFieldStringCache;
private final Pool<FieldNested> mFieldNestedCache;
private final Pool<Flow> mFlowCache;
- private final Pool<Builder> mBuilderCache;
+ private final Pool<BuilderImpl> mBuilderCache;
- private Builder() {
- this(sTrackEventExtra.get(), null, null);
- }
-
- private Builder(PerfettoTrackEventExtra extra, Builder parent, FieldContainer container) {
- mExtra = extra;
- mParent = parent;
- mCurrentContainer = container;
+ private BuilderImpl() {
+ mExtra = sTrackEventExtra.get();
mNamedTrackCache = mExtra.mNamedTrackCache;
mCounterTrackCache = mExtra.mCounterTrackCache;
@@ -180,25 +458,39 @@ public final class PerfettoTrackEventExtra {
mProto = mExtra.getProto();
}
- /**
- * Builds the track event extra.
- */
- public PerfettoTrackEventExtra build() {
+ @Override
+ public void emit() {
checkParent();
mIsBuilt = true;
+ native_emit(mTraceType, mCategory.getPtr(), mEventName, mExtra.getPtr());
+ // Reset after emitting to free any the extras used to trace the event.
+ mExtra.reset();
+ }
+
+ @Override
+ public Builder init(int traceType, PerfettoTrace.Category category) {
+ mTraceType = traceType;
+ mCategory = category;
+ mEventName = "";
mFieldInt64Cache.reset();
mFieldDoubleCache.reset();
mFieldStringCache.reset();
mFieldNestedCache.reset();
mBuilderCache.reset();
- return mExtra;
+ mExtra.reset();
+ // Reset after on init in case the thread created builders without calling emit
+ return initInternal(this, null);
}
- /**
- * Adds a debug arg with key {@code name} and value {@code val}.
- */
+ @Override
+ public Builder setEventName(String eventName, Object... args) {
+ mEventName = toString(eventName, args);
+ return this;
+ }
+
+ @Override
public Builder addArg(String name, long val) {
checkParent();
ArgInt64 arg = mArgInt64Cache.get(name.hashCode());
@@ -211,9 +503,7 @@ public final class PerfettoTrackEventExtra {
return this;
}
- /**
- * Adds a debug arg with key {@code name} and value {@code val}.
- */
+ @Override
public Builder addArg(String name, boolean val) {
checkParent();
ArgBool arg = mArgBoolCache.get(name.hashCode());
@@ -226,9 +516,7 @@ public final class PerfettoTrackEventExtra {
return this;
}
- /**
- * Adds a debug arg with key {@code name} and value {@code val}.
- */
+ @Override
public Builder addArg(String name, double val) {
checkParent();
ArgDouble arg = mArgDoubleCache.get(name.hashCode());
@@ -241,24 +529,20 @@ public final class PerfettoTrackEventExtra {
return this;
}
- /**
- * Adds a debug arg with key {@code name} and value {@code val}.
- */
- public Builder addArg(String name, String val) {
+ @Override
+ public Builder addArg(String name, String val, Object... args) {
checkParent();
ArgString arg = mArgStringCache.get(name.hashCode());
if (arg == null || !arg.getName().equals(name)) {
arg = new ArgString(name);
mArgStringCache.put(name.hashCode(), arg);
}
- arg.setValue(val);
+ arg.setValue(toString(val, args));
mExtra.addPerfettoPointer(arg);
return this;
}
- /**
- * Adds a flow with {@code id}.
- */
+ @Override
public Builder addFlow(int id) {
checkParent();
Flow flow = mFlowCache.get(Flow::new);
@@ -267,9 +551,7 @@ public final class PerfettoTrackEventExtra {
return this;
}
- /**
- * Adds a terminating flow with {@code id}.
- */
+ @Override
public Builder addTerminatingFlow(int id) {
checkParent();
Flow flow = mFlowCache.get(Flow::new);
@@ -278,12 +560,11 @@ public final class PerfettoTrackEventExtra {
return this;
}
- /**
- * Adds the events to a named track instead of the thread track where the
- * event occurred.
- */
- public Builder usingNamedTrack(String name, long parentUuid) {
+ @Override
+ public Builder usingNamedTrack(long parentUuid, String name, Object... args) {
checkParent();
+ name = toString(name, args);
+
NamedTrack track = mNamedTrackCache.get(name.hashCode());
if (track == null || !track.getName().equals(name)) {
track = new NamedTrack(name, parentUuid);
@@ -293,13 +574,21 @@ public final class PerfettoTrackEventExtra {
return this;
}
- /**
- * Adds the events to a counter track instead. This is required for
- * setting counter values.
- *
- */
- public Builder usingCounterTrack(String name, long parentUuid) {
+ @Override
+ public Builder usingProcessNamedTrack(String name, Object... args) {
+ return usingNamedTrack(PerfettoTrace.getProcessTrackUuid(), name, args);
+ }
+
+ @Override
+ public Builder usingThreadNamedTrack(long tid, String name, Object... args) {
+ return usingNamedTrack(PerfettoTrace.getThreadTrackUuid(tid), name, args);
+ }
+
+ @Override
+ public Builder usingCounterTrack(long parentUuid, String name, Object... args) {
checkParent();
+ name = toString(name, args);
+
CounterTrack track = mCounterTrackCache.get(name.hashCode());
if (track == null || !track.getName().equals(name)) {
track = new CounterTrack(name, parentUuid);
@@ -309,10 +598,17 @@ public final class PerfettoTrackEventExtra {
return this;
}
- /**
- * Sets a long counter value on the event.
- *
- */
+ @Override
+ public Builder usingProcessCounterTrack(String name, Object... args) {
+ return usingCounterTrack(PerfettoTrace.getProcessTrackUuid(), name, args);
+ }
+
+ @Override
+ public Builder usingThreadCounterTrack(long tid, String name, Object... args) {
+ return usingCounterTrack(PerfettoTrace.getThreadTrackUuid(tid), name, args);
+ }
+
+ @Override
public Builder setCounter(long val) {
checkParent();
mCounterInt64.setValue(val);
@@ -320,10 +616,7 @@ public final class PerfettoTrackEventExtra {
return this;
}
- /**
- * Sets a double counter value on the event.
- *
- */
+ @Override
public Builder setCounter(double val) {
checkParent();
mCounterDouble.setValue(val);
@@ -331,9 +624,7 @@ public final class PerfettoTrackEventExtra {
return this;
}
- /**
- * Adds a proto field with field id {@code id} and value {@code val}.
- */
+ @Override
public Builder addField(long id, long val) {
checkContainer();
FieldInt64 field = mFieldInt64Cache.get(FieldInt64::new);
@@ -342,9 +633,7 @@ public final class PerfettoTrackEventExtra {
return this;
}
- /**
- * Adds a proto field with field id {@code id} and value {@code val}.
- */
+ @Override
public Builder addField(long id, double val) {
checkContainer();
FieldDouble field = mFieldDoubleCache.get(FieldDouble::new);
@@ -353,35 +642,24 @@ public final class PerfettoTrackEventExtra {
return this;
}
- /**
- * Adds a proto field with field id {@code id} and value {@code val}.
- */
- public Builder addField(long id, String val) {
+ @Override
+ public Builder addField(long id, String val, Object... args) {
checkContainer();
FieldString field = mFieldStringCache.get(FieldString::new);
- field.setValue(id, val);
+ field.setValue(id, toString(val, args));
mCurrentContainer.addField(field);
return this;
}
- /**
- * Begins a proto field with field
- * Fields can be added from this point and there must be a corresponding
- * {@link endProto}.
- *
- * The proto field is a singleton and all proto fields get added inside the
- * one {@link beginProto} and {@link endProto} within the {@link Builder}.
- */
+ @Override
public Builder beginProto() {
checkParent();
mProto.clearFields();
mExtra.addPerfettoPointer(mProto);
- return mBuilderCache.get(Builder::new).init(this, mProto);
+ return mBuilderCache.get(BuilderImpl::new).initInternal(this, mProto);
}
- /**
- * Ends a proto field.
- */
+ @Override
public Builder endProto() {
if (mParent == null || mCurrentContainer == null) {
throw new IllegalStateException("No proto to end");
@@ -389,22 +667,16 @@ public final class PerfettoTrackEventExtra {
return mParent;
}
- /**
- * Begins a nested proto field with field id {@code id}.
- * Fields can be added from this point and there must be a corresponding
- * {@link endNested}.
- */
+ @Override
public Builder beginNested(long id) {
checkContainer();
FieldNested field = mFieldNestedCache.get(FieldNested::new);
field.setId(id);
mCurrentContainer.addField(field);
- return mBuilderCache.get(Builder::new).init(this, field);
+ return mBuilderCache.get(BuilderImpl::new).initInternal(this, field);
}
- /**
- * Ends a nested proto field.
- */
+ @Override
public Builder endNested() {
if (mParent == null || mCurrentContainer == null) {
throw new IllegalStateException("No nested field to end");
@@ -412,21 +684,15 @@ public final class PerfettoTrackEventExtra {
return mParent;
}
- /**
- * Initializes a {@link Builder}.
- */
- public Builder init(Builder parent, FieldContainer container) {
+ private static String toString(String val, Object... args) {
+ return args == null || args.length == 0 ? val : String.format(val, args);
+ }
+
+ private Builder initInternal(Builder parent, FieldContainer container) {
mParent = parent;
mCurrentContainer = container;
mIsBuilt = false;
- if (mParent == null) {
- if (mExtra.mIsInUse) {
- throw new IllegalStateException("Cannot create a new builder when another"
- + " extra is in use");
- }
- mExtra.mIsInUse = true;
- }
return this;
}
@@ -439,9 +705,8 @@ public final class PerfettoTrackEventExtra {
private void checkParent() {
checkState();
- if (mParent != null) {
- throw new IllegalStateException(
- "This builder has already been used. Create a new builder for another event.");
+ if (!this.equals(mParent)) {
+ throw new IllegalStateException("Operation not supported for proto");
}
}
@@ -458,7 +723,14 @@ public final class PerfettoTrackEventExtra {
* Start a {@link Builder} to build a {@link PerfettoTrackEventExtra}.
*/
public static Builder builder() {
- return sTrackEventExtra.get().mBuilderCache.get(Builder::new).init(null, null);
+ return sTrackEventExtra.get().mBuilderCache.get(BuilderImpl::new).initInternal(null, null);
+ }
+
+ /**
+ * Returns a no-op {@link Builder}. Useful if a category is disabled.
+ */
+ public static Builder noOpBuilder() {
+ return NO_OP_BUILDER;
}
private final RingBuffer<NamedTrack> mNamedTrackCache =
@@ -476,7 +748,7 @@ public final class PerfettoTrackEventExtra {
private final Pool<FieldString> mFieldStringCache = new Pool(DEFAULT_EXTRA_CACHE_SIZE);
private final Pool<FieldNested> mFieldNestedCache = new Pool(DEFAULT_EXTRA_CACHE_SIZE);
private final Pool<Flow> mFlowCache = new Pool(DEFAULT_EXTRA_CACHE_SIZE);
- private final Pool<Builder> mBuilderCache = new Pool(DEFAULT_EXTRA_CACHE_SIZE);
+ private final Pool<BuilderImpl> mBuilderCache = new Pool(DEFAULT_EXTRA_CACHE_SIZE);
private static final NativeAllocationRegistry sRegistry =
NativeAllocationRegistry.createMalloced(
@@ -509,7 +781,6 @@ public final class PerfettoTrackEventExtra {
*/
public void reset() {
native_clear_args(mPtr);
- mIsInUse = false;
}
private CounterInt64 getCounterInt64() {
@@ -1078,4 +1349,6 @@ public final class PerfettoTrackEventExtra {
private static native void native_add_arg(long ptr, long extraPtr);
@CriticalNative
private static native void native_clear_args(long ptr);
+ @FastNative
+ private static native void native_emit(int type, long tag, String name, long ptr);
}
diff --git a/core/java/android/os/ZygoteProcess.java b/core/java/android/os/ZygoteProcess.java
index 73a74b2f8abc..2d487b1e77d5 100644
--- a/core/java/android/os/ZygoteProcess.java
+++ b/core/java/android/os/ZygoteProcess.java
@@ -1319,6 +1319,6 @@ public class ZygoteProcess {
throw new RuntimeException("Starting child-zygote through Zygote failed", ex);
}
- return new ChildZygoteProcess(serverAddress, result.pid);
+ return new ChildZygoteProcess(serverAddress, result.pid, uid);
}
}
diff --git a/core/java/android/os/flags.aconfig b/core/java/android/os/flags.aconfig
index 8b8369890d1b..b12433a73a31 100644
--- a/core/java/android/os/flags.aconfig
+++ b/core/java/android/os/flags.aconfig
@@ -150,6 +150,13 @@ flag {
}
flag {
+ name: "app_zygote_retry_start"
+ namespace: "arc_next"
+ description: "Guard the new added retry logic in app zygote."
+ bug: "361799815"
+}
+
+flag {
name: "battery_part_status_api"
is_exported: true
namespace: "phoenix"
diff --git a/core/java/android/permission/flags.aconfig b/core/java/android/permission/flags.aconfig
index 70d8891f0677..a480a3b013bb 100644
--- a/core/java/android/permission/flags.aconfig
+++ b/core/java/android/permission/flags.aconfig
@@ -493,3 +493,9 @@ flag {
bug: "341941666"
}
+flag {
+ name: "sqlite_discrete_op_event_logging_enabled"
+ namespace: "permissions"
+ description: "Collect sqlite performance metrics for discrete ops."
+ bug: "377584611"
+}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 7d067fb3a3be..2e231e3957c6 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -353,6 +353,18 @@ public final class Settings {
*/
public static final String ACTION_ONE_HANDED_SETTINGS =
"android.settings.action.ONE_HANDED_SETTINGS";
+
+ /**
+ * Activity Action: Show Double tap power gesture Settings page.
+ * <p>
+ * Input: Nothing
+ * <p>
+ * Output: Nothing
+ * @hide
+ */
+ public static final String ACTION_DOUBLE_TAP_POWER_SETTINGS =
+ "android.settings.action.DOUBLE_TAP_POWER_SETTINGS";
+
/**
* The return values for {@link Settings.Config#set}
* @hide
@@ -6318,6 +6330,17 @@ public final class Settings {
public static final String TOUCHPAD_SYSTEM_GESTURES = "touchpad_system_gestures";
/**
+ * Whether touchpad acceleration is enabled.
+ *
+ * When enabled, the speed of the pointer will increase as the user moves their
+ * finger faster on the touchpad.
+ *
+ * @hide
+ */
+ public static final String TOUCHPAD_ACCELERATION_ENABLED =
+ "touchpad_acceleration_enabled";
+
+ /**
* Whether to enable reversed vertical scrolling for connected mice.
*
* When enabled, scrolling down on the mouse wheel will move the screen up and vice versa.
@@ -6612,6 +6635,7 @@ public final class Settings {
PRIVATE_SETTINGS.add(TOUCHPAD_TAP_DRAGGING);
PRIVATE_SETTINGS.add(TOUCHPAD_RIGHT_CLICK_ZONE);
PRIVATE_SETTINGS.add(TOUCHPAD_SYSTEM_GESTURES);
+ PRIVATE_SETTINGS.add(TOUCHPAD_ACCELERATION_ENABLED);
PRIVATE_SETTINGS.add(CAMERA_FLASH_NOTIFICATION);
PRIVATE_SETTINGS.add(SCREEN_FLASH_NOTIFICATION);
PRIVATE_SETTINGS.add(SCREEN_FLASH_NOTIFICATION_COLOR);
@@ -9314,6 +9338,16 @@ public final class Settings {
"accessibility_autoclick_cursor_area_size";
/**
+ * Setting that specifies whether minor cursor movement will be ignored when
+ * {@link #ACCESSIBILITY_AUTOCLICK_ENABLED} is set.
+ *
+ * @see #ACCESSIBILITY_AUTOCLICK_ENABLED
+ * @hide
+ */
+ public static final String ACCESSIBILITY_AUTOCLICK_IGNORE_MINOR_CURSOR_MOVEMENT =
+ "accessibility_autoclick_ignore_minor_cursor_movement";
+
+ /**
* Whether or not larger size icons are used for the pointer of mouse/trackpad for
* accessibility.
* (0 = false, 1 = true)
@@ -12383,12 +12417,6 @@ public final class Settings {
public static final String CAMERA_EXTENSIONS_FALLBACK = "camera_extensions_fallback";
/**
- * Controls whether contextual suggestions can be shown in the media controls.
- * @hide
- */
- public static final String MEDIA_CONTROLS_RECOMMENDATION = "qs_media_recommend";
-
- /**
* Controls magnification mode when magnification is enabled via a system-wide triple tap
* gesture or the accessibility shortcut.
*
diff --git a/core/java/android/security/flags.aconfig b/core/java/android/security/flags.aconfig
index a5586227cbb3..792e6ff52d01 100644
--- a/core/java/android/security/flags.aconfig
+++ b/core/java/android/security/flags.aconfig
@@ -43,7 +43,7 @@ flag {
flag {
name: "secure_array_zeroization"
- namespace: "platform_security"
+ namespace: "security"
description: "Enable secure array zeroization"
bug: "320392352"
metadata {
diff --git a/core/java/android/service/quickaccesswallet/QuickAccessWalletClient.java b/core/java/android/service/quickaccesswallet/QuickAccessWalletClient.java
index b5251db4e539..051885e10132 100644
--- a/core/java/android/service/quickaccesswallet/QuickAccessWalletClient.java
+++ b/core/java/android/service/quickaccesswallet/QuickAccessWalletClient.java
@@ -25,6 +25,7 @@ import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.graphics.drawable.Drawable;
+import android.os.UserHandle;
import java.io.Closeable;
import java.util.concurrent.Executor;
@@ -232,6 +233,15 @@ public interface QuickAccessWalletClient extends Closeable {
Drawable getTileIcon();
/**
+ * Returns the user that should receive the wallet intents
+ *
+ * @return UserHandle
+ * @hide
+ */
+ @Nullable
+ UserHandle getUser();
+
+ /**
* Returns the service label specified by {@code android:label} in the service manifest entry.
*
* @hide
diff --git a/core/java/android/service/quickaccesswallet/QuickAccessWalletClientImpl.java b/core/java/android/service/quickaccesswallet/QuickAccessWalletClientImpl.java
index 97a4beff633f..177164296eea 100644
--- a/core/java/android/service/quickaccesswallet/QuickAccessWalletClientImpl.java
+++ b/core/java/android/service/quickaccesswallet/QuickAccessWalletClientImpl.java
@@ -243,8 +243,9 @@ public class QuickAccessWalletClientImpl implements QuickAccessWalletClient, Ser
return null;
}
String packageName = mServiceInfo.getComponentName().getPackageName();
+ int userId = mServiceInfo.getUserId();
String walletActivity = mServiceInfo.getWalletActivity();
- return createIntent(walletActivity, packageName, ACTION_VIEW_WALLET);
+ return createIntent(walletActivity, packageName, userId, ACTION_VIEW_WALLET);
}
@Override
@@ -302,12 +303,15 @@ public class QuickAccessWalletClientImpl implements QuickAccessWalletClient, Ser
}
String packageName = mServiceInfo.getComponentName().getPackageName();
String settingsActivity = mServiceInfo.getSettingsActivity();
- return createIntent(settingsActivity, packageName, ACTION_VIEW_WALLET_SETTINGS);
+ return createIntent(settingsActivity, packageName, UserHandle.myUserId(),
+ ACTION_VIEW_WALLET_SETTINGS);
}
@Nullable
- private Intent createIntent(@Nullable String activityName, String packageName, String action) {
- PackageManager pm = mContext.getPackageManager();
+ private Intent createIntent(@Nullable String activityName, String packageName,
+ int userId, String action) {
+ Context userContext = mContext.createContextAsUser(UserHandle.of(userId), 0);
+ PackageManager pm = userContext.getPackageManager();
if (TextUtils.isEmpty(activityName)) {
activityName = queryActivityForAction(pm, packageName, action);
}
@@ -361,6 +365,12 @@ public class QuickAccessWalletClientImpl implements QuickAccessWalletClient, Ser
return mServiceInfo == null ? null : mServiceInfo.getTileIcon();
}
+ @Nullable
+ @Override
+ public UserHandle getUser() {
+ return mServiceInfo == null ? null : UserHandle.of(mServiceInfo.getUserId());
+ }
+
@Override
@Nullable
public CharSequence getServiceLabel() {
diff --git a/core/java/android/service/quickaccesswallet/QuickAccessWalletServiceInfo.java b/core/java/android/service/quickaccesswallet/QuickAccessWalletServiceInfo.java
index 8a3f6ceb852b..e1dc6f6d642b 100644
--- a/core/java/android/service/quickaccesswallet/QuickAccessWalletServiceInfo.java
+++ b/core/java/android/service/quickaccesswallet/QuickAccessWalletServiceInfo.java
@@ -16,6 +16,10 @@
package android.service.quickaccesswallet;
+import static android.permission.flags.Flags.walletRoleCrossUserEnabled;
+
+import static com.android.permission.flags.Flags.crossUserRoleEnabled;
+
import android.Manifest;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -32,10 +36,12 @@ import android.content.res.TypedArray;
import android.content.res.XmlResourceParser;
import android.graphics.drawable.Drawable;
import android.os.Binder;
+import android.os.UserHandle;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.Log;
+import android.util.Pair;
import android.util.Xml;
import com.android.internal.R;
@@ -59,22 +65,29 @@ class QuickAccessWalletServiceInfo {
private final ServiceInfo mServiceInfo;
private final ServiceMetadata mServiceMetadata;
private final TileServiceMetadata mTileServiceMetadata;
+ private final int mUserId;
private QuickAccessWalletServiceInfo(
@NonNull ServiceInfo serviceInfo,
@NonNull ServiceMetadata metadata,
- @NonNull TileServiceMetadata tileServiceMetadata) {
+ @NonNull TileServiceMetadata tileServiceMetadata,
+ int userId) {
mServiceInfo = serviceInfo;
mServiceMetadata = metadata;
mTileServiceMetadata = tileServiceMetadata;
+ mUserId = userId;
}
@Nullable
static QuickAccessWalletServiceInfo tryCreate(@NonNull Context context) {
String defaultAppPackageName = null;
+ int defaultAppUser = UserHandle.myUserId();
+
if (isWalletRoleAvailable(context)) {
- defaultAppPackageName = getDefaultWalletApp(context);
+ Pair<String, Integer> roleAndUser = getDefaultWalletApp(context);
+ defaultAppPackageName = roleAndUser.first;
+ defaultAppUser = roleAndUser.second;
} else {
ComponentName defaultPaymentApp = getDefaultPaymentApp(context);
if (defaultPaymentApp == null) {
@@ -83,7 +96,8 @@ class QuickAccessWalletServiceInfo {
defaultAppPackageName = defaultPaymentApp.getPackageName();
}
- ServiceInfo serviceInfo = getWalletServiceInfo(context, defaultAppPackageName);
+ ServiceInfo serviceInfo = getWalletServiceInfo(context, defaultAppPackageName,
+ defaultAppUser);
if (serviceInfo == null) {
return null;
}
@@ -98,15 +112,32 @@ class QuickAccessWalletServiceInfo {
ServiceMetadata metadata = parseServiceMetadata(context, serviceInfo);
TileServiceMetadata tileServiceMetadata =
new TileServiceMetadata(parseTileServiceMetadata(context, serviceInfo));
- return new QuickAccessWalletServiceInfo(serviceInfo, metadata, tileServiceMetadata);
+ return new QuickAccessWalletServiceInfo(serviceInfo, metadata, tileServiceMetadata,
+ defaultAppUser);
}
- private static String getDefaultWalletApp(Context context) {
+ @NonNull
+ private static Pair<String, Integer> getDefaultWalletApp(Context context) {
+ UserHandle user = UserHandle.of(UserHandle.myUserId());
+
final long token = Binder.clearCallingIdentity();
try {
RoleManager roleManager = context.getSystemService(RoleManager.class);
- List<String> roleHolders = roleManager.getRoleHolders(RoleManager.ROLE_WALLET);
- return roleHolders.isEmpty() ? null : roleHolders.get(0);
+
+ if (walletRoleCrossUserEnabled()
+ && crossUserRoleEnabled()
+ && context.checkCallingOrSelfPermission(
+ Manifest.permission.INTERACT_ACROSS_USERS_FULL)
+ == PackageManager.PERMISSION_GRANTED) {
+ user = roleManager.getActiveUserForRole(RoleManager.ROLE_WALLET);
+ if (user == null) {
+ return new Pair<>(null, UserHandle.myUserId());
+ }
+ }
+ List<String> roleHolders = roleManager.getRoleHoldersAsUser(RoleManager.ROLE_WALLET,
+ user);
+ return new Pair<>(roleHolders.isEmpty() ? null : roleHolders.get(0),
+ user.getIdentifier());
} finally {
Binder.restoreCallingIdentity(token);
}
@@ -128,15 +159,16 @@ class QuickAccessWalletServiceInfo {
return comp == null ? null : ComponentName.unflattenFromString(comp);
}
- private static ServiceInfo getWalletServiceInfo(Context context, String packageName) {
+ private static ServiceInfo getWalletServiceInfo(Context context, String packageName,
+ int userId) {
Intent intent = new Intent(QuickAccessWalletService.SERVICE_INTERFACE);
intent.setPackage(packageName);
List<ResolveInfo> resolveInfos =
- context.getPackageManager().queryIntentServices(intent,
+ context.getPackageManager().queryIntentServicesAsUser(intent,
PackageManager.MATCH_DIRECT_BOOT_AWARE
| PackageManager.MATCH_DIRECT_BOOT_UNAWARE
| PackageManager.MATCH_DEFAULT_ONLY
- | PackageManager.GET_META_DATA);
+ | PackageManager.GET_META_DATA, userId);
return resolveInfos.isEmpty() ? null : resolveInfos.get(0).serviceInfo;
}
@@ -247,6 +279,9 @@ class QuickAccessWalletServiceInfo {
return mServiceInfo.getComponentName();
}
+ int getUserId() {
+ return mUserId;
+ }
/**
* @return the fully qualified name of the activity that hosts the full wallet. If available,
* this intent should be started with the action
diff --git a/core/java/android/timezone/MobileCountries.java b/core/java/android/timezone/MobileCountries.java
new file mode 100644
index 000000000000..19ae60880d20
--- /dev/null
+++ b/core/java/android/timezone/MobileCountries.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.timezone;
+
+import android.annotation.NonNull;
+
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * Information about a telephony network.
+ *
+ * @hide
+ */
+public final class MobileCountries {
+
+ @NonNull
+ private final com.android.i18n.timezone.MobileCountries mDelegate;
+
+ MobileCountries(@NonNull com.android.i18n.timezone.MobileCountries delegate) {
+ mDelegate = Objects.requireNonNull(delegate);
+ }
+
+ /**
+ * Returns the Mobile Country Code of the network.
+ */
+ @NonNull
+ public String getMcc() {
+ return mDelegate.getMcc();
+ }
+
+ /**
+ * Returns the Mobile Country Code of the network.
+ */
+ @NonNull
+ public Set<String> getCountryIsoCodes() {
+ return mDelegate.getCountryIsoCodes();
+ }
+
+ /**
+ * Returns the country in which the network operates as an ISO 3166 alpha-2 (lower case).
+ */
+ @NonNull
+ public String getDefaultCountryIsoCode() {
+ return mDelegate.getDefaultCountryIsoCode();
+ }
+
+ @Override
+ public String toString() {
+ return "MobileCountries{"
+ + "mDelegate=" + mDelegate
+ + '}';
+ }
+}
diff --git a/core/java/android/timezone/TelephonyNetworkFinder.java b/core/java/android/timezone/TelephonyNetworkFinder.java
index c69ddf86d3f8..eb50fc24d9ad 100644
--- a/core/java/android/timezone/TelephonyNetworkFinder.java
+++ b/core/java/android/timezone/TelephonyNetworkFinder.java
@@ -19,6 +19,8 @@ package android.timezone;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import com.android.icu.Flags;
+
import java.util.Objects;
/**
@@ -50,4 +52,19 @@ public final class TelephonyNetworkFinder {
return telephonyNetworkDelegate != null
? new TelephonyNetwork(telephonyNetworkDelegate) : null;
}
+
+ /**
+ * Returns the countries where a given MCC is in use.
+ */
+ @Nullable
+ public MobileCountries findCountriesByMcc(@NonNull String mcc) {
+ if (!Flags.telephonyLookupMccExtension()) {
+ return null;
+ }
+ Objects.requireNonNull(mcc);
+
+ com.android.i18n.timezone.MobileCountries countriesByMcc =
+ mDelegate.findCountriesByMcc(mcc);
+ return countriesByMcc != null ? new MobileCountries(countriesByMcc) : null;
+ }
}
diff --git a/core/java/android/util/proto/ProtoFieldFilter.java b/core/java/android/util/proto/ProtoFieldFilter.java
new file mode 100644
index 000000000000..c3ae106b68f8
--- /dev/null
+++ b/core/java/android/util/proto/ProtoFieldFilter.java
@@ -0,0 +1,335 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.util.proto;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.function.Predicate;
+
+/**
+ * A utility class that reads raw protobuf data from an InputStream
+ * and copies only those fields for which a given predicate returns true.
+ *
+ * <p>
+ * This is a low-level approach that does not fully decode fields
+ * (unless necessary to determine lengths). It simply:
+ * <ul>
+ * <li>Parses each field's tag (varint for field number & wire type)</li>
+ * <li>If {@code includeFn(fieldNumber) == true}, copies
+ * the tag bytes and the field bytes directly to the output</li>
+ * <li>Otherwise, skips that field in the input</li>
+ * </ul>
+ * </p>
+ *
+ * <p>
+ * Because we do not re-encode, unknown or unrecognized fields are copied
+ * <i>verbatim</i> and remain exactly as in the input (useful for partial
+ * parsing or partial transformations).
+ * </p>
+ *
+ * <p>
+ * Note: This class only filters based on top-level field numbers. For length-delimited
+ * fields (including nested messages), the entire contents are either copied or skipped
+ * as a single unit. The class is not capable of nested filtering.
+ * </p>
+ *
+ * @hide
+ */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
+public class ProtoFieldFilter {
+
+ private static final int BUFFER_SIZE_BYTES = 4096;
+
+ private final Predicate<Integer> mFieldPredicate;
+ // General-purpose buffer for reading proto fields and their data
+ private final byte[] mBuffer;
+ // Buffer specifically designated to hold varint values (max 10 bytes in protobuf encoding)
+ private final byte[] mVarIntBuffer = new byte[10];
+
+ /**
+ * Constructs a ProtoFieldFilter with a predicate that considers depth.
+ *
+ * @param fieldPredicate A predicate returning true if the given fieldNumber should be
+ * included in the output.
+ * @param bufferSize The size of the internal buffer used for processing proto fields.
+ * Larger buffers may improve performance when processing large
+ * length-delimited fields.
+ */
+ public ProtoFieldFilter(Predicate<Integer> fieldPredicate, int bufferSize) {
+ this.mFieldPredicate = fieldPredicate;
+ this.mBuffer = new byte[bufferSize];
+ }
+
+ /**
+ * Constructs a ProtoFieldFilter with a predicate that considers depth and
+ * uses a default buffer size.
+ *
+ * @param fieldPredicate A predicate returning true if the given fieldNumber should be
+ * included in the output.
+ */
+ public ProtoFieldFilter(Predicate<Integer> fieldPredicate) {
+ this(fieldPredicate, BUFFER_SIZE_BYTES);
+ }
+
+ /**
+ * Reads raw protobuf data from {@code in} and writes only those fields
+ * passing {@code includeFn} to {@code out}. The predicate is given
+ * (fieldNumber, wireType) for each encountered field.
+ *
+ * @param in The input stream of protobuf data
+ * @param out The output stream to which we write the filtered protobuf
+ * @throws IOException If reading or writing fails, or if the protobuf data is corrupted
+ */
+ public void filter(InputStream in, OutputStream out) throws IOException {
+ int tagBytesLength;
+ while ((tagBytesLength = readRawVarint(in)) > 0) {
+ // Parse the varint loaded in mVarIntBuffer, through readRawVarint
+ long tagVal = parseVarint(mVarIntBuffer, tagBytesLength);
+ int fieldNumber = (int) (tagVal >>> ProtoStream.FIELD_ID_SHIFT);
+ int wireType = (int) (tagVal & ProtoStream.WIRE_TYPE_MASK);
+
+ if (fieldNumber == 0) {
+ break;
+ }
+ if (mFieldPredicate.test(fieldNumber)) {
+ out.write(mVarIntBuffer, 0, tagBytesLength);
+ copyFieldData(in, out, wireType);
+ } else {
+ skipFieldData(in, wireType);
+ }
+ }
+ }
+
+ /**
+ * Reads a varint (up to 10 bytes) from the stream as raw bytes
+ * and returns it in a byte array. If the stream is at EOF, returns null.
+ *
+ * @param in The input stream
+ * @return the size of the varint bytes moved to mVarIntBuffer
+ * @throws IOException If an error occurs, or if we detect a malformed varint
+ */
+ private int readRawVarint(InputStream in) throws IOException {
+ // We attempt to read 1 byte. If none available => null
+ int b = in.read();
+ if (b < 0) {
+ return 0;
+ }
+ int count = 0;
+ mVarIntBuffer[count++] = (byte) b;
+ // If the continuation bit is set, we continue
+ while ((b & 0x80) != 0) {
+ // read next byte
+ b = in.read();
+ // EOF
+ if (b < 0) {
+ throw new IOException("Malformed varint: reached EOF mid-varint");
+ }
+ // max 10 bytes for varint 64
+ if (count >= 10) {
+ throw new IOException("Malformed varint: too many bytes (max 10)");
+ }
+ mVarIntBuffer[count++] = (byte) b;
+ }
+ return count;
+ }
+
+ /**
+ * Parses a varint from the given raw bytes and returns it as a long.
+ *
+ * @param rawVarint The bytes representing the varint
+ * @param byteLength The number of bytes to read from rawVarint
+ * @return The decoded long value
+ */
+ private static long parseVarint(byte[] rawVarint, int byteLength) throws IOException {
+ long result = 0;
+ int shift = 0;
+ for (int i = 0; i < byteLength; i++) {
+ result |= ((rawVarint[i] & 0x7F) << shift);
+ shift += 7;
+ if (shift > 63) {
+ throw new IOException("Malformed varint: exceeds 64 bits");
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Copies the wire data for a single field from {@code in} to {@code out},
+ * assuming we have already read the field's tag.
+ *
+ * @param in The input stream (protobuf data)
+ * @param out The output stream
+ * @param wireType The wire type (0=varint, 1=fixed64, 2=length-delim, 5=fixed32)
+ * @throws IOException if reading/writing fails or data is malformed
+ */
+ private void copyFieldData(InputStream in, OutputStream out, int wireType)
+ throws IOException {
+ switch (wireType) {
+ case ProtoStream.WIRE_TYPE_VARINT:
+ copyVarint(in, out);
+ break;
+ case ProtoStream.WIRE_TYPE_FIXED64:
+ copyFixed(in, out, 8);
+ break;
+ case ProtoStream.WIRE_TYPE_LENGTH_DELIMITED:
+ copyLengthDelimited(in, out);
+ break;
+ case ProtoStream.WIRE_TYPE_FIXED32:
+ copyFixed(in, out, 4);
+ break;
+ // case WIRE_TYPE_START_GROUP:
+ // Not Supported
+ // case WIRE_TYPE_END_GROUP:
+ // Not Supported
+ default:
+ // Error or unrecognized wire type
+ throw new IOException("Unknown or unsupported wire type: " + wireType);
+ }
+ }
+
+ /**
+ * Skips the wire data for a single field from {@code in},
+ * assuming the field's tag was already read.
+ */
+ private void skipFieldData(InputStream in, int wireType) throws IOException {
+ switch (wireType) {
+ case ProtoStream.WIRE_TYPE_VARINT:
+ skipVarint(in);
+ break;
+ case ProtoStream.WIRE_TYPE_FIXED64:
+ skipBytes(in, 8);
+ break;
+ case ProtoStream.WIRE_TYPE_LENGTH_DELIMITED:
+ skipLengthDelimited(in);
+ break;
+ case ProtoStream.WIRE_TYPE_FIXED32:
+ skipBytes(in, 4);
+ break;
+ // case WIRE_TYPE_START_GROUP:
+ // Not Supported
+ // case WIRE_TYPE_END_GROUP:
+ // Not Supported
+ default:
+ throw new IOException("Unknown or unsupported wire type: " + wireType);
+ }
+ }
+
+ /** Copies a varint (the field's value) from in to out. */
+ private static void copyVarint(InputStream in, OutputStream out) throws IOException {
+ while (true) {
+ int b = in.read();
+ if (b < 0) {
+ throw new IOException("EOF while copying varint");
+ }
+ out.write(b);
+ if ((b & 0x80) == 0) {
+ break;
+ }
+ }
+ }
+
+ /**
+ * Copies exactly {@code length} bytes from {@code in} to {@code out}.
+ */
+ private void copyFixed(InputStream in, OutputStream out,
+ int length) throws IOException {
+ int toRead = length;
+ while (toRead > 0) {
+ int chunk = Math.min(toRead, mBuffer.length);
+ int readCount = in.read(mBuffer, 0, chunk);
+ if (readCount < 0) {
+ throw new IOException("EOF while copying fixed" + (length * 8) + " field");
+ }
+ out.write(mBuffer, 0, readCount);
+ toRead -= readCount;
+ }
+ }
+
+ /** Copies a length-delimited field */
+ private void copyLengthDelimited(InputStream in,
+ OutputStream out) throws IOException {
+ // 1) read length varint (and copy)
+ int lengthVarintLength = readRawVarint(in);
+ if (lengthVarintLength <= 0) {
+ throw new IOException("EOF reading length for length-delimited field");
+ }
+ out.write(mVarIntBuffer, 0, lengthVarintLength);
+
+ long lengthVal = parseVarint(mVarIntBuffer, lengthVarintLength);
+ if (lengthVal < 0 || lengthVal > Integer.MAX_VALUE) {
+ throw new IOException("Invalid length for length-delimited field: " + lengthVal);
+ }
+
+ // 2) copy that many bytes
+ copyFixed(in, out, (int) lengthVal);
+ }
+
+ /** Skips a varint in the input (does not write anything). */
+ private static void skipVarint(InputStream in) throws IOException {
+ int bytesSkipped = 0;
+ while (true) {
+ int b = in.read();
+ if (b < 0) {
+ throw new IOException("EOF while skipping varint");
+ }
+ if ((b & 0x80) == 0) {
+ break;
+ }
+ bytesSkipped++;
+ if (bytesSkipped > 10) {
+ throw new IOException("Malformed varint: exceeds maximum length of 10 bytes");
+ }
+ }
+ }
+
+ /** Skips exactly n bytes. */
+ private void skipBytes(InputStream in, long n) throws IOException {
+ long skipped = in.skip(n);
+ // If skip fails, fallback to reading the remaining bytes
+ if (skipped < n) {
+ long bytesRemaining = n - skipped;
+
+ while (bytesRemaining > 0) {
+ int bytesToRead = (int) Math.min(bytesRemaining, mBuffer.length);
+ int bytesRead = in.read(mBuffer, 0, bytesToRead);
+ if (bytesRemaining < 0) {
+ throw new IOException("EOF while skipping bytes");
+ }
+ bytesRemaining -= bytesRead;
+ }
+ }
+ }
+
+ /**
+ * Skips a length-delimited field.
+ * 1) read the length as varint,
+ * 2) skip that many bytes
+ */
+ private void skipLengthDelimited(InputStream in) throws IOException {
+ int lengthVarintLength = readRawVarint(in);
+ if (lengthVarintLength <= 0) {
+ throw new IOException("EOF reading length for length-delimited field");
+ }
+ long lengthVal = parseVarint(mVarIntBuffer, lengthVarintLength);
+ if (lengthVal < 0 || lengthVal > Integer.MAX_VALUE) {
+ throw new IOException("Invalid length to skip: " + lengthVal);
+ }
+ skipBytes(in, lengthVal);
+ }
+
+}
diff --git a/core/java/android/view/Choreographer.java b/core/java/android/view/Choreographer.java
index 992790e092d1..053ccdd73c8c 100644
--- a/core/java/android/view/Choreographer.java
+++ b/core/java/android/view/Choreographer.java
@@ -250,11 +250,14 @@ public final class Choreographer {
/**
* Set flag to indicate that client is blocked waiting for buffer release and
- * buffer stuffing recovery should soon begin.
+ * buffer stuffing recovery should soon begin. This is provided with the
+ * duration of time in nanoseconds that the client was blocked for.
* @hide
*/
- public void onWaitForBufferRelease() {
- mBufferStuffingState.isStuffed.set(true);
+ public void onWaitForBufferRelease(long durationNanos) {
+ if (durationNanos > mLastFrameIntervalNanos / 2) {
+ mBufferStuffingState.isStuffed.set(true);
+ }
}
/**
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index 780e76122e8a..dd32947c69e4 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -2391,4 +2391,9 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall
}
}
}
+
+ @Override
+ public CharSequence getAccessibilityClassName() {
+ return SurfaceView.class.getName();
+ }
}
diff --git a/core/java/android/view/TextureView.java b/core/java/android/view/TextureView.java
index ebc86ee96f75..0c6eaae601a8 100644
--- a/core/java/android/view/TextureView.java
+++ b/core/java/android/view/TextureView.java
@@ -918,6 +918,11 @@ public class TextureView extends View {
mLastFrameTimeMillis = now;
}
+ @Override
+ public CharSequence getAccessibilityClassName() {
+ return TextureView.class.getName();
+ }
+
@UnsupportedAppUsage
private final SurfaceTexture.OnFrameAvailableListener mUpdateListener =
surfaceTexture -> {
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index d88b6d642ee6..7206906658ff 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -28365,10 +28365,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
if (android.os.Flags.adpfMeasureDuringInputEventBoost()) {
final boolean notifyRenderer = hasExpensiveMeasuresDuringInputEvent();
if (notifyRenderer) {
- Trace.traceBegin(Trace.TRACE_TAG_VIEW,
- "CPU_LOAD_UP: " + "hasExpensiveMeasuresDuringInputEvent");
- getViewRootImpl().notifyRendererOfExpensiveFrame();
- Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+ getViewRootImpl().notifyRendererOfExpensiveFrame(
+ "ADPF_SendHint: hasExpensiveMeasuresDuringInputEvent");
}
}
// measure ourselves, this should set the measured dimension flag back
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 64e7becb1ed4..cd8a85a66c1a 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -2968,6 +2968,20 @@ public final class ViewRootImpl implements ViewParent,
}
}
+ /**
+ * Same as notifyRendererOfExpensiveFrame(), but adding {@code reason} for tracing.
+ *
+ * @hide
+ */
+ public void notifyRendererOfExpensiveFrame(String reason) {
+ Trace.traceBegin(Trace.TRACE_TAG_VIEW, reason);
+ try {
+ notifyRendererOfExpensiveFrame();
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+ }
+ }
+
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
void scheduleTraversals() {
if (!mTraversalScheduled) {
diff --git a/core/java/android/view/accessibility/AccessibilityManager.java b/core/java/android/view/accessibility/AccessibilityManager.java
index 64277b14098d..d267c9451d1a 100644
--- a/core/java/android/view/accessibility/AccessibilityManager.java
+++ b/core/java/android/view/accessibility/AccessibilityManager.java
@@ -160,6 +160,9 @@ public final class AccessibilityManager {
/** @hide */
public static final int AUTOCLICK_CURSOR_AREA_INCREMENT_SIZE = 20;
+ /** @hide */
+ public static final boolean AUTOCLICK_IGNORE_MINOR_CURSOR_MOVEMENT_DEFAULT = false;
+
/**
* Activity action: Launch UI to manage which accessibility service or feature is assigned
* to the navigation bar Accessibility button.
diff --git a/core/java/android/view/contentcapture/ChildContentCaptureSession.java b/core/java/android/view/contentcapture/ChildContentCaptureSession.java
index 8baa55f8e377..6e2e1009fd40 100644
--- a/core/java/android/view/contentcapture/ChildContentCaptureSession.java
+++ b/core/java/android/view/contentcapture/ChildContentCaptureSession.java
@@ -74,8 +74,8 @@ final class ChildContentCaptureSession extends ContentCaptureSession {
}
@Override
- void flush(@FlushReason int reason) {
- mParent.flush(reason);
+ void internalFlush(@FlushReason int reason) {
+ mParent.internalFlush(reason);
}
@Override
diff --git a/core/java/android/view/contentcapture/ContentCaptureManager.java b/core/java/android/view/contentcapture/ContentCaptureManager.java
index 3f3484d5a527..b7a77d701045 100644
--- a/core/java/android/view/contentcapture/ContentCaptureManager.java
+++ b/core/java/android/view/contentcapture/ContentCaptureManager.java
@@ -663,7 +663,7 @@ public final class ContentCaptureManager {
@UiThread
public void flush(@FlushReason int reason) {
if (mOptions.lite) return;
- getMainContentCaptureSession().flush(reason);
+ getMainContentCaptureSession().internalFlush(reason);
}
/**
diff --git a/core/java/android/view/contentcapture/ContentCaptureSession.java b/core/java/android/view/contentcapture/ContentCaptureSession.java
index 6bb2975d9cf1..791a6f4254ec 100644
--- a/core/java/android/view/contentcapture/ContentCaptureSession.java
+++ b/core/java/android/view/contentcapture/ContentCaptureSession.java
@@ -342,7 +342,7 @@ public abstract class ContentCaptureSession implements AutoCloseable {
/**
* Flushes the buffered events to the service.
*/
- abstract void flush(@FlushReason int reason);
+ abstract void internalFlush(@FlushReason int reason);
/**
* Sets the {@link ContentCaptureContext} associated with the session.
diff --git a/core/java/android/view/contentcapture/MainContentCaptureSession.java b/core/java/android/view/contentcapture/MainContentCaptureSession.java
index eddfc42da9bd..29cae857098d 100644
--- a/core/java/android/view/contentcapture/MainContentCaptureSession.java
+++ b/core/java/android/view/contentcapture/MainContentCaptureSession.java
@@ -375,7 +375,7 @@ public final class MainContentCaptureSession extends ContentCaptureSession {
void onDestroy() {
clearAndRunOnContentCaptureThread(() -> {
try {
- flush(FLUSH_REASON_SESSION_FINISHED);
+ internalFlush(FLUSH_REASON_SESSION_FINISHED);
} finally {
destroySession();
}
@@ -623,7 +623,7 @@ public final class MainContentCaptureSession extends ContentCaptureSession {
flushReason = forceFlush ? FLUSH_REASON_FORCE_FLUSH : FLUSH_REASON_FULL;
}
- flush(flushReason);
+ internalFlush(flushReason);
}
private boolean hasStarted() {
@@ -687,15 +687,18 @@ public final class MainContentCaptureSession extends ContentCaptureSession {
if (sVerbose) Log.v(TAG, "Nothing to flush");
return;
}
- flush(reason);
+ internalFlush(reason);
}
- /** @hide */
+ /**
+ * Internal API to flush the buffered events to the service.
+ *
+ * Do not confuse this with the public API {@link #flush()}.
+ *
+ * @hide */
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
@Override
- public void flush(@FlushReason int reason) {
- // TODO: b/380381249 renaming the internal APIs to prevent confusions between this and the
- // public API.
+ public void internalFlush(@FlushReason int reason) {
runOnContentCaptureThread(() -> flushImpl(reason));
}
diff --git a/core/java/android/view/inputmethod/InputMethodInfo.java b/core/java/android/view/inputmethod/InputMethodInfo.java
index 0a83bdc35b1f..8fef2d726b2c 100644
--- a/core/java/android/view/inputmethod/InputMethodInfo.java
+++ b/core/java/android/view/inputmethod/InputMethodInfo.java
@@ -766,7 +766,6 @@ public final class InputMethodInfo implements Parcelable {
* Returns true if IME supports only virtual devices.
* @hide
*/
- @FlaggedApi(android.companion.virtual.flags.Flags.FLAG_VDM_CUSTOM_IME)
@SystemApi
public boolean isVirtualDeviceOnly() {
return mIsVirtualDeviceOnly;
diff --git a/core/java/android/widget/CompoundButton.java b/core/java/android/widget/CompoundButton.java
index ed6ec32fca25..3cc0042f2bcc 100644
--- a/core/java/android/widget/CompoundButton.java
+++ b/core/java/android/widget/CompoundButton.java
@@ -16,6 +16,8 @@
package android.widget;
+import static android.view.accessibility.Flags.triStateChecked;
+
import android.annotation.DrawableRes;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -209,6 +211,10 @@ public abstract class CompoundButton extends Button implements Checkable {
mCheckedFromResource = false;
mChecked = checked;
refreshDrawableState();
+ if (triStateChecked()) {
+ notifyViewAccessibilityStateChangedIfNeeded(
+ AccessibilityEvent.CONTENT_CHANGE_TYPE_CHECKED);
+ }
// Avoid infinite recursions if setChecked() is called from a listener
if (mBroadcasting) {
@@ -490,7 +496,12 @@ public abstract class CompoundButton extends Button implements Checkable {
public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
super.onInitializeAccessibilityNodeInfoInternal(info);
info.setCheckable(true);
- info.setChecked(mChecked);
+ if (triStateChecked()) {
+ info.setChecked(mChecked ? AccessibilityNodeInfo.CHECKED_STATE_TRUE :
+ AccessibilityNodeInfo.CHECKED_STATE_FALSE);
+ } else {
+ info.setChecked(mChecked);
+ }
}
@Override
diff --git a/core/java/android/widget/EditText.java b/core/java/android/widget/EditText.java
index 3e0161a9b791..2056e2254ecd 100644
--- a/core/java/android/widget/EditText.java
+++ b/core/java/android/widget/EditText.java
@@ -57,8 +57,8 @@ import com.android.internal.R;
* Choosing the input type configures the keyboard type that is shown, acceptable characters,
* and appearance of the edit text.
* For example, if you want to accept a secret number, like a unique pin or serial number,
- * you can set inputType to "numericPassword".
- * An inputType of "numericPassword" results in an edit text that accepts numbers only,
+ * you can set inputType to {@link android.R.styleable#TextView_inputType numberPassword}.
+ * An input type of {@code numberPassword} results in an edit text that accepts numbers only,
* shows a numeric keyboard when focused, and masks the text that is entered for privacy.
* <p>
* See the <a href="{@docRoot}guide/topics/ui/controls/text.html">Text Fields</a>
diff --git a/core/java/android/window/ITaskOrganizerController.aidl b/core/java/android/window/ITaskOrganizerController.aidl
index 1748b9d38538..8934cf67b380 100644
--- a/core/java/android/window/ITaskOrganizerController.aidl
+++ b/core/java/android/window/ITaskOrganizerController.aidl
@@ -39,7 +39,12 @@ interface ITaskOrganizerController {
*/
void unregisterTaskOrganizer(ITaskOrganizer organizer);
- /** Creates a persistent root task in WM for a particular windowing-mode. */
+ /**
+ * Creates a persistent root task in WM for a particular windowing-mode.
+ *
+ * It may be removed using {@link #deleteRootTask} or through
+ * {@link WindowContainerTransaction#removeRootTask}.
+ */
void createRootTask(int displayId, int windowingMode, IBinder launchCookie,
boolean removeWithTaskOrganizer);
diff --git a/core/java/android/window/WindowContainerTransaction.java b/core/java/android/window/WindowContainerTransaction.java
index 02f8e2f59b33..ce0ccd5c6d0d 100644
--- a/core/java/android/window/WindowContainerTransaction.java
+++ b/core/java/android/window/WindowContainerTransaction.java
@@ -614,6 +614,10 @@ public final class WindowContainerTransaction implements Parcelable {
/**
* Finds and removes a task and its children using its container token. The task is removed
* from recents.
+ *
+ * If the task is a root task, its leaves are removed but the root task is not. Use
+ * {@link #removeRootTask(WindowContainerToken)} to remove the root task.
+ *
* @param containerToken ContainerToken of Task to be removed
*/
@NonNull
@@ -623,6 +627,19 @@ public final class WindowContainerTransaction implements Parcelable {
}
/**
+ * Finds and removes a root task created by an organizer and its leaves using its container
+ * token.
+ *
+ * @param containerToken ContainerToken of the root task to be removed
+ * @hide
+ */
+ @NonNull
+ public WindowContainerTransaction removeRootTask(@NonNull WindowContainerToken containerToken) {
+ mHierarchyOps.add(HierarchyOp.createForRemoveRootTask(containerToken.asBinder()));
+ return this;
+ }
+
+ /**
* Sets whether a container is being drag-resized.
* When {@code true}, the client will reuse a single (larger) surface size to avoid
* continuous allocations on every size change.
@@ -1573,6 +1590,7 @@ public final class WindowContainerTransaction implements Parcelable {
public static final int HIERARCHY_OP_TYPE_SET_EXCLUDE_INSETS_TYPES = 21;
public static final int HIERARCHY_OP_TYPE_SET_KEYGUARD_STATE = 22;
public static final int HIERARCHY_OP_TYPE_SET_DISABLE_LAUNCH_ADJACENT = 23;
+ public static final int HIERARCHY_OP_TYPE_REMOVE_ROOT_TASK = 24;
@IntDef(prefix = {"HIERARCHY_OP_TYPE_"}, value = {
HIERARCHY_OP_TYPE_REPARENT,
@@ -1598,7 +1616,8 @@ public final class WindowContainerTransaction implements Parcelable {
HIERARCHY_OP_TYPE_RESTORE_BACK_NAVIGATION,
HIERARCHY_OP_TYPE_SET_EXCLUDE_INSETS_TYPES,
HIERARCHY_OP_TYPE_SET_KEYGUARD_STATE,
- HIERARCHY_OP_TYPE_SET_DISABLE_LAUNCH_ADJACENT
+ HIERARCHY_OP_TYPE_SET_DISABLE_LAUNCH_ADJACENT,
+ HIERARCHY_OP_TYPE_REMOVE_ROOT_TASK,
})
@Retention(RetentionPolicy.SOURCE)
public @interface HierarchyOpType {
@@ -1795,6 +1814,18 @@ public final class WindowContainerTransaction implements Parcelable {
.build();
}
+ /**
+ * Creates a hierarchy op for deleting a root task
+ *
+ * @hide
+ **/
+ @NonNull
+ public static HierarchyOp createForRemoveRootTask(@NonNull IBinder container) {
+ return new HierarchyOp.Builder(HIERARCHY_OP_TYPE_REMOVE_ROOT_TASK)
+ .setContainer(container)
+ .build();
+ }
+
/** Creates a hierarchy op for clearing adjacent root tasks. */
@NonNull
public static HierarchyOp createForClearAdjacentRoots(@NonNull IBinder root) {
@@ -2012,6 +2043,7 @@ public final class WindowContainerTransaction implements Parcelable {
return "removeInsetsFrameProvider";
case HIERARCHY_OP_TYPE_SET_ALWAYS_ON_TOP: return "setAlwaysOnTop";
case HIERARCHY_OP_TYPE_REMOVE_TASK: return "removeTask";
+ case HIERARCHY_OP_TYPE_REMOVE_ROOT_TASK: return "removeRootTask";
case HIERARCHY_OP_TYPE_FINISH_ACTIVITY: return "finishActivity";
case HIERARCHY_OP_TYPE_CLEAR_ADJACENT_ROOTS: return "clearAdjacentRoots";
case HIERARCHY_OP_TYPE_SET_REPARENT_LEAF_TASK_IF_RELAUNCH:
@@ -2096,6 +2128,9 @@ public final class WindowContainerTransaction implements Parcelable {
case HIERARCHY_OP_TYPE_REMOVE_TASK:
sb.append("task=").append(mContainer);
break;
+ case HIERARCHY_OP_TYPE_REMOVE_ROOT_TASK:
+ sb.append("rootTask=").append(mContainer);
+ break;
case HIERARCHY_OP_TYPE_FINISH_ACTIVITY:
sb.append("activity=").append(mContainer);
break;
diff --git a/core/java/android/window/flags/windowing_sdk.aconfig b/core/java/android/window/flags/windowing_sdk.aconfig
index f178b0ed2043..1b946afd506c 100644
--- a/core/java/android/window/flags/windowing_sdk.aconfig
+++ b/core/java/android/window/flags/windowing_sdk.aconfig
@@ -178,3 +178,10 @@ flag {
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ namespace: "windowing_sdk"
+ name: "safe_region_letterboxing"
+ description: "Enables letterboxing for a safe region"
+ bug: "380132497"
+}
diff --git a/core/java/com/android/internal/jank/Cuj.java b/core/java/com/android/internal/jank/Cuj.java
index 158b526cb1a0..928fa8ca35cf 100644
--- a/core/java/com/android/internal/jank/Cuj.java
+++ b/core/java/com/android/internal/jank/Cuj.java
@@ -265,8 +265,17 @@ public class Cuj {
*/
public static final int CUJ_LAUNCHER_OVERVIEW_TASK_DISMISS = 121;
+ /**
+ * Track closing task in Desktop Windowing.
+ *
+ * <p> Tracking begins when the CloseDesktopTaskTransitionHandler in Launcher starts
+ * animating the task closure. This is triggered when the close button in the app header is
+ * clicked on a desktop window. </p>
+ */
+ public static final int CUJ_DESKTOP_MODE_CLOSE_TASK = 122;
+
// When adding a CUJ, update this and make sure to also update CUJ_TO_STATSD_INTERACTION_TYPE.
- @VisibleForTesting static final int LAST_CUJ = CUJ_LAUNCHER_OVERVIEW_TASK_DISMISS;
+ @VisibleForTesting static final int LAST_CUJ = CUJ_DESKTOP_MODE_CLOSE_TASK;
/** @hide */
@IntDef({
@@ -379,7 +388,8 @@ public class Cuj {
CUJ_DESKTOP_MODE_SNAP_RESIZE,
CUJ_DESKTOP_MODE_UNMAXIMIZE_WINDOW,
CUJ_DESKTOP_MODE_ENTER_FROM_OVERVIEW_MENU,
- CUJ_LAUNCHER_OVERVIEW_TASK_DISMISS
+ CUJ_LAUNCHER_OVERVIEW_TASK_DISMISS,
+ CUJ_DESKTOP_MODE_CLOSE_TASK
})
@Retention(RetentionPolicy.SOURCE)
public @interface CujType {}
@@ -503,6 +513,7 @@ public class Cuj {
CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_DESKTOP_MODE_UNMAXIMIZE_WINDOW] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__DESKTOP_MODE_UNMAXIMIZE_WINDOW;
CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_DESKTOP_MODE_ENTER_FROM_OVERVIEW_MENU] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__DESKTOP_MODE_ENTER_FROM_OVERVIEW_MENU;
CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_OVERVIEW_TASK_DISMISS] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_OVERVIEW_TASK_DISMISS;
+ CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_DESKTOP_MODE_CLOSE_TASK] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__DESKTOP_MODE_CLOSE_TASK;
}
private Cuj() {
@@ -741,6 +752,8 @@ public class Cuj {
return "DESKTOP_MODE_ENTER_FROM_OVERVIEW_MENU";
case CUJ_LAUNCHER_OVERVIEW_TASK_DISMISS:
return "LAUNCHER_OVERVIEW_TASK_DISMISS";
+ case CUJ_DESKTOP_MODE_CLOSE_TASK:
+ return "DESKTOP_MODE_CLOSE_TASK";
}
return "UNKNOWN";
}
diff --git a/core/java/com/android/internal/widget/NotificationProgressBar.java b/core/java/com/android/internal/widget/NotificationProgressBar.java
index 0d3c470b0e8a..973fd7ecf38b 100644
--- a/core/java/com/android/internal/widget/NotificationProgressBar.java
+++ b/core/java/com/android/internal/widget/NotificationProgressBar.java
@@ -47,6 +47,7 @@ import com.android.internal.widget.NotificationProgressDrawable.DrawablePoint;
import com.android.internal.widget.NotificationProgressDrawable.DrawableSegment;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -161,7 +162,7 @@ public final class NotificationProgressBar extends ProgressBar implements
final int progress = mProgressModel.getProgress();
final int progressMax = mProgressModel.getProgressMax();
- mParts = processAndConvertToViewParts(mProgressModel.getSegments(),
+ mParts = processModelAndConvertToViewParts(mProgressModel.getSegments(),
mProgressModel.getPoints(),
progress,
progressMax);
@@ -439,23 +440,107 @@ public final class NotificationProgressBar extends ProgressBar implements
return;
}
- mProgressDrawableParts = processAndConvertToDrawableParts(
+ final float segSegGap = mNotificationProgressDrawable.getSegSegGap();
+ final float segPointGap = mNotificationProgressDrawable.getSegPointGap();
+ final float pointRadius = mNotificationProgressDrawable.getPointRadius();
+ mProgressDrawableParts = processPartsAndConvertToDrawableParts(
mParts,
width,
- mNotificationProgressDrawable.getSegSegGap(),
- mNotificationProgressDrawable.getSegPointGap(),
- mNotificationProgressDrawable.getPointRadius(),
+ segSegGap,
+ segPointGap,
+ pointRadius,
mHasTrackerIcon
);
- Pair<List<DrawablePart>, Float> p = maybeStretchAndRescaleSegments(
- mParts,
- mProgressDrawableParts,
- mNotificationProgressDrawable.getSegmentMinWidth(),
- mNotificationProgressDrawable.getPointRadius(),
- getProgressFraction(),
- width,
- mProgressModel.isStyledByProgress(),
- mHasTrackerIcon ? 0F : mNotificationProgressDrawable.getSegSegGap());
+
+ final float segmentMinWidth = mNotificationProgressDrawable.getSegmentMinWidth();
+ final float progressFraction = getProgressFraction();
+ final boolean isStyledByProgress = mProgressModel.isStyledByProgress();
+ final float progressGap =
+ mHasTrackerIcon ? 0F : mNotificationProgressDrawable.getSegSegGap();
+ Pair<List<DrawablePart>, Float> p = null;
+ try {
+ p = maybeStretchAndRescaleSegments(
+ mParts,
+ mProgressDrawableParts,
+ segmentMinWidth,
+ pointRadius,
+ progressFraction,
+ width,
+ isStyledByProgress,
+ progressGap
+ );
+ } catch (NotEnoughWidthToFitAllPartsException ex) {
+ Log.w(TAG, "Failed to stretch and rescale segments", ex);
+ }
+
+ List<ProgressStyle.Segment> fallbackSegments = null;
+ if (p == null && mProgressModel.getSegments().size() > 1) {
+ Log.w(TAG, "Falling back to single segment");
+ try {
+ fallbackSegments = List.of(new ProgressStyle.Segment(getMax()).setColor(
+ mProgressModel.getSegmentsFallbackColor()
+ == NotificationProgressModel.INVALID_COLOR
+ ? mProgressModel.getSegments().getFirst().getColor()
+ : mProgressModel.getSegmentsFallbackColor()));
+ p = processModelAndConvertToFinalDrawableParts(
+ fallbackSegments,
+ mProgressModel.getPoints(),
+ mProgressModel.getProgress(),
+ getMax(),
+ width,
+ segSegGap,
+ segPointGap,
+ pointRadius,
+ mHasTrackerIcon,
+ segmentMinWidth,
+ isStyledByProgress
+ );
+ } catch (NotEnoughWidthToFitAllPartsException ex) {
+ Log.w(TAG, "Failed to stretch and rescale segments with single segment fallback",
+ ex);
+ }
+ }
+
+ if (p == null && !mProgressModel.getPoints().isEmpty()) {
+ Log.w(TAG, "Falling back to single segment and no points");
+ if (fallbackSegments == null) {
+ fallbackSegments = List.of(new ProgressStyle.Segment(getMax()).setColor(
+ mProgressModel.getSegmentsFallbackColor()
+ == NotificationProgressModel.INVALID_COLOR
+ ? mProgressModel.getSegments().getFirst().getColor()
+ : mProgressModel.getSegmentsFallbackColor()));
+ }
+ try {
+ p = processModelAndConvertToFinalDrawableParts(
+ fallbackSegments,
+ Collections.emptyList(),
+ mProgressModel.getProgress(),
+ getMax(),
+ width,
+ segSegGap,
+ segPointGap,
+ pointRadius,
+ mHasTrackerIcon,
+ segmentMinWidth,
+ isStyledByProgress
+ );
+ } catch (NotEnoughWidthToFitAllPartsException ex) {
+ Log.w(TAG,
+ "Failed to stretch and rescale segments with single segments and no points",
+ ex);
+ }
+ }
+
+ if (p == null) {
+ Log.w(TAG, "Falling back to no stretching and rescaling");
+ p = maybeSplitDrawableSegmentsByProgress(
+ mParts,
+ mProgressDrawableParts,
+ progressFraction,
+ width,
+ isStyledByProgress,
+ progressGap);
+ }
if (DEBUG) {
Log.d(TAG, "Updating NotificationProgressDrawable parts");
@@ -502,7 +587,11 @@ public final class NotificationProgressBar extends ProgressBar implements
int min = getMin();
int max = getMax();
int range = max - min;
- return range > 0 ? (getProgress() - min) / (float) range : 0;
+ return getProgressFraction(range, (getProgress() - min));
+ }
+
+ private static float getProgressFraction(int progressMax, int progress) {
+ return progressMax > 0 ? progress / (float) progressMax : 0;
}
/**
@@ -636,7 +725,7 @@ public final class NotificationProgressBar extends ProgressBar implements
* Processes the ProgressStyle data and convert to a list of {@code Part}.
*/
@VisibleForTesting
- public static List<Part> processAndConvertToViewParts(
+ public static List<Part> processModelAndConvertToViewParts(
List<ProgressStyle.Segment> segments,
List<ProgressStyle.Point> points,
int progress,
@@ -796,7 +885,7 @@ public final class NotificationProgressBar extends ProgressBar implements
* Processes the list of {@code Part} and convert to a list of {@code DrawablePart}.
*/
@VisibleForTesting
- public static List<DrawablePart> processAndConvertToDrawableParts(
+ public static List<DrawablePart> processPartsAndConvertToDrawableParts(
List<Part> parts,
float totalWidth,
float segSegGap,
@@ -823,7 +912,7 @@ public final class NotificationProgressBar extends ProgressBar implements
// Retract the end position to account for the padding and a point immediately
// after.
final float endOffset = getSegEndOffset(segment, nextPart, pointRadius, segPointGap,
- segSegGap, iPart == nParts - 2, totalWidth, hasTrackerIcon);
+ segSegGap, iPart == nParts - 2, hasTrackerIcon);
final float end = x + segWidth - endOffset;
drawableParts.add(new DrawableSegment(start, end, segment.mColor, segment.mFaded));
@@ -864,7 +953,7 @@ public final class NotificationProgressBar extends ProgressBar implements
}
private static float getSegEndOffset(Segment seg, Part nextPart, float pointRadius,
- float segPointGap, float segSegGap, boolean isSecondToLastPart, float totalWidth,
+ float segPointGap, float segSegGap, boolean isSecondToLastPart,
boolean hasTrackerIcon) {
if (nextPart == null) return 0F;
if (nextPart instanceof Segment nextSeg) {
@@ -894,7 +983,7 @@ public final class NotificationProgressBar extends ProgressBar implements
float totalWidth,
boolean isStyledByProgress,
float progressGap
- ) {
+ ) throws NotEnoughWidthToFitAllPartsException {
final List<DrawableSegment> drawableSegments = drawableParts
.stream()
.filter(DrawableSegment.class::isInstance)
@@ -920,16 +1009,8 @@ public final class NotificationProgressBar extends ProgressBar implements
}
if (totalExcessWidth < 0) {
- // TODO: b/372908709 - throw an error so that the caller can catch and go to fallback
- // option. (instead of return.)
- Log.w(TAG, "Not enough width to satisfy the minimum width for segments.");
- return maybeSplitDrawableSegmentsByProgress(
- parts,
- drawableParts,
- progressFraction,
- totalWidth,
- isStyledByProgress,
- progressGap);
+ throw new NotEnoughWidthToFitAllPartsException(
+ "Not enough width to satisfy the minimum width for segments.");
}
final int nParts = drawableParts.size();
@@ -1003,8 +1084,7 @@ public final class NotificationProgressBar extends ProgressBar implements
final int nParts = parts.size();
for (int iPart = 0; iPart < nParts; iPart++) {
final Part part = parts.get(iPart);
- if (!(part instanceof Segment)) continue;
- final Segment segment = (Segment) part;
+ if (!(part instanceof Segment segment)) continue;
if (startFraction == progressFraction) {
iPartFirstSegmentToStyle = iPart;
rescaledProgressX = segment.mStart;
@@ -1066,11 +1146,37 @@ public final class NotificationProgressBar extends ProgressBar implements
}
/**
+ * Processes the ProgressStyle data and convert to a pair of:
+ * - list of processed {@code DrawablePart}.
+ * - location of progress on the stretched and rescaled progress bar.
+ */
+ @VisibleForTesting
+ public static Pair<List<DrawablePart>, Float> processModelAndConvertToFinalDrawableParts(
+ List<ProgressStyle.Segment> segments,
+ List<ProgressStyle.Point> points,
+ int progress,
+ int progressMax,
+ float totalWidth,
+ float segSegGap,
+ float segPointGap,
+ float pointRadius,
+ boolean hasTrackerIcon,
+ float segmentMinWidth,
+ boolean isStyledByProgress
+ ) throws NotEnoughWidthToFitAllPartsException {
+ List<Part> parts = processModelAndConvertToViewParts(segments, points, progress,
+ progressMax);
+ List<DrawablePart> drawableParts = processPartsAndConvertToDrawableParts(parts, totalWidth,
+ segSegGap, segPointGap, pointRadius, hasTrackerIcon);
+ return maybeStretchAndRescaleSegments(parts, drawableParts, segmentMinWidth, pointRadius,
+ getProgressFraction(progressMax, progress), totalWidth, isStyledByProgress,
+ hasTrackerIcon ? 0F : segSegGap);
+ }
+
+ /**
* A part of the progress bar, which is either a {@link Segment} with non-zero length, or a
* {@link Point} with zero length.
*/
- // TODO: b/372908709 - maybe this should be made private? Only test the final
- // NotificationDrawable.Parts.
public interface Part {
}
@@ -1176,4 +1282,10 @@ public final class NotificationProgressBar extends ProgressBar implements
return Objects.hash(mColor);
}
}
+
+ public static class NotEnoughWidthToFitAllPartsException extends Exception {
+ public NotEnoughWidthToFitAllPartsException(String message) {
+ super(message);
+ }
+ }
}
diff --git a/core/java/com/android/internal/widget/NotificationProgressModel.java b/core/java/com/android/internal/widget/NotificationProgressModel.java
index e8cb37e8f19b..7eaf861f2af4 100644
--- a/core/java/com/android/internal/widget/NotificationProgressModel.java
+++ b/core/java/com/android/internal/widget/NotificationProgressModel.java
@@ -16,7 +16,6 @@
package com.android.internal.widget;
-
import android.annotation.ColorInt;
import android.annotation.FlaggedApi;
import android.annotation.NonNull;
@@ -45,24 +44,29 @@ import java.util.Objects;
*/
@FlaggedApi(Flags.FLAG_API_RICH_ONGOING)
public final class NotificationProgressModel {
- private static final int INVALID_INDETERMINATE_COLOR = Color.TRANSPARENT;
+ public static final int INVALID_COLOR = Color.TRANSPARENT;
private static final String KEY_SEGMENTS = "segments";
private static final String KEY_POINTS = "points";
private static final String KEY_PROGRESS = "progress";
private static final String KEY_IS_STYLED_BY_PROGRESS = "isStyledByProgress";
+ private static final String KEY_SEGMENTS_FALLBACK_COLOR = "segmentsFallColor";
private static final String KEY_INDETERMINATE_COLOR = "indeterminateColor";
private final List<Segment> mSegments;
private final List<Point> mPoints;
private final int mProgress;
private final boolean mIsStyledByProgress;
@ColorInt
+ private final int mSegmentsFallbackColor;
+
+ @ColorInt
private final int mIndeterminateColor;
public NotificationProgressModel(
@NonNull List<Segment> segments,
@NonNull List<Point> points,
int progress,
- boolean isStyledByProgress
+ boolean isStyledByProgress,
+ @ColorInt int segmentsFallbackColor
) {
Preconditions.checkArgument(progress >= 0);
Preconditions.checkArgument(!segments.isEmpty());
@@ -70,17 +74,19 @@ public final class NotificationProgressModel {
mPoints = points;
mProgress = progress;
mIsStyledByProgress = isStyledByProgress;
- mIndeterminateColor = INVALID_INDETERMINATE_COLOR;
+ mSegmentsFallbackColor = segmentsFallbackColor;
+ mIndeterminateColor = INVALID_COLOR;
}
public NotificationProgressModel(
@ColorInt int indeterminateColor
) {
- Preconditions.checkArgument(indeterminateColor != INVALID_INDETERMINATE_COLOR);
+ Preconditions.checkArgument(indeterminateColor != INVALID_COLOR);
mSegments = Collections.emptyList();
mPoints = Collections.emptyList();
mProgress = 0;
mIsStyledByProgress = false;
+ mSegmentsFallbackColor = INVALID_COLOR;
mIndeterminateColor = indeterminateColor;
}
@@ -105,12 +111,17 @@ public final class NotificationProgressModel {
}
@ColorInt
+ public int getSegmentsFallbackColor() {
+ return mSegmentsFallbackColor;
+ }
+
+ @ColorInt
public int getIndeterminateColor() {
return mIndeterminateColor;
}
public boolean isIndeterminate() {
- return mIndeterminateColor != INVALID_INDETERMINATE_COLOR;
+ return mIndeterminateColor != INVALID_COLOR;
}
/**
@@ -119,7 +130,7 @@ public final class NotificationProgressModel {
@NonNull
public Bundle toBundle() {
final Bundle bundle = new Bundle();
- if (mIndeterminateColor != INVALID_INDETERMINATE_COLOR) {
+ if (mIndeterminateColor != INVALID_COLOR) {
bundle.putInt(KEY_INDETERMINATE_COLOR, mIndeterminateColor);
} else {
bundle.putParcelableList(KEY_SEGMENTS,
@@ -128,6 +139,9 @@ public final class NotificationProgressModel {
Notification.ProgressStyle.getProgressPointsAsBundleList(mPoints));
bundle.putInt(KEY_PROGRESS, mProgress);
bundle.putBoolean(KEY_IS_STYLED_BY_PROGRESS, mIsStyledByProgress);
+ if (mSegmentsFallbackColor != INVALID_COLOR) {
+ bundle.putInt(KEY_SEGMENTS_FALLBACK_COLOR, mSegmentsFallbackColor);
+ }
}
return bundle;
}
@@ -138,8 +152,8 @@ public final class NotificationProgressModel {
@NonNull
public static NotificationProgressModel fromBundle(@NonNull Bundle bundle) {
final int indeterminateColor = bundle.getInt(KEY_INDETERMINATE_COLOR,
- INVALID_INDETERMINATE_COLOR);
- if (indeterminateColor != INVALID_INDETERMINATE_COLOR) {
+ INVALID_COLOR);
+ if (indeterminateColor != INVALID_COLOR) {
return new NotificationProgressModel(indeterminateColor);
} else {
final List<Segment> segments =
@@ -150,7 +164,10 @@ public final class NotificationProgressModel {
bundle.getParcelableArrayList(KEY_POINTS, Bundle.class));
final int progress = bundle.getInt(KEY_PROGRESS);
final boolean isStyledByProgress = bundle.getBoolean(KEY_IS_STYLED_BY_PROGRESS);
- return new NotificationProgressModel(segments, points, progress, isStyledByProgress);
+ final int segmentsFallbackColor = bundle.getInt(KEY_SEGMENTS_FALLBACK_COLOR,
+ INVALID_COLOR);
+ return new NotificationProgressModel(segments, points, progress, isStyledByProgress,
+ segmentsFallbackColor);
}
}
@@ -161,6 +178,7 @@ public final class NotificationProgressModel {
+ ", mPoints=" + mPoints
+ ", mProgress=" + mProgress
+ ", mIsStyledByProgress=" + mIsStyledByProgress
+ + ", mSegmentsFallbackColor=" + mSegmentsFallbackColor
+ ", mIndeterminateColor=" + mIndeterminateColor + "}";
}
@@ -171,6 +189,7 @@ public final class NotificationProgressModel {
final NotificationProgressModel that = (NotificationProgressModel) o;
return mProgress == that.mProgress
&& mIsStyledByProgress == that.mIsStyledByProgress
+ && mSegmentsFallbackColor == that.mSegmentsFallbackColor
&& mIndeterminateColor == that.mIndeterminateColor
&& Objects.equals(mSegments, that.mSegments)
&& Objects.equals(mPoints, that.mPoints);
@@ -182,6 +201,7 @@ public final class NotificationProgressModel {
mPoints,
mProgress,
mIsStyledByProgress,
+ mSegmentsFallbackColor,
mIndeterminateColor);
}
}
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 26b0d11955d2..f5f4e4332d28 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/CoreDocument.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/CoreDocument.java
@@ -62,7 +62,7 @@ public class CoreDocument {
// We also keep a more fine-grained BUILD number, exposed as
// ID_API_LEVEL = DOCUMENT_API_LEVEL + BUILD
- static final float BUILD = 0.2f;
+ static final float BUILD = 0.3f;
@NonNull ArrayList<Operation> mOperations = new ArrayList<>();
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 9a37a22390a2..0b6a3c415e4a 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/Operations.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/Operations.java
@@ -88,6 +88,8 @@ import com.android.internal.widget.remotecompose.core.operations.layout.TouchUpM
import com.android.internal.widget.remotecompose.core.operations.layout.animation.AnimationSpec;
import com.android.internal.widget.remotecompose.core.operations.layout.managers.BoxLayout;
import com.android.internal.widget.remotecompose.core.operations.layout.managers.CanvasLayout;
+import com.android.internal.widget.remotecompose.core.operations.layout.managers.CollapsibleColumnLayout;
+import com.android.internal.widget.remotecompose.core.operations.layout.managers.CollapsibleRowLayout;
import com.android.internal.widget.remotecompose.core.operations.layout.managers.ColumnLayout;
import com.android.internal.widget.remotecompose.core.operations.layout.managers.RowLayout;
import com.android.internal.widget.remotecompose.core.operations.layout.managers.StateLayout;
@@ -97,6 +99,7 @@ import com.android.internal.widget.remotecompose.core.operations.layout.modifier
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.GraphicsLayerModifierOperation;
+import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.HeightInModifierOperation;
import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.HeightModifierOperation;
import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.HostActionOperation;
import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.HostNamedActionOperation;
@@ -111,6 +114,7 @@ import com.android.internal.widget.remotecompose.core.operations.layout.modifier
import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ValueIntegerChangeActionOperation;
import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ValueIntegerExpressionChangeActionOperation;
import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ValueStringChangeActionOperation;
+import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.WidthInModifierOperation;
import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.WidthModifierOperation;
import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ZIndexModifierOperation;
import com.android.internal.widget.remotecompose.core.operations.utilities.IntMap;
@@ -208,7 +212,9 @@ public class Operations {
public static final int LAYOUT_CONTENT = 201;
public static final int LAYOUT_BOX = 202;
public static final int LAYOUT_ROW = 203;
+ public static final int LAYOUT_COLLAPSIBLE_ROW = 230;
public static final int LAYOUT_COLUMN = 204;
+ public static final int LAYOUT_COLLAPSIBLE_COLUMN = 233;
public static final int LAYOUT_CANVAS = 205;
public static final int LAYOUT_CANVAS_CONTENT = 207;
public static final int LAYOUT_TEXT = 208;
@@ -218,6 +224,8 @@ public class Operations {
public static final int MODIFIER_WIDTH = 16;
public static final int MODIFIER_HEIGHT = 67;
+ public static final int MODIFIER_WIDTH_IN = 231;
+ public static final int MODIFIER_HEIGHT_IN = 232;
public static final int MODIFIER_BACKGROUND = 55;
public static final int MODIFIER_BORDER = 107;
public static final int MODIFIER_PADDING = 58;
@@ -324,6 +332,8 @@ public class Operations {
map.put(MODIFIER_WIDTH, WidthModifierOperation::read);
map.put(MODIFIER_HEIGHT, HeightModifierOperation::read);
+ map.put(MODIFIER_WIDTH_IN, WidthInModifierOperation::read);
+ map.put(MODIFIER_HEIGHT_IN, HeightInModifierOperation::read);
map.put(MODIFIER_PADDING, PaddingModifierOperation::read);
map.put(MODIFIER_BACKGROUND, BackgroundModifierOperation::read);
map.put(MODIFIER_BORDER, BorderModifierOperation::read);
@@ -359,7 +369,9 @@ public class Operations {
map.put(LAYOUT_CONTENT, LayoutComponentContent::read);
map.put(LAYOUT_BOX, BoxLayout::read);
map.put(LAYOUT_COLUMN, ColumnLayout::read);
+ map.put(LAYOUT_COLLAPSIBLE_COLUMN, CollapsibleColumnLayout::read);
map.put(LAYOUT_ROW, RowLayout::read);
+ map.put(LAYOUT_COLLAPSIBLE_ROW, CollapsibleRowLayout::read);
map.put(LAYOUT_CANVAS, CanvasLayout::read);
map.put(LAYOUT_CANVAS_CONTENT, CanvasContent::read);
map.put(LAYOUT_TEXT, TextLayout::read);
diff --git a/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeBuffer.java b/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeBuffer.java
index 39cc997fadc2..1cb8fefde80c 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeBuffer.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeBuffer.java
@@ -86,6 +86,8 @@ 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.managers.BoxLayout;
import com.android.internal.widget.remotecompose.core.operations.layout.managers.CanvasLayout;
+import com.android.internal.widget.remotecompose.core.operations.layout.managers.CollapsibleColumnLayout;
+import com.android.internal.widget.remotecompose.core.operations.layout.managers.CollapsibleRowLayout;
import com.android.internal.widget.remotecompose.core.operations.layout.managers.ColumnLayout;
import com.android.internal.widget.remotecompose.core.operations.layout.managers.RowLayout;
import com.android.internal.widget.remotecompose.core.operations.layout.managers.StateLayout;
@@ -691,6 +693,12 @@ public class RemoteComposeBuffer {
return out;
}
+ /**
+ * Append a path to an existing path
+ *
+ * @param id id of the path to append to
+ * @param path the path to append
+ */
public void pathAppend(int id, float... path) {
PathAppend.apply(mBuffer, id, path);
}
@@ -772,8 +780,8 @@ public class RemoteComposeBuffer {
* @param text The text to be drawn
* @param start The index of the first character in text to draw
* @param end (end - 1) is the index of the last character in text to draw
- * @param contextStart
- * @param contextEnd
+ * @param contextStart the context start
+ * @param contextEnd the context end
* @param x The x-coordinate of the origin of the text being drawn
* @param y The y-coordinate of the baseline of the text being drawn
* @param rtl Draw RTTL
@@ -798,8 +806,8 @@ public class RemoteComposeBuffer {
* @param textId The text to be drawn
* @param start The index of the first character in text to draw
* @param end (end - 1) is the index of the last character in text to draw
- * @param contextStart
- * @param contextEnd
+ * @param contextStart the context start
+ * @param contextEnd the context end
* @param x The x-coordinate of the origin of the text being drawn
* @param y The y-coordinate of the baseline of the text being drawn
* @param rtl Draw RTTL
@@ -986,6 +994,11 @@ public class RemoteComposeBuffer {
///////////////////////////////////////////////////////////////////////////////////////////////
+ /**
+ * inflate the buffer into a list of operations
+ *
+ * @param operations the operations list to add to
+ */
public void inflateFromBuffer(@NonNull ArrayList<Operation> operations) {
mBuffer.setIndex(0);
while (mBuffer.available()) {
@@ -1001,6 +1014,12 @@ public class RemoteComposeBuffer {
}
}
+ /**
+ * Read the next operation from the buffer
+ *
+ * @param buffer The buff to read
+ * @param operations the operations list to add to
+ */
public static void readNextOperation(
@NonNull WireBuffer buffer, @NonNull ArrayList<Operation> operations) {
int opId = buffer.readByte();
@@ -1014,6 +1033,11 @@ public class RemoteComposeBuffer {
operation.read(buffer, operations);
}
+ /**
+ * copy the current buffer to a new one
+ *
+ * @return A new RemoteComposeBuffer
+ */
@NonNull
RemoteComposeBuffer copy() {
ArrayList<Operation> operations = new ArrayList<>();
@@ -1022,6 +1046,11 @@ public class RemoteComposeBuffer {
return copyFromOperations(operations, buffer);
}
+ /**
+ * add a set theme
+ *
+ * @param theme The theme to set
+ */
public void setTheme(int theme) {
Theme.apply(mBuffer, theme);
}
@@ -1040,6 +1069,14 @@ public class RemoteComposeBuffer {
return buffer;
}
+ /**
+ * Create a RemoteComposeBuffer from a file
+ *
+ * @param file A file
+ * @param remoteComposeState The RemoteComposeState
+ * @return A RemoteComposeBuffer
+ * @throws IOException if the file cannot be read
+ */
@NonNull
public RemoteComposeBuffer fromFile(
@NonNull File file, @NonNull RemoteComposeState remoteComposeState) throws IOException {
@@ -1048,6 +1085,13 @@ public class RemoteComposeBuffer {
return buffer;
}
+ /**
+ * Create a RemoteComposeBuffer from an InputStream
+ *
+ * @param inputStream An InputStream
+ * @param remoteComposeState The RemoteComposeState
+ * @return A RemoteComposeBuffer
+ */
@NonNull
public static RemoteComposeBuffer fromInputStream(
@NonNull InputStream inputStream, @NonNull RemoteComposeState remoteComposeState) {
@@ -1056,6 +1100,13 @@ public class RemoteComposeBuffer {
return buffer;
}
+ /**
+ * Create a RemoteComposeBuffer from an array of operations
+ *
+ * @param operations An array of operations
+ * @param buffer A RemoteComposeBuffer
+ * @return A RemoteComposeBuffer
+ */
@NonNull
RemoteComposeBuffer copyFromOperations(
@NonNull ArrayList<Operation> operations, @NonNull RemoteComposeBuffer buffer) {
@@ -1834,12 +1885,12 @@ public class RemoteComposeBuffer {
/**
* Add a marquee modifier
*
- * @param iterations
- * @param animationMode
- * @param repeatDelayMillis
- * @param initialDelayMillis
- * @param spacing
- * @param velocity
+ * @param iterations number of iterations
+ * @param animationMode animation mode
+ * @param repeatDelayMillis repeat delay
+ * @param initialDelayMillis initial delay
+ * @param spacing spacing between items
+ * @param velocity velocity of the marquee
*/
public void addModifierMarquee(
int iterations,
@@ -1861,14 +1912,21 @@ public class RemoteComposeBuffer {
/**
* Add a graphics layer
*
- * @param scaleX
- * @param scaleY
- * @param rotationX
- * @param rotationY
- * @param rotationZ
- * @param shadowElevation
- * @param transformOriginX
- * @param transformOriginY
+ * @param scaleX scale x
+ * @param scaleY scale y
+ * @param rotationX rotation in X
+ * @param rotationY rotation in Y
+ * @param rotationZ rotation in Z
+ * @param shadowElevation shadow elevation
+ * @param transformOriginX transform origin x
+ * @param transformOriginY transform origin y
+ * @param alpha alpha value
+ * @param cameraDistance camera distance
+ * @param blendMode blend mode
+ * @param spotShadowColorId spot shadow color
+ * @param ambientShadowColorId ambient shadow color
+ * @param colorFilterId id of color filter
+ * @param renderEffectId id of render effect
*/
public void addModifierGraphicsLayer(
float scaleX,
@@ -1923,14 +1981,32 @@ public class RemoteComposeBuffer {
ClipRectModifierOperation.apply(mBuffer);
}
+ /**
+ * add start of loop
+ *
+ * @param indexId id of the variable
+ * @param from start value
+ * @param step step value
+ * @param until stop value
+ */
public void addLoopStart(int indexId, float from, float step, float until) {
LoopOperation.apply(mBuffer, indexId, from, step, until);
}
+ /** Add a loop end */
public void addLoopEnd() {
ContainerEnd.apply(mBuffer);
}
+ /**
+ * add a state layout
+ *
+ * @param componentId id of the state
+ * @param animationId animation id
+ * @param horizontal horizontal alignment
+ * @param vertical vertical alignment
+ * @param indexId index of the state
+ */
public void addStateLayout(
int componentId, int animationId, int horizontal, int vertical, int indexId) {
mLastComponentId = getComponentId(componentId);
@@ -1966,6 +2042,22 @@ public class RemoteComposeBuffer {
}
/**
+ * Add a row start tag
+ *
+ * @param componentId component id
+ * @param animationId animation id
+ * @param horizontal horizontal alignment
+ * @param vertical vertical alignment
+ * @param spacedBy spacing between items
+ */
+ public void addCollapsibleRowStart(
+ int componentId, int animationId, int horizontal, int vertical, float spacedBy) {
+ mLastComponentId = getComponentId(componentId);
+ CollapsibleRowLayout.apply(
+ mBuffer, mLastComponentId, animationId, horizontal, vertical, spacedBy);
+ }
+
+ /**
* Add a column start tag
*
* @param componentId component id
@@ -1981,6 +2073,22 @@ public class RemoteComposeBuffer {
}
/**
+ * Add a column start tag
+ *
+ * @param componentId component id
+ * @param animationId animation id
+ * @param horizontal horizontal alignment
+ * @param vertical vertical alignment
+ * @param spacedBy spacing between items
+ */
+ public void addCollapsibleColumnStart(
+ int componentId, int animationId, int horizontal, int vertical, float spacedBy) {
+ mLastComponentId = getComponentId(componentId);
+ CollapsibleColumnLayout.apply(
+ mBuffer, mLastComponentId, animationId, horizontal, vertical, spacedBy);
+ }
+
+ /**
* Add a canvas start tag
*
* @param componentId component id
diff --git a/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeState.java b/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeState.java
index 43f8ea7dc78f..363b82bdf70c 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeState.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeState.java
@@ -67,8 +67,8 @@ public class RemoteComposeState implements CollectionsAccess {
* Get Object based on id. The system will cache things like bitmaps Paths etc. They can be
* accessed with this command
*
- * @param id
- * @return
+ * @param id the id of the object
+ * @return the object
*/
@Nullable
public Object getFromId(int id) {
@@ -78,8 +78,8 @@ public class RemoteComposeState implements CollectionsAccess {
/**
* true if the cache contain this id
*
- * @param id
- * @return
+ * @param id the id of the object
+ * @return true if the cache contain this id
*/
public boolean containsId(int id) {
return mIntDataMap.get(id) != null;
@@ -138,8 +138,8 @@ public class RemoteComposeState implements CollectionsAccess {
/**
* Get the path asociated with the Data
*
- * @param id
- * @return
+ * @param id of path
+ * @return path object
*/
public Object getPath(int id) {
return mPathMap.get(id);
@@ -180,7 +180,7 @@ public class RemoteComposeState implements CollectionsAccess {
/**
* Adds a data Override.
*
- * @param id
+ * @param id the id of the data
* @param item the new value
*/
public void overrideData(int id, @NonNull Object item) {
@@ -222,8 +222,8 @@ public class RemoteComposeState implements CollectionsAccess {
/**
* Adds a float Override.
*
- * @param id
- * @param value the new value
+ * @param id The id of the float
+ * @param value the override value
*/
public void overrideFloat(int id, float value) {
float previous = mFloatMap.get(id);
@@ -235,7 +235,12 @@ public class RemoteComposeState implements CollectionsAccess {
}
}
- /** Insert an item in the cache */
+ /**
+ * Insert an item in the cache
+ *
+ * @param item integer item to cache
+ * @return the id of the integer
+ */
public int cacheInteger(int item) {
int id = nextId();
mIntegerMap.put(id, item);
@@ -243,7 +248,12 @@ public class RemoteComposeState implements CollectionsAccess {
return id;
}
- /** Insert an integer item in the cache */
+ /**
+ * Insert an integer item in the cache
+ *
+ * @param id the id of the integer
+ * @param value the value of the integer
+ */
public void updateInteger(int id, int value) {
if (!mIntegerOverride[id]) {
int previous = mIntegerMap.get(id);
@@ -292,10 +302,10 @@ public class RemoteComposeState implements CollectionsAccess {
}
/**
- * Get the float value
+ * Get the color from the cache
*
- * @param id
- * @return
+ * @param id The id of the color
+ * @return The color
*/
public int getColor(int id) {
return mColorMap.get(id);
@@ -377,6 +387,9 @@ public class RemoteComposeState implements CollectionsAccess {
/**
* Method to determine if a cached value has been written to the documents WireBuffer based on
* its id.
+ *
+ * @param id id to check
+ * @return true if the value has not been written to the WireBuffer
*/
public boolean wasNotWritten(int id) {
return !mIntWrittenMap.get(id);
@@ -406,7 +419,7 @@ public class RemoteComposeState implements CollectionsAccess {
* Get the next available id 0 is normal (float,int,String,color) 1 is VARIABLES 2 is
* collections
*
- * @return
+ * @return return a unique id in the set
*/
public int nextId(int type) {
if (0 == type) {
@@ -418,7 +431,7 @@ public class RemoteComposeState implements CollectionsAccess {
/**
* Set the next id
*
- * @param id
+ * @param id set the id to increment off of
*/
public void setNextId(int id) {
mNextId = id;
@@ -440,8 +453,8 @@ public class RemoteComposeState implements CollectionsAccess {
/**
* Commands that listen to variables add themselves.
*
- * @param id
- * @param variableSupport
+ * @param id id of variable to listen to
+ * @param variableSupport command that listens to variable
*/
public void listenToVar(int id, @NonNull VariableSupport variableSupport) {
add(id, variableSupport);
@@ -450,8 +463,8 @@ public class RemoteComposeState implements CollectionsAccess {
/**
* Is any command listening to this variable
*
- * @param id
- * @return
+ * @param id The Variable id
+ * @return true if any command is listening to this variable
*/
public boolean hasListener(int id) {
return mVarListeners.get(id) != null;
@@ -460,8 +473,8 @@ public class RemoteComposeState implements CollectionsAccess {
/**
* List of Commands that need to be updated
*
- * @param context
- * @return
+ * @param context The context
+ * @return The number of ops to update
*/
public int getOpsToUpdate(@NonNull RemoteContext context) {
if (mVarListeners.get(RemoteContext.ID_CONTINUOUS_SEC) != null) {
@@ -479,7 +492,7 @@ public class RemoteComposeState implements CollectionsAccess {
/**
* Set the width of the overall document on screen.
*
- * @param width
+ * @param width the width of the document in pixels
*/
public void setWindowWidth(float width) {
updateFloat(RemoteContext.ID_WINDOW_WIDTH, width);
@@ -488,12 +501,18 @@ public class RemoteComposeState implements CollectionsAccess {
/**
* Set the width of the overall document on screen.
*
- * @param height
+ * @param height the height of the document in pixels
*/
public void setWindowHeight(float height) {
updateFloat(RemoteContext.ID_WINDOW_HEIGHT, height);
}
+ /**
+ * Add an array access
+ *
+ * @param id The id of the array Access
+ * @param collection The array access
+ */
public void addCollection(int id, @NonNull ArrayAccess collection) {
mCollectionMap.put(id & 0xFFFFF, collection);
}
@@ -513,10 +532,22 @@ public class RemoteComposeState implements CollectionsAccess {
return mCollectionMap.get(id & 0xFFFFF).getId(index);
}
+ /**
+ * adds a DataMap to the cache
+ *
+ * @param id The id of the data map
+ * @param map The data map
+ */
public void putDataMap(int id, @NonNull DataMap map) {
mDataMapMap.put(id, map);
}
+ /**
+ * Get the DataMap asociated with the id
+ *
+ * @param id the id of the DataMap
+ * @return the DataMap
+ */
public @Nullable DataMap getDataMap(int id) {
return mDataMapMap.get(id);
}
@@ -526,15 +557,32 @@ public class RemoteComposeState implements CollectionsAccess {
return mCollectionMap.get(id & 0xFFFFF).getLength();
}
+ /**
+ * sets the RemoteContext
+ *
+ * @param context the context
+ */
public void setContext(@NonNull RemoteContext context) {
mRemoteContext = context;
mRemoteContext.clearLastOpCount();
}
+ /**
+ * Add an object to the cache. Uses the id for the item and adds it to the cache based
+ *
+ * @param id the id of the object
+ * @param value the object
+ */
public void updateObject(int id, @NonNull Object value) {
mObjectMap.put(id, value);
}
+ /**
+ * Get an object from the cache
+ *
+ * @param id The id of the object
+ * @return The object
+ */
public @Nullable Object getObject(int id) {
return mObjectMap.get(id);
}
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 23c362830713..36e4ec1ff303 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/RemoteContext.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/RemoteContext.java
@@ -47,7 +47,7 @@ public abstract class RemoteContext {
new RemoteComposeState(); // todo, is this a valid use of RemoteComposeState -- bbade@
@Nullable protected PaintContext mPaintContext = null;
- protected float mDensity = 2.75f;
+ protected float mDensity = Float.NaN;
@NonNull ContextMode mMode = ContextMode.UNSET;
@@ -77,7 +77,7 @@ public abstract class RemoteContext {
* @param density
*/
public void setDensity(float density) {
- if (density > 0) {
+ if (!Float.isNaN(density) && density > 0) {
mDensity = density;
}
}
@@ -234,23 +234,60 @@ public abstract class RemoteContext {
*/
public abstract void addCollection(int id, @NonNull ArrayAccess collection);
+ /**
+ * put DataMap under an id
+ *
+ * @param id the id of the DataMap
+ * @param map the DataMap
+ */
public abstract void putDataMap(int id, @NonNull DataMap map);
+ /**
+ * Get a DataMap given an id
+ *
+ * @param id the id of the DataMap
+ * @return the DataMap
+ */
public abstract @Nullable DataMap getDataMap(int id);
+ /**
+ * Run an action
+ *
+ * @param id the id of the action
+ * @param metadata the metadata of the action
+ */
public abstract void runAction(int id, @NonNull String metadata);
// TODO: we might add an interface to group all valid parameter types
+
+ /**
+ * Run an action with a named parameter
+ *
+ * @param textId the text id of the action
+ * @param value the value of the parameter
+ */
public abstract void runNamedAction(int textId, Object value);
+ /**
+ * Put an object under an id
+ *
+ * @param mId the id of the object
+ * @param command the object
+ */
public abstract void putObject(int mId, @NonNull Object command);
+ /**
+ * Get an object given an id
+ *
+ * @param mId the id of the object
+ * @return the object
+ */
public abstract @Nullable Object getObject(int mId);
/**
* Add a touch listener to the context
*
- * @param touchExpression
+ * @param touchExpression the touch expression
*/
public void addTouchListener(TouchListener touchExpression) {}
@@ -668,11 +705,24 @@ public abstract class RemoteContext {
///////////////////////////////////////////////////////////////////////////////////////////////
// Click handling
///////////////////////////////////////////////////////////////////////////////////////////////
+
+ /**
+ * Is this a time id float
+ *
+ * @param fl the floatId to test
+ * @return true if it is a time id
+ */
public static boolean isTime(float fl) {
int value = Utils.idFromNan(fl);
return value >= ID_CONTINUOUS_SEC && value <= ID_DAY_OF_MONTH;
}
+ /**
+ * get the time from a float id that indicates a type of time
+ *
+ * @param fl id of the type of time information requested
+ * @return various time information such as seconds or min
+ */
public static float getTime(float fl) {
LocalDateTime dateTime =
LocalDateTime.now(ZoneId.systemDefault()); // TODO, pass in a timezone explicitly?
@@ -716,6 +766,17 @@ public abstract class RemoteContext {
return fl;
}
+ /**
+ * Add a click area to the doc
+ *
+ * @param id the id of the click area
+ * @param contentDescription the content description of the click area
+ * @param left the left bounds of the click area
+ * @param top the top bounds of the click area
+ * @param right the right bounds of the click area
+ * @param bottom the
+ * @param metadataId the id of the metadata string
+ */
public abstract void addClickArea(
int id,
int contentDescription,
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/ClipPath.java b/core/java/com/android/internal/widget/remotecompose/core/operations/ClipPath.java
index b55f25c911fe..06ef9979a267 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/ClipPath.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/ClipPath.java
@@ -102,6 +102,12 @@ public class ClipPath extends PaintOperation {
return OP_CODE;
}
+ /**
+ * Apply this operation to the buffer
+ *
+ * @param buffer the buffer to apply the operation to
+ * @param id the id of the path
+ */
public static void apply(@NonNull WireBuffer buffer, int id) {
buffer.start(OP_CODE);
buffer.writeInt(id);
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 ac6271c6328e..7a72b109b2a8 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
@@ -70,6 +70,13 @@ public class DataListFloat extends Operation implements VariableSupport, ArrayAc
return "DataListFloat[" + Utils.idString(mId) + "] " + Arrays.toString(mValues);
}
+ /**
+ * Write this operation to the buffer
+ *
+ * @param buffer the buffer to apply the operation to
+ * @param id the id of the array
+ * @param values the values of the array
+ */
public static void apply(@NonNull WireBuffer buffer, int id, @NonNull float[] values) {
buffer.start(OP_CODE);
buffer.writeInt(id);
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 47cbff36d492..7e29620ec104 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
@@ -62,6 +62,13 @@ public class DataListIds extends Operation implements VariableSupport, ArrayAcce
return "map[" + Utils.idString(mId) + "] \"" + Arrays.toString(mIds) + "\"";
}
+ /**
+ * Write this operation to the buffer
+ *
+ * @param buffer the buffer to apply the operation to
+ * @param id the id of the array
+ * @param ids the values of the array
+ */
public static void apply(@NonNull WireBuffer buffer, int id, @NonNull int[] ids) {
buffer.start(OP_CODE);
buffer.writeInt(id);
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DataMapIds.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DataMapIds.java
index ff85721027f7..33752e0b2134 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DataMapIds.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DataMapIds.java
@@ -89,6 +89,15 @@ public class DataMapIds extends Operation {
return builder.toString();
}
+ /**
+ * Write this operation to the buffer
+ *
+ * @param buffer the buffer to apply the operation to
+ * @param id the id
+ * @param names the names of the variables
+ * @param type the types of the variables
+ * @param ids the ids of the variables
+ */
public static void apply(
@NonNull WireBuffer buffer,
int id,
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 c1e2e662ca80..7f1ba6f94065 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase2.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase2.java
@@ -31,8 +31,8 @@ import java.util.List;
/** Base class for commands that take 3 float */
public abstract class DrawBase2 extends PaintOperation implements VariableSupport {
@NonNull protected String mName = "DrawRectBase";
- float mV1;
- float mV2;
+ protected float mV1;
+ protected float mV2;
float mValue1;
float mValue2;
@@ -76,6 +76,13 @@ public abstract class DrawBase2 extends PaintOperation implements VariableSuppor
return mName + " " + floatToString(mV1) + " " + floatToString(mV2);
}
+ /**
+ * Read this operation and add it to the list of operations
+ *
+ * @param maker the maker of the operation
+ * @param buffer the buffer to read
+ * @param operations the list of operations to add to
+ */
public static void read(
@NonNull Maker maker, @NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
float v1 = buffer.readFloat();
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 6fedea3245a2..a6bfda8beccd 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
@@ -92,6 +92,13 @@ public abstract class DrawBase3 extends PaintOperation implements VariableSuppor
+ floatToString(mV3);
}
+ /**
+ * Read this operation and add it to the list of operations
+ *
+ * @param maker the maker of the operation
+ * @param buffer the buffer to read
+ * @param operations the list of operations to add to
+ */
public static void read(
@NonNull Maker maker, @NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
float v1 = buffer.readFloat();
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 aa9cc68e6552..1e96bcd9cebf 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
@@ -102,6 +102,13 @@ public abstract class DrawBase4 extends PaintOperation implements VariableSuppor
+ floatToString(mY2Value, mY2);
}
+ /**
+ * Read this operation and add it to the list of operations
+ *
+ * @param maker the maker of the operation
+ * @param buffer the buffer to read
+ * @param operations the list of operations to add to
+ */
public static void read(
@NonNull Maker maker, @NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
float v1 = buffer.readFloat();
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 64c2730e5f9a..bc5904584527 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
@@ -116,6 +116,13 @@ public abstract class DrawBase6 extends PaintOperation implements VariableSuppor
DrawBase6 create(float v1, float v2, float v3, float v4, float v5, float v6);
}
+ /**
+ * Read this operation and add it to the list of operations
+ *
+ * @param build interface to construct the component
+ * @param buffer the buffer to read from
+ * @param operations the list of operations to add to
+ */
public static void read(
@NonNull Maker build, @NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
float sv1 = buffer.readFloat();
@@ -132,13 +139,13 @@ public abstract class DrawBase6 extends PaintOperation implements VariableSuppor
/**
* writes out a the operation to the buffer.
*
- * @param v1
- * @param v2
- * @param v3
- * @param v4
- * @param v5
- * @param v6
- * @return
+ * @param v1 the first parameter
+ * @param v2 the second parameter
+ * @param v3 the third parameter
+ * @param v4 the fourth parameter
+ * @param v5 the fifth parameter
+ * @param v6 the sixth parameter
+ * @return the operation
*/
@Nullable
public Operation construct(float v1, float v2, float v3, float v4, float v5, float v6) {
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmap.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmap.java
index cdb527dee460..40d3bede0912 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmap.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmap.java
@@ -137,6 +137,17 @@ public class DrawBitmap extends PaintOperation implements VariableSupport {
return OP_CODE;
}
+ /**
+ * Writes out the operation to the buffer
+ *
+ * @param buffer the buffer to write to
+ * @param id the id of the Bitmap
+ * @param left left most x coordinate
+ * @param top top most y coordinate
+ * @param right right most x coordinate
+ * @param bottom bottom most y coordinate
+ * @param descriptionId string id of the description
+ */
public static void apply(
@NonNull WireBuffer buffer,
int id,
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmapInt.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmapInt.java
index 638fe148d746..013dd1ae9db8 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmapInt.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmapInt.java
@@ -131,6 +131,21 @@ public class DrawBitmapInt extends PaintOperation implements AccessibleComponent
return OP_CODE;
}
+ /**
+ * Draw a bitmap using integer coordinates
+ *
+ * @param buffer the buffer to write to
+ * @param imageId the id of the bitmap
+ * @param srcLeft the left most pixel in the bitmap
+ * @param srcTop the top most pixel in the bitmap
+ * @param srcRight the right most pixel in the bitmap
+ * @param srcBottom the bottom most pixel in the bitmap
+ * @param dstLeft the left most pixel in the destination
+ * @param dstTop the top most pixel in the destination
+ * @param dstRight the right most pixel in the destination
+ * @param dstBottom the bottom most pixel in the destination
+ * @param cdId the content discription id
+ */
public static void apply(
@NonNull WireBuffer buffer,
int imageId,
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmapScaled.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmapScaled.java
index d6467c926747..e1070f97d5aa 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmapScaled.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmapScaled.java
@@ -217,6 +217,23 @@ public class DrawBitmapScaled extends PaintOperation
return OP_CODE;
}
+ /**
+ * Draw a bitmap using integer coordinates
+ *
+ * @param buffer the buffer to write to
+ * @param imageId the id of the image
+ * @param srcLeft the left most pixel in the image to draw
+ * @param srcTop the right most pixel in the image to draw
+ * @param srcRight the right most pixel in the image to draw
+ * @param srcBottom the bottom most pixel in the image to draw
+ * @param dstLeft the left most pixel in the destination
+ * @param dstTop the top most pixel in the destination
+ * @param dstRight the right most pixel in the destination
+ * @param dstBottom the bottom most pixel in the destination
+ * @param scaleType the type of scale operation
+ * @param scaleFactor the scalefactor to use with fixed scale
+ * @param cdId the content discription id
+ */
public static void apply(
@NonNull WireBuffer buffer,
int imageId,
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawPath.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawPath.java
index 398cf4892e12..db9c4d3efafa 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawPath.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawPath.java
@@ -81,6 +81,12 @@ public class DrawPath extends PaintOperation {
return Operations.DRAW_PATH;
}
+ /**
+ * Draw a path
+ *
+ * @param buffer the buffer to write to
+ * @param id the id of the path
+ */
public static void apply(@NonNull WireBuffer buffer, int id) {
buffer.start(Operations.DRAW_PATH);
buffer.writeInt(id);
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 86f3c992f2fb..3ab4a87c614c 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
@@ -117,6 +117,15 @@ public class DrawTextOnPath extends PaintOperation implements VariableSupport {
return Operations.DRAW_TEXT_ON_PATH;
}
+ /**
+ * add a draw text on path operation to the buffer
+ *
+ * @param buffer the buffer to add to
+ * @param textId the id of the text string
+ * @param pathId the id of the path
+ * @param hOffset the horizontal offset to position the string
+ * @param vOffset the vertical offset to position the string
+ */
public static void apply(
@NonNull WireBuffer buffer, int textId, int pathId, float hOffset, float vOffset) {
buffer.start(OP_CODE);
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTweenPath.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTweenPath.java
index d4d4a5ecf6b9..e2883949022c 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTweenPath.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTweenPath.java
@@ -127,6 +127,16 @@ public class DrawTweenPath extends PaintOperation implements VariableSupport {
return Operations.DRAW_TWEEN_PATH;
}
+ /**
+ * add a draw tween path operation to the buffer
+ *
+ * @param buffer the buffer to add to
+ * @param path1Id the first path
+ * @param path2Id the second path
+ * @param tween the amount of the tween
+ * @param start the start sub range to draw
+ * @param stop the end of the sub range to draw
+ */
public static void apply(
@NonNull WireBuffer buffer,
int path1Id,
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixRestore.java b/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixRestore.java
index 044430d1e3c1..66daa13dd21c 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixRestore.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixRestore.java
@@ -74,6 +74,11 @@ public class MatrixRestore extends PaintOperation {
return OP_CODE;
}
+ /**
+ * add a matrix restore operation to the buffer
+ *
+ * @param buffer the buffer to add to
+ */
public static void apply(@NonNull WireBuffer buffer) {
buffer.start(OP_CODE);
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixSave.java b/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixSave.java
index aec316aea361..ec918e8260b9 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixSave.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixSave.java
@@ -72,6 +72,11 @@ public class MatrixSave extends PaintOperation {
return OP_CODE;
}
+ /**
+ * add a matrix save operation to the buffer
+ *
+ * @param buffer the buffer to add to
+ */
public static void apply(@NonNull WireBuffer buffer) {
buffer.start(Operations.MATRIX_SAVE);
}
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 daf2c5502c5d..f756b76b86c3 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
@@ -32,6 +32,7 @@ import com.android.internal.widget.remotecompose.core.operations.paint.PaintBund
import java.util.List;
+/** Paint data operation */
public class PaintData extends PaintOperation implements VariableSupport {
private static final int OP_CODE = Operations.PAINT_VALUES;
private static final String CLASS_NAME = "PaintData";
@@ -80,6 +81,12 @@ public class PaintData extends PaintOperation implements VariableSupport {
return OP_CODE;
}
+ /**
+ * add a paint data to the buffer
+ *
+ * @param buffer the buffer to add to
+ * @param paintBundle the paint bundle
+ */
public static void apply(@NonNull WireBuffer buffer, @NonNull PaintBundle paintBundle) {
buffer.start(Operations.PAINT_VALUES);
paintBundle.writeBundle(buffer);
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 7ff879e41cac..e7cce03f0c4b 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
@@ -101,6 +101,7 @@ public class PathAppend extends PaintOperation implements VariableSupport {
public static final int CUBIC = 14;
public static final int CLOSE = 15;
public static final int DONE = 16;
+ public static final int RESET = 17;
public static final float MOVE_NAN = Utils.asNan(MOVE);
public static final float LINE_NAN = Utils.asNan(LINE);
public static final float QUADRATIC_NAN = Utils.asNan(QUADRATIC);
@@ -108,6 +109,7 @@ public class PathAppend extends PaintOperation implements VariableSupport {
public static final float CUBIC_NAN = Utils.asNan(CUBIC);
public static final float CLOSE_NAN = Utils.asNan(CLOSE);
public static final float DONE_NAN = Utils.asNan(DONE);
+ public static final float RESET_NAN = Utils.asNan(RESET);
/**
* The name of the class
@@ -128,6 +130,14 @@ public class PathAppend extends PaintOperation implements VariableSupport {
return OP_CODE;
}
+ /**
+ * add a path append operation to the buffer. With PathCreate allows you create a path
+ * dynamically
+ *
+ * @param buffer add the data to this buffer
+ * @param id id of the path
+ * @param data the path data to append
+ */
public static void apply(@NonNull WireBuffer buffer, int id, @NonNull float[] data) {
buffer.start(OP_CODE);
buffer.writeInt(id);
@@ -175,6 +185,10 @@ public class PathAppend extends PaintOperation implements VariableSupport {
public void apply(@NonNull RemoteContext context) {
float[] data = context.getPathData(mInstanceId);
float[] out = mOutputPath;
+ if (Float.floatToRawIntBits(out[0]) == Float.floatToRawIntBits(RESET_NAN)) {
+ context.loadPathData(mInstanceId, new float[0]);
+ return;
+ }
if (data != null) {
out = new float[data.length + mOutputPath.length];
@@ -190,6 +204,12 @@ public class PathAppend extends PaintOperation implements VariableSupport {
context.loadPathData(mInstanceId, out);
}
+ /**
+ * Convert a path to a string
+ *
+ * @param path the path to convert
+ * @return text representation of path
+ */
@NonNull
public static String pathString(@Nullable float[] path) {
if (path == null) {
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 75562cd8fb4c..1f76639b1b1f 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
@@ -131,6 +131,14 @@ public class PathCreate extends PaintOperation implements VariableSupport {
return OP_CODE;
}
+ /**
+ * add a create path operation
+ *
+ * @param buffer buffer to add to
+ * @param id the id of the path
+ * @param startX the start x of the path (moveTo x,y)
+ * @param startY the start y of the path (moveTo x,y)
+ */
public static void apply(@NonNull WireBuffer buffer, int id, float startX, float startY) {
buffer.start(OP_CODE);
buffer.writeInt(id);
@@ -165,6 +173,12 @@ public class PathCreate extends PaintOperation implements VariableSupport {
.field(FLOAT, "startX", "initial start y");
}
+ /**
+ * convert a path to a string
+ *
+ * @param path path to convert (expressed as an array of floats)
+ * @return the text representing the path
+ */
@NonNull
public static String pathString(@Nullable float[] path) {
if (path == null) {
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/PathData.java b/core/java/com/android/internal/widget/remotecompose/core/operations/PathData.java
index 85a01fc7cbc7..45d99a716443 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/PathData.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/PathData.java
@@ -131,6 +131,13 @@ public class PathData extends Operation implements VariableSupport {
return OP_CODE;
}
+ /**
+ * add a create path operation
+ *
+ * @param buffer buffer to add to
+ * @param id the id of the path
+ * @param data the path
+ */
public static void apply(@NonNull WireBuffer buffer, int id, @NonNull float[] data) {
buffer.start(Operations.DATA_PATH);
buffer.writeInt(id);
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 d48de37996ee..5788d8f4da64 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
@@ -73,6 +73,13 @@ public class TextData extends Operation implements SerializableToString {
return OP_CODE;
}
+ /**
+ * add a text data operation
+ *
+ * @param buffer buffer to add to
+ * @param textId the id for the text
+ * @param text the data to encode
+ */
public static void apply(@NonNull WireBuffer buffer, int textId, @NonNull String text) {
buffer.start(OP_CODE);
buffer.writeInt(textId);
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/TextLength.java b/core/java/com/android/internal/widget/remotecompose/core/operations/TextLength.java
index 37ea567f5913..a6570a371f15 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/TextLength.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/TextLength.java
@@ -50,6 +50,11 @@ public class TextLength extends Operation {
return CLASS_NAME + "[" + mLengthId + "] = " + mTextId;
}
+ /**
+ * The name of the class
+ *
+ * @return the name
+ */
public static @NonNull String name() {
return CLASS_NAME;
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/TextMeasure.java b/core/java/com/android/internal/widget/remotecompose/core/operations/TextMeasure.java
index d51b38924126..58cd68e2a5db 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/TextMeasure.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/TextMeasure.java
@@ -66,6 +66,11 @@ public class TextMeasure extends PaintOperation {
return "FloatConstant[" + mId + "] = " + mTextId + " " + mType;
}
+ /**
+ * The name of the class
+ *
+ * @return the name
+ */
public static @NonNull String name() {
return CLASS_NAME;
}
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 e71cb9a51830..dcd334822010 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
@@ -36,10 +36,12 @@ import com.android.internal.widget.remotecompose.core.operations.layout.modifier
import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ComponentVisibilityOperation;
import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.DimensionModifierOperation;
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;
import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ModifierOperation;
import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.PaddingModifierOperation;
import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ScrollModifierOperation;
+import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.WidthInModifierOperation;
import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.WidthModifierOperation;
import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ZIndexModifierOperation;
@@ -204,6 +206,9 @@ public class LayoutComponent extends Component {
mPaddingRight = 0f;
mPaddingBottom = 0f;
+ WidthInModifierOperation widthInConstraints = null;
+ HeightInModifierOperation heightInConstraints = null;
+
for (OperationInterface op : mComponentModifiers.getList()) {
if (op instanceof PaddingModifierOperation) {
// We are accumulating padding modifiers to compute the margin
@@ -221,6 +226,10 @@ public class LayoutComponent extends Component {
mWidthModifier = (WidthModifierOperation) op;
} else if (op instanceof HeightModifierOperation && mHeightModifier == null) {
mHeightModifier = (HeightModifierOperation) op;
+ } else if (op instanceof WidthInModifierOperation) {
+ widthInConstraints = (WidthInModifierOperation) op;
+ } else if (op instanceof HeightInModifierOperation) {
+ heightInConstraints = (HeightInModifierOperation) op;
} else if (op instanceof ZIndexModifierOperation) {
mZIndexModifier = (ZIndexModifierOperation) op;
} else if (op instanceof GraphicsLayerModifierOperation) {
@@ -241,6 +250,12 @@ public class LayoutComponent extends Component {
if (mHeightModifier == null) {
mHeightModifier = new HeightModifierOperation(DimensionModifierOperation.Type.WRAP);
}
+ if (widthInConstraints != null) {
+ mWidthModifier.setWidthIn(widthInConstraints);
+ }
+ if (heightInConstraints != null) {
+ mHeightModifier.setHeightIn(heightInConstraints);
+ }
setWidth(computeModifierDefinedWidth(null));
setHeight(computeModifierDefinedHeight(null));
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/CollapsibleColumnLayout.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/CollapsibleColumnLayout.java
new file mode 100644
index 000000000000..afc41b1873ef
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/CollapsibleColumnLayout.java
@@ -0,0 +1,175 @@
+/*
+ * 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.internal.widget.remotecompose.core.operations.layout.managers;
+
+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.WireBuffer;
+import com.android.internal.widget.remotecompose.core.operations.layout.Component;
+import com.android.internal.widget.remotecompose.core.operations.layout.measure.ComponentMeasure;
+import com.android.internal.widget.remotecompose.core.operations.layout.measure.MeasurePass;
+import com.android.internal.widget.remotecompose.core.operations.layout.measure.Size;
+
+import java.util.List;
+
+public class CollapsibleColumnLayout extends ColumnLayout {
+
+ public CollapsibleColumnLayout(
+ @Nullable Component parent,
+ int componentId,
+ int animationId,
+ float x,
+ float y,
+ float width,
+ float height,
+ int horizontalPositioning,
+ int verticalPositioning,
+ float spacedBy) {
+ super(
+ parent,
+ componentId,
+ animationId,
+ x,
+ y,
+ width,
+ height,
+ horizontalPositioning,
+ verticalPositioning,
+ spacedBy);
+ }
+
+ public CollapsibleColumnLayout(
+ @Nullable Component parent,
+ int componentId,
+ int animationId,
+ int horizontalPositioning,
+ int verticalPositioning,
+ float spacedBy) {
+ super(
+ parent,
+ componentId,
+ animationId,
+ horizontalPositioning,
+ verticalPositioning,
+ spacedBy);
+ }
+
+ @NonNull
+ @Override
+ protected String getSerializedName() {
+ return "COLLAPSIBLE_COLUMN";
+ }
+
+ /**
+ * The OP_CODE for this command
+ *
+ * @return the opcode
+ */
+ public static int id() {
+ return Operations.LAYOUT_COLLAPSIBLE_COLUMN;
+ }
+
+ /**
+ * Write the operation to the buffer
+ *
+ * @param buffer wire buffer
+ * @param componentId component id
+ * @param animationId animation id (-1 if not set)
+ * @param horizontalPositioning horizontal positioning rules
+ * @param verticalPositioning vertical positioning rules
+ * @param spacedBy spaced by value
+ */
+ public static void apply(
+ @NonNull WireBuffer buffer,
+ int componentId,
+ int animationId,
+ int horizontalPositioning,
+ int verticalPositioning,
+ float spacedBy) {
+ buffer.start(id());
+ buffer.writeInt(componentId);
+ buffer.writeInt(animationId);
+ buffer.writeInt(horizontalPositioning);
+ buffer.writeInt(verticalPositioning);
+ buffer.writeFloat(spacedBy);
+ }
+
+ /**
+ * 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 componentId = buffer.readInt();
+ int animationId = buffer.readInt();
+ int horizontalPositioning = buffer.readInt();
+ int verticalPositioning = buffer.readInt();
+ float spacedBy = buffer.readFloat();
+ operations.add(
+ new CollapsibleColumnLayout(
+ null,
+ componentId,
+ animationId,
+ horizontalPositioning,
+ verticalPositioning,
+ spacedBy));
+ }
+
+ @Override
+ protected boolean hasVerticalIntrinsicDimension() {
+ return true;
+ }
+
+ @Override
+ public void computeWrapSize(
+ @NonNull PaintContext context,
+ float maxWidth,
+ float maxHeight,
+ boolean horizontalWrap,
+ boolean verticalWrap,
+ @NonNull MeasurePass measure,
+ @NonNull Size size) {
+ super.computeWrapSize(
+ context, maxWidth, Float.MAX_VALUE, horizontalWrap, verticalWrap, measure, size);
+ }
+
+ @Override
+ public boolean applyVisibility(
+ float selfWidth, float selfHeight, @NonNull MeasurePass measure) {
+ float childrenWidth = 0f;
+ float childrenHeight = 0f;
+ boolean changedVisibility = false;
+ for (Component child : mChildrenComponents) {
+ ComponentMeasure childMeasure = measure.get(child);
+ if (childMeasure.getVisibility() == Visibility.GONE) {
+ continue;
+ }
+ if (childrenHeight + childMeasure.getH() > selfHeight) {
+ childMeasure.setVisibility(Visibility.GONE);
+ changedVisibility = true;
+ } else {
+ childrenHeight += childMeasure.getH();
+ childrenWidth = Math.max(childrenWidth, childMeasure.getW());
+ }
+ }
+ return changedVisibility;
+ }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/CollapsibleRowLayout.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/CollapsibleRowLayout.java
new file mode 100644
index 000000000000..0e7eb8676f46
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/CollapsibleRowLayout.java
@@ -0,0 +1,175 @@
+/*
+ * 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.internal.widget.remotecompose.core.operations.layout.managers;
+
+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.WireBuffer;
+import com.android.internal.widget.remotecompose.core.operations.layout.Component;
+import com.android.internal.widget.remotecompose.core.operations.layout.measure.ComponentMeasure;
+import com.android.internal.widget.remotecompose.core.operations.layout.measure.MeasurePass;
+import com.android.internal.widget.remotecompose.core.operations.layout.measure.Size;
+
+import java.util.List;
+
+public class CollapsibleRowLayout extends RowLayout {
+
+ public CollapsibleRowLayout(
+ @Nullable Component parent,
+ int componentId,
+ int animationId,
+ float x,
+ float y,
+ float width,
+ float height,
+ int horizontalPositioning,
+ int verticalPositioning,
+ float spacedBy) {
+ super(
+ parent,
+ componentId,
+ animationId,
+ x,
+ y,
+ width,
+ height,
+ horizontalPositioning,
+ verticalPositioning,
+ spacedBy);
+ }
+
+ public CollapsibleRowLayout(
+ @Nullable Component parent,
+ int componentId,
+ int animationId,
+ int horizontalPositioning,
+ int verticalPositioning,
+ float spacedBy) {
+ super(
+ parent,
+ componentId,
+ animationId,
+ horizontalPositioning,
+ verticalPositioning,
+ spacedBy);
+ }
+
+ @NonNull
+ @Override
+ protected String getSerializedName() {
+ return "COLLAPSIBLE_ROW";
+ }
+
+ /**
+ * The OP_CODE for this command
+ *
+ * @return the opcode
+ */
+ public static int id() {
+ return Operations.LAYOUT_COLLAPSIBLE_ROW;
+ }
+
+ /**
+ * Write the operation to the buffer
+ *
+ * @param buffer wire buffer
+ * @param componentId component id
+ * @param animationId animation id (-1 if not set)
+ * @param horizontalPositioning horizontal positioning rules
+ * @param verticalPositioning vertical positioning rules
+ * @param spacedBy spaced by value
+ */
+ public static void apply(
+ @NonNull WireBuffer buffer,
+ int componentId,
+ int animationId,
+ int horizontalPositioning,
+ int verticalPositioning,
+ float spacedBy) {
+ buffer.start(id());
+ buffer.writeInt(componentId);
+ buffer.writeInt(animationId);
+ buffer.writeInt(horizontalPositioning);
+ buffer.writeInt(verticalPositioning);
+ buffer.writeFloat(spacedBy);
+ }
+
+ /**
+ * 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 componentId = buffer.readInt();
+ int animationId = buffer.readInt();
+ int horizontalPositioning = buffer.readInt();
+ int verticalPositioning = buffer.readInt();
+ float spacedBy = buffer.readFloat();
+ operations.add(
+ new CollapsibleRowLayout(
+ null,
+ componentId,
+ animationId,
+ horizontalPositioning,
+ verticalPositioning,
+ spacedBy));
+ }
+
+ @Override
+ protected boolean hasHorizontalIntrinsicDimension() {
+ return true;
+ }
+
+ @Override
+ public void computeWrapSize(
+ @NonNull PaintContext context,
+ float maxWidth,
+ float maxHeight,
+ boolean horizontalWrap,
+ boolean verticalWrap,
+ @NonNull MeasurePass measure,
+ @NonNull Size size) {
+ super.computeWrapSize(
+ context, Float.MAX_VALUE, maxHeight, horizontalWrap, verticalWrap, measure, size);
+ }
+
+ @Override
+ public boolean applyVisibility(
+ float selfWidth, float selfHeight, @NonNull MeasurePass measure) {
+ float childrenWidth = 0f;
+ float childrenHeight = 0f;
+ boolean changedVisibility = false;
+ for (Component child : mChildrenComponents) {
+ ComponentMeasure childMeasure = measure.get(child);
+ if (childMeasure.getVisibility() == Visibility.GONE) {
+ continue;
+ }
+ if (childrenWidth + childMeasure.getW() > selfWidth) {
+ childMeasure.setVisibility(Visibility.GONE);
+ changedVisibility = true;
+ } else {
+ childrenWidth += childMeasure.getW();
+ childrenHeight = Math.max(childrenHeight, childMeasure.getH());
+ }
+ }
+ return changedVisibility;
+ }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/ColumnLayout.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/ColumnLayout.java
index f68d7b439578..4d0cbefb0c92 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/ColumnLayout.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/ColumnLayout.java
@@ -32,6 +32,7 @@ import com.android.internal.widget.remotecompose.core.operations.layout.LayoutCo
import com.android.internal.widget.remotecompose.core.operations.layout.measure.ComponentMeasure;
import com.android.internal.widget.remotecompose.core.operations.layout.measure.MeasurePass;
import com.android.internal.widget.remotecompose.core.operations.layout.measure.Size;
+import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.HeightInModifierOperation;
import com.android.internal.widget.remotecompose.core.operations.layout.utils.DebugLog;
import java.util.List;
@@ -93,7 +94,8 @@ public class ColumnLayout extends LayoutManager {
@NonNull
@Override
public String toString() {
- return "COLUMN ["
+ return getSerializedName()
+ + " ["
+ mComponentId
+ ":"
+ mAnimationId
@@ -213,41 +215,62 @@ public class ColumnLayout extends LayoutManager {
selfHeight =
mComponentModifiers.getVerticalScrollDimension() - mPaddingTop - mPaddingBottom;
}
- boolean hasWeights = false;
- float totalWeights = 0f;
- for (Component child : mChildrenComponents) {
- ComponentMeasure childMeasure = measure.get(child);
- if (childMeasure.getVisibility() == Visibility.GONE) {
- continue;
- }
- if (child instanceof LayoutComponent
- && ((LayoutComponent) child).getHeightModifier().hasWeight()) {
- hasWeights = true;
- totalWeights += ((LayoutComponent) child).getHeightModifier().getValue();
- } else {
- childrenHeight += childMeasure.getH();
- }
- }
- if (hasWeights) {
- float availableSpace = selfHeight - childrenHeight;
+ boolean checkWeights = true;
+ while (checkWeights) {
+ checkWeights = false;
+ boolean hasWeights = false;
+ float totalWeights = 0f;
for (Component child : mChildrenComponents) {
+ ComponentMeasure childMeasure = measure.get(child);
+ if (childMeasure.getVisibility() == Visibility.GONE) {
+ continue;
+ }
if (child instanceof LayoutComponent
&& ((LayoutComponent) child).getHeightModifier().hasWeight()) {
- ComponentMeasure childMeasure = measure.get(child);
- if (childMeasure.getVisibility() == Visibility.GONE) {
- continue;
+ hasWeights = true;
+ totalWeights += ((LayoutComponent) child).getHeightModifier().getValue();
+ } else {
+ childrenHeight += childMeasure.getH();
+ }
+ }
+ if (hasWeights) {
+ float availableSpace = selfHeight - childrenHeight;
+ for (Component child : mChildrenComponents) {
+ if (child instanceof LayoutComponent
+ && ((LayoutComponent) child).getHeightModifier().hasWeight()) {
+ ComponentMeasure childMeasure = measure.get(child);
+ if (childMeasure.getVisibility() == Visibility.GONE) {
+ continue;
+ }
+ float weight = ((LayoutComponent) child).getHeightModifier().getValue();
+ float childHeight = (weight * availableSpace) / totalWeights;
+ HeightInModifierOperation heightInConstraints =
+ ((LayoutComponent) child).getHeightModifier().getHeightIn();
+ if (heightInConstraints != null) {
+ float min = heightInConstraints.getMin();
+ float max = heightInConstraints.getMax();
+ if (min != -1) {
+ childHeight = Math.max(min, childHeight);
+ }
+ if (max != -1) {
+ childHeight = Math.min(max, childHeight);
+ }
+ }
+ childMeasure.setH(childHeight);
+ child.measure(
+ context,
+ childMeasure.getW(),
+ childMeasure.getW(),
+ childMeasure.getH(),
+ childMeasure.getH(),
+ measure);
}
- float weight = ((LayoutComponent) child).getHeightModifier().getValue();
- childMeasure.setH((weight * availableSpace) / totalWeights);
- child.measure(
- context,
- childMeasure.getW(),
- childMeasure.getW(),
- childMeasure.getH(),
- childMeasure.getH(),
- measure);
}
}
+
+ if (applyVisibility(selfWidth, selfHeight, measure) && hasWeights) {
+ checkWeights = true;
+ }
}
childrenHeight = 0f;
@@ -360,6 +383,16 @@ public class ColumnLayout extends LayoutManager {
return Operations.LAYOUT_COLUMN;
}
+ /**
+ * Write the operation to the buffer
+ *
+ * @param buffer wire buffer
+ * @param componentId component id
+ * @param animationId animation id (-1 if not set)
+ * @param horizontalPositioning horizontal positioning rules
+ * @param verticalPositioning vertical positioning rules
+ * @param spacedBy spaced by value
+ */
public static void apply(
@NonNull WireBuffer buffer,
int componentId,
@@ -367,7 +400,7 @@ public class ColumnLayout extends LayoutManager {
int horizontalPositioning,
int verticalPositioning,
float spacedBy) {
- buffer.start(Operations.LAYOUT_COLUMN);
+ buffer.start(id());
buffer.writeInt(componentId);
buffer.writeInt(animationId);
buffer.writeInt(horizontalPositioning);
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/LayoutManager.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/LayoutManager.java
index edfd69cbfa96..8b52bbe5cdf8 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/LayoutManager.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/LayoutManager.java
@@ -43,6 +43,18 @@ public abstract class LayoutManager extends LayoutComponent implements Measurabl
super(parent, componentId, animationId, x, y, width, height);
}
+ /**
+ * Allows layout managers to override elements visibility
+ *
+ * @param selfWidth intrinsic width of the layout manager content
+ * @param selfHeight intrinsic height of the layout manager content
+ * @param measure measure pass
+ */
+ public boolean applyVisibility(
+ float selfWidth, float selfHeight, @NonNull MeasurePass measure) {
+ return false;
+ }
+
/** Implemented by subclasses to provide a layout/measure pass */
public void internalLayoutMeasure(@NonNull PaintContext context, @NonNull MeasurePass measure) {
// nothing here
@@ -197,7 +209,7 @@ public abstract class LayoutManager extends LayoutComponent implements Measurabl
}
if (!hasWrap) {
- if (hasHorizontalScroll()) {
+ if (hasHorizontalIntrinsicDimension()) {
mCachedWrapSize.setWidth(0f);
mCachedWrapSize.setHeight(0f);
computeWrapSize(
@@ -210,15 +222,19 @@ public abstract class LayoutManager extends LayoutComponent implements Measurabl
mCachedWrapSize);
float w = mCachedWrapSize.getWidth();
computeSize(context, 0f, w, 0, measuredHeight, measure);
- mComponentModifiers.setHorizontalScrollDimension(measuredWidth, w);
- } else if (hasVerticalScroll()) {
+ if (hasHorizontalScroll()) {
+ mComponentModifiers.setHorizontalScrollDimension(measuredWidth, w);
+ }
+ } else if (hasVerticalIntrinsicDimension()) {
mCachedWrapSize.setWidth(0f);
mCachedWrapSize.setHeight(0f);
computeWrapSize(
context, maxWidth, Float.MAX_VALUE, false, false, measure, mCachedWrapSize);
float h = mCachedWrapSize.getHeight();
computeSize(context, 0f, measuredWidth, 0, h, measure);
- mComponentModifiers.setVerticalScrollDimension(measuredHeight, h);
+ if (hasVerticalScroll()) {
+ mComponentModifiers.setVerticalScrollDimension(measuredHeight, h);
+ }
} else {
float maxChildWidth = measuredWidth - mPaddingLeft - mPaddingRight;
float maxChildHeight = measuredHeight - mPaddingTop - mPaddingBottom;
@@ -246,6 +262,14 @@ public abstract class LayoutManager extends LayoutComponent implements Measurabl
return mComponentModifiers.hasHorizontalScroll();
}
+ protected boolean hasHorizontalIntrinsicDimension() {
+ return hasHorizontalScroll();
+ }
+
+ protected boolean hasVerticalIntrinsicDimension() {
+ return hasVerticalScroll();
+ }
+
private boolean hasVerticalScroll() {
return mComponentModifiers.hasVerticalScroll();
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/RowLayout.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/RowLayout.java
index b688f6e4175a..5b35c4c70702 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/RowLayout.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/RowLayout.java
@@ -32,6 +32,7 @@ import com.android.internal.widget.remotecompose.core.operations.layout.LayoutCo
import com.android.internal.widget.remotecompose.core.operations.layout.measure.ComponentMeasure;
import com.android.internal.widget.remotecompose.core.operations.layout.measure.MeasurePass;
import com.android.internal.widget.remotecompose.core.operations.layout.measure.Size;
+import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.WidthInModifierOperation;
import com.android.internal.widget.remotecompose.core.operations.layout.utils.DebugLog;
import java.util.List;
@@ -91,7 +92,8 @@ public class RowLayout extends LayoutManager {
@NonNull
@Override
public String toString() {
- return "ROW ["
+ return getSerializedName()
+ + " ["
+ mComponentId
+ ":"
+ mAnimationId
@@ -212,44 +214,66 @@ public class RowLayout extends LayoutManager {
mComponentModifiers.getVerticalScrollDimension() - mPaddingTop - mPaddingBottom;
}
- boolean hasWeights = false;
- float totalWeights = 0f;
- for (Component child : mChildrenComponents) {
- ComponentMeasure childMeasure = measure.get(child);
- if (childMeasure.getVisibility() == Visibility.GONE) {
- continue;
- }
- if (child instanceof LayoutComponent
- && ((LayoutComponent) child).getWidthModifier().hasWeight()) {
- hasWeights = true;
- totalWeights += ((LayoutComponent) child).getWidthModifier().getValue();
- } else {
- childrenWidth += childMeasure.getW();
- }
- }
+ boolean checkWeights = true;
- // TODO: need to move the weight measuring in the measure function,
- // currently we'll measure unnecessarily
- if (hasWeights) {
- float availableSpace = selfWidth - childrenWidth;
+ while (checkWeights) {
+ checkWeights = false;
+ boolean hasWeights = false;
+ float totalWeights = 0f;
for (Component child : mChildrenComponents) {
+ ComponentMeasure childMeasure = measure.get(child);
+ if (childMeasure.getVisibility() == Visibility.GONE) {
+ continue;
+ }
if (child instanceof LayoutComponent
&& ((LayoutComponent) child).getWidthModifier().hasWeight()) {
- ComponentMeasure childMeasure = measure.get(child);
- if (childMeasure.getVisibility() == Visibility.GONE) {
- continue;
+ hasWeights = true;
+ totalWeights += ((LayoutComponent) child).getWidthModifier().getValue();
+ } else {
+ childrenWidth += childMeasure.getW();
+ }
+ }
+
+ // TODO: need to move the weight measuring in the measure function,
+ // currently we'll measure unnecessarily
+ if (hasWeights) {
+ float availableSpace = selfWidth - childrenWidth;
+ for (Component child : mChildrenComponents) {
+ if (child instanceof LayoutComponent
+ && ((LayoutComponent) child).getWidthModifier().hasWeight()) {
+ ComponentMeasure childMeasure = measure.get(child);
+ if (childMeasure.getVisibility() == Visibility.GONE) {
+ continue;
+ }
+ float weight = ((LayoutComponent) child).getWidthModifier().getValue();
+ float childWidth = (weight * availableSpace) / totalWeights;
+ WidthInModifierOperation widthInConstraints =
+ ((LayoutComponent) child).getWidthModifier().getWidthIn();
+ if (widthInConstraints != null) {
+ float min = widthInConstraints.getMin();
+ float max = widthInConstraints.getMax();
+ if (min != -1) {
+ childWidth = Math.max(min, childWidth);
+ }
+ if (max != -1) {
+ childWidth = Math.min(max, childWidth);
+ }
+ }
+ childMeasure.setW(childWidth);
+ child.measure(
+ context,
+ childMeasure.getW(),
+ childMeasure.getW(),
+ childMeasure.getH(),
+ childMeasure.getH(),
+ measure);
}
- float weight = ((LayoutComponent) child).getWidthModifier().getValue();
- childMeasure.setW((weight * availableSpace) / totalWeights);
- child.measure(
- context,
- childMeasure.getW(),
- childMeasure.getW(),
- childMeasure.getH(),
- childMeasure.getH(),
- measure);
}
}
+
+ if (applyVisibility(selfWidth, selfHeight, measure) && hasWeights) {
+ checkWeights = true;
+ }
}
childrenWidth = 0f;
@@ -363,6 +387,16 @@ public class RowLayout extends LayoutManager {
return Operations.LAYOUT_ROW;
}
+ /**
+ * Write the operation to the buffer
+ *
+ * @param buffer wire buffer
+ * @param componentId component id
+ * @param animationId animation id (-1 if not set)
+ * @param horizontalPositioning horizontal positioning rules
+ * @param verticalPositioning vertical positioning rules
+ * @param spacedBy spaced by value
+ */
public static void apply(
@NonNull WireBuffer buffer,
int componentId,
@@ -370,7 +404,7 @@ public class RowLayout extends LayoutManager {
int horizontalPositioning,
int verticalPositioning,
float spacedBy) {
- buffer.start(Operations.LAYOUT_ROW);
+ buffer.start(id());
buffer.writeInt(componentId);
buffer.writeInt(animationId);
buffer.writeInt(horizontalPositioning);
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HeightInModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HeightInModifierOperation.java
new file mode 100644
index 000000000000..c19bd2f6b7c0
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HeightInModifierOperation.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.widget.remotecompose.core.operations.layout.modifiers;
+
+import android.annotation.NonNull;
+
+import com.android.internal.widget.remotecompose.core.Operation;
+import com.android.internal.widget.remotecompose.core.Operations;
+import com.android.internal.widget.remotecompose.core.PaintContext;
+import com.android.internal.widget.remotecompose.core.WireBuffer;
+import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder;
+import com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation;
+import com.android.internal.widget.remotecompose.core.operations.DrawBase2;
+import com.android.internal.widget.remotecompose.core.operations.utilities.StringSerializer;
+
+import java.util.List;
+
+/** Set the min / max height dimension on a component */
+public class HeightInModifierOperation extends DrawBase2 implements ModifierOperation {
+ private static final int OP_CODE = Operations.MODIFIER_HEIGHT_IN;
+ public static final String CLASS_NAME = "HeightInModifierOperation";
+
+ /**
+ * Read this operation and add it to the list of operations
+ *
+ * @param buffer the buffer to read
+ * @param operations the list of operations that will be added to
+ */
+ public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
+ Maker m = HeightInModifierOperation::new;
+ read(m, buffer, operations);
+ }
+
+ /**
+ * Returns the min value
+ *
+ * @return minimum value
+ */
+ public float getMin() {
+ return mV1;
+ }
+
+ /**
+ * Returns the max value
+ *
+ * @return maximum value
+ */
+ public float getMax() {
+ return mV2;
+ }
+
+ /**
+ * The OP_CODE for this command
+ *
+ * @return the opcode
+ */
+ public static int id() {
+ return OP_CODE;
+ }
+
+ /**
+ * The name of the class
+ *
+ * @return the name
+ */
+ @NonNull
+ public static String name() {
+ return CLASS_NAME;
+ }
+
+ @Override
+ protected void write(@NonNull WireBuffer buffer, float v1, float v2) {
+ apply(buffer, v1, v2);
+ }
+
+ /**
+ * Populate the documentation with a description of this operation
+ *
+ * @param doc to append the description to.
+ */
+ public static void documentation(@NonNull DocumentationBuilder doc) {
+ doc.operation("Layout Operations", OP_CODE, "HeightInModifierOperation")
+ .description("Add additional constraints to the height")
+ .field(DocumentedOperation.FLOAT, "min", "The minimum height, -1 if not applied")
+ .field(DocumentedOperation.FLOAT, "max", "The maximum height, -1 if not applied");
+ }
+
+ public HeightInModifierOperation(float min, float max) {
+ super(min, max);
+ mName = CLASS_NAME;
+ }
+
+ @Override
+ public void paint(@NonNull PaintContext context) {}
+
+ /**
+ * Writes out the HeightInModifier to the buffer
+ *
+ * @param buffer buffer to write to
+ * @param x1 start x of DrawOval
+ * @param y1 start y of the DrawOval
+ */
+ public static void apply(@NonNull WireBuffer buffer, float x1, float y1) {
+ write(buffer, OP_CODE, x1, y1);
+ }
+
+ @Override
+ public void serializeToString(int indent, @NonNull StringSerializer serializer) {
+ serializer.append(indent, "HEIGHT_IN = [" + getMin() + ", " + getMax() + "]");
+ }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HeightModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HeightModifierOperation.java
index ec078a9e73ea..4b50a916b9cd 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HeightModifierOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HeightModifierOperation.java
@@ -31,6 +31,7 @@ import java.util.List;
public class HeightModifierOperation extends DimensionModifierOperation {
private static final int OP_CODE = Operations.MODIFIER_HEIGHT;
public static final String CLASS_NAME = "HeightModifierOperation";
+ private HeightInModifierOperation mHeightIn = null;
/**
* The name of the class
@@ -110,4 +111,22 @@ public class HeightModifierOperation extends DimensionModifierOperation {
.field(INT, "type", "")
.field(FLOAT, "value", "");
}
+
+ /**
+ * Set height in constraints
+ *
+ * @param heightInConstraints height constraints
+ */
+ public void setHeightIn(HeightInModifierOperation heightInConstraints) {
+ mHeightIn = heightInConstraints;
+ }
+
+ /**
+ * Returns height in constraints
+ *
+ * @return height in constraints
+ */
+ public HeightInModifierOperation getHeightIn() {
+ return mHeightIn;
+ }
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/WidthInModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/WidthInModifierOperation.java
new file mode 100644
index 000000000000..c3624e5b3d88
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/WidthInModifierOperation.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.widget.remotecompose.core.operations.layout.modifiers;
+
+import android.annotation.NonNull;
+
+import com.android.internal.widget.remotecompose.core.Operation;
+import com.android.internal.widget.remotecompose.core.Operations;
+import com.android.internal.widget.remotecompose.core.PaintContext;
+import com.android.internal.widget.remotecompose.core.WireBuffer;
+import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder;
+import com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation;
+import com.android.internal.widget.remotecompose.core.operations.DrawBase2;
+import com.android.internal.widget.remotecompose.core.operations.utilities.StringSerializer;
+
+import java.util.List;
+
+/** Set the min / max width dimension on a component */
+public class WidthInModifierOperation extends DrawBase2 implements ModifierOperation {
+ private static final int OP_CODE = Operations.MODIFIER_WIDTH_IN;
+ public static final String CLASS_NAME = "WidthInModifierOperation";
+
+ /**
+ * Read this operation and add it to the list of operations
+ *
+ * @param buffer the buffer to read
+ * @param operations the list of operations that will be added to
+ */
+ public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
+ Maker m = WidthInModifierOperation::new;
+ read(m, buffer, operations);
+ }
+
+ /**
+ * Returns the min value
+ *
+ * @return minimum value
+ */
+ public float getMin() {
+ return mV1;
+ }
+
+ /**
+ * Returns the max value
+ *
+ * @return maximum value
+ */
+ public float getMax() {
+ return mV2;
+ }
+
+ /**
+ * The OP_CODE for this command
+ *
+ * @return the opcode
+ */
+ public static int id() {
+ return OP_CODE;
+ }
+
+ /**
+ * The name of the class
+ *
+ * @return the name
+ */
+ @NonNull
+ public static String name() {
+ return CLASS_NAME;
+ }
+
+ @Override
+ protected void write(@NonNull WireBuffer buffer, float v1, float v2) {
+ apply(buffer, v1, v2);
+ }
+
+ /**
+ * Populate the documentation with a description of this operation
+ *
+ * @param doc to append the description to.
+ */
+ public static void documentation(@NonNull DocumentationBuilder doc) {
+ doc.operation("Layout Operations", OP_CODE, "WidthInModifierOperation")
+ .description("Add additional constraints to the width")
+ .field(DocumentedOperation.FLOAT, "min", "The minimum width, -1 if not applied")
+ .field(DocumentedOperation.FLOAT, "max", "The maximum width, -1 if not applied");
+ }
+
+ public WidthInModifierOperation(float min, float max) {
+ super(min, max);
+ mName = CLASS_NAME;
+ }
+
+ @Override
+ public void paint(@NonNull PaintContext context) {}
+
+ /**
+ * Writes out the WidthInModifier to the buffer
+ *
+ * @param buffer buffer to write to
+ * @param x1 start x of DrawOval
+ * @param y1 start y of the DrawOval
+ */
+ public static void apply(@NonNull WireBuffer buffer, float x1, float y1) {
+ write(buffer, OP_CODE, x1, y1);
+ }
+
+ @Override
+ public void serializeToString(int indent, @NonNull StringSerializer serializer) {
+ serializer.append(indent, "WIDTH_IN = [" + getMin() + ", " + getMax() + "]");
+ }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/WidthModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/WidthModifierOperation.java
index 05305988a49f..532027ab2087 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/WidthModifierOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/WidthModifierOperation.java
@@ -31,6 +31,7 @@ import java.util.List;
public class WidthModifierOperation extends DimensionModifierOperation {
private static final int OP_CODE = Operations.MODIFIER_WIDTH;
public static final String CLASS_NAME = "WidthModifierOperation";
+ private WidthInModifierOperation mWidthIn = null;
/**
* The name of the class
@@ -110,4 +111,22 @@ public class WidthModifierOperation extends DimensionModifierOperation {
.field(INT, "type", "")
.field(FLOAT, "value", "");
}
+
+ /**
+ * Set width in constraints
+ *
+ * @param widthInConstraints width constraints
+ */
+ public void setWidthIn(WidthInModifierOperation widthInConstraints) {
+ mWidthIn = widthInConstraints;
+ }
+
+ /**
+ * Returns width in constraints
+ *
+ * @return width in constraints
+ */
+ public WidthInModifierOperation getWidthIn() {
+ return mWidthIn;
+ }
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/AnimatedFloatExpression.java b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/AnimatedFloatExpression.java
index b2ea0afd8fab..eb834a97c723 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/AnimatedFloatExpression.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/AnimatedFloatExpression.java
@@ -150,17 +150,47 @@ public class AnimatedFloatExpression {
/** RAND_SEED operator */
public static final float RAND_SEED = asNan(OFFSET + 40);
+ /** NOISE_FROM operator calculate a random 0..1 number based on a seed */
+ public static final float NOISE_FROM = asNan(OFFSET + 41);
+
+ /** RANDOM_IN_RANGE random number in range */
+ public static final float RAND_IN_RANGE = asNan(OFFSET + 42);
+
+ /** SQUARE_SUM the sum of the square of two numbers */
+ public static final float SQUARE_SUM = asNan(OFFSET + 43);
+
+ /** STEP x > edge ? 1 : 0; */
+ public static final float STEP = asNan(OFFSET + 44);
+
+ /** SQUARE x*x; */
+ public static final float SQUARE = asNan(OFFSET + 45);
+
+ /** DUP x,x; */
+ public static final float DUP = asNan(OFFSET + 46);
+
+ /** HYPOT sqrt(x*x+y*y); */
+ public static final float HYPOT = asNan(OFFSET + 47);
+
+ /** SWAP y,x; */
+ public static final float SWAP = asNan(OFFSET + 48);
+
+ /** LERP (1-t)*x+t*y; */
+ public static final float LERP = asNan(OFFSET + 49);
+
+ /** SMOOTH_STEP (1-smoothstep(edge0,edge1,x)); */
+ public static final float SMOOTH_STEP = asNan(OFFSET + 50);
+
/** LAST valid operator */
- public static final int LAST_OP = OFFSET + 40;
+ public static final int LAST_OP = OFFSET + 50;
/** VAR1 operator */
- public static final float VAR1 = asNan(OFFSET + 41);
+ public static final float VAR1 = asNan(OFFSET + 51);
/** VAR2 operator */
- public static final float VAR2 = asNan(OFFSET + 42);
+ public static final float VAR2 = asNan(OFFSET + 52);
/** VAR2 operator */
- public static final float VAR3 = asNan(OFFSET + 43);
+ public static final float VAR3 = asNan(OFFSET + 53);
// TODO SQUARE, DUP, HYPOT, SWAP
// private static final float FP_PI = (float) Math.PI;
@@ -399,6 +429,17 @@ public class AnimatedFloatExpression {
sNames.put(k++, "RAND");
sNames.put(k++, "RAND_SEED");
+ sNames.put(k++, "noise_from");
+ sNames.put(k++, "rand_in_range");
+ sNames.put(k++, "square_sum");
+ sNames.put(k++, "step");
+ sNames.put(k++, "square");
+ sNames.put(k++, "dup");
+ sNames.put(k++, "hypot");
+ sNames.put(k++, "swap");
+ sNames.put(k++, "lerp");
+ sNames.put(k++, "smooth_step");
+
sNames.put(k++, "a[0]");
sNames.put(k++, "a[1]");
sNames.put(k++, "a[2]");
@@ -615,9 +656,20 @@ public class AnimatedFloatExpression {
private static final int OP_RAND = OFFSET + 39;
private static final int OP_RAND_SEED = OFFSET + 40;
- private static final int OP_FIRST_VAR = OFFSET + 41;
- private static final int OP_SECOND_VAR = OFFSET + 42;
- private static final int OP_THIRD_VAR = OFFSET + 43;
+ private static final int OP_NOISE_FROM = OFFSET + 41;
+ private static final int OP_RAND_IN_RANGE = OFFSET + 42;
+ private static final int OP_SQUARE_SUM = OFFSET + 43;
+ private static final int OP_STEP = OFFSET + 44;
+ private static final int OP_SQUARE = OFFSET + 45;
+ private static final int OP_DUP = OFFSET + 46;
+ private static final int OP_HYPOT = OFFSET + 47;
+ private static final int OP_SWAP = OFFSET + 48;
+ private static final int OP_LERP = OFFSET + 49;
+ private static final int OP_SMOOTH_STEP = OFFSET + 50;
+
+ private static final int OP_FIRST_VAR = OFFSET + 51;
+ private static final int OP_SECOND_VAR = OFFSET + 52;
+ private static final int OP_THIRD_VAR = OFFSET + 53;
int opEval(int sp, int id) {
float[] array;
@@ -824,6 +876,66 @@ public class AnimatedFloatExpression {
}
}
return sp - 1;
+ case OP_NOISE_FROM:
+ int x = Float.floatToRawIntBits(mStack[sp]);
+ x = (x << 13) ^ x; // / Bitwise scrambling return
+ mStack[sp] =
+ (1.0f
+ - ((x * (x * x * 15731 + 789221) + 1376312589) & 0x7fffffff)
+ / 1073741824.0f);
+ return sp;
+
+ case OP_RAND_IN_RANGE:
+ if (sRandom == null) {
+ sRandom = new Random();
+ }
+ mStack[sp] = sRandom.nextFloat() * (mStack[sp] - mStack[sp - 1]) + mStack[sp - 1];
+ return sp;
+ case OP_SQUARE_SUM:
+ mStack[sp - 1] = mStack[sp - 1] * mStack[sp - 1] + mStack[sp] * mStack[sp];
+ return sp - 1;
+ case OP_STEP:
+ System.out.println(mStack[sp] + " > " + mStack[sp - 1]);
+ mStack[sp - 1] = (mStack[sp - 1] > mStack[sp]) ? 1f : 0f;
+ return sp - 1;
+ case OP_SQUARE:
+ mStack[sp] = mStack[sp] * mStack[sp];
+ return sp;
+ case OP_DUP:
+ mStack[sp + 1] = mStack[sp];
+ return sp + 1;
+ case OP_HYPOT:
+ mStack[sp - 1] = (float) Math.hypot(mStack[sp - 1], mStack[sp]);
+ return sp - 1;
+ case OP_SWAP:
+ float swap = mStack[sp - 1];
+ mStack[sp - 1] = mStack[sp];
+ mStack[sp] = swap;
+ return sp;
+ case OP_LERP:
+ float tmp1 = mStack[sp - 2];
+ float tmp2 = mStack[sp - 1];
+ float tmp3 = mStack[sp];
+ mStack[sp - 2] = tmp1 + (tmp2 - tmp1) * tmp3;
+ return sp - 2;
+ case OP_SMOOTH_STEP:
+ float val3 = mStack[sp - 2];
+ float max2 = mStack[sp - 1];
+ float min1 = mStack[sp];
+ System.out.println("val3 = " + val3 + " min1 = " + min1 + " max2 = " + max2);
+ if (val3 < min1) {
+ mStack[sp - 2] = 0f;
+ System.out.println("below min ");
+ } else if (val3 > max2) {
+ mStack[sp - 2] = 1f;
+ System.out.println("above max ");
+
+ } else {
+ float v = (val3 - min1) / (max2 - min1);
+ System.out.println("v = " + v);
+ mStack[sp - 2] = v * v * (3 - 2 * v);
+ }
+ return sp - 2;
case OP_FIRST_VAR:
mStack[sp] = mVar[0];
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/Easing.java b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/Easing.java
index d8bc83eb8a2e..2b5368297dae 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/Easing.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/Easing.java
@@ -19,24 +19,58 @@ package com.android.internal.widget.remotecompose.core.operations.utilities.easi
public abstract class Easing {
int mType;
- /** get the value at point x */
+ /**
+ * get the value at point x
+ *
+ * @param x the position at which to get the slope
+ * @return the value at the point
+ */
public abstract float get(float x);
- /** get the slope of the easing function at at x */
+ /**
+ * get the slope of the easing function at at x
+ *
+ * @param x the position at which to get the slope
+ * @return the slope
+ */
public abstract float getDiff(float x);
+ /**
+ * get the type of easing function
+ *
+ * @return the type of easing function
+ */
public int getType() {
return mType;
}
+ /** cubic Easing function that accelerates and decelerates */
public static final int CUBIC_STANDARD = 1;
+
+ /** cubic Easing function that accelerates */
public static final int CUBIC_ACCELERATE = 2;
+
+ /** cubic Easing function that decelerates */
public static final int CUBIC_DECELERATE = 3;
+
+ /** cubic Easing function that just linearly interpolates */
public static final int CUBIC_LINEAR = 4;
+
+ /** cubic Easing function that goes bacwards and then accelerates */
public static final int CUBIC_ANTICIPATE = 5;
+
+ /** cubic Easing function that overshoots and then goes back */
public static final int CUBIC_OVERSHOOT = 6;
+
+ /** cubic Easing function that you customize */
public static final int CUBIC_CUSTOM = 11;
+
+ /** a monotonic spline Easing function that you customize */
public static final int SPLINE_CUSTOM = 12;
+
+ /** a bouncing Easing function */
public static final int EASE_OUT_BOUNCE = 13;
+
+ /** a elastic Easing function */
public static final int EASE_OUT_ELASTIC = 14;
}
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 465c95d06726..65472c262206 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
@@ -52,16 +52,25 @@ public class FloatAnimation extends Easing {
return str;
}
- public FloatAnimation() {
- mType = CUBIC_STANDARD;
- mEasingCurve = new CubicEasing(mType);
- }
-
+ /**
+ * Create an animation based on a float encoding of the animation
+ *
+ * @param description the float encoding of the animation
+ */
public FloatAnimation(@NonNull float... description) {
mType = CUBIC_STANDARD;
setAnimationDescription(description);
}
+ /**
+ * Create an animation based on the parameters
+ *
+ * @param type The type of animation
+ * @param duration The duration of the animation
+ * @param description The float parameters describing the animation
+ * @param initialValue The initial value of the float (NaN if none)
+ * @param wrap The wrap value of the animation NaN if it does not wrap
+ */
public FloatAnimation(
int type,
float duration,
@@ -139,8 +148,8 @@ public class FloatAnimation extends Easing {
/**
* Useful to debug the packed form of an animation string
*
- * @param description
- * @return
+ * @param description the float encoding of the animation
+ * @return a string describing the animation
*/
public static String unpackAnimationToString(float[] description) {
float[] mSpec = description;
@@ -223,7 +232,7 @@ public class FloatAnimation extends Easing {
/**
* Create an animation based on a float encoding of the animation
*
- * @param description
+ * @param description the float encoding of the animation
*/
public void setAnimationDescription(@NonNull float[] description) {
mSpec = description;
@@ -288,7 +297,7 @@ public class FloatAnimation extends Easing {
/**
* Set the initial Value
*
- * @param value
+ * @param value the value to set
*/
public void setInitialValue(float value) {
@@ -321,7 +330,7 @@ public class FloatAnimation extends Easing {
/**
* Set the target value to interpolate to
*
- * @param value
+ * @param value the value to set
*/
public void setTargetValue(float value) {
mTargetValue = value;
@@ -342,6 +351,11 @@ public class FloatAnimation extends Easing {
setScaleOffset();
}
+ /**
+ * Get the target value
+ *
+ * @return the target value
+ */
public float getTargetValue() {
return mTargetValue;
}
@@ -369,6 +383,11 @@ public class FloatAnimation extends Easing {
return mEasingCurve.getDiff(t / mDuration) * (mTargetValue - mInitialValue);
}
+ /**
+ * Get the initial value
+ *
+ * @return the initial value
+ */
public float getInitialValue() {
return mInitialValue;
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/GeneralEasing.java b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/GeneralEasing.java
index 06969ccd1b10..960eff2e7242 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/GeneralEasing.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/GeneralEasing.java
@@ -25,13 +25,18 @@ public class GeneralEasing extends Easing {
/**
* Set the curve based on the float encoding of it
*
- * @param data
+ * @param data the float encoding of the curve
*/
public void setCurveSpecification(@NonNull float[] data) {
mEasingData = data;
createEngine();
}
+ /**
+ * Get the float encoding of the curve
+ *
+ * @return the float encoding of the curve
+ */
public @NonNull float[] getCurveSpecification() {
return mEasingData;
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/MonotonicCurveFit.java b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/MonotonicCurveFit.java
index f4579a24fd44..01d64dff10f9 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/MonotonicCurveFit.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/MonotonicCurveFit.java
@@ -76,10 +76,10 @@ public class MonotonicCurveFit {
}
/**
- * Get the position of all curves at time t
+ * Get the position of all curves at position t
*
- * @param t
- * @param v
+ * @param t the point on the spline
+ * @param v the array to fill (for multiple curves)
*/
public void getPos(double t, @NonNull double[] v) {
final int n = mT.length;
@@ -136,10 +136,10 @@ public class MonotonicCurveFit {
}
/**
- * Get the position of all curves at time t
+ * Get the position of all curves at position t
*
- * @param t
- * @param v
+ * @param t the point on the spline
+ * @param v the array to fill
*/
public void getPos(double t, @NonNull float[] v) {
final int n = mT.length;
@@ -196,11 +196,11 @@ public class MonotonicCurveFit {
}
/**
- * Get the position of the jth curve at time t
+ * Get the position of the jth curve at position t
*
- * @param t
- * @param j
- * @return
+ * @param t the position
+ * @param j the curve to get
+ * @return the position
*/
public double getPos(double t, int j) {
final int n = mT.length;
@@ -240,8 +240,8 @@ public class MonotonicCurveFit {
/**
* Get the slope of all the curves at position t
*
- * @param t
- * @param v
+ * @param t the position
+ * @param v the array to fill
*/
public void getSlope(double t, @NonNull double[] v) {
final int n = mT.length;
@@ -271,9 +271,9 @@ public class MonotonicCurveFit {
/**
* Get the slope of the j curve at position t
*
- * @param t
- * @param j
- * @return
+ * @param t the position
+ * @param j the curve to get the value at
+ * @return the slope
*/
public double getSlope(double t, int j) {
final int n = mT.length;
@@ -297,6 +297,11 @@ public class MonotonicCurveFit {
return 0; // should never reach here
}
+ /**
+ * Get the time point used to create the curve
+ *
+ * @return the time points used to create the curve
+ */
public @NonNull double[] getTimePoints() {
return mT;
}
@@ -332,7 +337,12 @@ public class MonotonicCurveFit {
+ h * t1;
}
- /** This builds a monotonic spline to be used as a wave function */
+ /**
+ * This builds a monotonic spline to be used as a wave function
+ *
+ * @param configString the configuration string
+ * @return the curve
+ */
@NonNull
public static MonotonicCurveFit buildWave(@NonNull String configString) {
// done this way for efficiency
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/MonotonicSpline.java b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/MonotonicSpline.java
index 23a664336c5f..8bb7dae2fd6a 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/MonotonicSpline.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/MonotonicSpline.java
@@ -76,6 +76,11 @@ public class MonotonicSpline {
mTangent = tangent;
}
+ /**
+ * Get the value point used in the interpolator.
+ *
+ * @return the value points
+ */
public float[] getArray() {
return mY;
}
@@ -83,7 +88,7 @@ public class MonotonicSpline {
/**
* Get the position of all curves at time t
*
- * @param t
+ * @param t the position along spline
* @return position at t
*/
public float getPos(float t) {
@@ -139,7 +144,7 @@ public class MonotonicSpline {
/**
* Get the slope of the curve at position t
*
- * @param t
+ * @param t the position along spline
* @return slope at t
*/
public float getSlope(float t) {
@@ -167,6 +172,11 @@ public class MonotonicSpline {
return v;
}
+ /**
+ * Get the time points used in the interpolator.
+ *
+ * @return the time points
+ */
public float[] getTimePoints() {
return mT;
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/SpringStopEngine.java b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/SpringStopEngine.java
index 03e45031e515..2f1379b3e9fc 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/SpringStopEngine.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/SpringStopEngine.java
@@ -42,9 +42,9 @@ public class SpringStopEngine {
private float mStopThreshold;
private int mBoundaryMode = 0;
- public String debug(String desc, float time) {
- return null;
- }
+ // public String debug(String desc, float time) {
+ // return null;
+ // }
void log(String str) {
StackTraceElement s = new Throwable().getStackTrace()[1];
@@ -53,20 +53,41 @@ public class SpringStopEngine {
System.out.println(line + str);
}
+ /** */
public SpringStopEngine() {}
+ /**
+ * get the value the sping is pulling towards
+ *
+ * @return the value the sping is pulling towards
+ */
public float getTargetValue() {
return (float) mTargetPos;
}
+ /**
+ * get the value the sping is starting from
+ *
+ * @param v the value the sping is starting from
+ */
public void setInitialValue(float v) {
mPos = v;
}
+ /**
+ * set the value the sping is pulling towards
+ *
+ * @param v the value the sping is pulling towards
+ */
public void setTargetValue(float v) {
mTargetPos = v;
}
+ /**
+ * Create a sping engine with the parameters encoded as an array of floats
+ *
+ * @param parameters the parameters to use
+ */
public SpringStopEngine(float[] parameters) {
if (parameters[0] != 0) {
throw new RuntimeException(" parameter[0] should be 0");
@@ -83,9 +104,9 @@ public class SpringStopEngine {
/**
* Config the spring starting conditions
*
- * @param currentPos
- * @param target
- * @param currentVelocity
+ * @param currentPos the current position of the spring
+ * @param target the target position of the spring
+ * @param currentVelocity the current velocity of the spring
*/
public void springStart(float currentPos, float target, float currentVelocity) {
mTargetPos = target;
@@ -115,10 +136,22 @@ public class SpringStopEngine {
mLastTime = 0;
}
+ /**
+ * get the velocity of the spring at a time
+ *
+ * @param time the time to get the velocity at
+ * @return the velocity of the spring at a time
+ */
public float getVelocity(float time) {
return (float) mV;
}
+ /**
+ * get the position of the spring at a time
+ *
+ * @param time the time to get the position at
+ * @return the position of the spring at a time
+ */
public float get(float time) {
compute(time - mLastTime);
mLastTime = time;
@@ -128,6 +161,11 @@ public class SpringStopEngine {
return (float) mPos;
}
+ /**
+ * get the acceleration of the spring
+ *
+ * @return the acceleration of the spring
+ */
public float getAcceleration() {
double k = mStiffness;
double c = mDamping;
@@ -135,10 +173,20 @@ public class SpringStopEngine {
return (float) (-k * x - c * mV) / mMass;
}
+ /**
+ * get the velocity of the spring
+ *
+ * @return the velocity of the spring
+ */
public float getVelocity() {
return 0;
}
+ /**
+ * is the spring stopped
+ *
+ * @return true if the spring is stopped
+ */
public boolean isStopped() {
double x = (mPos - mTargetPos);
double k = mStiffness;
@@ -149,6 +197,11 @@ public class SpringStopEngine {
return max_def <= mStopThreshold;
}
+ /**
+ * increment the spring position over time dt
+ *
+ * @param dt the time to increment the spring position over
+ */
private void compute(double dt) {
if (dt <= 0) {
// Nothing to compute if there's no time difference
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/StepCurve.java b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/StepCurve.java
index b1eb8041b0b3..376e1e9179f4 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/StepCurve.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/StepCurve.java
@@ -26,6 +26,13 @@ public class StepCurve extends Easing {
// private static final boolean DEBUG = false;
@NonNull private final MonotonicCurveFit mCurveFit;
+ /**
+ * Create a step curve from a series of values
+ *
+ * @param params the series of values to ease over
+ * @param offset the offset into the array
+ * @param len the length of the array to use
+ */
public StepCurve(@NonNull float[] params, int offset, int len) {
mCurveFit = genSpline(params, offset, len);
}
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 5de11a19799d..b17e3dc82d50 100644
--- a/core/java/com/android/internal/widget/remotecompose/player/RemoteComposePlayer.java
+++ b/core/java/com/android/internal/widget/remotecompose/player/RemoteComposePlayer.java
@@ -66,6 +66,28 @@ public class RemoteComposePlayer extends FrameLayout {
}
/**
+ * @inheritDoc
+ */
+ public void requestLayout() {
+ super.requestLayout();
+
+ if (mInner != null) {
+ mInner.requestLayout();
+ }
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public void invalidate() {
+ super.invalidate();
+
+ if (mInner != null) {
+ mInner.invalidate();
+ }
+ }
+
+ /**
* Returns true if the document supports drag touch events
*
* @return true if draggable content, false otherwise
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 970cc4a44672..334ba62636ff 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
@@ -48,7 +48,7 @@ public class RemoteComposeCanvas extends FrameLayout implements View.OnAttachSta
boolean mHasClickAreas = false;
Point mActionDownPoint = new Point(0, 0);
AndroidRemoteContext mARContext = new AndroidRemoteContext();
- float mDensity = 1f;
+ float mDensity = Float.NaN;
long mStart = System.nanoTime();
long mLastFrameDelay = 1;
@@ -68,24 +68,18 @@ public class RemoteComposeCanvas extends FrameLayout implements View.OnAttachSta
public RemoteComposeCanvas(Context context) {
super(context);
- if (USE_VIEW_AREA_CLICK) {
- addOnAttachStateChangeListener(this);
- }
+ addOnAttachStateChangeListener(this);
}
public RemoteComposeCanvas(Context context, AttributeSet attrs) {
super(context, attrs);
- if (USE_VIEW_AREA_CLICK) {
- addOnAttachStateChangeListener(this);
- }
+ addOnAttachStateChangeListener(this);
}
public RemoteComposeCanvas(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
setBackgroundColor(Color.WHITE);
- if (USE_VIEW_AREA_CLICK) {
- addOnAttachStateChangeListener(this);
- }
+ addOnAttachStateChangeListener(this);
}
public void setDebug(int value) {
@@ -124,6 +118,7 @@ public class RemoteComposeCanvas extends FrameLayout implements View.OnAttachSta
mChoreographer.postFrameCallback(mFrameCallback);
}
mDensity = getContext().getResources().getDisplayMetrics().density;
+ mARContext.setDensity(mDensity);
if (mDocument == null) {
return;
}
diff --git a/core/jni/android_graphics_BLASTBufferQueue.cpp b/core/jni/android_graphics_BLASTBufferQueue.cpp
index 7b61a5db0b41..10d6d33c5a76 100644
--- a/core/jni/android_graphics_BLASTBufferQueue.cpp
+++ b/core/jni/android_graphics_BLASTBufferQueue.cpp
@@ -107,10 +107,11 @@ public:
}
}
- void onWaitForBufferRelease() {
+ void onWaitForBufferRelease(const nsecs_t durationNanos) {
JNIEnv* env = getenv(mVm);
getenv(mVm)->CallVoidMethod(mWaitForBufferReleaseObject,
- gWaitForBufferReleaseCallback.onWaitForBufferRelease);
+ gWaitForBufferReleaseCallback.onWaitForBufferRelease,
+ durationNanos);
DieIfException(env, "Uncaught exception in WaitForBufferReleaseCallback.");
}
@@ -255,7 +256,9 @@ static void nativeSetWaitForBufferReleaseCallback(JNIEnv* env, jclass clazz, jlo
} else {
sp<WaitForBufferReleaseCallbackWrapper> wrapper =
new WaitForBufferReleaseCallbackWrapper{env, waitForBufferReleaseCallback};
- queue->setWaitForBufferReleaseCallback([wrapper]() { wrapper->onWaitForBufferRelease(); });
+ queue->setWaitForBufferReleaseCallback([wrapper](const nsecs_t durationNanos) {
+ wrapper->onWaitForBufferRelease(durationNanos);
+ });
}
}
@@ -305,7 +308,7 @@ int register_android_graphics_BLASTBufferQueue(JNIEnv* env) {
jclass waitForBufferReleaseClass =
FindClassOrDie(env, "android/graphics/BLASTBufferQueue$WaitForBufferReleaseCallback");
gWaitForBufferReleaseCallback.onWaitForBufferRelease =
- GetMethodIDOrDie(env, waitForBufferReleaseClass, "onWaitForBufferRelease", "()V");
+ GetMethodIDOrDie(env, waitForBufferReleaseClass, "onWaitForBufferRelease", "(J)V");
return 0;
}
diff --git a/core/jni/android_os_PerfettoTrace.cpp b/core/jni/android_os_PerfettoTrace.cpp
index 988aea722be3..962aefc482e4 100644
--- a/core/jni/android_os_PerfettoTrace.cpp
+++ b/core/jni/android_os_PerfettoTrace.cpp
@@ -23,6 +23,7 @@
#include <nativehelper/scoped_local_ref.h>
#include <nativehelper/scoped_primitive_array.h>
#include <nativehelper/scoped_utf_chars.h>
+#include <nativehelper/utils.h>
#include <tracing_sdk.h>
namespace android {
@@ -36,30 +37,6 @@ inline static jlong toJLong(T* ptr) {
return static_cast<jlong>(reinterpret_cast<uintptr_t>(ptr));
}
-static const char* fromJavaString(JNIEnv* env, jstring jstr) {
- if (!jstr) return "";
- ScopedUtfChars chars(env, jstr);
-
- if (!chars.c_str()) {
- ALOGE("Failed extracting string");
- return "";
- }
-
- return chars.c_str();
-}
-
-static void android_os_PerfettoTrace_event(JNIEnv* env, jclass, jint type, jlong cat_ptr,
- jstring name, jlong extra_ptr) {
- ScopedUtfChars name_utf(env, name);
- if (!name_utf.c_str()) {
- ALOGE("Failed extracting string");
- }
-
- tracing_perfetto::Category* category = toPointer<tracing_perfetto::Category>(cat_ptr);
- tracing_perfetto::trace_event(type, category->get(), name_utf.c_str(),
- toPointer<tracing_perfetto::Extra>(extra_ptr));
-}
-
static jlong android_os_PerfettoTrace_get_process_track_uuid() {
return tracing_perfetto::get_process_track_uuid();
}
@@ -70,20 +47,18 @@ static jlong android_os_PerfettoTrace_get_thread_track_uuid(jlong tid) {
static void android_os_PerfettoTrace_activate_trigger(JNIEnv* env, jclass, jstring name,
jint ttl_ms) {
- ScopedUtfChars name_utf(env, name);
- if (!name_utf.c_str()) {
- ALOGE("Failed extracting string");
- return;
- }
-
- tracing_perfetto::activate_trigger(name_utf.c_str(), static_cast<uint32_t>(ttl_ms));
+ ScopedUtfChars name_chars = GET_UTF_OR_RETURN_VOID(env, name);
+ tracing_perfetto::activate_trigger(name_chars.c_str(), static_cast<uint32_t>(ttl_ms));
}
static jlong android_os_PerfettoTraceCategory_init(JNIEnv* env, jclass, jstring name, jstring tag,
jstring severity) {
- return toJLong(new tracing_perfetto::Category(fromJavaString(env, name),
- fromJavaString(env, tag),
- fromJavaString(env, severity)));
+ ScopedUtfChars name_chars = GET_UTF_OR_RETURN(env, name);
+ ScopedUtfChars tag_chars = GET_UTF_OR_RETURN(env, tag);
+ ScopedUtfChars severity_chars = GET_UTF_OR_RETURN(env, severity);
+
+ return toJLong(new tracing_perfetto::Category(name_chars.c_str(), tag_chars.c_str(),
+ severity_chars.c_str()));
}
static jlong android_os_PerfettoTraceCategory_delete() {
@@ -121,8 +96,7 @@ static const JNINativeMethod gCategoryMethods[] = {
};
static const JNINativeMethod gTraceMethods[] =
- {{"native_event", "(IJLjava/lang/String;J)V", (void*)android_os_PerfettoTrace_event},
- {"native_get_process_track_uuid", "()J",
+ {{"native_get_process_track_uuid", "()J",
(void*)android_os_PerfettoTrace_get_process_track_uuid},
{"native_get_thread_track_uuid", "(J)J",
(void*)android_os_PerfettoTrace_get_thread_track_uuid},
@@ -132,10 +106,11 @@ static const JNINativeMethod gTraceMethods[] =
int register_android_os_PerfettoTrace(JNIEnv* env) {
int res = jniRegisterNativeMethods(env, "android/os/PerfettoTrace", gTraceMethods,
NELEM(gTraceMethods));
+ LOG_ALWAYS_FATAL_IF(res < 0, "Unable to register perfetto native methods.");
res = jniRegisterNativeMethods(env, "android/os/PerfettoTrace$Category", gCategoryMethods,
NELEM(gCategoryMethods));
- LOG_ALWAYS_FATAL_IF(res < 0, "Unable to register native methods.");
+ LOG_ALWAYS_FATAL_IF(res < 0, "Unable to register category native methods.");
return 0;
}
diff --git a/core/jni/android_os_PerfettoTrackEventExtra.cpp b/core/jni/android_os_PerfettoTrackEventExtra.cpp
index 9adad7bca940..b8bdc8c29199 100644
--- a/core/jni/android_os_PerfettoTrackEventExtra.cpp
+++ b/core/jni/android_os_PerfettoTrackEventExtra.cpp
@@ -20,6 +20,7 @@
#include <log/log.h>
#include <nativehelper/JNIHelp.h>
#include <nativehelper/scoped_utf_chars.h>
+#include <nativehelper/utils.h>
#include <tracing_sdk.h>
static constexpr ssize_t kMaxStrLen = 4096;
@@ -34,32 +35,24 @@ inline static jlong toJLong(T* ptr) {
return static_cast<jlong>(reinterpret_cast<uintptr_t>(ptr));
}
-static const char* fromJavaString(JNIEnv* env, jstring jstr) {
- if (!jstr) return "";
- ScopedUtfChars chars(env, jstr);
-
- if (!chars.c_str()) {
- ALOGE("Failed extracting string");
- return "";
- }
-
- return chars.c_str();
-}
-
static jlong android_os_PerfettoTrackEventExtraArgInt64_init(JNIEnv* env, jclass, jstring name) {
- return toJLong(new tracing_perfetto::DebugArg<int64_t>(fromJavaString(env, name)));
+ ScopedUtfChars name_chars = GET_UTF_OR_RETURN(env, name);
+ return toJLong(new tracing_perfetto::DebugArg<int64_t>(name_chars.c_str()));
}
static jlong android_os_PerfettoTrackEventExtraArgBool_init(JNIEnv* env, jclass, jstring name) {
- return toJLong(new tracing_perfetto::DebugArg<bool>(fromJavaString(env, name)));
+ ScopedUtfChars name_chars = GET_UTF_OR_RETURN(env, name);
+ return toJLong(new tracing_perfetto::DebugArg<bool>(name_chars.c_str()));
}
static jlong android_os_PerfettoTrackEventExtraArgDouble_init(JNIEnv* env, jclass, jstring name) {
- return toJLong(new tracing_perfetto::DebugArg<double>(fromJavaString(env, name)));
+ ScopedUtfChars name_chars = GET_UTF_OR_RETURN(env, name);
+ return toJLong(new tracing_perfetto::DebugArg<double>(name_chars.c_str()));
}
static jlong android_os_PerfettoTrackEventExtraArgString_init(JNIEnv* env, jclass, jstring name) {
- return toJLong(new tracing_perfetto::DebugArg<const char*>(fromJavaString(env, name)));
+ ScopedUtfChars name_chars = GET_UTF_OR_RETURN(env, name);
+ return toJLong(new tracing_perfetto::DebugArg<const char*>(name_chars.c_str()));
}
static jlong android_os_PerfettoTrackEventExtraArgInt64_delete() {
@@ -116,9 +109,11 @@ static void android_os_PerfettoTrackEventExtraArgDouble_set_value(jlong ptr, jdo
static void android_os_PerfettoTrackEventExtraArgString_set_value(JNIEnv* env, jclass, jlong ptr,
jstring val) {
+ ScopedUtfChars val_chars = GET_UTF_OR_RETURN_VOID(env, val);
+
tracing_perfetto::DebugArg<const char*>* arg =
toPointer<tracing_perfetto::DebugArg<const char*>>(ptr);
- arg->set_value(strdup(fromJavaString(env, val)));
+ arg->set_value(strdup(val_chars.c_str()));
}
static jlong android_os_PerfettoTrackEventExtraFieldInt64_init() {
@@ -191,9 +186,11 @@ static void android_os_PerfettoTrackEventExtraFieldDouble_set_value(jlong ptr, j
static void android_os_PerfettoTrackEventExtraFieldString_set_value(JNIEnv* env, jclass, jlong ptr,
jlong id, jstring val) {
+ ScopedUtfChars val_chars = GET_UTF_OR_RETURN_VOID(env, val);
+
tracing_perfetto::ProtoField<const char*>* field =
toPointer<tracing_perfetto::ProtoField<const char*>>(ptr);
- field->set_value(id, strdup(fromJavaString(env, val)));
+ field->set_value(id, strdup(val_chars.c_str()));
}
static void android_os_PerfettoTrackEventExtraFieldNested_add_field(jlong field_ptr,
@@ -234,7 +231,8 @@ static jlong android_os_PerfettoTrackEventExtraFlow_get_extra_ptr(jlong ptr) {
static jlong android_os_PerfettoTrackEventExtraNamedTrack_init(JNIEnv* env, jclass, jlong id,
jstring name, jlong parent_uuid) {
- return toJLong(new tracing_perfetto::NamedTrack(id, parent_uuid, fromJavaString(env, name)));
+ ScopedUtfChars name_chars = GET_UTF_OR_RETURN(env, name);
+ return toJLong(new tracing_perfetto::NamedTrack(id, parent_uuid, name_chars.c_str()));
}
static jlong android_os_PerfettoTrackEventExtraNamedTrack_delete() {
@@ -248,8 +246,9 @@ static jlong android_os_PerfettoTrackEventExtraNamedTrack_get_extra_ptr(jlong pt
static jlong android_os_PerfettoTrackEventExtraCounterTrack_init(JNIEnv* env, jclass, jstring name,
jlong parent_uuid) {
- return toJLong(
- new tracing_perfetto::RegisteredTrack(1, parent_uuid, fromJavaString(env, name), true));
+ ScopedUtfChars name_chars = GET_UTF_OR_RETURN(env, name);
+
+ return toJLong(new tracing_perfetto::RegisteredTrack(1, parent_uuid, name_chars.c_str(), true));
}
static jlong android_os_PerfettoTrackEventExtraCounterTrack_delete() {
@@ -317,6 +316,15 @@ static void android_os_PerfettoTrackEventExtra_clear_args(jlong ptr) {
extra->clear_extras();
}
+static void android_os_PerfettoTrackEventExtra_emit(JNIEnv* env, jclass, jint type, jlong cat_ptr,
+ jstring name, jlong extra_ptr) {
+ ScopedUtfChars name_chars = GET_UTF_OR_RETURN_VOID(env, name);
+
+ tracing_perfetto::Category* category = toPointer<tracing_perfetto::Category>(cat_ptr);
+ tracing_perfetto::trace_event(type, category->get(), name_chars.c_str(),
+ toPointer<tracing_perfetto::Extra>(extra_ptr));
+}
+
static jlong android_os_PerfettoTrackEventExtraProto_init() {
return toJLong(new tracing_perfetto::Proto());
}
@@ -344,7 +352,9 @@ static const JNINativeMethod gExtraMethods[] =
{{"native_init", "()J", (void*)android_os_PerfettoTrackEventExtra_init},
{"native_delete", "()J", (void*)android_os_PerfettoTrackEventExtra_delete},
{"native_add_arg", "(JJ)V", (void*)android_os_PerfettoTrackEventExtra_add_arg},
- {"native_clear_args", "(J)V", (void*)android_os_PerfettoTrackEventExtra_clear_args}};
+ {"native_clear_args", "(J)V", (void*)android_os_PerfettoTrackEventExtra_clear_args},
+ {"native_emit", "(IJLjava/lang/String;J)V",
+ (void*)android_os_PerfettoTrackEventExtra_emit}};
static const JNINativeMethod gProtoMethods[] =
{{"native_init", "()J", (void*)android_os_PerfettoTrackEventExtraProto_init},
diff --git a/core/proto/android/providers/settings/secure.proto b/core/proto/android/providers/settings/secure.proto
index 5d0b340ac839..69c812c6fb41 100644
--- a/core/proto/android/providers/settings/secure.proto
+++ b/core/proto/android/providers/settings/secure.proto
@@ -109,6 +109,7 @@ message SecureSettingsProto {
optional SettingProto em_value = 61 [ (android.privacy).dest = DEST_AUTOMATIC ];
// Settings for accessibility autoclick
optional SettingProto autoclick_cursor_area_size = 62 [ (android.privacy).dest = DEST_AUTOMATIC ];
+ optional SettingProto autoclick_ignore_minor_cursor_movement = 63 [ (android.privacy).dest = DEST_AUTOMATIC ];
}
optional Accessibility accessibility = 2;
diff --git a/core/proto/android/providers/settings/system.proto b/core/proto/android/providers/settings/system.proto
index 64c9f540a97b..325790c22fce 100644
--- a/core/proto/android/providers/settings/system.proto
+++ b/core/proto/android/providers/settings/system.proto
@@ -218,7 +218,8 @@ message SystemSettingsProto {
optional SettingProto tap_to_click = 4 [ (android.privacy).dest = DEST_AUTOMATIC ];
optional SettingProto tap_dragging = 5 [ (android.privacy).dest = DEST_AUTOMATIC ];
optional SettingProto three_finger_tap_customization = 6 [ (android.privacy).dest = DEST_AUTOMATIC ];
- optional SettingProto system_gestures = 7 [ (android.privacy).dest = DEST_AUTOMATIC ];;
+ optional SettingProto system_gestures = 7 [ (android.privacy).dest = DEST_AUTOMATIC ];
+ optional SettingProto acceleration_enabled = 8 [ (android.privacy).dest = DEST_AUTOMATIC ];;
}
optional Touchpad touchpad = 36;
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 73279700ecb1..aad8f8a156b5 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -8324,16 +8324,15 @@
<!-- Allows an application to perform actions on behalf of users inside of
applications.
- <p>This permission is currently only granted to preinstalled / system apps having the
- {@link android.app.role.ASSISTANT} role.
+ <p>This permission is currently only granted to privileged system apps.
<p>Apps contributing app functions can opt to disallow callers with this permission,
limiting to only callers with {@link android.permission.EXECUTE_APP_FUNCTIONS_TRUSTED}
instead.
- <p>Protection level: internal|role
+ <p>Protection level: internal|privileged
@FlaggedApi(android.app.appfunctions.flags.Flags.FLAG_ENABLE_APP_FUNCTION_MANAGER) -->
<permission android:name="android.permission.EXECUTE_APP_FUNCTIONS"
android:featureFlag="android.app.appfunctions.flags.enable_app_function_manager"
- android:protectionLevel="internal|role" />
+ android:protectionLevel="internal|privileged" />
<!-- Allows an application to display its suggestions using the autofill framework.
<p>For now, this permission is only granted to the Browser application.
diff --git a/core/res/res/layout/preference_list_fragment.xml b/core/res/res/layout/preference_list_fragment.xml
index 44a5df9b60be..c43975e4ad3c 100644
--- a/core/res/res/layout/preference_list_fragment.xml
+++ b/core/res/res/layout/preference_list_fragment.xml
@@ -19,7 +19,6 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
- android:fitsSystemWindows="true"
android:layout_height="match_parent"
android:layout_width="match_parent"
android:background="@android:color/transparent"
diff --git a/core/res/res/layout/preference_list_fragment_material.xml b/core/res/res/layout/preference_list_fragment_material.xml
index 4df76029e606..db2fe7d038e0 100644
--- a/core/res/res/layout/preference_list_fragment_material.xml
+++ b/core/res/res/layout/preference_list_fragment_material.xml
@@ -19,7 +19,6 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
- android:fitsSystemWindows="true"
android:layout_height="match_parent"
android:layout_width="match_parent"
android:background="@android:color/transparent"
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index bd1d3033165e..a1f85c380a95 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -7585,7 +7585,7 @@
<!-- Used to config the segments of a NotificationProgressDrawable. -->
<!-- @hide internal use only -->
<declare-styleable name="NotificationProgressDrawableSegments">
- <!-- TODO: b/372908709 - maybe move this to NotificationProgressBar, because that's the only
+ <!-- TODO: b/390196782 - maybe move this to NotificationProgressBar, because that's the only
place this is used actually. Same for NotificationProgressDrawable.segSegGap/segPointGap
above. -->
<!-- Minimum required drawing width. The drawing width refers to the width after
diff --git a/core/res/res/values/public-staging.xml b/core/res/res/values/public-staging.xml
index 3f657541eb28..7baaa6d590f2 100644
--- a/core/res/res/values/public-staging.xml
+++ b/core/res/res/values/public-staging.xml
@@ -116,7 +116,7 @@
<public name="adServiceTypes" />
<!-- @FlaggedApi("android.view.inputmethod.ime_switcher_revamp_api") -->
<public name="languageSettingsActivity"/>
- <!-- @FlaggedApi("android.service.controls.flags.Flags.FLAG_HOME_PANEL_DREAM") -->
+ <!-- @FlaggedApi(android.service.controls.flags.Flags.FLAG_HOME_PANEL_DREAM) -->
<public name="dreamCategory"/>
<!-- @FlaggedApi("android.permission.flags.replace_body_sensor_permission_enabled")
@hide @SystemApi -->
diff --git a/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InputMethodInfoTest.java b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InputMethodInfoTest.java
index 44b2d90decf2..dfe7d0306905 100644
--- a/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InputMethodInfoTest.java
+++ b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InputMethodInfoTest.java
@@ -26,7 +26,6 @@ import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.os.Bundle;
import android.os.Parcel;
-import android.platform.test.annotations.EnableFlags;
import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.platform.test.flag.junit.SetFlagsRule;
@@ -124,7 +123,6 @@ public class InputMethodInfoTest {
}
@Test
- @EnableFlags(android.companion.virtual.flags.Flags.FLAG_VDM_CUSTOM_IME)
public void testIsVirtualDeviceOnly() throws Exception {
final InputMethodInfo imi = buildInputMethodForTest(R.xml.ime_meta_virtual_device_only);
diff --git a/core/tests/coretests/src/android/app/NotificationManagerTest.java b/core/tests/coretests/src/android/app/NotificationManagerTest.java
index 18ba6a16bf72..41432294b3c2 100644
--- a/core/tests/coretests/src/android/app/NotificationManagerTest.java
+++ b/core/tests/coretests/src/android/app/NotificationManagerTest.java
@@ -30,8 +30,10 @@ import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.annotation.Nullable;
import android.content.Context;
import android.content.ContextWrapper;
+import android.content.pm.PackageManager;
import android.content.pm.ParceledListSlice;
import android.os.UserHandle;
import android.platform.test.annotations.EnableFlags;
@@ -263,6 +265,38 @@ public class NotificationManagerTest {
}
@Test
+ @EnableFlags({Flags.FLAG_NM_BINDER_PERF_THROTTLE_NOTIFY,
+ Flags.FLAG_NM_BINDER_PERF_LOG_NM_THROTTLING})
+ public void notify_rapidUpdate_logsOncePerSecond() throws Exception {
+ Notification n = exampleNotification();
+
+ for (int i = 0; i < 650; i++) {
+ mNotificationManager.notify(1, n);
+ mClock.advanceByMillis(10);
+ }
+
+ // Runs for a total of 6.5 seconds, so should log once (when RateEstimator catches up) + 6
+ // more times (after 1 second each).
+ verify(mNotificationManager.mBackendService, times(7)).incrementCounter(
+ eq("notifications.value_client_throttled_notify_update"));
+ }
+
+ @Test
+ @EnableFlags({Flags.FLAG_NM_BINDER_PERF_THROTTLE_NOTIFY,
+ Flags.FLAG_NM_BINDER_PERF_LOG_NM_THROTTLING})
+ public void cancel_unnecessaryAndRapid_logsOncePerSecond() throws Exception {
+ for (int i = 0; i < 650; i++) {
+ mNotificationManager.cancel(1);
+ mClock.advanceByMillis(10);
+ }
+
+ // Runs for a total of 6.5 seconds, so should log once (when RateEstimator catches up) + 6
+ // more times (after 1 second each).
+ verify(mNotificationManager.mBackendService, times(7)).incrementCounter(
+ eq("notifications.value_client_throttled_cancel_duplicate"));
+ }
+
+ @Test
@EnableFlags(Flags.FLAG_NM_BINDER_PERF_CACHE_CHANNELS)
public void getNotificationChannel_cachedUntilInvalidated() throws Exception {
// Invalidate the cache first because the cache won't do anything until then
@@ -409,6 +443,46 @@ public class NotificationManagerTest {
.getOrCreateNotificationChannels(any(), any(), anyInt(), anyBoolean());
}
+ @Test
+ @EnableFlags({Flags.FLAG_MODES_API, Flags.FLAG_MODES_UI})
+ public void areAutomaticZenRulesUserManaged_handheld_isTrue() {
+ PackageManager pm = mock(PackageManager.class);
+ when(pm.hasSystemFeature(any())).thenReturn(false);
+ mContext.setPackageManager(pm);
+
+ assertThat(mNotificationManager.areAutomaticZenRulesUserManaged()).isTrue();
+ }
+
+ @Test
+ @EnableFlags({Flags.FLAG_MODES_API, Flags.FLAG_MODES_UI})
+ public void areAutomaticZenRulesUserManaged_auto_isFalse() {
+ PackageManager pm = mock(PackageManager.class);
+ when(pm.hasSystemFeature(eq(PackageManager.FEATURE_AUTOMOTIVE))).thenReturn(true);
+ mContext.setPackageManager(pm);
+
+ assertThat(mNotificationManager.areAutomaticZenRulesUserManaged()).isFalse();
+ }
+
+ @Test
+ @EnableFlags({Flags.FLAG_MODES_API, Flags.FLAG_MODES_UI})
+ public void areAutomaticZenRulesUserManaged_tv_isFalse() {
+ PackageManager pm = mock(PackageManager.class);
+ when(pm.hasSystemFeature(eq(PackageManager.FEATURE_LEANBACK))).thenReturn(true);
+ mContext.setPackageManager(pm);
+
+ assertThat(mNotificationManager.areAutomaticZenRulesUserManaged()).isFalse();
+ }
+
+ @Test
+ @EnableFlags({Flags.FLAG_MODES_API, Flags.FLAG_MODES_UI})
+ public void areAutomaticZenRulesUserManaged_watch_isFalse() {
+ PackageManager pm = mock(PackageManager.class);
+ when(pm.hasSystemFeature(eq(PackageManager.FEATURE_WATCH))).thenReturn(true);
+ mContext.setPackageManager(pm);
+
+ assertThat(mNotificationManager.areAutomaticZenRulesUserManaged()).isFalse();
+ }
+
private Notification exampleNotification() {
return new Notification.Builder(mContext, "channel")
.setSmallIcon(android.R.drawable.star_big_on)
@@ -438,6 +512,7 @@ public class NotificationManagerTest {
// Helper context wrapper class where we can control just the return values of getPackageName,
// getOpPackageName, and getUserId (used in getNotificationChannels).
private static class PackageTestableContext extends ContextWrapper {
+ private PackageManager mPm;
private String mPackage;
private String mOpPackage;
private Integer mUserId;
@@ -446,6 +521,10 @@ public class NotificationManagerTest {
super(base);
}
+ void setPackageManager(@Nullable PackageManager pm) {
+ mPm = pm;
+ }
+
void setParameters(String packageName, String opPackageName, int userId) {
mPackage = packageName;
mOpPackage = opPackageName;
@@ -453,6 +532,12 @@ public class NotificationManagerTest {
}
@Override
+ public PackageManager getPackageManager() {
+ if (mPm != null) return mPm;
+ return super.getPackageManager();
+ }
+
+ @Override
public String getPackageName() {
if (mPackage != null) return mPackage;
return super.getPackageName();
diff --git a/core/tests/coretests/src/android/content/pm/RegisteredServicesCacheTest.java b/core/tests/coretests/src/android/content/pm/RegisteredServicesCacheTest.java
index 939bf2ec4b0a..ee4761b9d024 100644
--- a/core/tests/coretests/src/android/content/pm/RegisteredServicesCacheTest.java
+++ b/core/tests/coretests/src/android/content/pm/RegisteredServicesCacheTest.java
@@ -76,6 +76,10 @@ public class RegisteredServicesCacheTest extends AndroidTestCase {
mUsers = new ArrayList<>();
mUsers.add(new UserInfo(0, "Owner", UserInfo.FLAG_ADMIN));
mUsers.add(new UserInfo(1, "User1", 0));
+ addServiceInfoIntoResolveInfo(r1, "r1.package.name" /* packageName */,
+ "r1.service.name" /* serviceName */);
+ addServiceInfoIntoResolveInfo(r2, "r2.package.name" /* packageName */,
+ "r2.service.name" /* serviceName */);
}
public void testGetAllServicesHappyPath() {
@@ -218,6 +222,14 @@ public class RegisteredServicesCacheTest extends AndroidTestCase {
assertTrue("File should be created at " + file, file.length() > 0);
}
+ private void addServiceInfoIntoResolveInfo(ResolveInfo resolveInfo, String packageName,
+ String serviceName) {
+ final ServiceInfo serviceInfo = new ServiceInfo();
+ serviceInfo.packageName = packageName;
+ serviceInfo.name = serviceName;
+ resolveInfo.serviceInfo = serviceInfo;
+ }
+
/**
* Mock implementation of {@link android.content.pm.RegisteredServicesCache} for testing
*/
@@ -301,8 +313,8 @@ public class RegisteredServicesCacheTest extends AndroidTestCase {
}
@Override
- protected ServiceInfo<TestServiceType> parseServiceInfo(
- ResolveInfo resolveInfo, int userId) throws XmlPullParserException, IOException {
+ protected ServiceInfo<TestServiceType> parseServiceInfo(ResolveInfo resolveInfo,
+ long lastUpdateTime) throws XmlPullParserException, IOException {
int size = mServices.size();
for (int i = 0; i < size; i++) {
Map<ResolveInfo, ServiceInfo<TestServiceType>> map = mServices.valueAt(i);
diff --git a/core/tests/coretests/src/android/os/PerfettoTraceTest.java b/core/tests/coretests/src/android/os/PerfettoTraceTest.java
index 292f7500479b..ad28383689af 100644
--- a/core/tests/coretests/src/android/os/PerfettoTraceTest.java
+++ b/core/tests/coretests/src/android/os/PerfettoTraceTest.java
@@ -112,15 +112,14 @@ public class PerfettoTraceTest {
long ptr = nativeStartTracing(traceConfig.toByteArray());
- PerfettoTrackEventExtra extra = PerfettoTrackEventExtra.builder()
+ PerfettoTrace.instant(FOO_CATEGORY, "event")
.addFlow(2)
.addTerminatingFlow(3)
.addArg("long_val", 10000000000L)
.addArg("bool_val", true)
.addArg("double_val", 3.14)
.addArg("string_val", FOO)
- .build();
- PerfettoTrace.instant(FOO_CATEGORY, "event", extra);
+ .emit();
byte[] traceBytes = nativeStopTracing(ptr);
@@ -163,12 +162,12 @@ public class PerfettoTraceTest {
@Test
@RequiresFlagsEnabled(android.os.Flags.FLAG_PERFETTO_SDK_TRACING_V2)
- public void testDebugAnnotationsWithLamda() throws Exception {
+ public void testDebugAnnotationsWithLambda() throws Exception {
TraceConfig traceConfig = getTraceConfig(FOO);
long ptr = nativeStartTracing(traceConfig.toByteArray());
- PerfettoTrace.instant(FOO_CATEGORY, "event", e -> e.addArg("long_val", 123L));
+ PerfettoTrace.instant(FOO_CATEGORY, "event").addArg("long_val", 123L).emit();
byte[] traceBytes = nativeStopTracing(ptr);
@@ -203,15 +202,14 @@ public class PerfettoTraceTest {
long ptr = nativeStartTracing(traceConfig.toByteArray());
- PerfettoTrackEventExtra beginExtra = PerfettoTrackEventExtra.builder()
- .usingNamedTrack(FOO, PerfettoTrace.getProcessTrackUuid())
- .build();
- PerfettoTrace.begin(FOO_CATEGORY, "event", beginExtra);
+ PerfettoTrace.begin(FOO_CATEGORY, "event")
+ .usingNamedTrack(PerfettoTrace.getProcessTrackUuid(), FOO)
+ .emit();
- PerfettoTrackEventExtra endExtra = PerfettoTrackEventExtra.builder()
- .usingNamedTrack("bar", PerfettoTrace.getThreadTrackUuid(Process.myTid()))
- .build();
- PerfettoTrace.end(FOO_CATEGORY, endExtra);
+
+ PerfettoTrace.end(FOO_CATEGORY)
+ .usingNamedTrack(PerfettoTrace.getThreadTrackUuid(Process.myTid()), "bar")
+ .emit();
Trace trace = Trace.parseFrom(nativeStopTracing(ptr));
@@ -242,26 +240,67 @@ public class PerfettoTraceTest {
assertThat(hasTrackUuid).isTrue();
assertThat(mCategoryNames).contains(FOO);
assertThat(mTrackNames).contains(FOO);
+ assertThat(mTrackNames).contains("bar");
}
@Test
@RequiresFlagsEnabled(android.os.Flags.FLAG_PERFETTO_SDK_TRACING_V2)
- public void testCounter() throws Exception {
+ public void testProcessThreadNamedTrack() throws Exception {
TraceConfig traceConfig = getTraceConfig(FOO);
long ptr = nativeStartTracing(traceConfig.toByteArray());
- PerfettoTrackEventExtra intExtra = PerfettoTrackEventExtra.builder()
- .usingCounterTrack(FOO, PerfettoTrace.getProcessTrackUuid())
- .setCounter(16)
- .build();
- PerfettoTrace.counter(FOO_CATEGORY, intExtra);
+ PerfettoTrace.begin(FOO_CATEGORY, "event")
+ .usingProcessNamedTrack(FOO)
+ .emit();
- PerfettoTrackEventExtra doubleExtra = PerfettoTrackEventExtra.builder()
- .usingCounterTrack("bar", PerfettoTrace.getProcessTrackUuid())
- .setCounter(3.14)
- .build();
- PerfettoTrace.counter(FOO_CATEGORY, doubleExtra);
+
+ PerfettoTrace.end(FOO_CATEGORY)
+ .usingThreadNamedTrack(Process.myTid(), "%s-%s", "bar", "stool")
+ .emit();
+
+ Trace trace = Trace.parseFrom(nativeStopTracing(ptr));
+
+ boolean hasTrackEvent = false;
+ boolean hasTrackUuid = false;
+ for (TracePacket packet: trace.getPacketList()) {
+ TrackEvent event;
+ if (packet.hasTrackEvent()) {
+ hasTrackEvent = true;
+ event = packet.getTrackEvent();
+
+ if (TrackEvent.Type.TYPE_SLICE_BEGIN.equals(event.getType())
+ && event.hasTrackUuid()) {
+ hasTrackUuid = true;
+ }
+
+ if (TrackEvent.Type.TYPE_SLICE_END.equals(event.getType())
+ && event.hasTrackUuid()) {
+ hasTrackUuid &= true;
+ }
+ }
+
+ collectInternedData(packet);
+ collectTrackNames(packet);
+ }
+
+ assertThat(hasTrackEvent).isTrue();
+ assertThat(hasTrackUuid).isTrue();
+ assertThat(mCategoryNames).contains(FOO);
+ assertThat(mTrackNames).contains(FOO);
+ assertThat(mTrackNames).contains("bar-stool");
+ }
+
+ @Test
+ @RequiresFlagsEnabled(android.os.Flags.FLAG_PERFETTO_SDK_TRACING_V2)
+ public void testCounterSimple() throws Exception {
+ TraceConfig traceConfig = getTraceConfig(FOO);
+
+ long ptr = nativeStartTracing(traceConfig.toByteArray());
+
+ PerfettoTrace.counter(FOO_CATEGORY, 16, FOO).emit();
+
+ PerfettoTrace.counter(FOO_CATEGORY, 3.14, "bar").emit();
Trace trace = Trace.parseFrom(nativeStopTracing(ptr));
@@ -297,12 +336,102 @@ public class PerfettoTraceTest {
@Test
@RequiresFlagsEnabled(android.os.Flags.FLAG_PERFETTO_SDK_TRACING_V2)
+ public void testCounter() throws Exception {
+ TraceConfig traceConfig = getTraceConfig(FOO);
+
+ long ptr = nativeStartTracing(traceConfig.toByteArray());
+
+ PerfettoTrace.counter(FOO_CATEGORY, 16)
+ .usingCounterTrack(PerfettoTrace.getProcessTrackUuid(), FOO).emit();
+
+ PerfettoTrace.counter(FOO_CATEGORY, 3.14)
+ .usingCounterTrack(PerfettoTrace.getThreadTrackUuid(Process.myTid()),
+ "%s-%s", "bar", "stool").emit();
+
+ Trace trace = Trace.parseFrom(nativeStopTracing(ptr));
+
+ boolean hasTrackEvent = false;
+ boolean hasCounterValue = false;
+ boolean hasDoubleCounterValue = false;
+ for (TracePacket packet: trace.getPacketList()) {
+ TrackEvent event;
+ if (packet.hasTrackEvent()) {
+ hasTrackEvent = true;
+ event = packet.getTrackEvent();
+
+ if (TrackEvent.Type.TYPE_COUNTER.equals(event.getType())
+ && event.getCounterValue() == 16) {
+ hasCounterValue = true;
+ }
+
+ if (TrackEvent.Type.TYPE_COUNTER.equals(event.getType())
+ && event.getDoubleCounterValue() == 3.14) {
+ hasDoubleCounterValue = true;
+ }
+ }
+
+ collectTrackNames(packet);
+ }
+
+ assertThat(hasTrackEvent).isTrue();
+ assertThat(hasCounterValue).isTrue();
+ assertThat(hasDoubleCounterValue).isTrue();
+ assertThat(mTrackNames).contains(FOO);
+ assertThat(mTrackNames).contains("bar-stool");
+ }
+
+ @Test
+ @RequiresFlagsEnabled(android.os.Flags.FLAG_PERFETTO_SDK_TRACING_V2)
+ public void testProcessThreadCounter() throws Exception {
+ TraceConfig traceConfig = getTraceConfig(FOO);
+
+ long ptr = nativeStartTracing(traceConfig.toByteArray());
+
+ PerfettoTrace.counter(FOO_CATEGORY, 16).usingProcessCounterTrack(FOO).emit();
+
+ PerfettoTrace.counter(FOO_CATEGORY, 3.14)
+ .usingThreadCounterTrack(Process.myTid(), "%s-%s", "bar", "stool").emit();
+
+ Trace trace = Trace.parseFrom(nativeStopTracing(ptr));
+
+ boolean hasTrackEvent = false;
+ boolean hasCounterValue = false;
+ boolean hasDoubleCounterValue = false;
+ for (TracePacket packet: trace.getPacketList()) {
+ TrackEvent event;
+ if (packet.hasTrackEvent()) {
+ hasTrackEvent = true;
+ event = packet.getTrackEvent();
+
+ if (TrackEvent.Type.TYPE_COUNTER.equals(event.getType())
+ && event.getCounterValue() == 16) {
+ hasCounterValue = true;
+ }
+
+ if (TrackEvent.Type.TYPE_COUNTER.equals(event.getType())
+ && event.getDoubleCounterValue() == 3.14) {
+ hasDoubleCounterValue = true;
+ }
+ }
+
+ collectTrackNames(packet);
+ }
+
+ assertThat(hasTrackEvent).isTrue();
+ assertThat(hasCounterValue).isTrue();
+ assertThat(hasDoubleCounterValue).isTrue();
+ assertThat(mTrackNames).contains(FOO);
+ assertThat(mTrackNames).contains("bar-stool");
+ }
+
+ @Test
+ @RequiresFlagsEnabled(android.os.Flags.FLAG_PERFETTO_SDK_TRACING_V2)
public void testProto() throws Exception {
TraceConfig traceConfig = getTraceConfig(FOO);
long ptr = nativeStartTracing(traceConfig.toByteArray());
- PerfettoTrackEventExtra extra5 = PerfettoTrackEventExtra.builder()
+ PerfettoTrace.instant(FOO_CATEGORY, "event_proto")
.beginProto()
.beginNested(33L)
.addField(4L, 2L)
@@ -310,8 +439,7 @@ public class PerfettoTraceTest {
.endNested()
.addField(2001, "AIDL::IActivityManager")
.endProto()
- .build();
- PerfettoTrace.instant(FOO_CATEGORY, "event_proto", extra5);
+ .emit();
byte[] traceBytes = nativeStopTracing(ptr);
@@ -351,7 +479,7 @@ public class PerfettoTraceTest {
long ptr = nativeStartTracing(traceConfig.toByteArray());
- PerfettoTrackEventExtra extra6 = PerfettoTrackEventExtra.builder()
+ PerfettoTrace.instant(FOO_CATEGORY, "event_proto_nested")
.beginProto()
.beginNested(29L)
.beginNested(4L)
@@ -364,8 +492,7 @@ public class PerfettoTraceTest {
.endNested()
.endNested()
.endProto()
- .build();
- PerfettoTrace.instant(FOO_CATEGORY, "event_proto_nested", extra6);
+ .emit();
byte[] traceBytes = nativeStopTracing(ptr);
@@ -413,8 +540,7 @@ public class PerfettoTraceTest {
long ptr = nativeStartTracing(traceConfig.toByteArray());
- PerfettoTrackEventExtra extra = PerfettoTrackEventExtra.builder().build();
- PerfettoTrace.instant(FOO_CATEGORY, "event_trigger", extra);
+ PerfettoTrace.instant(FOO_CATEGORY, "event_trigger").emit();
PerfettoTrace.activateTrigger(FOO, 1000);
@@ -439,49 +565,21 @@ public class PerfettoTraceTest {
@Test
@RequiresFlagsEnabled(android.os.Flags.FLAG_PERFETTO_SDK_TRACING_V2)
- public void testMultipleExtras() throws Exception {
- boolean hasException = false;
- try {
- PerfettoTrackEventExtra.builder();
-
- // Unclosed extra will throw an exception here
- PerfettoTrackEventExtra.builder();
- } catch (Exception e) {
- hasException = true;
- }
-
- try {
- PerfettoTrackEventExtra.builder().build();
-
- // Closed extra but unused (reset hasn't been called internally) will throw an exception
- // here.
- PerfettoTrackEventExtra.builder();
- } catch (Exception e) {
- hasException &= true;
- }
-
- assertThat(hasException).isTrue();
- }
-
- @Test
- @RequiresFlagsEnabled(android.os.Flags.FLAG_PERFETTO_SDK_TRACING_V2)
public void testRegister() throws Exception {
TraceConfig traceConfig = getTraceConfig(BAR);
Category barCategory = new Category(BAR);
long ptr = nativeStartTracing(traceConfig.toByteArray());
- PerfettoTrackEventExtra beforeExtra = PerfettoTrackEventExtra.builder()
+ PerfettoTrace.instant(barCategory, "event")
.addArg("before", 1)
- .build();
- PerfettoTrace.instant(barCategory, "event", beforeExtra);
+ .emit();
barCategory.register();
- PerfettoTrackEventExtra afterExtra = PerfettoTrackEventExtra.builder()
+ PerfettoTrace.instant(barCategory, "event")
.addArg("after", 1)
- .build();
- PerfettoTrace.instant(barCategory, "event", afterExtra);
+ .emit();
byte[] traceBytes = nativeStopTracing(ptr);
diff --git a/core/tests/coretests/src/android/view/contentcapture/ContentCaptureSessionTest.java b/core/tests/coretests/src/android/view/contentcapture/ContentCaptureSessionTest.java
index f87b6994900f..ee8d428d8370 100644
--- a/core/tests/coretests/src/android/view/contentcapture/ContentCaptureSessionTest.java
+++ b/core/tests/coretests/src/android/view/contentcapture/ContentCaptureSessionTest.java
@@ -224,7 +224,7 @@ public class ContentCaptureSessionTest {
}
@Override
- void flush(int reason) {
+ void internalFlush(int reason) {
throw new UnsupportedOperationException("should not have been called");
}
diff --git a/core/tests/coretests/src/android/view/contentcapture/MainContentCaptureSessionTest.java b/core/tests/coretests/src/android/view/contentcapture/MainContentCaptureSessionTest.java
index 4a5123ec0663..a1d7f87614e4 100644
--- a/core/tests/coretests/src/android/view/contentcapture/MainContentCaptureSessionTest.java
+++ b/core/tests/coretests/src/android/view/contentcapture/MainContentCaptureSessionTest.java
@@ -263,7 +263,7 @@ public class MainContentCaptureSessionTest {
session.mEvents = new ArrayList<>(Arrays.asList(EVENT));
session.mDirectServiceInterface = mMockContentCaptureDirectManager;
- session.flush(REASON);
+ session.internalFlush(REASON);
mTestableLooper.processAllMessages();
verifyZeroInteractions(mMockContentProtectionEventProcessor);
@@ -280,7 +280,7 @@ public class MainContentCaptureSessionTest {
session.mEvents = new ArrayList<>(Arrays.asList(EVENT));
session.mDirectServiceInterface = mMockContentCaptureDirectManager;
- session.flush(REASON);
+ session.internalFlush(REASON);
mTestableLooper.processAllMessages();
verifyZeroInteractions(mMockContentProtectionEventProcessor);
@@ -298,7 +298,7 @@ public class MainContentCaptureSessionTest {
session.mEvents = new ArrayList<>(Arrays.asList(EVENT));
session.mDirectServiceInterface = mMockContentCaptureDirectManager;
- session.flush(REASON);
+ session.internalFlush(REASON);
mTestableLooper.processAllMessages();
verifyZeroInteractions(mMockContentProtectionEventProcessor);
@@ -316,7 +316,7 @@ public class MainContentCaptureSessionTest {
session.mEvents = new ArrayList<>(Arrays.asList(EVENT));
session.mDirectServiceInterface = mMockContentCaptureDirectManager;
- session.flush(REASON);
+ session.internalFlush(REASON);
mTestableLooper.processAllMessages();
verifyZeroInteractions(mMockContentProtectionEventProcessor);
@@ -544,7 +544,7 @@ public class MainContentCaptureSessionTest {
session.mContentCaptureHandler = null;
session.mDirectServiceInterface = null;
- session.flush(REASON);
+ session.internalFlush(REASON);
assertThat(session.mEvents).hasSize(1);
assertThat(session.mEventProcessQueue).isEmpty();
diff --git a/core/tests/coretests/src/com/android/internal/widget/NotificationProgressBarTest.java b/core/tests/coretests/src/com/android/internal/widget/NotificationProgressBarTest.java
index 9818e19cea02..ec19c0c52759 100644
--- a/core/tests/coretests/src/com/android/internal/widget/NotificationProgressBarTest.java
+++ b/core/tests/coretests/src/com/android/internal/widget/NotificationProgressBarTest.java
@@ -16,6 +16,8 @@
package com.android.internal.widget;
+import static com.android.internal.widget.NotificationProgressBar.NotEnoughWidthToFitAllPartsException;
+
import static com.google.common.truth.Truth.assertThat;
import android.app.Notification.ProgressStyle;
@@ -35,6 +37,7 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
@RunWith(AndroidJUnit4.class)
@@ -47,7 +50,7 @@ public class NotificationProgressBarTest {
int progress = 50;
int progressMax = 100;
- NotificationProgressBar.processAndConvertToViewParts(segments, points, progress,
+ NotificationProgressBar.processModelAndConvertToViewParts(segments, points, progress,
progressMax);
}
@@ -60,7 +63,7 @@ public class NotificationProgressBarTest {
int progress = 50;
int progressMax = 100;
- NotificationProgressBar.processAndConvertToViewParts(segments, points, progress,
+ NotificationProgressBar.processModelAndConvertToViewParts(segments, points, progress,
progressMax);
}
@@ -73,7 +76,7 @@ public class NotificationProgressBarTest {
int progress = 50;
int progressMax = 100;
- NotificationProgressBar.processAndConvertToViewParts(segments, points, progress,
+ NotificationProgressBar.processModelAndConvertToViewParts(segments, points, progress,
progressMax);
}
@@ -86,7 +89,7 @@ public class NotificationProgressBarTest {
int progress = 50;
int progressMax = 100;
- NotificationProgressBar.processAndConvertToViewParts(segments, points, progress,
+ NotificationProgressBar.processModelAndConvertToViewParts(segments, points, progress,
progressMax);
}
@@ -98,20 +101,21 @@ public class NotificationProgressBarTest {
int progress = -50;
int progressMax = 100;
- NotificationProgressBar.processAndConvertToViewParts(segments, points, progress,
+ NotificationProgressBar.processModelAndConvertToViewParts(segments, points, progress,
progressMax);
}
@Test
- public void processAndConvertToParts_progressIsZero() {
+ public void processAndConvertToParts_progressIsZero()
+ throws NotificationProgressBar.NotEnoughWidthToFitAllPartsException {
List<ProgressStyle.Segment> segments = new ArrayList<>();
segments.add(new ProgressStyle.Segment(100).setColor(Color.RED));
List<ProgressStyle.Point> points = new ArrayList<>();
int progress = 0;
int progressMax = 100;
- List<Part> parts = NotificationProgressBar.processAndConvertToViewParts(segments, points,
- progress, progressMax);
+ List<Part> parts = NotificationProgressBar.processModelAndConvertToViewParts(segments,
+ points, progress, progressMax);
List<Part> expectedParts = new ArrayList<>(List.of(new Segment(1f, Color.RED)));
@@ -123,8 +127,9 @@ public class NotificationProgressBarTest {
float pointRadius = 6;
boolean hasTrackerIcon = true;
- List<DrawablePart> drawableParts = NotificationProgressBar.processAndConvertToDrawableParts(
- parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
+ List<DrawablePart> drawableParts =
+ NotificationProgressBar.processPartsAndConvertToDrawableParts(
+ parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
List<DrawablePart> expectedDrawableParts = new ArrayList<>(
List.of(new DrawableSegment(0, 300, Color.RED)));
@@ -148,15 +153,16 @@ public class NotificationProgressBarTest {
}
@Test
- public void processAndConvertToParts_progressAtMax() {
+ public void processAndConvertToParts_progressAtMax()
+ throws NotEnoughWidthToFitAllPartsException {
List<ProgressStyle.Segment> segments = new ArrayList<>();
segments.add(new ProgressStyle.Segment(100).setColor(Color.RED));
List<ProgressStyle.Point> points = new ArrayList<>();
int progress = 100;
int progressMax = 100;
- List<Part> parts = NotificationProgressBar.processAndConvertToViewParts(segments, points,
- progress, progressMax);
+ List<Part> parts = NotificationProgressBar.processModelAndConvertToViewParts(segments,
+ points, progress, progressMax);
List<Part> expectedParts = new ArrayList<>(List.of(new Segment(1f, Color.RED)));
@@ -168,8 +174,9 @@ public class NotificationProgressBarTest {
float pointRadius = 6;
boolean hasTrackerIcon = true;
- List<DrawablePart> drawableParts = NotificationProgressBar.processAndConvertToDrawableParts(
- parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
+ List<DrawablePart> drawableParts =
+ NotificationProgressBar.processPartsAndConvertToDrawableParts(
+ parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
List<DrawablePart> expectedDrawableParts = new ArrayList<>(
List.of(new DrawableSegment(0, 300, Color.RED)));
@@ -195,7 +202,7 @@ public class NotificationProgressBarTest {
int progress = 150;
int progressMax = 100;
- NotificationProgressBar.processAndConvertToViewParts(segments, points, progress,
+ NotificationProgressBar.processModelAndConvertToViewParts(segments, points, progress,
progressMax);
}
@@ -208,7 +215,7 @@ public class NotificationProgressBarTest {
int progress = 50;
int progressMax = 100;
- NotificationProgressBar.processAndConvertToViewParts(segments, points, progress,
+ NotificationProgressBar.processModelAndConvertToViewParts(segments, points, progress,
progressMax);
}
@@ -221,12 +228,62 @@ public class NotificationProgressBarTest {
int progress = 50;
int progressMax = 100;
- NotificationProgressBar.processAndConvertToViewParts(segments, points, progress,
+ NotificationProgressBar.processModelAndConvertToViewParts(segments, points, progress,
progressMax);
}
@Test
- public void processAndConvertToParts_multipleSegmentsWithoutPoints() {
+ public void processAndConvertToParts_singleSegmentWithoutPoints()
+ throws NotEnoughWidthToFitAllPartsException {
+ List<ProgressStyle.Segment> segments = new ArrayList<>();
+ segments.add(new ProgressStyle.Segment(100).setColor(Color.BLUE));
+ List<ProgressStyle.Point> points = new ArrayList<>();
+ int progress = 60;
+ int progressMax = 100;
+
+ List<Part> parts = NotificationProgressBar.processModelAndConvertToViewParts(segments,
+ points, progress, progressMax);
+
+ List<Part> expectedParts = new ArrayList<>(
+ List.of(new Segment(1, Color.BLUE)));
+
+ assertThat(parts).isEqualTo(expectedParts);
+
+ float drawableWidth = 300;
+ float segSegGap = 4;
+ float segPointGap = 4;
+ float pointRadius = 6;
+ boolean hasTrackerIcon = true;
+
+ List<DrawablePart> drawableParts =
+ NotificationProgressBar.processPartsAndConvertToDrawableParts(
+ parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
+
+ List<DrawablePart> expectedDrawableParts = new ArrayList<>(
+ List.of(new DrawableSegment(0, 300, Color.BLUE)));
+
+ assertThat(drawableParts).isEqualTo(expectedDrawableParts);
+
+ float segmentMinWidth = 16;
+ boolean isStyledByProgress = true;
+
+ Pair<List<DrawablePart>, Float> p = NotificationProgressBar.maybeStretchAndRescaleSegments(
+ parts, drawableParts, segmentMinWidth, pointRadius, (float) progress / progressMax,
+ 300, isStyledByProgress, hasTrackerIcon ? 0 : segSegGap);
+
+ // Colors with 40% opacity
+ int fadedBlue = 0x660000FF;
+ expectedDrawableParts = new ArrayList<>(
+ List.of(new DrawableSegment(0, 180, Color.BLUE),
+ new DrawableSegment(180, 300, fadedBlue, true)));
+
+ assertThat(p.second).isEqualTo(180);
+ assertThat(p.first).isEqualTo(expectedDrawableParts);
+ }
+
+ @Test
+ public void processAndConvertToParts_multipleSegmentsWithoutPoints()
+ throws NotEnoughWidthToFitAllPartsException {
List<ProgressStyle.Segment> segments = new ArrayList<>();
segments.add(new ProgressStyle.Segment(50).setColor(Color.RED));
segments.add(new ProgressStyle.Segment(50).setColor(Color.GREEN));
@@ -234,8 +291,8 @@ public class NotificationProgressBarTest {
int progress = 60;
int progressMax = 100;
- List<Part> parts = NotificationProgressBar.processAndConvertToViewParts(segments, points,
- progress, progressMax);
+ List<Part> parts = NotificationProgressBar.processModelAndConvertToViewParts(segments,
+ points, progress, progressMax);
List<Part> expectedParts = new ArrayList<>(
List.of(new Segment(0.50f, Color.RED), new Segment(0.50f, Color.GREEN)));
@@ -248,8 +305,9 @@ public class NotificationProgressBarTest {
float pointRadius = 6;
boolean hasTrackerIcon = true;
- List<DrawablePart> drawableParts = NotificationProgressBar.processAndConvertToDrawableParts(
- parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
+ List<DrawablePart> drawableParts =
+ NotificationProgressBar.processPartsAndConvertToDrawableParts(
+ parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
List<DrawablePart> expectedDrawableParts = new ArrayList<>(
List.of(new DrawableSegment(0, 146, Color.RED),
@@ -274,15 +332,16 @@ public class NotificationProgressBarTest {
}
@Test
- public void processAndConvertToParts_multipleSegmentsWithoutPoints_noTracker() {
+ public void processAndConvertToParts_multipleSegmentsWithoutPoints_noTracker()
+ throws NotEnoughWidthToFitAllPartsException {
List<ProgressStyle.Segment> segments = new ArrayList<>();
segments.add(new ProgressStyle.Segment(50).setColor(Color.RED));
segments.add(new ProgressStyle.Segment(50).setColor(Color.GREEN));
List<ProgressStyle.Point> points = new ArrayList<>();
int progress = 60;
int progressMax = 100;
- List<Part> parts = NotificationProgressBar.processAndConvertToViewParts(segments, points,
- progress, progressMax);
+ List<Part> parts = NotificationProgressBar.processModelAndConvertToViewParts(segments,
+ points, progress, progressMax);
List<Part> expectedParts = new ArrayList<>(
List.of(new Segment(0.50f, Color.RED), new Segment(0.50f, Color.GREEN)));
@@ -295,8 +354,9 @@ public class NotificationProgressBarTest {
float pointRadius = 6;
boolean hasTrackerIcon = false;
- List<DrawablePart> drawableParts = NotificationProgressBar.processAndConvertToDrawableParts(
- parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
+ List<DrawablePart> drawableParts =
+ NotificationProgressBar.processPartsAndConvertToDrawableParts(
+ parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
List<DrawablePart> expectedDrawableParts = new ArrayList<>(
List.of(new DrawableSegment(0, 146, Color.RED),
@@ -321,7 +381,8 @@ public class NotificationProgressBarTest {
}
@Test
- public void processAndConvertToParts_singleSegmentWithPoints() {
+ public void processAndConvertToParts_singleSegmentWithPoints()
+ throws NotEnoughWidthToFitAllPartsException {
List<ProgressStyle.Segment> segments = new ArrayList<>();
segments.add(new ProgressStyle.Segment(100).setColor(Color.BLUE));
List<ProgressStyle.Point> points = new ArrayList<>();
@@ -332,8 +393,8 @@ public class NotificationProgressBarTest {
int progress = 60;
int progressMax = 100;
- List<Part> parts = NotificationProgressBar.processAndConvertToViewParts(segments, points,
- progress, progressMax);
+ List<Part> parts = NotificationProgressBar.processModelAndConvertToViewParts(segments,
+ points, progress, progressMax);
List<Part> expectedParts = new ArrayList<>(
List.of(new Segment(0.15f, Color.BLUE),
@@ -354,8 +415,9 @@ public class NotificationProgressBarTest {
float pointRadius = 6;
boolean hasTrackerIcon = true;
- List<DrawablePart> drawableParts = NotificationProgressBar.processAndConvertToDrawableParts(
- parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
+ List<DrawablePart> drawableParts =
+ NotificationProgressBar.processPartsAndConvertToDrawableParts(
+ parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
List<DrawablePart> expectedDrawableParts = new ArrayList<>(
List.of(new DrawableSegment(0, 35, Color.BLUE),
@@ -396,7 +458,8 @@ public class NotificationProgressBarTest {
}
@Test
- public void processAndConvertToParts_multipleSegmentsWithPoints() {
+ public void processAndConvertToParts_multipleSegmentsWithPoints()
+ throws NotEnoughWidthToFitAllPartsException {
List<ProgressStyle.Segment> segments = new ArrayList<>();
segments.add(new ProgressStyle.Segment(50).setColor(Color.RED));
segments.add(new ProgressStyle.Segment(50).setColor(Color.GREEN));
@@ -408,8 +471,8 @@ public class NotificationProgressBarTest {
int progress = 60;
int progressMax = 100;
- List<Part> parts = NotificationProgressBar.processAndConvertToViewParts(segments, points,
- progress, progressMax);
+ List<Part> parts = NotificationProgressBar.processModelAndConvertToViewParts(segments,
+ points, progress, progressMax);
List<Part> expectedParts = new ArrayList<>(
List.of(new Segment(0.15f, Color.RED),
@@ -430,8 +493,9 @@ public class NotificationProgressBarTest {
float segPointGap = 4;
float pointRadius = 6;
boolean hasTrackerIcon = true;
- List<DrawablePart> drawableParts = NotificationProgressBar.processAndConvertToDrawableParts(
- parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
+ List<DrawablePart> drawableParts =
+ NotificationProgressBar.processPartsAndConvertToDrawableParts(
+ parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
List<DrawablePart> expectedDrawableParts = new ArrayList<>(
List.of(new DrawableSegment(0, 35, Color.RED), new DrawablePoint(39, 51, Color.RED),
@@ -473,7 +537,8 @@ public class NotificationProgressBarTest {
}
@Test
- public void processAndConvertToParts_multipleSegmentsWithPointsAtStartAndEnd() {
+ public void processAndConvertToParts_multipleSegmentsWithPointsAtStartAndEnd()
+ throws NotEnoughWidthToFitAllPartsException {
List<ProgressStyle.Segment> segments = new ArrayList<>();
segments.add(new ProgressStyle.Segment(50).setColor(Color.RED));
segments.add(new ProgressStyle.Segment(50).setColor(Color.GREEN));
@@ -485,8 +550,8 @@ public class NotificationProgressBarTest {
int progress = 60;
int progressMax = 100;
- List<Part> parts = NotificationProgressBar.processAndConvertToViewParts(segments, points,
- progress, progressMax);
+ List<Part> parts = NotificationProgressBar.processModelAndConvertToViewParts(segments,
+ points, progress, progressMax);
List<Part> expectedParts = new ArrayList<>(
List.of(new Point(Color.RED),
@@ -505,8 +570,10 @@ public class NotificationProgressBarTest {
float segPointGap = 4;
float pointRadius = 6;
boolean hasTrackerIcon = true;
- List<DrawablePart> drawableParts = NotificationProgressBar.processAndConvertToDrawableParts(
- parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
+
+ List<DrawablePart> drawableParts =
+ NotificationProgressBar.processPartsAndConvertToDrawableParts(
+ parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
List<DrawablePart> expectedDrawableParts = new ArrayList<>(
List.of(new DrawablePoint(0, 12, Color.RED),
@@ -547,7 +614,8 @@ public class NotificationProgressBarTest {
// The points are so close to start/end that they would go out of bounds without the minimum
// segment width requirement.
@Test
- public void processAndConvertToParts_multipleSegmentsWithPointsNearStartAndEnd() {
+ public void processAndConvertToParts_multipleSegmentsWithPointsNearStartAndEnd()
+ throws NotEnoughWidthToFitAllPartsException {
List<ProgressStyle.Segment> segments = new ArrayList<>();
segments.add(new ProgressStyle.Segment(50).setColor(Color.RED));
segments.add(new ProgressStyle.Segment(50).setColor(Color.GREEN));
@@ -559,8 +627,8 @@ public class NotificationProgressBarTest {
int progress = 60;
int progressMax = 100;
- List<Part> parts = NotificationProgressBar.processAndConvertToViewParts(segments, points,
- progress, progressMax);
+ List<Part> parts = NotificationProgressBar.processModelAndConvertToViewParts(segments,
+ points, progress, progressMax);
List<Part> expectedParts = new ArrayList<>(
List.of(new Segment(0.01f, Color.RED),
@@ -581,8 +649,10 @@ public class NotificationProgressBarTest {
float segPointGap = 4;
float pointRadius = 6;
boolean hasTrackerIcon = true;
- List<DrawablePart> drawableParts = NotificationProgressBar.processAndConvertToDrawableParts(
- parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
+
+ List<DrawablePart> drawableParts =
+ NotificationProgressBar.processPartsAndConvertToDrawableParts(
+ parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
List<DrawablePart> expectedDrawableParts = new ArrayList<>(
List.of(new DrawableSegment(0, -7, Color.RED),
@@ -625,7 +695,8 @@ public class NotificationProgressBarTest {
}
@Test
- public void processAndConvertToParts_multipleSegmentsWithPoints_notStyledByProgress() {
+ public void processAndConvertToParts_multipleSegmentsWithPoints_notStyledByProgress()
+ throws NotEnoughWidthToFitAllPartsException {
List<ProgressStyle.Segment> segments = new ArrayList<>();
segments.add(new ProgressStyle.Segment(50).setColor(Color.RED));
segments.add(new ProgressStyle.Segment(50).setColor(Color.GREEN));
@@ -636,8 +707,8 @@ public class NotificationProgressBarTest {
int progress = 60;
int progressMax = 100;
- List<Part> parts = NotificationProgressBar.processAndConvertToViewParts(segments, points,
- progress, progressMax);
+ List<Part> parts = NotificationProgressBar.processModelAndConvertToViewParts(segments,
+ points, progress, progressMax);
List<Part> expectedParts = new ArrayList<>(
List.of(new Segment(0.15f, Color.RED), new Point(Color.RED),
@@ -653,8 +724,9 @@ public class NotificationProgressBarTest {
float pointRadius = 6;
boolean hasTrackerIcon = true;
- List<DrawablePart> drawableParts = NotificationProgressBar.processAndConvertToDrawableParts(
- parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
+ List<DrawablePart> drawableParts =
+ NotificationProgressBar.processPartsAndConvertToDrawableParts(
+ parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
List<DrawablePart> expectedDrawableParts = new ArrayList<>(
List.of(new DrawableSegment(0, 35, Color.RED), new DrawablePoint(39, 51, Color.RED),
@@ -691,7 +763,8 @@ public class NotificationProgressBarTest {
// The only difference from the `zeroWidthDrawableSegment` test below is the longer
// segmentMinWidth (= 16dp).
@Test
- public void maybeStretchAndRescaleSegments_negativeWidthDrawableSegment() {
+ public void maybeStretchAndRescaleSegments_negativeWidthDrawableSegment()
+ throws NotEnoughWidthToFitAllPartsException {
List<ProgressStyle.Segment> segments = new ArrayList<>();
segments.add(new ProgressStyle.Segment(100).setColor(Color.BLUE));
segments.add(new ProgressStyle.Segment(200).setColor(Color.BLUE));
@@ -702,8 +775,8 @@ public class NotificationProgressBarTest {
int progress = 1000;
int progressMax = 1000;
- List<Part> parts = NotificationProgressBar.processAndConvertToViewParts(segments, points,
- progress, progressMax);
+ List<Part> parts = NotificationProgressBar.processModelAndConvertToViewParts(segments,
+ points, progress, progressMax);
List<Part> expectedParts = new ArrayList<>(
List.of(new Point(Color.BLUE), new Segment(0.1f, Color.BLUE),
@@ -717,8 +790,10 @@ public class NotificationProgressBarTest {
float segPointGap = 4;
float pointRadius = 6;
boolean hasTrackerIcon = true;
- List<DrawablePart> drawableParts = NotificationProgressBar.processAndConvertToDrawableParts(
- parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
+
+ List<DrawablePart> drawableParts =
+ NotificationProgressBar.processPartsAndConvertToDrawableParts(
+ parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
List<DrawablePart> expectedDrawableParts = new ArrayList<>(
List.of(new DrawablePoint(0, 12, Color.BLUE),
@@ -749,7 +824,8 @@ public class NotificationProgressBarTest {
// The only difference from the `negativeWidthDrawableSegment` test above is the shorter
// segmentMinWidth (= 10dp).
@Test
- public void maybeStretchAndRescaleSegments_zeroWidthDrawableSegment() {
+ public void maybeStretchAndRescaleSegments_zeroWidthDrawableSegment()
+ throws NotEnoughWidthToFitAllPartsException {
List<ProgressStyle.Segment> segments = new ArrayList<>();
segments.add(new ProgressStyle.Segment(100).setColor(Color.BLUE));
segments.add(new ProgressStyle.Segment(200).setColor(Color.BLUE));
@@ -760,8 +836,8 @@ public class NotificationProgressBarTest {
int progress = 1000;
int progressMax = 1000;
- List<Part> parts = NotificationProgressBar.processAndConvertToViewParts(segments, points,
- progress, progressMax);
+ List<Part> parts = NotificationProgressBar.processModelAndConvertToViewParts(segments,
+ points, progress, progressMax);
List<Part> expectedParts = new ArrayList<>(
List.of(new Point(Color.BLUE), new Segment(0.1f, Color.BLUE),
@@ -775,8 +851,10 @@ public class NotificationProgressBarTest {
float segPointGap = 4;
float pointRadius = 6;
boolean hasTrackerIcon = true;
- List<DrawablePart> drawableParts = NotificationProgressBar.processAndConvertToDrawableParts(
- parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
+
+ List<DrawablePart> drawableParts =
+ NotificationProgressBar.processPartsAndConvertToDrawableParts(
+ parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
List<DrawablePart> expectedDrawableParts = new ArrayList<>(
List.of(new DrawablePoint(0, 12, Color.BLUE),
@@ -805,7 +883,8 @@ public class NotificationProgressBarTest {
}
@Test
- public void maybeStretchAndRescaleSegments_noStretchingNecessary() {
+ public void maybeStretchAndRescaleSegments_noStretchingNecessary()
+ throws NotEnoughWidthToFitAllPartsException {
List<ProgressStyle.Segment> segments = new ArrayList<>();
segments.add(new ProgressStyle.Segment(200).setColor(Color.BLUE));
segments.add(new ProgressStyle.Segment(100).setColor(Color.BLUE));
@@ -816,8 +895,8 @@ public class NotificationProgressBarTest {
int progress = 1000;
int progressMax = 1000;
- List<Part> parts = NotificationProgressBar.processAndConvertToViewParts(segments, points,
- progress, progressMax);
+ List<Part> parts = NotificationProgressBar.processModelAndConvertToViewParts(segments,
+ points, progress, progressMax);
List<Part> expectedParts = new ArrayList<>(
List.of(new Point(Color.BLUE), new Segment(0.2f, Color.BLUE),
@@ -832,8 +911,9 @@ public class NotificationProgressBarTest {
float pointRadius = 6;
boolean hasTrackerIcon = true;
- List<DrawablePart> drawableParts = NotificationProgressBar.processAndConvertToDrawableParts(
- parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
+ List<DrawablePart> drawableParts =
+ NotificationProgressBar.processPartsAndConvertToDrawableParts(
+ parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
List<DrawablePart> expectedDrawableParts = new ArrayList<>(
List.of(new DrawablePoint(0, 12, Color.BLUE),
@@ -854,4 +934,168 @@ public class NotificationProgressBarTest {
assertThat(p.second).isEqualTo(200);
assertThat(p.first).isEqualTo(expectedDrawableParts);
}
+
+ @Test(expected = NotEnoughWidthToFitAllPartsException.class)
+ public void maybeStretchAndRescaleSegments_notEnoughWidthToFitAllParts()
+ throws NotEnoughWidthToFitAllPartsException {
+ final int orange = 0xff7f50;
+ List<ProgressStyle.Segment> segments = new ArrayList<>();
+ segments.add(new ProgressStyle.Segment(10).setColor(orange));
+ segments.add(new ProgressStyle.Segment(10).setColor(Color.YELLOW));
+ segments.add(new ProgressStyle.Segment(10).setColor(Color.BLUE));
+ segments.add(new ProgressStyle.Segment(10).setColor(Color.GREEN));
+ segments.add(new ProgressStyle.Segment(10).setColor(Color.RED));
+ segments.add(new ProgressStyle.Segment(10).setColor(orange));
+ segments.add(new ProgressStyle.Segment(10).setColor(Color.YELLOW));
+ segments.add(new ProgressStyle.Segment(10).setColor(Color.BLUE));
+ segments.add(new ProgressStyle.Segment(10).setColor(Color.GREEN));
+ segments.add(new ProgressStyle.Segment(10).setColor(Color.RED));
+ List<ProgressStyle.Point> points = new ArrayList<>();
+ points.add(new ProgressStyle.Point(0).setColor(orange));
+ points.add(new ProgressStyle.Point(1).setColor(Color.BLUE));
+ points.add(new ProgressStyle.Point(55).setColor(Color.BLUE));
+ points.add(new ProgressStyle.Point(100).setColor(orange));
+ int progress = 50;
+ int progressMax = 100;
+
+ List<Part> parts = NotificationProgressBar.processModelAndConvertToViewParts(segments,
+ points, progress, progressMax);
+
+ List<Part> expectedParts = new ArrayList<>(
+ List.of(new Point(orange),
+ new Segment(0.01f, orange),
+ new Point(Color.BLUE),
+ new Segment(0.09f, orange),
+ new Segment(0.1f, Color.YELLOW),
+ new Segment(0.1f, Color.BLUE),
+ new Segment(0.1f, Color.GREEN),
+ new Segment(0.1f, Color.RED),
+ new Segment(0.05f, orange),
+ new Point(Color.BLUE),
+ new Segment(0.05f, orange),
+ new Segment(0.1f, Color.YELLOW),
+ new Segment(0.1f, Color.BLUE),
+ new Segment(0.1f, Color.GREEN),
+ new Segment(0.1f, Color.RED),
+ new Point(orange)));
+
+ assertThat(parts).isEqualTo(expectedParts);
+
+ // For the list of ProgressStyle.Part used in this test, 300 is the minimum width.
+ float drawableWidth = 299;
+ float segSegGap = 4;
+ float segPointGap = 4;
+ float pointRadius = 6;
+ boolean hasTrackerIcon = true;
+
+ List<DrawablePart> drawableParts =
+ NotificationProgressBar.processPartsAndConvertToDrawableParts(
+ parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
+
+ // Skips the validation of the intermediate list of DrawableParts.
+
+ float segmentMinWidth = 16;
+ boolean isStyledByProgress = true;
+
+ NotificationProgressBar.maybeStretchAndRescaleSegments(
+ parts, drawableParts, segmentMinWidth, pointRadius, (float) progress / progressMax,
+ 300, isStyledByProgress, hasTrackerIcon ? 0 : segSegGap);
+ }
+
+ @Test
+ public void processModelAndConvertToFinalDrawableParts_singleSegmentWithPoints()
+ throws NotEnoughWidthToFitAllPartsException {
+ List<ProgressStyle.Segment> segments = new ArrayList<>();
+ segments.add(new ProgressStyle.Segment(100).setColor(Color.BLUE));
+ List<ProgressStyle.Point> points = new ArrayList<>();
+ points.add(new ProgressStyle.Point(15).setColor(Color.RED));
+ points.add(new ProgressStyle.Point(25).setColor(Color.BLUE));
+ points.add(new ProgressStyle.Point(60).setColor(Color.BLUE));
+ points.add(new ProgressStyle.Point(75).setColor(Color.YELLOW));
+ int progress = 60;
+ int progressMax = 100;
+
+ float drawableWidth = 300;
+ float segSegGap = 4;
+ float segPointGap = 4;
+ float pointRadius = 6;
+ boolean hasTrackerIcon = true;
+
+ float segmentMinWidth = 16;
+ boolean isStyledByProgress = true;
+
+ Pair<List<DrawablePart>, Float> p =
+ NotificationProgressBar.processModelAndConvertToFinalDrawableParts(
+ segments,
+ points,
+ progress,
+ progressMax,
+ drawableWidth,
+ segSegGap,
+ segPointGap,
+ pointRadius,
+ hasTrackerIcon,
+ segmentMinWidth,
+ isStyledByProgress
+ );
+
+ // Colors with 40% opacity
+ int fadedBlue = 0x660000FF;
+ int fadedYellow = 0x66FFFF00;
+ List<DrawablePart> expectedDrawableParts = new ArrayList<>(
+ List.of(new DrawableSegment(0, 34.219177F, Color.BLUE),
+ new DrawablePoint(38.219177F, 50.219177F, Color.RED),
+ new DrawableSegment(54.219177F, 70.21918F, Color.BLUE),
+ new DrawablePoint(74.21918F, 86.21918F, Color.BLUE),
+ new DrawableSegment(90.21918F, 172.38356F, Color.BLUE),
+ new DrawablePoint(176.38356F, 188.38356F, Color.BLUE),
+ new DrawableSegment(192.38356F, 217.0137F, fadedBlue, true),
+ new DrawablePoint(221.0137F, 233.0137F, fadedYellow),
+ new DrawableSegment(237.0137F, 300F, fadedBlue, true)));
+
+ assertThat(p.second).isEqualTo(182.38356F);
+ assertThat(p.first).isEqualTo(expectedDrawableParts);
+ }
+
+ @Test
+ public void processModelAndConvertToFinalDrawableParts_singleSegmentWithoutPoints()
+ throws NotEnoughWidthToFitAllPartsException {
+ List<ProgressStyle.Segment> segments = new ArrayList<>();
+ segments.add(new ProgressStyle.Segment(100).setColor(Color.BLUE));
+ int progress = 60;
+ int progressMax = 100;
+
+ float drawableWidth = 100;
+ float segSegGap = 4;
+ float segPointGap = 4;
+ float pointRadius = 6;
+ boolean hasTrackerIcon = true;
+
+ float segmentMinWidth = 16;
+ boolean isStyledByProgress = true;
+
+ Pair<List<DrawablePart>, Float> p =
+ NotificationProgressBar.processModelAndConvertToFinalDrawableParts(
+ segments,
+ Collections.emptyList(),
+ progress,
+ progressMax,
+ drawableWidth,
+ segSegGap,
+ segPointGap,
+ pointRadius,
+ hasTrackerIcon,
+ segmentMinWidth,
+ isStyledByProgress
+ );
+
+ // Colors with 40% opacity
+ int fadedBlue = 0x660000FF;
+ List<DrawablePart> expectedDrawableParts = new ArrayList<>(
+ List.of(new DrawableSegment(0, 60.000004F, Color.BLUE),
+ new DrawableSegment(60.000004F, 100, fadedBlue, true)));
+
+ assertThat(p.second).isWithin(1e-5f).of(60);
+ assertThat(p.first).isEqualTo(expectedDrawableParts);
+ }
}
diff --git a/core/tests/coretests/src/com/android/internal/widget/NotificationProgressModelTest.java b/core/tests/coretests/src/com/android/internal/widget/NotificationProgressModelTest.java
index 962399e48fb8..e1f5b1c2e4a4 100644
--- a/core/tests/coretests/src/com/android/internal/widget/NotificationProgressModelTest.java
+++ b/core/tests/coretests/src/com/android/internal/widget/NotificationProgressModelTest.java
@@ -49,7 +49,8 @@ public class NotificationProgressModelTest {
new NotificationProgressModel(List.of(),
List.of(),
10,
- false);
+ false,
+ Color.TRANSPARENT);
}
@Test(expected = IllegalArgumentException.class)
@@ -58,7 +59,8 @@ public class NotificationProgressModelTest {
List.of(new Notification.ProgressStyle.Segment(50).setColor(Color.YELLOW)),
List.of(),
-1,
- false);
+ false,
+ Color.TRANSPARENT);
}
@Test
@@ -74,14 +76,15 @@ public class NotificationProgressModelTest {
// THEN
assertThat(restoredModel.getIndeterminateColor()).isEqualTo(Color.RED);
assertThat(restoredModel.isIndeterminate()).isTrue();
- assertThat(restoredModel.getProgress()).isEqualTo(-1);
+ assertThat(restoredModel.getProgress()).isEqualTo(0);
assertThat(restoredModel.getSegments()).isEmpty();
assertThat(restoredModel.getPoints()).isEmpty();
assertThat(restoredModel.isStyledByProgress()).isFalse();
+ assertThat(restoredModel.getSegmentsFallbackColor()).isEqualTo(Color.TRANSPARENT);
}
@Test
- public void save_and_restore_non_indeterminate_progress_model() {
+ public void save_and_restore_determinate_progress_model() {
// GIVEN
final List<Notification.ProgressStyle.Segment> segments = List.of(
new Notification.ProgressStyle.Segment(50).setColor(Color.YELLOW),
@@ -92,7 +95,8 @@ public class NotificationProgressModelTest {
final NotificationProgressModel savedModel = new NotificationProgressModel(segments,
points,
100,
- true);
+ true,
+ Color.RED);
final Bundle bundle = savedModel.toBundle();
@@ -106,6 +110,38 @@ public class NotificationProgressModelTest {
assertThat(restoredModel.getPoints()).isEqualTo(points);
assertThat(restoredModel.getProgress()).isEqualTo(100);
assertThat(restoredModel.isStyledByProgress()).isTrue();
- assertThat(restoredModel.getIndeterminateColor()).isEqualTo(-1);
+ assertThat(restoredModel.getSegmentsFallbackColor()).isEqualTo(Color.RED);
+ assertThat(restoredModel.getIndeterminateColor()).isEqualTo(Color.TRANSPARENT);
+ }
+
+ @Test
+ public void save_and_restore_non_determinate_progress_model_segments_fallback_color_invalid() {
+ // GIVEN
+ final List<Notification.ProgressStyle.Segment> segments = List.of(
+ new Notification.ProgressStyle.Segment(50).setColor(Color.YELLOW),
+ new Notification.ProgressStyle.Segment(50).setColor(Color.YELLOW));
+ final List<Notification.ProgressStyle.Point> points = List.of(
+ new Notification.ProgressStyle.Point(0).setColor(Color.RED),
+ new Notification.ProgressStyle.Point(20).setColor(Color.BLUE));
+ final NotificationProgressModel savedModel = new NotificationProgressModel(segments,
+ points,
+ 100,
+ true,
+ Color.TRANSPARENT);
+
+ final Bundle bundle = savedModel.toBundle();
+
+ // WHEN
+ final NotificationProgressModel restoredModel =
+ NotificationProgressModel.fromBundle(bundle);
+
+ // THEN
+ assertThat(restoredModel.isIndeterminate()).isFalse();
+ assertThat(restoredModel.getSegments()).isEqualTo(segments);
+ assertThat(restoredModel.getPoints()).isEqualTo(points);
+ assertThat(restoredModel.getProgress()).isEqualTo(100);
+ assertThat(restoredModel.isStyledByProgress()).isTrue();
+ assertThat(restoredModel.getSegmentsFallbackColor()).isEqualTo(Color.TRANSPARENT);
+ assertThat(restoredModel.getIndeterminateColor()).isEqualTo(Color.TRANSPARENT);
}
}
diff --git a/core/tests/utiltests/src/android/util/proto/ProtoFieldFilterTest.java b/core/tests/utiltests/src/android/util/proto/ProtoFieldFilterTest.java
new file mode 100644
index 000000000000..76d0aaaa4309
--- /dev/null
+++ b/core/tests/utiltests/src/android/util/proto/ProtoFieldFilterTest.java
@@ -0,0 +1,230 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.util.proto;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+
+/**
+ * Unit tests for {@link android.util.proto.ProtoFieldFilter}.
+ *
+ * Build/Install/Run:
+ * atest FrameworksCoreTests:ProtoFieldFilterTest
+ *
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class ProtoFieldFilterTest {
+
+ private static final class FieldTypes {
+ static final long INT64 = ProtoStream.FIELD_TYPE_INT64 | ProtoStream.FIELD_COUNT_SINGLE;
+ static final long FIXED64 = ProtoStream.FIELD_TYPE_FIXED64 | ProtoStream.FIELD_COUNT_SINGLE;
+ static final long BYTES = ProtoStream.FIELD_TYPE_BYTES | ProtoStream.FIELD_COUNT_SINGLE;
+ static final long FIXED32 = ProtoStream.FIELD_TYPE_FIXED32 | ProtoStream.FIELD_COUNT_SINGLE;
+ static final long MESSAGE = ProtoStream.FIELD_TYPE_MESSAGE | ProtoStream.FIELD_COUNT_SINGLE;
+ static final long INT32 = ProtoStream.FIELD_TYPE_INT32 | ProtoStream.FIELD_COUNT_SINGLE;
+ }
+
+ private static ProtoOutputStream createBasicTestProto() {
+ ProtoOutputStream out = new ProtoOutputStream();
+
+ out.writeInt64(ProtoStream.makeFieldId(1, FieldTypes.INT64), 12345L);
+ out.writeFixed64(ProtoStream.makeFieldId(2, FieldTypes.FIXED64), 0x1234567890ABCDEFL);
+ out.writeBytes(ProtoStream.makeFieldId(3, FieldTypes.BYTES), new byte[]{1, 2, 3, 4, 5});
+ out.writeFixed32(ProtoStream.makeFieldId(4, FieldTypes.FIXED32), 0xDEADBEEF);
+
+ return out;
+ }
+
+ private static byte[] filterProto(byte[] input, ProtoFieldFilter filter) throws IOException {
+ ByteArrayInputStream inputStream = new ByteArrayInputStream(input);
+ ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+ filter.filter(inputStream, outputStream);
+ return outputStream.toByteArray();
+ }
+
+ @Test
+ public void testNoFieldsFiltered() throws IOException {
+ byte[] input = createBasicTestProto().getBytes();
+ byte[] output = filterProto(input, new ProtoFieldFilter(fieldNumber -> true));
+ assertArrayEquals("No fields should be filtered out", input, output);
+ }
+
+ @Test
+ public void testAllFieldsFiltered() throws IOException {
+ byte[] input = createBasicTestProto().getBytes();
+ byte[] output = filterProto(input, new ProtoFieldFilter(fieldNumber -> false));
+
+ assertEquals("All fields should be filtered out", 0, output.length);
+ }
+
+ @Test
+ public void testSpecificFieldsFiltered() throws IOException {
+
+ ProtoOutputStream out = createBasicTestProto();
+ byte[] output = filterProto(out.getBytes(), new ProtoFieldFilter(n -> n != 2));
+
+ ProtoInputStream in = new ProtoInputStream(output);
+ boolean[] fieldsFound = new boolean[5];
+
+ int fieldNumber;
+ while ((fieldNumber = in.nextField()) != ProtoInputStream.NO_MORE_FIELDS) {
+ fieldsFound[fieldNumber] = true;
+ switch (fieldNumber) {
+ case 1:
+ assertEquals(12345L, in.readLong(ProtoStream.makeFieldId(1, FieldTypes.INT64)));
+ break;
+ case 2:
+ fail("Field 2 should be filtered out");
+ break;
+ case 3:
+ assertArrayEquals(new byte[]{1, 2, 3, 4, 5},
+ in.readBytes(ProtoStream.makeFieldId(3, FieldTypes.BYTES)));
+ break;
+ case 4:
+ assertEquals(0xDEADBEEF,
+ in.readInt(ProtoStream.makeFieldId(4, FieldTypes.FIXED32)));
+ break;
+ default:
+ fail("Unexpected field number: " + fieldNumber);
+ }
+ }
+
+ assertTrue("Field 1 should be present", fieldsFound[1]);
+ assertFalse("Field 2 should be filtered", fieldsFound[2]);
+ assertTrue("Field 3 should be present", fieldsFound[3]);
+ assertTrue("Field 4 should be present", fieldsFound[4]);
+ }
+
+ @Test
+ public void testDifferentWireTypes() throws IOException {
+ ProtoOutputStream out = new ProtoOutputStream();
+
+ out.writeInt64(ProtoStream.makeFieldId(1, FieldTypes.INT64), 12345L);
+ out.writeFixed64(ProtoStream.makeFieldId(2, FieldTypes.FIXED64), 0x1234567890ABCDEFL);
+ out.writeBytes(ProtoStream.makeFieldId(3, FieldTypes.BYTES), new byte[]{10, 20, 30});
+
+ long token = out.start(ProtoStream.makeFieldId(4, FieldTypes.MESSAGE));
+ out.writeInt32(ProtoStream.makeFieldId(1, FieldTypes.INT32), 42);
+ out.end(token);
+
+ out.writeFixed32(ProtoStream.makeFieldId(5, FieldTypes.FIXED32), 0xDEADBEEF);
+
+ byte[] output = filterProto(out.getBytes(), new ProtoFieldFilter(fieldNumber -> true));
+
+ ProtoInputStream in = new ProtoInputStream(output);
+ boolean[] fieldsFound = new boolean[6];
+
+ int fieldNumber;
+ while ((fieldNumber = in.nextField()) != ProtoInputStream.NO_MORE_FIELDS) {
+ fieldsFound[fieldNumber] = true;
+ switch (fieldNumber) {
+ case 1:
+ assertEquals(12345L, in.readLong(ProtoStream.makeFieldId(1, FieldTypes.INT64)));
+ break;
+ case 2:
+ assertEquals(0x1234567890ABCDEFL,
+ in.readLong(ProtoStream.makeFieldId(2, FieldTypes.FIXED64)));
+ break;
+ case 3:
+ assertArrayEquals(new byte[]{10, 20, 30},
+ in.readBytes(ProtoStream.makeFieldId(3, FieldTypes.BYTES)));
+ break;
+ case 4:
+ token = in.start(ProtoStream.makeFieldId(4, FieldTypes.MESSAGE));
+ assertTrue(in.nextField() == 1);
+ assertEquals(42, in.readInt(ProtoStream.makeFieldId(1, FieldTypes.INT32)));
+ assertTrue(in.nextField() == ProtoInputStream.NO_MORE_FIELDS);
+ in.end(token);
+ break;
+ case 5:
+ assertEquals(0xDEADBEEF,
+ in.readInt(ProtoStream.makeFieldId(5, FieldTypes.FIXED32)));
+ break;
+ default:
+ fail("Unexpected field number: " + fieldNumber);
+ }
+ }
+
+ assertTrue("All fields should be present",
+ fieldsFound[1] && fieldsFound[2] && fieldsFound[3]
+ && fieldsFound[4] && fieldsFound[5]);
+ }
+ @Test
+ public void testNestedMessagesUnfiltered() throws IOException {
+ ProtoOutputStream out = new ProtoOutputStream();
+
+ out.writeInt64(ProtoStream.makeFieldId(1, FieldTypes.INT64), 12345L);
+
+ long token = out.start(ProtoStream.makeFieldId(2, FieldTypes.MESSAGE));
+ out.writeInt32(ProtoStream.makeFieldId(1, FieldTypes.INT32), 6789);
+ out.writeFixed32(ProtoStream.makeFieldId(2, FieldTypes.FIXED32), 0xCAFEBABE);
+ out.end(token);
+
+ byte[] output = filterProto(out.getBytes(), new ProtoFieldFilter(n -> n != 2));
+
+ // Verify output
+ ProtoInputStream in = new ProtoInputStream(output);
+ boolean[] fieldsFound = new boolean[3];
+
+ int fieldNumber;
+ while ((fieldNumber = in.nextField()) != ProtoInputStream.NO_MORE_FIELDS) {
+ fieldsFound[fieldNumber] = true;
+ if (fieldNumber == 1) {
+ assertEquals(12345L, in.readLong(ProtoStream.makeFieldId(1, FieldTypes.INT64)));
+ } else {
+ fail("Unexpected field number: " + fieldNumber);
+ }
+ }
+
+ assertTrue("Field 1 should be present", fieldsFound[1]);
+ assertFalse("Field 2 should be filtered out", fieldsFound[2]);
+ }
+
+ @Test
+ public void testRepeatedFields() throws IOException {
+
+ ProtoOutputStream out = new ProtoOutputStream();
+ long fieldId = ProtoStream.makeFieldId(1,
+ ProtoStream.FIELD_TYPE_INT32 | ProtoStream.FIELD_COUNT_REPEATED);
+
+ out.writeRepeatedInt32(fieldId, 100);
+ out.writeRepeatedInt32(fieldId, 200);
+ out.writeRepeatedInt32(fieldId, 300);
+
+ byte[] input = out.getBytes();
+
+ byte[] output = filterProto(input, new ProtoFieldFilter(fieldNumber -> true));
+
+ assertArrayEquals("Repeated fields should be preserved", input, output);
+ }
+
+}
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index a30570a4cce5..b8059d08756a 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -613,6 +613,10 @@ applications that come with the platform
<permission name="android.permission.SUBSCRIBE_TO_KEYGUARD_LOCKED_STATE" />
<!-- Permission required for CTS test - CtsContentProviderMultiUserTest -->
<permission name="android.permission.RESOLVE_COMPONENT_FOR_UID"/>
+ <!-- Permission required for CTS test - MediaQualityTest -->
+ <permission name="android.permission.MANAGE_GLOBAL_PICTURE_QUALITY_SERVICE"/>
+ <permission name="android.permission.MANAGE_GLOBAL_SOUND_QUALITY_SERVICE"/>
+ <permission name="android.permission.READ_COLOR_ZONES"/>
</privapp-permissions>
<privapp-permissions package="com.android.statementservice">
diff --git a/graphics/java/android/graphics/BLASTBufferQueue.java b/graphics/java/android/graphics/BLASTBufferQueue.java
index 1c34e0d54908..9b9be244cf90 100644
--- a/graphics/java/android/graphics/BLASTBufferQueue.java
+++ b/graphics/java/android/graphics/BLASTBufferQueue.java
@@ -61,8 +61,10 @@ public final class BLASTBufferQueue {
/**
* Indicates that the client is waiting on buffer release
* due to no free buffers being available to render into.
+ * @param durationNanos The length of time in nanoseconds
+ * that the client was blocked on buffer release.
*/
- void onWaitForBufferRelease();
+ void onWaitForBufferRelease(long durationNanos);
}
/** Create a new connection with the surface flinger. */
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
index 4c7e47769613..d0d1721115cb 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -73,6 +73,7 @@ import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
+import android.os.OperationCanceledException;
import android.os.RemoteException;
import android.os.SystemProperties;
import android.util.ArrayMap;
@@ -1106,7 +1107,12 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
void onTaskFragmentError(@NonNull WindowContainerTransaction wct,
@Nullable IBinder errorCallbackToken, @Nullable TaskFragmentInfo taskFragmentInfo,
@TaskFragmentOperation.OperationType int opType, @NonNull Throwable exception) {
- Log.e(TAG, "onTaskFragmentError=" + exception.getMessage());
+ if (exception instanceof OperationCanceledException) {
+ // This is a non-fatal error and the operation just canceled.
+ Log.i(TAG, "operation canceled:" + exception.getMessage());
+ } else {
+ Log.e(TAG, "onTaskFragmentError=" + exception.getMessage(), exception);
+ }
switch (opType) {
case OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT:
case OP_TYPE_REPARENT_ACTIVITY_TO_TASK_FRAGMENT: {
diff --git a/libs/WindowManager/Shell/aconfig/multitasking.aconfig b/libs/WindowManager/Shell/aconfig/multitasking.aconfig
index b10b099c970b..e4210261a9fc 100644
--- a/libs/WindowManager/Shell/aconfig/multitasking.aconfig
+++ b/libs/WindowManager/Shell/aconfig/multitasking.aconfig
@@ -134,6 +134,16 @@ flag {
}
flag {
+ name: "enable_recents_bookend_transition"
+ namespace: "multitasking"
+ description: "Use a finish-transition to clean up recents instead of the finish-WCT"
+ bug: "346588978"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "bubble_view_info_executors"
namespace: "multitasking"
description: "Use executors to inflate bubble views"
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleControllerBubbleBarTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleControllerBubbleBarTest.kt
index 90ea7d35015e..dd387b382dc6 100644
--- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleControllerBubbleBarTest.kt
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleControllerBubbleBarTest.kt
@@ -51,6 +51,7 @@ import com.android.wm.shell.shared.bubbles.BubbleBarUpdate
import com.android.wm.shell.sysui.ShellCommandHandler
import com.android.wm.shell.sysui.ShellController
import com.android.wm.shell.sysui.ShellInit
+import com.android.wm.shell.taskview.TaskViewRepository
import com.android.wm.shell.taskview.TaskViewTransitions
import com.android.wm.shell.transition.Transitions
import com.google.common.truth.Truth.assertThat
@@ -282,6 +283,7 @@ class BubbleControllerBubbleBarTest {
mainExecutor,
mock<Handler>(),
bgExecutor,
+ mock<TaskViewRepository>(),
mock<TaskViewTransitions>(),
mock<Transitions>(),
SyncTransactionQueue(TransactionPool(), mainExecutor),
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt
index a7eebd6159e4..9d445f0bb80d 100644
--- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt
@@ -36,6 +36,8 @@ import com.android.internal.protolog.ProtoLog
import com.android.launcher3.icons.BubbleIconFactory
import com.android.wm.shell.Flags
import com.android.wm.shell.R
+import com.android.wm.shell.bubbles.BubbleStackView.SurfaceSynchronizer
+import com.android.wm.shell.bubbles.Bubbles.BubbleExpandListener
import com.android.wm.shell.bubbles.Bubbles.SysuiProxy
import com.android.wm.shell.bubbles.animation.AnimatableScaleMatrix
import com.android.wm.shell.common.FloatingContentCoordinator
@@ -75,6 +77,7 @@ class BubbleStackViewTest {
private lateinit var bubbleTaskViewFactory: BubbleTaskViewFactory
private lateinit var bubbleData: BubbleData
private lateinit var bubbleStackViewManager: FakeBubbleStackViewManager
+ private lateinit var surfaceSynchronizer: FakeSurfaceSynchronizer
private var sysuiProxy = mock<SysuiProxy>()
@Before
@@ -108,13 +111,14 @@ class BubbleStackViewTest {
bubbleStackViewManager = FakeBubbleStackViewManager()
expandedViewManager = FakeBubbleExpandedViewManager()
bubbleTaskViewFactory = FakeBubbleTaskViewFactory(context, shellExecutor)
+ surfaceSynchronizer = FakeSurfaceSynchronizer()
bubbleStackView =
BubbleStackView(
context,
bubbleStackViewManager,
positioner,
bubbleData,
- null,
+ surfaceSynchronizer,
FloatingContentCoordinator(),
{ sysuiProxy },
shellExecutor
@@ -309,6 +313,7 @@ class BubbleStackViewTest {
@Test
fun tapDifferentBubble_shouldReorder() {
+ surfaceSynchronizer.isActive = false
val bubble1 = createAndInflateChatBubble(key = "bubble1")
val bubble2 = createAndInflateChatBubble(key = "bubble2")
InstrumentationRegistry.getInstrumentation().runOnMainSync {
@@ -378,6 +383,147 @@ class BubbleStackViewTest {
.inOrder()
}
+ @Test
+ fun tapDifferentBubble_imeVisible_shouldWaitForIme() {
+ val bubble1 = createAndInflateChatBubble(key = "bubble1")
+ val bubble2 = createAndInflateChatBubble(key = "bubble2")
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {
+ bubbleStackView.addBubble(bubble1)
+ bubbleStackView.addBubble(bubble2)
+ }
+ InstrumentationRegistry.getInstrumentation().waitForIdleSync()
+
+ assertThat(bubbleStackView.bubbleCount).isEqualTo(2)
+ assertThat(bubbleData.bubbles).hasSize(2)
+ assertThat(bubbleData.selectedBubble).isEqualTo(bubble2)
+ assertThat(bubble2.iconView).isNotNull()
+
+ val expandListener = FakeBubbleExpandListener()
+ bubbleStackView.setExpandListener(expandListener)
+
+ var lastUpdate: BubbleData.Update? = null
+ val semaphore = Semaphore(0)
+ val listener =
+ BubbleData.Listener { update ->
+ lastUpdate = update
+ semaphore.release()
+ }
+ bubbleData.setListener(listener)
+
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {
+ bubble2.iconView!!.performClick()
+ assertThat(bubbleData.isExpanded).isTrue()
+
+ bubbleStackView.setSelectedBubble(bubble2)
+ bubbleStackView.isExpanded = true
+ shellExecutor.flushAll()
+ }
+
+ assertThat(semaphore.tryAcquire(5, TimeUnit.SECONDS)).isTrue()
+ assertThat(lastUpdate!!.expanded).isTrue()
+ assertThat(lastUpdate!!.bubbles.map { it.key })
+ .containsExactly("bubble2", "bubble1")
+ .inOrder()
+
+ // wait for idle to allow the animation to start
+ InstrumentationRegistry.getInstrumentation().waitForIdleSync()
+ // wait for the expansion animation to complete before interacting with the bubbles
+ PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(
+ AnimatableScaleMatrix.SCALE_X, AnimatableScaleMatrix.SCALE_Y)
+
+ // make the IME visible and tap on bubble1 to select it
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {
+ positioner.setImeVisible(true, 100)
+ bubble1.iconView!!.performClick()
+ // we have to set the selected bubble in the stack view manually because we don't have a
+ // listener wired up.
+ bubbleStackView.setSelectedBubble(bubble1)
+ shellExecutor.flushAll()
+ }
+
+ val onImeHidden = bubbleStackViewManager.onImeHidden
+ assertThat(onImeHidden).isNotNull()
+
+ assertThat(expandListener.bubblesExpandedState).isEqualTo(mapOf("bubble2" to true))
+
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {
+ onImeHidden!!.run()
+ shellExecutor.flushAll()
+ }
+
+ assertThat(expandListener.bubblesExpandedState)
+ .isEqualTo(mapOf("bubble1" to true, "bubble2" to false))
+ assertThat(semaphore.tryAcquire(5, TimeUnit.SECONDS)).isTrue()
+ assertThat(bubbleData.selectedBubble).isEqualTo(bubble1)
+ }
+
+ @Test
+ fun tapDifferentBubble_imeHidden_updatesImmediately() {
+ val bubble1 = createAndInflateChatBubble(key = "bubble1")
+ val bubble2 = createAndInflateChatBubble(key = "bubble2")
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {
+ bubbleStackView.addBubble(bubble1)
+ bubbleStackView.addBubble(bubble2)
+ }
+ InstrumentationRegistry.getInstrumentation().waitForIdleSync()
+
+ assertThat(bubbleStackView.bubbleCount).isEqualTo(2)
+ assertThat(bubbleData.bubbles).hasSize(2)
+ assertThat(bubbleData.selectedBubble).isEqualTo(bubble2)
+ assertThat(bubble2.iconView).isNotNull()
+
+ val expandListener = FakeBubbleExpandListener()
+ bubbleStackView.setExpandListener(expandListener)
+
+ var lastUpdate: BubbleData.Update? = null
+ val semaphore = Semaphore(0)
+ val listener =
+ BubbleData.Listener { update ->
+ lastUpdate = update
+ semaphore.release()
+ }
+ bubbleData.setListener(listener)
+
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {
+ bubble2.iconView!!.performClick()
+ assertThat(bubbleData.isExpanded).isTrue()
+
+ bubbleStackView.setSelectedBubble(bubble2)
+ bubbleStackView.isExpanded = true
+ shellExecutor.flushAll()
+ }
+
+ assertThat(semaphore.tryAcquire(5, TimeUnit.SECONDS)).isTrue()
+ assertThat(lastUpdate!!.expanded).isTrue()
+ assertThat(lastUpdate!!.bubbles.map { it.key })
+ .containsExactly("bubble2", "bubble1")
+ .inOrder()
+
+ // wait for idle to allow the animation to start
+ InstrumentationRegistry.getInstrumentation().waitForIdleSync()
+ // wait for the expansion animation to complete before interacting with the bubbles
+ PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(
+ AnimatableScaleMatrix.SCALE_X, AnimatableScaleMatrix.SCALE_Y)
+
+ // make the IME hidden and tap on bubble1 to select it
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {
+ positioner.setImeVisible(false, 0)
+ bubble1.iconView!!.performClick()
+ // we have to set the selected bubble in the stack view manually because we don't have a
+ // listener wired up.
+ bubbleStackView.setSelectedBubble(bubble1)
+ shellExecutor.flushAll()
+ }
+
+ val onImeHidden = bubbleStackViewManager.onImeHidden
+ assertThat(onImeHidden).isNull()
+
+ assertThat(expandListener.bubblesExpandedState)
+ .isEqualTo(mapOf("bubble1" to true, "bubble2" to false))
+ assertThat(semaphore.tryAcquire(5, TimeUnit.SECONDS)).isTrue()
+ assertThat(bubbleData.selectedBubble).isEqualTo(bubble1)
+ }
+
@EnableFlags(Flags.FLAG_ENABLE_OPTIONAL_BUBBLE_OVERFLOW)
@Test
fun testCreateStackView_noOverflowContents_noOverflow() {
@@ -563,4 +709,18 @@ class BubbleStackViewTest {
this.onImeHidden = onImeHidden
}
}
+
+ private class FakeBubbleExpandListener : BubbleExpandListener {
+ val bubblesExpandedState = mutableMapOf<String, Boolean>()
+ override fun onBubbleExpandChanged(isExpanding: Boolean, key: String) {
+ bubblesExpandedState[key] = isExpanding
+ }
+ }
+
+ private class FakeSurfaceSynchronizer : SurfaceSynchronizer {
+ var isActive = true
+ override fun syncSurfaceAndRun(callback: Runnable) {
+ if (isActive) callback.run()
+ }
+ }
}
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleViewInfoTaskTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleViewInfoTaskTest.kt
index a83327bbadee..f1ba0423b422 100644
--- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleViewInfoTaskTest.kt
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleViewInfoTaskTest.kt
@@ -49,6 +49,7 @@ import com.android.wm.shell.sysui.ShellCommandHandler
import com.android.wm.shell.sysui.ShellController
import com.android.wm.shell.sysui.ShellInit
import com.android.wm.shell.taskview.TaskView
+import com.android.wm.shell.taskview.TaskViewRepository
import com.android.wm.shell.taskview.TaskViewTransitions
import com.android.wm.shell.transition.Transitions
import com.google.common.truth.Truth.assertThat
@@ -155,6 +156,7 @@ class BubbleViewInfoTaskTest {
mainExecutor,
mock<Handler>(),
bgExecutor,
+ mock<TaskViewRepository>(),
mock<TaskViewTransitions>(),
mock<Transitions>(),
SyncTransactionQueue(TransactionPool(), mainExecutor),
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/FakeBubbleTaskViewFactory.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/FakeBubbleTaskViewFactory.kt
index 42b66aa29bfc..896f2ee5dd8d 100644
--- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/FakeBubbleTaskViewFactory.kt
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/FakeBubbleTaskViewFactory.kt
@@ -20,6 +20,7 @@ import android.app.ActivityManager
import android.content.Context
import com.android.wm.shell.common.ShellExecutor
import com.android.wm.shell.taskview.TaskView
+import com.android.wm.shell.taskview.TaskViewController
import com.android.wm.shell.taskview.TaskViewTaskController
import org.mockito.kotlin.mock
import org.mockito.kotlin.whenever
@@ -33,7 +34,7 @@ class FakeBubbleTaskViewFactory(
) : BubbleTaskViewFactory {
override fun create(): BubbleTaskView {
val taskViewTaskController = mock<TaskViewTaskController>()
- val taskView = TaskView(context, taskViewTaskController)
+ val taskView = TaskView(context, mock<TaskViewController>(), taskViewTaskController)
val taskInfo = mock<ActivityManager.RunningTaskInfo>()
whenever(taskViewTaskController.taskInfo).thenReturn(taskInfo)
return BubbleTaskView(taskView, mainExecutor)
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelperTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelperTest.kt
index 9e58b5be9d0d..d3cfbd00c4a3 100644
--- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelperTest.kt
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelperTest.kt
@@ -47,6 +47,7 @@ import com.android.wm.shell.bubbles.FakeBubbleExpandedViewManager
import com.android.wm.shell.bubbles.FakeBubbleFactory
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 java.util.concurrent.Semaphore
@@ -58,6 +59,7 @@ import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.kotlin.any
import org.mockito.kotlin.clearInvocations
+import org.mockito.kotlin.eq
import org.mockito.kotlin.mock
import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever
@@ -165,7 +167,9 @@ class BubbleBarAnimationHelperTest {
fun animateSwitch_bubbleToBubble_updateTaskBounds() {
val fromBubble = createBubble("from").initialize(container)
val toBubbleTaskController = mock<TaskViewTaskController>()
- val toBubble = createBubble("to", toBubbleTaskController).initialize(container)
+ val taskController = mock<TaskViewController>()
+ val toBubble = createBubble("to", taskController, toBubbleTaskController).initialize(
+ container)
activityScenario.onActivity {
animationHelper.animateSwitch(fromBubble, toBubble) {}
@@ -174,11 +178,11 @@ class BubbleBarAnimationHelperTest {
}
getInstrumentation().waitForIdleSync()
// Clear invocations to ensure that bounds update happens after animation ends
- clearInvocations(toBubbleTaskController)
+ clearInvocations(taskController)
getInstrumentation().runOnMainSync { animatorTestRule.advanceTimeBy(900) }
getInstrumentation().waitForIdleSync()
- verify(toBubbleTaskController).setWindowBounds(any())
+ verify(taskController).setTaskBounds(eq(toBubbleTaskController), any())
}
@Test
@@ -229,8 +233,9 @@ class BubbleBarAnimationHelperTest {
@Test
fun animateToRestPosition_updateTaskBounds() {
- val taskController = mock<TaskViewTaskController>()
- val bubble = createBubble("key", taskController).initialize(container)
+ val taskView = mock<TaskViewTaskController>()
+ val controller = mock<TaskViewController>()
+ val bubble = createBubble("key", controller, taskView).initialize(container)
val semaphore = Semaphore(0)
val after = Runnable { semaphore.release() }
@@ -247,11 +252,11 @@ class BubbleBarAnimationHelperTest {
animatorTestRule.advanceTimeBy(100)
}
// Clear invocations to ensure that bounds update happens after animation ends
- clearInvocations(taskController)
+ clearInvocations(controller)
getInstrumentation().runOnMainSync { animatorTestRule.advanceTimeBy(900) }
getInstrumentation().waitForIdleSync()
- verify(taskController).setWindowBounds(any())
+ verify(controller).setTaskBounds(eq(taskView), any())
}
@Test
@@ -329,9 +334,10 @@ class BubbleBarAnimationHelperTest {
private fun createBubble(
key: String,
+ taskViewController: TaskViewController = mock<TaskViewController>(),
taskViewTaskController: TaskViewTaskController = mock<TaskViewTaskController>(),
): Bubble {
- val taskView = TaskView(context, taskViewTaskController)
+ val taskView = TaskView(context, taskViewController, taskViewTaskController)
val taskInfo = mock<ActivityManager.RunningTaskInfo>()
whenever(taskViewTaskController.taskInfo).thenReturn(taskInfo)
val bubbleTaskView = BubbleTaskView(taskView, mainExecutor)
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewTest.kt
index fbbcff2dee92..7f65e22736b3 100644
--- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewTest.kt
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewTest.kt
@@ -46,6 +46,7 @@ import com.android.wm.shell.bubbles.UiEventSubject.Companion.assertThat
import com.android.wm.shell.common.TestShellExecutor
import com.android.wm.shell.shared.handles.RegionSamplingHelper
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 com.google.common.truth.Truth.assertWithMessage
@@ -356,7 +357,7 @@ class BubbleBarExpandedViewTest {
private inner class FakeBubbleTaskViewFactory : BubbleTaskViewFactory {
override fun create(): BubbleTaskView {
val taskViewTaskController = mock<TaskViewTaskController>()
- val taskView = TaskView(context, taskViewTaskController)
+ val taskView = TaskView(context, mock<TaskViewController>(), taskViewTaskController)
val taskInfo = mock<ActivityManager.RunningTaskInfo>()
whenever(taskViewTaskController.taskInfo).thenReturn(taskInfo)
return BubbleTaskView(taskView, mainExecutor)
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerViewTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerViewTest.kt
index 5c5dde7da351..a6492476176b 100644
--- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerViewTest.kt
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerViewTest.kt
@@ -64,6 +64,7 @@ import com.android.wm.shell.shared.bubbles.BubbleBarLocation
import com.android.wm.shell.sysui.ShellCommandHandler
import com.android.wm.shell.sysui.ShellController
import com.android.wm.shell.sysui.ShellInit
+import com.android.wm.shell.taskview.TaskViewRepository
import com.android.wm.shell.taskview.TaskViewTransitions
import com.android.wm.shell.transition.Transitions
import com.google.common.truth.Truth.assertThat
@@ -194,6 +195,7 @@ class BubbleBarLayerViewTest {
mainExecutor,
mock<Handler>(),
bgExecutor,
+ mock<TaskViewRepository>(),
mock<TaskViewTransitions>(),
mock<Transitions>(),
SyncTransactionQueue(TransactionPool(), mainExecutor),
diff --git a/libs/WindowManager/Shell/res/drawable/decor_desktop_mode_maximize_button_dark.xml b/libs/WindowManager/Shell/res/drawable/decor_desktop_mode_maximize_button_dark.xml
index e5fe1b5431eb..83a3959ee2f9 100644
--- a/libs/WindowManager/Shell/res/drawable/decor_desktop_mode_maximize_button_dark.xml
+++ b/libs/WindowManager/Shell/res/drawable/decor_desktop_mode_maximize_button_dark.xml
@@ -22,5 +22,6 @@
android:viewportWidth="960">
<path
android:fillColor="@android:color/black"
- android:pathData="M160,800Q127,800 103.5,776.5Q80,753 80,720L80,240Q80,207 103.5,183.5Q127,160 160,160L800,160Q833,160 856.5,183.5Q880,207 880,240L880,720Q880,753 856.5,776.5Q833,800 800,800L160,800ZM160,720L800,720Q800,720 800,720Q800,720 800,720L800,320L160,320L160,720Q160,720 160,720Q160,720 160,720Z"/>
+ android:pathData="M 244.79 796.408 C 222.79 796.408 203.79 788.74 187.79 773.408 C 172.457 757.408 164.79 738.408 164.79 716.408 L 164.79 236.408 C 164.79 214.408 172.457 195.741 187.79 180.408 C 203.79 164.408 222.79 156.408 244.79 156.408 L 724.79 156.408 C 746.79 156.408 765.458 164.408 780.79 180.408 C 796.79 195.741 804.79 214.408 804.79 236.408 L 804.79 716.408 C 804.79 738.408 796.79 757.408 780.79 773.408 C 765.458 788.74 746.79 796.408 724.79 796.408 Z M 244.79 716.408 L 724.79 716.408 L 724.79 236.408 L 244.79 236.408 Z M 244.79 236.408 L 244.79 716.408 Z"
+ />
</vector> \ No newline at end of file
diff --git a/libs/WindowManager/Shell/shared/Android.bp b/libs/WindowManager/Shell/shared/Android.bp
index d7669ed5cf34..0974930adfa2 100644
--- a/libs/WindowManager/Shell/shared/Android.bp
+++ b/libs/WindowManager/Shell/shared/Android.bp
@@ -78,3 +78,17 @@ java_library {
"com.android.window.flags.window-aconfig-java",
],
}
+
+// Things that can be shared with launcher3
+java_library {
+ name: "WindowManager-Shell-shared-AOSP",
+
+ sdk_version: "current",
+
+ srcs: [
+ "src/com/android/wm/shell/shared/bubbles/BubbleAnythingFlagHelper.java",
+ ],
+ static_libs: [
+ "com_android_wm_shell_flags_lib",
+ ],
+}
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 08e36927d978..30d679006c98 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
@@ -117,6 +117,8 @@ import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.taskview.TaskView;
+import com.android.wm.shell.taskview.TaskViewController;
+import com.android.wm.shell.taskview.TaskViewRepository;
import com.android.wm.shell.taskview.TaskViewTaskController;
import com.android.wm.shell.taskview.TaskViewTransitions;
import com.android.wm.shell.transition.Transitions;
@@ -192,7 +194,7 @@ public class BubbleController implements ConfigurationChangeListener,
private final TaskStackListenerImpl mTaskStackListener;
private final ShellTaskOrganizer mTaskOrganizer;
private final DisplayController mDisplayController;
- private final TaskViewTransitions mTaskViewTransitions;
+ private final TaskViewController mTaskViewController;
private final Transitions mTransitions;
private final SyncTransactionQueue mSyncQueue;
private final ShellController mShellController;
@@ -309,6 +311,7 @@ public class BubbleController implements ConfigurationChangeListener,
@ShellMainThread ShellExecutor mainExecutor,
@ShellMainThread Handler mainHandler,
@ShellBackgroundThread ShellExecutor bgExecutor,
+ TaskViewRepository taskViewRepository,
TaskViewTransitions taskViewTransitions,
Transitions transitions,
SyncTransactionQueue syncQueue,
@@ -347,7 +350,12 @@ public class BubbleController implements ConfigurationChangeListener,
context.getResources().getDimensionPixelSize(
com.android.internal.R.dimen.importance_ring_stroke_width));
mDisplayController = displayController;
- mTaskViewTransitions = taskViewTransitions;
+ if (TaskViewTransitions.useRepo()) {
+ mTaskViewController = new TaskViewTransitions(transitions, taskViewRepository,
+ organizer, syncQueue);
+ } else {
+ mTaskViewController = taskViewTransitions;
+ }
mTransitions = transitions;
mOneHandedOptional = oneHandedOptional;
mDragAndDropController = dragAndDropController;
@@ -359,8 +367,9 @@ public class BubbleController implements ConfigurationChangeListener,
@Override
public BubbleTaskView create() {
TaskViewTaskController taskViewTaskController = new TaskViewTaskController(
- context, organizer, taskViewTransitions, syncQueue);
- TaskView taskView = new TaskView(context, taskViewTaskController);
+ context, organizer, mTaskViewController, syncQueue);
+ TaskView taskView = new TaskView(context, mTaskViewController,
+ taskViewTaskController);
return new BubbleTaskView(taskView, mainExecutor);
}
};
@@ -843,14 +852,6 @@ public class BubbleController implements ConfigurationChangeListener,
return mTaskOrganizer;
}
- SyncTransactionQueue getSyncTransactionQueue() {
- return mSyncQueue;
- }
-
- TaskViewTransitions getTaskViewTransitions() {
- return mTaskViewTransitions;
- }
-
/** Contains information to help position things on the screen. */
@VisibleForTesting
public BubblePositioner getPositioner() {
@@ -1439,9 +1440,9 @@ public class BubbleController implements ConfigurationChangeListener,
*
* @param intent the intent for the bubble.
*/
- public void expandStackAndSelectBubble(Intent intent) {
+ public void expandStackAndSelectBubble(Intent intent, UserHandle user) {
if (!Flags.enableBubbleAnything()) return;
- Bubble b = mBubbleData.getOrCreateBubble(intent); // Removes from overflow
+ Bubble b = mBubbleData.getOrCreateBubble(intent, user); // Removes from overflow
ProtoLog.v(WM_SHELL_BUBBLES, "expandStackAndSelectBubble - intent=%s", intent);
if (b.isInflated()) {
mBubbleData.setSelectedBubbleAndExpandStack(b);
@@ -2648,8 +2649,8 @@ public class BubbleController implements ConfigurationChangeListener,
}
@Override
- public void showAppBubble(Intent intent) {
- mMainExecutor.execute(() -> mController.expandStackAndSelectBubble(intent));
+ public void showAppBubble(Intent intent, UserHandle user) {
+ mMainExecutor.execute(() -> mController.expandStackAndSelectBubble(intent, user));
}
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
index dc2025bb2dce..76d91ede7aa3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
@@ -461,10 +461,8 @@ public class BubbleData {
return bubbleToReturn;
}
- Bubble getOrCreateBubble(Intent intent) {
- UserHandle user = UserHandle.of(mCurrentUserId);
- String bubbleKey = Bubble.getAppBubbleKeyForApp(intent.getPackage(),
- user);
+ Bubble getOrCreateBubble(Intent intent, UserHandle user) {
+ String bubbleKey = Bubble.getAppBubbleKeyForApp(intent.getPackage(), user);
Bubble bubbleToReturn = findAndRemoveBubbleFromOverflow(bubbleKey);
if (bubbleToReturn == null) {
bubbleToReturn = Bubble.createAppBubble(intent, user, null, mMainExecutor, mBgExecutor);
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 979d958e3c5f..1094c290df06 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
@@ -2183,34 +2183,39 @@ public class BubbleStackView extends FrameLayout
ProtoLog.d(WM_SHELL_BUBBLES, "showNewlySelectedBubble b=%s, previouslySelected=%s,"
+ " mIsExpanded=%b", newlySelectedKey, previouslySelectedKey, mIsExpanded);
if (mIsExpanded) {
- hideCurrentInputMethod();
-
- if (Flags.enableRetrievableBubbles()) {
- if (mBubbleData.getBubbles().size() == 1) {
- // First bubble, check if overflow visibility needs to change
- updateOverflowVisibility();
+ Runnable onImeHidden = () -> {
+ if (Flags.enableRetrievableBubbles()) {
+ if (mBubbleData.getBubbles().size() == 1) {
+ // First bubble, check if overflow visibility needs to change
+ updateOverflowVisibility();
+ }
}
- }
- // Make the container of the expanded view transparent before removing the expanded view
- // from it. Otherwise a punch hole created by {@link android.view.SurfaceView} in the
- // expanded view becomes visible on the screen. See b/126856255
- mExpandedViewContainer.setAlpha(0.0f);
- mSurfaceSynchronizer.syncSurfaceAndRun(() -> {
- if (previouslySelected != null) {
- previouslySelected.setTaskViewVisibility(false);
- }
+ // Make the container of the expanded view transparent before removing the expanded
+ // view from it. Otherwise a punch hole created by {@link android.view.SurfaceView}
+ // in the expanded view becomes visible on the screen. See b/126856255
+ mExpandedViewContainer.setAlpha(0.0f);
+ mSurfaceSynchronizer.syncSurfaceAndRun(() -> {
+ if (previouslySelected != null) {
+ previouslySelected.setTaskViewVisibility(false);
+ }
- updateExpandedBubble();
- requestUpdate();
+ updateExpandedBubble();
+ requestUpdate();
- logBubbleEvent(previouslySelected,
- FrameworkStatsLog.BUBBLE_UICHANGED__ACTION__COLLAPSED);
- logBubbleEvent(bubbleToSelect,
- FrameworkStatsLog.BUBBLE_UICHANGED__ACTION__EXPANDED);
- notifyExpansionChanged(previouslySelected, false /* expanded */);
- notifyExpansionChanged(bubbleToSelect, true /* expanded */);
- });
+ logBubbleEvent(previouslySelected,
+ FrameworkStatsLog.BUBBLE_UICHANGED__ACTION__COLLAPSED);
+ logBubbleEvent(bubbleToSelect,
+ FrameworkStatsLog.BUBBLE_UICHANGED__ACTION__EXPANDED);
+ notifyExpansionChanged(previouslySelected, false /* expanded */);
+ notifyExpansionChanged(bubbleToSelect, true /* expanded */);
+ });
+ };
+ if (mPositioner.isImeVisible()) {
+ hideCurrentInputMethod(onImeHidden);
+ } else {
+ onImeHidden.run();
+ }
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl
index 9c2d35431554..0a4d79a6c297 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl
@@ -17,8 +17,9 @@
package com.android.wm.shell.bubbles;
import android.content.Intent;
-import android.graphics.Rect;
import android.content.pm.ShortcutInfo;
+import android.graphics.Rect;
+import android.os.UserHandle;
import com.android.wm.shell.bubbles.IBubblesListener;
import com.android.wm.shell.shared.bubbles.BubbleBarLocation;
@@ -52,7 +53,7 @@ interface IBubbles {
oneway void showShortcutBubble(in ShortcutInfo info) = 12;
- oneway void showAppBubble(in Intent intent) = 13;
+ oneway void showAppBubble(in Intent intent, in UserHandle user) = 13;
oneway void showExpandedView() = 14;
} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java
index 72be066fc7a7..e69d60ddd6c6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java
@@ -20,6 +20,7 @@ import android.annotation.Nullable;
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.Rect;
+import android.graphics.RectF;
import android.hardware.display.DisplayManager;
import android.hardware.display.DisplayTopology;
import android.os.RemoteException;
@@ -41,7 +42,9 @@ import com.android.wm.shell.shared.annotations.ShellMainThread;
import com.android.wm.shell.sysui.ShellInit;
import java.util.ArrayList;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
import java.util.Set;
/**
@@ -62,6 +65,7 @@ public class DisplayController {
private final SparseArray<DisplayRecord> mDisplays = new SparseArray<>();
private final ArrayList<OnDisplaysChangedListener> mDisplayChangedListeners = new ArrayList<>();
+ private final Map<Integer, RectF> mUnpopulatedDisplayBounds = new HashMap<>();
public DisplayController(Context context, IWindowManager wmService, ShellInit shellInit,
ShellExecutor mainExecutor, DisplayManager displayManager) {
@@ -193,7 +197,12 @@ public class DisplayController {
? mContext
: mContext.createDisplayContext(display);
final DisplayRecord record = new DisplayRecord(displayId);
- record.setDisplayLayout(context, new DisplayLayout(context, display));
+ DisplayLayout displayLayout = new DisplayLayout(context, display);
+ if (Flags.enableConnectedDisplaysWindowDrag()
+ && mUnpopulatedDisplayBounds.containsKey(displayId)) {
+ displayLayout.setGlobalBoundsDp(mUnpopulatedDisplayBounds.get(displayId));
+ }
+ record.setDisplayLayout(context, displayLayout);
mDisplays.put(displayId, record);
for (int i = 0; i < mDisplayChangedListeners.size(); ++i) {
mDisplayChangedListeners.get(i).onDisplayAdded(displayId);
@@ -231,10 +240,27 @@ public class DisplayController {
}
private void onDisplayTopologyChanged(DisplayTopology topology) {
- // TODO(b/381472611): Call DisplayTopology#getCoordinates and update values in
- // DisplayLayout when DM code is ready.
+ if (topology == null) {
+ return;
+ }
+ SparseArray<RectF> absoluteBounds = topology.getAbsoluteBounds();
+ mUnpopulatedDisplayBounds.clear();
+ for (int i = 0; i < absoluteBounds.size(); ++i) {
+ int displayId = absoluteBounds.keyAt(i);
+ DisplayLayout displayLayout = getDisplayLayout(displayId);
+ if (displayLayout == null) {
+ // onDisplayTopologyChanged can arrive before onDisplayAdded.
+ // Store the bounds to be applied later in onDisplayAdded.
+ Slog.d(TAG, "Storing bounds for onDisplayTopologyChanged on unknown"
+ + " display, displayId=" + displayId);
+ mUnpopulatedDisplayBounds.put(displayId, absoluteBounds.valueAt(i));
+ } else {
+ displayLayout.setGlobalBoundsDp(absoluteBounds.valueAt(i));
+ }
+ }
+
for (int i = 0; i < mDisplayChangedListeners.size(); ++i) {
- mDisplayChangedListeners.get(i).onTopologyChanged();
+ mDisplayChangedListeners.get(i).onTopologyChanged(topology);
}
}
@@ -429,6 +455,6 @@ public class DisplayController {
/**
* Called when the display topology has changed.
*/
- default void onTopologyChanged() {}
+ default void onTopologyChanged(DisplayTopology topology) {}
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/SyncTransactionQueue.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/SyncTransactionQueue.java
index bcd40a9a9765..c4696d5f44f4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/SyncTransactionQueue.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/SyncTransactionQueue.java
@@ -192,15 +192,22 @@ public final class SyncTransactionQueue {
throw new IllegalStateException("Sync Transactions must be serialized. In Flight: "
+ mInFlight.mId + " - " + mInFlight.mWCT);
}
- mInFlight = this;
if (DEBUG) Slog.d(TAG, "Sending sync transaction: " + mWCT);
- if (mLegacyTransition != null) {
- mId = new WindowOrganizer().startLegacyTransition(mLegacyTransition.getType(),
- mLegacyTransition.getAdapter(), this, mWCT);
- } else {
- mId = new WindowOrganizer().applySyncTransaction(mWCT, this);
+ try {
+ if (mLegacyTransition != null) {
+ mId = new WindowOrganizer().startLegacyTransition(mLegacyTransition.getType(),
+ mLegacyTransition.getAdapter(), this, mWCT);
+ } else {
+ mId = new WindowOrganizer().applySyncTransaction(mWCT, this);
+ }
+ } catch (RuntimeException e) {
+ Slog.e(TAG, "Send failed", e);
+ // Finish current sync callback immediately.
+ onTransactionReady(mId, new SurfaceControl.Transaction());
+ return;
}
if (DEBUG) Slog.d(TAG, " Sent sync transaction. Got id=" + mId);
+ mInFlight = this;
mMainExecutor.executeDelayed(mOnReplyTimeout, REPLY_TIMEOUT);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/IPip.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/IPip.aidl
index e77987963b48..37779077f9b6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/IPip.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/IPip.aidl
@@ -16,6 +16,7 @@
package com.android.wm.shell.common.pip;
+import android.app.ActivityManager;
import android.app.PictureInPictureParams;
import android.view.SurfaceControl;
import android.content.ComponentName;
@@ -41,9 +42,8 @@ interface IPip {
bounds
* @return destination bounds the PiP window should land into
*/
- Rect startSwipePipToHome(in ComponentName componentName, in ActivityInfo activityInfo,
- in PictureInPictureParams pictureInPictureParams,
- int launcherRotation, in Rect hotseatKeepClearArea) = 1;
+ Rect startSwipePipToHome(in ActivityManager.RunningTaskInfo taskInfo, int launcherRotation,
+ in Rect hotseatKeepClearArea) = 1;
/**
* Notifies the swiping Activity to PiP onto home transition is finished
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipMenuController.java
index 85353d307056..bad4a934adbf 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipMenuController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipMenuController.java
@@ -18,7 +18,6 @@ package com.android.wm.shell.common.pip;
import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
import static android.view.WindowManager.LayoutParams.FLAG_SLIPPERY;
-import static android.view.WindowManager.LayoutParams.FLAG_SPLIT_TOUCH;
import static android.view.WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
@@ -111,7 +110,7 @@ public interface PipMenuController {
int width, int height) {
final WindowManager.LayoutParams lp = new WindowManager.LayoutParams(width, height,
TYPE_APPLICATION_OVERLAY,
- FLAG_WATCH_OUTSIDE_TOUCH | FLAG_SPLIT_TOUCH | FLAG_SLIPPERY | FLAG_NOT_TOUCHABLE,
+ FLAG_WATCH_OUTSIDE_TOUCH | FLAG_SLIPPERY | FLAG_NOT_TOUCHABLE,
PixelFormat.TRANSLUCENT);
lp.privateFlags |= PRIVATE_FLAG_TRUSTED_OVERLAY;
lp.setTitle(title);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitWindowManager.java
index 89573ccef24b..84b710d52717 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitWindowManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitWindowManager.java
@@ -19,7 +19,6 @@ package com.android.wm.shell.common.split;
import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
import static android.view.WindowManager.LayoutParams.FLAG_SLIPPERY;
-import static android.view.WindowManager.LayoutParams.FLAG_SPLIT_TOUCH;
import static android.view.WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;
@@ -126,7 +125,7 @@ public final class SplitWindowManager extends WindowlessWindowManager {
WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
dividerBounds.width(), dividerBounds.height(), TYPE_DOCK_DIVIDER,
FLAG_NOT_FOCUSABLE | FLAG_NOT_TOUCH_MODAL | FLAG_WATCH_OUTSIDE_TOUCH
- | FLAG_SPLIT_TOUCH | FLAG_SLIPPERY,
+ | FLAG_SLIPPERY,
PixelFormat.TRANSLUCENT);
lp.token = new Binder();
lp.setTitle(mWindowName);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
index cbbe8a2b5613..84042591ad1b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
@@ -771,8 +771,9 @@ public abstract class WMShellBaseModule {
@WMSingleton
@Provides
static TaskViewTransitions provideTaskViewTransitions(Transitions transitions,
- TaskViewRepository repository) {
- return new TaskViewTransitions(transitions, repository);
+ TaskViewRepository repository, ShellTaskOrganizer organizer,
+ SyncTransactionQueue syncQueue) {
+ return new TaskViewTransitions(transitions, repository, organizer, syncQueue);
}
@WMSingleton
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index e8add56619c4..67e345365d26 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
@@ -133,6 +133,7 @@ import com.android.wm.shell.splitscreen.SplitScreenController;
import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
+import com.android.wm.shell.taskview.TaskViewRepository;
import com.android.wm.shell.taskview.TaskViewTransitions;
import com.android.wm.shell.transition.DefaultMixedHandler;
import com.android.wm.shell.transition.FocusTransitionObserver;
@@ -247,6 +248,7 @@ public abstract class WMShellModule {
@ShellMainThread ShellExecutor mainExecutor,
@ShellMainThread Handler mainHandler,
@ShellBackgroundThread ShellExecutor bgExecutor,
+ TaskViewRepository taskViewRepository,
TaskViewTransitions taskViewTransitions,
Transitions transitions,
SyncTransactionQueue syncQueue,
@@ -280,6 +282,7 @@ public abstract class WMShellModule {
mainExecutor,
mainHandler,
bgExecutor,
+ taskViewRepository,
taskViewTransitions,
transitions,
syncQueue,
@@ -1028,8 +1031,9 @@ public abstract class WMShellModule {
static CloseDesktopTaskTransitionHandler provideCloseDesktopTaskTransitionHandler(
Context context,
@ShellMainThread ShellExecutor mainExecutor,
- @ShellAnimationThread ShellExecutor animExecutor) {
- return new CloseDesktopTaskTransitionHandler(context, mainExecutor, animExecutor);
+ @ShellAnimationThread ShellExecutor animExecutor,
+ @ShellMainThread Handler handler) {
+ return new CloseDesktopTaskTransitionHandler(context, mainExecutor, animExecutor, handler);
}
@WMSingleton
@@ -1154,9 +1158,12 @@ public abstract class WMShellModule {
Context context,
ShellInit shellInit,
Transitions transitions,
- DesktopModeEventLogger desktopModeEventLogger) {
+ DesktopModeEventLogger desktopModeEventLogger,
+ Optional<DesktopTasksLimiter> desktopTasksLimiter,
+ ShellTaskOrganizer shellTaskOrganizer) {
return new DesktopModeLoggerTransitionObserver(
- context, shellInit, transitions, desktopModeEventLogger);
+ context, shellInit, transitions, desktopModeEventLogger,
+ desktopTasksLimiter, shellTaskOrganizer);
}
@WMSingleton
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
index c8d0dab39837..793bdf0b5614 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
@@ -83,13 +83,14 @@ public abstract class Pip2Module {
@NonNull PipTransitionState pipStackListenerController,
@NonNull PipDisplayLayoutState pipDisplayLayoutState,
@NonNull PipUiStateChangeController pipUiStateChangeController,
+ DisplayController displayController,
Optional<DesktopUserRepositories> desktopUserRepositoriesOptional,
Optional<DesktopWallpaperActivityTokenProvider>
desktopWallpaperActivityTokenProviderOptional) {
return new PipTransition(context, shellInit, shellTaskOrganizer, transitions,
pipBoundsState, null, pipBoundsAlgorithm, pipTaskListener,
pipScheduler, pipStackListenerController, pipDisplayLayoutState,
- pipUiStateChangeController, desktopUserRepositoriesOptional,
+ pipUiStateChangeController, displayController, desktopUserRepositoriesOptional,
desktopWallpaperActivityTokenProviderOptional);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/CloseDesktopTaskTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/CloseDesktopTaskTransitionHandler.kt
index 9b5a28916148..1ce093e02a4a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/CloseDesktopTaskTransitionHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/CloseDesktopTaskTransitionHandler.kt
@@ -23,8 +23,10 @@ import android.animation.ValueAnimator
import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
import android.content.Context
import android.graphics.Rect
+import android.os.Handler
import android.os.IBinder
import android.util.TypedValue
+import android.view.Choreographer
import android.view.SurfaceControl.Transaction
import android.view.WindowManager
import android.window.TransitionInfo
@@ -32,7 +34,10 @@ import android.window.TransitionRequestInfo
import android.window.WindowContainerTransaction
import androidx.core.animation.addListener
import com.android.app.animation.Interpolators
+import com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_CLOSE_TASK
+import com.android.internal.jank.InteractionJankMonitor
import com.android.wm.shell.common.ShellExecutor
+import com.android.wm.shell.shared.annotations.ShellMainThread
import com.android.wm.shell.transition.Transitions
import java.util.function.Supplier
@@ -44,9 +49,11 @@ constructor(
private val mainExecutor: ShellExecutor,
private val animExecutor: ShellExecutor,
private val transactionSupplier: Supplier<Transaction> = Supplier { Transaction() },
+ @ShellMainThread private val handler: Handler,
) : Transitions.TransitionHandler {
private val runningAnimations = mutableMapOf<IBinder, List<Animator>>()
+ private val interactionJankMonitor = InteractionJankMonitor.getInstance()
/** Returns null, as it only handles transitions started from Shell. */
override fun handleRequest(
@@ -71,18 +78,27 @@ constructor(
// All animations completed, finish the transition
runningAnimations.remove(transition)
finishCallback.onTransitionFinished(/* wct= */ null)
+ interactionJankMonitor.end(CUJ_DESKTOP_MODE_CLOSE_TASK)
}
}
}
+ val closingChanges =
+ info.changes.filter {
+ it.mode == WindowManager.TRANSIT_CLOSE &&
+ it.taskInfo?.windowingMode == WINDOWING_MODE_FREEFORM
+ }
animations +=
- info.changes
- .filter {
- it.mode == WindowManager.TRANSIT_CLOSE &&
- it.taskInfo?.windowingMode == WINDOWING_MODE_FREEFORM
- }
- .map { createCloseAnimation(it, finishTransaction, onAnimFinish) }
+ closingChanges.map { createCloseAnimation(it, finishTransaction, onAnimFinish) }
if (animations.isEmpty()) return false
runningAnimations[transition] = animations
+ closingChanges.lastOrNull()?.leash?.let { lastChangeLeash ->
+ interactionJankMonitor.begin(
+ lastChangeLeash,
+ context,
+ handler,
+ CUJ_DESKTOP_MODE_CLOSE_TASK,
+ )
+ }
animExecutor.execute { animations.forEach(Animator::start) }
return true
}
@@ -127,6 +143,7 @@ constructor(
.get()
.setPosition(change.leash, animBounds.left.toFloat(), animBounds.top.toFloat())
.setScale(change.leash, animScale, animScale)
+ .setFrameTimeline(Choreographer.getInstance().vsyncId)
.apply()
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt
index e8f9a789bb98..68bdbd1758b3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt
@@ -467,9 +467,13 @@ class DesktopModeEventLogger {
FrameworkStatsLog
.DESKTOP_MODE_SESSION_TASK_UPDATE__MINIMIZE_REASON__MINIMIZE_TASK_LIMIT
),
- MINIMIZE_BUTTON( // TODO(b/356843241): use this enum value
+ MINIMIZE_BUTTON(
FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE__MINIMIZE_REASON__MINIMIZE_BUTTON
),
+ KEY_GESTURE(
+ FrameworkStatsLog
+ .DESKTOP_MODE_SESSION_TASK_UPDATE__MINIMIZE_REASON__MINIMIZE_KEY_GESTURE
+ ),
}
// Default value used when the task was not unminimized.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandler.kt
index 1ddb834399cb..9334898fdb93 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandler.kt
@@ -30,6 +30,7 @@ import com.android.window.flags.Flags.enableTaskResizingKeyboardShortcuts
import com.android.wm.shell.ShellTaskOrganizer
import com.android.wm.shell.common.DisplayController
import com.android.wm.shell.common.ShellExecutor
+import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.MinimizeReason
import com.android.wm.shell.desktopmode.common.ToggleTaskSizeInteraction
import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
import com.android.wm.shell.shared.annotations.ShellMainThread
@@ -125,7 +126,9 @@ class DesktopModeKeyGestureHandler(
KeyGestureEvent.KEY_GESTURE_TYPE_MINIMIZE_FREEFORM_WINDOW -> {
logV("Key gesture MINIMIZE_FREEFORM_WINDOW is handled")
getGloballyFocusedFreeformTask()?.let {
- mainExecutor.execute { desktopTasksController.get().minimizeTask(it) }
+ mainExecutor.execute {
+ desktopTasksController.get().minimizeTask(it, MinimizeReason.KEY_GESTURE)
+ }
}
return true
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt
index c09504ee3725..2dd89c790b58 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt
@@ -36,6 +36,7 @@ import androidx.core.util.isNotEmpty
import androidx.core.util.plus
import androidx.core.util.putAll
import com.android.internal.protolog.ProtoLog
+import com.android.wm.shell.ShellTaskOrganizer
import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.EnterReason
import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ExitReason
import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.MinimizeReason
@@ -52,6 +53,8 @@ import com.android.wm.shell.shared.TransitionUtil
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
import com.android.wm.shell.sysui.ShellInit
import com.android.wm.shell.transition.Transitions
+import java.util.Optional
+import kotlin.jvm.optionals.getOrNull
/**
* A [Transitions.TransitionObserver] that observes transitions and the proposed changes to log
@@ -63,6 +66,8 @@ class DesktopModeLoggerTransitionObserver(
shellInit: ShellInit,
private val transitions: Transitions,
private val desktopModeEventLogger: DesktopModeEventLogger,
+ private val desktopTasksLimiter: Optional<DesktopTasksLimiter>,
+ private val shellTaskOrganizer: ShellTaskOrganizer,
) : Transitions.TransitionObserver {
init {
@@ -141,6 +146,7 @@ class DesktopModeLoggerTransitionObserver(
// identify if we need to log any changes and update the state of visible freeform tasks
identifyLogEventAndUpdateState(
+ transition = transition,
transitionInfo = info,
preTransitionVisibleFreeformTasks = visibleFreeformTaskInfos,
postTransitionVisibleFreeformTasks = postTransitionVisibleFreeformTasks,
@@ -227,6 +233,7 @@ class DesktopModeLoggerTransitionObserver(
* state and update it
*/
private fun identifyLogEventAndUpdateState(
+ transition: IBinder,
transitionInfo: TransitionInfo,
preTransitionVisibleFreeformTasks: SparseArray<TaskInfo>,
postTransitionVisibleFreeformTasks: SparseArray<TaskInfo>,
@@ -238,6 +245,7 @@ class DesktopModeLoggerTransitionObserver(
) {
// Sessions is finishing, log task updates followed by an exit event
identifyAndLogTaskUpdates(
+ transition,
transitionInfo,
preTransitionVisibleFreeformTasks,
postTransitionVisibleFreeformTasks,
@@ -255,6 +263,7 @@ class DesktopModeLoggerTransitionObserver(
desktopModeEventLogger.logSessionEnter(getEnterReason(transitionInfo))
identifyAndLogTaskUpdates(
+ transition,
transitionInfo,
preTransitionVisibleFreeformTasks,
postTransitionVisibleFreeformTasks,
@@ -262,6 +271,7 @@ class DesktopModeLoggerTransitionObserver(
} else if (isSessionActive) {
// Session is neither starting, nor finishing, log task updates if there are any
identifyAndLogTaskUpdates(
+ transition,
transitionInfo,
preTransitionVisibleFreeformTasks,
postTransitionVisibleFreeformTasks,
@@ -275,6 +285,7 @@ class DesktopModeLoggerTransitionObserver(
/** Compare the old and new state of taskInfos and identify and log the changes */
private fun identifyAndLogTaskUpdates(
+ transition: IBinder,
transitionInfo: TransitionInfo,
preTransitionVisibleFreeformTasks: SparseArray<TaskInfo>,
postTransitionVisibleFreeformTasks: SparseArray<TaskInfo>,
@@ -310,12 +321,9 @@ class DesktopModeLoggerTransitionObserver(
// find old tasks that were removed
preTransitionVisibleFreeformTasks.forEach { taskId, taskInfo ->
if (!postTransitionVisibleFreeformTasks.containsKey(taskId)) {
- val minimizeReason =
- if (transitionInfo.type == Transitions.TRANSIT_MINIMIZE) {
- MinimizeReason.MINIMIZE_BUTTON
- } else {
- null
- }
+ // The task is no longer visible, it might have been minimized, get the minimize
+ // reason (if any)
+ val minimizeReason = getMinimizeReason(transition, transitionInfo, taskInfo)
val taskUpdate =
buildTaskUpdateForTask(
taskInfo,
@@ -336,6 +344,21 @@ class DesktopModeLoggerTransitionObserver(
}
}
+ private fun getMinimizeReason(
+ transition: IBinder,
+ transitionInfo: TransitionInfo,
+ taskInfo: TaskInfo,
+ ): MinimizeReason? {
+ if (transitionInfo.type == Transitions.TRANSIT_MINIMIZE) {
+ return MinimizeReason.MINIMIZE_BUTTON
+ }
+ val minimizingTask = desktopTasksLimiter.getOrNull()?.getMinimizingTask(transition)
+ if (minimizingTask?.taskId == taskInfo.taskId) {
+ return minimizingTask.minimizeReason
+ }
+ return null
+ }
+
private fun buildTaskUpdateForTask(
taskInfo: TaskInfo,
visibleTasks: Int,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt
index c975533abf24..fa696682de28 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt
@@ -24,8 +24,8 @@ import android.util.SparseArray
import android.view.Display.INVALID_DISPLAY
import android.window.DesktopModeFlags
import androidx.core.util.forEach
-import androidx.core.util.keyIterator
import androidx.core.util.valueIterator
+import com.android.internal.annotations.VisibleForTesting
import com.android.internal.protolog.ProtoLog
import com.android.window.flags.Flags
import com.android.wm.shell.desktopmode.persistence.DesktopPersistentRepository
@@ -43,26 +43,36 @@ class DesktopRepository(
@ShellMainThread private val mainCoroutineScope: CoroutineScope,
val userId: Int,
) {
+ /** A display that supports desktops. */
+ private data class DesktopDisplay(
+ val displayId: Int,
+ val orderedDesks: MutableSet<Desk> = mutableSetOf(),
+ // TODO: b/389960283 - update on desk activation / deactivation.
+ var activeDeskId: Int? = null,
+ )
+
/**
- * Task data tracked per desktop.
+ * Task data tracked per desk.
*
- * @property activeTasks task ids of active tasks currently or previously visible in Desktop
- * mode session. Tasks become inactive when task closes or when desktop mode session ends.
+ * @property activeTasks task ids of active tasks currently or previously visible in the desk.
+ * Tasks become inactive when task closes or when the desk becomes inactive.
* @property visibleTasks task ids for active freeform tasks that are currently visible. There
- * might be other active tasks in desktop mode that are not visible.
+ * might be other active tasks in a desk that are not visible.
* @property minimizedTasks task ids for active freeform tasks that are currently minimized.
* @property closingTasks task ids for tasks that are going to close, but are currently visible.
* @property freeformTasksInZOrder list of current freeform task ids ordered from top to bottom
- * @property fullImmersiveTaskId the task id of the desktop task that is in full-immersive mode.
+ * @property fullImmersiveTaskId the task id of the desk's task that is in full-immersive mode.
* @property topTransparentFullscreenTaskId the task id of any current top transparent
- * fullscreen task launched on top of Desktop Mode. Cleared when the transparent task is
- * closed or sent to back. (top is at index 0).
+ * fullscreen task launched on top of the desk. Cleared when the transparent task is closed or
+ * sent to back. (top is at index 0).
* @property pipTaskId the task id of PiP task entered while in Desktop Mode.
- * @property pipShouldKeepDesktopActive whether an active PiP window should keep the Desktop
- * Mode session active. Only false when we are explicitly exiting Desktop Mode (via user
- * action) while there is an active PiP window.
+ * @property pipShouldKeepDesktopActive whether an active PiP window should keep the desk
+ * active. Only false when we are explicitly exiting Desktop Mode (via user action) while
+ * there is an active PiP window.
*/
- private data class DesktopTaskData(
+ private data class Desk(
+ val deskId: Int,
+ val displayId: Int,
val activeTasks: ArraySet<Int> = ArraySet(),
val visibleTasks: ArraySet<Int> = ArraySet(),
val minimizedTasks: ArraySet<Int> = ArraySet(),
@@ -72,10 +82,13 @@ class DesktopRepository(
var fullImmersiveTaskId: Int? = null,
var topTransparentFullscreenTaskId: Int? = null,
var pipTaskId: Int? = null,
+ // TODO: b/389960283 - consolidate this with [DesktopDisplay#activeDeskId].
var pipShouldKeepDesktopActive: Boolean = true,
) {
- fun deepCopy(): DesktopTaskData =
- DesktopTaskData(
+ fun deepCopy(): Desk =
+ Desk(
+ deskId = deskId,
+ displayId = displayId,
activeTasks = ArraySet(activeTasks),
visibleTasks = ArraySet(visibleTasks),
minimizedTasks = ArraySet(minimizedTasks),
@@ -87,6 +100,8 @@ class DesktopRepository(
pipShouldKeepDesktopActive = pipShouldKeepDesktopActive,
)
+ // TODO: b/362720497 - remove when multi-desktops is enabled where instances aren't
+ // reusable.
fun clear() {
activeTasks.clear()
visibleTasks.clear()
@@ -121,11 +136,11 @@ class DesktopRepository(
private var desktopGestureExclusionListener: Consumer<Region>? = null
private var desktopGestureExclusionExecutor: Executor? = null
- private val desktopTaskDataByDisplayId =
- object : SparseArray<DesktopTaskData>() {
- /** Gets [DesktopTaskData] for existing [displayId] or creates a new one. */
- fun getOrCreate(displayId: Int): DesktopTaskData =
- this[displayId] ?: DesktopTaskData().also { this[displayId] = it }
+ private val desktopData: DesktopData =
+ if (Flags.enableMultipleDesktopsBackend()) {
+ MultiDesktopData()
+ } else {
+ SingleDesktopData()
}
/** Adds [activeTasksListener] to be notified of updates to active tasks. */
@@ -136,10 +151,16 @@ class DesktopRepository(
/** Adds [visibleTasksListener] to be notified of updates to visible tasks. */
fun addVisibleTasksListener(visibleTasksListener: VisibleTasksListener, executor: Executor) {
visibleTasksListeners[visibleTasksListener] = executor
- desktopTaskDataByDisplayId.keyIterator().forEach {
- val visibleTaskCount = getVisibleTaskCount(it)
- executor.execute { visibleTasksListener.onTasksVisibilityChanged(it, visibleTaskCount) }
- }
+ desktopData
+ .desksSequence()
+ .groupBy { it.displayId }
+ .keys
+ .forEach { displayId ->
+ val visibleTaskCount = getVisibleTaskCount(displayId)
+ executor.execute {
+ visibleTasksListener.onTasksVisibilityChanged(displayId, visibleTaskCount)
+ }
+ }
}
/** Updates tasks changes on all the active task listeners for given display id. */
@@ -147,9 +168,8 @@ class DesktopRepository(
activeTasksListeners.onEach { it.onActiveTasksChanged(displayId) }
}
- /** Returns a list of all [DesktopTaskData] in the repository. */
- private fun desktopTaskDataSequence(): Sequence<DesktopTaskData> =
- desktopTaskDataByDisplayId.valueIterator().asSequence()
+ /** Returns a list of all [Desk]s in the repository. */
+ private fun desksSequence(): Sequence<Desk> = desktopData.desksSequence()
/** Adds [regionListener] to inform about changes to exclusion regions for all Desktop tasks. */
fun setExclusionRegionListener(regionListener: Consumer<Region>, executor: Executor) {
@@ -179,99 +199,183 @@ class DesktopRepository(
visibleTasksListeners.remove(visibleTasksListener)
}
- /** Adds task with [taskId] to the list of freeform tasks on [displayId]. */
+ /** Adds the given desk under the given display. */
+ fun addDesk(displayId: Int, deskId: Int) {
+ desktopData.getOrCreateDesk(displayId, deskId)
+ }
+
+ /** Returns the default desk in the given display. */
+ fun getDefaultDesk(displayId: Int): Int? = desktopData.getDefaultDesk(displayId)?.deskId
+
+ /** Sets the given desk as the active one in the given display. */
+ fun setActiveDesk(displayId: Int, deskId: Int) {
+ desktopData.setActiveDesk(displayId = displayId, deskId = deskId)
+ }
+
+ /**
+ * Adds task with [taskId] to the list of freeform tasks on [displayId]'s active desk.
+ *
+ * TODO: b/389960283 - add explicit [deskId] argument.
+ */
fun addTask(displayId: Int, taskId: Int, isVisible: Boolean) {
addOrMoveFreeformTaskToTop(displayId, taskId)
addActiveTask(displayId, taskId)
updateTask(displayId, taskId, isVisible)
}
- /** Adds task with [taskId] to the list of active tasks on [displayId]. */
+ /**
+ * Adds task with [taskId] to the list of active tasks on [displayId]'s active desk.
+ *
+ * TODO: b/389960283 - add explicit [deskId] argument.
+ */
private fun addActiveTask(displayId: Int, taskId: Int) {
- // Removes task if it is active on another display excluding [displayId].
- removeActiveTask(taskId, excludedDisplayId = displayId)
+ val activeDeskId =
+ desktopData.getActiveDesk(displayId)?.deskId
+ ?: error("Expected active desk in display: $displayId")
+
+ // Removes task if it is active on another desk excluding [activeDesk].
+ removeActiveTask(taskId, excludedDeskId = activeDeskId)
- if (desktopTaskDataByDisplayId.getOrCreate(displayId).activeTasks.add(taskId)) {
- logD("Adds active task=%d displayId=%d", taskId, displayId)
+ if (desktopData.getOrCreateDesk(displayId, activeDeskId).activeTasks.add(taskId)) {
+ logD("Adds active task=%d displayId=%d deskId=%d", taskId, displayId, activeDeskId)
updateActiveTasksListeners(displayId)
}
}
- /** Removes task from active task list of displays excluding the [excludedDisplayId]. */
- fun removeActiveTask(taskId: Int, excludedDisplayId: Int? = null) {
- desktopTaskDataByDisplayId.forEach { displayId, desktopTaskData ->
- if ((displayId != excludedDisplayId) && desktopTaskData.activeTasks.remove(taskId)) {
- logD("Removed active task=%d displayId=%d", taskId, displayId)
- updateActiveTasksListeners(displayId)
+ /** Removes task from active task list of desks excluding the [excludedDeskId]. */
+ @VisibleForTesting
+ fun removeActiveTask(taskId: Int, excludedDeskId: Int? = null) {
+ val affectedDisplays = mutableSetOf<Int>()
+ desktopData.forAllDesks { displayId, desk ->
+ if (desk.deskId != excludedDeskId && desk.activeTasks.remove(taskId)) {
+ logD(
+ "Removed active task=%d displayId=%d deskId=%d",
+ taskId,
+ displayId,
+ desk.deskId,
+ )
+ affectedDisplays.add(displayId)
}
}
+ affectedDisplays.forEach { displayId -> updateActiveTasksListeners(displayId) }
}
- /** Adds given task to the closing task list for [displayId]. */
+ /**
+ * Adds given task to the closing task list for [displayId]'s active desk.
+ *
+ * TODO: b/389960283 - add explicit [deskId] argument.
+ */
fun addClosingTask(displayId: Int, taskId: Int) {
- if (desktopTaskDataByDisplayId.getOrCreate(displayId).closingTasks.add(taskId)) {
- logD("Added closing task=%d displayId=%d", taskId, displayId)
+ val activeDeskId =
+ desktopData.getActiveDesk(displayId)?.deskId
+ ?: error("Expected active desk in display: $displayId")
+ if (desktopData.getOrCreateDesk(displayId, activeDeskId).closingTasks.add(taskId)) {
+ logD("Added closing task=%d displayId=%d deskId=%d", taskId, displayId, activeDeskId)
} else {
// If the task hasn't been removed from closing list after it disappeared.
- logW("Task with taskId=%d displayId=%d is already closing", taskId, displayId)
+ logW(
+ "Task with taskId=%d displayId=%d deskId=%d is already closing",
+ taskId,
+ displayId,
+ activeDeskId,
+ )
}
}
- /** Removes task from the list of closing tasks for [displayId]. */
+ /** Removes task from the list of closing tasks for all desks. */
fun removeClosingTask(taskId: Int) {
- desktopTaskDataByDisplayId.forEach { displayId, taskInfo ->
- if (taskInfo.closingTasks.remove(taskId)) {
- logD("Removed closing task=%d displayId=%d", taskId, displayId)
+ desktopData.forAllDesks { desk ->
+ if (desk.closingTasks.remove(taskId)) {
+ logD("Removed closing task=%d deskId=%d", taskId, desk.deskId)
}
}
}
- fun isActiveTask(taskId: Int) = desktopTaskDataSequence().any { taskId in it.activeTasks }
+ fun isActiveTask(taskId: Int) = desksSequence().any { taskId in it.activeTasks }
- fun isClosingTask(taskId: Int) = desktopTaskDataSequence().any { taskId in it.closingTasks }
+ fun isClosingTask(taskId: Int) = desksSequence().any { taskId in it.closingTasks }
- fun isVisibleTask(taskId: Int) = desktopTaskDataSequence().any { taskId in it.visibleTasks }
+ fun isVisibleTask(taskId: Int) = desksSequence().any { taskId in it.visibleTasks }
- fun isMinimizedTask(taskId: Int) = desktopTaskDataSequence().any { taskId in it.minimizedTasks }
+ fun isMinimizedTask(taskId: Int) = desksSequence().any { taskId in it.minimizedTasks }
- /** Checks if a task is the only visible, non-closing, non-minimized task on its display. */
+ /**
+ * Checks if a task is the only visible, non-closing, non-minimized task on the active desk of
+ * the given display, or any display's active desk if [displayId] is [INVALID_DISPLAY].
+ *
+ * TODO: b/389960283 - add explicit [deskId] argument.
+ */
fun isOnlyVisibleNonClosingTask(taskId: Int, displayId: Int = INVALID_DISPLAY): Boolean {
- val seq =
+ val activeDesks =
if (displayId != INVALID_DISPLAY) {
- sequenceOf(desktopTaskDataByDisplayId[displayId]).filterNotNull()
+ setOfNotNull(desktopData.getActiveDesk(displayId))
} else {
- desktopTaskDataSequence()
+ desktopData.getAllActiveDesks()
}
- return seq.any {
- it.visibleTasks.subtract(it.closingTasks).subtract(it.minimizedTasks).singleOrNull() ==
- taskId
+ return activeDesks.any { desk ->
+ desk.visibleTasks
+ .subtract(desk.closingTasks)
+ .subtract(desk.minimizedTasks)
+ .singleOrNull() == taskId
}
}
+ /**
+ * Returns the active tasks in the given display's active desk.
+ *
+ * TODO: b/389960283 - add explicit [deskId] argument.
+ */
+ @VisibleForTesting
fun getActiveTasks(displayId: Int): ArraySet<Int> =
- ArraySet(desktopTaskDataByDisplayId[displayId]?.activeTasks)
+ ArraySet(desktopData.getActiveDesk(displayId)?.activeTasks)
+ /**
+ * Returns the minimized tasks in the given display's active desk.
+ *
+ * TODO: b/389960283 - add explicit [deskId] argument.
+ */
fun getMinimizedTasks(displayId: Int): ArraySet<Int> =
- ArraySet(desktopTaskDataByDisplayId[displayId]?.minimizedTasks)
+ ArraySet(desktopData.getActiveDesk(displayId)?.minimizedTasks)
- /** Returns all active non-minimized tasks for [displayId] ordered from top to bottom. */
+ /**
+ * Returns all active non-minimized tasks for [displayId] ordered from top to bottom.
+ *
+ * TODO: b/389960283 - add explicit [deskId] argument.
+ */
fun getExpandedTasksOrdered(displayId: Int): List<Int> =
getFreeformTasksInZOrder(displayId).filter { !isMinimizedTask(it) }
- /** Returns the count of active non-minimized tasks for [displayId]. */
+ /**
+ * Returns the count of active non-minimized tasks for [displayId].
+ *
+ * TODO: b/389960283 - add explicit [deskId] argument.
+ */
fun getExpandedTaskCount(displayId: Int): Int {
return getActiveTasks(displayId).count { !isMinimizedTask(it) }
}
- /** Returns a list of freeform tasks, ordered from top-bottom (top at index 0). */
+ /**
+ * Returns a list of freeform tasks, ordered from top-bottom (top at index 0).
+ *
+ * TODO: b/389960283 - add explicit [deskId] argument.
+ */
+ @VisibleForTesting
fun getFreeformTasksInZOrder(displayId: Int): ArrayList<Int> =
- ArrayList(desktopTaskDataByDisplayId[displayId]?.freeformTasksInZOrder ?: emptyList())
+ ArrayList(desktopData.getActiveDesk(displayId)?.freeformTasksInZOrder ?: emptyList())
+
+ /** Returns the tasks inside the given desk. */
+ fun getActiveTaskIdsInDesk(deskId: Int): Set<Int> =
+ desktopData.getDesk(deskId)?.activeTasks?.toSet()
+ ?: run {
+ logW("getTasksInDesk: could not find desk: deskId=%d", deskId)
+ emptySet()
+ }
/** Removes task from visible tasks of all displays except [excludedDisplayId]. */
private fun removeVisibleTask(taskId: Int, excludedDisplayId: Int? = null) {
- desktopTaskDataByDisplayId.forEach { displayId, data ->
- if ((displayId != excludedDisplayId) && data.visibleTasks.remove(taskId)) {
- notifyVisibleTaskListeners(displayId, data.visibleTasks.size)
+ desktopData.forAllDesks { displayId, desk ->
+ if (displayId != excludedDisplayId && desk.visibleTasks.remove(taskId)) {
+ notifyVisibleTaskListeners(displayId, desk.visibleTasks.size)
}
}
}
@@ -281,6 +385,8 @@ class DesktopRepository(
*
* If task was visible on a different display with a different [displayId], removes from the set
* of visible tasks on that display and notifies listeners.
+ *
+ * TODO: b/389960283 - add explicit [deskId] argument.
*/
fun updateTask(displayId: Int, taskId: Int, isVisible: Boolean) {
logD("updateTask taskId=%d, displayId=%d, isVisible=%b", taskId, displayId, isVisible)
@@ -295,10 +401,11 @@ class DesktopRepository(
}
val prevCount = getVisibleTaskCount(displayId)
if (isVisible) {
- desktopTaskDataByDisplayId.getOrCreate(displayId).visibleTasks.add(taskId)
+ desktopData.getActiveDesk(displayId)?.visibleTasks?.add(taskId)
+ ?: error("Expected non-null active desk in display $displayId")
unminimizeTask(displayId, taskId)
} else {
- desktopTaskDataByDisplayId[displayId]?.visibleTasks?.remove(taskId)
+ desktopData.getActiveDesk(displayId)?.visibleTasks?.remove(taskId)
}
val newCount = getVisibleTaskCount(displayId)
if (prevCount != newCount) {
@@ -316,57 +423,94 @@ class DesktopRepository(
}
}
- /** Set whether the given task is the Desktop-entered PiP task in this display. */
+ /**
+ * Set whether the given task is the Desktop-entered PiP task in this display's active desk.
+ *
+ * TODO: b/389960283 - add explicit [deskId] argument.
+ */
fun setTaskInPip(displayId: Int, taskId: Int, enterPip: Boolean) {
- val desktopData = desktopTaskDataByDisplayId.getOrCreate(displayId)
+ val activeDesk =
+ desktopData.getActiveDesk(displayId)
+ ?: error("Expected active desk in display: $displayId")
if (enterPip) {
- desktopData.pipTaskId = taskId
- desktopData.pipShouldKeepDesktopActive = true
+ activeDesk.pipTaskId = taskId
+ activeDesk.pipShouldKeepDesktopActive = true
} else {
- desktopData.pipTaskId =
- if (desktopData.pipTaskId == taskId) null
+ activeDesk.pipTaskId =
+ if (activeDesk.pipTaskId == taskId) null
else {
logW(
- "setTaskInPip: taskId=$taskId did not match saved taskId=${desktopData.pipTaskId}"
+ "setTaskInPip: taskId=%d did not match saved taskId=%d",
+ taskId,
+ activeDesk.pipTaskId,
)
- desktopData.pipTaskId
+ activeDesk.pipTaskId
}
}
notifyVisibleTaskListeners(displayId, getVisibleTaskCount(displayId))
}
- /** Returns whether there is a PiP that was entered/minimized from Desktop in this display. */
+ /**
+ * Returns whether there is a PiP that was entered/minimized from Desktop in this display's
+ * active desk.
+ *
+ * TODO: b/389960283 - add explicit [deskId] argument.
+ */
fun isMinimizedPipPresentInDisplay(displayId: Int): Boolean =
- desktopTaskDataByDisplayId.getOrCreate(displayId).pipTaskId != null
+ desktopData.getActiveDesk(displayId)?.pipTaskId != null
- /** Returns whether the given task is the Desktop-entered PiP task in this display. */
+ /**
+ * Returns whether the given task is the Desktop-entered PiP task in this display's active desk.
+ *
+ * TODO: b/389960283 - add explicit [deskId] argument.
+ */
fun isTaskMinimizedPipInDisplay(displayId: Int, taskId: Int): Boolean =
- desktopTaskDataByDisplayId.getOrCreate(displayId).pipTaskId == taskId
+ desktopData.getActiveDesk(displayId)?.pipTaskId == taskId
- /** Returns whether Desktop session should be active in this display due to active PiP. */
+ /**
+ * Returns whether a desk should be active in this display due to active PiP.
+ *
+ * TODO: b/389960283 - add explicit [deskId] argument.
+ */
fun shouldDesktopBeActiveForPip(displayId: Int): Boolean =
Flags.enableDesktopWindowingPip() &&
isMinimizedPipPresentInDisplay(displayId) &&
- desktopTaskDataByDisplayId.getOrCreate(displayId).pipShouldKeepDesktopActive
+ (desktopData.getActiveDesk(displayId)?.pipShouldKeepDesktopActive ?: false)
- /** Saves whether a PiP window should keep Desktop session active in this display. */
+ /**
+ * Saves whether a PiP window should keep Desktop session active in this display.
+ *
+ * TODO: b/389960283 - add explicit [deskId] argument.
+ */
fun setPipShouldKeepDesktopActive(displayId: Int, keepActive: Boolean) {
- desktopTaskDataByDisplayId.getOrCreate(displayId).pipShouldKeepDesktopActive = keepActive
+ desktopData.getActiveDesk(displayId)?.pipShouldKeepDesktopActive = keepActive
}
- /** Saves callback to handle a pending PiP transition being aborted. */
- fun setOnPipAbortedCallback(callbackIfPipAborted: ((Int, Int) -> Unit)?) {
+ /**
+ * Saves callback to handle a pending PiP transition being aborted.
+ *
+ * TODO: b/389960283 - add explicit [deskId] argument.
+ */
+ fun setOnPipAbortedCallback(callbackIfPipAborted: ((displayId: Int, pipTaskId: Int) -> Unit)?) {
onPipAbortedCallback = callbackIfPipAborted
}
- /** Invokes callback to handle a pending PiP transition with the given task id being aborted. */
+ /**
+ * Invokes callback to handle a pending PiP transition with the given task id being aborted.
+ *
+ * TODO: b/389960283 - add explicit [deskId] argument.
+ */
fun onPipAborted(displayId: Int, pipTaskId: Int) {
onPipAbortedCallback?.invoke(displayId, pipTaskId)
}
- /** Set whether the given task is the full-immersive task in this display. */
+ /**
+ * Set whether the given task is the full-immersive task in this display's active desk.
+ *
+ * TODO: b/389960283 - add explicit [deskId] argument.
+ */
fun setTaskInFullImmersiveState(displayId: Int, taskId: Int, immersive: Boolean) {
- val desktopData = desktopTaskDataByDisplayId.getOrCreate(displayId)
+ val desktopData = desktopData.getActiveDesk(displayId) ?: return
if (immersive) {
desktopData.fullImmersiveTaskId = taskId
} else {
@@ -378,25 +522,41 @@ class DesktopRepository(
/* Whether the task is in full-immersive state. */
fun isTaskInFullImmersiveState(taskId: Int): Boolean {
- return desktopTaskDataSequence().any { taskId == it.fullImmersiveTaskId }
+ return desksSequence().any { taskId == it.fullImmersiveTaskId }
}
- /** Returns the task that is currently in immersive mode in this display, or null. */
+ /**
+ * Returns the task that is currently in immersive mode in this display, or null.
+ *
+ * TODO: b/389960283 - add explicit [deskId] argument.
+ */
fun getTaskInFullImmersiveState(displayId: Int): Int? =
- desktopTaskDataByDisplayId.getOrCreate(displayId).fullImmersiveTaskId
+ desktopData.getActiveDesk(displayId)?.fullImmersiveTaskId
- /** Sets the top transparent fullscreen task id for a given display. */
+ /**
+ * Sets the top transparent fullscreen task id for a given display's active desk.
+ *
+ * TODO: b/389960283 - add explicit [deskId] argument.
+ */
fun setTopTransparentFullscreenTaskId(displayId: Int, taskId: Int) {
- desktopTaskDataByDisplayId.getOrCreate(displayId).topTransparentFullscreenTaskId = taskId
+ desktopData.getActiveDesk(displayId)?.topTransparentFullscreenTaskId = taskId
}
- /** Returns the top transparent fullscreen task id for a given display, or null. */
+ /**
+ * Returns the top transparent fullscreen task id for a given display's active desk, or null.
+ *
+ * TODO: b/389960283 - add explicit [deskId] argument.
+ */
fun getTopTransparentFullscreenTaskId(displayId: Int): Int? =
- desktopTaskDataByDisplayId.getOrCreate(displayId).topTransparentFullscreenTaskId
+ desktopData.getActiveDesk(displayId)?.topTransparentFullscreenTaskId
- /** Clears the top transparent fullscreen task id info for a given display. */
+ /**
+ * Clears the top transparent fullscreen task id info for a given display's active desk.
+ *
+ * TODO: b/389960283 - add explicit [deskId] argument.
+ */
fun clearTopTransparentFullscreenTaskId(displayId: Int) {
- desktopTaskDataByDisplayId.getOrCreate(displayId).topTransparentFullscreenTaskId = null
+ desktopData.getActiveDesk(displayId)?.topTransparentFullscreenTaskId = null
}
private fun notifyVisibleTaskListeners(displayId: Int, visibleTasksCount: Int) {
@@ -409,22 +569,35 @@ class DesktopRepository(
}
}
- /** Gets number of visible freeform tasks on given [displayId] */
+ /**
+ * Gets number of visible freeform tasks on given [displayId]'s active desk.
+ *
+ * TODO: b/389960283 - add explicit [deskId] argument.
+ */
fun getVisibleTaskCount(displayId: Int): Int =
- desktopTaskDataByDisplayId[displayId]?.visibleTasks?.size
- ?: 0.also { logD("getVisibleTaskCount=$it") }
+ (desktopData.getActiveDesk(displayId)?.visibleTasks?.size ?: 0).also {
+ logD("getVisibleTaskCount=$it")
+ }
/**
* Adds task (or moves if it already exists) to the top of the ordered list.
*
* Unminimizes the task if it is minimized.
+ *
+ * TODO: b/389960283 - add explicit [deskId] argument.
*/
private fun addOrMoveFreeformTaskToTop(displayId: Int, taskId: Int) {
- logD("Add or move task to top: display=%d taskId=%d", taskId, displayId)
- desktopTaskDataByDisplayId.forEach { _, value ->
- value.freeformTasksInZOrder.remove(taskId)
- }
- desktopTaskDataByDisplayId.getOrCreate(displayId).freeformTasksInZOrder.add(0, taskId)
+ val activeDesk =
+ desktopData.getActiveDesk(displayId)
+ ?: error("Expected a desk to be active in display: $displayId")
+ logD(
+ "Add or move task to top: display=%d taskId=%d deskId=%d",
+ taskId,
+ displayId,
+ activeDesk.deskId,
+ )
+ desktopData.forAllDesks { _, desk -> desk.freeformTasksInZOrder.remove(taskId) }
+ activeDesk.freeformTasksInZOrder.add(0, taskId)
// Unminimize the task if it is minimized.
unminimizeTask(displayId, taskId)
if (DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_PERSISTENCE.isTrue()) {
@@ -432,7 +605,11 @@ class DesktopRepository(
}
}
- /** Minimizes the task for [taskId] and [displayId] */
+ /**
+ * Minimizes the task for [taskId] and [displayId]'s active display.
+ *
+ * TODO: b/389960283 - add explicit [deskId] argument.
+ */
fun minimizeTask(displayId: Int, taskId: Int) {
if (displayId == INVALID_DISPLAY) {
// When a task vanishes it doesn't have a displayId. Find the display of the task and
@@ -441,7 +618,8 @@ class DesktopRepository(
?: logW("Minimize task: No display id found for task: taskId=%d", taskId)
} else {
logD("Minimize Task: display=%d, task=%d", displayId, taskId)
- desktopTaskDataByDisplayId.getOrCreate(displayId).minimizedTasks.add(taskId)
+ desktopData.getActiveDesk(displayId)?.minimizedTasks?.add(taskId)
+ ?: logD("Minimize task: No active desk found for task: taskId=%d", taskId)
}
updateTask(displayId, taskId, isVisible = false)
if (DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_PERSISTENCE.isTrue()) {
@@ -449,26 +627,42 @@ class DesktopRepository(
}
}
- /** Unminimizes the task for [taskId] and [displayId] */
+ /**
+ * Unminimizes the task for [taskId] and [displayId].
+ *
+ * TODO: b/389960283 - consider adding an explicit [deskId] argument.
+ */
fun unminimizeTask(displayId: Int, taskId: Int) {
logD("Unminimize Task: display=%d, task=%d", displayId, taskId)
- desktopTaskDataByDisplayId[displayId]?.minimizedTasks?.remove(taskId)
- ?: logW("Unminimize Task: display=%d, task=%d, no task data", displayId, taskId)
+ var removed = false
+ desktopData.forAllDesks(displayId) { desk ->
+ if (desk.minimizedTasks.remove(taskId)) {
+ removed = true
+ }
+ }
+ if (!removed) {
+ logW("Unminimize Task: display=%d, task=%d, no task data", displayId, taskId)
+ }
}
private fun getDisplayIdForTask(taskId: Int): Int? {
- desktopTaskDataByDisplayId.forEach { displayId, data ->
- if (taskId in data.freeformTasksInZOrder) {
- return displayId
+ var displayForTask: Int? = null
+ desktopData.forAllDesks { displayId, desk ->
+ if (taskId in desk.freeformTasksInZOrder) {
+ displayForTask = displayId
}
}
- logW("No display id found for task: taskId=%d", taskId)
- return null
+ if (displayForTask == null) {
+ logW("No display id found for task: taskId=%d", taskId)
+ }
+ return displayForTask
}
/**
* Removes [taskId] from the respective display. If [INVALID_DISPLAY], the original display id
* will be looked up from the task id.
+ *
+ * TODO: b/389960283 - consider adding an explicit [deskId] argument.
*/
fun removeTask(displayId: Int, taskId: Int) {
logD("Removes freeform task: taskId=%d", taskId)
@@ -483,13 +677,17 @@ class DesktopRepository(
/** Removes given task from a valid [displayId] and updates the repository state. */
private fun removeTaskFromDisplay(displayId: Int, taskId: Int) {
logD("Removes freeform task: taskId=%d, displayId=%d", taskId, displayId)
- desktopTaskDataByDisplayId[displayId]?.freeformTasksInZOrder?.remove(taskId)
+ desktopData.forAllDesks(displayId) { desk ->
+ if (desk.freeformTasksInZOrder.remove(taskId)) {
+ logD(
+ "Remaining freeform tasks in desk: %d, tasks: %s",
+ desk.deskId,
+ desk.freeformTasksInZOrder.toDumpString(),
+ )
+ }
+ }
boundsBeforeMaximizeByTaskId.remove(taskId)
boundsBeforeFullImmersiveByTaskId.remove(taskId)
- logD(
- "Remaining freeform tasks: %s",
- desktopTaskDataByDisplayId[displayId]?.freeformTasksInZOrder?.toDumpString(),
- )
// Remove task from unminimized task if it is minimized.
unminimizeTask(displayId, taskId)
// Mark task as not in immersive if it was immersive.
@@ -502,15 +700,18 @@ class DesktopRepository(
}
/**
- * Removes the desktop for the given [displayId] and returns the active tasks on that desktop.
+ * Removes the active desk for the given [displayId] and returns the active tasks on that desk.
+ *
+ * TODO: b/389960283 - add explicit [deskId] argument.
*/
- fun removeDesktop(displayId: Int): ArraySet<Int> {
- if (!desktopTaskDataByDisplayId.contains(displayId)) {
- logW("Could not find desktop to remove: displayId=%d", displayId)
+ fun removeDesk(displayId: Int): ArraySet<Int> {
+ val desk = desktopData.getActiveDesk(displayId)
+ if (desk == null) {
+ logW("Could not find desk to remove: displayId=%d", displayId)
return ArraySet()
}
- val activeTasks = ArraySet(desktopTaskDataByDisplayId[displayId].activeTasks)
- desktopTaskDataByDisplayId[displayId].clear()
+ val activeTasks = ArraySet(desk.activeTasks)
+ desktopData.remove(desk.deskId)
return activeTasks
}
@@ -564,19 +765,20 @@ class DesktopRepository(
fun saveBoundsBeforeFullImmersive(taskId: Int, bounds: Rect) =
boundsBeforeFullImmersiveByTaskId.set(taskId, Rect(bounds))
+ /** TODO: b/389960283 - consider updating only the changing desks. */
private fun updatePersistentRepository(displayId: Int) {
- // Create a deep copy of the data
- desktopTaskDataByDisplayId[displayId]?.deepCopy()?.let { desktopTaskDataByDisplayIdCopy ->
- mainCoroutineScope.launch {
+ val desks = desktopData.desksSequence(displayId).map { desk -> desk.deepCopy() }.toList()
+ mainCoroutineScope.launch {
+ desks.forEach { desk ->
try {
persistentRepository.addOrUpdateDesktop(
- // Use display id as desktop id for now since only once desktop per display
+ // Use display id as desk id for now since only once desk per display
// is supported.
userId = userId,
- desktopId = displayId,
- visibleTasks = desktopTaskDataByDisplayIdCopy.visibleTasks,
- minimizedTasks = desktopTaskDataByDisplayIdCopy.minimizedTasks,
- freeformTasksInZOrder = desktopTaskDataByDisplayIdCopy.freeformTasksInZOrder,
+ desktopId = desk.deskId,
+ visibleTasks = desk.visibleTasks,
+ minimizedTasks = desk.minimizedTasks,
+ freeformTasksInZOrder = desk.freeformTasksInZOrder,
)
} catch (exception: Exception) {
logE(
@@ -598,20 +800,27 @@ class DesktopRepository(
private fun dumpDesktopTaskData(pw: PrintWriter, prefix: String) {
val innerPrefix = "$prefix "
- desktopTaskDataByDisplayId.forEach { displayId, data ->
- pw.println("${prefix}Display $displayId:")
- pw.println("${innerPrefix}activeTasks=${data.activeTasks.toDumpString()}")
- pw.println("${innerPrefix}visibleTasks=${data.visibleTasks.toDumpString()}")
- pw.println(
- "${innerPrefix}freeformTasksInZOrder=${data.freeformTasksInZOrder.toDumpString()}"
- )
- pw.println("${innerPrefix}minimizedTasks=${data.minimizedTasks.toDumpString()}")
- pw.println("${innerPrefix}fullImmersiveTaskId=${data.fullImmersiveTaskId}")
- pw.println(
- "${innerPrefix}topTransparentFullscreenTaskId=" +
- "${data.topTransparentFullscreenTaskId}"
- )
- }
+ desktopData
+ .desksSequence()
+ .groupBy { it.displayId }
+ .forEach { (displayId, desks) ->
+ pw.println("${prefix}Display #$displayId:")
+ desks.forEach { desk ->
+ pw.println("${innerPrefix}Desk #${desk.deskId}:")
+ pw.print("$innerPrefix activeTasks=")
+ pw.println(desk.activeTasks.toDumpString())
+ pw.print("$innerPrefix visibleTasks=")
+ pw.println(desk.visibleTasks.toDumpString())
+ pw.print("$innerPrefix freeformTasksInZOrder=")
+ pw.println(desk.freeformTasksInZOrder.toDumpString())
+ pw.print("$innerPrefix minimizedTasks=")
+ pw.println(desk.minimizedTasks.toDumpString())
+ pw.print("$innerPrefix fullImmersiveTaskId=")
+ pw.println(desk.fullImmersiveTaskId)
+ pw.print("$innerPrefix topTransparentFullscreenTaskId=")
+ pw.println(desk.topTransparentFullscreenTaskId)
+ }
+ }
}
/** Listens to changes for active tasks in desktop mode. */
@@ -624,6 +833,227 @@ class DesktopRepository(
fun onTasksVisibilityChanged(displayId: Int, visibleTasksCount: Int) {}
}
+ /** An interface for the desktop hierarchy's data managed by this repository. */
+ private interface DesktopData {
+ /**
+ * Returns the existing desk or creates a new entry if needed.
+ *
+ * TODO: 389787966 - consider removing this as it cannot be assumed a desk can be created in
+ * all devices / form-factors.
+ */
+ fun getOrCreateDesk(displayId: Int, deskId: Int): Desk
+
+ /** Returns the desk with the given id, or null if it does not exist. */
+ fun getDesk(deskId: Int): Desk?
+
+ /** Returns the active desk in this diplay, or null if none are active. */
+ fun getActiveDesk(displayId: Int): Desk?
+
+ /** Sets the given desk as the active desk in the given display. */
+ fun setActiveDesk(displayId: Int, deskId: Int)
+
+ /**
+ * Returns the default desk in the given display. Useful when the system wants to activate a
+ * desk but doesn't care about which one it activates (e.g. when putting a window into a
+ * desk using the App Handle). May return null if the display does not support desks.
+ *
+ * TODO: 389787966 - consider removing or renaming. In practice, this is needed for
+ * soon-to-be deprecated IDesktopMode APIs, adb commands or entry-points into the only
+ * desk (single-desk devices) or the most-recent desk (multi-desk devices).
+ */
+ fun getDefaultDesk(displayId: Int): Desk?
+
+ /** Returns all the active desks of all displays. */
+ fun getAllActiveDesks(): Set<Desk>
+
+ /** Returns the number of desks in the given display. */
+ fun getNumberOfDesks(displayId: Int): Int
+
+ /** Applies a function to all desks. */
+ fun forAllDesks(consumer: (Desk) -> Unit)
+
+ /** Applies a function to all desks. */
+ fun forAllDesks(consumer: (displayId: Int, Desk) -> Unit)
+
+ /** Applies a function to all desks under the given display. */
+ fun forAllDesks(displayId: Int, consumer: (Desk) -> Unit)
+
+ /** Returns a sequence of all desks. */
+ fun desksSequence(): Sequence<Desk>
+
+ /** Returns a sequence of all desks under the given display. */
+ fun desksSequence(displayId: Int): Sequence<Desk>
+
+ /** Remove an existing desk if it exists. */
+ fun remove(deskId: Int)
+
+ /** Returns the id of the display where the given desk is located. */
+ fun getDisplayForDesk(deskId: Int): Int
+ }
+
+ /**
+ * A [DesktopData] implementation that only supports one desk per display.
+ *
+ * Internally, it reuses the displayId as that display's single desk's id.
+ */
+ private class SingleDesktopData : DesktopData {
+ private val deskByDisplayId =
+ object : SparseArray<Desk>() {
+ /** Gets [Desk] for existing [displayId] or creates a new one. */
+ fun getOrCreate(displayId: Int): Desk =
+ this[displayId]
+ ?: Desk(deskId = displayId, displayId = displayId).also {
+ this[displayId] = it
+ }
+ }
+
+ override fun getOrCreateDesk(displayId: Int, deskId: Int): Desk {
+ check(displayId == deskId)
+ return deskByDisplayId.getOrCreate(displayId)
+ }
+
+ override fun getDesk(deskId: Int): Desk = getOrCreateDesk(deskId, deskId)
+
+ override fun getActiveDesk(displayId: Int): Desk {
+ // TODO: 389787966 - consider migrating to an "active" state instead of checking the
+ // number of visible active tasks, PIP in desktop, and empty desktop logic. In
+ // practice, existing single-desktop devices are ok with this function returning the
+ // only desktop, even if it's not active.
+ return deskByDisplayId.getOrCreate(displayId)
+ }
+
+ override fun setActiveDesk(displayId: Int, deskId: Int) {
+ // No-op, in single-desk setups, which desktop is "active" is determined by the
+ // existence of visible desktop windows, among other factors.
+ }
+
+ override fun getDefaultDesk(displayId: Int): Desk = getOrCreateDesk(displayId, displayId)
+
+ override fun getAllActiveDesks(): Set<Desk> =
+ deskByDisplayId.valueIterator().asSequence().toSet()
+
+ override fun getNumberOfDesks(displayId: Int): Int = 1
+
+ override fun forAllDesks(consumer: (Desk) -> Unit) {
+ deskByDisplayId.forEach { _, desk -> consumer(desk) }
+ }
+
+ override fun forAllDesks(consumer: (Int, Desk) -> Unit) {
+ deskByDisplayId.forEach { displayId, desk -> consumer(displayId, desk) }
+ }
+
+ override fun forAllDesks(displayId: Int, consumer: (Desk) -> Unit) {
+ consumer(getOrCreateDesk(displayId, displayId))
+ }
+
+ override fun desksSequence(): Sequence<Desk> = deskByDisplayId.valueIterator().asSequence()
+
+ override fun desksSequence(displayId: Int): Sequence<Desk> =
+ deskByDisplayId[displayId]?.let { sequenceOf(it) } ?: emptySequence()
+
+ override fun remove(deskId: Int) {
+ deskByDisplayId[deskId]?.clear()
+ }
+
+ override fun getDisplayForDesk(deskId: Int): Int = deskId
+ }
+
+ /** A [DesktopData] implementation that supports multiple desks. */
+ private class MultiDesktopData : DesktopData {
+ private val desktopDisplays = SparseArray<DesktopDisplay>()
+
+ override fun getOrCreateDesk(displayId: Int, deskId: Int): Desk {
+ val display =
+ desktopDisplays[displayId]
+ ?: DesktopDisplay(displayId).also { desktopDisplays[displayId] = it }
+ val desk =
+ display.orderedDesks.find { desk -> desk.deskId == deskId }
+ ?: Desk(deskId = deskId, displayId = displayId).also {
+ display.orderedDesks.add(it)
+ }
+ return desk
+ }
+
+ override fun getDesk(deskId: Int): Desk? {
+ desktopDisplays.forEach { _, display ->
+ val desk = display.orderedDesks.find { desk -> desk.deskId == deskId }
+ if (desk != null) {
+ return desk
+ }
+ }
+ return null
+ }
+
+ override fun getActiveDesk(displayId: Int): Desk? {
+ val display = desktopDisplays[displayId] ?: return null
+ if (display.activeDeskId == null) return null
+ return display.orderedDesks.find { it.deskId == display.activeDeskId }
+ }
+
+ override fun setActiveDesk(displayId: Int, deskId: Int) {
+ val display =
+ desktopDisplays[displayId] ?: error("Expected display#$displayId to exist")
+ val desk = display.orderedDesks.single { it.deskId == deskId }
+ display.activeDeskId = desk.deskId
+ }
+
+ override fun getDefaultDesk(displayId: Int): Desk? {
+ val display = desktopDisplays[displayId] ?: return null
+ return display.orderedDesks.firstOrNull()
+ }
+
+ override fun getAllActiveDesks(): Set<Desk> {
+ return desktopDisplays
+ .valueIterator()
+ .asSequence()
+ .filter { display -> display.activeDeskId != null }
+ .map { display ->
+ display.orderedDesks.single { it.deskId == display.activeDeskId }
+ }
+ .toSet()
+ }
+
+ override fun getNumberOfDesks(displayId: Int): Int =
+ desktopDisplays[displayId]?.orderedDesks?.size ?: 0
+
+ override fun forAllDesks(consumer: (Desk) -> Unit) {
+ desktopDisplays.forEach { _, display -> display.orderedDesks.forEach { consumer(it) } }
+ }
+
+ override fun forAllDesks(consumer: (Int, Desk) -> Unit) {
+ desktopDisplays.forEach { _, display ->
+ display.orderedDesks.forEach { consumer(display.displayId, it) }
+ }
+ }
+
+ override fun forAllDesks(displayId: Int, consumer: (Desk) -> Unit) {
+ desktopDisplays
+ .valueIterator()
+ .asSequence()
+ .filter { display -> display.displayId == displayId }
+ .flatMap { display -> display.orderedDesks.asSequence() }
+ .forEach { desk -> consumer(desk) }
+ }
+
+ override fun desksSequence(): Sequence<Desk> =
+ desktopDisplays.valueIterator().asSequence().flatMap { display ->
+ display.orderedDesks.asSequence()
+ }
+
+ override fun desksSequence(displayId: Int): Sequence<Desk> =
+ desktopDisplays[displayId]?.orderedDesks?.asSequence() ?: emptySequence()
+
+ override fun remove(deskId: Int) {
+ desktopDisplays.forEach { _, display ->
+ display.orderedDesks.removeIf { it.deskId == deskId }
+ }
+ }
+
+ override fun getDisplayForDesk(deskId: Int): Int =
+ getAllActiveDesks().find { it.deskId == deskId }?.displayId
+ ?: error("Display for desk=$deskId not found")
+ }
+
private fun logD(msg: String, vararg arguments: Any?) {
ProtoLog.d(WM_SHELL_DESKTOP_MODE, "%s: $msg", TAG, *arguments)
}
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 3d57038b27fb..172410d0482c 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
@@ -35,6 +35,7 @@ import android.graphics.PointF
import android.graphics.Rect
import android.graphics.Region
import android.os.Binder
+import android.os.Bundle
import android.os.Handler
import android.os.IBinder
import android.os.SystemProperties
@@ -86,6 +87,7 @@ import com.android.wm.shell.common.SyncTransactionQueue
import com.android.wm.shell.compatui.isTopActivityExemptFromDesktopWindowing
import com.android.wm.shell.compatui.isTransparentTask
import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.InputMethod
+import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.MinimizeReason
import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ResizeTrigger
import com.android.wm.shell.desktopmode.DesktopModeUiEventLogger.DesktopUiEventEnum
import com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.DragStartState
@@ -465,7 +467,9 @@ class DesktopTasksController(
desktopModeEnterExitTransitionListener?.onEnterDesktopModeTransitionStarted(
FREEFORM_ANIMATION_DURATION
)
- taskIdToMinimize?.let { addPendingMinimizeTransition(transition, it) }
+ taskIdToMinimize?.let {
+ addPendingMinimizeTransition(transition, it, MinimizeReason.TASK_LIMIT)
+ }
exitResult.asExit()?.runOnTransitionStart?.invoke(transition)
return true
}
@@ -511,7 +515,9 @@ class DesktopTasksController(
desktopModeEnterExitTransitionListener?.onEnterDesktopModeTransitionStarted(
FREEFORM_ANIMATION_DURATION
)
- taskIdToMinimize?.let { addPendingMinimizeTransition(transition, it) }
+ taskIdToMinimize?.let {
+ addPendingMinimizeTransition(transition, it, MinimizeReason.TASK_LIMIT)
+ }
exitResult.asExit()?.runOnTransitionStart?.invoke(transition)
}
@@ -572,7 +578,9 @@ class DesktopTasksController(
DRAG_TO_DESKTOP_FINISH_ANIM_DURATION_MS.toInt()
)
transition?.let {
- taskIdToMinimize?.let { taskId -> addPendingMinimizeTransition(it, taskId) }
+ taskIdToMinimize?.let { taskId ->
+ addPendingMinimizeTransition(it, taskId, MinimizeReason.TASK_LIMIT)
+ }
exitResult.asExit()?.runOnTransitionStart?.invoke(transition)
}
}
@@ -621,7 +629,7 @@ class DesktopTasksController(
?.runOnTransitionStart
}
- fun minimizeTask(taskInfo: RunningTaskInfo) {
+ fun minimizeTask(taskInfo: RunningTaskInfo, minimizeReason: MinimizeReason) {
val wct = WindowContainerTransaction()
val isMinimizingToPip = taskInfo.pictureInPictureParams?.isAutoEnterEnabled() ?: false
@@ -641,16 +649,16 @@ class DesktopTasksController(
freeformTaskTransitionStarter.startPipTransition(wct)
taskRepository.setTaskInPip(taskInfo.displayId, taskInfo.taskId, enterPip = true)
taskRepository.setOnPipAbortedCallback { displayId, taskId ->
- minimizeTaskInner(shellTaskOrganizer.getRunningTaskInfo(taskId)!!)
+ minimizeTaskInner(shellTaskOrganizer.getRunningTaskInfo(taskId)!!, minimizeReason)
taskRepository.setTaskInPip(displayId, taskId, enterPip = false)
}
return
}
- minimizeTaskInner(taskInfo)
+ minimizeTaskInner(taskInfo, minimizeReason)
}
- private fun minimizeTaskInner(taskInfo: RunningTaskInfo) {
+ private fun minimizeTaskInner(taskInfo: RunningTaskInfo, minimizeReason: MinimizeReason) {
val taskId = taskInfo.taskId
val displayId = taskInfo.displayId
val wct = WindowContainerTransaction()
@@ -670,6 +678,7 @@ class DesktopTasksController(
transition = transition,
displayId = displayId,
taskId = taskId,
+ minimizeReason = minimizeReason,
)
}
exitResult.asExit()?.runOnTransitionStart?.invoke(transition)
@@ -825,7 +834,7 @@ class DesktopTasksController(
minimizingTaskId = taskIdToMinimize,
exitingImmersiveTask = exitImmersiveResult.asExit()?.exitingTask,
)
- taskIdToMinimize?.let { addPendingMinimizeTransition(t, it) }
+ taskIdToMinimize?.let { addPendingMinimizeTransition(t, it, MinimizeReason.TASK_LIMIT) }
exitImmersiveResult.asExit()?.runOnTransitionStart?.invoke(t)
return t
}
@@ -845,7 +854,7 @@ class DesktopTasksController(
)
val t = transitions.startTransition(transitionType, wct, remoteTransitionHandler)
remoteTransitionHandler.setTransition(t)
- taskIdToMinimize.let { addPendingMinimizeTransition(t, it) }
+ taskIdToMinimize.let { addPendingMinimizeTransition(t, it, MinimizeReason.TASK_LIMIT) }
exitImmersiveResult.asExit()?.runOnTransitionStart?.invoke(t)
return t
}
@@ -884,6 +893,36 @@ class DesktopTasksController(
}
/**
+ * Start an intent through a launch transition for starting tasks whose transition does not get
+ * handled by [handleRequest]
+ */
+ fun startLaunchIntentTransition(intent: Intent, options: Bundle, displayId: Int) {
+ val wct = WindowContainerTransaction()
+ val displayLayout = displayController.getDisplayLayout(displayId) ?: return
+ val bounds = calculateDefaultDesktopTaskBounds(displayLayout)
+ if (DesktopModeFlags.ENABLE_CASCADING_WINDOWS.isTrue) {
+ cascadeWindow(bounds, displayLayout, displayId)
+ }
+ val pendingIntent =
+ PendingIntent.getActivity(
+ context,
+ /* requestCode= */ 0,
+ intent,
+ PendingIntent.FLAG_IMMUTABLE,
+ )
+ val ops =
+ ActivityOptions.fromBundle(options).apply {
+ launchWindowingMode = WINDOWING_MODE_FREEFORM
+ pendingIntentBackgroundActivityStartMode =
+ ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS
+ launchBounds = bounds
+ }
+
+ wct.sendPendingIntent(pendingIntent, intent, ops.toBundle())
+ startLaunchTransition(TRANSIT_OPEN, wct, launchingTaskId = null)
+ }
+
+ /**
* Move [task] to display with [displayId].
*
* No-op if task is already on that display per [RunningTaskInfo.displayId].
@@ -1867,7 +1906,7 @@ class DesktopTasksController(
val taskIdToMinimize = addAndGetMinimizeChanges(task.displayId, wct, task.taskId)
addPendingAppLaunchTransition(transition, task.taskId, taskIdToMinimize)
if (taskIdToMinimize != null) {
- addPendingMinimizeTransition(transition, taskIdToMinimize)
+ addPendingMinimizeTransition(transition, taskIdToMinimize, MinimizeReason.TASK_LIMIT)
return wct
}
if (!wct.isEmpty) {
@@ -1901,7 +1940,9 @@ class DesktopTasksController(
// Desktop Mode is already showing and we're launching a new Task - we might need to
// minimize another Task.
val taskIdToMinimize = addAndGetMinimizeChanges(task.displayId, wct, task.taskId)
- taskIdToMinimize?.let { addPendingMinimizeTransition(transition, it) }
+ taskIdToMinimize?.let {
+ addPendingMinimizeTransition(transition, it, MinimizeReason.TASK_LIMIT)
+ }
addPendingAppLaunchTransition(transition, task.taskId, taskIdToMinimize)
desktopImmersiveController.exitImmersiveIfApplicable(
transition,
@@ -2149,13 +2190,18 @@ class DesktopTasksController(
.addAndGetMinimizeTaskChanges(displayId, wct, newTaskId, launchingNewIntent)
}
- private fun addPendingMinimizeTransition(transition: IBinder, taskIdToMinimize: Int) {
+ private fun addPendingMinimizeTransition(
+ transition: IBinder,
+ taskIdToMinimize: Int,
+ minimizeReason: MinimizeReason,
+ ) {
val taskToMinimize = shellTaskOrganizer.getRunningTaskInfo(taskIdToMinimize)
desktopTasksLimiter.ifPresent {
it.addPendingMinimizeChange(
transition = transition,
displayId = taskToMinimize?.displayId ?: DEFAULT_DISPLAY,
taskId = taskIdToMinimize,
+ minimizeReason = minimizeReason,
)
}
}
@@ -2185,7 +2231,7 @@ class DesktopTasksController(
fun removeDesktop(displayId: Int) {
if (!DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION.isTrue()) return
- val tasksToRemove = taskRepository.removeDesktop(displayId)
+ val tasksToRemove = taskRepository.removeDesk(displayId)
val wct = WindowContainerTransaction()
tasksToRemove.forEach {
val task = shellTaskOrganizer.getRunningTaskInfo(it)
@@ -2904,6 +2950,12 @@ class DesktopTasksController(
c.moveToNextDisplay(taskId)
}
}
+
+ override fun startLaunchIntentTransition(intent: Intent, options: Bundle, displayId: Int) {
+ executeRemoteCallWithTaskPermission(controller, "startLaunchIntentTransition") { c ->
+ c.startLaunchIntentTransition(intent, options, displayId)
+ }
+ }
}
private fun logV(msg: String, vararg arguments: Any?) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt
index e4a28e9efe60..204b39645248 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt
@@ -30,6 +30,7 @@ import com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_MINIMIZE_WINDOW
import com.android.internal.jank.InteractionJankMonitor
import com.android.internal.protolog.ProtoLog
import com.android.wm.shell.ShellTaskOrganizer
+import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.MinimizeReason
import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
import com.android.wm.shell.shared.annotations.ShellMainThread
import com.android.wm.shell.sysui.UserChangeListener
@@ -67,12 +68,21 @@ class DesktopTasksLimiter(
logV("Starting limiter with a maximum of %d tasks", maxTasksLimit)
}
- private data class TaskDetails(
+ data class TaskDetails(
val displayId: Int,
val taskId: Int,
- var transitionInfo: TransitionInfo?,
+ var transitionInfo: TransitionInfo? = null,
+ val minimizeReason: MinimizeReason? = null,
)
+ /**
+ * Returns the task being minimized in the given transition if that transition is a pending or
+ * active minimize transition.
+ */
+ fun getMinimizingTask(transition: IBinder): TaskDetails? {
+ return minimizeTransitionObserver.getMinimizingTask(transition)
+ }
+
// TODO(b/333018485): replace this observer when implementing the minimize-animation
private inner class MinimizeTransitionObserver : TransitionObserver {
private val pendingTransitionTokensAndTasks = mutableMapOf<IBinder, TaskDetails>()
@@ -82,6 +92,11 @@ class DesktopTasksLimiter(
pendingTransitionTokensAndTasks[transition] = taskDetails
}
+ fun getMinimizingTask(transition: IBinder): TaskDetails? {
+ return pendingTransitionTokensAndTasks[transition]
+ ?: activeTransitionTokensAndTasks[transition]
+ }
+
override fun onTransitionReady(
transition: IBinder,
info: TransitionInfo,
@@ -89,6 +104,14 @@ class DesktopTasksLimiter(
finishTransaction: SurfaceControl.Transaction,
) {
val taskRepository = desktopUserRepositories.current
+ handleMinimizeTransition(taskRepository, transition, info)
+ }
+
+ private fun handleMinimizeTransition(
+ taskRepository: DesktopRepository,
+ transition: IBinder,
+ info: TransitionInfo,
+ ) {
val taskToMinimize = pendingTransitionTokensAndTasks.remove(transition) ?: return
if (!taskRepository.isActiveTask(taskToMinimize.taskId)) return
if (!isTaskReadyForMinimize(info, taskToMinimize)) {
@@ -241,10 +264,15 @@ class DesktopTasksLimiter(
* Add a pending minimize transition change to update the list of minimized apps once the
* transition goes through.
*/
- fun addPendingMinimizeChange(transition: IBinder, displayId: Int, taskId: Int) {
+ fun addPendingMinimizeChange(
+ transition: IBinder,
+ displayId: Int,
+ taskId: Int,
+ minimizeReason: MinimizeReason,
+ ) {
minimizeTransitionObserver.addPendingTransitionToken(
transition,
- TaskDetails(displayId, taskId, transitionInfo = null),
+ TaskDetails(displayId, taskId, transitionInfo = null, minimizeReason = minimizeReason),
)
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl
index fa383cb55118..54f031293486 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl
@@ -17,6 +17,8 @@
package com.android.wm.shell.desktopmode;
import android.app.ActivityManager.RunningTaskInfo;
+import android.content.Intent;
+import android.os.Bundle;
import android.window.RemoteTransition;
import com.android.wm.shell.desktopmode.IDesktopTaskListener;
import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource;
@@ -61,4 +63,7 @@ interface IDesktopMode {
/** Move a task with given `taskId` to external display */
void moveToExternalDisplay(int taskId);
+
+ /** Start a transition when launching an intent in desktop mode */
+ void startLaunchIntentTransition(in Intent intent, in Bundle options, in int displayId);
} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
index 9c3e815b389d..912d3839fae7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
@@ -1317,14 +1317,14 @@ public class PipController implements PipTransitionController.PipTransitionCallb
}
@Override
- public Rect startSwipePipToHome(ComponentName componentName, ActivityInfo activityInfo,
- PictureInPictureParams pictureInPictureParams, int launcherRotation,
- Rect keepClearArea) {
+ public Rect startSwipePipToHome(ActivityManager.RunningTaskInfo taskInfo,
+ int launcherRotation, Rect keepClearArea) {
Rect[] result = new Rect[1];
executeRemoteCallWithTaskPermission(mController, "startSwipePipToHome",
(controller) -> {
- result[0] = controller.startSwipePipToHome(componentName, activityInfo,
- pictureInPictureParams, launcherRotation, keepClearArea);
+ result[0] = controller.startSwipePipToHome(taskInfo.topActivity,
+ taskInfo.topActivityInfo, taskInfo.pictureInPictureParams,
+ launcherRotation, keepClearArea);
}, true /* blocking */);
return result[0];
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipAlphaAnimator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipAlphaAnimator.java
index 63c151268bdb..a033b824aa28 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipAlphaAnimator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipAlphaAnimator.java
@@ -50,6 +50,11 @@ public class PipAlphaAnimator extends ValueAnimator {
private final SurfaceControl mLeash;
private final SurfaceControl.Transaction mStartTransaction;
+ private final SurfaceControl.Transaction mFinishTransaction;
+
+ private final int mDirection;
+ private final int mCornerRadius;
+ private final int mShadowRadius;
private final Animator.AnimatorListener mAnimatorListener = new AnimatorListenerAdapter() {
@Override
@@ -59,6 +64,7 @@ public class PipAlphaAnimator extends ValueAnimator {
mAnimationStartCallback.run();
}
if (mStartTransaction != null) {
+ onAlphaAnimationUpdate(getStartAlphaValue(), mStartTransaction);
mStartTransaction.apply();
}
}
@@ -66,6 +72,10 @@ public class PipAlphaAnimator extends ValueAnimator {
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
+ if (mFinishTransaction != null) {
+ onAlphaAnimationUpdate(getEndAlphaValue(), mFinishTransaction);
+ mFinishTransaction.apply();
+ }
if (mAnimationEndCallback != null) {
mAnimationEndCallback.run();
}
@@ -77,8 +87,9 @@ public class PipAlphaAnimator extends ValueAnimator {
@Override
public void onAnimationUpdate(@NonNull ValueAnimator animation) {
final float alpha = (Float) animation.getAnimatedValue();
- mSurfaceControlTransactionFactory.getTransaction()
- .setAlpha(mLeash, alpha).apply();
+ final SurfaceControl.Transaction tx =
+ mSurfaceControlTransactionFactory.getTransaction();
+ onAlphaAnimationUpdate(alpha, tx);
}
};
@@ -91,19 +102,21 @@ public class PipAlphaAnimator extends ValueAnimator {
public PipAlphaAnimator(Context context,
SurfaceControl leash,
- SurfaceControl.Transaction tx,
+ SurfaceControl.Transaction startTransaction,
+ SurfaceControl.Transaction finishTransaction,
@Fade int direction) {
mLeash = leash;
- mStartTransaction = tx;
- if (direction == FADE_IN) {
- setFloatValues(0f, 1f);
- } else { // direction == FADE_OUT
- setFloatValues(1f, 0f);
- }
+ mStartTransaction = startTransaction;
+ mFinishTransaction = finishTransaction;
+
+ mDirection = direction;
+ setFloatValues(getStartAlphaValue(), getEndAlphaValue());
mSurfaceControlTransactionFactory =
new PipSurfaceTransactionHelper.VsyncSurfaceControlTransactionFactory();
final int enterAnimationDuration = context.getResources()
.getInteger(R.integer.config_pipEnterAnimationDuration);
+ mCornerRadius = context.getResources().getDimensionPixelSize(R.dimen.pip_corner_radius);
+ mShadowRadius = context.getResources().getDimensionPixelSize(R.dimen.pip_shadow_radius);
setDuration(enterAnimationDuration);
addListener(mAnimatorListener);
addUpdateListener(mAnimatorUpdateListener);
@@ -117,6 +130,21 @@ public class PipAlphaAnimator extends ValueAnimator {
mAnimationEndCallback = runnable;
}
+ private void onAlphaAnimationUpdate(float alpha, SurfaceControl.Transaction tx) {
+ tx.setAlpha(mLeash, alpha)
+ .setCornerRadius(mLeash, mCornerRadius)
+ .setShadowRadius(mLeash, mShadowRadius);
+ tx.apply();
+ }
+
+ private float getStartAlphaValue() {
+ return mDirection == FADE_IN ? 0f : 1f;
+ }
+
+ private float getEndAlphaValue() {
+ return mDirection == FADE_IN ? 1f : 0f;
+ }
+
@VisibleForTesting
void setSurfaceControlTransactionFactory(
@NonNull PipSurfaceTransactionHelper.SurfaceControlTransactionFactory factory) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
index 562b26014bf3..b1984ccef4cb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
@@ -40,6 +40,7 @@ import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.ProtoLog;
import com.android.internal.util.Preconditions;
+import com.android.window.flags.Flags;
import com.android.wm.shell.R;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayChangeController;
@@ -358,10 +359,21 @@ public class PipController implements ConfigurationChangeListener,
//
private Rect getSwipePipToHomeBounds(ComponentName componentName, ActivityInfo activityInfo,
- PictureInPictureParams pictureInPictureParams,
+ int displayId, PictureInPictureParams pictureInPictureParams,
int launcherRotation, Rect hotseatKeepClearArea) {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"getSwipePipToHomeBounds: %s", componentName);
+
+ // If PiP is enabled on Connected Displays, update PipDisplayLayoutState to have the correct
+ // display info that PiP is entering in.
+ if (Flags.enableConnectedDisplaysPip()) {
+ final DisplayLayout displayLayout = mDisplayController.getDisplayLayout(displayId);
+ if (displayLayout != null) {
+ mPipDisplayLayoutState.setDisplayId(displayId);
+ mPipDisplayLayoutState.setDisplayLayout(displayLayout);
+ }
+ }
+
// Preemptively add the keep clear area for Hotseat, so that it is taken into account
// when calculating the entry destination bounds of PiP window.
mPipBoundsState.setNamedUnrestrictedKeepClearArea(
@@ -592,14 +604,14 @@ public class PipController implements ConfigurationChangeListener,
}
@Override
- public Rect startSwipePipToHome(ComponentName componentName, ActivityInfo activityInfo,
- PictureInPictureParams pictureInPictureParams, int launcherRotation,
- Rect keepClearArea) {
+ public Rect startSwipePipToHome(ActivityManager.RunningTaskInfo taskInfo,
+ int launcherRotation, Rect keepClearArea) {
Rect[] result = new Rect[1];
executeRemoteCallWithTaskPermission(mController, "startSwipePipToHome",
(controller) -> {
- result[0] = controller.getSwipePipToHomeBounds(componentName, activityInfo,
- pictureInPictureParams, launcherRotation, keepClearArea);
+ result[0] = controller.getSwipePipToHomeBounds(taskInfo.topActivity,
+ taskInfo.topActivityInfo, taskInfo.displayId,
+ taskInfo.pictureInPictureParams, launcherRotation, keepClearArea);
}, true /* blocking */);
return result[0];
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java
index ed532cad0523..21b0820f523a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java
@@ -294,7 +294,8 @@ public class PipScheduler {
interface PipAlphaAnimatorSupplier {
PipAlphaAnimator get(@NonNull Context context,
SurfaceControl leash,
- SurfaceControl.Transaction tx,
+ SurfaceControl.Transaction startTransaction,
+ SurfaceControl.Transaction finishTransaction,
@PipAlphaAnimator.Fade int direction);
}
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 4902455cae16..8cba076d28f2 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
@@ -59,6 +59,8 @@ import com.android.internal.util.Preconditions;
import com.android.window.flags.Flags;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.ComponentUtils;
+import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
import com.android.wm.shell.common.pip.PipBoundsState;
import com.android.wm.shell.common.pip.PipDisplayLayoutState;
@@ -112,6 +114,7 @@ public class PipTransition extends PipTransitionController implements
private final PipScheduler mPipScheduler;
private final PipTransitionState mPipTransitionState;
private final PipDisplayLayoutState mPipDisplayLayoutState;
+ private final DisplayController mDisplayController;
private final Optional<DesktopUserRepositories> mDesktopUserRepositoriesOptional;
private final Optional<DesktopWallpaperActivityTokenProvider>
mDesktopWallpaperActivityTokenProviderOptional;
@@ -151,6 +154,7 @@ public class PipTransition extends PipTransitionController implements
PipTransitionState pipTransitionState,
PipDisplayLayoutState pipDisplayLayoutState,
PipUiStateChangeController pipUiStateChangeController,
+ DisplayController displayController,
Optional<DesktopUserRepositories> desktopUserRepositoriesOptional,
Optional<DesktopWallpaperActivityTokenProvider>
desktopWallpaperActivityTokenProviderOptional) {
@@ -164,6 +168,7 @@ public class PipTransition extends PipTransitionController implements
mPipTransitionState = pipTransitionState;
mPipTransitionState.addPipTransitionStateChangedListener(this);
mPipDisplayLayoutState = pipDisplayLayoutState;
+ mDisplayController = displayController;
mDesktopUserRepositoriesOptional = desktopUserRepositoriesOptional;
mDesktopWallpaperActivityTokenProviderOptional =
desktopWallpaperActivityTokenProviderOptional;
@@ -513,7 +518,7 @@ public class PipTransition extends PipTransitionController implements
private void startOverlayFadeoutAnimation(@NonNull SurfaceControl overlayLeash,
@NonNull Runnable onAnimationEnd) {
PipAlphaAnimator animator = new PipAlphaAnimator(mContext, overlayLeash,
- null /* startTx */, PipAlphaAnimator.FADE_OUT);
+ null /* startTx */, null /* finishTx */, PipAlphaAnimator.FADE_OUT);
animator.setDuration(CONTENT_OVERLAY_FADE_OUT_DELAY_MS);
animator.setAnimationEndCallback(onAnimationEnd);
animator.start();
@@ -604,7 +609,7 @@ public class PipTransition extends PipTransitionController implements
.setAlpha(pipLeash, 0f);
PipAlphaAnimator animator = new PipAlphaAnimator(mContext, pipLeash, startTransaction,
- PipAlphaAnimator.FADE_IN);
+ finishTransaction, PipAlphaAnimator.FADE_IN);
// This should update the pip transition state accordingly after we stop playing.
animator.setAnimationEndCallback(this::finishTransition);
cacheAndStartTransitionAnimator(animator);
@@ -699,7 +704,7 @@ public class PipTransition extends PipTransitionController implements
finishTransaction.setAlpha(pipChange.getLeash(), 0f);
if (mPendingRemoveWithFadeout) {
PipAlphaAnimator animator = new PipAlphaAnimator(mContext, pipChange.getLeash(),
- startTransaction, PipAlphaAnimator.FADE_OUT);
+ startTransaction, finishTransaction, PipAlphaAnimator.FADE_OUT);
animator.setAnimationEndCallback(this::finishTransition);
animator.start();
} else {
@@ -824,6 +829,17 @@ public class PipTransition extends PipTransitionController implements
mPipBoundsState.setBoundsStateForEntry(pipTask.topActivity, pipTask.topActivityInfo,
pipParams, mPipBoundsAlgorithm);
+ // If PiP is enabled on Connected Displays, update PipDisplayLayoutState to have the correct
+ // display info that PiP is entering in.
+ if (Flags.enableConnectedDisplaysPip()) {
+ final DisplayLayout displayLayout = mDisplayController.getDisplayLayout(
+ pipTask.displayId);
+ if (displayLayout != null) {
+ mPipDisplayLayoutState.setDisplayId(pipTask.displayId);
+ mPipDisplayLayoutState.setDisplayLayout(displayLayout);
+ }
+ }
+
// calculate the entry bounds and notify core to move task to pinned with final bounds
final Rect entryBounds = mPipBoundsAlgorithm.getEntryDestinationBounds();
mPipBoundsState.setBounds(entryBounds);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
index 0182588398a4..2d4d458292ea 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
@@ -43,7 +43,6 @@ import android.graphics.Point;
import android.os.Bundle;
import android.os.RemoteException;
import android.util.Slog;
-import android.util.SparseArray;
import android.util.SparseIntArray;
import android.window.DesktopModeFlags;
import android.window.WindowContainerToken;
@@ -83,6 +82,7 @@ import java.util.Optional;
import java.util.Set;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
+import java.util.stream.Collectors;
/**
* Manages the recent task list from the system, caching it as necessary.
@@ -124,6 +124,10 @@ public class RecentTasksController implements TaskStackListenerCallback,
* Cached list of the visible tasks, sorted from top most to bottom most.
*/
private final List<RunningTaskInfo> mVisibleTasks = new ArrayList<>();
+ private final Map<Integer, TaskInfo> mVisibleTasksMap = new HashMap<>();
+
+ // Temporary vars used in `generateList()`
+ private final Map<Integer, TaskInfo> mTmpRemaining = new HashMap<>();
/**
* Creates {@link RecentTasksController}, returns {@code null} if the feature is not
@@ -348,8 +352,11 @@ public class RecentTasksController implements TaskStackListenerCallback,
public void onVisibleTasksChanged(@NonNull List<? extends RunningTaskInfo> visibleTasks) {
mVisibleTasks.clear();
mVisibleTasks.addAll(visibleTasks);
+ mVisibleTasksMap.clear();
+ mVisibleTasksMap.putAll(mVisibleTasks.stream().collect(
+ Collectors.toMap(TaskInfo::getTaskId, task -> task)));
// Notify with all the info and not just the running task info
- notifyVisibleTasksChanged(visibleTasks);
+ notifyVisibleTasksChanged(mVisibleTasks);
}
@VisibleForTesting
@@ -458,7 +465,7 @@ public class RecentTasksController implements TaskStackListenerCallback,
}
try {
// Compute the visible recent tasks in order, and move the task to the top
- mListener.onVisibleTasksChanged(generateList(visibleTasks)
+ mListener.onVisibleTasksChanged(generateList(visibleTasks, "visibleTasksChanged")
.toArray(new GroupedTaskInfo[0]));
} catch (RemoteException e) {
Slog.w(TAG, "Failed call onVisibleTasksChanged", e);
@@ -494,40 +501,87 @@ public class RecentTasksController implements TaskStackListenerCallback,
@VisibleForTesting
ArrayList<GroupedTaskInfo> getRecentTasks(int maxNum, int flags, int userId) {
// Note: the returned task list is ordered from the most-recent to least-recent order
- return generateList(mActivityTaskManager.getRecentTasks(maxNum, flags, userId));
+ return generateList(mActivityTaskManager.getRecentTasks(maxNum, flags, userId),
+ "getRecentTasks");
}
/**
- * Generates a list of GroupedTaskInfos for the given list of tasks.
+ * Returns whether the given task should be excluded from the generated list.
*/
- private <T extends TaskInfo> ArrayList<GroupedTaskInfo> generateList(@NonNull List<T> tasks) {
- // Make a mapping of task id -> task info
- final SparseArray<TaskInfo> rawMapping = new SparseArray<>();
- for (int i = 0; i < tasks.size(); i++) {
- final TaskInfo taskInfo = tasks.get(i);
- rawMapping.put(taskInfo.taskId, taskInfo);
+ private boolean excludeTaskFromGeneratedList(TaskInfo taskInfo) {
+ if (taskInfo.getWindowingMode() == WINDOWING_MODE_PINNED) {
+ // We don't current send pinned tasks as a part of recent or running tasks
+ return true;
+ }
+ if (isWallpaperTask(taskInfo)) {
+ // Don't add the fullscreen wallpaper task as an entry in grouped tasks
+ return true;
}
+ return false;
+ }
- ArrayList<TaskInfo> freeformTasks = new ArrayList<>();
- Set<Integer> minimizedFreeformTasks = new HashSet<>();
+ /**
+ * Generates a list of GroupedTaskInfos for the given raw list of tasks (either recents or
+ * running tasks).
+ *
+ * The general flow is:
+ * - Collect the desktop tasks
+ * - Collect the visible tasks (in order), including the desktop tasks if visible
+ * - Construct the final list with the visible tasks, followed by the subsequent tasks
+ * - if enableShellTopTaskTracking() is enabled, the visible tasks will be grouped into
+ * a single mixed task
+ * - if the desktop tasks are not visible, they will be appended to the end of the list
+ *
+ * TODO(346588978): Generate list in per-display order
+ *
+ * @param tasks The list of tasks ordered from most recent to least recent
+ */
+ @VisibleForTesting
+ <T extends TaskInfo> ArrayList<GroupedTaskInfo> generateList(@NonNull List<T> tasks,
+ String reason) {
+ if (tasks.isEmpty()) {
+ return new ArrayList<>();
+ }
+
+ if (enableShellTopTaskTracking()) {
+ ProtoLog.v(WM_SHELL_TASK_OBSERVER, "RecentTasksController.generateList(%s)", reason);
+ }
- int mostRecentFreeformTaskIndex = Integer.MAX_VALUE;
+ // Make a mapping of task id -> task info for the remaining tasks to be processed, this
+ // mapping is used to keep track of split tasks that may exist later in the task list that
+ // should be ignored because they've already been grouped
+ mTmpRemaining.clear();
+ mTmpRemaining.putAll(tasks.stream().collect(
+ Collectors.toMap(TaskInfo::getTaskId, task -> task)));
- ArrayList<GroupedTaskInfo> groupedTasks = new ArrayList<>();
- // Pull out the pairs as we iterate back in the list
+ // The final grouped tasks
+ ArrayList<GroupedTaskInfo> groupedTasks = new ArrayList<>(tasks.size());
+ ArrayList<GroupedTaskInfo> visibleGroupedTasks = new ArrayList<>();
+
+ // Phase 1: Extract the desktop and visible fullscreen/split tasks. We make new collections
+ // here as the GroupedTaskInfo can store them without copying
+ ArrayList<TaskInfo> desktopTasks = new ArrayList<>();
+ Set<Integer> minimizedDesktopTasks = new HashSet<>();
+ boolean desktopTasksVisible = false;
for (int i = 0; i < tasks.size(); i++) {
final TaskInfo taskInfo = tasks.get(i);
- if (!rawMapping.contains(taskInfo.taskId)) {
- // If it's not in the mapping, then it was already paired with another task
+ final int taskId = taskInfo.taskId;
+
+ if (!mTmpRemaining.containsKey(taskInfo.taskId)) {
+ // Skip if we've already processed it
continue;
}
+
+ if (excludeTaskFromGeneratedList(taskInfo)) {
+ // Skip and update the list if we are excluding this task
+ mTmpRemaining.remove(taskId);
+ continue;
+ }
+
+ // Desktop tasks
if (DesktopModeStatus.canEnterDesktopMode(mContext) &&
- mDesktopUserRepositories.isPresent()
- && mDesktopUserRepositories.get().getCurrent().isActiveTask(taskInfo.taskId)) {
- // Freeform tasks will be added as a separate entry
- if (mostRecentFreeformTaskIndex == Integer.MAX_VALUE) {
- mostRecentFreeformTaskIndex = groupedTasks.size();
- }
+ mDesktopUserRepositories.isPresent()
+ && mDesktopUserRepositories.get().getCurrent().isActiveTask(taskId)) {
// If task has their app bounds set to null which happens after reboot, set the
// app bounds to persisted lastFullscreenBounds. Also set the position in parent
// to the top left of the bounds.
@@ -538,49 +592,132 @@ public class RecentTasksController implements TaskStackListenerCallback,
taskInfo.positionInParent = new Point(taskInfo.lastNonFullscreenBounds.left,
taskInfo.lastNonFullscreenBounds.top);
}
- freeformTasks.add(taskInfo);
- if (mDesktopUserRepositories.get().getCurrent().isMinimizedTask(taskInfo.taskId)) {
- minimizedFreeformTasks.add(taskInfo.taskId);
+ desktopTasks.add(taskInfo);
+ if (mDesktopUserRepositories.get().getCurrent().isMinimizedTask(taskId)) {
+ minimizedDesktopTasks.add(taskId);
}
+ desktopTasksVisible |= mVisibleTasksMap.containsKey(taskId);
+ mTmpRemaining.remove(taskId);
continue;
}
- final int pairedTaskId = mSplitTasks.get(taskInfo.taskId, INVALID_TASK_ID);
- if (pairedTaskId != INVALID_TASK_ID && rawMapping.contains(pairedTaskId)) {
- final TaskInfo pairedTaskInfo = rawMapping.get(pairedTaskId);
- rawMapping.remove(pairedTaskId);
- groupedTasks.add(GroupedTaskInfo.forSplitTasks(taskInfo, pairedTaskInfo,
- mTaskSplitBoundsMap.get(pairedTaskId)));
+ if (enableShellTopTaskTracking()) {
+ // Visible tasks
+ if (mVisibleTasksMap.containsKey(taskId)) {
+ // Split tasks
+ if (extractAndAddSplitGroupedTask(taskInfo, mTmpRemaining,
+ visibleGroupedTasks)) {
+ continue;
+ }
+
+ // Fullscreen tasks
+ visibleGroupedTasks.add(GroupedTaskInfo.forFullscreenTasks(taskInfo));
+ mTmpRemaining.remove(taskId);
+ }
} else {
- if (isWallpaperTask(taskInfo)) {
- // Don't add the wallpaper task as an entry in grouped tasks
+ // Split tasks
+ if (extractAndAddSplitGroupedTask(taskInfo, mTmpRemaining, groupedTasks)) {
continue;
}
- // TODO(346588978): Consolidate multiple visible fullscreen tasks into the same
- // grouped task
+
+ // Fullscreen tasks
groupedTasks.add(GroupedTaskInfo.forFullscreenTasks(taskInfo));
}
}
- // Add a special entry for freeform tasks
- if (!freeformTasks.isEmpty()) {
- groupedTasks.add(mostRecentFreeformTaskIndex,
- GroupedTaskInfo.forFreeformTasks(
- freeformTasks,
- minimizedFreeformTasks));
- }
-
if (enableShellTopTaskTracking()) {
- // We don't current send pinned tasks as a part of recent or running tasks, so remove
- // them from the list here
- groupedTasks.removeIf(
- gti -> gti.getTaskInfo1().getWindowingMode() == WINDOWING_MODE_PINNED);
+ // Phase 2: If there were desktop tasks and they are visible, add them to the visible
+ // list as well (the actual order doesn't matter for Overview)
+ if (!desktopTasks.isEmpty() && desktopTasksVisible) {
+ visibleGroupedTasks.add(
+ GroupedTaskInfo.forFreeformTasks(desktopTasks, minimizedDesktopTasks));
+ }
+
+ if (!visibleGroupedTasks.isEmpty()) {
+ // Phase 3: Combine the visible tasks into a single mixed grouped task, only if
+ // there are > 1 tasks to group, and add them to the final list
+ if (visibleGroupedTasks.size() > 1) {
+ groupedTasks.add(GroupedTaskInfo.forMixed(visibleGroupedTasks));
+ } else {
+ groupedTasks.addAll(visibleGroupedTasks);
+ }
+ }
+ dumpGroupedTasks(groupedTasks, "Phase 3");
+
+ // Phase 4: For the remaining non-visible split and fullscreen tasks, add grouped tasks
+ // in order to the final list
+ for (int i = 0; i < tasks.size(); i++) {
+ final TaskInfo taskInfo = tasks.get(i);
+ if (!mTmpRemaining.containsKey(taskInfo.taskId)) {
+ // Skip if we've already processed it
+ continue;
+ }
+
+ // Split tasks
+ if (extractAndAddSplitGroupedTask(taskInfo, mTmpRemaining, groupedTasks)) {
+ continue;
+ }
+
+ // Fullscreen tasks
+ groupedTasks.add(GroupedTaskInfo.forFullscreenTasks(taskInfo));
+ }
+ dumpGroupedTasks(groupedTasks, "Phase 4");
+
+ // Phase 5: If there were desktop tasks and they are not visible (ie. weren't added
+ // above), add them to the end of the final list (the actual order doesn't
+ // matter for Overview)
+ if (!desktopTasks.isEmpty() && !desktopTasksVisible) {
+ groupedTasks.add(
+ GroupedTaskInfo.forFreeformTasks(desktopTasks, minimizedDesktopTasks));
+ }
+ dumpGroupedTasks(groupedTasks, "Phase 5");
+ } else {
+ // Add the desktop tasks at the end of the list
+ if (!desktopTasks.isEmpty()) {
+ groupedTasks.add(
+ GroupedTaskInfo.forFreeformTasks(desktopTasks, minimizedDesktopTasks));
+ }
}
return groupedTasks;
}
/**
+ * Only to be called from `generateList()`. If the given {@param taskInfo} has a paired task,
+ * then a split grouped task with the pair is added to {@param tasksOut}.
+ *
+ * @return whether a split task was extracted and added to the given list
+ */
+ private boolean extractAndAddSplitGroupedTask(@NonNull TaskInfo taskInfo,
+ @NonNull Map<Integer, TaskInfo> remainingTasks,
+ @NonNull ArrayList<GroupedTaskInfo> tasksOut) {
+ final int pairedTaskId = mSplitTasks.get(taskInfo.taskId, INVALID_TASK_ID);
+ if (pairedTaskId == INVALID_TASK_ID || !remainingTasks.containsKey(pairedTaskId)) {
+ return false;
+ }
+
+ // Add both this task and its pair to the list, and mark the paired task to be
+ // skipped when it is encountered in the list
+ final TaskInfo pairedTaskInfo = remainingTasks.get(pairedTaskId);
+ remainingTasks.remove(taskInfo.taskId);
+ remainingTasks.remove(pairedTaskId);
+ tasksOut.add(GroupedTaskInfo.forSplitTasks(taskInfo, pairedTaskInfo,
+ mTaskSplitBoundsMap.get(pairedTaskId)));
+ return true;
+ }
+
+ /** Dumps the set of tasks to protolog */
+ private void dumpGroupedTasks(List<GroupedTaskInfo> groupedTasks, String reason) {
+ if (!WM_SHELL_TASK_OBSERVER.isEnabled()) {
+ return;
+ }
+ ProtoLog.v(WM_SHELL_TASK_OBSERVER, " Tasks (%s):", reason);
+ for (GroupedTaskInfo task : groupedTasks) {
+ ProtoLog.v(WM_SHELL_TASK_OBSERVER, " %s", task);
+ }
+ }
+
+ /**
* Returns the top running leaf task ignoring {@param ignoreTaskToken} if it is specified.
* NOTE: This path currently makes assumptions that ignoreTaskToken is for the top task.
*/
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 aeccd86e122c..afc6fee2eca3 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
@@ -228,7 +228,7 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler,
break;
}
}
- final int transitionType = Flags.enableShellTopTaskTracking()
+ final int transitionType = Flags.enableRecentsBookendTransition()
? TRANSIT_START_RECENTS_TRANSITION
: TRANSIT_TO_FRONT;
final IBinder transition = mTransitions.startTransition(transitionType,
@@ -920,7 +920,7 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler,
return;
}
- if (Flags.enableShellTopTaskTracking()
+ if (Flags.enableRecentsBookendTransition()
&& info.getType() == TRANSIT_END_RECENTS_TRANSITION
&& mergeTarget == mTransition) {
// This is a pending finish, so merge the end transition to trigger completing the
@@ -1148,9 +1148,12 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler,
change, layer, info, t, mLeashMap);
appearedTargets[nextTargetIdx++] = target;
// reparent into the original `mInfo` since that's where we are animating.
- final int rootIdx = TransitionUtil.rootIndexFor(change, mInfo);
+ final TransitionInfo.Root root = TransitionUtil.getRootFor(change, mInfo);
final boolean wasClosing = closingIdx >= 0;
- t.reparent(target.leash, mInfo.getRoot(rootIdx).getLeash());
+ t.reparent(target.leash, root.getLeash());
+ t.setPosition(target.leash,
+ change.getStartAbsBounds().left - root.getOffset().x,
+ change.getStartAbsBounds().top - root.getOffset().y);
t.setLayer(target.leash, layer);
if (wasClosing) {
// App was previously visible and is closing
@@ -1287,8 +1290,8 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler,
return;
}
- if (mFinishCB == null
- || (Flags.enableShellTopTaskTracking() && mPendingFinishTransition != null)) {
+ if (mFinishCB == null || (Flags.enableRecentsBookendTransition()
+ && mPendingFinishTransition != null)) {
Slog.e(TAG, "Duplicate call to finish");
if (runnerFinishCb != null) {
try {
@@ -1307,7 +1310,7 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler,
&& !mWillFinishToHome
&& mPausingTasks != null
&& mState == STATE_NORMAL;
- if (!Flags.enableShellTopTaskTracking()) {
+ if (!Flags.enableRecentsBookendTransition()) {
// This is only necessary when the recents transition is finished using a finishWCT,
// otherwise a new transition will notify the relevant observers
if (returningToApp && allAppsAreTranslucent(mPausingTasks)) {
@@ -1440,7 +1443,7 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler,
// We need to clear the WCT to send finishWCT=null for Recents.
wct.clear();
- if (Flags.enableShellTopTaskTracking()) {
+ if (Flags.enableRecentsBookendTransition()) {
// In this case, we've already started the PIP transition, so we can
// clean up immediately
mPendingRunnerFinishCb = runnerFinishCb;
@@ -1452,7 +1455,7 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler,
}
}
- if (Flags.enableShellTopTaskTracking()) {
+ if (Flags.enableRecentsBookendTransition()) {
if (!wct.isEmpty()) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
"[%d] RecentsController.finishInner: "
@@ -1571,7 +1574,7 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler,
/**
* A temporary transition handler used with the pending finish transition, which runs the
* cleanup/finish logic once the pending transition is merged/handled.
- * This is only initialized if Flags.enableShellTopTaskTracking() is enabled.
+ * This is only initialized if Flags.enableRecentsBookendTransition() is enabled.
*/
private class PendingFinishTransitionHandler implements Transitions.TransitionHandler {
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/TaskStackTransitionObserver.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/TaskStackTransitionObserver.kt
index 93f2e4cf0e45..11cd4031e8ef 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/TaskStackTransitionObserver.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/TaskStackTransitionObserver.kt
@@ -193,16 +193,18 @@ class TaskStackTransitionObserver(
override fun onTransitionMerged(merged: IBinder, playing: IBinder) {}
override fun onTransitionFinished(transition: IBinder, aborted: Boolean) {
- if (enableShellTopTaskTracking()) {
- if (pendingCloseTasks.isNotEmpty()) {
- // Update the visible task list based on the pending close tasks
- for (change in pendingCloseTasks) {
- visibleTasks.removeIf {
- it.taskId == change.taskId
- }
+ if (!enableShellTopTaskTracking()) {
+ return
+ }
+
+ if (pendingCloseTasks.isNotEmpty()) {
+ // Update the visible task list based on the pending close tasks
+ for (change in pendingCloseTasks) {
+ visibleTasks.removeIf {
+ it.taskId == change.taskId
}
- updateVisibleTasksList("transition-finished")
}
+ updateVisibleTasksList("transition-finished")
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index c9136b4ad18d..37c93518998a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -138,6 +138,7 @@ import com.android.wm.shell.common.DisplayInsetsController;
import com.android.wm.shell.common.LaunchAdjacentController;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
+import com.android.wm.shell.common.pip.PipUtils;
import com.android.wm.shell.common.split.OffscreenTouchZone;
import com.android.wm.shell.common.split.SplitDecorManager;
import com.android.wm.shell.common.split.SplitLayout;
@@ -556,6 +557,13 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
return true;
}
+ if (PipUtils.isPip2ExperimentEnabled()
+ && request.getPipChange() != null && getSplitPosition(
+ request.getPipChange().getTaskInfo().taskId) != SPLIT_POSITION_UNDEFINED) {
+ // In PiP2, PiP-able task can also come in through the pip change request field.
+ return true;
+ }
+
// If one of the splitting tasks support auto-pip, wm-core might reparent the task to TDA
// and file a TRANSIT_PIP transition when finishing transitions.
// @see com.android.server.wm.RootWindowContainer#moveActivityToPinnedRootTask
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskView.java
index 361d766370e5..0445add9cba9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskView.java
@@ -74,13 +74,16 @@ public class TaskView extends SurfaceView implements SurfaceHolder.Callback,
private final Rect mTmpRootRect = new Rect();
private final int[] mTmpLocation = new int[2];
private final Rect mBoundsOnScreen = new Rect();
+ private final TaskViewController mTaskViewController;
private final TaskViewTaskController mTaskViewTaskController;
private Region mObscuredTouchRegion;
private Insets mCaptionInsets;
private Handler mHandler;
- public TaskView(Context context, TaskViewTaskController taskViewTaskController) {
+ public TaskView(Context context, TaskViewController taskViewController,
+ TaskViewTaskController taskViewTaskController) {
super(context, null, 0, 0, true /* disableBackgroundLayer */);
+ mTaskViewController = taskViewController;
mTaskViewTaskController = taskViewTaskController;
// TODO(b/266736992): Think about a better way to set the TaskViewBase on the
// TaskViewTaskController and vice-versa
@@ -100,7 +103,8 @@ public class TaskView extends SurfaceView implements SurfaceHolder.Callback,
*/
public void startActivity(@NonNull PendingIntent pendingIntent, @Nullable Intent fillInIntent,
@NonNull ActivityOptions options, @Nullable Rect launchBounds) {
- mTaskViewTaskController.startActivity(pendingIntent, fillInIntent, options, launchBounds);
+ mTaskViewController.startActivity(mTaskViewTaskController, pendingIntent, fillInIntent,
+ options, launchBounds);
}
/**
@@ -115,19 +119,20 @@ public class TaskView extends SurfaceView implements SurfaceHolder.Callback,
*/
public void startShortcutActivity(@NonNull ShortcutInfo shortcut,
@NonNull ActivityOptions options, @Nullable Rect launchBounds) {
- mTaskViewTaskController.startShortcutActivity(shortcut, options, launchBounds);
+ mTaskViewController.startShortcutActivity(mTaskViewTaskController, shortcut, options,
+ launchBounds);
}
/**
* Moves the current task in taskview out of the view and back to fullscreen.
*/
public void moveToFullscreen() {
- mTaskViewTaskController.moveToFullscreen();
+ mTaskViewController.moveTaskViewToFullscreen(mTaskViewTaskController);
}
@Override
public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) {
- if (mTaskViewTaskController.isUsingShellTransitions()) {
+ if (mTaskViewController.isUsingShellTransitions()) {
// No need for additional work as it is already taken care of during
// prepareOpenAnimation().
return;
@@ -222,14 +227,14 @@ public class TaskView extends SurfaceView implements SurfaceHolder.Callback,
*/
public void onLocationChanged() {
getBoundsOnScreen(mTmpRect);
- mTaskViewTaskController.setWindowBounds(mTmpRect);
+ mTaskViewController.setTaskBounds(mTaskViewTaskController, mTmpRect);
}
/**
* Call to remove the task from window manager. This task will not appear in recents.
*/
public void removeTask() {
- mTaskViewTaskController.removeTask();
+ mTaskViewController.removeTaskView(mTaskViewTaskController, null /* token */);
}
/**
@@ -254,7 +259,7 @@ public class TaskView extends SurfaceView implements SurfaceHolder.Callback,
public void surfaceChanged(@androidx.annotation.NonNull SurfaceHolder holder, int format,
int width, int height) {
getBoundsOnScreen(mTmpRect);
- mTaskViewTaskController.setWindowBounds(mTmpRect);
+ mTaskViewController.setTaskBounds(mTaskViewTaskController, mTmpRect);
}
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewController.java
new file mode 100644
index 000000000000..59becf73e90b
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewController.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.wm.shell.taskview;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.ActivityManager;
+import android.app.ActivityOptions;
+import android.app.PendingIntent;
+import android.content.Intent;
+import android.content.pm.LauncherApps;
+import android.content.pm.ShortcutInfo;
+import android.graphics.Rect;
+import android.view.SurfaceControl;
+import android.window.WindowContainerToken;
+import android.window.WindowContainerTransaction;
+
+import com.android.wm.shell.ShellTaskOrganizer;
+
+/**
+ * Interface which provides methods to control TaskView properties and state.
+ *
+ * <ul>
+ * <li>To start an activity based task view, use {@link #startActivity}</li>
+ *
+ * <li>To start an activity (represented by {@link ShortcutInfo}) based task view, use
+ * {@link #startShortcutActivity}
+ * </li>
+ *
+ * <li>To start a root-task based task view, use {@link #startRootTask}.
+ * This method is special as it doesn't create a root task and instead expects that the
+ * launch root task is already created and started. This method just attaches the taskInfo to
+ * the TaskView.
+ * </li>
+ * </ul>
+ */
+public interface TaskViewController {
+ /** Registers a TaskView with this controller. */
+ void registerTaskView(@NonNull TaskViewTaskController tv);
+
+ /** Un-registers a TaskView from this controller. */
+ void unregisterTaskView(@NonNull TaskViewTaskController tv);
+
+ /**
+ * Launch an activity represented by {@link ShortcutInfo}.
+ * <p>The owner of this container must be allowed to access the shortcut information,
+ * as defined in {@link LauncherApps#hasShortcutHostPermission()} to use this method.
+ *
+ * @param destination the TaskView to start the shortcut into.
+ * @param shortcut the shortcut used to launch the activity.
+ * @param options options for the activity.
+ * @param launchBounds the bounds (window size and position) that the activity should be
+ * launched in, in pixels and in screen coordinates.
+ */
+ void startShortcutActivity(@NonNull TaskViewTaskController destination,
+ @NonNull ShortcutInfo shortcut,
+ @NonNull ActivityOptions options, @Nullable Rect launchBounds);
+
+ /**
+ * Launch a new activity into a TaskView
+ *
+ * @param destination The TaskView to start the activity into.
+ * @param pendingIntent Intent used to launch an activity.
+ * @param fillInIntent Additional Intent data, see {@link Intent#fillIn Intent.fillIn()}
+ * @param options options for the activity.
+ * @param launchBounds the bounds (window size and position) that the activity should be
+ * launched in, in pixels and in screen coordinates.
+ */
+ void startActivity(@NonNull TaskViewTaskController destination,
+ @NonNull PendingIntent pendingIntent, @Nullable Intent fillInIntent,
+ @NonNull ActivityOptions options, @Nullable Rect launchBounds);
+
+ /**
+ * Attaches the given root task {@code taskInfo} in the task view.
+ *
+ * <p> Since {@link ShellTaskOrganizer#createRootTask(int, int,
+ * ShellTaskOrganizer.TaskListener)} does not use the shell transitions flow, this method is
+ * used as an entry point for an already-created root-task in the task view.
+ *
+ * @param destination The TaskView to put the root-task into.
+ * @param taskInfo the task info of the root task.
+ * @param leash the {@link android.content.pm.ShortcutInfo.Surface} of the root task
+ * @param wct The Window container work that should happen as part of this set up.
+ */
+ void startRootTask(@NonNull TaskViewTaskController destination,
+ ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash,
+ @Nullable WindowContainerTransaction wct);
+
+ /**
+ * Closes a taskview and removes the task from window manager. This task will not appear in
+ * recents.
+ */
+ void removeTaskView(@NonNull TaskViewTaskController taskView,
+ @Nullable WindowContainerToken taskToken);
+
+ /**
+ * Moves the current task in TaskView out of the view and back to fullscreen.
+ */
+ void moveTaskViewToFullscreen(@NonNull TaskViewTaskController taskView);
+
+ /**
+ * Starts a new transition to make the given {@code taskView} visible and optionally change
+ * the task order.
+ *
+ * @param taskView the task view which the visibility is being changed for
+ * @param visible the new visibility of the task view
+ */
+ void setTaskViewVisible(TaskViewTaskController taskView, boolean visible);
+
+ /**
+ * Sets the task bounds to {@code boundsOnScreen}.
+ * Usually called when the taskview's position or size has changed.
+ *
+ * @param boundsOnScreen the on screen bounds of the surface view.
+ */
+ void setTaskBounds(TaskViewTaskController taskView, Rect boundsOnScreen);
+
+ /** Whether shell-transitions are currently enabled. */
+ boolean isUsingShellTransitions();
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewFactoryController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewFactoryController.java
index e4fcff0c372a..b2813bb382ea 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewFactoryController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewFactoryController.java
@@ -32,16 +32,16 @@ public class TaskViewFactoryController {
private final ShellTaskOrganizer mTaskOrganizer;
private final ShellExecutor mShellExecutor;
private final SyncTransactionQueue mSyncQueue;
- private final TaskViewTransitions mTaskViewTransitions;
+ private final TaskViewController mTaskViewController;
private final TaskViewFactory mImpl = new TaskViewFactoryImpl();
public TaskViewFactoryController(ShellTaskOrganizer taskOrganizer,
ShellExecutor shellExecutor, SyncTransactionQueue syncQueue,
- TaskViewTransitions taskViewTransitions) {
+ TaskViewController taskViewController) {
mTaskOrganizer = taskOrganizer;
mShellExecutor = shellExecutor;
mSyncQueue = syncQueue;
- mTaskViewTransitions = taskViewTransitions;
+ mTaskViewController = taskViewController;
}
public TaskViewFactoryController(ShellTaskOrganizer taskOrganizer,
@@ -49,7 +49,7 @@ public class TaskViewFactoryController {
mTaskOrganizer = taskOrganizer;
mShellExecutor = shellExecutor;
mSyncQueue = syncQueue;
- mTaskViewTransitions = null;
+ mTaskViewController = null;
}
/**
@@ -61,8 +61,8 @@ public class TaskViewFactoryController {
/** Creates an {@link TaskView} */
public void create(@UiContext Context context, Executor executor, Consumer<TaskView> onCreate) {
- TaskView taskView = new TaskView(context, new TaskViewTaskController(context,
- mTaskOrganizer, mTaskViewTransitions, mSyncQueue));
+ TaskView taskView = new TaskView(context, mTaskViewController, new TaskViewTaskController(
+ context, mTaskOrganizer, mTaskViewController, mSyncQueue));
executor.execute(() -> {
onCreate.accept(taskView);
});
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java
index 5c7dd078ee45..d19a7eac6ad2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java
@@ -16,32 +16,21 @@
package com.android.wm.shell.taskview;
-import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
-import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
-import static android.view.WindowManager.TRANSIT_CHANGE;
-
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
-import android.app.ActivityOptions;
-import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.Context;
-import android.content.Intent;
-import android.content.pm.LauncherApps;
-import android.content.pm.ShortcutInfo;
import android.graphics.Rect;
import android.gui.TrustedOverlay;
import android.os.Binder;
import android.util.CloseGuard;
-import android.util.Slog;
import android.view.SurfaceControl;
import android.view.WindowInsets;
import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.wm.shell.Flags;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.SyncTransactionQueue;
@@ -49,27 +38,10 @@ import java.io.PrintWriter;
import java.util.concurrent.Executor;
/**
- * This class implements the core logic to show a task on the {@link TaskView}. All the {@link
+ * This class represents the visible aspect of a task in a {@link TaskView}. All the {@link
* TaskView} to {@link TaskViewTaskController} interactions are done via direct method calls.
*
* The reverse communication is done via the {@link TaskViewBase} interface.
- *
- * <ul>
- * <li>The entry point for an activity based task view is {@link
- * TaskViewTaskController#startActivity(PendingIntent, Intent, ActivityOptions, Rect)}</li>
- *
- * <li>The entry point for an activity (represented by {@link ShortcutInfo}) based task view
- * is {@link TaskViewTaskController#startShortcutActivity(ShortcutInfo, ActivityOptions, Rect)}
- * </li>
- *
- * <li>The entry point for a root-task based task view is {@link
- * TaskViewTaskController#startRootTask(ActivityManager.RunningTaskInfo, SurfaceControl,
- * WindowContainerTransaction)}.
- * This method is special as it doesn't create a root task and instead expects that the
- * launch root task is already created and started. This method just attaches the taskInfo to
- * the TaskView.
- * </li>
- * </ul>
*/
public class TaskViewTaskController implements ShellTaskOrganizer.TaskListener {
@@ -82,7 +54,7 @@ public class TaskViewTaskController implements ShellTaskOrganizer.TaskListener {
private final ShellTaskOrganizer mTaskOrganizer;
private final Executor mShellExecutor;
private final SyncTransactionQueue mSyncQueue;
- private final TaskViewTransitions mTaskViewTransitions;
+ private final TaskViewController mTaskViewController;
private final Context mContext;
/**
@@ -109,15 +81,15 @@ public class TaskViewTaskController implements ShellTaskOrganizer.TaskListener {
private Rect mCaptionInsets;
public TaskViewTaskController(Context context, ShellTaskOrganizer organizer,
- TaskViewTransitions taskViewTransitions, SyncTransactionQueue syncQueue) {
+ TaskViewController taskViewController, SyncTransactionQueue syncQueue) {
mContext = context;
mTaskOrganizer = organizer;
mShellExecutor = organizer.getExecutor();
mSyncQueue = syncQueue;
- mTaskViewTransitions = taskViewTransitions;
+ mTaskViewController = taskViewController;
mShellExecutor.execute(() -> {
- if (mTaskViewTransitions != null) {
- mTaskViewTransitions.addTaskView(this);
+ if (mTaskViewController != null) {
+ mTaskViewController.registerTaskView(this);
}
});
mGuard.open("release");
@@ -140,6 +112,10 @@ public class TaskViewTaskController implements ShellTaskOrganizer.TaskListener {
return mSurfaceControl;
}
+ Context getContext() {
+ return mContext;
+ }
+
/**
* Sets the provided {@link TaskViewBase}, which is used to notify the client part about the
* task related changes and getting the current bounds.
@@ -155,9 +131,12 @@ public class TaskViewTaskController implements ShellTaskOrganizer.TaskListener {
return mIsInitialized;
}
- /** Until all users are converted, we may have mixed-use (eg. Car). */
- public boolean isUsingShellTransitions() {
- return mTaskViewTransitions != null && mTaskViewTransitions.isEnabled();
+ WindowContainerToken getTaskToken() {
+ return mTaskToken;
+ }
+
+ void setResizeBgColor(SurfaceControl.Transaction t, int bgColor) {
+ mTaskViewBase.setResizeBgColor(t, bgColor);
}
/**
@@ -173,122 +152,6 @@ public class TaskViewTaskController implements ShellTaskOrganizer.TaskListener {
}
/**
- * Launch an activity represented by {@link ShortcutInfo}.
- * <p>The owner of this container must be allowed to access the shortcut information,
- * as defined in {@link LauncherApps#hasShortcutHostPermission()} to use this method.
- *
- * @param shortcut the shortcut used to launch the activity.
- * @param options options for the activity.
- * @param launchBounds the bounds (window size and position) that the activity should be
- * launched in, in pixels and in screen coordinates.
- */
- public void startShortcutActivity(@NonNull ShortcutInfo shortcut,
- @NonNull ActivityOptions options, @Nullable Rect launchBounds) {
- prepareActivityOptions(options, launchBounds);
- LauncherApps service = mContext.getSystemService(LauncherApps.class);
- if (isUsingShellTransitions()) {
- mShellExecutor.execute(() -> {
- final WindowContainerTransaction wct = new WindowContainerTransaction();
- wct.startShortcut(mContext.getPackageName(), shortcut, options.toBundle());
- mTaskViewTransitions.startTaskView(wct, this, options.getLaunchCookie());
- });
- return;
- }
- try {
- service.startShortcut(shortcut, null /* sourceBounds */, options.toBundle());
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
- }
-
- /**
- * Launch a new activity.
- *
- * @param pendingIntent Intent used to launch an activity.
- * @param fillInIntent Additional Intent data, see {@link Intent#fillIn Intent.fillIn()}
- * @param options options for the activity.
- * @param launchBounds the bounds (window size and position) that the activity should be
- * launched in, in pixels and in screen coordinates.
- */
- public void startActivity(@NonNull PendingIntent pendingIntent, @Nullable Intent fillInIntent,
- @NonNull ActivityOptions options, @Nullable Rect launchBounds) {
- prepareActivityOptions(options, launchBounds);
- if (isUsingShellTransitions()) {
- mShellExecutor.execute(() -> {
- WindowContainerTransaction wct = new WindowContainerTransaction();
- wct.sendPendingIntent(pendingIntent, fillInIntent, options.toBundle());
- mTaskViewTransitions.startTaskView(wct, this, options.getLaunchCookie());
- });
- return;
- }
- try {
- pendingIntent.send(mContext, 0 /* code */, fillInIntent,
- null /* onFinished */, null /* handler */, null /* requiredPermission */,
- options.toBundle());
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
- }
-
-
- /**
- * Attaches the given root task {@code taskInfo} in the task view.
- *
- * <p> Since {@link ShellTaskOrganizer#createRootTask(int, int,
- * ShellTaskOrganizer.TaskListener)} does not use the shell transitions flow, this method is
- * used as an entry point for an already-created root-task in the task view.
- *
- * @param taskInfo the task info of the root task.
- * @param leash the {@link android.content.pm.ShortcutInfo.Surface} of the root task
- * @param wct The Window container work that should happen as part of this set up.
- */
- public void startRootTask(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash,
- @Nullable WindowContainerTransaction wct) {
- if (wct == null) {
- wct = new WindowContainerTransaction();
- }
- // This method skips the regular flow where an activity task is launched as part of a new
- // transition in taskview and then transition is intercepted using the launchcookie.
- // The task here is already created and running, it just needs to be reparented, resized
- // and tracked correctly inside taskview. Which is done by calling
- // prepareOpenAnimationInternal() and then manually enqueuing the resulting window container
- // transaction.
- prepareOpenAnimationInternal(true /* newTask */, mTransaction /* startTransaction */,
- null /* finishTransaction */, taskInfo, leash, wct);
- mTransaction.apply();
- mTaskViewTransitions.startInstantTransition(TRANSIT_CHANGE, wct);
- }
-
- /**
- * Moves the current task in TaskView out of the view and back to fullscreen.
- */
- public void moveToFullscreen() {
- if (mTaskToken == null) return;
- mShellExecutor.execute(() -> {
- WindowContainerTransaction wct = new WindowContainerTransaction();
- wct.setWindowingMode(mTaskToken, WINDOWING_MODE_UNDEFINED);
- wct.setAlwaysOnTop(mTaskToken, false);
- mTaskOrganizer.setInterceptBackPressedOnTaskRoot(mTaskToken, false);
- mTaskViewTransitions.moveTaskViewToFullscreen(wct, this);
- if (mListener != null) {
- // Task is being "removed" from the clients perspective
- mListener.onTaskRemovalStarted(mTaskInfo.taskId);
- }
- });
- }
-
- private void prepareActivityOptions(ActivityOptions options, Rect launchBounds) {
- final Binder launchCookie = new Binder();
- mShellExecutor.execute(() -> {
- mTaskOrganizer.setPendingLaunchCookieListener(launchCookie, this);
- });
- options.setLaunchBounds(launchBounds);
- options.setLaunchCookie(launchCookie);
- options.setLaunchWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
- options.setRemoveWithTaskOrganizer(true);
- }
-
- /**
* Release this container if it is initialized.
*/
public void release() {
@@ -309,8 +172,8 @@ public class TaskViewTaskController implements ShellTaskOrganizer.TaskListener {
private void performRelease() {
mShellExecutor.execute(() -> {
- if (mTaskViewTransitions != null) {
- mTaskViewTransitions.removeTaskView(this);
+ if (mTaskViewController != null) {
+ mTaskViewController.unregisterTaskView(this);
}
mTaskOrganizer.removeListener(this);
resetTaskInfo();
@@ -364,7 +227,7 @@ public class TaskViewTaskController implements ShellTaskOrganizer.TaskListener {
@Override
public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo,
SurfaceControl leash) {
- if (isUsingShellTransitions()) {
+ if (mTaskViewController.isUsingShellTransitions()) {
mPendingInfo = taskInfo;
if (mTaskNotFound) {
// If we were already notified by shell transit that we don't have the
@@ -484,8 +347,8 @@ public class TaskViewTaskController implements ShellTaskOrganizer.TaskListener {
// Nothing to update, task is not yet available
return;
}
- if (isUsingShellTransitions()) {
- mTaskViewTransitions.setTaskViewVisible(this, true /* visible */);
+ if (mTaskViewController.isUsingShellTransitions()) {
+ mTaskViewController.setTaskViewVisible(this, true /* visible */);
return;
}
// Reparent the task when this surface is created
@@ -497,56 +360,6 @@ public class TaskViewTaskController implements ShellTaskOrganizer.TaskListener {
}
/**
- * Sets the window bounds to {@code boundsOnScreen}.
- * Call when view position or size has changed. Can also be called before the animation when
- * the final bounds are known.
- * Do not call during the animation.
- *
- * @param boundsOnScreen the on screen bounds of the surface view.
- */
- public void setWindowBounds(Rect boundsOnScreen) {
- if (mTaskToken == null) {
- return;
- }
-
- if (isUsingShellTransitions()) {
- mShellExecutor.execute(() -> {
- // Sync Transactions can't operate simultaneously with shell transition collection.
- mTaskViewTransitions.setTaskBounds(this, boundsOnScreen);
- });
- return;
- }
-
- WindowContainerTransaction wct = new WindowContainerTransaction();
- wct.setBounds(mTaskToken, boundsOnScreen);
- mSyncQueue.queue(wct);
- }
-
- /**
- * Call to remove the task from window manager. This task will not appear in recents.
- */
- void removeTask() {
- if (mTaskToken == null) {
- if (Flags.enableTaskViewControllerCleanup()) {
- // We don't have a task yet. Only clean up the controller
- mTaskViewTransitions.removeTaskView(this);
- } else {
- // Call to remove task before we have one, do nothing
- Slog.w(TAG, "Trying to remove a task that was never added? (no taskToken)");
- }
- return;
- }
- // Cache it to avoid NPE and make sure to remove it from recents history.
- // mTaskToken can be cleared in onTaskVanished() when the task is removed.
- final WindowContainerToken taskToken = mTaskToken;
- mShellExecutor.execute(() -> {
- WindowContainerTransaction wct = new WindowContainerTransaction();
- wct.removeTask(taskToken);
- mTaskViewTransitions.closeTaskView(wct, this);
- });
- }
-
- /**
* Sets a region of the task to inset to allow for a caption bar.
*
* @param captionInsets the rect for the insets in screen coordinates.
@@ -583,8 +396,8 @@ public class TaskViewTaskController implements ShellTaskOrganizer.TaskListener {
return;
}
- if (isUsingShellTransitions()) {
- mTaskViewTransitions.setTaskViewVisible(this, false /* visible */);
+ if (mTaskViewController.isUsingShellTransitions()) {
+ mTaskViewController.setTaskViewVisible(this, false /* visible */);
return;
}
@@ -604,15 +417,16 @@ public class TaskViewTaskController implements ShellTaskOrganizer.TaskListener {
}
}
+ void notifyTaskRemovalStarted(@NonNull ActivityManager.RunningTaskInfo taskInfo) {
+ if (mListener == null) return;
+ final int taskId = taskInfo.taskId;
+ mListenerExecutor.execute(() -> mListener.onTaskRemovalStarted(taskId));
+ }
+
/** Notifies listeners of a task being removed and stops intercepting back presses on it. */
private void handleAndNotifyTaskRemoval(ActivityManager.RunningTaskInfo taskInfo) {
if (taskInfo != null) {
- if (mListener != null) {
- final int taskId = taskInfo.taskId;
- mListenerExecutor.execute(() -> {
- mListener.onTaskRemovalStarted(taskId);
- });
- }
+ notifyTaskRemovalStarted(taskInfo);
mTaskViewBase.onTaskVanished(taskInfo);
}
}
@@ -651,9 +465,7 @@ public class TaskViewTaskController implements ShellTaskOrganizer.TaskListener {
handleAndNotifyTaskRemoval(pendingInfo);
// Make sure the task is removed
- WindowContainerTransaction wct = new WindowContainerTransaction();
- wct.removeTask(pendingInfo.token);
- mTaskViewTransitions.closeTaskView(wct, this);
+ mTaskViewController.removeTaskView(this, pendingInfo.token);
}
resetTaskInfo();
}
@@ -681,72 +493,23 @@ public class TaskViewTaskController implements ShellTaskOrganizer.TaskListener {
resetTaskInfo();
}
- void prepareOpenAnimation(final boolean newTask,
- @NonNull SurfaceControl.Transaction startTransaction,
- @NonNull SurfaceControl.Transaction finishTransaction,
- ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash,
- WindowContainerTransaction wct) {
- prepareOpenAnimationInternal(newTask, startTransaction, finishTransaction, taskInfo, leash,
- wct);
- }
-
- private TaskViewRepository.TaskViewState getState() {
- return mTaskViewTransitions.getRepository().byTaskView(this);
- }
-
- private void prepareOpenAnimationInternal(final boolean newTask,
- SurfaceControl.Transaction startTransaction,
- SurfaceControl.Transaction finishTransaction,
- ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash,
- WindowContainerTransaction wct) {
+ /**
+ * Prepare this taskview to open {@param taskInfo}.
+ * @return The bounds of the task or {@code null} on failure (surface is destroyed)
+ */
+ Rect prepareOpen(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) {
mPendingInfo = null;
mTaskInfo = taskInfo;
mTaskToken = mTaskInfo.token;
mTaskLeash = leash;
- if (mSurfaceCreated) {
- // Surface is ready, so just reparent the task to this surface control
- startTransaction.reparent(mTaskLeash, mSurfaceControl)
- .show(mTaskLeash);
- // Also reparent on finishTransaction since the finishTransaction will reparent back
- // to its "original" parent by default.
- Rect boundsOnScreen = mTaskViewBase.getCurrentBoundsOnScreen();
- if (finishTransaction != null) {
- finishTransaction.reparent(mTaskLeash, mSurfaceControl)
- .setPosition(mTaskLeash, 0, 0)
- // TODO: maybe once b/280900002 is fixed this will be unnecessary
- .setWindowCrop(mTaskLeash, boundsOnScreen.width(), boundsOnScreen.height());
- }
- if (TaskViewTransitions.useRepo()) {
- final TaskViewRepository.TaskViewState state = getState();
- if (state != null) {
- state.mBounds.set(boundsOnScreen);
- state.mVisible = true;
- }
- } else {
- mTaskViewTransitions.updateBoundsState(this, boundsOnScreen);
- mTaskViewTransitions.updateVisibilityState(this, true /* visible */);
- }
- wct.setBounds(mTaskToken, boundsOnScreen);
- applyCaptionInsetsIfNeeded();
- } else {
- // The surface has already been destroyed before the task has appeared,
- // so go ahead and hide the task entirely
- wct.setHidden(mTaskToken, true /* hidden */);
- mTaskViewTransitions.updateVisibilityState(this, false /* visible */);
- // listener callback is below
- }
- if (newTask) {
- mTaskOrganizer.setInterceptBackPressedOnTaskRoot(mTaskToken, true /* intercept */);
- }
-
- if (mTaskInfo.taskDescription != null) {
- int backgroundColor = mTaskInfo.taskDescription.getBackgroundColor();
- mTaskViewBase.setResizeBgColor(startTransaction, backgroundColor);
+ if (!mSurfaceCreated) {
+ return null;
}
+ return mTaskViewBase.getCurrentBoundsOnScreen();
+ }
- // After the embedded task has appeared, set it to non-trimmable. This is important
- // to prevent recents from trimming and removing the embedded task.
- wct.setTaskTrimmableFromRecents(taskInfo.token, false /* isTrimmableFromRecents */);
+ /** Notify that the associated task has appeared. This will call appropriate listeners. */
+ void notifyAppeared(final boolean newTask) {
mTaskViewBase.onTaskAppeared(mTaskInfo, mTaskLeash);
if (mListener != null) {
final int taskId = mTaskInfo.taskId;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java
index 0cbb7bd14e6f..6c90a9060523 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java
@@ -16,6 +16,8 @@
package com.android.wm.shell.taskview;
+import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_OPEN;
@@ -25,7 +27,14 @@ import static android.view.WindowManager.TRANSIT_TO_FRONT;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
+import android.app.ActivityOptions;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.LauncherApps;
+import android.content.pm.ShortcutInfo;
import android.graphics.Rect;
+import android.os.Binder;
import android.os.IBinder;
import android.util.ArrayMap;
import android.util.Slog;
@@ -33,11 +42,14 @@ import android.view.SurfaceControl;
import android.view.WindowManager;
import android.window.TransitionInfo;
import android.window.TransitionRequestInfo;
+import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
import androidx.annotation.VisibleForTesting;
import com.android.wm.shell.Flags;
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.shared.TransitionUtil;
import com.android.wm.shell.transition.Transitions;
@@ -45,11 +57,12 @@ import java.util.ArrayList;
import java.util.Map;
import java.util.Objects;
import java.util.WeakHashMap;
+import java.util.concurrent.Executor;
/**
* Handles Shell Transitions that involve TaskView tasks.
*/
-public class TaskViewTransitions implements Transitions.TransitionHandler {
+public class TaskViewTransitions implements Transitions.TransitionHandler, TaskViewController {
static final String TAG = "TaskViewTransitions";
/**
@@ -65,6 +78,12 @@ public class TaskViewTransitions implements Transitions.TransitionHandler {
private final ArrayList<PendingTransition> mPending = new ArrayList<>();
private final Transitions mTransitions;
private final boolean[] mRegistered = new boolean[]{false};
+ private final ShellTaskOrganizer mTaskOrganizer;
+ private final Executor mShellExecutor;
+ private final SyncTransactionQueue mSyncQueue;
+
+ /** A temp transaction used for quick things. */
+ private final SurfaceControl.Transaction mTransaction = new SurfaceControl.Transaction();
/**
* TaskView makes heavy use of startTransition. Only one shell-initiated transition can be
@@ -96,8 +115,12 @@ public class TaskViewTransitions implements Transitions.TransitionHandler {
}
}
- public TaskViewTransitions(Transitions transitions, TaskViewRepository repository) {
+ public TaskViewTransitions(Transitions transitions, TaskViewRepository repository,
+ ShellTaskOrganizer taskOrganizer, SyncTransactionQueue syncQueue) {
mTransitions = transitions;
+ mTaskOrganizer = taskOrganizer;
+ mShellExecutor = taskOrganizer.getExecutor();
+ mSyncQueue = syncQueue;
if (useRepo()) {
mTaskViews = null;
} else if (Flags.enableTaskViewControllerCleanup()) {
@@ -111,7 +134,8 @@ public class TaskViewTransitions implements Transitions.TransitionHandler {
// TODO(210041388): register here once we have an explicit ordering mechanism.
}
- static boolean useRepo() {
+ /** @return whether the shared taskview repository is being used. */
+ public static boolean useRepo() {
return Flags.taskViewRepository() || Flags.enableBubbleAnything();
}
@@ -119,7 +143,8 @@ public class TaskViewTransitions implements Transitions.TransitionHandler {
return mTaskViewRepo;
}
- void addTaskView(TaskViewTaskController tv) {
+ @Override
+ public void registerTaskView(TaskViewTaskController tv) {
synchronized (mRegistered) {
if (!mRegistered[0]) {
mRegistered[0] = true;
@@ -133,7 +158,8 @@ public class TaskViewTransitions implements Transitions.TransitionHandler {
}
}
- void removeTaskView(TaskViewTaskController tv) {
+ @Override
+ public void unregisterTaskView(TaskViewTaskController tv) {
if (useRepo()) {
mTaskViewRepo.remove(tv);
} else {
@@ -142,27 +168,12 @@ public class TaskViewTransitions implements Transitions.TransitionHandler {
// Note: Don't unregister handler since this is a singleton with lifetime bound to Shell
}
- boolean isEnabled() {
+ @Override
+ public boolean isUsingShellTransitions() {
return mTransitions.isRegistered();
}
/**
- * Looks through the pending transitions for a closing transaction that matches the provided
- * `taskView`.
- *
- * @param taskView the pending transition should be for this.
- */
- private PendingTransition findPendingCloseTransition(TaskViewTaskController taskView) {
- for (int i = mPending.size() - 1; i >= 0; --i) {
- if (mPending.get(i).mTaskView != taskView) continue;
- if (TransitionUtil.isClosingType(mPending.get(i).mType)) {
- return mPending.get(i);
- }
- }
- return null;
- }
-
- /**
* Starts a transition outside of the handler associated with {@link TaskViewTransitions}.
*/
public void startInstantTransition(@WindowManager.TransitionType int type,
@@ -264,6 +275,82 @@ public class TaskViewTransitions implements Transitions.TransitionHandler {
return findTaskView(taskInfo) != null;
}
+ private void prepareActivityOptions(ActivityOptions options, Rect launchBounds,
+ @NonNull TaskViewTaskController destination) {
+ final Binder launchCookie = new Binder();
+ mShellExecutor.execute(() -> {
+ mTaskOrganizer.setPendingLaunchCookieListener(launchCookie, destination);
+ });
+ options.setLaunchBounds(launchBounds);
+ options.setLaunchCookie(launchCookie);
+ options.setLaunchWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
+ options.setRemoveWithTaskOrganizer(true);
+ }
+
+ @Override
+ public void startShortcutActivity(@NonNull TaskViewTaskController destination,
+ @NonNull ShortcutInfo shortcut, @NonNull ActivityOptions options,
+ @Nullable Rect launchBounds) {
+ prepareActivityOptions(options, launchBounds, destination);
+ final Context context = destination.getContext();
+ if (isUsingShellTransitions()) {
+ mShellExecutor.execute(() -> {
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ wct.startShortcut(context.getPackageName(), shortcut, options.toBundle());
+ startTaskView(wct, destination, options.getLaunchCookie());
+ });
+ return;
+ }
+ try {
+ LauncherApps service = context.getSystemService(LauncherApps.class);
+ service.startShortcut(shortcut, null /* sourceBounds */, options.toBundle());
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public void startActivity(@NonNull TaskViewTaskController destination,
+ @NonNull PendingIntent pendingIntent, @Nullable Intent fillInIntent,
+ @NonNull ActivityOptions options, @Nullable Rect launchBounds) {
+ prepareActivityOptions(options, launchBounds, destination);
+ if (isUsingShellTransitions()) {
+ mShellExecutor.execute(() -> {
+ WindowContainerTransaction wct = new WindowContainerTransaction();
+ wct.sendPendingIntent(pendingIntent, fillInIntent, options.toBundle());
+ startTaskView(wct, destination, options.getLaunchCookie());
+ });
+ return;
+ }
+ try {
+ pendingIntent.send(destination.getContext(), 0 /* code */, fillInIntent,
+ null /* onFinished */, null /* handler */, null /* requiredPermission */,
+ options.toBundle());
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public void startRootTask(@NonNull TaskViewTaskController destination,
+ ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash,
+ @Nullable WindowContainerTransaction wct) {
+ if (wct == null) {
+ wct = new WindowContainerTransaction();
+ }
+ // This method skips the regular flow where an activity task is launched as part of a new
+ // transition in taskview and then transition is intercepted using the launchcookie.
+ // The task here is already created and running, it just needs to be reparented, resized
+ // and tracked correctly inside taskview. Which is done by calling
+ // prepareOpenAnimationInternal() and then manually enqueuing the resulting window container
+ // transaction.
+ prepareOpenAnimation(destination, true /* newTask */, mTransaction /* startTransaction */,
+ null /* finishTransaction */, taskInfo, leash, wct);
+ mTransaction.apply();
+ mTransitions.startTransition(TRANSIT_CHANGE, wct, null);
+ }
+
+ @VisibleForTesting
void startTaskView(@NonNull WindowContainerTransaction wct,
@NonNull TaskViewTaskController taskView, @NonNull IBinder launchCookie) {
updateVisibilityState(taskView, true /* visible */);
@@ -271,30 +358,53 @@ public class TaskViewTransitions implements Transitions.TransitionHandler {
startNextTransition();
}
- void closeTaskView(@NonNull WindowContainerTransaction wct,
- @NonNull TaskViewTaskController taskView) {
+ @Override
+ public void removeTaskView(@NonNull TaskViewTaskController taskView,
+ @Nullable WindowContainerToken taskToken) {
+ final WindowContainerToken token = taskToken != null ? taskToken : taskView.getTaskToken();
+ if (token == null) {
+ // We don't have a task yet, so just clean up records
+ if (!Flags.enableTaskViewControllerCleanup()) {
+ // Call to remove task before we have one, do nothing
+ Slog.w(TAG, "Trying to remove a task that was never added? (no taskToken)");
+ return;
+ }
+ unregisterTaskView(taskView);
+ return;
+ }
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ wct.removeTask(token);
updateVisibilityState(taskView, false /* visible */);
- mPending.add(new PendingTransition(TRANSIT_CLOSE, wct, taskView, null /* cookie */));
- startNextTransition();
+ mShellExecutor.execute(() -> {
+ mPending.add(new PendingTransition(TRANSIT_CLOSE, wct, taskView, null /* cookie */));
+ startNextTransition();
+ });
}
- void moveTaskViewToFullscreen(@NonNull WindowContainerTransaction wct,
- @NonNull TaskViewTaskController taskView) {
- mPending.add(new PendingTransition(TRANSIT_CHANGE, wct, taskView, null /* cookie */));
- startNextTransition();
+ @Override
+ public void moveTaskViewToFullscreen(@NonNull TaskViewTaskController taskView) {
+ final WindowContainerToken taskToken = taskView.getTaskToken();
+ if (taskToken == null) return;
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ wct.setWindowingMode(taskToken, WINDOWING_MODE_UNDEFINED);
+ wct.setAlwaysOnTop(taskToken, false);
+ mShellExecutor.execute(() -> {
+ mTaskOrganizer.setInterceptBackPressedOnTaskRoot(taskToken, false);
+ mPending.add(new PendingTransition(TRANSIT_CHANGE, wct, taskView, null /* cookie */));
+ startNextTransition();
+ taskView.notifyTaskRemovalStarted(taskView.getTaskInfo());
+ });
}
- /** Starts a new transition to make the given {@code taskView} visible. */
+ @Override
public void setTaskViewVisible(TaskViewTaskController taskView, boolean visible) {
setTaskViewVisible(taskView, visible, false /* reorder */);
}
/**
- * Starts a new transition to make the given {@code taskView} visible and optionally change
- * the task order.
+ * Starts a new transition to make the given {@code taskView} visible and optionally
+ * reordering it.
*
- * @param taskView the task view which the visibility is being changed for
- * @param visible the new visibility of the task view
* @param reorder whether to reorder the task or not. If this is {@code true}, the task will
* be reordered as per the given {@code visible}. For {@code visible = true},
* task will be reordered to top. For {@code visible = false}, task will be
@@ -359,7 +469,26 @@ public class TaskViewTransitions implements Transitions.TransitionHandler {
state.mVisible = visible;
}
- void setTaskBounds(TaskViewTaskController taskView, Rect boundsOnScreen) {
+ @Override
+ public void setTaskBounds(TaskViewTaskController taskView, Rect boundsOnScreen) {
+ if (taskView.getTaskToken() == null) {
+ return;
+ }
+
+ if (isUsingShellTransitions()) {
+ mShellExecutor.execute(() -> {
+ // Sync Transactions can't operate simultaneously with shell transition collection.
+ setTaskBoundsInTransition(taskView, boundsOnScreen);
+ });
+ return;
+ }
+
+ WindowContainerTransaction wct = new WindowContainerTransaction();
+ wct.setBounds(taskView.getTaskToken(), boundsOnScreen);
+ mSyncQueue.queue(wct);
+ }
+
+ private void setTaskBoundsInTransition(TaskViewTaskController taskView, Rect boundsOnScreen) {
final TaskViewRepository.TaskViewState state = useRepo()
? mTaskViewRepo.byTaskView(taskView)
: mTaskViews.get(taskView);
@@ -476,7 +605,7 @@ public class TaskViewTransitions implements Transitions.TransitionHandler {
}
}
if (wct == null) wct = new WindowContainerTransaction();
- tv.prepareOpenAnimation(taskIsNew, startTransaction, finishTransaction,
+ prepareOpenAnimation(tv, taskIsNew, startTransaction, finishTransaction,
chg.getTaskInfo(), chg.getLeash(), wct);
changesHandled++;
} else if (chg.getMode() == TRANSIT_CHANGE) {
@@ -510,4 +639,60 @@ public class TaskViewTransitions implements Transitions.TransitionHandler {
startNextTransition();
return true;
}
+
+ @VisibleForTesting
+ void prepareOpenAnimation(TaskViewTaskController taskView,
+ final boolean newTask,
+ SurfaceControl.Transaction startTransaction,
+ SurfaceControl.Transaction finishTransaction,
+ ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash,
+ WindowContainerTransaction wct) {
+ final Rect boundsOnScreen = taskView.prepareOpen(taskInfo, leash);
+ if (boundsOnScreen != null) {
+ final SurfaceControl tvSurface = taskView.getSurfaceControl();
+ // Surface is ready, so just reparent the task to this surface control
+ startTransaction.reparent(leash, tvSurface)
+ .show(leash);
+ // Also reparent on finishTransaction since the finishTransaction will reparent back
+ // to its "original" parent by default.
+ if (finishTransaction != null) {
+ finishTransaction.reparent(leash, tvSurface)
+ .setPosition(leash, 0, 0)
+ // TODO: maybe once b/280900002 is fixed this will be unnecessary
+ .setWindowCrop(leash, boundsOnScreen.width(), boundsOnScreen.height());
+ }
+ if (useRepo()) {
+ final TaskViewRepository.TaskViewState state = mTaskViewRepo.byTaskView(taskView);
+ if (state != null) {
+ state.mBounds.set(boundsOnScreen);
+ state.mVisible = true;
+ }
+ } else {
+ updateBoundsState(taskView, boundsOnScreen);
+ updateVisibilityState(taskView, true /* visible */);
+ }
+ wct.setBounds(taskInfo.token, boundsOnScreen);
+ taskView.applyCaptionInsetsIfNeeded();
+ } else {
+ // The surface has already been destroyed before the task has appeared,
+ // so go ahead and hide the task entirely
+ wct.setHidden(taskInfo.token, true /* hidden */);
+ updateVisibilityState(taskView, false /* visible */);
+ // listener callback is below
+ }
+ if (newTask) {
+ mTaskOrganizer.setInterceptBackPressedOnTaskRoot(taskInfo.token, true /* intercept */);
+ }
+
+ if (taskInfo.taskDescription != null) {
+ int backgroundColor = taskInfo.taskDescription.getBackgroundColor();
+ taskView.setResizeBgColor(startTransaction, backgroundColor);
+ }
+
+ // After the embedded task has appeared, set it to non-trimmable. This is important
+ // to prevent recents from trimming and removing the embedded task.
+ wct.setTaskTrimmableFromRecents(taskInfo.token, false /* isTrimmableFromRecents */);
+
+ taskView.notifyAppeared(newTask);
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/MixedTransitionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/MixedTransitionHelper.java
index e61929fef312..2133275cde61 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/MixedTransitionHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/MixedTransitionHelper.java
@@ -17,13 +17,14 @@
package com.android.wm.shell.transition;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+import static android.view.WindowManager.TRANSIT_PIP;
import static android.view.WindowManager.TRANSIT_TO_BACK;
import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
-import static com.android.wm.shell.shared.split.SplitScreenConstants.FLAG_IS_DIVIDER_BAR;
-import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED;
import static com.android.wm.shell.pip.PipAnimationController.ANIM_TYPE_ALPHA;
import static com.android.wm.shell.shared.TransitionUtil.isOpeningMode;
+import static com.android.wm.shell.shared.split.SplitScreenConstants.FLAG_IS_DIVIDER_BAR;
+import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED;
import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_MAIN;
import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_SIDE;
import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED;
@@ -36,6 +37,7 @@ import android.view.SurfaceControl;
import android.window.TransitionInfo;
import com.android.internal.protolog.ProtoLog;
+import com.android.wm.shell.common.pip.PipUtils;
import com.android.wm.shell.keyguard.KeyguardTransitionHandler;
import com.android.wm.shell.pip.PipTransitionController;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
@@ -54,6 +56,7 @@ public class MixedTransitionHelper {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Animating a mixed transition for "
+ "entering PIP while Split-Screen is foreground.");
TransitionInfo.Change pipChange = null;
+ TransitionInfo.Change pipActivityChange = null;
TransitionInfo.Change wallpaper = null;
final TransitionInfo everythingElse =
subCopy(info, TRANSIT_TO_BACK, true /* changes */);
@@ -68,6 +71,13 @@ public class MixedTransitionHelper {
pipChange = change;
// going backwards, so remove-by-index is fine.
everythingElse.getChanges().remove(i);
+ } else if (change.getTaskInfo() == null && change.getParent() != null
+ && pipChange != null && change.getParent().equals(pipChange.getContainer())) {
+ // Cache the PiP activity if it's a target and cached pip task change is its parent;
+ // note that we are bottom-to-top, so if such activity has a task
+ // that is also a target, then it must have been cached already as pipChange.
+ pipActivityChange = change;
+ everythingElse.getChanges().remove(i);
} else if (isHomeOpening(change)) {
homeIsOpening = true;
} else if (isWallpaper(change)) {
@@ -138,9 +148,19 @@ public class MixedTransitionHelper {
}
}
- pipHandler.setEnterAnimationType(ANIM_TYPE_ALPHA);
- pipHandler.startEnterAnimation(pipChange, startTransaction, finishTransaction,
- finishCB);
+ if (PipUtils.isPip2ExperimentEnabled()) {
+ TransitionInfo pipInfo = subCopy(info, TRANSIT_PIP, false /* withChanges */);
+ pipInfo.getChanges().add(pipChange);
+ if (pipActivityChange != null) {
+ pipInfo.getChanges().add(pipActivityChange);
+ }
+ pipHandler.startAnimation(mixed.mTransition, pipInfo, startTransaction,
+ finishTransaction, finishCB);
+ } else {
+ pipHandler.setEnterAnimationType(ANIM_TYPE_ALPHA);
+ pipHandler.startEnterAnimation(pipChange, startTransaction, finishTransaction,
+ finishCB);
+ }
// make a new finishTransaction because pip's startEnterAnimation "consumes" it so
// we need a separate one to send over to launcher.
SurfaceControl.Transaction otherFinishT = new SurfaceControl.Transaction();
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 792f5cad3418..7aa00370ff58 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
@@ -284,6 +284,10 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel, FocusT
}
private boolean shouldShowWindowDecor(RunningTaskInfo taskInfo) {
+ if (mDisplayController.getDisplay(taskInfo.displayId) == null) {
+ // If DisplayController doesn't have it tracked, it could be a private/managed display.
+ return false;
+ }
if (taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) {
return true;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CarWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CarWindowDecorViewModel.java
index 0d75e659d95c..7948eadb28f4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CarWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CarWindowDecorViewModel.java
@@ -110,9 +110,6 @@ public abstract class CarWindowDecorViewModel
SurfaceControl taskSurface,
SurfaceControl.Transaction startT,
SurfaceControl.Transaction finishT) {
- if (!shouldShowWindowDecor(taskInfo)) {
- return false;
- }
createWindowDecoration(taskInfo, taskSurface, startT, finishT);
return true;
}
@@ -125,12 +122,9 @@ public abstract class CarWindowDecorViewModel
return;
}
- if (!shouldShowWindowDecor(taskInfo)) {
- destroyWindowDecoration(taskInfo);
- return;
- }
-
- decoration.relayout(taskInfo, decoration.mHasGlobalFocus, decoration.mExclusionRegion);
+ final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+ decoration.relayout(taskInfo, t, t,
+ /* isCaptionVisible= */ shouldShowWindowDecor(taskInfo));
}
@Override
@@ -221,7 +215,8 @@ public abstract class CarWindowDecorViewModel
mWindowDecorViewHostSupplier,
new ButtonClickListener(taskInfo));
mWindowDecorByTaskId.put(taskInfo.taskId, windowDecoration);
- windowDecoration.relayout(taskInfo, startT, finishT);
+ windowDecoration.relayout(taskInfo, startT, finishT,
+ /* isCaptionVisible= */ shouldShowWindowDecor(taskInfo));
}
private class ButtonClickListener implements View.OnClickListener {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CarWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CarWindowDecoration.java
index 1ca82d23c830..39437845301e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CarWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CarWindowDecoration.java
@@ -20,14 +20,17 @@ import static android.view.InsetsSource.FLAG_FORCE_CONSUMING_OPAQUE_CAPTION_BAR;
import android.annotation.SuppressLint;
import android.app.ActivityManager;
import android.content.Context;
+import android.graphics.Insets;
import android.graphics.Rect;
import android.graphics.Region;
import android.view.InsetsState;
import android.view.SurfaceControl;
import android.view.View;
+import android.view.WindowInsets;
import android.window.WindowContainerTransaction;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import com.android.wm.shell.R;
import com.android.wm.shell.ShellTaskOrganizer;
@@ -44,6 +47,7 @@ public class CarWindowDecoration extends WindowDecoration<WindowDecorLinearLayou
private WindowDecorLinearLayout mRootView;
private @ShellBackgroundThread final ShellExecutor mBgExecutor;
private final View.OnClickListener mClickListener;
+ private final RelayoutResult<WindowDecorLinearLayout> mResult = new RelayoutResult<>();
CarWindowDecoration(
Context context,
@@ -71,26 +75,32 @@ public class CarWindowDecoration extends WindowDecoration<WindowDecorLinearLayou
@SuppressLint("MissingPermission")
void relayout(ActivityManager.RunningTaskInfo taskInfo,
SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT) {
+ relayout(taskInfo, startT, finishT, /* isCaptionVisible= */ true);
+ }
+
+ @SuppressLint("MissingPermission")
+ void relayout(ActivityManager.RunningTaskInfo taskInfo,
+ SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT,
+ boolean isCaptionVisible) {
final WindowContainerTransaction wct = new WindowContainerTransaction();
RelayoutParams relayoutParams = new RelayoutParams();
- RelayoutResult<WindowDecorLinearLayout> outResult = new RelayoutResult<>();
updateRelayoutParams(relayoutParams, taskInfo,
- mDisplayController.getInsetsState(taskInfo.displayId));
+ mDisplayController.getInsetsState(taskInfo.displayId), isCaptionVisible);
- relayout(relayoutParams, startT, finishT, wct, mRootView, outResult);
+ relayout(relayoutParams, startT, finishT, wct, mRootView, mResult);
// After this line, mTaskInfo is up-to-date and should be used instead of taskInfo
mBgExecutor.execute(() -> mTaskOrganizer.applyTransaction(wct));
- if (outResult.mRootView == null) {
+ if (mResult.mRootView == null) {
// This means something blocks the window decor from showing, e.g. the task is hidden.
// Nothing is set up in this case including the decoration surface.
return;
}
- if (mRootView != outResult.mRootView) {
- mRootView = outResult.mRootView;
- setupRootView(outResult.mRootView, mClickListener);
+ if (mRootView != mResult.mRootView) {
+ mRootView = mResult.mRootView;
+ setupRootView(mResult.mRootView, mClickListener);
}
}
@@ -108,18 +118,31 @@ public class CarWindowDecoration extends WindowDecoration<WindowDecorLinearLayou
private void updateRelayoutParams(
RelayoutParams relayoutParams,
ActivityManager.RunningTaskInfo taskInfo,
- InsetsState displayInsetsState) {
+ @Nullable InsetsState displayInsetsState,
+ boolean isCaptionVisible) {
relayoutParams.reset();
relayoutParams.mRunningTaskInfo = taskInfo;
// todo(b/382071404): update to car specific UI
relayoutParams.mLayoutResId = R.layout.caption_window_decor;
relayoutParams.mCaptionHeightId = R.dimen.freeform_decor_caption_height;
- relayoutParams.mIsCaptionVisible = mIsStatusBarVisible && !mIsKeyguardVisibleAndOccluded;
- relayoutParams.mCaptionTopPadding = 0;
+ relayoutParams.mIsCaptionVisible =
+ isCaptionVisible && mIsStatusBarVisible && !mIsKeyguardVisibleAndOccluded;
+ if (displayInsetsState != null) {
+ relayoutParams.mCaptionTopPadding = getTopPadding(
+ taskInfo.getConfiguration().windowConfiguration.getBounds(),
+ displayInsetsState);
+ }
relayoutParams.mInsetSourceFlags |= FLAG_FORCE_CONSUMING_OPAQUE_CAPTION_BAR;
relayoutParams.mApplyStartTransactionOnDraw = true;
}
+ private static int getTopPadding(Rect taskBounds, @NonNull InsetsState insetsState) {
+ Insets systemDecor = insetsState.calculateInsets(taskBounds,
+ WindowInsets.Type.systemBars() & ~WindowInsets.Type.captionBar(),
+ false /* ignoreVisibility */);
+ return systemDecor.top;
+ }
+
/**
* Sets up listeners when a new root view is created.
*/
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopHeaderManageWindowsMenu.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopHeaderManageWindowsMenu.kt
index ff52a45c94e2..575aac381c42 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopHeaderManageWindowsMenu.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopHeaderManageWindowsMenu.kt
@@ -74,8 +74,7 @@ class DesktopHeaderManageWindowsMenu(
override fun addToContainer(menuView: ManageWindowsView) {
val menuPosition = Point(x, y)
val flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or
- WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH or
- WindowManager.LayoutParams.FLAG_SPLIT_TOUCH
+ WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
val desktopRepository = desktopUserRepositories.getProfile(callerTaskInfo.userId)
menuViewContainer = if (Flags.enableFullyImmersiveInDesktop()
&& desktopRepository.isTaskInFullImmersiveState(callerTaskInfo.taskId)) {
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 d6d393f2500c..51b0291cab91 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
@@ -34,6 +34,7 @@ import static com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_ENTER_MODE_APP_HAND
import static com.android.window.flags.Flags.enableDisplayFocusInShellTransitions;
import static com.android.wm.shell.compatui.AppCompatUtils.isTopActivityExemptFromDesktopWindowing;
import static com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.InputMethod;
+import static com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.MinimizeReason;
import static com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ResizeTrigger;
import static com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR;
import static com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_LEFT_INDICATOR;
@@ -980,7 +981,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
ToggleTaskSizeInteraction.AmbiguousSource.HEADER_BUTTON, mMotionEvent);
}
} else if (id == R.id.minimize_window) {
- mDesktopTasksController.minimizeTask(decoration.mTaskInfo);
+ mDesktopTasksController.minimizeTask(
+ decoration.mTaskInfo, MinimizeReason.MINIMIZE_BUTTON);
}
}
@@ -1641,6 +1643,10 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
}
private boolean shouldShowWindowDecor(RunningTaskInfo taskInfo) {
+ if (mDisplayController.getDisplay(taskInfo.displayId) == null) {
+ // If DisplayController doesn't have it tracked, it could be a private/managed display.
+ return false;
+ }
if (taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) return true;
if (mSplitScreenController != null
&& mSplitScreenController.isTaskRootOrStageRoot(taskInfo.taskId)) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
index 7d1471f44674..b531079f18c1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
@@ -17,7 +17,6 @@
package com.android.wm.shell.windowdecor;
import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
-import static android.view.WindowManager.LayoutParams.FLAG_SPLIT_TOUCH;
import static android.view.WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL;
import static android.view.WindowManager.LayoutParams.INPUT_FEATURE_SPY;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;
@@ -122,7 +121,7 @@ class DragResizeInputListener implements AutoCloseable {
mDecorationSurface,
mClientToken,
null /* hostInputToken */,
- FLAG_NOT_FOCUSABLE | FLAG_SPLIT_TOUCH,
+ FLAG_NOT_FOCUSABLE,
PRIVATE_FLAG_TRUSTED_OVERLAY,
INPUT_FEATURE_SPY,
TYPE_APPLICATION,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt
index 9d73950abcf0..e5c989ed5f97 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt
@@ -245,8 +245,7 @@ class HandleMenu(
width = menuWidth,
height = menuHeight,
flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or
- WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH or
- WindowManager.LayoutParams.FLAG_SPLIT_TOUCH,
+ WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH,
view = handleMenuView.rootView,
forciblyShownTypes = if (forceShowSystemBars) { systemBars() } else { 0 },
ignoreCutouts = Flags.showAppHandleLargeScreens()
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt
index cc54d25b3639..1ce0366728b9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt
@@ -178,8 +178,7 @@ class MaximizeMenu(
menuHeight,
WindowManager.LayoutParams.TYPE_APPLICATION,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
- or WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
- or WindowManager.LayoutParams.FLAG_SPLIT_TOUCH,
+ or WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH,
PixelFormat.TRANSPARENT
)
lp.title = "Maximize Menu for Task=" + taskInfo.taskId
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 fa7183ad0fd8..3fcb09349033 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
@@ -22,7 +22,6 @@ import static android.view.WindowInsets.Type.captionBar;
import static android.view.WindowInsets.Type.mandatorySystemGestures;
import static android.view.WindowInsets.Type.statusBars;
import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
-import static android.view.WindowManager.LayoutParams.FLAG_SPLIT_TOUCH;
import static android.view.WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
@@ -333,7 +332,7 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
outResult.mCaptionWidth,
outResult.mCaptionHeight,
TYPE_APPLICATION,
- FLAG_NOT_FOCUSABLE | FLAG_SPLIT_TOUCH,
+ FLAG_NOT_FOCUSABLE,
PixelFormat.TRANSPARENT);
lp.setTitle("Caption of Task=" + mTaskInfo.taskId);
lp.setTrustedOverlay();
@@ -750,7 +749,7 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
width,
height,
TYPE_APPLICATION,
- FLAG_NOT_FOCUSABLE | FLAG_WATCH_OUTSIDE_TOUCH | FLAG_SPLIT_TOUCH,
+ FLAG_NOT_FOCUSABLE | FLAG_WATCH_OUTSIDE_TOUCH,
PixelFormat.TRANSPARENT);
lp.setTitle("Additional window of Task=" + mTaskInfo.taskId);
lp.setTrustedOverlay();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/ReusableWindowDecorViewHost.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/ReusableWindowDecorViewHost.kt
index da41e1b1d8d8..4a09614029dc 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/ReusableWindowDecorViewHost.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/ReusableWindowDecorViewHost.kt
@@ -24,7 +24,6 @@ import android.view.SurfaceControl
import android.view.View
import android.view.WindowManager
import android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
-import android.view.WindowManager.LayoutParams.FLAG_SPLIT_TOUCH
import android.view.WindowManager.LayoutParams.TYPE_APPLICATION
import android.widget.FrameLayout
import androidx.tracing.Trace
@@ -72,7 +71,7 @@ class ReusableWindowDecorViewHost(
0 /* width*/,
0 /* height */,
TYPE_APPLICATION,
- FLAG_NOT_FOCUSABLE or FLAG_SPLIT_TOUCH,
+ FLAG_NOT_FOCUSABLE,
PixelFormat.TRANSPARENT,
)
.apply {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDividerWindowManager.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDividerWindowManager.kt
index 583282247f58..fbbf1a5db72c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDividerWindowManager.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDividerWindowManager.kt
@@ -34,7 +34,6 @@ import android.view.WindowManager
import android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
import android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
import android.view.WindowManager.LayoutParams.FLAG_SLIPPERY
-import android.view.WindowManager.LayoutParams.FLAG_SPLIT_TOUCH
import android.view.WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
import android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION
import android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY
@@ -240,7 +239,6 @@ class DesktopTilingDividerWindowManager(
FLAG_NOT_FOCUSABLE or
FLAG_NOT_TOUCH_MODAL or
FLAG_WATCH_OUTSIDE_TOUCH or
- FLAG_SPLIT_TOUCH or
FLAG_SLIPPERY,
PixelFormat.TRANSLUCENT,
)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java
index ce640b5e5195..ffcc3446d436 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java
@@ -445,6 +445,15 @@ public class BubbleDataTest extends ShellTestCase {
assertThat(update.updatedBubble.showFlyout()).isFalse();
}
+ @Test
+ public void getOrCreateBubble_withIntent_usesCorrectUser() {
+ Intent intent = new Intent();
+ intent.setPackage(mContext.getPackageName());
+ Bubble b = mBubbleData.getOrCreateBubble(intent, UserHandle.of(/* userId= */ 10));
+
+ assertThat(b.getUser().getIdentifier()).isEqualTo(10);
+ }
+
//
// Overflow
//
@@ -1441,12 +1450,6 @@ public class BubbleDataTest extends ShellTestCase {
assertWithMessage("selectedBubble").that(update.selectedBubble).isEqualTo(bubble);
}
- private void assertSelectionCleared() {
- BubbleData.Update update = mUpdateCaptor.getValue();
- assertWithMessage("selectionChanged").that(update.selectionChanged).isTrue();
- assertWithMessage("selectedBubble").that(update.selectedBubble).isNull();
- }
-
private void assertExpandedChangedTo(boolean expected) {
BubbleData.Update update = mUpdateCaptor.getValue();
assertWithMessage("expandedChanged").that(update.expandedChanged).isTrue();
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleViewInfoTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleViewInfoTest.kt
index eeb83df48ab5..417b43a9c6c0 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleViewInfoTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleViewInfoTest.kt
@@ -46,6 +46,7 @@ import com.android.wm.shell.sysui.ShellCommandHandler
import com.android.wm.shell.sysui.ShellController
import com.android.wm.shell.sysui.ShellInit
import com.android.wm.shell.taskview.TaskView
+import com.android.wm.shell.taskview.TaskViewRepository
import com.android.wm.shell.taskview.TaskViewTransitions
import com.android.wm.shell.transition.Transitions
import com.google.common.truth.Truth.assertThat
@@ -137,6 +138,7 @@ class BubbleViewInfoTest : ShellTestCase() {
mainExecutor,
mock<Handler>(),
bgExecutor,
+ mock<TaskViewRepository>(),
mock<TaskViewTransitions>(),
mock<Transitions>(),
mock<SyncTransactionQueue>(),
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/CloseDesktopTaskTransitionHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/CloseDesktopTaskTransitionHandlerTest.kt
index 04f9ada8a9d7..03aad1c5c721 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/CloseDesktopTaskTransitionHandlerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/CloseDesktopTaskTransitionHandlerTest.kt
@@ -21,6 +21,7 @@ import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD
import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
import android.app.WindowConfiguration.WindowingMode
+import android.os.Handler
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper.RunWithLooper
import android.view.SurfaceControl
@@ -52,6 +53,7 @@ class CloseDesktopTaskTransitionHandlerTest : ShellTestCase() {
@Mock lateinit var testExecutor: ShellExecutor
@Mock lateinit var closingTaskLeash: SurfaceControl
+ @Mock lateinit var mockHandler: Handler
private val transactionSupplier = Supplier { mock<SurfaceControl.Transaction>() }
@@ -65,6 +67,7 @@ class CloseDesktopTaskTransitionHandlerTest : ShellTestCase() {
testExecutor,
testExecutor,
transactionSupplier,
+ mockHandler,
)
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandlerTest.kt
index 413e7bc5d1d6..016e04039b12 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandlerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandlerTest.kt
@@ -46,6 +46,7 @@ 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.desktopmode.DesktopModeEventLogger.Companion.MinimizeReason
import com.android.wm.shell.desktopmode.DesktopTestHelpers.createFreeformTask
import com.android.wm.shell.desktopmode.common.ToggleTaskSizeInteraction
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
@@ -294,7 +295,7 @@ class DesktopModeKeyGestureHandlerTest : ShellTestCase() {
testExecutor.flushAll()
assertThat(result).isTrue()
- verify(desktopTasksController).minimizeTask(task)
+ verify(desktopTasksController).minimizeTask(task, MinimizeReason.KEY_GESTURE)
}
private fun setUpFreeformTask(
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt
index 4317143aebfe..a9ebcef9bd98 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt
@@ -42,6 +42,7 @@ import android.window.WindowContainerToken
import androidx.test.filters.SmallTest
import com.android.dx.mockito.inline.extended.ExtendedMockito
import com.android.modules.utils.testing.ExtendedMockitoRule
+import com.android.wm.shell.ShellTaskOrganizer
import com.android.wm.shell.ShellTestCase
import com.android.wm.shell.common.ShellExecutor
import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.EnterReason
@@ -62,6 +63,7 @@ import com.android.wm.shell.sysui.ShellInit
import com.android.wm.shell.transition.TransitionInfoBuilder
import com.android.wm.shell.transition.Transitions
import com.android.wm.shell.transition.Transitions.TRANSIT_MINIMIZE
+import java.util.Optional
import kotlin.test.assertFalse
import kotlin.test.assertTrue
import org.junit.Before
@@ -69,6 +71,7 @@ import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
+import org.mockito.Mockito.`when`
import org.mockito.kotlin.any
import org.mockito.kotlin.eq
import org.mockito.kotlin.mock
@@ -102,6 +105,8 @@ class DesktopModeLoggerTransitionObserverTest : ShellTestCase() {
private val mockShellInit = mock<ShellInit>()
private val transitions = mock<Transitions>()
private val context = mock<Context>()
+ private val shellTaskOrganizer = mock<ShellTaskOrganizer>()
+ private val desktopTasksLimiter = mock<DesktopTasksLimiter>()
private lateinit var transitionObserver: DesktopModeLoggerTransitionObserver
private lateinit var shellInit: ShellInit
@@ -119,6 +124,8 @@ class DesktopModeLoggerTransitionObserverTest : ShellTestCase() {
mockShellInit,
transitions,
desktopModeEventLogger,
+ Optional.of(desktopTasksLimiter),
+ shellTaskOrganizer,
)
val initRunnableCaptor = ArgumentCaptor.forClass(Runnable::class.java)
verify(mockShellInit)
@@ -755,6 +762,39 @@ class DesktopModeLoggerTransitionObserverTest : ShellTestCase() {
verify(desktopModeEventLogger, never()).logSessionExit(any())
}
+ @Test
+ fun onTransitionReady_taskIsBeingMinimized_logsTaskMinimized() {
+ transitionObserver.isSessionActive = true
+ transitionObserver.addTaskInfosToCachedMap(createTaskInfo(WINDOWING_MODE_FREEFORM, id = 1))
+ val taskInfo2 = createTaskInfo(WINDOWING_MODE_FREEFORM, id = 2)
+ transitionObserver.addTaskInfosToCachedMap(taskInfo2)
+ val transitionInfo =
+ TransitionInfoBuilder(TRANSIT_TO_BACK, 0)
+ .addChange(createChange(TRANSIT_TO_BACK, taskInfo2))
+ .build()
+ `when`(desktopTasksLimiter.getMinimizingTask(any()))
+ .thenReturn(
+ DesktopTasksLimiter.TaskDetails(
+ taskInfo2.displayId,
+ taskInfo2.taskId,
+ minimizeReason = MinimizeReason.TASK_LIMIT,
+ )
+ )
+
+ callOnTransitionReady(transitionInfo)
+
+ verify(desktopModeEventLogger, times(1))
+ .logTaskRemoved(
+ eq(
+ DEFAULT_TASK_UPDATE.copy(
+ instanceId = 2,
+ visibleTaskCount = 1,
+ minimizeReason = MinimizeReason.TASK_LIMIT,
+ )
+ )
+ )
+ }
+
/** Simulate calling the onTransitionReady() method */
private fun callOnTransitionReady(transitionInfo: TransitionInfo) {
val transition = mock<IBinder>()
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt
index 6003a219d4db..8d73f3f59afd 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt
@@ -1046,14 +1046,14 @@ class DesktopRepositoryTest : ShellTestCase() {
}
@Test
- fun removeDesktop_multipleTasks_removesAll() {
+ fun removeDesk_multipleTasks_removesAll() {
// The front-most task will be the one added last through `addTask`.
repo.addTask(displayId = DEFAULT_DISPLAY, taskId = 3, isVisible = true)
repo.addTask(displayId = DEFAULT_DISPLAY, taskId = 2, isVisible = true)
repo.addTask(displayId = DEFAULT_DISPLAY, taskId = 1, isVisible = true)
repo.minimizeTask(displayId = DEFAULT_DISPLAY, taskId = 2)
- val tasksBeforeRemoval = repo.removeDesktop(displayId = DEFAULT_DISPLAY)
+ val tasksBeforeRemoval = repo.removeDesk(displayId = DEFAULT_DISPLAY)
assertThat(tasksBeforeRemoval).containsExactly(1, 2, 3).inOrder()
assertThat(repo.getActiveTasks(displayId = DEFAULT_DISPLAY)).isEmpty()
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 4b749d1274b1..6c4f043a4f39 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
@@ -102,6 +102,7 @@ import com.android.wm.shell.common.ShellExecutor
import com.android.wm.shell.common.SyncTransactionQueue
import com.android.wm.shell.desktopmode.DesktopImmersiveController.ExitResult
import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.InputMethod
+import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.MinimizeReason
import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ResizeTrigger
import com.android.wm.shell.desktopmode.DesktopTasksController.DesktopModeEntryExitTransitionListener
import com.android.wm.shell.desktopmode.DesktopTasksController.SnapPosition
@@ -1146,6 +1147,21 @@ class DesktopTasksControllerTest : ShellTestCase() {
}
@Test
+ fun launchIntent_taskInDesktopMode_transitionStarted() {
+ setUpLandscapeDisplay()
+ val freeformTask = setUpFreeformTask()
+
+ controller.startLaunchIntentTransition(
+ freeformTask.baseIntent,
+ Bundle.EMPTY,
+ DEFAULT_DISPLAY,
+ )
+
+ val wct = getLatestDesktopMixedTaskWct(type = TRANSIT_OPEN)
+ assertThat(wct.hierarchyOps).hasSize(1)
+ }
+
+ @Test
@EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
fun addMoveToDesktopChanges_landscapeDevice_userFullscreenOverride_defaultPortraitBounds() {
setUpLandscapeDisplay()
@@ -2147,7 +2163,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
whenever(transitions.dispatchRequest(any(), any(), anyOrNull()))
.thenReturn(android.util.Pair(handler, WindowContainerTransaction()))
- controller.minimizeTask(pipTask)
+ controller.minimizeTask(pipTask, MinimizeReason.MINIMIZE_BUTTON)
verifyExitDesktopWCTNotExecuted()
taskRepository.setTaskInPip(DEFAULT_DISPLAY, pipTask.taskId, enterPip = false)
@@ -2167,7 +2183,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
whenever(freeformTaskTransitionStarter.startMinimizedModeTransition(any()))
.thenReturn(transition)
- controller.minimizeTask(task)
+ controller.minimizeTask(task, MinimizeReason.MINIMIZE_BUTTON)
val captor = ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
verify(freeformTaskTransitionStarter).startMinimizedModeTransition(captor.capture())
@@ -2183,7 +2199,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
whenever(transitions.dispatchRequest(any(), any(), anyOrNull()))
.thenReturn(android.util.Pair(handler, WindowContainerTransaction()))
- controller.minimizeTask(task)
+ controller.minimizeTask(task, MinimizeReason.MINIMIZE_BUTTON)
verify(freeformTaskTransitionStarter).startPipTransition(any())
verify(freeformTaskTransitionStarter, never()).startMinimizedModeTransition(any())
@@ -2195,7 +2211,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
whenever(freeformTaskTransitionStarter.startMinimizedModeTransition(any()))
.thenReturn(Binder())
- controller.minimizeTask(task)
+ controller.minimizeTask(task, MinimizeReason.MINIMIZE_BUTTON)
verify(freeformTaskTransitionStarter).startMinimizedModeTransition(any())
verify(freeformTaskTransitionStarter, never()).startPipTransition(any())
@@ -2208,7 +2224,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
whenever(transitions.dispatchRequest(any(), any(), anyOrNull()))
.thenReturn(android.util.Pair(handler, WindowContainerTransaction()))
- controller.minimizeTask(task)
+ controller.minimizeTask(task, MinimizeReason.MINIMIZE_BUTTON)
val captor = ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
verify(freeformTaskTransitionStarter).startPipTransition(captor.capture())
@@ -2224,7 +2240,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
whenever(freeformTaskTransitionStarter.startMinimizedModeTransition(any()))
.thenReturn(transition)
- controller.minimizeTask(task)
+ controller.minimizeTask(task, MinimizeReason.MINIMIZE_BUTTON)
val captor = ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
verify(freeformTaskTransitionStarter).startMinimizedModeTransition(captor.capture())
@@ -2240,7 +2256,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
.thenReturn(transition)
// The only active task is being minimized.
- controller.minimizeTask(task)
+ controller.minimizeTask(task, MinimizeReason.MINIMIZE_BUTTON)
val captor = ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
verify(freeformTaskTransitionStarter).startMinimizedModeTransition(captor.capture())
@@ -2257,7 +2273,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
taskRepository.minimizeTask(DEFAULT_DISPLAY, task.taskId)
// The only active task is already minimized.
- controller.minimizeTask(task)
+ controller.minimizeTask(task, MinimizeReason.MINIMIZE_BUTTON)
val captor = ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
verify(freeformTaskTransitionStarter).startMinimizedModeTransition(captor.capture())
@@ -2274,7 +2290,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
whenever(freeformTaskTransitionStarter.startMinimizedModeTransition(any()))
.thenReturn(transition)
- controller.minimizeTask(task1)
+ controller.minimizeTask(task1, MinimizeReason.MINIMIZE_BUTTON)
val captor = ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
verify(freeformTaskTransitionStarter).startMinimizedModeTransition(captor.capture())
@@ -2294,7 +2310,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
taskRepository.minimizeTask(DEFAULT_DISPLAY, task2.taskId)
// task1 is the only visible task as task2 is minimized.
- controller.minimizeTask(task1)
+ controller.minimizeTask(task1, MinimizeReason.MINIMIZE_BUTTON)
// Adds remove wallpaper operation
val captor = ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
verify(freeformTaskTransitionStarter).startMinimizedModeTransition(captor.capture())
@@ -2309,7 +2325,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
whenever(freeformTaskTransitionStarter.startMinimizedModeTransition(any()))
.thenReturn(transition)
- controller.minimizeTask(task)
+ controller.minimizeTask(task, MinimizeReason.MINIMIZE_BUTTON)
verify(mMockDesktopImmersiveController).exitImmersiveIfApplicable(any(), eq(task), any())
}
@@ -2326,7 +2342,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
ExitResult.Exit(exitingTask = task.taskId, runOnTransitionStart = runOnTransit)
)
- controller.minimizeTask(task)
+ controller.minimizeTask(task, MinimizeReason.MINIMIZE_BUTTON)
assertThat(runOnTransit.invocations).isEqualTo(1)
assertThat(runOnTransit.lastInvoked).isEqualTo(transition)
@@ -3270,7 +3286,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
whenever(transitions.dispatchRequest(any(), any(), anyOrNull()))
.thenReturn(android.util.Pair(handler, WindowContainerTransaction()))
- controller.minimizeTask(pipTask)
+ controller.minimizeTask(pipTask, MinimizeReason.MINIMIZE_BUTTON)
verifyExitDesktopWCTNotExecuted()
freeformTask.isFocused = true
@@ -5204,7 +5220,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
val arg: ArgumentCaptor<WindowContainerTransaction> =
ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
verify(desktopMixedTransitionHandler)
- .startLaunchTransition(eq(type), capture(arg), anyInt(), anyOrNull(), anyOrNull())
+ .startLaunchTransition(eq(type), capture(arg), anyOrNull(), anyOrNull(), anyOrNull())
return arg.value
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt
index c8214b3838e2..acfe1e9fd5a2 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt
@@ -20,6 +20,7 @@ import android.app.ActivityManager.RunningTaskInfo
import android.graphics.Rect
import android.os.Binder
import android.os.Handler
+import android.os.IBinder
import android.os.UserManager
import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
@@ -43,6 +44,7 @@ import com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGAT
import com.android.wm.shell.ShellTaskOrganizer
import com.android.wm.shell.ShellTestCase
import com.android.wm.shell.common.ShellExecutor
+import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.MinimizeReason
import com.android.wm.shell.desktopmode.DesktopTestHelpers.createFreeformTask
import com.android.wm.shell.desktopmode.persistence.DesktopPersistentRepository
import com.android.wm.shell.desktopmode.persistence.DesktopRepositoryInitializer
@@ -180,7 +182,7 @@ class DesktopTasksLimiterTest : ShellTestCase() {
val task = setUpFreeformTask()
markTaskHidden(task)
- desktopTasksLimiter.addPendingMinimizeChange(Binder(), displayId = 1, taskId = task.taskId)
+ addPendingMinimizeChange(Binder(), displayId = 1, taskId = task.taskId)
assertThat(desktopTaskRepo.isMinimizedTask(taskId = task.taskId)).isFalse()
}
@@ -208,11 +210,7 @@ class DesktopTasksLimiterTest : ShellTestCase() {
val taskTransition = Binder()
val task = setUpFreeformTask()
markTaskHidden(task)
- desktopTasksLimiter.addPendingMinimizeChange(
- pendingTransition,
- displayId = DEFAULT_DISPLAY,
- taskId = task.taskId,
- )
+ addPendingMinimizeChange(pendingTransition, taskId = task.taskId)
desktopTasksLimiter
.getTransitionObserver()
@@ -231,11 +229,7 @@ class DesktopTasksLimiterTest : ShellTestCase() {
val transition = Binder()
val task = setUpFreeformTask()
markTaskVisible(task)
- desktopTasksLimiter.addPendingMinimizeChange(
- transition,
- displayId = DEFAULT_DISPLAY,
- taskId = task.taskId,
- )
+ addPendingMinimizeChange(transition, taskId = task.taskId)
desktopTasksLimiter
.getTransitionObserver()
@@ -254,11 +248,7 @@ class DesktopTasksLimiterTest : ShellTestCase() {
val transition = Binder()
val task = setUpFreeformTask()
markTaskHidden(task)
- desktopTasksLimiter.addPendingMinimizeChange(
- transition,
- displayId = DEFAULT_DISPLAY,
- taskId = task.taskId,
- )
+ addPendingMinimizeChange(transition, taskId = task.taskId)
desktopTasksLimiter
.getTransitionObserver()
@@ -276,11 +266,7 @@ class DesktopTasksLimiterTest : ShellTestCase() {
fun onTransitionReady_pendingTransition_changeTaskToBack_taskIsMinimized() {
val transition = Binder()
val task = setUpFreeformTask()
- desktopTasksLimiter.addPendingMinimizeChange(
- transition,
- displayId = DEFAULT_DISPLAY,
- taskId = task.taskId,
- )
+ addPendingMinimizeChange(transition, taskId = task.taskId)
desktopTasksLimiter
.getTransitionObserver()
@@ -299,11 +285,7 @@ class DesktopTasksLimiterTest : ShellTestCase() {
val bounds = Rect(0, 0, 200, 200)
val transition = Binder()
val task = setUpFreeformTask()
- desktopTasksLimiter.addPendingMinimizeChange(
- transition,
- displayId = DEFAULT_DISPLAY,
- taskId = task.taskId,
- )
+ addPendingMinimizeChange(transition, taskId = task.taskId)
val change =
TransitionInfo.Change(task.token, mock(SurfaceControl::class.java)).apply {
@@ -330,11 +312,7 @@ class DesktopTasksLimiterTest : ShellTestCase() {
val mergedTransition = Binder()
val newTransition = Binder()
val task = setUpFreeformTask()
- desktopTasksLimiter.addPendingMinimizeChange(
- mergedTransition,
- displayId = DEFAULT_DISPLAY,
- taskId = task.taskId,
- )
+ addPendingMinimizeChange(mergedTransition, taskId = task.taskId)
desktopTasksLimiter
.getTransitionObserver()
.onTransitionMerged(mergedTransition, newTransition)
@@ -541,11 +519,7 @@ class DesktopTasksLimiterTest : ShellTestCase() {
(1..<MAX_TASK_LIMIT).forEach { _ -> setUpFreeformTask() }
val transition = Binder()
val task = setUpFreeformTask()
- desktopTasksLimiter.addPendingMinimizeChange(
- transition,
- displayId = DEFAULT_DISPLAY,
- taskId = task.taskId,
- )
+ addPendingMinimizeChange(transition, taskId = task.taskId)
desktopTasksLimiter
.getTransitionObserver()
@@ -573,11 +547,7 @@ class DesktopTasksLimiterTest : ShellTestCase() {
(1..<MAX_TASK_LIMIT).forEach { _ -> setUpFreeformTask() }
val transition = Binder()
val task = setUpFreeformTask()
- desktopTasksLimiter.addPendingMinimizeChange(
- transition,
- displayId = DEFAULT_DISPLAY,
- taskId = task.taskId,
- )
+ addPendingMinimizeChange(transition, taskId = task.taskId)
desktopTasksLimiter
.getTransitionObserver()
@@ -606,11 +576,7 @@ class DesktopTasksLimiterTest : ShellTestCase() {
val mergedTransition = Binder()
val newTransition = Binder()
val task = setUpFreeformTask()
- desktopTasksLimiter.addPendingMinimizeChange(
- mergedTransition,
- displayId = DEFAULT_DISPLAY,
- taskId = task.taskId,
- )
+ addPendingMinimizeChange(mergedTransition, taskId = task.taskId)
desktopTasksLimiter
.getTransitionObserver()
@@ -633,6 +599,60 @@ class DesktopTasksLimiterTest : ShellTestCase() {
verify(interactionJankMonitor).end(eq(CUJ_DESKTOP_MODE_MINIMIZE_WINDOW))
}
+ @Test
+ fun getMinimizingTask_noPendingTransition_returnsNull() {
+ val transition = Binder()
+
+ assertThat(desktopTasksLimiter.getMinimizingTask(transition)).isNull()
+ }
+
+ @Test
+ fun getMinimizingTask_pendingTaskTransition_returnsTask() {
+ val transition = Binder()
+ val task = setUpFreeformTask()
+ addPendingMinimizeChange(
+ transition,
+ taskId = task.taskId,
+ minimizeReason = MinimizeReason.TASK_LIMIT,
+ )
+
+ assertThat(desktopTasksLimiter.getMinimizingTask(transition))
+ .isEqualTo(
+ createTaskDetails(taskId = task.taskId, minimizeReason = MinimizeReason.TASK_LIMIT)
+ )
+ }
+
+ @Test
+ fun getMinimizingTask_activeTaskTransition_returnsTask() {
+ val transition = Binder()
+ val task = setUpFreeformTask()
+ addPendingMinimizeChange(
+ transition,
+ taskId = task.taskId,
+ minimizeReason = MinimizeReason.TASK_LIMIT,
+ )
+ val transitionInfo =
+ TransitionInfoBuilder(TRANSIT_OPEN).addChange(TRANSIT_TO_BACK, task).build()
+
+ desktopTasksLimiter
+ .getTransitionObserver()
+ .onTransitionReady(
+ transition,
+ transitionInfo,
+ /* startTransaction= */ StubTransaction(),
+ /* finishTransaction= */ StubTransaction(),
+ )
+
+ assertThat(desktopTasksLimiter.getMinimizingTask(transition))
+ .isEqualTo(
+ createTaskDetails(
+ taskId = task.taskId,
+ transitionInfo = transitionInfo,
+ minimizeReason = MinimizeReason.TASK_LIMIT,
+ )
+ )
+ }
+
private fun setUpFreeformTask(displayId: Int = DEFAULT_DISPLAY): RunningTaskInfo {
val task = createFreeformTask(displayId)
`when`(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task)
@@ -640,6 +660,20 @@ class DesktopTasksLimiterTest : ShellTestCase() {
return task
}
+ private fun createTaskDetails(
+ displayId: Int = DEFAULT_DISPLAY,
+ taskId: Int,
+ transitionInfo: TransitionInfo? = null,
+ minimizeReason: MinimizeReason? = null,
+ ) = DesktopTasksLimiter.TaskDetails(displayId, taskId, transitionInfo, minimizeReason)
+
+ fun addPendingMinimizeChange(
+ transition: IBinder,
+ displayId: Int = DEFAULT_DISPLAY,
+ taskId: Int,
+ minimizeReason: MinimizeReason = MinimizeReason.TASK_LIMIT,
+ ) = desktopTasksLimiter.addPendingMinimizeChange(transition, displayId, taskId, minimizeReason)
+
private fun markTaskVisible(task: RunningTaskInfo) {
desktopTaskRepo.updateTask(task.displayId, task.taskId, isVisible = true)
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipAlphaAnimatorTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipAlphaAnimatorTest.java
index 9cc18ffdaed7..607e6a450883 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipAlphaAnimatorTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipAlphaAnimatorTest.java
@@ -19,6 +19,8 @@ package com.android.wm.shell.pip2.animation;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyFloat;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyZeroInteractions;
@@ -33,6 +35,7 @@ import android.view.SurfaceControl;
import androidx.test.filters.SmallTest;
import androidx.test.platform.app.InstrumentationRegistry;
+import com.android.wm.shell.R;
import com.android.wm.shell.pip2.PipSurfaceTransactionHelper;
import org.junit.Before;
@@ -48,6 +51,8 @@ import org.mockito.MockitoAnnotations;
@TestableLooper.RunWithLooper
@RunWith(AndroidTestingRunner.class)
public class PipAlphaAnimatorTest {
+ private static final float TEST_CORNER_RADIUS = 1f;
+ private static final float TEST_SHADOW_RADIUS = 2f;
@Mock private Context mMockContext;
@@ -55,7 +60,9 @@ public class PipAlphaAnimatorTest {
@Mock private PipSurfaceTransactionHelper.SurfaceControlTransactionFactory mMockFactory;
- @Mock private SurfaceControl.Transaction mMockTransaction;
+ @Mock private SurfaceControl.Transaction mMockAnimateTransaction;
+ @Mock private SurfaceControl.Transaction mMockStartTransaction;
+ @Mock private SurfaceControl.Transaction mMockFinishTransaction;
@Mock private Runnable mMockStartCallback;
@@ -69,9 +76,15 @@ public class PipAlphaAnimatorTest {
MockitoAnnotations.initMocks(this);
when(mMockContext.getResources()).thenReturn(mMockResources);
when(mMockResources.getInteger(anyInt())).thenReturn(0);
- when(mMockFactory.getTransaction()).thenReturn(mMockTransaction);
- when(mMockTransaction.setAlpha(any(SurfaceControl.class), anyFloat()))
- .thenReturn(mMockTransaction);
+ when(mMockFactory.getTransaction()).thenReturn(mMockAnimateTransaction);
+ when(mMockResources.getDimensionPixelSize(R.dimen.pip_corner_radius))
+ .thenReturn((int) TEST_CORNER_RADIUS);
+ when(mMockResources.getDimensionPixelSize(R.dimen.pip_shadow_radius))
+ .thenReturn((int) TEST_SHADOW_RADIUS);
+
+ prepareTransaction(mMockAnimateTransaction);
+ prepareTransaction(mMockStartTransaction);
+ prepareTransaction(mMockFinishTransaction);
mTestLeash = new SurfaceControl.Builder()
.setContainerLayer()
@@ -82,8 +95,8 @@ public class PipAlphaAnimatorTest {
@Test
public void setAnimationStartCallback_fadeInAnimator_callbackStartCallback() {
- mPipAlphaAnimator = new PipAlphaAnimator(mMockContext, mTestLeash, mMockTransaction,
- PipAlphaAnimator.FADE_IN);
+ mPipAlphaAnimator = new PipAlphaAnimator(mMockContext, mTestLeash, mMockStartTransaction,
+ mMockFinishTransaction, PipAlphaAnimator.FADE_IN);
mPipAlphaAnimator.setAnimationStartCallback(mMockStartCallback);
mPipAlphaAnimator.setAnimationEndCallback(mMockEndCallback);
@@ -98,8 +111,8 @@ public class PipAlphaAnimatorTest {
@Test
public void setAnimationEndCallback_fadeInAnimator_callbackStartAndEndCallback() {
- mPipAlphaAnimator = new PipAlphaAnimator(mMockContext, mTestLeash, mMockTransaction,
- PipAlphaAnimator.FADE_IN);
+ mPipAlphaAnimator = new PipAlphaAnimator(mMockContext, mTestLeash, mMockStartTransaction,
+ mMockFinishTransaction, PipAlphaAnimator.FADE_IN);
mPipAlphaAnimator.setAnimationStartCallback(mMockStartCallback);
mPipAlphaAnimator.setAnimationEndCallback(mMockEndCallback);
@@ -109,36 +122,98 @@ public class PipAlphaAnimatorTest {
});
verify(mMockStartCallback).run();
- verify(mMockStartCallback).run();
+ verify(mMockEndCallback).run();
+ }
+
+ @Test
+ public void onAnimationStart_setCornerAndShadowRadii() {
+ mPipAlphaAnimator = new PipAlphaAnimator(mMockContext, mTestLeash, mMockStartTransaction,
+ mMockFinishTransaction, PipAlphaAnimator.FADE_IN);
+ mPipAlphaAnimator.setSurfaceControlTransactionFactory(mMockFactory);
+
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+ mPipAlphaAnimator.start();
+ mPipAlphaAnimator.pause();
+ });
+
+ verify(mMockStartTransaction, atLeastOnce())
+ .setCornerRadius(eq(mTestLeash), eq(TEST_CORNER_RADIUS));
+ verify(mMockStartTransaction, atLeastOnce())
+ .setShadowRadius(eq(mTestLeash), eq(TEST_SHADOW_RADIUS));
+ }
+
+ @Test
+ public void onAnimationUpdate_setCornerAndShadowRadii() {
+ mPipAlphaAnimator = new PipAlphaAnimator(mMockContext, mTestLeash, mMockStartTransaction,
+ mMockFinishTransaction, PipAlphaAnimator.FADE_IN);
+ mPipAlphaAnimator.setSurfaceControlTransactionFactory(mMockFactory);
+
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+ mPipAlphaAnimator.start();
+ mPipAlphaAnimator.pause();
+ });
+
+ verify(mMockAnimateTransaction, atLeastOnce())
+ .setCornerRadius(eq(mTestLeash), eq(TEST_CORNER_RADIUS));
+ verify(mMockAnimateTransaction, atLeastOnce())
+ .setShadowRadius(eq(mTestLeash), eq(TEST_SHADOW_RADIUS));
+ }
+
+ @Test
+ public void onAnimationEnd_setCornerAndShadowRadii() {
+ mPipAlphaAnimator = new PipAlphaAnimator(mMockContext, mTestLeash, mMockStartTransaction,
+ mMockFinishTransaction, PipAlphaAnimator.FADE_IN);
+ mPipAlphaAnimator.setSurfaceControlTransactionFactory(mMockFactory);
+
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+ mPipAlphaAnimator.start();
+ mPipAlphaAnimator.end();
+ });
+
+ verify(mMockFinishTransaction, atLeastOnce())
+ .setCornerRadius(eq(mTestLeash), eq(TEST_CORNER_RADIUS));
+ verify(mMockFinishTransaction, atLeastOnce())
+ .setShadowRadius(eq(mTestLeash), eq(TEST_SHADOW_RADIUS));
}
@Test
public void onAnimationEnd_fadeInAnimator_leashVisibleAtEnd() {
- mPipAlphaAnimator = new PipAlphaAnimator(mMockContext, mTestLeash, mMockTransaction,
- PipAlphaAnimator.FADE_IN);
+ mPipAlphaAnimator = new PipAlphaAnimator(mMockContext, mTestLeash, mMockStartTransaction,
+ mMockFinishTransaction, PipAlphaAnimator.FADE_IN);
mPipAlphaAnimator.setSurfaceControlTransactionFactory(mMockFactory);
InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
mPipAlphaAnimator.start();
- clearInvocations(mMockTransaction);
+ clearInvocations(mMockAnimateTransaction);
mPipAlphaAnimator.end();
});
- verify(mMockTransaction).setAlpha(mTestLeash, 1.0f);
+ verify(mMockAnimateTransaction).setAlpha(mTestLeash, 1.0f);
}
@Test
public void onAnimationEnd_fadeOutAnimator_leashInvisibleAtEnd() {
- mPipAlphaAnimator = new PipAlphaAnimator(mMockContext, mTestLeash, mMockTransaction,
- PipAlphaAnimator.FADE_OUT);
+ mPipAlphaAnimator = new PipAlphaAnimator(mMockContext, mTestLeash, mMockStartTransaction,
+ mMockFinishTransaction, PipAlphaAnimator.FADE_OUT);
mPipAlphaAnimator.setSurfaceControlTransactionFactory(mMockFactory);
InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
mPipAlphaAnimator.start();
- clearInvocations(mMockTransaction);
+ clearInvocations(mMockAnimateTransaction);
mPipAlphaAnimator.end();
});
- verify(mMockTransaction).setAlpha(mTestLeash, 0f);
+ verify(mMockAnimateTransaction).setAlpha(mTestLeash, 0f);
+ }
+
+
+ // set up transaction chaining
+ private void prepareTransaction(SurfaceControl.Transaction tx) {
+ when(tx.setAlpha(any(SurfaceControl.class), anyFloat()))
+ .thenReturn(tx);
+ when(tx.setCornerRadius(any(SurfaceControl.class), anyFloat()))
+ .thenReturn(tx);
+ when(tx.setShadowRadius(any(SurfaceControl.class), anyFloat()))
+ .thenReturn(tx);
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipSchedulerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipSchedulerTest.java
index aef44a40fa0f..bd857c7dcd45 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipSchedulerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipSchedulerTest.java
@@ -111,7 +111,7 @@ public class PipSchedulerTest {
mRootTaskDisplayAreaOrganizer);
mPipScheduler.setPipTransitionController(mMockPipTransitionController);
mPipScheduler.setSurfaceControlTransactionFactory(mMockFactory);
- mPipScheduler.setPipAlphaAnimatorSupplier((context, leash, tx, direction) ->
+ mPipScheduler.setPipAlphaAnimatorSupplier((context, leash, startTx, finishTx, direction) ->
mMockAlphaAnimator);
SurfaceControl testLeash = new SurfaceControl.Builder()
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
index 28f4ea0c7ada..065fa219e8d0 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
@@ -20,6 +20,7 @@ import static android.app.ActivityManager.RECENT_IGNORE_UNAVAILABLE;
import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
import static com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_PERSISTENCE;
@@ -28,7 +29,6 @@ import static com.android.wm.shell.shared.GroupedTaskInfo.TYPE_FULLSCREEN;
import static com.android.wm.shell.shared.GroupedTaskInfo.TYPE_SPLIT;
import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_50_50;
-import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
@@ -48,9 +48,10 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static java.lang.Integer.MAX_VALUE;
+import static java.util.stream.Collectors.joining;
-import android.app.ActivityManager;
import android.app.ActivityManager.RecentTaskInfo;
+import android.app.ActivityManager.RunningTaskInfo;
import android.app.ActivityTaskManager;
import android.app.KeyguardManager;
import android.content.ComponentName;
@@ -247,10 +248,10 @@ public class RecentTasksControllerTest extends ShellTestCase {
ArrayList<GroupedTaskInfo> recentTasks =
mRecentTasksController.getRecentTasks(MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0);
- assertGroupedTasksListEquals(recentTasks,
- t1.taskId, -1,
- t2.taskId, -1,
- t3.taskId, -1);
+ assertGroupedTasksListEquals(recentTasks, List.of(
+ List.of(t1.taskId),
+ List.of(t2.taskId),
+ List.of(t3.taskId)));
}
@Test
@@ -262,7 +263,9 @@ public class RecentTasksControllerTest extends ShellTestCase {
ArrayList<GroupedTaskInfo> recentTasks =
mRecentTasksController.getRecentTasks(MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0);
- assertGroupedTasksListEquals(recentTasks, t1.taskId, -1, t3.taskId, -1);
+ assertGroupedTasksListEquals(recentTasks, List.of(
+ List.of(t1.taskId),
+ List.of(t3.taskId)));
}
@Test
@@ -286,11 +289,11 @@ public class RecentTasksControllerTest extends ShellTestCase {
ArrayList<GroupedTaskInfo> recentTasks =
mRecentTasksController.getRecentTasks(MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0);
- assertGroupedTasksListEquals(recentTasks,
- t1.taskId, -1,
- t2.taskId, t4.taskId,
- t3.taskId, t5.taskId,
- t6.taskId, -1);
+ assertGroupedTasksListEquals(recentTasks, List.of(
+ List.of(t1.taskId),
+ List.of(t2.taskId, t4.taskId),
+ List.of(t3.taskId, t5.taskId),
+ List.of(t6.taskId)));
}
@Test
@@ -320,11 +323,11 @@ public class RecentTasksControllerTest extends ShellTestCase {
consumer);
mMainExecutor.flushAll();
- assertGroupedTasksListEquals(recentTasks[0],
- t1.taskId, -1,
- t2.taskId, t4.taskId,
- t3.taskId, t5.taskId,
- t6.taskId, -1);
+ assertGroupedTasksListEquals(recentTasks[0], List.of(
+ List.of(t1.taskId),
+ List.of(t2.taskId, t4.taskId),
+ List.of(t3.taskId, t5.taskId),
+ List.of(t6.taskId)));
}
@Test
@@ -343,9 +346,9 @@ public class RecentTasksControllerTest extends ShellTestCase {
// 2 freeform tasks should be grouped into one, 3 total recents entries
assertEquals(3, recentTasks.size());
- GroupedTaskInfo freeformGroup = recentTasks.get(0);
- GroupedTaskInfo singleGroup1 = recentTasks.get(1);
- GroupedTaskInfo singleGroup2 = recentTasks.get(2);
+ GroupedTaskInfo singleGroup1 = recentTasks.get(0);
+ GroupedTaskInfo singleGroup2 = recentTasks.get(1);
+ GroupedTaskInfo freeformGroup = recentTasks.get(2);
// Check that groups have expected types
assertTrue(freeformGroup.isBaseType(TYPE_FREEFORM));
@@ -383,8 +386,8 @@ public class RecentTasksControllerTest extends ShellTestCase {
// 2 split screen tasks grouped, 2 freeform tasks grouped, 3 total recents entries
assertEquals(3, recentTasks.size());
GroupedTaskInfo splitGroup = recentTasks.get(0);
- GroupedTaskInfo freeformGroup = recentTasks.get(1);
- GroupedTaskInfo singleGroup = recentTasks.get(2);
+ GroupedTaskInfo singleGroup = recentTasks.get(1);
+ GroupedTaskInfo freeformGroup = recentTasks.get(2);
// Check that groups have expected types
assertTrue(splitGroup.isBaseType(TYPE_SPLIT));
@@ -454,9 +457,9 @@ public class RecentTasksControllerTest extends ShellTestCase {
// 3 freeform tasks should be grouped into one, 2 single tasks, 3 total recents entries
assertEquals(3, recentTasks.size());
- GroupedTaskInfo freeformGroup = recentTasks.get(0);
- GroupedTaskInfo singleGroup1 = recentTasks.get(1);
- GroupedTaskInfo singleGroup2 = recentTasks.get(2);
+ GroupedTaskInfo singleGroup1 = recentTasks.get(0);
+ GroupedTaskInfo singleGroup2 = recentTasks.get(1);
+ GroupedTaskInfo freeformGroup = recentTasks.get(2);
// Check that groups have expected types
assertTrue(freeformGroup.isBaseType(TYPE_FREEFORM));
@@ -523,7 +526,7 @@ public class RecentTasksControllerTest extends ShellTestCase {
// Remove one of the tasks and ensure the pair is removed
SurfaceControl mockLeash = mock(SurfaceControl.class);
- ActivityManager.RunningTaskInfo rt2 = makeRunningTaskInfo(2);
+ RunningTaskInfo rt2 = makeRunningTaskInfo(2);
mShellTaskOrganizer.onTaskAppeared(rt2, mockLeash);
mShellTaskOrganizer.onTaskVanished(rt2);
@@ -537,13 +540,13 @@ public class RecentTasksControllerTest extends ShellTestCase {
// Remove one of the tasks and ensure the pair is removed
SurfaceControl mockLeash = mock(SurfaceControl.class);
- ActivityManager.RunningTaskInfo rt2Fullscreen = makeRunningTaskInfo(2);
+ RunningTaskInfo rt2Fullscreen = makeRunningTaskInfo(2);
rt2Fullscreen.configuration.windowConfiguration.setWindowingMode(
WINDOWING_MODE_FULLSCREEN);
mShellTaskOrganizer.onTaskAppeared(rt2Fullscreen, mockLeash);
// Change the windowing mode and ensure the recent tasks change is notified
- ActivityManager.RunningTaskInfo rt2MultiWIndow = makeRunningTaskInfo(2);
+ RunningTaskInfo rt2MultiWIndow = makeRunningTaskInfo(2);
rt2MultiWIndow.configuration.windowConfiguration.setWindowingMode(
WINDOWING_MODE_MULTI_WINDOW);
mShellTaskOrganizer.onTaskInfoChanged(rt2MultiWIndow);
@@ -557,7 +560,7 @@ public class RecentTasksControllerTest extends ShellTestCase {
public void onTaskAdded_desktopModeRunningAppsEnabled_triggersOnRunningTaskAppeared()
throws Exception {
mRecentTasksControllerReal.registerRecentTasksListener(mRecentTasksListener);
- ActivityManager.RunningTaskInfo taskInfo = makeRunningTaskInfo(/* taskId= */10);
+ RunningTaskInfo taskInfo = makeRunningTaskInfo(/* taskId= */10);
mRecentTasksControllerReal.onTaskAdded(taskInfo);
@@ -570,7 +573,7 @@ public class RecentTasksControllerTest extends ShellTestCase {
public void onTaskAdded_desktopModeRunningAppsDisabled_doesNotTriggerOnRunningTaskAppeared()
throws Exception {
mRecentTasksControllerReal.registerRecentTasksListener(mRecentTasksListener);
- ActivityManager.RunningTaskInfo taskInfo = makeRunningTaskInfo(/* taskId= */10);
+ RunningTaskInfo taskInfo = makeRunningTaskInfo(/* taskId= */10);
mRecentTasksControllerReal.onTaskAdded(taskInfo);
@@ -583,7 +586,7 @@ public class RecentTasksControllerTest extends ShellTestCase {
public void taskWindowingModeChanged_desktopRunningAppsEnabled_triggersOnRunningTaskChanged()
throws Exception {
mRecentTasksControllerReal.registerRecentTasksListener(mRecentTasksListener);
- ActivityManager.RunningTaskInfo taskInfo = makeRunningTaskInfo(/* taskId= */10);
+ RunningTaskInfo taskInfo = makeRunningTaskInfo(/* taskId= */10);
mRecentTasksControllerReal.onTaskRunningInfoChanged(taskInfo);
@@ -597,7 +600,7 @@ public class RecentTasksControllerTest extends ShellTestCase {
taskWindowingModeChanged_desktopRunningAppsDisabled_doesNotTriggerOnRunningTaskChanged()
throws Exception {
mRecentTasksControllerReal.registerRecentTasksListener(mRecentTasksListener);
- ActivityManager.RunningTaskInfo taskInfo = makeRunningTaskInfo(/* taskId= */10);
+ RunningTaskInfo taskInfo = makeRunningTaskInfo(/* taskId= */10);
mRecentTasksControllerReal.onTaskRunningInfoChanged(taskInfo);
@@ -610,7 +613,7 @@ public class RecentTasksControllerTest extends ShellTestCase {
public void onTaskRemoved_desktopModeRunningAppsEnabled_triggersOnRunningTaskVanished()
throws Exception {
mRecentTasksControllerReal.registerRecentTasksListener(mRecentTasksListener);
- ActivityManager.RunningTaskInfo taskInfo = makeRunningTaskInfo(/* taskId= */10);
+ RunningTaskInfo taskInfo = makeRunningTaskInfo(/* taskId= */10);
mRecentTasksControllerReal.onTaskRemoved(taskInfo);
@@ -623,7 +626,7 @@ public class RecentTasksControllerTest extends ShellTestCase {
public void onTaskRemoved_desktopModeRunningAppsDisabled_doesNotTriggerOnRunningTaskVanished()
throws Exception {
mRecentTasksControllerReal.registerRecentTasksListener(mRecentTasksListener);
- ActivityManager.RunningTaskInfo taskInfo = makeRunningTaskInfo(/* taskId= */10);
+ RunningTaskInfo taskInfo = makeRunningTaskInfo(/* taskId= */10);
mRecentTasksControllerReal.onTaskRemoved(taskInfo);
@@ -632,10 +635,11 @@ public class RecentTasksControllerTest extends ShellTestCase {
@Test
@EnableFlags(Flags.FLAG_ENABLE_TASK_STACK_OBSERVER_IN_SHELL)
+ @DisableFlags(com.android.wm.shell.Flags.FLAG_ENABLE_SHELL_TOP_TASK_TRACKING)
public void onTaskMovedToFront_TaskStackObserverEnabled_triggersOnTaskMovedToFront()
throws Exception {
mRecentTasksControllerReal.registerRecentTasksListener(mRecentTasksListener);
- ActivityManager.RunningTaskInfo taskInfo = makeRunningTaskInfo(/* taskId= */10);
+ RunningTaskInfo taskInfo = makeRunningTaskInfo(/* taskId= */10);
mRecentTasksControllerReal.onTaskMovedToFrontThroughTransition(taskInfo);
@@ -648,7 +652,7 @@ public class RecentTasksControllerTest extends ShellTestCase {
public void onTaskMovedToFront_TaskStackObserverEnabled_doesNotTriggersOnTaskMovedToFront()
throws Exception {
mRecentTasksControllerReal.registerRecentTasksListener(mRecentTasksListener);
- ActivityManager.RunningTaskInfo taskInfo = makeRunningTaskInfo(/* taskId= */10);
+ RunningTaskInfo taskInfo = makeRunningTaskInfo(/* taskId= */10);
mRecentTasksControllerReal.onTaskMovedToFront(taskInfo);
@@ -700,7 +704,7 @@ public class RecentTasksControllerTest extends ShellTestCase {
@EnableFlags(com.android.wm.shell.Flags.FLAG_ENABLE_SHELL_TOP_TASK_TRACKING)
public void shellTopTaskTracker_onTaskRemoved_expectNoRecentsChanged() throws Exception {
mRecentTasksControllerReal.registerRecentTasksListener(mRecentTasksListener);
- ActivityManager.RunningTaskInfo taskInfo = makeRunningTaskInfo(/* taskId= */10);
+ RunningTaskInfo taskInfo = makeRunningTaskInfo(/* taskId= */10);
mRecentTasksControllerReal.onTaskRemoved(taskInfo);
verify(mRecentTasksListener, never()).onRecentTasksChanged();
}
@@ -710,22 +714,105 @@ public class RecentTasksControllerTest extends ShellTestCase {
@EnableFlags(com.android.wm.shell.Flags.FLAG_ENABLE_SHELL_TOP_TASK_TRACKING)
public void shellTopTaskTracker_onVisibleTasksChanged() throws Exception {
mRecentTasksControllerReal.registerRecentTasksListener(mRecentTasksListener);
- ActivityManager.RunningTaskInfo taskInfo = makeRunningTaskInfo(/* taskId= */10);
+ RunningTaskInfo taskInfo = makeRunningTaskInfo(/* taskId= */10);
mRecentTasksControllerReal.onVisibleTasksChanged(List.of(taskInfo));
verify(mRecentTasksListener, never()).onVisibleTasksChanged(any());
}
+ @Test
+ public void generateList_emptyTaskList_expectNoGroupedTasks() throws Exception {
+ assertTrue(mRecentTasksControllerReal.generateList(List.of(), "test").isEmpty());
+ }
+
+ @Test
+ public void generateList_excludePipTask() throws Exception {
+ RunningTaskInfo task1 = makeRunningTaskInfo(1);
+ RunningTaskInfo pipTask = makeRunningTaskInfo(2);
+ pipTask.configuration.windowConfiguration.setWindowingMode(
+ WINDOWING_MODE_PINNED);
+
+ ArrayList<GroupedTaskInfo> groupedTasks = mRecentTasksControllerReal.generateList(
+ List.of(task1, pipTask),
+ "test");
+
+ assertGroupedTasksListEquals(groupedTasks, List.of(List.of(task1.taskId)));
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_ENABLE_TASK_STACK_OBSERVER_IN_SHELL)
+ @EnableFlags(com.android.wm.shell.Flags.FLAG_ENABLE_SHELL_TOP_TASK_TRACKING)
+ public void generateList_fullscreen_noVisibleTasks_expectNoGrouping() throws Exception {
+ RunningTaskInfo task1 = makeRunningTaskInfo(1);
+ RunningTaskInfo task2 = makeRunningTaskInfo(2);
+ RunningTaskInfo task3 = makeRunningTaskInfo(3);
+ // Reset visible tasks list
+ mRecentTasksControllerReal.onVisibleTasksChanged(List.of());
+
+ // Generate a list with a number of fullscreen tasks
+ ArrayList<GroupedTaskInfo> groupedTasks = mRecentTasksControllerReal.generateList(
+ List.of(task1, task2, task3),
+ "test");
+
+ assertGroupedTasksListEquals(groupedTasks, List.of(
+ List.of(task1.taskId),
+ List.of(task2.taskId),
+ List.of(task3.taskId)));
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_ENABLE_TASK_STACK_OBSERVER_IN_SHELL)
+ @EnableFlags(com.android.wm.shell.Flags.FLAG_ENABLE_SHELL_TOP_TASK_TRACKING)
+ public void generateList_fullscreen_singleVisibleTask_expectNoGrouping() throws Exception {
+ RunningTaskInfo task1 = makeRunningTaskInfo(1);
+ RunningTaskInfo task2 = makeRunningTaskInfo(2);
+ RunningTaskInfo task3 = makeRunningTaskInfo(3);
+
+ // Reset visible tasks list
+ mRecentTasksControllerReal.onVisibleTasksChanged(List.of(task1));
+
+ // Generate a list with a number of fullscreen tasks
+ ArrayList<GroupedTaskInfo> groupedTasks = mRecentTasksControllerReal.generateList(
+ List.of(task1, task2, task3),
+ "test");
+
+ assertGroupedTasksListEquals(groupedTasks, List.of(
+ List.of(task1.taskId),
+ List.of(task2.taskId),
+ List.of(task3.taskId)));
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_ENABLE_TASK_STACK_OBSERVER_IN_SHELL)
+ @EnableFlags(com.android.wm.shell.Flags.FLAG_ENABLE_SHELL_TOP_TASK_TRACKING)
+ public void generateList_fullscreen_multipleVisibleTasks_expectGrouping() throws Exception {
+ RunningTaskInfo task1 = makeRunningTaskInfo(1);
+ RunningTaskInfo task2 = makeRunningTaskInfo(2);
+ RunningTaskInfo task3 = makeRunningTaskInfo(3);
+
+ // Reset visible tasks list
+ mRecentTasksControllerReal.onVisibleTasksChanged(List.of(task1, task2));
+
+ // Generate a list with a number of fullscreen tasks
+ ArrayList<GroupedTaskInfo> groupedTasks = mRecentTasksControllerReal.generateList(
+ List.of(task1, task2, task3),
+ "test");
+
+ assertGroupedTasksListEquals(groupedTasks, List.of(
+ List.of(task1.taskId),
+ List.of(task2.taskId),
+ List.of(task3.taskId)));
+ }
+
/**
* Helper to create a task with a given task id.
*/
private RecentTaskInfo makeTaskInfo(int taskId) {
RecentTaskInfo info = new RecentTaskInfo();
info.taskId = taskId;
-
+ info.realActivity = new ComponentName("testPackage", "testClass");
Intent intent = new Intent();
intent.setComponent(new ComponentName("com." + taskId, "Activity" + taskId));
info.baseIntent = intent;
-
info.lastNonFullscreenBounds = new Rect();
return info;
}
@@ -742,10 +829,14 @@ public class RecentTasksControllerTest extends ShellTestCase {
/**
* Helper to create a running task with a given task id.
*/
- private ActivityManager.RunningTaskInfo makeRunningTaskInfo(int taskId) {
- ActivityManager.RunningTaskInfo info = new ActivityManager.RunningTaskInfo();
+ private RunningTaskInfo makeRunningTaskInfo(int taskId) {
+ RunningTaskInfo info = new RunningTaskInfo();
info.taskId = taskId;
info.realActivity = new ComponentName("testPackage", "testClass");
+ Intent intent = new Intent();
+ intent.setComponent(new ComponentName("com." + taskId, "Activity" + taskId));
+ info.baseIntent = intent;
+ info.lastNonFullscreenBounds = new Rect();
return info;
}
@@ -759,37 +850,42 @@ public class RecentTasksControllerTest extends ShellTestCase {
/**
* Asserts that the recent tasks matches the given task ids.
+ * TODO(346588978): Separate out specific split verification during the iteration below
*
- * @param expectedTaskIds list of task ids that map to the flattened task ids of the tasks in
- * the grouped task list
+ * @param expectedTaskIds a list of expected grouped task ids (itself a list of ints)
*/
- private void assertGroupedTasksListEquals(List<GroupedTaskInfo> recentTasks,
- int... expectedTaskIds) {
- int[] flattenedTaskIds = new int[recentTasks.size() * 2];
- for (int i = 0; i < recentTasks.size(); i++) {
- GroupedTaskInfo pair = recentTasks.get(i);
- int taskId1 = pair.getTaskInfo1().taskId;
- flattenedTaskIds[2 * i] = taskId1;
- flattenedTaskIds[2 * i + 1] = pair.getTaskInfo2() != null
- ? pair.getTaskInfo2().taskId
- : -1;
-
- if (pair.getTaskInfo2() != null) {
- assertNotNull(pair.getSplitBounds());
- int leftTopTaskId = pair.getSplitBounds().leftTopTaskId;
- int bottomRightTaskId = pair.getSplitBounds().rightBottomTaskId;
+ private void assertGroupedTasksListEquals(List<GroupedTaskInfo> groupedTasks,
+ List<List<Integer>> expectedTaskIds) {
+ List<List<Integer>> foundTaskIds = new ArrayList<>();
+ for (int i = 0; i < groupedTasks.size(); i++) {
+ GroupedTaskInfo groupedTask = groupedTasks.get(i);
+ List<Integer> groupedTaskIds = groupedTask.getTaskInfoList().stream()
+ .map(taskInfo -> taskInfo.taskId)
+ .toList();
+ foundTaskIds.add(groupedTaskIds);
+
+ if (groupedTask.isBaseType(TYPE_SPLIT)) {
+ assertNotNull(groupedTask.getSplitBounds());
+ int leftTopTaskId = groupedTask.getSplitBounds().leftTopTaskId;
+ int bottomRightTaskId = groupedTask.getSplitBounds().rightBottomTaskId;
// Unclear if pairs are ordered by split position, most likely not.
- assertTrue(leftTopTaskId == taskId1
- || leftTopTaskId == pair.getTaskInfo2().taskId);
- assertTrue(bottomRightTaskId == taskId1
- || bottomRightTaskId == pair.getTaskInfo2().taskId);
- } else {
- assertNull(pair.getSplitBounds());
+ assertTrue(leftTopTaskId == groupedTaskIds.getFirst()
+ || leftTopTaskId == groupedTaskIds.getLast());
+ assertTrue(bottomRightTaskId == groupedTaskIds.getFirst()
+ || bottomRightTaskId == groupedTaskIds.getLast());
}
}
- assertArrayEquals("Expected: " + Arrays.toString(expectedTaskIds)
- + " Received: " + Arrays.toString(flattenedTaskIds),
- flattenedTaskIds,
- expectedTaskIds);
+ List<Integer> flattenedExpectedTaskIds = expectedTaskIds.stream()
+ .flatMap(List::stream)
+ .toList();
+ List<Integer> flattenedFoundTaskIds = foundTaskIds.stream()
+ .flatMap(List::stream)
+ .toList();
+ assertEquals("Expected: "
+ + flattenedExpectedTaskIds.stream().map(String::valueOf).collect(joining())
+ + " Received: "
+ + flattenedFoundTaskIds.stream().map(String::valueOf).collect(joining()),
+ flattenedExpectedTaskIds,
+ flattenedFoundTaskIds);
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTest.java
index 66636c56bb64..6ac34d736f6f 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTest.java
@@ -165,10 +165,11 @@ public class TaskViewTest extends ShellTestCase {
doReturn(true).when(mTransitions).isRegistered();
}
mTaskViewRepository = new TaskViewRepository();
- mTaskViewTransitions = spy(new TaskViewTransitions(mTransitions, mTaskViewRepository));
+ mTaskViewTransitions = spy(new TaskViewTransitions(mTransitions, mTaskViewRepository,
+ mOrganizer, mSyncQueue));
mTaskViewTaskController = new TaskViewTaskController(mContext, mOrganizer,
mTaskViewTransitions, mSyncQueue);
- mTaskView = new TaskView(mContext, mTaskViewTaskController);
+ mTaskView = new TaskView(mContext, mTaskViewTransitions, mTaskViewTaskController);
mTaskView.setHandler(mViewHandler);
mTaskView.setListener(mExecutor, mViewListener);
}
@@ -182,7 +183,7 @@ public class TaskViewTest extends ShellTestCase {
@Test
public void testSetPendingListener_throwsException() {
- TaskView taskView = new TaskView(mContext,
+ TaskView taskView = new TaskView(mContext, mTaskViewTransitions,
new TaskViewTaskController(mContext, mOrganizer, mTaskViewTransitions, mSyncQueue));
taskView.setListener(mExecutor, mViewListener);
try {
@@ -326,7 +327,7 @@ public class TaskViewTest extends ShellTestCase {
public void testOnNewTask_noSurface() {
assumeTrue(Transitions.ENABLE_SHELL_TRANSITIONS);
WindowContainerTransaction wct = new WindowContainerTransaction();
- mTaskViewTaskController.prepareOpenAnimation(true /* newTask */,
+ mTaskViewTransitions.prepareOpenAnimation(mTaskViewTaskController, true /* newTask */,
new SurfaceControl.Transaction(), new SurfaceControl.Transaction(), mTaskInfo,
mLeash, wct);
@@ -354,7 +355,7 @@ public class TaskViewTest extends ShellTestCase {
assumeTrue(Transitions.ENABLE_SHELL_TRANSITIONS);
mTaskView.surfaceCreated(mock(SurfaceHolder.class));
WindowContainerTransaction wct = new WindowContainerTransaction();
- mTaskViewTaskController.prepareOpenAnimation(true /* newTask */,
+ mTaskViewTransitions.prepareOpenAnimation(mTaskViewTaskController, true /* newTask */,
new SurfaceControl.Transaction(), new SurfaceControl.Transaction(), mTaskInfo,
mLeash, wct);
@@ -366,7 +367,7 @@ public class TaskViewTest extends ShellTestCase {
public void testSurfaceCreated_withTask() {
assumeTrue(Transitions.ENABLE_SHELL_TRANSITIONS);
WindowContainerTransaction wct = new WindowContainerTransaction();
- mTaskViewTaskController.prepareOpenAnimation(true /* newTask */,
+ mTaskViewTransitions.prepareOpenAnimation(mTaskViewTaskController, true /* newTask */,
new SurfaceControl.Transaction(), new SurfaceControl.Transaction(), mTaskInfo,
mLeash, wct);
mTaskView.surfaceCreated(mock(SurfaceHolder.class));
@@ -374,7 +375,7 @@ public class TaskViewTest extends ShellTestCase {
verify(mViewListener).onInitialized();
verify(mTaskViewTransitions).setTaskViewVisible(eq(mTaskViewTaskController), eq(true));
- mTaskViewTaskController.prepareOpenAnimation(false /* newTask */,
+ mTaskViewTransitions.prepareOpenAnimation(mTaskViewTaskController, false /* newTask */,
new SurfaceControl.Transaction(), new SurfaceControl.Transaction(), mTaskInfo,
mLeash, wct);
@@ -396,7 +397,7 @@ public class TaskViewTest extends ShellTestCase {
assumeTrue(Transitions.ENABLE_SHELL_TRANSITIONS);
SurfaceHolder sh = mock(SurfaceHolder.class);
WindowContainerTransaction wct = new WindowContainerTransaction();
- mTaskViewTaskController.prepareOpenAnimation(true /* newTask */,
+ mTaskViewTransitions.prepareOpenAnimation(mTaskViewTaskController, true /* newTask */,
new SurfaceControl.Transaction(), new SurfaceControl.Transaction(), mTaskInfo,
mLeash, wct);
mTaskView.surfaceCreated(sh);
@@ -414,7 +415,7 @@ public class TaskViewTest extends ShellTestCase {
public void testOnReleased() {
assumeTrue(Transitions.ENABLE_SHELL_TRANSITIONS);
WindowContainerTransaction wct = new WindowContainerTransaction();
- mTaskViewTaskController.prepareOpenAnimation(true /* newTask */,
+ mTaskViewTransitions.prepareOpenAnimation(mTaskViewTaskController, true /* newTask */,
new SurfaceControl.Transaction(), new SurfaceControl.Transaction(), mTaskInfo,
mLeash, wct);
mTaskView.surfaceCreated(mock(SurfaceHolder.class));
@@ -423,14 +424,14 @@ public class TaskViewTest extends ShellTestCase {
verify(mOrganizer).removeListener(eq(mTaskViewTaskController));
verify(mViewListener).onReleased();
assertThat(mTaskView.isInitialized()).isFalse();
- verify(mTaskViewTransitions).removeTaskView(eq(mTaskViewTaskController));
+ verify(mTaskViewTransitions).unregisterTaskView(eq(mTaskViewTaskController));
}
@Test
public void testOnTaskVanished() {
assumeTrue(Transitions.ENABLE_SHELL_TRANSITIONS);
WindowContainerTransaction wct = new WindowContainerTransaction();
- mTaskViewTaskController.prepareOpenAnimation(true /* newTask */,
+ mTaskViewTransitions.prepareOpenAnimation(mTaskViewTaskController, true /* newTask */,
new SurfaceControl.Transaction(), new SurfaceControl.Transaction(), mTaskInfo,
mLeash, wct);
mTaskView.surfaceCreated(mock(SurfaceHolder.class));
@@ -443,7 +444,7 @@ public class TaskViewTest extends ShellTestCase {
public void testOnBackPressedOnTaskRoot() {
assumeTrue(Transitions.ENABLE_SHELL_TRANSITIONS);
WindowContainerTransaction wct = new WindowContainerTransaction();
- mTaskViewTaskController.prepareOpenAnimation(true /* newTask */,
+ mTaskViewTransitions.prepareOpenAnimation(mTaskViewTaskController, true /* newTask */,
new SurfaceControl.Transaction(), new SurfaceControl.Transaction(), mTaskInfo,
mLeash, wct);
mTaskViewTaskController.onBackPressedOnTaskRoot(mTaskInfo);
@@ -455,7 +456,7 @@ public class TaskViewTest extends ShellTestCase {
public void testSetOnBackPressedOnTaskRoot() {
assumeTrue(Transitions.ENABLE_SHELL_TRANSITIONS);
WindowContainerTransaction wct = new WindowContainerTransaction();
- mTaskViewTaskController.prepareOpenAnimation(true /* newTask */,
+ mTaskViewTransitions.prepareOpenAnimation(mTaskViewTaskController, true /* newTask */,
new SurfaceControl.Transaction(), new SurfaceControl.Transaction(), mTaskInfo,
mLeash, wct);
verify(mOrganizer).setInterceptBackPressedOnTaskRoot(eq(mTaskInfo.token), eq(true));
@@ -524,7 +525,7 @@ public class TaskViewTest extends ShellTestCase {
// Make the task available
WindowContainerTransaction wct = mock(WindowContainerTransaction.class);
- mTaskViewTaskController.startRootTask(mTaskInfo, mLeash, wct);
+ mTaskViewTransitions.startRootTask(mTaskViewTaskController, mTaskInfo, mLeash, wct);
// Bounds got set
verify(wct).setBounds(any(WindowContainerToken.class), eq(bounds));
@@ -564,7 +565,7 @@ public class TaskViewTest extends ShellTestCase {
// Make the task available / start prepareOpen
WindowContainerTransaction wct = mock(WindowContainerTransaction.class);
- mTaskViewTaskController.prepareOpenAnimation(true /* newTask */,
+ mTaskViewTransitions.prepareOpenAnimation(mTaskViewTaskController, true /* newTask */,
new SurfaceControl.Transaction(), new SurfaceControl.Transaction(), mTaskInfo,
mLeash, wct);
@@ -594,7 +595,7 @@ public class TaskViewTest extends ShellTestCase {
// Task is available, but the surface was never created
WindowContainerTransaction wct = mock(WindowContainerTransaction.class);
- mTaskViewTaskController.prepareOpenAnimation(true /* newTask */,
+ mTaskViewTransitions.prepareOpenAnimation(mTaskViewTaskController, true /* newTask */,
new SurfaceControl.Transaction(), new SurfaceControl.Transaction(), mTaskInfo,
mLeash, wct);
@@ -619,7 +620,7 @@ public class TaskViewTest extends ShellTestCase {
assumeTrue(Transitions.ENABLE_SHELL_TRANSITIONS);
mTaskView.removeTask();
- verify(mTaskViewTransitions, never()).closeTaskView(any(), any());
+ assertFalse(mTaskViewTransitions.hasPending());
}
@Test
@@ -628,14 +629,14 @@ public class TaskViewTest extends ShellTestCase {
mTaskView.surfaceCreated(mock(SurfaceHolder.class));
WindowContainerTransaction wct = new WindowContainerTransaction();
- mTaskViewTaskController.prepareOpenAnimation(true /* newTask */,
+ mTaskViewTransitions.prepareOpenAnimation(mTaskViewTaskController, true /* newTask */,
new SurfaceControl.Transaction(), new SurfaceControl.Transaction(), mTaskInfo,
mLeash, wct);
verify(mViewListener).onTaskCreated(eq(mTaskInfo.taskId), any());
mTaskView.removeTask();
- verify(mTaskViewTransitions).closeTaskView(any(), eq(mTaskViewTaskController));
+ verify(mTaskViewTransitions).removeTaskView(eq(mTaskViewTaskController), any());
}
@Test
@@ -646,7 +647,7 @@ public class TaskViewTest extends ShellTestCase {
mTaskViewTaskController.onTaskAppeared(mTaskInfo, mLeash);
assertNull(mTaskViewTaskController.getTaskInfo());
- verify(mTaskViewTransitions).closeTaskView(any(), eq(mTaskViewTaskController));
+ verify(mTaskViewTransitions).removeTaskView(eq(mTaskViewTaskController), any());
}
@Test
@@ -655,7 +656,7 @@ public class TaskViewTest extends ShellTestCase {
mTaskViewTaskController.onTaskAppeared(mTaskInfo, mLeash);
assertEquals(mTaskInfo, mTaskViewTaskController.getPendingInfo());
- verify(mTaskViewTransitions, never()).closeTaskView(any(), any());
+ verify(mTaskViewTransitions, never()).removeTaskView(any(), any());
}
@Test
@@ -671,7 +672,7 @@ public class TaskViewTest extends ShellTestCase {
mTaskView.surfaceCreated(mock(SurfaceHolder.class));
reset(mOrganizer);
WindowContainerTransaction wct = new WindowContainerTransaction();
- mTaskViewTaskController.prepareOpenAnimation(true /* newTask */,
+ mTaskViewTransitions.prepareOpenAnimation(mTaskViewTaskController, true /* newTask */,
new SurfaceControl.Transaction(), new SurfaceControl.Transaction(), mTaskInfo,
mLeash, wct);
mTaskView.onComputeInternalInsets(new ViewTreeObserver.InternalInsetsInfo());
@@ -688,7 +689,7 @@ public class TaskViewTest extends ShellTestCase {
mTaskView.surfaceCreated(mock(SurfaceHolder.class));
WindowContainerTransaction wct = new WindowContainerTransaction();
- mTaskViewTaskController.prepareOpenAnimation(true /* newTask */,
+ mTaskViewTransitions.prepareOpenAnimation(mTaskViewTaskController, true /* newTask */,
new SurfaceControl.Transaction(), new SurfaceControl.Transaction(), mTaskInfo,
mLeash, wct);
reset(mOrganizer);
@@ -706,7 +707,7 @@ public class TaskViewTest extends ShellTestCase {
public void testReleaseInOnTaskRemoval_noNPE() {
mTaskViewTaskController = spy(new TaskViewTaskController(mContext, mOrganizer,
mTaskViewTransitions, mSyncQueue));
- mTaskView = new TaskView(mContext, mTaskViewTaskController);
+ mTaskView = new TaskView(mContext, mTaskViewTransitions, mTaskViewTaskController);
mTaskView.setListener(mExecutor, new TaskView.Listener() {
@Override
public void onTaskRemovalStarted(int taskId) {
@@ -715,7 +716,7 @@ public class TaskViewTest extends ShellTestCase {
});
WindowContainerTransaction wct = new WindowContainerTransaction();
- mTaskViewTaskController.prepareOpenAnimation(true /* newTask */,
+ mTaskViewTransitions.prepareOpenAnimation(mTaskViewTaskController, true /* newTask */,
new SurfaceControl.Transaction(), new SurfaceControl.Transaction(), mTaskInfo,
mLeash, wct);
mTaskView.surfaceCreated(mock(SurfaceHolder.class));
@@ -763,7 +764,7 @@ public class TaskViewTest extends ShellTestCase {
@Test
public void testOnAppeared_setsTrimmableTask() {
WindowContainerTransaction wct = new WindowContainerTransaction();
- mTaskViewTaskController.prepareOpenAnimation(true /* newTask */,
+ mTaskViewTransitions.prepareOpenAnimation(mTaskViewTaskController, true /* newTask */,
new SurfaceControl.Transaction(), new SurfaceControl.Transaction(), mTaskInfo,
mLeash, wct);
@@ -773,11 +774,11 @@ public class TaskViewTest extends ShellTestCase {
@Test
public void testMoveToFullscreen_callsTaskRemovalStarted() {
WindowContainerTransaction wct = new WindowContainerTransaction();
- mTaskViewTaskController.prepareOpenAnimation(true /* newTask */,
+ mTaskViewTransitions.prepareOpenAnimation(mTaskViewTaskController, true /* newTask */,
new SurfaceControl.Transaction(), new SurfaceControl.Transaction(), mTaskInfo,
mLeash, wct);
mTaskView.surfaceCreated(mock(SurfaceHolder.class));
- mTaskViewTaskController.moveToFullscreen();
+ mTaskViewTransitions.moveTaskViewToFullscreen(mTaskViewTaskController);
verify(mViewListener).onTaskRemovalStarted(eq(mTaskInfo.taskId));
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTransitionsTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTransitionsTest.java
index 5f6f18f82fb4..326f11e300fd 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTransitionsTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTransitionsTest.java
@@ -44,7 +44,9 @@ import android.window.WindowContainerTransaction;
import androidx.test.filters.SmallTest;
import com.android.wm.shell.Flags;
+import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.transition.Transitions;
import org.junit.Before;
@@ -56,6 +58,7 @@ import org.mockito.MockitoAnnotations;
import java.util.ArrayList;
import java.util.List;
+import java.util.concurrent.Executor;
import platform.test.runner.parameterized.ParameterizedAndroidJunit4;
import platform.test.runner.parameterized.Parameters;
@@ -82,6 +85,12 @@ public class TaskViewTransitionsTest extends ShellTestCase {
ActivityManager.RunningTaskInfo mTaskInfo;
@Mock
WindowContainerToken mToken;
+ @Mock
+ ShellTaskOrganizer mOrganizer;
+ @Mock
+ SyncTransactionQueue mSyncQueue;
+
+ Executor mExecutor = command -> command.run();
TaskViewRepository mTaskViewRepository;
TaskViewTransitions mTaskViewTransitions;
@@ -104,9 +113,12 @@ public class TaskViewTransitionsTest extends ShellTestCase {
mTaskInfo.taskDescription = mock(ActivityManager.TaskDescription.class);
mTaskViewRepository = new TaskViewRepository();
- mTaskViewTransitions = spy(new TaskViewTransitions(mTransitions, mTaskViewRepository));
- mTaskViewTransitions.addTaskView(mTaskViewTaskController);
+ when(mOrganizer.getExecutor()).thenReturn(mExecutor);
+ mTaskViewTransitions = spy(new TaskViewTransitions(mTransitions, mTaskViewRepository,
+ mOrganizer, mSyncQueue));
+ mTaskViewTransitions.registerTaskView(mTaskViewTaskController);
when(mTaskViewTaskController.getTaskInfo()).thenReturn(mTaskInfo);
+ when(mTaskViewTaskController.getTaskToken()).thenReturn(mToken);
}
@Test
@@ -212,7 +224,7 @@ public class TaskViewTransitionsTest extends ShellTestCase {
@Test
public void testSetTaskVisibility_taskRemoved_noNPE() {
- mTaskViewTransitions.removeTaskView(mTaskViewTaskController);
+ mTaskViewTransitions.unregisterTaskView(mTaskViewTaskController);
assumeTrue(Transitions.ENABLE_SHELL_TRANSITIONS);
@@ -221,7 +233,7 @@ public class TaskViewTransitionsTest extends ShellTestCase {
@Test
public void testSetTaskBounds_taskRemoved_noNPE() {
- mTaskViewTransitions.removeTaskView(mTaskViewTaskController);
+ mTaskViewTransitions.unregisterTaskView(mTaskViewTaskController);
assumeTrue(Transitions.ENABLE_SHELL_TRANSITIONS);
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 79e9b9c8cd77..b4791642663a 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
@@ -61,6 +61,7 @@ import com.android.window.flags.Flags
import com.android.wm.shell.R
import com.android.wm.shell.desktopmode.DesktopImmersiveController
import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.InputMethod
+import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.MinimizeReason
import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ResizeTrigger
import com.android.wm.shell.desktopmode.DesktopTasksController.SnapPosition
import com.android.wm.shell.desktopmode.common.ToggleTaskSizeInteraction
@@ -272,7 +273,7 @@ class DesktopModeWindowDecorViewModelTests : DesktopModeWindowDecorViewModelTest
onClickListenerCaptor.value.onClick(view)
- verify(mockDesktopTasksController).minimizeTask(decor.mTaskInfo)
+ verify(mockDesktopTasksController).minimizeTask(decor.mTaskInfo, MinimizeReason.MINIMIZE_BUTTON)
}
@Test
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 b44af4733fd2..908bc9952e99 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
@@ -31,6 +31,7 @@ import android.platform.test.flag.junit.SetFlagsRule
import android.testing.TestableContext
import android.util.SparseArray
import android.view.Choreographer
+import android.view.Display
import android.view.Display.DEFAULT_DISPLAY
import android.view.IWindowManager
import android.view.InputChannel
@@ -157,6 +158,7 @@ open class DesktopModeWindowDecorViewModelTestsBase : ShellTestCase() {
protected val mockRecentsTransitionHandler = mock<RecentsTransitionHandler>()
protected val motionEvent = mock<MotionEvent>()
val displayLayout = mock<DisplayLayout>()
+ val display = mock<Display>()
protected lateinit var spyContext: TestableContext
private lateinit var desktopModeEventLogger: DesktopModeEventLogger
@@ -183,6 +185,7 @@ open class DesktopModeWindowDecorViewModelTestsBase : ShellTestCase() {
desktopModeEventLogger = mock<DesktopModeEventLogger>()
whenever(mockDesktopUserRepositories.current).thenReturn(mockDesktopRepository)
whenever(mockDisplayController.getDisplayContext(any())).thenReturn(spyContext)
+ whenever(mockDisplayController.getDisplay(any())).thenReturn(display)
whenever(mockDesktopUserRepositories.getProfile(anyInt()))
.thenReturn(mockDesktopRepository)
desktopModeWindowDecorViewModel = DesktopModeWindowDecorViewModel(
diff --git a/media/java/android/media/MediaCodecInfo.java b/media/java/android/media/MediaCodecInfo.java
index 302969f58ba8..9bb31d0076c9 100644
--- a/media/java/android/media/MediaCodecInfo.java
+++ b/media/java/android/media/MediaCodecInfo.java
@@ -908,13 +908,13 @@ public final class MediaCodecInfo {
/** @hide */
public String[] validFeatures() {
Feature[] features = getValidFeatures();
- String[] res = new String[features.length];
- for (int i = 0; i < res.length; i++) {
+ ArrayList<String> res = new ArrayList();
+ for (int i = 0; i < features.length; i++) {
if (!features[i].mInternal) {
- res[i] = features[i].mName;
+ res.add(features[i].mName);
}
}
- return res;
+ return res.toArray(new String[0]);
}
private Feature[] getValidFeatures() {
diff --git a/media/java/android/media/flags/media_better_together.aconfig b/media/java/android/media/flags/media_better_together.aconfig
index c48b5f4e4aea..312f78e2b24e 100644
--- a/media/java/android/media/flags/media_better_together.aconfig
+++ b/media/java/android/media/flags/media_better_together.aconfig
@@ -173,6 +173,18 @@ flag {
bug: "281072508"
}
+
+flag {
+ name: "enable_singleton_audio_manager_route_controller"
+ is_exported: true
+ namespace: "media_solutions"
+ description: "Use singleton AudioManagerRouteController shared across all users."
+ bug: "372868909"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
flag {
name: "enable_use_of_bluetooth_device_get_alias_for_mr2info_get_name"
namespace: "media_solutions"
diff --git a/media/java/android/media/tv/TvInputService.java b/media/java/android/media/tv/TvInputService.java
index abfc24413d56..f352a4183111 100644
--- a/media/java/android/media/tv/TvInputService.java
+++ b/media/java/android/media/tv/TvInputService.java
@@ -161,11 +161,6 @@ public abstract class TvInputService extends Service {
new RemoteCallbackList<>();
private TvInputManager mTvInputManager;
- /**
- * @hide
- */
- protected TvInputServiceExtensionManager mTvInputServiceExtensionManager =
- new TvInputServiceExtensionManager();
@Override
public final IBinder onBind(Intent intent) {
@@ -230,12 +225,20 @@ public abstract class TvInputService extends Service {
@Override
public IBinder getExtensionInterface(String name) {
- if (tifExtensionStandardization() && name != null) {
- if (TvInputServiceExtensionManager.checkIsStandardizedInterfaces(name)) {
- return mTvInputServiceExtensionManager.getExtensionIBinder(name);
+ IBinder binder = TvInputService.this.getExtensionInterface(name);
+ if (tifExtensionStandardization()) {
+ if (name != null
+ && TvInputServiceExtensionManager.checkIsStandardizedInterfaces(name)) {
+ if (TvInputServiceExtensionManager.checkIsStandardizedIBinder(name,
+ binder)) {
+ return binder;
+ } else {
+ // binder with standardized name is not standardized
+ return null;
+ }
}
}
- return TvInputService.this.getExtensionInterface(name);
+ return binder;
}
@Override
diff --git a/media/java/android/media/tv/TvInputServiceExtensionManager.java b/media/java/android/media/tv/TvInputServiceExtensionManager.java
index d33ac9256a21..02d261610d8a 100644
--- a/media/java/android/media/tv/TvInputServiceExtensionManager.java
+++ b/media/java/android/media/tv/TvInputServiceExtensionManager.java
@@ -16,13 +16,9 @@
package android.media.tv;
-import android.annotation.FlaggedApi;
-import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.RequiresPermission;
import android.annotation.StringDef;
-import android.media.tv.flags.Flags;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
@@ -30,21 +26,17 @@ import android.util.Log;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
-import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
-import java.util.Map;
import java.util.Set;
/**
* This class provides a list of available standardized TvInputService extension interface names
- * and a container storing IBinder objects that implement these interfaces created by SoC/OEMs.
- * It also provides an API for SoC/OEMs to register implemented IBinder objects.
+ * and checks if IBinder objects created by SoC/OEMs implement these interfaces.
*
* @hide
*/
-@FlaggedApi(Flags.FLAG_TIF_EXTENSION_STANDARDIZATION)
public final class TvInputServiceExtensionManager {
private static final String TAG = "TvInputServiceExtensionManager";
private static final String SCAN_PACKAGE = "android.media.tv.extension.scan.";
@@ -63,33 +55,6 @@ public final class TvInputServiceExtensionManager {
private static final String ANALOG_PACKAGE = "android.media.tv.extension.analog.";
private static final String TUNE_PACKAGE = "android.media.tv.extension.tune.";
- @IntDef(prefix = {"REGISTER_"}, value = {
- REGISTER_SUCCESS,
- REGISTER_FAIL_NAME_NOT_STANDARDIZED,
- REGISTER_FAIL_IMPLEMENTATION_NOT_STANDARDIZED,
- REGISTER_FAIL_REMOTE_EXCEPTION
- })
- @Retention(RetentionPolicy.SOURCE)
- public @interface RegisterResult {}
-
- /**
- * Registering binder returns success when it abides standardized interface structure
- */
- public static final int REGISTER_SUCCESS = 0;
- /**
- * Registering binder returns failure when the extension name is not in the standardization
- * list
- */
- public static final int REGISTER_FAIL_NAME_NOT_STANDARDIZED = 1;
- /**
- * Registering binder returns failure when the IBinder does not implement standardized interface
- */
- public static final int REGISTER_FAIL_IMPLEMENTATION_NOT_STANDARDIZED = 2;
- /**
- * Registering binder returns failure when remote server is not available
- */
- public static final int REGISTER_FAIL_REMOTE_EXCEPTION = 3;
-
@StringDef({
ISCAN_INTERFACE,
ISCAN_SESSION,
@@ -673,12 +638,6 @@ public final class TvInputServiceExtensionManager {
IMUX_TUNE
));
- // Store the mapping between interface names and IBinder
- private Map<String, IBinder> mExtensionInterfaceIBinderMapping = new HashMap<>();
-
- TvInputServiceExtensionManager() {
- }
-
/**
* Function to return available extension interface names
*/
@@ -694,43 +653,18 @@ public final class TvInputServiceExtensionManager {
}
/**
- * Registers IBinder objects that implement standardized AIDL interfaces.
- * <p>This function should be used by SoCs/OEMs
- *
- * @param extensionName Extension Interface Name
- * @param binder IBinder object to be registered
- * @return {@link #REGISTER_SUCCESS} on success of registering IBinder object
- * {@link #REGISTER_FAIL_NAME_NOT_STANDARDIZED} on failure due to registering extension
- * with non-standardized name
- * {@link #REGISTER_FAIL_IMPLEMENTATION_NOT_STANDARDIZED} on failure due to IBinder not
- * implementing standardized AIDL interface
- * {@link #REGISTER_FAIL_REMOTE_EXCEPTION} on failure due to remote exception
- */
- @RequiresPermission(android.Manifest.permission.TV_INPUT_HARDWARE)
- @RegisterResult
- public int registerExtensionIBinder(@StandardizedExtensionName @NonNull String extensionName,
- @NonNull IBinder binder) {
- if (!checkIsStandardizedInterfaces(extensionName)) {
- return REGISTER_FAIL_NAME_NOT_STANDARDIZED;
- }
- try {
- if (binder.getInterfaceDescriptor().equals(extensionName)) {
- mExtensionInterfaceIBinderMapping.put(extensionName, binder);
- return REGISTER_SUCCESS;
- } else {
- return REGISTER_FAIL_IMPLEMENTATION_NOT_STANDARDIZED;
+ * Function check if the IBinder object implements standardized interface
+ */
+ public static boolean checkIsStandardizedIBinder(@NonNull String extensionName,
+ @Nullable IBinder binder) {
+ if (binder != null) {
+ try {
+ return binder.getInterfaceDescriptor().equals(extensionName);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Fetching IBinder object failure due to " + e);
}
- } catch (RemoteException e) {
- Log.e(TAG, "Fetching IBinder object failure due to " + e);
- return REGISTER_FAIL_REMOTE_EXCEPTION;
}
- }
-
- /**
- * Function to get corresponding IBinder object
- */
- @Nullable IBinder getExtensionIBinder(@NonNull String extensionName) {
- return mExtensionInterfaceIBinderMapping.get(extensionName);
+ return false;
}
}
diff --git a/packages/SettingsLib/ActionButtonsPreference/res/color/settingslib_expressive_actionbutton_background.xml b/packages/SettingsLib/ActionButtonsPreference/res/color/settingslib_expressive_actionbutton_background.xml
new file mode 100644
index 000000000000..ec9ee2211259
--- /dev/null
+++ b/packages/SettingsLib/ActionButtonsPreference/res/color/settingslib_expressive_actionbutton_background.xml
@@ -0,0 +1,24 @@
+<?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">
+ <item android:state_enabled="false"
+ android:color="@color/settingslib_materialColorSurface"/>
+ <item android:state_checked="true" android:color="?attr/colorContainerChecked"/>
+ <item android:state_checkable="true" android:color="?attr/colorContainerUnchecked"/>
+ <item android:color="@color/settingslib_materialColorPrimaryContainer" />
+</selector> \ No newline at end of file
diff --git a/packages/SettingsLib/ActionButtonsPreference/res/color/settingslib_expressive_actionbutton_content_color.xml b/packages/SettingsLib/ActionButtonsPreference/res/color/settingslib_expressive_actionbutton_content_color.xml
new file mode 100644
index 000000000000..0488cbaead22
--- /dev/null
+++ b/packages/SettingsLib/ActionButtonsPreference/res/color/settingslib_expressive_actionbutton_content_color.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.
+ -->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_enabled="false"
+ android:alpha="@dimen/material_emphasis_disabled" android:color="?attr/colorOnSurface"/>
+ <item android:state_checkable="true" android:state_checked="true"
+ android:color="?attr/colorOnContainerChecked"/>
+ <item android:state_checkable="true" android:color="?attr/colorOnContainerUnchecked"/>
+ <item android:color="@color/settingslib_materialColorOnPrimaryContainer"/>
+</selector> \ No newline at end of file
diff --git a/packages/SettingsLib/ActionButtonsPreference/res/values-v35/styles_expressive.xml b/packages/SettingsLib/ActionButtonsPreference/res/values-v35/styles_expressive.xml
index fd8cecb8536e..267c9f65e104 100644
--- a/packages/SettingsLib/ActionButtonsPreference/res/values-v35/styles_expressive.xml
+++ b/packages/SettingsLib/ActionButtonsPreference/res/values-v35/styles_expressive.xml
@@ -17,9 +17,14 @@
<resources>
<style name="SettingsLibActionButton.Expressive" parent="SettingsLibButtonStyle.Expressive.Tonal">
- <item name="android:backgroundTint">@color/settingslib_materialColorPrimaryContainer</item>
- <item name="iconTint">@color/settingslib_materialColorOnPrimaryContainer</item>
- <item name="iconGravity">textTop</item>
+ <item name="android:backgroundTint">@color/settingslib_expressive_actionbutton_background</item>
+ <item name="android:textColor">@color/settingslib_expressive_actionbutton_content_color</item>
+ <item name="android:insetTop">@dimen/settingslib_expressive_space_none</item>
+ <item name="android:insetBottom">@dimen/settingslib_expressive_space_none</item>
+ <item name="iconTint">@color/settingslib_expressive_actionbutton_content_color</item>
+ <item name="iconSize">@dimen/settingslib_expressive_space_small4</item>
+ <item name="iconPadding">@dimen/settingslib_expressive_space_none</item>"
+ <item name="iconGravity">textStart</item>
</style>
<style name="SettingsLibActionButton.Expressive.Label" parent="">
diff --git a/packages/SettingsLib/ActionButtonsPreference/src/com/android/settingslib/widget/ActionButtonsPreference.java b/packages/SettingsLib/ActionButtonsPreference/src/com/android/settingslib/widget/ActionButtonsPreference.java
index 601e001f48c2..0027d632319b 100644
--- a/packages/SettingsLib/ActionButtonsPreference/src/com/android/settingslib/widget/ActionButtonsPreference.java
+++ b/packages/SettingsLib/ActionButtonsPreference/src/com/android/settingslib/widget/ActionButtonsPreference.java
@@ -549,7 +549,7 @@ public class ActionButtonsPreference extends Preference implements GroupSectionD
((MaterialButton) mButton).setIcon(mIcon);
}
mButton.setEnabled(mIsEnabled);
- mActionLayout.setOnClickListener(mListener);
+ mButton.setOnClickListener(mListener);
mActionLayout.setEnabled(mIsEnabled);
mActionLayout.setContentDescription(mText);
} else {
diff --git a/packages/SettingsLib/Android.bp b/packages/SettingsLib/Android.bp
index 933c512313ce..e5b58370e6dd 100644
--- a/packages/SettingsLib/Android.bp
+++ b/packages/SettingsLib/Android.bp
@@ -46,6 +46,8 @@ android_library {
"SettingsLibIntroPreference",
"SettingsLibLayoutPreference",
"SettingsLibMainSwitchPreference",
+ "SettingsLibMetadata",
+ "SettingsLibPreference",
"SettingsLibProfileSelector",
"SettingsLibProgressBar",
"SettingsLibRestrictedLockUtils",
@@ -77,6 +79,7 @@ android_library {
"src/**/*.kt",
"src/**/I*.aidl",
],
+ kotlincflags: ["-Xjvm-default=all"],
}
// defaults for lint option
diff --git a/packages/SettingsLib/BannerMessagePreference/Android.bp b/packages/SettingsLib/BannerMessagePreference/Android.bp
index 3f671b9e7b10..77e2cc735895 100644
--- a/packages/SettingsLib/BannerMessagePreference/Android.bp
+++ b/packages/SettingsLib/BannerMessagePreference/Android.bp
@@ -14,12 +14,16 @@ android_library {
"SettingsLintDefaults",
],
- srcs: ["src/**/*.java"],
+ srcs: [
+ "src/**/*.java",
+ "src/**/*.kt",
+ ],
resource_dirs: ["res"],
static_libs: [
- "androidx.preference_preference",
+ "SettingsLibButtonPreference",
"SettingsLibSettingsTheme",
+ "androidx.preference_preference",
],
sdk_version: "system_current",
diff --git a/packages/SettingsLib/BannerMessagePreference/res/color/settingslib_banner_button_background_high.xml b/packages/SettingsLib/BannerMessagePreference/res/color/settingslib_banner_button_background_high.xml
new file mode 100644
index 000000000000..d113b547bf3e
--- /dev/null
+++ b/packages/SettingsLib/BannerMessagePreference/res/color/settingslib_banner_button_background_high.xml
@@ -0,0 +1,24 @@
+<?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">
+ <item android:state_enabled="false"
+ android:alpha="@dimen/material_emphasis_disabled_background" android:color="@color/settingslib_colorBackgroundLevel_high"/>
+ <item android:state_checked="true" android:color="?attr/colorContainerChecked"/>
+ <item android:state_checkable="true" android:color="?attr/colorContainerUnchecked"/>
+ <item android:color="@color/settingslib_colorBackgroundLevel_high" />
+</selector> \ No newline at end of file
diff --git a/packages/SettingsLib/BannerMessagePreference/res/color/settingslib_banner_button_background_low.xml b/packages/SettingsLib/BannerMessagePreference/res/color/settingslib_banner_button_background_low.xml
new file mode 100644
index 000000000000..cb89d9a18fc2
--- /dev/null
+++ b/packages/SettingsLib/BannerMessagePreference/res/color/settingslib_banner_button_background_low.xml
@@ -0,0 +1,24 @@
+<?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">
+ <item android:state_enabled="false"
+ android:alpha="@dimen/material_emphasis_disabled_background" android:color="@color/settingslib_colorBackgroundLevel_low"/>
+ <item android:state_checked="true" android:color="?attr/colorContainerChecked"/>
+ <item android:state_checkable="true" android:color="?attr/colorContainerUnchecked"/>
+ <item android:color="@color/settingslib_colorBackgroundLevel_low" />
+</selector> \ No newline at end of file
diff --git a/packages/SettingsLib/BannerMessagePreference/res/color/settingslib_banner_button_background_medium.xml b/packages/SettingsLib/BannerMessagePreference/res/color/settingslib_banner_button_background_medium.xml
new file mode 100644
index 000000000000..f820c352632d
--- /dev/null
+++ b/packages/SettingsLib/BannerMessagePreference/res/color/settingslib_banner_button_background_medium.xml
@@ -0,0 +1,24 @@
+<?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">
+ <item android:state_enabled="false"
+ android:alpha="@dimen/material_emphasis_disabled_background" android:color="@color/settingslib_colorBackgroundLevel_medium"/>
+ <item android:state_checked="true" android:color="?attr/colorContainerChecked"/>
+ <item android:state_checkable="true" android:color="?attr/colorContainerUnchecked"/>
+ <item android:color="@color/settingslib_colorBackgroundLevel_medium" />
+</selector> \ No newline at end of file
diff --git a/packages/SettingsLib/BannerMessagePreference/res/color/settingslib_banner_button_background_normal.xml b/packages/SettingsLib/BannerMessagePreference/res/color/settingslib_banner_button_background_normal.xml
new file mode 100644
index 000000000000..8037a8bb75be
--- /dev/null
+++ b/packages/SettingsLib/BannerMessagePreference/res/color/settingslib_banner_button_background_normal.xml
@@ -0,0 +1,24 @@
+<?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">
+ <item android:state_enabled="false"
+ android:alpha="@dimen/material_emphasis_disabled_background" android:color="?attr/colorOnSurface"/>
+ <item android:state_checked="true" android:color="?attr/colorContainerChecked"/>
+ <item android:state_checkable="true" android:color="?attr/colorContainerUnchecked"/>
+ <item android:color="?attr/colorContainer" />
+</selector> \ No newline at end of file
diff --git a/packages/SettingsLib/BannerMessagePreference/res/drawable-v31/settingslib_card_background.xml b/packages/SettingsLib/BannerMessagePreference/res/drawable-v31/settingslib_card_background.xml
index 072eb5873ce5..3f806e1574b7 100644
--- a/packages/SettingsLib/BannerMessagePreference/res/drawable-v31/settingslib_card_background.xml
+++ b/packages/SettingsLib/BannerMessagePreference/res/drawable-v31/settingslib_card_background.xml
@@ -16,6 +16,6 @@
-->
<shape xmlns:android="http://schemas.android.com/apk/res/android">
- <solid android:color="?android:attr/background" />
+ <solid android:color="@android:color/white" />
<corners android:radius="28dp"/>
</shape> \ No newline at end of file
diff --git a/packages/SettingsLib/BannerMessagePreference/res/drawable-v35/settingslib_expressive_card_background.xml b/packages/SettingsLib/BannerMessagePreference/res/drawable-v35/settingslib_expressive_card_background.xml
new file mode 100644
index 000000000000..a677a66f86e7
--- /dev/null
+++ b/packages/SettingsLib/BannerMessagePreference/res/drawable-v35/settingslib_expressive_card_background.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2025 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+ <solid android:color="@color/settingslib_materialColorSurfaceBright" />
+ <corners android:radius="28dp"/>
+</shape> \ No newline at end of file
diff --git a/packages/SettingsLib/BannerMessagePreference/res/layout-v31/settingslib_banner_message.xml b/packages/SettingsLib/BannerMessagePreference/res/layout-v31/settingslib_banner_message.xml
index 9d53e39c7bf8..ca596d89fab6 100644
--- a/packages/SettingsLib/BannerMessagePreference/res/layout-v31/settingslib_banner_message.xml
+++ b/packages/SettingsLib/BannerMessagePreference/res/layout-v31/settingslib_banner_message.xml
@@ -15,85 +15,92 @@
limitations under the License.
-->
-<com.android.settingslib.widget.BannerMessageView
- xmlns:android="http://schemas.android.com/apk/res/android"
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:orientation="vertical"
- style="@style/Banner.Preference.SettingsLib">
+ android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+ android:paddingStart="?android:attr/listPreferredItemPaddingStart">
- <RelativeLayout
- android:id="@+id/top_row"
+ <com.android.settingslib.widget.BannerMessageView
+ android:id="@+id/banner_background"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:paddingBottom="8dp"
- android:orientation="horizontal">
+ android:orientation="vertical"
+ style="@style/Banner.Preference.SettingsLib">
- <ImageView
- android:id="@+id/banner_icon"
- android:layout_width="24dp"
- android:layout_height="24dp"
- android:layout_alignParentStart="true"
- android:importantForAccessibility="no" />
-
- <ImageButton
- android:id="@+id/banner_dismiss_btn"
- android:layout_width="wrap_content"
+ <RelativeLayout
+ android:id="@+id/top_row"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:src="@drawable/settingslib_ic_cross"
- android:layout_alignParentEnd="true"
- android:contentDescription="@string/accessibility_banner_message_dismiss"
- style="@style/Banner.Dismiss.SettingsLib" />
- </RelativeLayout>
+ android:paddingBottom="8dp"
+ android:orientation="horizontal">
- <TextView
- android:id="@+id/banner_title"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="start"
- android:textAlignment="viewStart"
- android:paddingTop="0dp"
- android:paddingBottom="4dp"
- android:textAppearance="@style/Banner.Title.SettingsLib"/>
+ <ImageView
+ android:id="@+id/banner_icon"
+ android:layout_width="24dp"
+ android:layout_height="24dp"
+ android:layout_alignParentStart="true"
+ android:importantForAccessibility="no" />
- <TextView
- android:id="@+id/banner_subtitle"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="start"
- android:textAlignment="viewStart"
- android:paddingTop="0dp"
- android:paddingBottom="4dp"
- android:textAppearance="@style/Banner.Subtitle.SettingsLib"
- android:visibility="gone"/>
+ <ImageButton
+ android:id="@+id/banner_dismiss_btn"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:src="@drawable/settingslib_ic_cross"
+ android:layout_alignParentEnd="true"
+ android:contentDescription="@string/accessibility_banner_message_dismiss"
+ style="@style/Banner.Dismiss.SettingsLib" />
+ </RelativeLayout>
- <TextView
- android:id="@+id/banner_summary"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="start"
- android:textAlignment="viewStart"
- android:paddingTop="4dp"
- android:paddingBottom="8dp"
- android:textAppearance="@style/Banner.Summary.SettingsLib"/>
-
- <LinearLayout
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:orientation="horizontal"
- android:minHeight="8dp"
- android:gravity="end">
+ <TextView
+ android:id="@+id/banner_title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="start"
+ android:textAlignment="viewStart"
+ android:paddingTop="0dp"
+ android:paddingBottom="4dp"
+ android:textAppearance="@style/Banner.Title.SettingsLib"/>
- <Button
- android:id="@+id/banner_negative_btn"
+ <TextView
+ android:id="@+id/banner_subtitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- style="@style/Banner.ButtonText.SettingsLib"/>
+ android:layout_gravity="start"
+ android:textAlignment="viewStart"
+ android:paddingTop="0dp"
+ android:paddingBottom="4dp"
+ android:textAppearance="@style/Banner.Subtitle.SettingsLib"
+ android:visibility="gone"/>
- <Button
- android:id="@+id/banner_positive_btn"
+ <TextView
+ android:id="@+id/banner_summary"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- style="@style/Banner.ButtonText.SettingsLib"/>
- </LinearLayout>
-</com.android.settingslib.widget.BannerMessageView> \ No newline at end of file
+ android:layout_gravity="start"
+ android:textAlignment="viewStart"
+ android:paddingTop="4dp"
+ android:paddingBottom="8dp"
+ android:textAppearance="@style/Banner.Summary.SettingsLib"/>
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:minHeight="8dp"
+ android:gravity="end">
+
+ <Button
+ android:id="@+id/banner_negative_btn"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ style="@style/Banner.ButtonText.SettingsLib"/>
+
+ <Button
+ android:id="@+id/banner_positive_btn"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ style="@style/Banner.ButtonText.SettingsLib"/>
+ </LinearLayout>
+ </com.android.settingslib.widget.BannerMessageView>
+</LinearLayout> \ No newline at end of file
diff --git a/packages/SettingsLib/BannerMessagePreference/res/layout-v35/settingslib_expressive_banner_message.xml b/packages/SettingsLib/BannerMessagePreference/res/layout-v35/settingslib_expressive_banner_message.xml
new file mode 100644
index 000000000000..b10ef6e77ec7
--- /dev/null
+++ b/packages/SettingsLib/BannerMessagePreference/res/layout-v35/settingslib_expressive_banner_message.xml
@@ -0,0 +1,108 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2025 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+ android:paddingStart="?android:attr/listPreferredItemPaddingStart">
+
+ <com.android.settingslib.widget.BannerMessageView
+ android:id="@+id/banner_background"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ style="@style/Banner.Preference.SettingsLib.Expressive">
+
+ <RelativeLayout
+ android:id="@+id/top_row"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal">
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_alignParentStart="true"
+ android:layout_marginEnd="@dimen/settingslib_expressive_space_medium4"
+ android:orientation="vertical">
+ <TextView
+ android:id="@+id/banner_header"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ style="@style/Banner.Header.SettingsLib.Expressive"/>
+
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content">
+
+ <ImageView
+ android:id="@+id/banner_icon"
+ android:layout_width="@dimen/settingslib_expressive_space_small3"
+ android:layout_height="@dimen/settingslib_expressive_space_small3"
+ android:layout_gravity="center_vertical"
+ android:importantForAccessibility="no"
+ android:scaleType="fitCenter" />
+
+ <TextView
+ android:id="@+id/banner_title"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ style="@style/Banner.Title.SettingsLib.Expressive" />
+ </LinearLayout>
+
+ <TextView
+ android:id="@+id/banner_subtitle"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ style="@style/Banner.Subtitle.SettingsLib.Expressive"/>
+ </LinearLayout>
+
+ <ImageButton
+ android:id="@+id/banner_dismiss_btn"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ style="@style/Banner.Dismiss.SettingsLib.Expressive" />
+ </RelativeLayout>
+
+ <TextView
+ android:id="@+id/banner_summary"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ style="@style/Banner.Summary.SettingsLib.Expressive"/>
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:id="@+id/banner_buttons_frame"
+ android:paddingTop="@dimen/settingslib_expressive_space_extrasmall6"
+ android:orientation="horizontal">
+
+ <com.google.android.material.button.MaterialButton
+ android:id="@+id/banner_negative_btn"
+ android:layout_weight="1"
+ style="@style/Banner.NegativeButton.SettingsLib.Expressive"/>
+ <Space
+ android:layout_width="@dimen/settingslib_expressive_space_extrasmall4"
+ android:layout_height="@dimen/settingslib_expressive_space_small1"/>
+ <com.google.android.material.button.MaterialButton
+ android:id="@+id/banner_positive_btn"
+ android:layout_weight="1"
+ style="@style/Banner.PositiveButton.SettingsLib.Expressive"/>
+ </LinearLayout>
+ </com.android.settingslib.widget.BannerMessageView>
+</LinearLayout> \ No newline at end of file
diff --git a/packages/SettingsLib/BannerMessagePreference/res/layout/settingslib_banner_message_preference_group.xml b/packages/SettingsLib/BannerMessagePreference/res/layout/settingslib_banner_message_preference_group.xml
new file mode 100644
index 000000000000..c74e39187562
--- /dev/null
+++ b/packages/SettingsLib/BannerMessagePreference/res/layout/settingslib_banner_message_preference_group.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2025 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:baselineAligned="false"
+ android:id="@+id/banner_group_layout"
+ android:importantForAccessibility="no"
+ android:filterTouchesWhenObscured="false"
+ android:orientation="horizontal">
+</LinearLayout> \ No newline at end of file
diff --git a/packages/SettingsLib/BannerMessagePreference/res/values-v31/styles.xml b/packages/SettingsLib/BannerMessagePreference/res/values-v31/styles.xml
index fede44feb090..5909f8e0f85e 100644
--- a/packages/SettingsLib/BannerMessagePreference/res/values-v31/styles.xml
+++ b/packages/SettingsLib/BannerMessagePreference/res/values-v31/styles.xml
@@ -24,9 +24,7 @@
<item name="android:paddingTop">20dp</item>
<item name="android:paddingBottom">8dp</item>
<item name="android:layout_marginTop">8dp</item>
- <item name="android:layout_marginStart">16dp</item>
<item name="android:layout_marginBottom">8dp</item>
- <item name="android:layout_marginEnd">16dp</item>
<item name="android:background">@drawable/settingslib_card_background</item>
</style>
diff --git a/packages/SettingsLib/BannerMessagePreference/res/values-v35/styles_expressive.xml b/packages/SettingsLib/BannerMessagePreference/res/values-v35/styles_expressive.xml
new file mode 100644
index 000000000000..b864311bf9b7
--- /dev/null
+++ b/packages/SettingsLib/BannerMessagePreference/res/values-v35/styles_expressive.xml
@@ -0,0 +1,76 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2025 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources>
+
+ <style name="Banner.Preference.SettingsLib.Expressive">
+ <item name="android:padding">@dimen/settingslib_expressive_space_small1</item>
+ <item name="android:background">@drawable/settingslib_expressive_card_background</item>
+ </style>
+
+ <style name="Banner.Header.SettingsLib.Expressive"
+ parent="">
+ <item name="android:textAlignment">viewStart</item>
+ <item name="android:paddingBottom">@dimen/settingslib_expressive_space_extrasmall4</item>
+ <item name="android:textAppearance">@style/TextAppearance.SettingsLib.LabelMedium</item>
+ <item name="android:textColor">?android:attr/textColorPrimary</item>
+ </style>
+
+ <style name="Banner.Title.SettingsLib.Expressive"
+ parent="">
+ <item name="android:layout_gravity">start</item>
+ <item name="android:layout_marginLeft">@dimen/settingslib_expressive_space_extrasmall4</item>
+ <item name="android:textAlignment">viewStart</item>
+ <item name="android:textAppearance">@style/TextAppearance.SettingsLib.TitleLarge.Emphasized</item>
+ <item name="android:textColor">?android:attr/textColorPrimary</item>
+ </style>
+
+ <style name="Banner.Subtitle.SettingsLib.Expressive"
+ parent="">
+ <item name="android:layout_gravity">start</item>
+ <item name="android:textAlignment">viewStart</item>
+ <item name="android:paddingTop">@dimen/settingslib_expressive_space_extrasmall4</item>
+ <item name="android:textAppearance">@style/TextAppearance.SettingsLib.BodyMedium</item>
+ <item name="android:textColor">?android:attr/textColorPrimary</item>
+ </style>
+
+ <style name="Banner.Summary.SettingsLib.Expressive"
+ parent="">
+ <item name="android:layout_gravity">start</item>
+ <item name="android:textAlignment">viewStart</item>
+ <item name="android:paddingTop">@dimen/settingslib_expressive_space_extrasmall6</item>
+ <item name="android:textAppearance">@style/TextAppearance.SettingsLib.BodyMedium</item>
+ <item name="android:textColor">?android:attr/textColorPrimary</item>
+ </style>
+
+ <style name="Banner.Dismiss.SettingsLib.Expressive">
+ <item name="android:src">@drawable/settingslib_expressive_icon_cross</item>
+ <item name="android:layout_alignParentEnd">true</item>
+ <item name="android:contentDescription">@string/accessibility_banner_message_dismiss</item>
+ </style>
+
+ <style name="Banner.PositiveButton.SettingsLib.Expressive"
+ parent="@style/SettingsLibButtonStyle.Expressive.Filled.Extra">
+ <item name="android:textColor">?android:attr/textColorPrimaryInverse</item>
+ <item name="materialSizeOverlay">@style/SizeOverlay.Material3Expressive.Button.Small</item>
+ </style>
+
+ <style name="Banner.NegativeButton.SettingsLib.Expressive"
+ parent="@style/SettingsLibButtonStyle.Expressive.Outline.Extra">
+ <item name="materialSizeOverlay">@style/SizeOverlay.Material3Expressive.Button.Small</item>
+ </style>
+</resources>
diff --git a/packages/SettingsLib/BannerMessagePreference/res/values/attrs.xml b/packages/SettingsLib/BannerMessagePreference/res/values/attrs.xml
index 96634a51de56..86d5f476b0f2 100644
--- a/packages/SettingsLib/BannerMessagePreference/res/values/attrs.xml
+++ b/packages/SettingsLib/BannerMessagePreference/res/values/attrs.xml
@@ -21,7 +21,18 @@
<enum name="high" value="0"/>
<enum name="medium" value="1"/>
<enum name="low" value="2"/>
+ <enum name="normal" value="3"/>
</attr>
<attr format="string" name="subtitle" />
+ <attr format="string" name="bannerHeader" />
+ <attr format="integer" name="buttonOrientation" />
+ </declare-styleable>
+
+ <declare-styleable name="BannerMessagePreferenceGroup">
+ <attr format="string" name="expandKey" />
+ <attr format="string" name="expandTitle" />
+ <attr format="string" name="collapseKey" />
+ <attr format="string" name="collapseTitle" />
+ <attr format="reference" name="collapseIcon" />
</declare-styleable>
</resources> \ No newline at end of file
diff --git a/packages/SettingsLib/BannerMessagePreference/res/values/colors.xml b/packages/SettingsLib/BannerMessagePreference/res/values/colors.xml
index 53d72d1eeff7..891def1c610a 100644
--- a/packages/SettingsLib/BannerMessagePreference/res/values/colors.xml
+++ b/packages/SettingsLib/BannerMessagePreference/res/values/colors.xml
@@ -19,7 +19,9 @@
<color name="banner_background_attention_high">#FFDAD5</color> <!-- red card background -->
<color name="banner_background_attention_medium">#F0E3A8</color> <!-- yellow card background -->
<color name="banner_background_attention_low">#CFEBC0</color> <!-- green card background -->
+ <color name="banner_background_attention_normal">@color/settingslib_materialColorSurfaceBright</color> <!-- normal card background -->
<color name="banner_accent_attention_high">#BB3322</color> <!-- red accent color -->
<color name="banner_accent_attention_medium">#895900</color> <!-- yellow accent color -->
<color name="banner_accent_attention_low">#1D7233</color> <!-- green accent color -->
+ <color name="banner_accent_attention_normal">@color/settingslib_materialColorPrimary</color> <!-- normal accent color -->
</resources>
diff --git a/packages/SettingsLib/BannerMessagePreference/src/com/android/settingslib/widget/BannerMessagePreference.java b/packages/SettingsLib/BannerMessagePreference/src/com/android/settingslib/widget/BannerMessagePreference.java
index 10769ecfbe42..60a9ebd6f98b 100644
--- a/packages/SettingsLib/BannerMessagePreference/src/com/android/settingslib/widget/BannerMessagePreference.java
+++ b/packages/SettingsLib/BannerMessagePreference/src/com/android/settingslib/widget/BannerMessagePreference.java
@@ -17,6 +17,7 @@
package com.android.settingslib.widget;
import android.content.Context;
+import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.PorterDuff;
@@ -29,6 +30,7 @@ import android.view.View;
import android.widget.Button;
import android.widget.ImageButton;
import android.widget.ImageView;
+import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.annotation.ColorInt;
@@ -39,6 +41,8 @@ import androidx.preference.Preference;
import androidx.preference.PreferenceViewHolder;
import com.android.settingslib.widget.preference.banner.R;
+
+import com.google.android.material.button.MaterialButton;
/**
* Banner message is a banner displaying important information (permission request, page error etc),
* and provide actions for user to address. It requires a user action to be dismissed.
@@ -46,22 +50,36 @@ import com.android.settingslib.widget.preference.banner.R;
public class BannerMessagePreference extends Preference implements GroupSectionDividerMixin {
public enum AttentionLevel {
- HIGH(0, R.color.banner_background_attention_high, R.color.banner_accent_attention_high),
+ HIGH(0,
+ R.color.banner_background_attention_high,
+ R.color.banner_accent_attention_high,
+ R.color.settingslib_banner_button_background_high),
MEDIUM(1,
- R.color.banner_background_attention_medium,
- R.color.banner_accent_attention_medium),
- LOW(2, R.color.banner_background_attention_low, R.color.banner_accent_attention_low);
+ R.color.banner_background_attention_medium,
+ R.color.banner_accent_attention_medium,
+ R.color.settingslib_banner_button_background_medium),
+ LOW(2,
+ R.color.banner_background_attention_low,
+ R.color.banner_accent_attention_low,
+ R.color.settingslib_banner_button_background_low),
+ NORMAL(3,
+ R.color.banner_background_attention_normal,
+ R.color.banner_accent_attention_normal,
+ R.color.settingslib_banner_button_background_normal);
// Corresponds to the enum valye of R.attr.attentionLevel
private final int mAttrValue;
@ColorRes private final int mBackgroundColorResId;
@ColorRes private final int mAccentColorResId;
+ @ColorRes private final int mButtonBackgroundColorResId;
AttentionLevel(int attrValue, @ColorRes int backgroundColorResId,
- @ColorRes int accentColorResId) {
+ @ColorRes int accentColorResId,
+ @ColorRes int buttonBackgroundColorResId) {
mAttrValue = attrValue;
mBackgroundColorResId = backgroundColorResId;
mAccentColorResId = accentColorResId;
+ mButtonBackgroundColorResId = buttonBackgroundColorResId;
}
static AttentionLevel fromAttr(int attrValue) {
@@ -80,6 +98,10 @@ public class BannerMessagePreference extends Preference implements GroupSectionD
public @ColorRes int getBackgroundColorResId() {
return mBackgroundColorResId;
}
+
+ public @ColorRes int getButtonBackgroundColorResId() {
+ return mButtonBackgroundColorResId;
+ }
}
private static final String TAG = "BannerPreference";
@@ -95,6 +117,8 @@ public class BannerMessagePreference extends Preference implements GroupSectionD
// Default attention level is High.
private AttentionLevel mAttentionLevel = AttentionLevel.HIGH;
private String mSubtitle;
+ private String mHeader;
+ private int mButtonOrientation;
public BannerMessagePreference(Context context) {
super(context);
@@ -119,7 +143,10 @@ public class BannerMessagePreference extends Preference implements GroupSectionD
private void init(Context context, AttributeSet attrs) {
setSelectable(false);
- setLayoutResource(R.layout.settingslib_banner_message);
+ int resId = SettingsThemeHelper.isExpressiveTheme(context)
+ ? R.layout.settingslib_expressive_banner_message
+ : R.layout.settingslib_banner_message;
+ setLayoutResource(resId);
if (IS_AT_LEAST_S) {
if (attrs != null) {
@@ -130,6 +157,9 @@ public class BannerMessagePreference extends Preference implements GroupSectionD
a.getInt(R.styleable.BannerMessagePreference_attentionLevel, 0);
mAttentionLevel = AttentionLevel.fromAttr(mAttentionLevelValue);
mSubtitle = a.getString(R.styleable.BannerMessagePreference_subtitle);
+ mHeader = a.getString(R.styleable.BannerMessagePreference_bannerHeader);
+ mButtonOrientation = a.getInt(R.styleable.BannerMessagePreference_buttonOrientation,
+ LinearLayout.HORIZONTAL);
a.recycle();
}
}
@@ -142,11 +172,16 @@ public class BannerMessagePreference extends Preference implements GroupSectionD
final TextView titleView = (TextView) holder.findViewById(R.id.banner_title);
CharSequence title = getTitle();
- titleView.setText(title);
- titleView.setVisibility(title == null ? View.GONE : View.VISIBLE);
+ if (titleView != null) {
+ titleView.setText(title);
+ titleView.setVisibility(title == null ? View.GONE : View.VISIBLE);
+ }
final TextView summaryView = (TextView) holder.findViewById(R.id.banner_summary);
- summaryView.setText(getSummary());
+ if (summaryView != null) {
+ summaryView.setText(getSummary());
+ summaryView.setVisibility(TextUtils.isEmpty(getSummary()) ? View.GONE : View.VISIBLE);
+ }
mPositiveButtonInfo.mButton = (Button) holder.findViewById(R.id.banner_positive_btn);
mNegativeButtonInfo.mButton = (Button) holder.findViewById(R.id.banner_negative_btn);
@@ -162,8 +197,11 @@ public class BannerMessagePreference extends Preference implements GroupSectionD
icon == null
? getContext().getDrawable(R.drawable.ic_warning)
: icon);
- iconView.setColorFilter(
- new PorterDuffColorFilter(accentColor, PorterDuff.Mode.SRC_IN));
+ if (mAttentionLevel != AttentionLevel.NORMAL
+ && !SettingsThemeHelper.isExpressiveTheme(context)) {
+ iconView.setColorFilter(
+ new PorterDuffColorFilter(accentColor, PorterDuff.Mode.SRC_IN));
+ }
}
if (IS_AT_LEAST_S) {
@@ -171,12 +209,25 @@ public class BannerMessagePreference extends Preference implements GroupSectionD
context.getResources().getColor(
mAttentionLevel.getBackgroundColorResId(), theme);
+ @ColorInt final int btnBackgroundColor =
+ context.getResources().getColor(mAttentionLevel.getButtonBackgroundColorResId(),
+ theme);
+ ColorStateList strokeColor = context.getResources().getColorStateList(
+ mAttentionLevel.getButtonBackgroundColorResId(), theme);
+
holder.setDividerAllowedAbove(false);
holder.setDividerAllowedBelow(false);
- holder.itemView.getBackground().setTint(backgroundColor);
+ View backgroundView = holder.findViewById(R.id.banner_background);
+ if (backgroundView != null && !SettingsThemeHelper.isExpressiveTheme(context)) {
+ backgroundView.getBackground().setTint(backgroundColor);
+ }
mPositiveButtonInfo.mColor = accentColor;
mNegativeButtonInfo.mColor = accentColor;
+ if (mAttentionLevel != AttentionLevel.NORMAL) {
+ mPositiveButtonInfo.mBackgroundColor = btnBackgroundColor;
+ mNegativeButtonInfo.mStrokeColor = strokeColor;
+ }
mDismissButtonInfo.mButton = (ImageButton) holder.findViewById(R.id.banner_dismiss_btn);
mDismissButtonInfo.setUpButton();
@@ -185,6 +236,13 @@ public class BannerMessagePreference extends Preference implements GroupSectionD
subtitleView.setText(mSubtitle);
subtitleView.setVisibility(mSubtitle == null ? View.GONE : View.VISIBLE);
+ TextView headerView = (TextView) holder.findViewById(R.id.banner_header);
+ if (headerView != null) {
+ headerView.setText(mHeader);
+ headerView.setVisibility(TextUtils.isEmpty(mHeader) ? View.GONE : View.VISIBLE);
+ }
+
+
} else {
holder.setDividerAllowedAbove(true);
holder.setDividerAllowedBelow(true);
@@ -192,6 +250,24 @@ public class BannerMessagePreference extends Preference implements GroupSectionD
mPositiveButtonInfo.setUpButton();
mNegativeButtonInfo.setUpButton();
+ View buttonFrame = holder.findViewById(R.id.banner_buttons_frame);
+ if (buttonFrame != null) {
+ buttonFrame.setVisibility(
+ mPositiveButtonInfo.shouldBeVisible() || mNegativeButtonInfo.shouldBeVisible()
+ ? View.VISIBLE : View.GONE);
+
+ LinearLayout linearLayout = (LinearLayout) buttonFrame;
+ if (mButtonOrientation != linearLayout.getOrientation()) {
+ int childCount = linearLayout.getChildCount();
+ //reverse the order of the buttons
+ for (int i = childCount - 1; i >= 0; i--) {
+ View child = linearLayout.getChildAt(i);
+ linearLayout.removeViewAt(i);
+ linearLayout.addView(child);
+ }
+ linearLayout.setOrientation(mButtonOrientation);
+ }
+ }
}
/**
@@ -302,6 +378,18 @@ public class BannerMessagePreference extends Preference implements GroupSectionD
}
/**
+ * Sets button orientation.
+ */
+ @RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
+ public BannerMessagePreference setButtonOrientation(int orientation) {
+ if (mButtonOrientation != orientation) {
+ mButtonOrientation = orientation;
+ notifyChanged();
+ }
+ return this;
+ }
+
+ /**
* Sets the subtitle.
*/
@RequiresApi(Build.VERSION_CODES.S)
@@ -322,6 +410,26 @@ public class BannerMessagePreference extends Preference implements GroupSectionD
}
/**
+ * Sets the header.
+ */
+ @RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
+ public BannerMessagePreference setHeader(@StringRes int textResId) {
+ return setHeader(getContext().getString(textResId));
+ }
+
+ /**
+ * Sets the header.
+ */
+ @RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
+ public BannerMessagePreference setHeader(String header) {
+ if (!TextUtils.equals(header, mSubtitle)) {
+ mHeader = header;
+ notifyChanged();
+ }
+ return this;
+ }
+
+ /**
* Sets the attention level. This will update the color theme of the preference.
*/
public BannerMessagePreference setAttentionLevel(AttentionLevel attentionLevel) {
@@ -342,13 +450,29 @@ public class BannerMessagePreference extends Preference implements GroupSectionD
private View.OnClickListener mListener;
private boolean mIsVisible = true;
@ColorInt private int mColor;
+ @ColorInt private int mBackgroundColor;
+ private ColorStateList mStrokeColor;
void setUpButton() {
mButton.setText(mText);
mButton.setOnClickListener(mListener);
+ MaterialButton btn = null;
+ if (mButton instanceof MaterialButton) {
+ btn = (MaterialButton) mButton;
+ }
+
if (IS_AT_LEAST_S) {
- mButton.setTextColor(mColor);
+ if (btn != null && SettingsThemeHelper.isExpressiveTheme(btn.getContext())) {
+ if (mBackgroundColor != 0) {
+ btn.setBackgroundColor(mBackgroundColor);
+ }
+ if (mStrokeColor != null) {
+ btn.setStrokeColor(mStrokeColor);
+ }
+ } else {
+ mButton.setTextColor(mColor);
+ }
}
if (shouldBeVisible()) {
diff --git a/packages/SettingsLib/BannerMessagePreference/src/com/android/settingslib/widget/BannerMessagePreferenceGroup.kt b/packages/SettingsLib/BannerMessagePreference/src/com/android/settingslib/widget/BannerMessagePreferenceGroup.kt
new file mode 100644
index 000000000000..75455635fca1
--- /dev/null
+++ b/packages/SettingsLib/BannerMessagePreference/src/com/android/settingslib/widget/BannerMessagePreferenceGroup.kt
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.widget
+
+import android.content.Context
+import android.graphics.drawable.Drawable
+import android.util.AttributeSet
+import android.view.View
+import androidx.preference.Preference
+import androidx.preference.PreferenceGroup
+import androidx.preference.PreferenceViewHolder
+
+import com.android.settingslib.widget.preference.banner.R
+
+/**
+ * Custom PreferenceGroup that allows expanding and collapsing child preferences.
+ */
+class BannerMessagePreferenceGroup @JvmOverloads constructor(
+ context: Context,
+ attrs: AttributeSet? = null,
+ defStyleAttr: Int = 0,
+ defStyleRes: Int = 0
+) : PreferenceGroup(context, attrs, defStyleAttr, defStyleRes), GroupSectionDividerMixin {
+
+ private var isExpanded = false
+ private var expandPreference: NumberButtonPreference? = null
+ private var collapsePreference: SectionButtonPreference? = null
+ private val childPreferences = mutableListOf<BannerMessagePreference>()
+ private var expandKey: String? = null
+ private var expandTitle: String? = null
+ private var collapseKey: String? = null
+ private var collapseTitle: String? = null
+ private var collapseIcon: Drawable? = null
+ var expandContentDescription: Int = 0
+ set(value) {
+ field = value
+ expandPreference?.btnContentDescription = expandContentDescription
+ }
+
+ init {
+ isPersistent = false // This group doesn't store data
+ layoutResource = R.layout.settingslib_banner_message_preference_group
+
+ initAttributes(context, attrs, defStyleAttr)
+ }
+
+ override fun addPreference(preference: Preference): Boolean {
+ if (preference !is BannerMessagePreference) {
+ return false
+ }
+
+ if (childPreferences.size >= MAX_CHILDREN) {
+ return false
+ }
+
+ childPreferences.add(preference)
+ return super.addPreference(preference)
+ }
+
+ override fun removePreference(preference: Preference): Boolean {
+ if (preference !is BannerMessagePreference) {
+ return false
+ }
+ childPreferences.remove(preference)
+ return super.removePreference(preference)
+ }
+
+ override fun onBindViewHolder(holder: PreferenceViewHolder) {
+ super.onBindViewHolder(holder)
+ if (childPreferences.size >= MAX_CHILDREN - 1) {
+ if (expandPreference == null) {
+ expandPreference = NumberButtonPreference(context).apply {
+ key = expandKey
+ title = expandTitle
+ count = childPreferences.size - 1
+ btnContentDescription = expandContentDescription
+ clickListener = View.OnClickListener {
+ toggleExpansion()
+ }
+ }
+ super.addPreference(expandPreference!!)
+ }
+ if (collapsePreference == null) {
+ collapsePreference = SectionButtonPreference(context)
+ .apply {
+ key = collapseKey
+ title = collapseTitle
+ icon = collapseIcon
+ setOnClickListener {
+ toggleExpansion()
+ }
+ }
+ super.addPreference(collapsePreference!!)
+ }
+ }
+ updateExpandCollapsePreference()
+ updateChildrenVisibility()
+ }
+
+ private fun updateExpandCollapsePreference() {
+ expandPreference?.isVisible = !isExpanded
+ collapsePreference?.isVisible = isExpanded
+ }
+
+ private fun updateChildrenVisibility() {
+ for (i in 1 until childPreferences.size) {
+ val child = childPreferences[i]
+ child.isVisible = isExpanded
+ }
+ }
+
+ private fun toggleExpansion() {
+ isExpanded = !isExpanded
+ updateExpandCollapsePreference()
+ updateChildrenVisibility()
+ }
+
+ private fun initAttributes(context: Context, attrs: AttributeSet?, defStyleAttr: Int) {
+ context.obtainStyledAttributes(
+ attrs,
+ R.styleable.BannerMessagePreferenceGroup, defStyleAttr, 0
+ ).apply {
+ expandKey = getString(R.styleable.BannerMessagePreferenceGroup_expandKey)
+ expandTitle = getString(R.styleable.BannerMessagePreferenceGroup_expandTitle)
+ collapseKey = getString(R.styleable.BannerMessagePreferenceGroup_collapseKey)
+ collapseTitle = getString(R.styleable.BannerMessagePreferenceGroup_collapseTitle)
+ collapseIcon = getDrawable(R.styleable.BannerMessagePreferenceGroup_collapseIcon)
+ recycle()
+ }
+ }
+
+ companion object {
+ private const val MAX_CHILDREN = 3
+ }
+} \ No newline at end of file
diff --git a/packages/SettingsLib/ButtonPreference/Android.bp b/packages/SettingsLib/ButtonPreference/Android.bp
index 08dd27fd25ce..a377f312ffbf 100644
--- a/packages/SettingsLib/ButtonPreference/Android.bp
+++ b/packages/SettingsLib/ButtonPreference/Android.bp
@@ -14,12 +14,15 @@ android_library {
"SettingsLintDefaults",
],
- srcs: ["src/**/*.java"],
+ srcs: [
+ "src/**/*.java",
+ "src/**/*.kt",
+ ],
resource_dirs: ["res"],
static_libs: [
- "androidx.preference_preference",
"SettingsLibSettingsTheme",
+ "androidx.preference_preference",
],
sdk_version: "system_current",
diff --git a/packages/SettingsLib/ButtonPreference/res/color/settingslib_section_button_background.xml b/packages/SettingsLib/ButtonPreference/res/color/settingslib_section_button_background.xml
new file mode 100644
index 000000000000..0972b625d4fd
--- /dev/null
+++ b/packages/SettingsLib/ButtonPreference/res/color/settingslib_section_button_background.xml
@@ -0,0 +1,22 @@
+<?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">
+ <item android:state_enabled="false"
+ android:alpha="@dimen/material_emphasis_disabled_background" android:color="@color/settingslib_materialColorSurfaceBright"/>
+ <item android:color="@color/settingslib_materialColorSurfaceBright" />
+</selector> \ No newline at end of file
diff --git a/packages/SettingsLib/ButtonPreference/res/drawable/settingslib_number_button_background.xml b/packages/SettingsLib/ButtonPreference/res/drawable/settingslib_number_button_background.xml
new file mode 100644
index 000000000000..9bf5c430e8e7
--- /dev/null
+++ b/packages/SettingsLib/ButtonPreference/res/drawable/settingslib_number_button_background.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2025 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+ <solid android:color="@color/settingslib_materialColorSurfaceBright" />
+ <corners android:radius="@dimen/settingslib_expressive_radius_extralarge2"/>
+</shape> \ No newline at end of file
diff --git a/packages/SettingsLib/ButtonPreference/res/drawable/settingslib_number_count_background.xml b/packages/SettingsLib/ButtonPreference/res/drawable/settingslib_number_count_background.xml
new file mode 100644
index 000000000000..b993811b34f5
--- /dev/null
+++ b/packages/SettingsLib/ButtonPreference/res/drawable/settingslib_number_count_background.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2025 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+ <solid android:color="@color/settingslib_materialColorSecondaryContainer" />
+ <corners android:radius="100dp"/>
+</shape> \ No newline at end of file
diff --git a/packages/SettingsLib/ButtonPreference/res/layout/settingslib_number_button.xml b/packages/SettingsLib/ButtonPreference/res/layout/settingslib_number_button.xml
new file mode 100644
index 000000000000..fa13b4125065
--- /dev/null
+++ b/packages/SettingsLib/ButtonPreference/res/layout/settingslib_number_button.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2025 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+ android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+ android:paddingVertical="@dimen/settingslib_expressive_space_extrasmall4">
+
+ <LinearLayout
+ android:id="@+id/settingslib_number_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:paddingVertical="@dimen/settingslib_expressive_space_small1"
+ android:paddingHorizontal="@dimen/settingslib_expressive_space_small4"
+ android:background="@drawable/settingslib_number_button_background">
+ <TextView
+ android:id="@+id/settingslib_number_count"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ style="@style/SettingsLibNumberButtonStyle.Number"/>
+ <TextView
+ android:id="@+id/settingslib_number_title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ style="@style/SettingsLibNumberButtonStyle.Title"/>
+ </LinearLayout>
+
+</LinearLayout> \ No newline at end of file
diff --git a/packages/SettingsLib/ButtonPreference/res/layout/settingslib_section_button.xml b/packages/SettingsLib/ButtonPreference/res/layout/settingslib_section_button.xml
new file mode 100644
index 000000000000..e7fb572d06dc
--- /dev/null
+++ b/packages/SettingsLib/ButtonPreference/res/layout/settingslib_section_button.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2025 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+ android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+ android:paddingVertical="@dimen/settingslib_expressive_space_extrasmall4">
+
+ <com.google.android.material.button.MaterialButton
+ android:id="@+id/settingslib_section_button"
+ style="@style/SettingsLibSectionButtonStyle.Expressive" />
+
+</LinearLayout> \ No newline at end of file
diff --git a/packages/SettingsLib/ButtonPreference/res/values/styles.xml b/packages/SettingsLib/ButtonPreference/res/values/styles.xml
index 3963732f7ae4..5208e2004a5f 100644
--- a/packages/SettingsLib/ButtonPreference/res/values/styles.xml
+++ b/packages/SettingsLib/ButtonPreference/res/values/styles.xml
@@ -30,4 +30,33 @@
<item name="android:textColor">@color/settingslib_btn_colored_text_material</item>
<item name="android:background">@drawable/settingslib_btn_colored_material</item>
</style>
+
+ <style name="SettingsLibSectionButtonStyle.Expressive"
+ parent="@style/SettingsLibButtonStyle.Expressive.Filled.Large">
+ <item name="android:textColor">?android:attr/textColorPrimary</item>
+ <item name="backgroundTint">@color/settingslib_section_button_background</item>
+ <item name="iconTint">?android:attr/textColorPrimary</item>
+ </style>
+
+ <style name="SettingsLibNumberButtonStyle.Number"
+ parent="">
+ <item name="android:minWidth">@dimen/settingslib_expressive_space_small4</item>
+ <item name="android:minHeight">@dimen/settingslib_expressive_space_small4</item>
+ <item name="android:gravity">center</item>
+ <item name="android:background">@drawable/settingslib_number_count_background</item>
+ <item name="android:paddingStart">@dimen/settingslib_expressive_radius_extrasmall2</item>
+ <item name="android:paddingEnd">@dimen/settingslib_expressive_radius_extrasmall2</item>
+ <item name="android:layout_marginEnd">@dimen/settingslib_expressive_space_extrasmall4</item>
+ <item name="android:textAppearance">@style/TextAppearance.SettingsLib.TitleMedium</item>
+ <item name="android:textColor">@color/settingslib_materialColorOnSecondaryContainer</item>
+ <item name="android:importantForAccessibility">no</item>
+ </style>
+
+ <style name="SettingsLibNumberButtonStyle.Title"
+ parent="">
+ <item name="android:gravity">center</item>
+ <item name="android:textAppearance">@style/TextAppearance.SettingsLib.LabelLarge</item>
+ <item name="android:textColor">@color/settingslib_materialColorOnSurface</item>
+ <item name="android:importantForAccessibility">no</item>
+ </style>
</resources>
diff --git a/packages/SettingsLib/ButtonPreference/src/com/android/settingslib/widget/NumberButtonPreference.kt b/packages/SettingsLib/ButtonPreference/src/com/android/settingslib/widget/NumberButtonPreference.kt
new file mode 100644
index 000000000000..a1772d5acdf9
--- /dev/null
+++ b/packages/SettingsLib/ButtonPreference/src/com/android/settingslib/widget/NumberButtonPreference.kt
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.widget
+
+import android.content.Context
+import android.util.AttributeSet
+import android.view.View
+import android.widget.TextView
+import androidx.preference.Preference
+import androidx.preference.PreferenceViewHolder
+
+import com.android.settingslib.widget.preference.button.R
+
+class NumberButtonPreference @JvmOverloads constructor(
+ context: Context,
+ attrs: AttributeSet? = null,
+ defStyleAttr: Int = 0,
+ defStyleRes: Int = 0
+) : Preference(context, attrs, defStyleAttr, defStyleRes), GroupSectionDividerMixin {
+
+ var clickListener: View.OnClickListener? = null
+
+ var count: Int = 0
+ set(value) {
+ field = value
+ notifyChanged()
+ }
+
+ var btnContentDescription: Int = 0
+ set(value) {
+ field = value
+ notifyChanged()
+ }
+
+ init {
+ isPersistent = false // This preference doesn't store data
+ order = Int.MAX_VALUE
+ layoutResource = R.layout.settingslib_number_button
+ }
+
+ override fun onBindViewHolder(holder: PreferenceViewHolder) {
+ super.onBindViewHolder(holder)
+ holder.isDividerAllowedAbove = false
+ holder.isDividerAllowedBelow = false
+
+ holder.findViewById(R.id.settingslib_number_button)?.apply {
+ setOnClickListener(clickListener)
+ if (btnContentDescription != 0) {
+ setContentDescription(context.getString(btnContentDescription, count))
+ }
+ }
+ (holder.findViewById(R.id.settingslib_number_title) as? TextView)?.text = title
+
+ (holder.findViewById(R.id.settingslib_number_count) as? TextView)?.text = "$count"
+ }
+} \ No newline at end of file
diff --git a/packages/SettingsLib/ButtonPreference/src/com/android/settingslib/widget/SectionButtonPreference.kt b/packages/SettingsLib/ButtonPreference/src/com/android/settingslib/widget/SectionButtonPreference.kt
new file mode 100644
index 000000000000..b374dea48d21
--- /dev/null
+++ b/packages/SettingsLib/ButtonPreference/src/com/android/settingslib/widget/SectionButtonPreference.kt
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.widget
+
+import android.content.Context
+import android.util.AttributeSet
+import android.view.View
+import androidx.preference.Preference
+import androidx.preference.PreferenceViewHolder
+import com.android.settingslib.widget.preference.button.R
+import com.google.android.material.button.MaterialButton
+
+/**
+ * A Preference that displays a button with an optional icon.
+ */
+class SectionButtonPreference @JvmOverloads constructor(
+ context: Context,
+ attrs: AttributeSet? = null,
+ defStyleAttr: Int = 0,
+ defStyleRes: Int = 0
+) : Preference(context, attrs, defStyleAttr, defStyleRes), GroupSectionDividerMixin {
+
+ private var clickListener: ((View) -> Unit)? = null
+ set(value) {
+ field = value
+ notifyChanged()
+ }
+ private var button: MaterialButton? = null
+ init {
+ isPersistent = false // This preference doesn't store data
+ order = Int.MAX_VALUE
+ layoutResource = R.layout.settingslib_section_button
+ }
+
+ override fun onBindViewHolder(holder: PreferenceViewHolder) {
+ super.onBindViewHolder(holder)
+ holder.isDividerAllowedAbove = false
+ holder.isDividerAllowedBelow = false
+
+ button = holder.findViewById(R.id.settingslib_section_button) as? MaterialButton
+ button?.apply{
+ text = title
+ isFocusable = isSelectable
+ isClickable = isSelectable
+ setOnClickListener { view -> clickListener?.let { it(view) } }
+ }
+ button?.isEnabled = isEnabled
+ button?.icon = icon
+ }
+
+ /**
+ * Set a listener for button click
+ */
+ fun setOnClickListener(listener: (View) -> Unit) {
+ clickListener = listener
+ }
+} \ No newline at end of file
diff --git a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGraphBuilder.kt b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGraphBuilder.kt
index 8b29b0044eea..ce66a360a99f 100644
--- a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGraphBuilder.kt
+++ b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGraphBuilder.kt
@@ -40,7 +40,6 @@ import com.android.settingslib.graph.proto.PreferenceProto
import com.android.settingslib.graph.proto.PreferenceProto.ActionTarget
import com.android.settingslib.graph.proto.PreferenceScreenProto
import com.android.settingslib.graph.proto.TextProto
-import com.android.settingslib.metadata.FloatPersistentPreference
import com.android.settingslib.metadata.PersistentPreference
import com.android.settingslib.metadata.PreferenceAvailabilityProvider
import com.android.settingslib.metadata.PreferenceHierarchy
@@ -410,13 +409,13 @@ fun PreferenceMetadata.toProto(
value = preferenceValueProto {
when (metadata) {
is RangeValue -> storage.getInt(metadata.key)?.let { intValue = it }
- is FloatPersistentPreference ->
- storage.getFloat(metadata.key)?.let { floatValue = it }
else -> {}
}
when (metadata.valueType) {
Boolean::class.javaObjectType ->
storage.getBoolean(metadata.key)?.let { booleanValue = it }
+ Float::class.javaObjectType ->
+ storage.getFloat(metadata.key)?.let { floatValue = it }
}
}
}
@@ -428,12 +427,12 @@ fun PreferenceMetadata.toProto(
max = metadata.getMaxValue(context)
step = metadata.getIncrementStep(context)
}
- is FloatPersistentPreference -> floatType = true
else -> {}
}
if (metadata is PersistentPreference<*>) {
when (metadata.valueType) {
Boolean::class.javaObjectType -> booleanType = true
+ Float::class.javaObjectType -> floatType = true
}
}
}
diff --git a/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PersistentPreference.kt b/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PersistentPreference.kt
index 4cc65815a78a..be705b583974 100644
--- a/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PersistentPreference.kt
+++ b/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PersistentPreference.kt
@@ -76,7 +76,7 @@ annotation class SensitivityLevel {
}
/** Preference interface that has a value persisted in datastore. */
-interface PersistentPreference<T> {
+interface PersistentPreference<T> : PreferenceMetadata {
/**
* The value type the preference is associated with.
@@ -93,7 +93,7 @@ interface PersistentPreference<T> {
* [PreferenceScreenRegistry.getKeyValueStore].
*/
fun storage(context: Context): KeyValueStore =
- PreferenceScreenRegistry.getKeyValueStore(context, this as PreferenceMetadata)!!
+ PreferenceScreenRegistry.getKeyValueStore(context, this)!!
/** Returns the required permissions to read preference value. */
fun getReadPermissions(context: Context): Permissions? = null
@@ -111,7 +111,7 @@ interface PersistentPreference<T> {
context,
callingPid,
callingUid,
- this as PreferenceMetadata,
+ this,
)
/** Returns the required permissions to write preference value. */
@@ -136,7 +136,7 @@ interface PersistentPreference<T> {
value,
callingPid,
callingUid,
- this as PreferenceMetadata,
+ this,
)
/** The sensitivity level of the preference. */
@@ -219,12 +219,3 @@ interface RangeValue : ValueDescriptor {
override fun isValidValue(context: Context, index: Int) =
index in getMinValue(context)..getMaxValue(context)
}
-
-/** A persistent preference that has a boolean value. */
-interface BooleanPreference : PersistentPreference<Boolean> {
- override val valueType: Class<Boolean>
- get() = Boolean::class.javaObjectType
-}
-
-/** A persistent preference that has a float value. */
-interface FloatPersistentPreference : PersistentPreference<Float>
diff --git a/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceTypes.kt b/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceTypes.kt
index b79a0c4f6381..fecf3e50800a 100644
--- a/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceTypes.kt
+++ b/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceTypes.kt
@@ -18,8 +18,17 @@ package com.android.settingslib.metadata
import androidx.annotation.StringRes
-/** Common base class for preferences that have two selectable states and save a boolean value. */
-interface TwoStatePreference : PreferenceMetadata, BooleanPreference
+/** A persistent preference that has a boolean value. */
+interface BooleanValuePreference : PersistentPreference<Boolean> {
+ override val valueType: Class<Boolean>
+ get() = Boolean::class.javaObjectType
+}
+
+/** A persistent preference that has a float value. */
+interface FloatValuePreference : PersistentPreference<Float> {
+ override val valueType: Class<Float>
+ get() = Float::class.javaObjectType
+}
/** A preference that provides a two-state toggleable option. */
open class SwitchPreference
@@ -28,9 +37,10 @@ constructor(
override val key: String,
@StringRes override val title: Int = 0,
@StringRes override val summary: Int = 0,
-) : TwoStatePreference
+) : BooleanValuePreference
/** A preference that provides a two-state toggleable option that can be used as a main switch. */
open class MainSwitchPreference
@JvmOverloads
-constructor(override val key: String, @StringRes override val title: Int = 0) : TwoStatePreference
+constructor(override val key: String, @StringRes override val title: Int = 0) :
+ BooleanValuePreference
diff --git a/packages/SettingsLib/OWNERS b/packages/SettingsLib/OWNERS
index e4bc7b4660e1..04df308e72a0 100644
--- a/packages/SettingsLib/OWNERS
+++ b/packages/SettingsLib/OWNERS
@@ -3,6 +3,7 @@ cantol@google.com
chiujason@google.com
cipson@google.com
dsandler@android.com
+dswliu@google.com
edgarwang@google.com
evanlaird@google.com
jiannan@google.com
diff --git a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBinding.kt b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBinding.kt
index 6b7be91c1903..c61c6a5c75fa 100644
--- a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBinding.kt
+++ b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBinding.kt
@@ -17,6 +17,7 @@
package com.android.settingslib.preference
import android.content.Context
+import androidx.annotation.CallSuper
import androidx.preference.DialogPreference
import androidx.preference.ListPreference
import androidx.preference.Preference
@@ -59,6 +60,7 @@ interface PreferenceBinding {
* @param preference preference widget created by [createWidget]
* @param metadata metadata to apply
*/
+ @CallSuper
fun bind(preference: Preference, metadata: PreferenceMetadata) {
metadata.apply {
preference.key = key
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 bd5d17cb2468..65fbe2b66e77 100644
--- a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBindings.kt
+++ b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBindings.kt
@@ -45,7 +45,7 @@ interface PreferenceScreenBinding : PreferenceBinding {
context.getString(screenTitle)
} else {
screenMetadata.getScreenTitle(context)
- ?: (this as? PreferenceTitleProvider)?.getTitle(context)
+ ?: (screenMetadata as? PreferenceTitleProvider)?.getTitle(context)
}
}
}
@@ -66,7 +66,7 @@ interface PreferenceCategoryBinding : PreferenceBinding {
}
/** A boolean value type preference associated with the abstract [TwoStatePreference]. */
-interface TwoStatePreferenceBinding : PreferenceBinding {
+interface BooleanValuePreferenceBinding : PreferenceBinding {
override fun bind(preference: Preference, metadata: PreferenceMetadata) {
super.bind(preference, metadata)
@@ -78,7 +78,7 @@ interface TwoStatePreferenceBinding : PreferenceBinding {
}
/** A boolean value type preference associated with [SwitchPreferenceCompat]. */
-interface SwitchPreferenceBinding : TwoStatePreferenceBinding {
+interface SwitchPreferenceBinding : BooleanValuePreferenceBinding {
override fun createWidget(context: Context): Preference = SwitchPreferenceCompat(context)
@@ -88,7 +88,7 @@ interface SwitchPreferenceBinding : TwoStatePreferenceBinding {
}
/** A boolean value type preference associated with [MainSwitchPreference]. */
-interface MainSwitchPreferenceBinding : TwoStatePreferenceBinding {
+interface MainSwitchPreferenceBinding : BooleanValuePreferenceBinding {
override fun createWidget(context: Context): Preference = MainSwitchPreference(context)
diff --git a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceFragment.kt b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceFragment.kt
index e237a6a4cf14..ffe181d0c350 100644
--- a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceFragment.kt
+++ b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceFragment.kt
@@ -21,6 +21,7 @@ import android.content.Intent
import android.os.Bundle
import android.util.Log
import androidx.annotation.XmlRes
+import androidx.lifecycle.Lifecycle
import androidx.preference.PreferenceScreen
import com.android.settingslib.metadata.EXTRA_BINDING_SCREEN_KEY
import com.android.settingslib.metadata.PreferenceScreenBindingKeyProvider
@@ -38,6 +39,11 @@ open class PreferenceFragment :
preferenceScreen = createPreferenceScreen()
}
+ override fun setPreferenceScreen(preferenceScreen: PreferenceScreen?) {
+ super.setPreferenceScreen(preferenceScreen)
+ updateActivityTitle()
+ }
+
fun createPreferenceScreen(): PreferenceScreen? =
createPreferenceScreen(PreferenceScreenFactory(this))
@@ -102,9 +108,19 @@ open class PreferenceFragment :
override fun onResume() {
super.onResume()
+ // Even when activity has several fragments with preference screen, this will keep activity
+ // title in sync when fragment manager pops back stack.
+ updateActivityTitle()
preferenceScreenBindingHelper?.onResume()
}
+ internal fun updateActivityTitle() {
+ if (!lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED)) return
+ val activity = activity ?: return
+ val title = preferenceScreen?.title ?: return
+ if (activity.title != title) activity.title = title
+ }
+
override fun onPause() {
preferenceScreenBindingHelper?.onPause()
super.onPause()
diff --git a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceScreenBindingHelper.kt b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceScreenBindingHelper.kt
index 7492c2d0aaf2..4a6a589cd3c9 100644
--- a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceScreenBindingHelper.kt
+++ b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceScreenBindingHelper.kt
@@ -52,7 +52,7 @@ import com.google.common.collect.ImmutableMultimap
*/
class PreferenceScreenBindingHelper(
context: Context,
- fragment: PreferenceFragment,
+ private val fragment: PreferenceFragment,
private val preferenceBindingFactory: PreferenceBindingFactory,
private val preferenceScreen: PreferenceScreen,
private val preferenceHierarchy: PreferenceHierarchy,
@@ -156,7 +156,9 @@ class PreferenceScreenBindingHelper(
// bind preference to update UI
preferenceScreen.findPreference<Preference>(key)?.let {
- preferences[key]?.let { node -> preferenceBindingFactory.bind(it, node) }
+ val node = preferences[key] ?: return@let
+ preferenceBindingFactory.bind(it, node)
+ if (it == preferenceScreen) fragment.updateActivityTitle()
}
// check reason to avoid potential infinite loop
diff --git a/packages/SettingsLib/SettingsTheme/Android.bp b/packages/SettingsLib/SettingsTheme/Android.bp
index 1661dfb2a86b..e5c009dcdded 100644
--- a/packages/SettingsLib/SettingsTheme/Android.bp
+++ b/packages/SettingsLib/SettingsTheme/Android.bp
@@ -16,6 +16,7 @@ android_library {
],
resource_dirs: ["res"],
static_libs: [
+ "aconfig_settingstheme_exported_flags_java_lib",
"androidx.preference_preference",
"com.google.android.material_material",
],
@@ -23,12 +24,12 @@ android_library {
min_sdk_version: "21",
apex_available: [
"//apex_available:platform",
+ "com.android.adservices",
"com.android.cellbroadcast",
"com.android.devicelock",
"com.android.extservices",
- "com.android.permission",
- "com.android.adservices",
"com.android.healthfitness",
"com.android.mediaprovider",
+ "com.android.permission",
],
}
diff --git a/packages/SettingsLib/SettingsTheme/aconfig/settingstheme.aconfig b/packages/SettingsLib/SettingsTheme/aconfig/settingstheme.aconfig
new file mode 100644
index 000000000000..83e732b63c15
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/aconfig/settingstheme.aconfig
@@ -0,0 +1,10 @@
+package: "com.android.settingslib.widget.theme.flags"
+container: "system"
+
+flag {
+ name: "is_expressive_design_enabled"
+ namespace: "android_settings"
+ description: "enable expressive design in Settings"
+ bug: "386013400"
+ is_exported: true
+} \ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_expressive_icon_chevron.xml b/packages/SettingsLib/SettingsTheme/res/drawable/settingslib_expressive_icon_chevron.xml
index 94476538a6a5..94476538a6a5 100644
--- a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_expressive_icon_chevron.xml
+++ b/packages/SettingsLib/SettingsTheme/res/drawable/settingslib_expressive_icon_chevron.xml
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_expressive_icon_collapse.xml b/packages/SettingsLib/SettingsTheme/res/drawable/settingslib_expressive_icon_collapse.xml
index 161ece73f21c..161ece73f21c 100644
--- a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_expressive_icon_collapse.xml
+++ b/packages/SettingsLib/SettingsTheme/res/drawable/settingslib_expressive_icon_collapse.xml
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable/settingslib_expressive_icon_cross.xml b/packages/SettingsLib/SettingsTheme/res/drawable/settingslib_expressive_icon_cross.xml
new file mode 100644
index 000000000000..3ba85a2a6c79
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/drawable/settingslib_expressive_icon_cross.xml
@@ -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.
+ -->
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+ <item>
+ <shape
+ android:shape="oval">
+ <size android:width="28dp" android:height="28dp"/>
+ <solid android:color="@color/settingslib_materialColorSurfaceContainerHigh"/>
+ </shape>
+ </item>
+ <item android:gravity="center">
+ <vector
+ android:width="16dp"
+ android:height="16dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:fillColor="@color/settingslib_materialColorOnSurface"
+ android:pathData="M19,6.41L17.59,5 12,10.59 6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 12,13.41 17.59,19 19,17.59 13.41,12 19,6.41z"/>
+ </vector>
+ </item>
+</layer-list>
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_expressive_icon_expand.xml b/packages/SettingsLib/SettingsTheme/res/drawable/settingslib_expressive_icon_expand.xml
index 1b5d5182d9b2..1b5d5182d9b2 100644
--- a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_expressive_icon_expand.xml
+++ b/packages/SettingsLib/SettingsTheme/res/drawable/settingslib_expressive_icon_expand.xml
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable/settingslib_expressive_icon_level_high.xml b/packages/SettingsLib/SettingsTheme/res/drawable/settingslib_expressive_icon_level_high.xml
new file mode 100644
index 000000000000..aa4155bc939c
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/drawable/settingslib_expressive_icon_level_high.xml
@@ -0,0 +1,41 @@
+<?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.
+ -->
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+ <item>
+ <vector
+ android:width="20dp"
+ android:height="20dp"
+ android:viewportWidth="20"
+ android:viewportHeight="20">
+ <path
+ android:pathData="M8.804,0.296C9.554,-0.099 10.446,-0.099 11.196,0.296L12.45,0.955C12.703,1.088 12.976,1.178 13.257,1.221L14.654,1.436C15.489,1.564 16.211,2.096 16.589,2.862L17.223,4.144C17.349,4.402 17.518,4.637 17.721,4.84L18.726,5.847C19.328,6.449 19.603,7.31 19.465,8.155L19.236,9.57C19.189,9.855 19.189,10.145 19.236,10.43L19.465,11.845C19.603,12.69 19.328,13.55 18.726,14.153L17.721,15.16C17.518,15.363 17.349,15.597 17.223,15.856L16.589,17.137C16.211,17.903 15.489,18.435 14.654,18.564L13.257,18.78C12.976,18.822 12.703,18.913 12.45,19.045L11.196,19.704C10.446,20.099 9.554,20.099 8.804,19.704L7.549,19.045C7.297,18.913 7.024,18.822 6.743,18.78L5.346,18.564C4.511,18.435 3.789,17.903 3.411,17.137L2.777,15.856C2.651,15.597 2.482,15.363 2.279,15.16L1.274,14.153C0.672,13.55 0.397,12.69 0.535,11.845L0.764,10.43C0.811,10.145 0.811,9.855 0.764,9.57L0.535,8.155C0.397,7.31 0.672,6.449 1.274,5.847L2.279,4.84C2.482,4.637 2.651,4.402 2.777,4.144L3.411,2.862C3.789,2.096 4.511,1.564 5.346,1.436L6.743,1.221C7.024,1.178 7.297,1.088 7.549,0.955L8.804,0.296Z"
+ android:fillColor="@color/settingslib_colorBackgroundLevel_high"/>
+ </vector>
+ </item>
+ <item android:gravity="center">
+ <vector
+ android:width="4dp"
+ android:height="12dp"
+ android:viewportWidth="4"
+ android:viewportHeight="12">
+ <path
+ android:pathData="M0.894,8.081V0.919H3.106V8.081H0.894ZM0.894,11.081V8.869H3.106V11.081H0.894Z"
+ android:fillColor="@color/settingslib_colorContentLevel_high"/>
+ </vector>
+ </item>
+</layer-list> \ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable/settingslib_expressive_icon_level_low.xml b/packages/SettingsLib/SettingsTheme/res/drawable/settingslib_expressive_icon_level_low.xml
new file mode 100644
index 000000000000..9caa09516de9
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/drawable/settingslib_expressive_icon_level_low.xml
@@ -0,0 +1,41 @@
+<?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.
+ -->
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+ <item>
+ <vector
+ android:width="20dp"
+ android:height="20dp"
+ android:viewportWidth="20"
+ android:viewportHeight="20">
+ <path
+ android:pathData="M8.804,0.296C9.554,-0.099 10.446,-0.099 11.196,0.296L12.45,0.955C12.703,1.088 12.976,1.178 13.257,1.221L14.654,1.436C15.489,1.564 16.211,2.096 16.589,2.862L17.223,4.144C17.349,4.402 17.518,4.637 17.721,4.84L18.726,5.847C19.328,6.449 19.603,7.31 19.465,8.155L19.236,9.57C19.189,9.855 19.189,10.145 19.236,10.43L19.465,11.845C19.603,12.69 19.328,13.55 18.726,14.153L17.721,15.16C17.518,15.363 17.349,15.597 17.223,15.856L16.589,17.137C16.211,17.903 15.489,18.435 14.654,18.564L13.257,18.78C12.976,18.822 12.703,18.913 12.45,19.045L11.196,19.704C10.446,20.099 9.554,20.099 8.804,19.704L7.549,19.045C7.297,18.913 7.024,18.822 6.743,18.78L5.346,18.564C4.511,18.435 3.789,17.903 3.411,17.137L2.777,15.856C2.651,15.597 2.482,15.363 2.279,15.16L1.274,14.153C0.672,13.55 0.397,12.69 0.535,11.845L0.764,10.43C0.811,10.145 0.811,9.855 0.764,9.57L0.535,8.155C0.397,7.31 0.672,6.449 1.274,5.847L2.279,4.84C2.482,4.637 2.651,4.402 2.777,4.144L3.411,2.862C3.789,2.096 4.511,1.564 5.346,1.436L6.743,1.221C7.024,1.178 7.297,1.088 7.549,0.955L8.804,0.296Z"
+ android:fillColor="@color/settingslib_colorBackgroundLevel_low"/>
+ </vector>
+ </item>
+ <item android:gravity="center">
+ <vector
+ android:width="10dp"
+ android:height="9dp"
+ android:viewportWidth="10"
+ android:viewportHeight="9">
+ <path
+ android:pathData="M3.5,8.975L0.069,5.544L1.644,3.969L3.5,5.825L8.356,0.969L9.931,2.544L3.5,8.975Z"
+ android:fillColor="@color/settingslib_colorContentLevel_low"/>
+ </vector>
+ </item>
+</layer-list> \ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable/settingslib_expressive_icon_level_medium.xml b/packages/SettingsLib/SettingsTheme/res/drawable/settingslib_expressive_icon_level_medium.xml
new file mode 100644
index 000000000000..cdcb98246d56
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/drawable/settingslib_expressive_icon_level_medium.xml
@@ -0,0 +1,41 @@
+<?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.
+ -->
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+ <item>
+ <vector
+ android:width="20dp"
+ android:height="20dp"
+ android:viewportWidth="20"
+ android:viewportHeight="20">
+ <path
+ android:pathData="M8.804,0.296C9.554,-0.099 10.446,-0.099 11.196,0.296L12.45,0.955C12.703,1.088 12.976,1.178 13.257,1.221L14.654,1.436C15.489,1.564 16.211,2.096 16.589,2.862L17.223,4.144C17.349,4.402 17.518,4.637 17.721,4.84L18.726,5.847C19.328,6.449 19.603,7.31 19.465,8.155L19.236,9.57C19.189,9.855 19.189,10.145 19.236,10.43L19.465,11.845C19.603,12.69 19.328,13.55 18.726,14.153L17.721,15.16C17.518,15.363 17.349,15.597 17.223,15.856L16.589,17.137C16.211,17.903 15.489,18.435 14.654,18.564L13.257,18.78C12.976,18.822 12.703,18.913 12.45,19.045L11.196,19.704C10.446,20.099 9.554,20.099 8.804,19.704L7.549,19.045C7.297,18.913 7.024,18.822 6.743,18.78L5.346,18.564C4.511,18.435 3.789,17.903 3.411,17.137L2.777,15.856C2.651,15.597 2.482,15.363 2.279,15.16L1.274,14.153C0.672,13.55 0.397,12.69 0.535,11.845L0.764,10.43C0.811,10.145 0.811,9.855 0.764,9.57L0.535,8.155C0.397,7.31 0.672,6.449 1.274,5.847L2.279,4.84C2.482,4.637 2.651,4.402 2.777,4.144L3.411,2.862C3.789,2.096 4.511,1.564 5.346,1.436L6.743,1.221C7.024,1.178 7.297,1.088 7.549,0.955L8.804,0.296Z"
+ android:fillColor="@color/settingslib_colorBackgroundLevel_medium"/>
+ </vector>
+ </item>
+ <item android:gravity="center">
+ <vector
+ android:width="4dp"
+ android:height="12dp"
+ android:viewportWidth="4"
+ android:viewportHeight="12">
+ <path
+ android:pathData="M0.894,8.081V0.919H3.106V8.081H0.894ZM0.894,11.081V8.869H3.106V11.081H0.894Z"
+ android:fillColor="@color/settingslib_colorContentLevel_medium"/>
+ </vector>
+ </item>
+</layer-list> \ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable/settingslib_expressive_icon_level_normal.xml b/packages/SettingsLib/SettingsTheme/res/drawable/settingslib_expressive_icon_level_normal.xml
new file mode 100644
index 000000000000..448d596f05ec
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/drawable/settingslib_expressive_icon_level_normal.xml
@@ -0,0 +1,41 @@
+<?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.
+ -->
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+ <item>
+ <vector
+ android:width="20dp"
+ android:height="20dp"
+ android:viewportWidth="20"
+ android:viewportHeight="20">
+ <path
+ android:pathData="M8.804,0.296C9.554,-0.099 10.446,-0.099 11.196,0.296L12.45,0.955C12.703,1.088 12.976,1.178 13.257,1.221L14.654,1.436C15.489,1.564 16.211,2.096 16.589,2.862L17.223,4.144C17.349,4.402 17.518,4.637 17.721,4.84L18.726,5.847C19.328,6.449 19.603,7.31 19.465,8.155L19.236,9.57C19.189,9.855 19.189,10.145 19.236,10.43L19.465,11.845C19.603,12.69 19.328,13.55 18.726,14.153L17.721,15.16C17.518,15.363 17.349,15.597 17.223,15.856L16.589,17.137C16.211,17.903 15.489,18.435 14.654,18.564L13.257,18.78C12.976,18.822 12.703,18.913 12.45,19.045L11.196,19.704C10.446,20.099 9.554,20.099 8.804,19.704L7.549,19.045C7.297,18.913 7.024,18.822 6.743,18.78L5.346,18.564C4.511,18.435 3.789,17.903 3.411,17.137L2.777,15.856C2.651,15.597 2.482,15.363 2.279,15.16L1.274,14.153C0.672,13.55 0.397,12.69 0.535,11.845L0.764,10.43C0.811,10.145 0.811,9.855 0.764,9.57L0.535,8.155C0.397,7.31 0.672,6.449 1.274,5.847L2.279,4.84C2.482,4.637 2.651,4.402 2.777,4.144L3.411,2.862C3.789,2.096 4.511,1.564 5.346,1.436L6.743,1.221C7.024,1.178 7.297,1.088 7.549,0.955L8.804,0.296Z"
+ android:fillColor="@color/settingslib_materialColorOnSurface"/>
+ </vector>
+ </item>
+ <item android:gravity="center">
+ <vector
+ android:width="14dp"
+ android:height="4dp"
+ android:viewportWidth="14"
+ android:viewportHeight="4">
+ <path
+ android:pathData="M0.962,3.106V0.894H13.038V3.106H0.962Z"
+ android:fillColor="@color/settingslib_materialColorSurface"/>
+ </vector>
+ </item>
+</layer-list> \ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable/settingslib_expressive_icon_up.xml b/packages/SettingsLib/SettingsTheme/res/drawable/settingslib_expressive_icon_up.xml
new file mode 100644
index 000000000000..c38730534f03
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/drawable/settingslib_expressive_icon_up.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.
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="960"
+ android:viewportHeight="960">
+ <path
+ android:fillColor="@color/settingslib_materialColorOnSurface"
+ android:pathData="M480,432L296,616L240,560L480,320L720,560L664,616L480,432Z"/>
+</vector> \ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_preference.xml b/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_preference.xml
index 511e2bb64f1e..4ef747a99b49 100644
--- a/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_preference.xml
+++ b/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_preference.xml
@@ -19,7 +19,7 @@
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:minHeight="?android:attr/listPreferredItemHeightSmall"
+ android:minHeight="72dp"
android:gravity="center_vertical"
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
diff --git a/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_collapsable_textview.xml b/packages/SettingsLib/SettingsTheme/res/layout/settingslib_expressive_collapsable_textview.xml
index 2776544e2948..7d7bec14ed78 100644
--- a/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_collapsable_textview.xml
+++ b/packages/SettingsLib/SettingsTheme/res/layout/settingslib_expressive_collapsable_textview.xml
@@ -48,6 +48,7 @@
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@android:id/title"
app:layout_constraintStart_toStartOf="parent"
+ android:paddingTop="@dimen/settingslib_expressive_space_extrasmall6"
android:textAlignment="viewStart"
android:clickable="true"
android:visibility="gone"
diff --git a/packages/SettingsLib/SettingsTheme/res/values-night/colors.xml b/packages/SettingsLib/SettingsTheme/res/values-night/colors.xml
index e57fe4f512fe..d677bbaa4ee8 100644
--- a/packages/SettingsLib/SettingsTheme/res/values-night/colors.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values-night/colors.xml
@@ -50,4 +50,9 @@
<color name="settingslib_materialColorSurfaceContainerLow">#0E0E0E</color>
<color name="settingslib_materialColorSurfaceContainerHigh">#2A2A2A</color>
<color name="settingslib_materialColorSurfaceContainerHighest">#343434</color>
+
+ <color name="settingslib_colorBackgroundLevel_high">@color/m3_ref_palette_red60</color>
+ <color name="settingslib_colorContentLevel_high">@color/m3_ref_palette_red10</color>
+ <color name="settingslib_colorBackgroundLevel_low">@color/m3_ref_palette_green70</color>
+ <color name="settingslib_colorContentLevel_low">@color/m3_ref_palette_green10</color>
</resources> \ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/values-v31/styles_expressive.xml b/packages/SettingsLib/SettingsTheme/res/values-v31/styles_expressive.xml
index b5f22b73cecd..ec67d068123a 100644
--- a/packages/SettingsLib/SettingsTheme/res/values-v31/styles_expressive.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values-v31/styles_expressive.xml
@@ -104,6 +104,11 @@
<item name="android:layout_width">match_parent</item>
</style>
+ <style name="SettingslibTextAppearance.LinkableTextStyle.Expressive"
+ parent="@style/TextAppearance.SettingsLib.LabelLarge">
+ <item name="android:textColor">?android:attr/colorAccent</item>
+ </style>
+
<style name="SettingslibTextButtonStyle.Expressive"
parent="@style/Widget.Material3Expressive.Button.TextButton.Icon">
<item name="android:theme">@style/Theme.Material3.DynamicColors.DayNight</item>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-v35/styles_expressive.xml b/packages/SettingsLib/SettingsTheme/res/values-v35/styles_expressive.xml
index 1a085681864a..3af88c48e8ca 100644
--- a/packages/SettingsLib/SettingsTheme/res/values-v35/styles_expressive.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values-v35/styles_expressive.xml
@@ -70,11 +70,6 @@
<item name="android:textAppearance">@style/TextAppearance.SettingsLib.TitleLarge.Emphasized</item>
</style>
- <style name="SettingslibTextAppearance.LinkableTextStyle.Expressive"
- parent="@style/TextAppearance.SettingsLib.LabelLarge">
- <item name="android:textColor">?android:attr/colorAccent</item>
- </style>
-
<style name="SettingsLibStatusBannerCardStyle">
<item name="android:layout_width">match_parent</item>
<item name="android:layout_height">wrap_content</item>
diff --git a/packages/SettingsLib/SettingsTheme/res/values/colors.xml b/packages/SettingsLib/SettingsTheme/res/values/colors.xml
index c5c613b4b329..e8ab99e6bc14 100644
--- a/packages/SettingsLib/SettingsTheme/res/values/colors.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values/colors.xml
@@ -76,4 +76,11 @@
<color name="settingslib_materialColorSurfaceContainerLowest">#FFFFFF</color>
<color name="settingslib_materialColorSurfaceContainerHigh">#E8E8E8</color>
<color name="settingslib_materialColorSurfaceContainerHighest">#E3E3E3</color>
+
+ <color name="settingslib_colorBackgroundLevel_high">@color/m3_ref_palette_red50</color>
+ <color name="settingslib_colorContentLevel_high">@color/m3_ref_palette_red100</color>
+ <color name="settingslib_colorBackgroundLevel_medium">@color/m3_ref_palette_yellow80</color>
+ <color name="settingslib_colorContentLevel_medium">@color/m3_ref_palette_yellow10</color>
+ <color name="settingslib_colorBackgroundLevel_low">@color/m3_ref_palette_green50</color>
+ <color name="settingslib_colorContentLevel_low">@color/m3_ref_palette_green100</color>
</resources> \ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/src/com/android/settingslib/widget/SettingsThemeHelper.kt b/packages/SettingsLib/SettingsTheme/src/com/android/settingslib/widget/SettingsThemeHelper.kt
index 6794cd0e30a2..1776d252e28b 100644
--- a/packages/SettingsLib/SettingsTheme/src/com/android/settingslib/widget/SettingsThemeHelper.kt
+++ b/packages/SettingsLib/SettingsTheme/src/com/android/settingslib/widget/SettingsThemeHelper.kt
@@ -18,6 +18,7 @@ package com.android.settingslib.widget
import android.content.Context
import android.os.Build
+import com.android.settingslib.widget.theme.flags.Flags
object SettingsThemeHelper {
private const val IS_EXPRESSIVE_DESIGN_ENABLED = "is_expressive_design_enabled"
@@ -56,7 +57,8 @@ object SettingsThemeHelper {
expressiveThemeState =
if (
(Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM) &&
- getPropBoolean(context, IS_EXPRESSIVE_DESIGN_ENABLED, false)
+ (getPropBoolean(context, IS_EXPRESSIVE_DESIGN_ENABLED, false) ||
+ Flags.isExpressiveDesignEnabled())
) {
ExpressiveThemeState.ENABLED
} else {
diff --git a/packages/SettingsLib/Spa/.gitignore b/packages/SettingsLib/Spa/.gitignore
index b2ed2681b24c..5790fde1a797 100644
--- a/packages/SettingsLib/Spa/.gitignore
+++ b/packages/SettingsLib/Spa/.gitignore
@@ -7,6 +7,7 @@
/.idea/workspace.xml
/.idea/navEditor.xml
/.idea/assetWizardSettings.xml
+/.kotlin
.DS_Store
build
/captures
diff --git a/packages/SettingsLib/Spa/build.gradle.kts b/packages/SettingsLib/Spa/build.gradle.kts
index cf695d0543c7..5e72c43ef494 100644
--- a/packages/SettingsLib/Spa/build.gradle.kts
+++ b/packages/SettingsLib/Spa/build.gradle.kts
@@ -28,7 +28,7 @@ val androidTop: String = File(rootDir, "../../../../..").canonicalPath
allprojects {
extra["androidTop"] = androidTop
- extra["jetpackComposeVersion"] = "1.8.0-alpha06"
+ extra["jetpackComposeVersion"] = "1.8.0-alpha08"
}
subprojects {
diff --git a/packages/SettingsLib/Spa/gradle/libs.versions.toml b/packages/SettingsLib/Spa/gradle/libs.versions.toml
index 04ef96a89843..4113ad86b5f8 100644
--- a/packages/SettingsLib/Spa/gradle/libs.versions.toml
+++ b/packages/SettingsLib/Spa/gradle/libs.versions.toml
@@ -15,9 +15,9 @@
#
[versions]
-agp = "8.7.3"
+agp = "8.8.0"
dexmaker-mockito = "2.28.3"
-jvm = "17"
+jvm = "21"
kotlin = "2.0.21"
truth = "1.4.4"
diff --git a/packages/SettingsLib/Spa/spa/build.gradle.kts b/packages/SettingsLib/Spa/spa/build.gradle.kts
index a0bbb0ca9ae6..13966299b923 100644
--- a/packages/SettingsLib/Spa/spa/build.gradle.kts
+++ b/packages/SettingsLib/Spa/spa/build.gradle.kts
@@ -52,14 +52,14 @@ android {
dependencies {
api(project(":SettingsLibColor"))
api("androidx.appcompat:appcompat:1.7.0")
- api("androidx.compose.material3:material3:1.4.0-alpha04")
+ api("androidx.compose.material3:material3:1.4.0-alpha05")
api("androidx.compose.material:material-icons-extended")
api("androidx.compose.runtime:runtime-livedata:$jetpackComposeVersion")
api("androidx.compose.ui:ui-tooling-preview:$jetpackComposeVersion")
api("androidx.graphics:graphics-shapes-android:1.0.1")
api("androidx.lifecycle:lifecycle-livedata-ktx")
api("androidx.lifecycle:lifecycle-runtime-compose")
- api("androidx.navigation:navigation-compose:2.9.0-alpha03")
+ api("androidx.navigation:navigation-compose:2.9.0-alpha04")
api("com.github.PhilJay:MPAndroidChart:v3.1.0-alpha")
api("com.google.android.material:material:1.13.0-alpha08")
debugApi("androidx.compose.ui:ui-tooling:$jetpackComposeVersion")
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/DropdownTextBox.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/DropdownTextBox.kt
index bdbe62c07425..8e59fd74c740 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/DropdownTextBox.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/DropdownTextBox.kt
@@ -20,9 +20,9 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.ExposedDropdownMenuAnchorType
import androidx.compose.material3.ExposedDropdownMenuBox
import androidx.compose.material3.ExposedDropdownMenuDefaults
-import androidx.compose.material3.MenuAnchorType
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
@@ -66,7 +66,7 @@ internal fun DropdownTextBox(
OutlinedTextField(
// The `menuAnchor` modifier must be passed to the text field for correctness.
modifier = Modifier
- .menuAnchor(MenuAnchorType.PrimaryNotEditable)
+ .menuAnchor(ExposedDropdownMenuAnchorType.PrimaryNotEditable)
.fillMaxWidth(),
value = text,
onValueChange = { },
diff --git a/packages/SettingsLib/Spa/testutils/build.gradle.kts b/packages/SettingsLib/Spa/testutils/build.gradle.kts
index 7dbd320c7d5a..03cd243932bd 100644
--- a/packages/SettingsLib/Spa/testutils/build.gradle.kts
+++ b/packages/SettingsLib/Spa/testutils/build.gradle.kts
@@ -36,7 +36,7 @@ android {
dependencies {
api(project(":spa"))
- api("androidx.arch.core:core-testing:2.2.0-alpha01")
+ api("androidx.arch.core:core-testing:2.2.0")
api("androidx.compose.ui:ui-test-junit4:$jetpackComposeVersion")
api("androidx.lifecycle:lifecycle-runtime-testing")
api("org.mockito.kotlin:mockito-kotlin:2.2.11")
diff --git a/packages/SettingsLib/TopIntroPreference/res/layout-v33/top_intro_preference.xml b/packages/SettingsLib/TopIntroPreference/res/layout-v33/top_intro_preference.xml
deleted file mode 100644
index 195d45f3a639..000000000000
--- a/packages/SettingsLib/TopIntroPreference/res/layout-v33/top_intro_preference.xml
+++ /dev/null
@@ -1,41 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2022 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
- -->
-
-<LinearLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:paddingStart="?android:attr/listPreferredItemPaddingStart"
- android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
- android:paddingBottom="16dp"
- android:paddingTop="8dp"
- android:background="?android:attr/selectableItemBackground"
- android:clipToPadding="false">
-
- <TextView
- android:id="@android:id/title"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="start"
- android:textAlignment="viewStart"
- android:clickable="false"
- android:longClickable="false"
- android:maxLines="10"
- android:hyphenationFrequency="normalFast"
- android:lineBreakWordStyle="phrase"
- android:textAppearance="@style/TextAppearance.TopIntroText"/>
-</LinearLayout> \ No newline at end of file
diff --git a/packages/SettingsLib/TopIntroPreference/res/layout-v35/settingslib_expressive_top_intro.xml b/packages/SettingsLib/TopIntroPreference/res/layout/settingslib_expressive_top_intro.xml
index fb13ef79cc3b..834814ccbc6b 100644
--- a/packages/SettingsLib/TopIntroPreference/res/layout-v35/settingslib_expressive_top_intro.xml
+++ b/packages/SettingsLib/TopIntroPreference/res/layout/settingslib_expressive_top_intro.xml
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
- 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.
diff --git a/packages/SettingsLib/TopIntroPreference/res/layout/top_intro_preference.xml b/packages/SettingsLib/TopIntroPreference/res/layout/top_intro_preference.xml
deleted file mode 100644
index bee6bc70f2c8..000000000000
--- a/packages/SettingsLib/TopIntroPreference/res/layout/top_intro_preference.xml
+++ /dev/null
@@ -1,39 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2020 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
- -->
-
-<LinearLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:paddingStart="?android:attr/listPreferredItemPaddingStart"
- android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
- android:paddingBottom="16dp"
- android:paddingTop="8dp"
- android:background="?android:attr/selectableItemBackground"
- android:clipToPadding="false">
-
- <TextView
- android:id="@android:id/title"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="start"
- android:textAlignment="viewStart"
- android:clickable="false"
- android:longClickable="false"
- android:maxLines="10"
- android:textAppearance="@style/TextAppearance.TopIntroText"/>
-</LinearLayout> \ No newline at end of file
diff --git a/packages/SettingsLib/TopIntroPreference/src/com/android/settingslib/widget/TopIntroPreference.kt b/packages/SettingsLib/TopIntroPreference/src/com/android/settingslib/widget/TopIntroPreference.kt
index 4428480eaa3e..08ba836b21ca 100644
--- a/packages/SettingsLib/TopIntroPreference/src/com/android/settingslib/widget/TopIntroPreference.kt
+++ b/packages/SettingsLib/TopIntroPreference/src/com/android/settingslib/widget/TopIntroPreference.kt
@@ -17,20 +17,20 @@
package com.android.settingslib.widget
import android.content.Context
-import android.os.Build
import android.text.TextUtils
import android.util.AttributeSet
import android.view.View
-import androidx.annotation.RequiresApi
import androidx.preference.Preference
import androidx.preference.PreferenceViewHolder
import com.android.settingslib.widget.preference.topintro.R
-open class TopIntroPreference @JvmOverloads constructor(
+open class TopIntroPreference
+@JvmOverloads
+constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0,
- defStyleRes: Int = 0
+ defStyleRes: Int = 0,
) : Preference(context, attrs, defStyleAttr, defStyleRes), GroupSectionDividerMixin {
private var isCollapsable: Boolean = false
@@ -40,25 +40,18 @@ open class TopIntroPreference @JvmOverloads constructor(
private var learnMoreText: CharSequence? = null
init {
- if (SettingsThemeHelper.isExpressiveTheme(context)) {
- layoutResource = R.layout.settingslib_expressive_top_intro
- initAttributes(context, attrs, defStyleAttr)
- } else {
- layoutResource = R.layout.top_intro_preference
- }
+ layoutResource = R.layout.settingslib_expressive_top_intro
+ initAttributes(context, attrs, defStyleAttr)
+
isSelectable = false
}
private fun initAttributes(context: Context, attrs: AttributeSet?, defStyleAttr: Int) {
- context.obtainStyledAttributes(
- attrs,
- COLLAPSABLE_TEXT_VIEW_ATTRS, defStyleAttr, 0
- ).apply {
+ 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)
+ minLines =
+ getInt(MIN_LINES, if (isCollapsable) DEFAULT_MIN_LINES else DEFAULT_MAX_LINES)
+ .coerceIn(1, DEFAULT_MAX_LINES)
recycle()
}
}
@@ -68,10 +61,6 @@ open class TopIntroPreference @JvmOverloads constructor(
holder.isDividerAllowedAbove = false
holder.isDividerAllowedBelow = false
- if (!SettingsThemeHelper.isExpressiveTheme(context)) {
- return
- }
-
(holder.findViewById(R.id.collapsable_text_view) as? CollapsableTextView)?.apply {
setCollapsable(isCollapsable)
setMinLines(minLines)
@@ -89,9 +78,9 @@ open class TopIntroPreference @JvmOverloads constructor(
/**
* Sets whether the text view is collapsable.
+ *
* @param collapsable True if the text view should be collapsable, false otherwise.
*/
- @RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
fun setCollapsable(collapsable: Boolean) {
isCollapsable = collapsable
notifyChanged()
@@ -99,9 +88,9 @@ open class TopIntroPreference @JvmOverloads constructor(
/**
* Sets the minimum number of lines to display when collapsed.
+ *
* @param lines The minimum number of lines.
*/
- @RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
fun setMinLines(lines: Int) {
minLines = lines.coerceIn(1, DEFAULT_MAX_LINES)
notifyChanged()
@@ -109,9 +98,9 @@ open class TopIntroPreference @JvmOverloads constructor(
/**
* Sets the action when clicking on the hyperlink in the text.
+ *
* @param listener The click listener for hyperlink.
*/
- @RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
fun setHyperlinkListener(listener: View.OnClickListener) {
if (hyperlinkListener != listener) {
hyperlinkListener = listener
@@ -121,9 +110,9 @@ open class TopIntroPreference @JvmOverloads constructor(
/**
* Sets the action when clicking on the learn more view.
+ *
* @param listener The click listener for learn more.
*/
- @RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
fun setLearnMoreAction(listener: View.OnClickListener) {
if (learnMoreListener != listener) {
learnMoreListener = listener
@@ -133,9 +122,9 @@ open class TopIntroPreference @JvmOverloads constructor(
/**
* Sets the text of learn more view.
+ *
* @param text The text of learn more.
*/
- @RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
fun setLearnMoreText(text: CharSequence) {
if (!TextUtils.equals(learnMoreText, text)) {
learnMoreText = text
diff --git a/packages/SettingsLib/res/values/config.xml b/packages/SettingsLib/res/values/config.xml
index 3c3de044cc4e..502eb6c7296a 100644
--- a/packages/SettingsLib/res/values/config.xml
+++ b/packages/SettingsLib/res/values/config.xml
@@ -41,4 +41,15 @@
<string name="config_avatar_picker_class" translatable="false">
com.android.avatarpicker.ui.AvatarPickerActivity
</string>
+
+ <array name="config_override_carrier_5g_plus">
+ <item>@array/carrier_2032_5g_plus</item>
+ </array>
+
+ <integer-array name="carrier_2032_5g_plus">
+ <!-- carrier id: 2032 -->
+ <item>2032</item>
+ <!-- network type: "5G+" -->
+ <item>@string/data_connection_5g_plus_carrier_2032</item>
+ </integer-array>
</resources> \ No newline at end of file
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index 6cf9e83ef342..3da2271431f8 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -1723,6 +1723,8 @@
<!-- Content description of the data connection type 5G+. [CHAR LIMIT=NONE] -->
<string name="data_connection_5g_plus" translatable="false">5G+</string>
+ <!-- Content description of the data connection type 5G+ for carrier 2032. [CHAR LIMIT=NONE] -->
+ <string name="data_connection_5g_plus_carrier_2032" translatable="false">5G+</string>
<!-- Content description of the data connection type Carrier WiFi. [CHAR LIMIT=NONE] -->
<string name="data_connection_carrier_wifi">W+</string>
diff --git a/packages/SettingsLib/src/com/android/settingslib/PreferenceBindings.kt b/packages/SettingsLib/src/com/android/settingslib/PreferenceBindings.kt
new file mode 100644
index 000000000000..a64e8cc07b15
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/PreferenceBindings.kt
@@ -0,0 +1,37 @@
+/*
+ * 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.
+ */
+@file:Suppress("ktlint:standard:filename") // remove once we have more bindings
+
+package com.android.settingslib
+
+import android.content.Context
+import androidx.preference.Preference
+import com.android.settingslib.metadata.PreferenceMetadata
+import com.android.settingslib.preference.PreferenceBinding
+
+/** Preference binding for [PrimarySwitchPreference]. */
+interface PrimarySwitchPreferenceBinding : PreferenceBinding {
+
+ override fun createWidget(context: Context): Preference = PrimarySwitchPreference(context)
+
+ override fun bind(preference: Preference, metadata: PreferenceMetadata) {
+ super.bind(preference, metadata)
+ (preference as PrimarySwitchPreference).apply {
+ isChecked = preferenceDataStore!!.getBoolean(key, false)
+ isSwitchEnabled = isEnabled
+ }
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/mobile/MobileMappings.java b/packages/SettingsLib/src/com/android/settingslib/mobile/MobileMappings.java
index b7108c98c3fe..cf52eb3ab060 100644
--- a/packages/SettingsLib/src/com/android/settingslib/mobile/MobileMappings.java
+++ b/packages/SettingsLib/src/com/android/settingslib/mobile/MobileMappings.java
@@ -15,11 +15,14 @@
*/
package com.android.settingslib.mobile;
+import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.Resources;
+import android.content.res.TypedArray;
import android.os.PersistableBundle;
import android.telephony.Annotation;
import android.telephony.CarrierConfigManager;
+import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyDisplayInfo;
import android.telephony.TelephonyManager;
@@ -196,9 +199,9 @@ public class MobileMappings {
networkToIconLookup.put(toDisplayIconKey(
TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA),
TelephonyIcons.NR_5G);
- networkToIconLookup.put(toDisplayIconKey(
- TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_ADVANCED),
- TelephonyIcons.NR_5G_PLUS);
+ networkToIconLookup.put(
+ toDisplayIconKey(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_ADVANCED),
+ config.mobileIconGroup5gPlus);
networkToIconLookup.put(toIconKey(
TelephonyManager.NETWORK_TYPE_NR),
TelephonyIcons.NR_5G);
@@ -217,6 +220,7 @@ public class MobileMappings {
public boolean hideLtePlus = false;
public boolean hspaDataDistinguishable;
public boolean alwaysShowDataRatIcon = false;
+ public MobileIconGroup mobileIconGroup5gPlus = TelephonyIcons.NR_5G_PLUS;
/**
* Reads the latest configs.
@@ -250,9 +254,54 @@ public class MobileMappings {
config.hideLtePlus = b.getBoolean(
CarrierConfigManager.KEY_HIDE_LTE_PLUS_DATA_ICON_BOOL);
}
+
+ SubscriptionManager subscriptionManager =
+ context.getSystemService(SubscriptionManager.class);
+ if (subscriptionManager != null) {
+ SubscriptionInfo subInfo = subscriptionManager.getDefaultDataSubscriptionInfo();
+ if (subInfo != null) {
+ readMobileIconGroup5gPlus(subInfo.getCarrierId(), res, config);
+ }
+ }
return config;
}
+ @SuppressLint("ResourceType")
+ private static void readMobileIconGroup5gPlus(int carrierId, Resources res, Config config) {
+ int networkTypeResId = 0;
+ TypedArray groupArray;
+ try {
+ groupArray = res.obtainTypedArray(R.array.config_override_carrier_5g_plus);
+ } catch (Resources.NotFoundException e) {
+ return;
+ }
+ for (int i = 0; i < groupArray.length() && networkTypeResId == 0; i++) {
+ int groupId = groupArray.getResourceId(i, 0);
+ if (groupId == 0) {
+ continue;
+ }
+ TypedArray carrierArray;
+ try {
+ carrierArray = res.obtainTypedArray(groupId);
+ } catch (Resources.NotFoundException e) {
+ continue;
+ }
+ int groupCarrierId = carrierArray.getInt(0, 0);
+ if (groupCarrierId == carrierId) {
+ networkTypeResId = carrierArray.getResourceId(1, 0);
+ }
+ carrierArray.recycle();
+ }
+ groupArray.recycle();
+
+ if (networkTypeResId != 0) {
+ config.mobileIconGroup5gPlus = new MobileIconGroup(
+ TelephonyIcons.NR_5G_PLUS.name,
+ networkTypeResId,
+ TelephonyIcons.NR_5G_PLUS.dataType);
+ }
+ }
+
/**
* Returns true if this config and the other config are semantically equal.
*
diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenDurationDialog.java b/packages/SettingsLib/src/com/android/settingslib/notification/modes/DndDurationDialogFactory.java
index 98c3edba8223..c5fa0aacbda1 100644
--- a/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenDurationDialog.java
+++ b/packages/SettingsLib/src/com/android/settingslib/notification/modes/DndDurationDialogFactory.java
@@ -44,7 +44,12 @@ import com.android.settingslib.R;
import java.util.Arrays;
-public class ZenDurationDialog {
+/**
+ * This dialog configures the default behavior that the user prefers when enabling DND.
+ * Not to be confused with {@link EnableDndDialogFactory}, which is the dialog that will be shown
+ * when the user enables DND if the "Ask every time" option was selected in this dialog.
+ */
+public class DndDurationDialogFactory {
private static final int[] MINUTE_BUCKETS = ZenModeConfig.MINUTE_BUCKETS;
@VisibleForTesting
protected static final int MIN_BUCKET_MINUTES = MINUTE_BUCKETS[0];
@@ -72,7 +77,7 @@ public class ZenDurationDialog {
@VisibleForTesting
protected LayoutInflater mLayoutInflater;
- public ZenDurationDialog(Context context) {
+ public DndDurationDialogFactory(Context context) {
mContext = context;
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/modes/EnableZenModeDialog.java b/packages/SettingsLib/src/com/android/settingslib/notification/modes/EnableDndDialogFactory.java
index c48694cecb4c..f0e7fb851d5f 100644
--- a/packages/SettingsLib/src/com/android/settingslib/notification/modes/EnableZenModeDialog.java
+++ b/packages/SettingsLib/src/com/android/settingslib/notification/modes/EnableDndDialogFactory.java
@@ -54,8 +54,15 @@ import java.util.GregorianCalendar;
import java.util.Locale;
import java.util.Objects;
-public class EnableZenModeDialog {
- private static final String TAG = "EnableZenModeDialog";
+/**
+ * When enabling DND, if the user has the setting to "Ask every time" for the duration, we show
+ * this dialog to allow the user to select for how long they want DND to be enabled this time.
+ * Not to be confused with {@link DndDurationDialogFactory}, which is the dialog that allows the
+ * user to configure the default behavior for enabling DND (and in turn may lead to this dialog
+ * being shown, since it contains the said "Ask every time" option).
+ */
+public class EnableDndDialogFactory {
+ private static final String TAG = "EnableDndDialogFactory";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
private static final int[] MINUTE_BUCKETS = ZenModeConfig.MINUTE_BUCKETS;
@@ -74,7 +81,7 @@ public class EnableZenModeDialog {
private static final int MINUTES_MS = 60 * SECONDS_MS;
@Nullable
- private final ZenModeDialogMetricsLogger mMetricsLogger;
+ private final EnableDndDialogMetricsLogger mMetricsLogger;
@VisibleForTesting
protected Uri mForeverId;
@@ -101,17 +108,17 @@ public class EnableZenModeDialog {
@VisibleForTesting
protected LayoutInflater mLayoutInflater;
- public EnableZenModeDialog(Context context) {
+ public EnableDndDialogFactory(Context context) {
this(context, 0);
}
- public EnableZenModeDialog(Context context, int themeResId) {
+ public EnableDndDialogFactory(Context context, int themeResId) {
this(context, themeResId, false /* cancelIsNeutral */,
- new ZenModeDialogMetricsLogger(context));
+ new EnableDndDialogMetricsLogger(context));
}
- public EnableZenModeDialog(Context context, int themeResId, boolean cancelIsNeutral,
- ZenModeDialogMetricsLogger metricsLogger) {
+ public EnableDndDialogFactory(Context context, int themeResId, boolean cancelIsNeutral,
+ EnableDndDialogMetricsLogger metricsLogger) {
mContext = context;
mThemeResId = themeResId;
mCancelIsNeutral = cancelIsNeutral;
diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenModeDialogMetricsLogger.java b/packages/SettingsLib/src/com/android/settingslib/notification/modes/EnableDndDialogMetricsLogger.java
index 17695e396bef..552bf8d95dcf 100644
--- a/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenModeDialogMetricsLogger.java
+++ b/packages/SettingsLib/src/com/android/settingslib/notification/modes/EnableDndDialogMetricsLogger.java
@@ -22,12 +22,12 @@ import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto;
/**
- * Logs ui events for {@link EnableZenModeDialog}.
+ * Logs ui events for {@link EnableDndDialogFactory}.
*/
-public class ZenModeDialogMetricsLogger {
+public class EnableDndDialogMetricsLogger {
private final Context mContext;
- public ZenModeDialogMetricsLogger(Context context) {
+ public EnableDndDialogMetricsLogger(Context context) {
mContext = context;
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/modes/ZenDurationDialogTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/modes/DndDurationDialogFactoryTest.java
index 19845a04ea18..0b05a4f2dff0 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/modes/ZenDurationDialogTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/modes/DndDurationDialogFactoryTest.java
@@ -16,6 +16,12 @@
package com.android.settingslib.notification.modes;
+import static com.android.settingslib.notification.modes.DndDurationDialogFactory.ALWAYS_ASK_CONDITION_INDEX;
+import static com.android.settingslib.notification.modes.DndDurationDialogFactory.COUNTDOWN_CONDITION_INDEX;
+import static com.android.settingslib.notification.modes.DndDurationDialogFactory.FOREVER_CONDITION_INDEX;
+import static com.android.settingslib.notification.modes.DndDurationDialogFactory.MAX_BUCKET_MINUTES;
+import static com.android.settingslib.notification.modes.DndDurationDialogFactory.MIN_BUCKET_MINUTES;
+
import static com.google.common.truth.Truth.assertThat;
import static junit.framework.Assert.assertEquals;
@@ -32,6 +38,8 @@ import android.view.View;
import androidx.appcompat.app.AlertDialog;
+import com.android.settingslib.notification.modes.DndDurationDialogFactory.ConditionTag;
+
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -39,8 +47,8 @@ import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
@RunWith(RobolectricTestRunner.class)
-public class ZenDurationDialogTest {
- private ZenDurationDialog mController;
+public class DndDurationDialogFactoryTest {
+ private DndDurationDialogFactory mController;
private Context mContext;
private LayoutInflater mLayoutInflater;
@@ -53,7 +61,7 @@ public class ZenDurationDialogTest {
mContentResolver = RuntimeEnvironment.application.getContentResolver();
mLayoutInflater = LayoutInflater.from(mContext);
- mController = spy(new ZenDurationDialog(mContext));
+ mController = spy(new DndDurationDialogFactory(mContext));
mController.mLayoutInflater = mLayoutInflater;
mController.getContentView();
mBuilder = new AlertDialog.Builder(mContext);
@@ -65,12 +73,9 @@ public class ZenDurationDialogTest {
Settings.Global.ZEN_DURATION_PROMPT);
mController.setupDialog(mBuilder);
- assertFalse(mController.getConditionTagAt(ZenDurationDialog.FOREVER_CONDITION_INDEX).rb
- .isChecked());
- assertFalse(mController.getConditionTagAt(ZenDurationDialog.COUNTDOWN_CONDITION_INDEX).rb
- .isChecked());
- assertTrue(mController.getConditionTagAt(
- ZenDurationDialog.ALWAYS_ASK_CONDITION_INDEX).rb.isChecked());
+ assertFalse(mController.getConditionTagAt(FOREVER_CONDITION_INDEX).rb.isChecked());
+ assertFalse(mController.getConditionTagAt(COUNTDOWN_CONDITION_INDEX).rb.isChecked());
+ assertTrue(mController.getConditionTagAt(ALWAYS_ASK_CONDITION_INDEX).rb.isChecked());
}
@Test
@@ -79,12 +84,9 @@ public class ZenDurationDialogTest {
Settings.Secure.ZEN_DURATION_FOREVER);
mController.setupDialog(mBuilder);
- assertTrue(mController.getConditionTagAt(ZenDurationDialog.FOREVER_CONDITION_INDEX).rb
- .isChecked());
- assertFalse(mController.getConditionTagAt(ZenDurationDialog.COUNTDOWN_CONDITION_INDEX).rb
- .isChecked());
- assertFalse(mController.getConditionTagAt(
- ZenDurationDialog.ALWAYS_ASK_CONDITION_INDEX).rb.isChecked());
+ assertTrue(mController.getConditionTagAt(FOREVER_CONDITION_INDEX).rb.isChecked());
+ assertFalse(mController.getConditionTagAt(COUNTDOWN_CONDITION_INDEX).rb.isChecked());
+ assertFalse(mController.getConditionTagAt(ALWAYS_ASK_CONDITION_INDEX).rb.isChecked());
}
@Test
@@ -92,12 +94,9 @@ public class ZenDurationDialogTest {
Settings.Secure.putInt(mContentResolver, Settings.Secure.ZEN_DURATION, 45);
mController.setupDialog(mBuilder);
- assertFalse(mController.getConditionTagAt(ZenDurationDialog.FOREVER_CONDITION_INDEX).rb
- .isChecked());
- assertTrue(mController.getConditionTagAt(ZenDurationDialog.COUNTDOWN_CONDITION_INDEX).rb
- .isChecked());
- assertFalse(mController.getConditionTagAt(
- ZenDurationDialog.ALWAYS_ASK_CONDITION_INDEX).rb.isChecked());
+ assertFalse(mController.getConditionTagAt(FOREVER_CONDITION_INDEX).rb.isChecked());
+ assertTrue(mController.getConditionTagAt(COUNTDOWN_CONDITION_INDEX).rb.isChecked());
+ assertFalse(mController.getConditionTagAt(ALWAYS_ASK_CONDITION_INDEX).rb.isChecked());
}
@Test
@@ -106,8 +105,7 @@ public class ZenDurationDialogTest {
Settings.Secure.ZEN_DURATION_FOREVER);
mController.setupDialog(mBuilder);
- mController.getConditionTagAt(ZenDurationDialog.ALWAYS_ASK_CONDITION_INDEX).rb.setChecked(
- true);
+ mController.getConditionTagAt(ALWAYS_ASK_CONDITION_INDEX).rb.setChecked(true);
mController.updateZenDuration(Settings.Secure.ZEN_DURATION_FOREVER);
assertEquals(Settings.Secure.ZEN_DURATION_PROMPT, Settings.Secure.getInt(mContentResolver,
@@ -120,8 +118,7 @@ public class ZenDurationDialogTest {
Settings.Secure.ZEN_DURATION_PROMPT);
mController.setupDialog(mBuilder);
- mController.getConditionTagAt(ZenDurationDialog.FOREVER_CONDITION_INDEX).rb.setChecked(
- true);
+ mController.getConditionTagAt(FOREVER_CONDITION_INDEX).rb.setChecked(true);
mController.updateZenDuration(Settings.Secure.ZEN_DURATION_PROMPT);
assertEquals(Settings.Secure.ZEN_DURATION_FOREVER, Settings.Secure.getInt(mContentResolver,
@@ -134,8 +131,7 @@ public class ZenDurationDialogTest {
Settings.Secure.ZEN_DURATION_PROMPT);
mController.setupDialog(mBuilder);
- mController.getConditionTagAt(ZenDurationDialog.COUNTDOWN_CONDITION_INDEX).rb.setChecked(
- true);
+ mController.getConditionTagAt(COUNTDOWN_CONDITION_INDEX).rb.setChecked(true);
mController.updateZenDuration(Settings.Secure.ZEN_DURATION_PROMPT);
// countdown defaults to 60 minutes:
@@ -152,59 +148,50 @@ public class ZenDurationDialogTest {
// click time button starts at 60 minutes
// - 1 hour to MAX_BUCKET_MINUTES (12 hours), increments by 1 hour
// - 0-60 minutes increments by 15 minutes
- View view = mController.mZenRadioGroupContent.getChildAt(
- ZenDurationDialog.COUNTDOWN_CONDITION_INDEX);
- ZenDurationDialog.ConditionTag tag = mController.getConditionTagAt(
- ZenDurationDialog.COUNTDOWN_CONDITION_INDEX);
+ View view = mController.mZenRadioGroupContent.getChildAt(COUNTDOWN_CONDITION_INDEX);
+ ConditionTag tag = mController.getConditionTagAt(COUNTDOWN_CONDITION_INDEX);
// test incrementing up:
- mController.onClickTimeButton(view, tag, true, ZenDurationDialog.COUNTDOWN_CONDITION_INDEX);
+ mController.onClickTimeButton(view, tag, true, COUNTDOWN_CONDITION_INDEX);
assertEquals(120, tag.countdownZenDuration); // goes from 1 hour to 2 hours
// try clicking up 50 times - should max out at ZenDurationDialog.MAX_BUCKET_MINUTES
for (int i = 0; i < 50; i++) {
- mController.onClickTimeButton(view, tag, true,
- ZenDurationDialog.COUNTDOWN_CONDITION_INDEX);
+ mController.onClickTimeButton(view, tag, true, COUNTDOWN_CONDITION_INDEX);
}
- assertEquals(ZenDurationDialog.MAX_BUCKET_MINUTES, tag.countdownZenDuration);
+ assertEquals(MAX_BUCKET_MINUTES, tag.countdownZenDuration);
// reset, test incrementing down:
mController.mBucketIndex = -1; // reset current bucket index to reset countdownZenDuration
tag.countdownZenDuration = 60; // back to default
- mController.onClickTimeButton(view, tag, false,
- ZenDurationDialog.COUNTDOWN_CONDITION_INDEX);
+ mController.onClickTimeButton(view, tag, false, COUNTDOWN_CONDITION_INDEX);
assertEquals(45, tag.countdownZenDuration); // goes from 60 minutes to 45 minutes
// try clicking down 50 times - should stop at MIN_BUCKET_MINUTES
for (int i = 0; i < 50; i++) {
- mController.onClickTimeButton(view, tag, false,
- ZenDurationDialog.COUNTDOWN_CONDITION_INDEX);
+ mController.onClickTimeButton(view, tag, false, COUNTDOWN_CONDITION_INDEX);
}
- assertEquals(ZenDurationDialog.MIN_BUCKET_MINUTES, tag.countdownZenDuration);
+ assertEquals(MIN_BUCKET_MINUTES, tag.countdownZenDuration);
// reset countdownZenDuration to unbucketed number, should round change to nearest bucket
mController.mBucketIndex = -1;
tag.countdownZenDuration = 50;
- mController.onClickTimeButton(view, tag, false,
- ZenDurationDialog.COUNTDOWN_CONDITION_INDEX);
+ mController.onClickTimeButton(view, tag, false, COUNTDOWN_CONDITION_INDEX);
assertEquals(45, tag.countdownZenDuration);
mController.mBucketIndex = -1;
tag.countdownZenDuration = 50;
- mController.onClickTimeButton(view, tag, true,
- ZenDurationDialog.COUNTDOWN_CONDITION_INDEX);
+ mController.onClickTimeButton(view, tag, true, COUNTDOWN_CONDITION_INDEX);
assertEquals(60, tag.countdownZenDuration);
mController.mBucketIndex = -1;
tag.countdownZenDuration = 75;
- mController.onClickTimeButton(view, tag, false,
- ZenDurationDialog.COUNTDOWN_CONDITION_INDEX);
+ mController.onClickTimeButton(view, tag, false, COUNTDOWN_CONDITION_INDEX);
assertEquals(60, tag.countdownZenDuration);
mController.mBucketIndex = -1;
tag.countdownZenDuration = 75;
- mController.onClickTimeButton(view, tag, true,
- ZenDurationDialog.COUNTDOWN_CONDITION_INDEX);
+ mController.onClickTimeButton(view, tag, true, COUNTDOWN_CONDITION_INDEX);
assertEquals(120, tag.countdownZenDuration);
}
@@ -213,12 +200,9 @@ public class ZenDurationDialogTest {
Settings.Secure.putInt(mContentResolver, Settings.Secure.ZEN_DURATION,
Settings.Secure.ZEN_DURATION_FOREVER);
mController.setupDialog(mBuilder);
- ZenDurationDialog.ConditionTag forever = mController.getConditionTagAt(
- ZenDurationDialog.FOREVER_CONDITION_INDEX);
- ZenDurationDialog.ConditionTag countdown = mController.getConditionTagAt(
- ZenDurationDialog.COUNTDOWN_CONDITION_INDEX);
- ZenDurationDialog.ConditionTag alwaysAsk = mController.getConditionTagAt(
- ZenDurationDialog.ALWAYS_ASK_CONDITION_INDEX);
+ ConditionTag forever = mController.getConditionTagAt(FOREVER_CONDITION_INDEX);
+ ConditionTag countdown = mController.getConditionTagAt(COUNTDOWN_CONDITION_INDEX);
+ ConditionTag alwaysAsk = mController.getConditionTagAt(ALWAYS_ASK_CONDITION_INDEX);
forever.rb.setChecked(true);
assertThat(forever.line1.getStateDescription().toString()).isEqualTo("selected");
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/modes/EnableZenModeDialogTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/modes/EnableDndDialogFactoryTest.java
index e397f97e0277..fc9fd8077fd0 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/modes/EnableZenModeDialogTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/modes/EnableDndDialogFactoryTest.java
@@ -16,6 +16,10 @@
package com.android.settingslib.notification.modes;
+import static com.android.settingslib.notification.modes.EnableDndDialogFactory.COUNTDOWN_ALARM_CONDITION_INDEX;
+import static com.android.settingslib.notification.modes.EnableDndDialogFactory.COUNTDOWN_CONDITION_INDEX;
+import static com.android.settingslib.notification.modes.EnableDndDialogFactory.FOREVER_CONDITION_INDEX;
+
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertFalse;
@@ -39,6 +43,8 @@ import android.net.Uri;
import android.service.notification.Condition;
import android.view.LayoutInflater;
+import com.android.settingslib.notification.modes.EnableDndDialogFactory.ConditionTag;
+
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -48,8 +54,8 @@ import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
@RunWith(RobolectricTestRunner.class)
-public class EnableZenModeDialogTest {
- private EnableZenModeDialog mController;
+public class EnableDndDialogFactoryTest {
+ private EnableDndDialogFactory mController;
@Mock
private Context mContext;
@@ -74,7 +80,7 @@ public class EnableZenModeDialogTest {
when(mFragment.getContext()).thenReturn(mShadowContext);
mLayoutInflater = LayoutInflater.from(mShadowContext);
- mController = spy(new EnableZenModeDialog(mContext));
+ mController = spy(new EnableDndDialogFactory(mContext));
mController.mContext = mContext;
mController.mLayoutInflater = mLayoutInflater;
mController.mForeverId = Condition.newId(mContext).appendPath("forever").build();
@@ -101,36 +107,29 @@ public class EnableZenModeDialogTest {
Uri countdown = Condition.newId(mContext).appendPath("countdown").build();
mCountdownCondition = new Condition(countdown, "countdown", "", "", 0, 0, 0);
mController.bind(mCountdownCondition,
- mController.mZenRadioGroupContent.getChildAt(
- EnableZenModeDialog.COUNTDOWN_CONDITION_INDEX),
- EnableZenModeDialog.COUNTDOWN_CONDITION_INDEX);
+ mController.mZenRadioGroupContent.getChildAt(COUNTDOWN_CONDITION_INDEX),
+ COUNTDOWN_CONDITION_INDEX);
mController.bind(mAlarmCondition,
mController.mZenRadioGroupContent.getChildAt(
- EnableZenModeDialog.COUNTDOWN_ALARM_CONDITION_INDEX),
- EnableZenModeDialog.COUNTDOWN_ALARM_CONDITION_INDEX);
+ COUNTDOWN_ALARM_CONDITION_INDEX),
+ COUNTDOWN_ALARM_CONDITION_INDEX);
}
@Test
public void testForeverChecked() {
mController.bindConditions(mController.forever());
- assertTrue(mController.getConditionTagAt(EnableZenModeDialog.FOREVER_CONDITION_INDEX).rb
- .isChecked());
- assertFalse(mController.getConditionTagAt(EnableZenModeDialog.COUNTDOWN_CONDITION_INDEX).rb
- .isChecked());
- assertFalse(mController.getConditionTagAt(
- EnableZenModeDialog.COUNTDOWN_ALARM_CONDITION_INDEX).rb.isChecked());
+ assertTrue(mController.getConditionTagAt(FOREVER_CONDITION_INDEX).rb.isChecked());
+ assertFalse(mController.getConditionTagAt(COUNTDOWN_CONDITION_INDEX).rb.isChecked());
+ assertFalse(mController.getConditionTagAt(COUNTDOWN_ALARM_CONDITION_INDEX).rb.isChecked());
}
@Test
public void testNoneChecked() {
mController.bindConditions(null);
- assertFalse(mController.getConditionTagAt(EnableZenModeDialog.FOREVER_CONDITION_INDEX).rb
- .isChecked());
- assertFalse(mController.getConditionTagAt(EnableZenModeDialog.COUNTDOWN_CONDITION_INDEX).rb
- .isChecked());
- assertFalse(mController.getConditionTagAt(
- EnableZenModeDialog.COUNTDOWN_ALARM_CONDITION_INDEX).rb.isChecked());
+ assertFalse(mController.getConditionTagAt(FOREVER_CONDITION_INDEX).rb.isChecked());
+ assertFalse(mController.getConditionTagAt(COUNTDOWN_CONDITION_INDEX).rb.isChecked());
+ assertFalse(mController.getConditionTagAt(COUNTDOWN_ALARM_CONDITION_INDEX).rb.isChecked());
}
@Test
@@ -139,12 +138,9 @@ public class EnableZenModeDialogTest {
doReturn(true).when(mController).isAlarm(mAlarmCondition);
mController.bindConditions(mAlarmCondition);
- assertFalse(mController.getConditionTagAt(EnableZenModeDialog.FOREVER_CONDITION_INDEX).rb
- .isChecked());
- assertFalse(mController.getConditionTagAt(EnableZenModeDialog.COUNTDOWN_CONDITION_INDEX).rb
- .isChecked());
- assertTrue(mController.getConditionTagAt(
- EnableZenModeDialog.COUNTDOWN_ALARM_CONDITION_INDEX).rb.isChecked());
+ assertFalse(mController.getConditionTagAt(FOREVER_CONDITION_INDEX).rb.isChecked());
+ assertFalse(mController.getConditionTagAt(COUNTDOWN_CONDITION_INDEX).rb.isChecked());
+ assertTrue(mController.getConditionTagAt(COUNTDOWN_ALARM_CONDITION_INDEX).rb.isChecked());
}
@Test
@@ -153,12 +149,9 @@ public class EnableZenModeDialogTest {
doReturn(true).when(mController).isCountdown(mCountdownCondition);
mController.bindConditions(mCountdownCondition);
- assertFalse(mController.getConditionTagAt(EnableZenModeDialog.FOREVER_CONDITION_INDEX).rb
- .isChecked());
- assertTrue(mController.getConditionTagAt(EnableZenModeDialog.COUNTDOWN_CONDITION_INDEX).rb
- .isChecked());
- assertFalse(mController.getConditionTagAt(
- EnableZenModeDialog.COUNTDOWN_ALARM_CONDITION_INDEX).rb.isChecked());
+ assertFalse(mController.getConditionTagAt(FOREVER_CONDITION_INDEX).rb.isChecked());
+ assertTrue(mController.getConditionTagAt(COUNTDOWN_CONDITION_INDEX).rb.isChecked());
+ assertFalse(mController.getConditionTagAt(COUNTDOWN_ALARM_CONDITION_INDEX).rb.isChecked());
}
@Test
@@ -198,12 +191,12 @@ public class EnableZenModeDialogTest {
@Test
public void testAccessibility() {
mController.bindConditions(null);
- EnableZenModeDialog.ConditionTag forever = mController.getConditionTagAt(
- ZenDurationDialog.FOREVER_CONDITION_INDEX);
- EnableZenModeDialog.ConditionTag countdown = mController.getConditionTagAt(
- ZenDurationDialog.COUNTDOWN_CONDITION_INDEX);
- EnableZenModeDialog.ConditionTag alwaysAsk = mController.getConditionTagAt(
- ZenDurationDialog.ALWAYS_ASK_CONDITION_INDEX);
+ ConditionTag forever = mController.getConditionTagAt(
+ DndDurationDialogFactory.FOREVER_CONDITION_INDEX);
+ ConditionTag countdown = mController.getConditionTagAt(
+ DndDurationDialogFactory.COUNTDOWN_CONDITION_INDEX);
+ ConditionTag alwaysAsk = mController.getConditionTagAt(
+ DndDurationDialogFactory.ALWAYS_ASK_CONDITION_INDEX);
forever.rb.setChecked(true);
assertThat(forever.line1.getStateDescription().toString()).isEqualTo("selected");
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
index 7aef6578890a..d936a5c699c7 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
@@ -92,6 +92,7 @@ public class SecureSettings {
Settings.Secure.KEY_REPEAT_DELAY_MS,
Settings.Secure.CAMERA_GESTURE_DISABLED,
Settings.Secure.ACCESSIBILITY_AUTOCLICK_CURSOR_AREA_SIZE,
+ Settings.Secure.ACCESSIBILITY_AUTOCLICK_IGNORE_MINOR_CURSOR_MOVEMENT,
Settings.Secure.ACCESSIBILITY_AUTOCLICK_ENABLED,
Settings.Secure.ACCESSIBILITY_AUTOCLICK_DELAY,
Settings.Secure.ACCESSIBILITY_LARGE_POINTER_ICON,
@@ -203,7 +204,6 @@ public class SecureSettings {
Settings.Secure.AWARE_TAP_PAUSE_TOUCH_COUNT,
Settings.Secure.PEOPLE_STRIP,
Settings.Secure.MEDIA_CONTROLS_RESUME,
- Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION,
Settings.Secure.MEDIA_CONTROLS_LOCK_SCREEN,
Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE,
Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS,
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java
index 1f56f10cca7d..cf0447f9fb3a 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java
@@ -117,6 +117,7 @@ public class SystemSettings {
Settings.System.TOUCHPAD_TAP_TO_CLICK,
Settings.System.TOUCHPAD_TAP_DRAGGING,
Settings.System.TOUCHPAD_RIGHT_CLICK_ZONE,
+ Settings.System.TOUCHPAD_ACCELERATION_ENABLED,
Settings.System.CAMERA_FLASH_NOTIFICATION,
Settings.System.SCREEN_FLASH_NOTIFICATION,
Settings.System.SCREEN_FLASH_NOTIFICATION_COLOR,
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
index 43ceda724ddc..919c3c4721f2 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
@@ -142,6 +142,8 @@ public class SecureSettingsValidators {
VALIDATORS.put(Secure.CAMERA_GESTURE_DISABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(
Secure.ACCESSIBILITY_AUTOCLICK_CURSOR_AREA_SIZE, NON_NEGATIVE_INTEGER_VALIDATOR);
+ VALIDATORS.put(
+ Secure.ACCESSIBILITY_AUTOCLICK_IGNORE_MINOR_CURSOR_MOVEMENT, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.ACCESSIBILITY_AUTOCLICK_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.ACCESSIBILITY_AUTOCLICK_DELAY, NON_NEGATIVE_INTEGER_VALIDATOR);
VALIDATORS.put(Secure.ACCESSIBILITY_LARGE_POINTER_ICON, BOOLEAN_VALIDATOR);
@@ -310,7 +312,6 @@ public class SecureSettingsValidators {
VALIDATORS.put(Secure.TAP_GESTURE, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.PEOPLE_STRIP, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.MEDIA_CONTROLS_RESUME, BOOLEAN_VALIDATOR);
- VALIDATORS.put(Secure.MEDIA_CONTROLS_RECOMMENDATION, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.MEDIA_CONTROLS_LOCK_SCREEN, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.ACCESSIBILITY_MAGNIFICATION_MODE,
new InclusiveIntegerRangeValidator(
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java
index 4d98a11bdfe7..4f649ed49be3 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java
@@ -234,6 +234,7 @@ public class SystemSettingsValidators {
VALIDATORS.put(System.TOUCHPAD_TAP_DRAGGING, BOOLEAN_VALIDATOR);
VALIDATORS.put(System.TOUCHPAD_RIGHT_CLICK_ZONE, BOOLEAN_VALIDATOR);
VALIDATORS.put(System.TOUCHPAD_SYSTEM_GESTURES, BOOLEAN_VALIDATOR);
+ VALIDATORS.put(System.TOUCHPAD_ACCELERATION_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(System.LOCK_TO_APP_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(
System.EGG_MODE,
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index 1c6d6816e9b4..95059779ce3d 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -1718,6 +1718,9 @@ class SettingsProtoDumpUtil {
Settings.Secure.ACCESSIBILITY_AUTOCLICK_CURSOR_AREA_SIZE,
SecureSettingsProto.Accessibility.AUTOCLICK_CURSOR_AREA_SIZE);
dumpSetting(s, p,
+ Settings.Secure.ACCESSIBILITY_AUTOCLICK_IGNORE_MINOR_CURSOR_MOVEMENT,
+ SecureSettingsProto.Accessibility.AUTOCLICK_IGNORE_MINOR_CURSOR_MOVEMENT);
+ dumpSetting(s, p,
Settings.Secure.ACCESSIBILITY_AUTOCLICK_ENABLED,
SecureSettingsProto.Accessibility.AUTOCLICK_ENABLED);
dumpSetting(s, p,
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 4448000324d8..a044738d2e91 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -998,6 +998,11 @@
<!-- Permission required for CTS test - CtsContentProviderMultiUserTest -->
<uses-permission android:name="android.permission.RESOLVE_COMPONENT_FOR_UID" />
+ <!-- Permissions required for CTS test - MediaQualityTest -->
+ <uses-permission android:name="android.permission.MANAGE_GLOBAL_PICTURE_QUALITY_SERVICE" />
+ <uses-permission android:name="android.permission.MANAGE_GLOBAL_SOUND_QUALITY_SERVICE" />
+ <uses-permission android:name="android.permission.READ_COLOR_ZONES" />
+
<application
android:label="@string/app_label"
android:theme="@android:style/Theme.DeviceDefault.DayNight"
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index 67a90648fd24..0a7d880677d8 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -532,9 +532,13 @@ android_library {
"-Adagger.fastInit=enabled",
"-Adagger.explicitBindingConflictsWithInject=ERROR",
"-Adagger.strictMultibindingValidation=enabled",
+ "-Adagger.useBindingGraphFix=ENABLED",
"-Aroom.schemaLocation=frameworks/base/packages/SystemUI/schemas",
],
- kotlincflags: ["-Xjvm-default=all"],
+ kotlincflags: [
+ "-Xjvm-default=all",
+ "-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi",
+ ],
plugins: [
"androidx.room_room-compiler-plugin",
@@ -712,6 +716,9 @@ android_library {
use_resource_processor: true,
manifest: "tests/AndroidManifest-base.xml",
resource_dirs: [],
+
+ kotlin_lang_version: "1.9",
+
additional_manifests: ["tests/AndroidManifest.xml"],
srcs: [
"tests/src/**/*.kt",
@@ -758,7 +765,12 @@ android_library {
"-Xjvm-default=all",
// TODO(b/352363800): Why do we need this?
"-J-Xmx8192M",
+ "-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi",
],
+ javacflags: [
+ "-Adagger.useBindingGraphFix=ENABLED",
+ ],
+
aaptflags: [
"--extra-packages",
"com.android.systemui",
@@ -849,7 +861,6 @@ android_robolectric_test {
"androidx.test.ext.truth",
],
-
instrumentation_for: "SystemUIRobo-stub",
java_resource_dirs: ["tests/robolectric/config"],
plugins: [
@@ -886,7 +897,6 @@ android_robolectric_test {
"androidx.test.ext.truth",
],
-
instrumentation_for: "SystemUIRobo-stub",
java_resource_dirs: ["tests/robolectric/config"],
plugins: [
diff --git a/packages/SystemUI/OWNERS b/packages/SystemUI/OWNERS
index 07262533b81a..33e9919f06eb 100644
--- a/packages/SystemUI/OWNERS
+++ b/packages/SystemUI/OWNERS
@@ -40,6 +40,7 @@ florenceyang@google.com
gallmann@google.com
graciecheng@google.com
gwasserman@google.com
+helencheuk@google.com
hwwang@google.com
hyunyoungs@google.com
ikateryna@google.com
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 9531bc38e797..f753316cb67a 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -176,13 +176,6 @@ flag {
}
flag {
- name: "notifications_dismiss_pruned_summaries"
- namespace: "systemui"
- description: "NotifCollection.dismissNotifications will now dismiss summaries that are pruned from the shade."
- bug: "355967751"
-}
-
-flag {
name: "notification_transparent_header_fix"
namespace: "systemui"
description: "fix the transparent group header issue for async header inflation."
@@ -1940,3 +1933,17 @@ flag {
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ name: "ui_rich_ongoing_force_expanded"
+ namespace: "systemui"
+ description: "Force promoted notifications to always be expanded"
+ bug: "380901479"
+}
+
+flag {
+ name: "aod_ui_rich_ongoing"
+ namespace: "systemui"
+ description: "Show a rich ongoing notification on the always-on display (depends on ui_rich_ongoing)"
+ bug: "369151941"
+} \ No newline at end of file
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/gesture/effect/OffsetOverscrollEffect.kt b/packages/SystemUI/compose/core/src/com/android/compose/gesture/effect/OffsetOverscrollEffect.kt
index d9924038d8ef..fc01c78f8c54 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/gesture/effect/OffsetOverscrollEffect.kt
+++ b/packages/SystemUI/compose/core/src/com/android/compose/gesture/effect/OffsetOverscrollEffect.kt
@@ -18,10 +18,10 @@ package com.android.compose.gesture.effect
import androidx.annotation.VisibleForTesting
import androidx.compose.animation.core.AnimationSpec
-import androidx.compose.animation.core.Spring
-import androidx.compose.animation.core.spring
import androidx.compose.foundation.OverscrollEffect
import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
+import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
@@ -41,7 +41,7 @@ import kotlinx.coroutines.CoroutineScope
class OffsetOverscrollEffect(
orientation: Orientation,
animationScope: CoroutineScope,
- animationSpec: AnimationSpec<Float> = DefaultAnimationSpec,
+ animationSpec: AnimationSpec<Float>,
) : BaseContentOverscrollEffect(orientation, animationScope, animationSpec) {
private var _node: DelegatableNode = newNode()
override val node: DelegatableNode
@@ -71,13 +71,6 @@ class OffsetOverscrollEffect(
companion object {
private val MaxDistance = 400.dp
- internal val DefaultAnimationSpec =
- spring(
- stiffness = Spring.StiffnessLow,
- dampingRatio = Spring.DampingRatioLowBouncy,
- visibilityThreshold = 0.5f,
- )
-
@VisibleForTesting
fun computeOffset(density: Density, overscrollDistance: Float): Int {
val maxDistancePx = with(density) { MaxDistance.toPx() }
@@ -87,10 +80,11 @@ class OffsetOverscrollEffect(
}
}
+@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
fun rememberOffsetOverscrollEffect(
orientation: Orientation,
- animationSpec: AnimationSpec<Float> = OffsetOverscrollEffect.DefaultAnimationSpec,
+ animationSpec: AnimationSpec<Float> = MaterialTheme.motionScheme.defaultSpatialSpec(),
): OffsetOverscrollEffect {
val animationScope = rememberCoroutineScope()
return remember(orientation, animationScope, animationSpec) {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt
index d43b596b32f1..456edaf1012e 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt
@@ -89,9 +89,9 @@ import com.android.compose.PlatformButton
import com.android.compose.animation.Easings
import com.android.compose.animation.scene.ContentScope
import com.android.compose.animation.scene.ElementKey
-import com.android.compose.animation.scene.MutableSceneTransitionLayoutState
import com.android.compose.animation.scene.SceneKey
import com.android.compose.animation.scene.SceneTransitionLayout
+import com.android.compose.animation.scene.rememberMutableSceneTransitionLayoutState
import com.android.compose.animation.scene.transitions
import com.android.compose.windowsizeclass.LocalWindowSizeClass
import com.android.systemui.bouncer.shared.model.BouncerActionButtonModel
@@ -494,7 +494,7 @@ private fun FoldAware(
val currentSceneKey =
if (isSplitAroundTheFold) SceneKeys.SplitSceneKey else SceneKeys.ContiguousSceneKey
- val state = remember { MutableSceneTransitionLayoutState(currentSceneKey, SceneTransitions) }
+ val state = rememberMutableSceneTransitionLayoutState(currentSceneKey, SceneTransitions)
// Update state whenever currentSceneKey has changed.
LaunchedEffect(state, currentSceneKey) {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
index 9b5ff7f9e7e8..824f5a28b4d1 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
@@ -43,6 +43,7 @@ import com.android.compose.animation.scene.SceneKey
import com.android.compose.animation.scene.SceneTransitionLayout
import com.android.compose.animation.scene.Swipe
import com.android.compose.animation.scene.observableTransitionState
+import com.android.compose.animation.scene.rememberMutableSceneTransitionLayoutState
import com.android.compose.animation.scene.transitions
import com.android.compose.modifiers.thenIf
import com.android.systemui.communal.shared.model.CommunalBackgroundType
@@ -165,13 +166,13 @@ fun CommunalContainer(
viewModel.communalBackground.collectAsStateWithLifecycle(
initialValue = CommunalBackgroundType.ANIMATED
)
- val state: MutableSceneTransitionLayoutState = remember {
- MutableSceneTransitionLayoutState(
+ val state: MutableSceneTransitionLayoutState =
+ rememberMutableSceneTransitionLayoutState(
initialScene = currentSceneKey,
canChangeScene = { _ -> viewModel.canChangeScene() },
transitions = if (viewModel.v2FlagEnabled()) sceneTransitionsV2 else sceneTransitions,
)
- }
+
val isUiBlurred by viewModel.isUiBlurred.collectAsStateWithLifecycle()
val detector = remember { CommunalSwipeDetector() }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
index 70a74f064563..3c0480d150e0 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
@@ -1065,8 +1065,7 @@ private fun EmptyStateCta(contentPadding: PaddingValues, viewModel: BaseCommunal
) {
Icon(
imageVector = Icons.Default.Add,
- contentDescription =
- stringResource(R.string.label_for_button_in_empty_state_cta),
+ contentDescription = null,
modifier = Modifier.size(24.dp),
)
Spacer(Modifier.width(ButtonDefaults.IconSpacing))
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 79cf24b9c547..410499a3c23f 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
@@ -27,7 +27,6 @@ import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
-import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity
@@ -36,7 +35,7 @@ import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.IntOffset
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.compose.animation.scene.ContentScope
-import com.android.compose.animation.scene.MutableSceneTransitionLayoutState
+import com.android.compose.animation.scene.rememberMutableSceneTransitionLayoutState
import com.android.compose.modifiers.thenIf
import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
import com.android.systemui.keyguard.ui.composable.blueprint.ClockScenes.largeClockScene
@@ -82,9 +81,11 @@ constructor(
WeatherClockScenes.splitShadeLargeClockScene
}
- val state = remember {
- MutableSceneTransitionLayoutState(currentScene, ClockTransition.defaultClockTransitions)
- }
+ val state =
+ rememberMutableSceneTransitionLayoutState(
+ currentScene,
+ ClockTransition.defaultClockTransitions,
+ )
// Update state whenever currentSceneKey has changed.
LaunchedEffect(state, currentScene) {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt
index a6a63624cf7c..0ca7a6af01e0 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt
@@ -36,7 +36,6 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalView
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.compose.animation.scene.ContentKey
-import com.android.compose.animation.scene.MutableSceneTransitionLayoutState
import com.android.compose.animation.scene.OverlayKey
import com.android.compose.animation.scene.SceneKey
import com.android.compose.animation.scene.SceneTransitionLayout
@@ -44,6 +43,7 @@ import com.android.compose.animation.scene.SceneTransitions
import com.android.compose.animation.scene.UserAction
import com.android.compose.animation.scene.UserActionResult
import com.android.compose.animation.scene.observableTransitionState
+import com.android.compose.animation.scene.rememberMutableSceneTransitionLayoutState
import com.android.systemui.lifecycle.rememberActivated
import com.android.systemui.qs.ui.adapter.QSSceneAdapter
import com.android.systemui.qs.ui.composable.QuickSettingsTheme
@@ -94,29 +94,27 @@ fun SceneContainer(
val sceneJankMonitor =
rememberActivated(traceName = "sceneJankMonitor") { sceneJankMonitorFactory.create() }
- val state: MutableSceneTransitionLayoutState =
- remember(view, sceneJankMonitor) {
- MutableSceneTransitionLayoutState(
- initialScene = initialSceneKey,
- canChangeScene = { toScene -> viewModel.canChangeScene(toScene) },
- transitions = sceneTransitions,
- onTransitionStart = { transition ->
- sceneJankMonitor.onTransitionStart(
- view = view,
- from = transition.fromContent,
- to = transition.toContent,
- cuj = transition.cuj,
- )
- },
- onTransitionEnd = { transition ->
- sceneJankMonitor.onTransitionEnd(
- from = transition.fromContent,
- to = transition.toContent,
- cuj = transition.cuj,
- )
- },
- )
- }
+ val state =
+ rememberMutableSceneTransitionLayoutState(
+ initialScene = initialSceneKey,
+ canChangeScene = { toScene -> viewModel.canChangeScene(toScene) },
+ transitions = sceneTransitions,
+ onTransitionStart = { transition ->
+ sceneJankMonitor.onTransitionStart(
+ view = view,
+ from = transition.fromContent,
+ to = transition.toContent,
+ cuj = transition.cuj,
+ )
+ },
+ onTransitionEnd = { transition ->
+ sceneJankMonitor.onTransitionEnd(
+ from = transition.fromContent,
+ to = transition.toContent,
+ cuj = transition.cuj,
+ )
+ },
+ )
DisposableEffect(state) {
val dataSource = SceneTransitionLayoutDataSource(state, coroutineScope)
@@ -179,6 +177,12 @@ fun SceneContainer(
}
}
) {
+ SceneRevealScrim(
+ viewModel = viewModel.lightRevealScrim,
+ wallpaperViewModel = viewModel.wallpaperViewModel,
+ modifier = Modifier.fillMaxSize(),
+ )
+
SceneTransitionLayout(
state = state,
modifier = modifier.fillMaxSize(),
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt
index aa8b4ae9000d..d7545cb07849 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt
@@ -1,6 +1,5 @@
package com.android.systemui.scene.ui.composable
-import androidx.compose.animation.core.spring
import com.android.compose.animation.scene.TransitionKey
import com.android.compose.animation.scene.transitions
import com.android.internal.jank.Cuj
@@ -48,9 +47,6 @@ import com.android.systemui.shade.ui.composable.Shade
val SceneContainerTransitions = transitions {
interruptionHandler = SceneContainerInterruptionHandler
- defaultMotionSpatialSpec =
- spring(stiffness = 300f, dampingRatio = 0.8f, visibilityThreshold = 0.5f)
-
// Scene transitions
from(Scenes.Bouncer, to = Scenes.Gone) { bouncerToGoneTransition() }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneRevealScrim.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneRevealScrim.kt
new file mode 100644
index 000000000000..a1f89fcb123e
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneRevealScrim.kt
@@ -0,0 +1,45 @@
+/*
+ * 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.scene.ui.composable
+
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.viewinterop.AndroidView
+import com.android.systemui.keyguard.ui.binder.LightRevealScrimViewBinder
+import com.android.systemui.keyguard.ui.viewmodel.LightRevealScrimViewModel
+import com.android.systemui.statusbar.LightRevealScrim
+import com.android.systemui.wallpapers.ui.viewmodel.WallpaperViewModel
+
+@Composable
+fun SceneRevealScrim(
+ viewModel: LightRevealScrimViewModel,
+ wallpaperViewModel: WallpaperViewModel,
+ modifier: Modifier = Modifier,
+) {
+ AndroidView(
+ factory = { context ->
+ LightRevealScrim(context).apply {
+ LightRevealScrimViewBinder.bind(
+ revealScrim = this,
+ viewModel = viewModel,
+ wallpaperViewModel = wallpaperViewModel,
+ )
+ }
+ },
+ modifier = modifier,
+ )
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToSplitShadeTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToSplitShadeTransition.kt
index e30e7d3ee34c..b53c4e238462 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToSplitShadeTransition.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToSplitShadeTransition.kt
@@ -16,8 +16,6 @@
package com.android.systemui.scene.ui.composable.transitions
-import androidx.compose.animation.core.Spring
-import androidx.compose.animation.core.spring
import androidx.compose.animation.core.tween
import com.android.compose.animation.scene.TransitionBuilder
import com.android.compose.animation.scene.UserActionDistance
@@ -30,11 +28,6 @@ import kotlin.time.Duration.Companion.milliseconds
fun TransitionBuilder.goneToSplitShadeTransition(durationScale: Double = 1.0) {
spec = tween(durationMillis = (DefaultDuration * durationScale).inWholeMilliseconds.toInt())
- motionSpatialSpec =
- spring(
- stiffness = Spring.StiffnessMediumLow,
- visibilityThreshold = Shade.Dimensions.ScrimVisibilityThreshold,
- )
distance = UserActionDistance { fromContent, _, _ ->
val fromContentSize = checkNotNull(fromContent.targetSize())
fromContentSize.height.toFloat() * 2 / 3f
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToSplitShadeTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToSplitShadeTransition.kt
index 1a243ca48157..6c8885e2e379 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToSplitShadeTransition.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToSplitShadeTransition.kt
@@ -16,8 +16,6 @@
package com.android.systemui.scene.ui.composable.transitions
-import androidx.compose.animation.core.Spring
-import androidx.compose.animation.core.spring
import androidx.compose.animation.core.tween
import com.android.compose.animation.scene.TransitionBuilder
import com.android.compose.animation.scene.UserActionDistance
@@ -29,11 +27,6 @@ import kotlin.time.Duration.Companion.milliseconds
fun TransitionBuilder.lockscreenToSplitShadeTransition(durationScale: Double = 1.0) {
spec = tween(durationMillis = (DefaultDuration * durationScale).inWholeMilliseconds.toInt())
- motionSpatialSpec =
- spring(
- stiffness = Spring.StiffnessMediumLow,
- visibilityThreshold = Shade.Dimensions.ScrimVisibilityThreshold,
- )
distance = UserActionDistance { fromContent, _, _ ->
val fromContentSize = checkNotNull(fromContent.targetSize())
fromContentSize.height.toFloat() * 2 / 3f
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromNotificationsShadeToQuickSettingsShadeTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromNotificationsShadeToQuickSettingsShadeTransition.kt
index a9af95bdcb8a..9a7f99f3247b 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromNotificationsShadeToQuickSettingsShadeTransition.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromNotificationsShadeToQuickSettingsShadeTransition.kt
@@ -16,22 +16,14 @@
package com.android.systemui.scene.ui.composable.transitions
-import androidx.compose.animation.core.Spring
-import androidx.compose.animation.core.spring
import androidx.compose.animation.core.tween
import com.android.compose.animation.scene.TransitionBuilder
-import com.android.systemui.shade.ui.composable.Shade
import kotlin.time.Duration.Companion.milliseconds
fun TransitionBuilder.notificationsShadeToQuickSettingsShadeTransition(
durationScale: Double = 1.0
) {
spec = tween(durationMillis = (DefaultDuration * durationScale).inWholeMilliseconds.toInt())
- motionSpatialSpec =
- spring(
- stiffness = Spring.StiffnessMediumLow,
- visibilityThreshold = Shade.Dimensions.ScrimVisibilityThreshold,
- )
}
private val DefaultDuration = 300.milliseconds
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToNotificationsShadeTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToNotificationsShadeTransition.kt
index ddea5854d67e..019639da48c5 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToNotificationsShadeTransition.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToNotificationsShadeTransition.kt
@@ -16,8 +16,6 @@
package com.android.systemui.scene.ui.composable.transitions
-import androidx.compose.animation.core.Spring
-import androidx.compose.animation.core.spring
import androidx.compose.animation.core.tween
import com.android.compose.animation.scene.Edge
import com.android.compose.animation.scene.TransitionBuilder
@@ -26,16 +24,10 @@ import com.android.systemui.notifications.ui.composable.Notifications
import com.android.systemui.notifications.ui.composable.NotificationsShade
import com.android.systemui.scene.shared.model.Overlays
import com.android.systemui.shade.ui.composable.OverlayShade
-import com.android.systemui.shade.ui.composable.Shade
import kotlin.time.Duration.Companion.milliseconds
fun TransitionBuilder.toNotificationsShadeTransition(durationScale: Double = 1.0) {
spec = tween(durationMillis = (DefaultDuration * durationScale).inWholeMilliseconds.toInt())
- motionSpatialSpec =
- spring(
- stiffness = Spring.StiffnessMediumLow,
- visibilityThreshold = Shade.Dimensions.ScrimVisibilityThreshold,
- )
// Ensure the clock isn't clipped by the shade outline during the transition from lockscreen.
sharedElement(
ClockElementKeys.smallClockElementKey,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToQuickSettingsShadeTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToQuickSettingsShadeTransition.kt
index e477a41ac608..faccf14767b5 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToQuickSettingsShadeTransition.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToQuickSettingsShadeTransition.kt
@@ -16,23 +16,15 @@
package com.android.systemui.scene.ui.composable.transitions
-import androidx.compose.animation.core.Spring
-import androidx.compose.animation.core.spring
import androidx.compose.animation.core.tween
import com.android.compose.animation.scene.Edge
import com.android.compose.animation.scene.TransitionBuilder
import com.android.compose.animation.scene.UserActionDistance
import com.android.systemui.shade.ui.composable.OverlayShade
-import com.android.systemui.shade.ui.composable.Shade
import kotlin.time.Duration.Companion.milliseconds
fun TransitionBuilder.toQuickSettingsShadeTransition(durationScale: Double = 1.0) {
spec = tween(durationMillis = (DefaultDuration * durationScale).inWholeMilliseconds.toInt())
- motionSpatialSpec =
- spring(
- stiffness = Spring.StiffnessMediumLow,
- visibilityThreshold = Shade.Dimensions.ScrimVisibilityThreshold,
- )
distance = UserActionDistance { fromContent, _, _ ->
val fromContentSize = checkNotNull(fromContent.targetSize())
fromContentSize.height.toFloat() * 2 / 3f
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToShadeTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToShadeTransition.kt
index 4db4934cf271..2ed296b3b02d 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToShadeTransition.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToShadeTransition.kt
@@ -16,8 +16,6 @@
package com.android.systemui.scene.ui.composable.transitions
-import androidx.compose.animation.core.Spring
-import androidx.compose.animation.core.spring
import androidx.compose.animation.core.tween
import com.android.compose.animation.scene.Edge
import com.android.compose.animation.scene.TransitionBuilder
@@ -32,11 +30,6 @@ import kotlin.time.Duration.Companion.milliseconds
fun TransitionBuilder.toShadeTransition(durationScale: Double = 1.0) {
spec = tween(durationMillis = (DefaultDuration * durationScale).inWholeMilliseconds.toInt())
- motionSpatialSpec =
- spring(
- stiffness = Spring.StiffnessMediumLow,
- visibilityThreshold = Shade.Dimensions.ScrimVisibilityThreshold,
- )
distance = UserActionDistance { _, _, _ ->
Notifications.Elements.NotificationScrim.targetOffset(Scenes.Shade)?.y ?: 0f
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/spatialaudio/ui/composable/SpatialAudioPopup.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/spatialaudio/ui/composable/SpatialAudioPopup.kt
index 3295dde55238..bcd4d925814b 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/spatialaudio/ui/composable/SpatialAudioPopup.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/spatialaudio/ui/composable/SpatialAudioPopup.kt
@@ -28,6 +28,7 @@ import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.text.style.TextOverflow
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.internal.logging.UiEventLogger
import com.android.systemui.animation.Expandable
@@ -54,7 +55,7 @@ constructor(
VolumePanelUiEvent.VOLUME_PANEL_SPATIAL_AUDIO_POP_UP_SHOWN,
0,
null,
- viewModel.spatialAudioButtons.value.indexOfFirst { it.button.isActive }
+ viewModel.spatialAudioButtons.value.indexOfFirst { it.button.isActive },
)
val gravity = horizontalGravity or Gravity.BOTTOM
volumePanelPopup.show(expandable, gravity, { Title() }, { Content(it) })
@@ -95,14 +96,14 @@ constructor(
icon = { Icon(icon = buttonViewModel.button.icon) },
label = {
Text(
- modifier = Modifier.basicMarquee(),
text = label,
style = MaterialTheme.typography.labelMedium,
color = LocalContentColor.current,
textAlign = TextAlign.Center,
- maxLines = 2
+ maxLines = 1,
+ overflow = TextOverflow.Ellipsis,
)
- }
+ },
)
}
}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateContent.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateContent.kt
index d876606154fd..b04d89d8160f 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateContent.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateContent.kt
@@ -19,6 +19,7 @@ package com.android.compose.animation.scene
import androidx.compose.animation.core.Animatable
import androidx.compose.animation.core.AnimationVector1D
import androidx.compose.animation.core.SpringSpec
+import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import com.android.compose.animation.scene.content.state.TransitionState
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
@@ -32,7 +33,10 @@ internal fun CoroutineScope.animateContent(
): Job {
oneOffAnimation.onRun = {
// Animate the progress to its target value.
- val animationSpec = transition.transformationSpec.progressSpec
+ @OptIn(ExperimentalMaterial3ExpressiveApi::class)
+ val animationSpec =
+ transition.transformationSpec.progressSpec
+ ?: layoutState.motionScheme.defaultSpatialSpec()
val visibilityThreshold =
(animationSpec as? SpringSpec)?.visibilityThreshold ?: ProgressVisibilityThreshold
val replacedTransition = transition.replacedTransition
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
index a1117e1bc1db..431a376d8eaf 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
@@ -159,6 +159,9 @@ interface BaseContentScope : ElementStateScope {
/** The state of the [SceneTransitionLayout] in which this content is contained. */
val layoutState: SceneTransitionLayoutState
+ /** The [LookaheadScope] used by the [SceneTransitionLayout]. */
+ val lookaheadScope: LookaheadScope
+
/**
* Tag an element identified by [key].
*
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
index 8153586efbca..5b275a556f93 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
@@ -14,14 +14,22 @@
* limitations under the License.
*/
+@file:OptIn(ExperimentalMaterial3ExpressiveApi::class)
+
package com.android.compose.animation.scene
import android.util.Log
import androidx.annotation.VisibleForTesting
+import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.MotionScheme
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.SideEffect
import androidx.compose.runtime.Stable
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.util.fastAll
import androidx.compose.ui.util.fastAny
@@ -226,6 +234,7 @@ sealed interface MutableSceneTransitionLayoutState : SceneTransitionLayoutState
*/
fun MutableSceneTransitionLayoutState(
initialScene: SceneKey,
+ motionScheme: MotionScheme,
transitions: SceneTransitions = SceneTransitions.Empty,
initialOverlays: Set<OverlayKey> = emptySet(),
canChangeScene: (SceneKey) -> Boolean = { true },
@@ -237,6 +246,7 @@ fun MutableSceneTransitionLayoutState(
): MutableSceneTransitionLayoutState {
return MutableSceneTransitionLayoutStateImpl(
initialScene,
+ motionScheme,
transitions,
initialOverlays,
canChangeScene,
@@ -248,19 +258,62 @@ fun MutableSceneTransitionLayoutState(
)
}
+@Composable
+fun rememberMutableSceneTransitionLayoutState(
+ initialScene: SceneKey,
+ transitions: SceneTransitions = SceneTransitions.Empty,
+ initialOverlays: Set<OverlayKey> = emptySet(),
+ canChangeScene: (SceneKey) -> Boolean = { true },
+ canShowOverlay: (OverlayKey) -> Boolean = { true },
+ canHideOverlay: (OverlayKey) -> Boolean = { true },
+ canReplaceOverlay: (from: OverlayKey, to: OverlayKey) -> Boolean = { _, _ -> true },
+ onTransitionStart: (TransitionState.Transition) -> Unit = {},
+ onTransitionEnd: (TransitionState.Transition) -> Unit = {},
+): MutableSceneTransitionLayoutState {
+ val motionScheme = MaterialTheme.motionScheme
+ val layoutState = remember {
+ MutableSceneTransitionLayoutStateImpl(
+ initialScene = initialScene,
+ motionScheme = motionScheme,
+ transitions = transitions,
+ initialOverlays = initialOverlays,
+ canChangeScene = canChangeScene,
+ canShowOverlay = canShowOverlay,
+ canHideOverlay = canHideOverlay,
+ canReplaceOverlay = canReplaceOverlay,
+ onTransitionStart = onTransitionStart,
+ onTransitionEnd = onTransitionEnd,
+ )
+ }
+
+ SideEffect {
+ layoutState.transitions = transitions
+ layoutState.motionScheme = motionScheme
+ layoutState.canChangeScene = canChangeScene
+ layoutState.canShowOverlay = canShowOverlay
+ layoutState.canHideOverlay = canHideOverlay
+ layoutState.canReplaceOverlay = canReplaceOverlay
+ layoutState.onTransitionStart = onTransitionStart
+ layoutState.onTransitionEnd = onTransitionEnd
+ }
+ return layoutState
+}
+
/** A [MutableSceneTransitionLayoutState] that holds the value for the current scene. */
+@OptIn(ExperimentalMaterial3ExpressiveApi::class)
internal class MutableSceneTransitionLayoutStateImpl(
initialScene: SceneKey,
+ internal var motionScheme: MotionScheme,
override var transitions: SceneTransitions = transitions {},
initialOverlays: Set<OverlayKey> = emptySet(),
- internal val canChangeScene: (SceneKey) -> Boolean = { true },
- internal val canShowOverlay: (OverlayKey) -> Boolean = { true },
- internal val canHideOverlay: (OverlayKey) -> Boolean = { true },
- internal val canReplaceOverlay: (from: OverlayKey, to: OverlayKey) -> Boolean = { _, _ ->
+ internal var canChangeScene: (SceneKey) -> Boolean = { true },
+ internal var canShowOverlay: (OverlayKey) -> Boolean = { true },
+ internal var canHideOverlay: (OverlayKey) -> Boolean = { true },
+ internal var canReplaceOverlay: (from: OverlayKey, to: OverlayKey) -> Boolean = { _, _ ->
true
},
- private val onTransitionStart: (TransitionState.Transition) -> Unit = {},
- private val onTransitionEnd: (TransitionState.Transition) -> Unit = {},
+ internal var onTransitionStart: (TransitionState.Transition) -> Unit = {},
+ internal var onTransitionEnd: (TransitionState.Transition) -> Unit = {},
) : MutableSceneTransitionLayoutState {
private val creationThread: Thread = Thread.currentThread()
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt
index d50304d433f9..52c9ddc96723 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt
@@ -17,10 +17,7 @@
package com.android.compose.animation.scene
import androidx.compose.animation.core.AnimationSpec
-import androidx.compose.animation.core.Spring
-import androidx.compose.animation.core.SpringSpec
import androidx.compose.animation.core.snap
-import androidx.compose.animation.core.spring
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.util.fastForEach
@@ -34,7 +31,6 @@ import com.android.internal.jank.Cuj.CujType
/** The transitions configuration of a [SceneTransitionLayout]. */
class SceneTransitions
internal constructor(
- internal val defaultMotionSpatialSpec: SpringSpec<Float>,
internal val transitionSpecs: List<TransitionSpecImpl>,
internal val interruptionHandler: InterruptionHandler,
) {
@@ -123,16 +119,8 @@ internal constructor(
)
companion object {
- internal val DefaultSwipeSpec =
- spring(
- stiffness = Spring.StiffnessMediumLow,
- dampingRatio = Spring.DampingRatioLowBouncy,
- visibilityThreshold = OffsetVisibilityThreshold,
- )
-
val Empty =
SceneTransitions(
- defaultMotionSpatialSpec = DefaultSwipeSpec,
transitionSpecs = emptyList(),
interruptionHandler = DefaultInterruptionHandler,
)
@@ -188,15 +176,7 @@ internal interface TransformationSpec {
* The [AnimationSpec] used to animate the associated transition progress from `0` to `1` when
* the transition is triggered (i.e. it is not gesture-based).
*/
- val progressSpec: AnimationSpec<Float>
-
- /**
- * The [SpringSpec] used to animate the associated transition progress when the transition was
- * started by a swipe and is now animating back to a scene because the user lifted their finger.
- *
- * If `null`, then the [SceneTransitions.defaultMotionSpatialSpec] will be used.
- */
- val motionSpatialSpec: AnimationSpec<Float>?
+ val progressSpec: AnimationSpec<Float>?
/**
* The distance it takes for this transition to animate from 0% to 100% when it is driven by a
@@ -213,7 +193,6 @@ internal interface TransformationSpec {
internal val Empty =
TransformationSpecImpl(
progressSpec = snap(),
- motionSpatialSpec = null,
distance = null,
transformationMatchers = emptyList(),
)
@@ -246,7 +225,6 @@ internal class TransitionSpecImpl(
val reverse = transformationSpec.invoke(transition)
TransformationSpecImpl(
progressSpec = reverse.progressSpec,
- motionSpatialSpec = reverse.motionSpatialSpec,
distance = reverse.distance,
transformationMatchers =
reverse.transformationMatchers.map {
@@ -275,8 +253,7 @@ internal class TransitionSpecImpl(
* [ElementTransformations].
*/
internal class TransformationSpecImpl(
- override val progressSpec: AnimationSpec<Float>,
- override val motionSpatialSpec: SpringSpec<Float>?,
+ override val progressSpec: AnimationSpec<Float>?,
override val distance: UserActionDistance?,
override val transformationMatchers: List<TransformationMatcher>,
) : TransformationSpec {
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeAnimation.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeAnimation.kt
index 4137f5f5725b..3bd37ad018b0 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeAnimation.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeAnimation.kt
@@ -14,12 +14,15 @@
* limitations under the License.
*/
+@file:OptIn(ExperimentalMaterial3ExpressiveApi::class)
+
package com.android.compose.animation.scene
import androidx.compose.animation.core.Animatable
import androidx.compose.animation.core.AnimationSpec
import androidx.compose.animation.core.AnimationVector1D
import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
@@ -364,10 +367,7 @@ internal class SwipeAnimation<T : ContentKey>(
check(isAnimatingOffset())
- val motionSpatialSpec =
- spec
- ?: contentTransition.transformationSpec.motionSpatialSpec
- ?: layoutState.transitions.defaultMotionSpatialSpec
+ val motionSpatialSpec = spec ?: layoutState.motionScheme.defaultSpatialSpec()
val velocityConsumed = CompletableDeferred<Float>()
offsetAnimationRunnable.complete {
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt
index 776d553ee49c..a29c1bbe0a0c 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt
@@ -19,7 +19,6 @@ package com.android.compose.animation.scene
import androidx.compose.animation.core.AnimationSpec
import androidx.compose.animation.core.Easing
import androidx.compose.animation.core.LinearEasing
-import androidx.compose.animation.core.SpringSpec
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
@@ -37,12 +36,6 @@ fun transitions(builder: SceneTransitionsBuilder.() -> Unit): SceneTransitions {
@TransitionDsl
interface SceneTransitionsBuilder {
/**
- * The default [AnimationSpec] used when after the user lifts their finger after starting a
- * swipe to transition, to animate back into one of the 2 scenes we are transitioning to.
- */
- var defaultMotionSpatialSpec: SpringSpec<Float>
-
- /**
* The [InterruptionHandler] used when transitions are interrupted. Defaults to
* [DefaultInterruptionHandler].
*/
@@ -139,15 +132,7 @@ interface TransitionBuilder : BaseTransitionBuilder {
* The [AnimationSpec] used to animate the associated transition progress from `0` to `1` when
* the transition is triggered (i.e. it is not gesture-based).
*/
- var spec: AnimationSpec<Float>
-
- /**
- * The [SpringSpec] used to animate the associated transition progress when the transition was
- * started by a swipe and is now animating back to a scene because the user lifted their finger.
- *
- * If `null`, then the [SceneTransitionsBuilder.defaultMotionSpatialSpec] will be used.
- */
- var motionSpatialSpec: SpringSpec<Float>?
+ var spec: AnimationSpec<Float>?
/** The CUJ associated to this transitions. */
@CujType var cuj: Int?
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt
index 9a9b05eb3c1d..ccb7024cb2c1 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt
@@ -19,10 +19,7 @@ package com.android.compose.animation.scene
import androidx.compose.animation.core.AnimationSpec
import androidx.compose.animation.core.DurationBasedAnimationSpec
import androidx.compose.animation.core.Easing
-import androidx.compose.animation.core.Spring
-import androidx.compose.animation.core.SpringSpec
import androidx.compose.animation.core.VectorConverter
-import androidx.compose.animation.core.spring
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.unit.Dp
import com.android.compose.animation.scene.content.state.TransitionState
@@ -42,14 +39,12 @@ import com.android.internal.jank.Cuj.CujType
internal fun transitionsImpl(builder: SceneTransitionsBuilder.() -> Unit): SceneTransitions {
val impl = SceneTransitionsBuilderImpl().apply(builder)
return SceneTransitions(
- defaultMotionSpatialSpec = impl.defaultMotionSpatialSpec,
transitionSpecs = impl.transitionSpecs,
interruptionHandler = impl.interruptionHandler,
)
}
private class SceneTransitionsBuilderImpl : SceneTransitionsBuilder {
- override var defaultMotionSpatialSpec: SpringSpec<Float> = SceneTransitions.DefaultSwipeSpec
override var interruptionHandler: InterruptionHandler = DefaultInterruptionHandler
val transitionSpecs = mutableListOf<TransitionSpecImpl>()
@@ -109,7 +104,6 @@ private class SceneTransitionsBuilderImpl : SceneTransitionsBuilder {
val impl = TransitionBuilderImpl(transition).apply(builder)
return TransformationSpecImpl(
progressSpec = impl.spec,
- motionSpatialSpec = impl.motionSpatialSpec,
distance = impl.distance,
transformationMatchers = impl.transformationMatchers,
)
@@ -212,8 +206,7 @@ internal abstract class BaseTransitionBuilderImpl : BaseTransitionBuilder {
internal class TransitionBuilderImpl(override val transition: TransitionState.Transition) :
BaseTransitionBuilderImpl(), TransitionBuilder {
- override var spec: AnimationSpec<Float> = spring(stiffness = Spring.StiffnessLow)
- override var motionSpatialSpec: SpringSpec<Float>? = null
+ override var spec: AnimationSpec<Float>? = null
override var distance: UserActionDistance? = null
override var cuj: Int? = null
private val durationMillis: Int by lazy {
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Content.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Content.kt
index 59b4a09385f5..6ccd498f7a04 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Content.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Content.kt
@@ -17,8 +17,12 @@
package com.android.compose.animation.scene.content
import android.annotation.SuppressLint
+import androidx.compose.animation.core.AnimationSpec
+import androidx.compose.animation.core.AnimationVector
+import androidx.compose.animation.core.TwoWayConverter
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.layout.Box
+import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable
import androidx.compose.runtime.getValue
@@ -27,6 +31,7 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
+import androidx.compose.ui.layout.LookaheadScope
import androidx.compose.ui.layout.approachLayout
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.unit.IntSize
@@ -119,16 +124,28 @@ internal class ContentScopeImpl(
override val layoutState: SceneTransitionLayoutState = layoutImpl.state
+ override val lookaheadScope: LookaheadScope
+ get() = layoutImpl.lookaheadScope
+
+ @OptIn(ExperimentalMaterial3ExpressiveApi::class)
+ private val animationSpatialSpec =
+ object : AnimationSpec<Float> {
+ override fun <V : AnimationVector> vectorize(converter: TwoWayConverter<Float, V>) =
+ layoutImpl.state.motionScheme.defaultSpatialSpec<Float>().vectorize(converter)
+ }
+
private val _verticalOverscrollEffect =
OffsetOverscrollEffect(
orientation = Orientation.Vertical,
animationScope = layoutImpl.animationScope,
+ animationSpec = animationSpatialSpec,
)
private val _horizontalOverscrollEffect =
OffsetOverscrollEffect(
orientation = Orientation.Horizontal,
animationScope = layoutImpl.animationScope,
+ animationSpec = animationSpatialSpec,
)
val verticalOverscrollGestureEffect = GestureEffect(_verticalOverscrollEffect)
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/state/TransitionState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/state/TransitionState.kt
index e9542c830b4d..e23e234b1cad 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/state/TransitionState.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/state/TransitionState.kt
@@ -18,8 +18,7 @@ package com.android.compose.animation.scene.content.state
import androidx.compose.animation.core.Animatable
import androidx.compose.animation.core.AnimationVector1D
-import androidx.compose.animation.core.Spring
-import androidx.compose.animation.core.spring
+import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.runtime.Stable
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
@@ -385,14 +384,13 @@ sealed interface TransitionState {
fun create(): Animatable<Float, AnimationVector1D> {
val animatable = Animatable(1f, visibilityThreshold = ProgressVisibilityThreshold)
layoutImpl.animationScope.launch {
- val motionSpatialSpec = layoutImpl.state.transitions.defaultMotionSpatialSpec
- val progressSpec =
- spring(
- stiffness = motionSpatialSpec.stiffness,
- dampingRatio = Spring.DampingRatioNoBouncy,
- visibilityThreshold = ProgressVisibilityThreshold,
- )
- animatable.animateTo(0f, progressSpec)
+ @OptIn(ExperimentalMaterial3ExpressiveApi::class)
+ animatable.animateTo(
+ targetValue = 0f,
+ // Quickly animate (use fast) the current transition and without bounces
+ // (use effects). A new transition will start soon.
+ animationSpec = layoutImpl.state.motionScheme.fastEffectsSpec(),
+ )
}
return animatable
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/reveal/ContainerReveal.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/reveal/ContainerReveal.kt
index 00cd0ca564b1..2134510557d0 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/reveal/ContainerReveal.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/reveal/ContainerReveal.kt
@@ -20,7 +20,6 @@ import androidx.compose.animation.core.AnimationVector1D
import androidx.compose.animation.core.DeferredTargetAnimation
import androidx.compose.animation.core.ExperimentalAnimatableApi
import androidx.compose.animation.core.FiniteAnimationSpec
-import androidx.compose.animation.core.Spring
import androidx.compose.animation.core.VectorConverter
import androidx.compose.animation.core.spring
import androidx.compose.ui.unit.Dp
@@ -103,14 +102,6 @@ fun TransitionBuilder.verticalContainerReveal(
// The spring animating the alpha of the container.
val alphaSpec = spring<Float>(stiffness = 1200f, dampingRatio = 0.99f)
- // The spring animating the progress when releasing the finger.
- motionSpatialSpec =
- spring(
- stiffness = Spring.StiffnessMediumLow,
- dampingRatio = Spring.DampingRatioNoBouncy,
- visibilityThreshold = 0.5f,
- )
-
// Size transformation.
transformation(container) {
VerticalContainerRevealSizeTransformation(
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/AnimatedSharedAsStateTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/AnimatedSharedAsStateTest.kt
index 62ec2215ca14..d11e6da18ed0 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/AnimatedSharedAsStateTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/AnimatedSharedAsStateTest.kt
@@ -386,7 +386,7 @@ class AnimatedSharedAsStateTest {
@Test
fun animatedValueIsUsingLastTransition() = runTest {
val state =
- rule.runOnUiThread { MutableSceneTransitionLayoutStateImpl(SceneA, transitions {}) }
+ rule.runOnUiThread { MutableSceneTransitionLayoutStateForTests(SceneA, transitions {}) }
val foo = ValueKey("foo")
val bar = ValueKey("bar")
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ContentTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ContentTest.kt
index 06a9735d97e2..dc694269b7af 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ContentTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ContentTest.kt
@@ -42,7 +42,7 @@ class ContentTest {
lateinit var layoutImpl: SceneTransitionLayoutImpl
rule.setContent {
SceneTransitionLayoutForTesting(
- remember { MutableSceneTransitionLayoutState(SceneA) },
+ remember { MutableSceneTransitionLayoutStateForTests(SceneA) },
onLayoutImpl = { layoutImpl = it },
) {
scene(SceneA) {
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt
index ef360770bc41..35ff0044f4d6 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt
@@ -60,7 +60,7 @@ class DraggableHandlerTest {
private class TestGestureScope(val testScope: MonotonicClockTestScope) {
var canChangeScene: (SceneKey) -> Boolean = { true }
val layoutState =
- MutableSceneTransitionLayoutStateImpl(
+ MutableSceneTransitionLayoutStateForTests(
SceneA,
EmptyTestTransitions,
canChangeScene = { canChangeScene(it) },
@@ -640,10 +640,7 @@ class DraggableHandlerTest {
@Test
fun overscroll_releaseBetween0And100Percent_up() = runGestureTest {
// Make scene B overscrollable.
- layoutState.transitions = transitions {
- defaultMotionSpatialSpec = spring(dampingRatio = Spring.DampingRatioNoBouncy)
- from(SceneA, to = SceneB) {}
- }
+ layoutState.transitions = transitions { from(SceneA, to = SceneB) {} }
val dragController =
onDragStarted(
@@ -671,10 +668,7 @@ class DraggableHandlerTest {
@Test
fun overscroll_releaseBetween0And100Percent_down() = runGestureTest {
// Make scene C overscrollable.
- layoutState.transitions = transitions {
- defaultMotionSpatialSpec = spring(dampingRatio = Spring.DampingRatioNoBouncy)
- from(SceneA, to = SceneC) {}
- }
+ layoutState.transitions = transitions { from(SceneA, to = SceneC) {} }
val dragController =
onDragStarted(
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
index b405fbe89302..8d0af9b57f2a 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
@@ -209,7 +209,7 @@ class ElementTest {
val state =
rule.runOnUiThread {
- MutableSceneTransitionLayoutState(
+ MutableSceneTransitionLayoutStateForTests(
SceneA,
transitions {
from(SceneA, to = SceneB) { spec = tween }
@@ -343,7 +343,7 @@ class ElementTest {
@Test
fun elementIsReusedBetweenScenes() {
- val state = rule.runOnUiThread { MutableSceneTransitionLayoutState(SceneA) }
+ val state = rule.runOnUiThread { MutableSceneTransitionLayoutStateForTests(SceneA) }
var sceneCState by mutableStateOf(0)
val key = TestElements.Foo
var nullableLayoutImpl: SceneTransitionLayoutImpl? = null
@@ -473,7 +473,7 @@ class ElementTest {
@Test
fun elementModifierSupportsUpdates() {
- val state = rule.runOnUiThread { MutableSceneTransitionLayoutState(SceneA) }
+ val state = rule.runOnUiThread { MutableSceneTransitionLayoutStateForTests(SceneA) }
var key by mutableStateOf(TestElements.Foo)
var nullableLayoutImpl: SceneTransitionLayoutImpl? = null
@@ -523,7 +523,7 @@ class ElementTest {
rule.waitUntil(timeoutMillis = 10_000) { animationFinished }
}
- val state = rule.runOnUiThread { MutableSceneTransitionLayoutState(SceneA) }
+ val state = rule.runOnUiThread { MutableSceneTransitionLayoutStateForTests(SceneA) }
rule.setContent {
scrollScope = rememberCoroutineScope()
@@ -618,7 +618,7 @@ class ElementTest {
fun layoutGetsCurrentTransitionStateFromComposition() {
val state =
rule.runOnUiThread {
- MutableSceneTransitionLayoutState(
+ MutableSceneTransitionLayoutStateForTests(
SceneA,
transitions {
from(SceneA, to = SceneB) {
@@ -663,7 +663,8 @@ class ElementTest {
// The draggable touch slop, i.e. the min px distance a touch pointer must move before it is
// detected as a drag event.
var touchSlop = 0f
- val state = rule.runOnUiThread { MutableSceneTransitionLayoutState(initialScene = SceneA) }
+ val state =
+ rule.runOnUiThread { MutableSceneTransitionLayoutStateForTests(initialScene = SceneA) }
rule.setContent {
density = LocalDensity.current
touchSlop = LocalViewConfiguration.current.touchSlop
@@ -718,7 +719,8 @@ class ElementTest {
// The draggable touch slop, i.e. the min px distance a touch pointer must move before it is
// detected as a drag event.
var touchSlop = 0f
- val state = rule.runOnUiThread { MutableSceneTransitionLayoutState(initialScene = SceneA) }
+ val state =
+ rule.runOnUiThread { MutableSceneTransitionLayoutStateForTests(initialScene = SceneA) }
rule.setContent {
density = LocalDensity.current
touchSlop = LocalViewConfiguration.current.touchSlop
@@ -813,7 +815,8 @@ class ElementTest {
// The draggable touch slop, i.e. the min px distance a touch pointer must move before it is
// detected as a drag event.
var touchSlop = 0f
- val state = rule.runOnUiThread { MutableSceneTransitionLayoutState(initialScene = SceneA) }
+ val state =
+ rule.runOnUiThread { MutableSceneTransitionLayoutStateForTests(initialScene = SceneA) }
rule.setContent {
density = LocalDensity.current
touchSlop = LocalViewConfiguration.current.touchSlop
@@ -866,7 +869,8 @@ class ElementTest {
val layoutWidth = 200.dp
val layoutHeight = 400.dp
- val state = rule.runOnUiThread { MutableSceneTransitionLayoutState(initialScene = SceneA) }
+ val state =
+ rule.runOnUiThread { MutableSceneTransitionLayoutStateForTests(initialScene = SceneA) }
rule.setContent {
density = LocalDensity.current
@@ -934,7 +938,7 @@ class ElementTest {
val state =
rule.runOnUiThread {
- MutableSceneTransitionLayoutState(
+ MutableSceneTransitionLayoutStateForTests(
initialScene = SceneA,
transitions =
transitions {
@@ -943,7 +947,6 @@ class ElementTest {
}
},
)
- as MutableSceneTransitionLayoutStateImpl
}
rule.setContent {
@@ -999,7 +1002,7 @@ class ElementTest {
val state =
rule.runOnUiThread {
- MutableSceneTransitionLayoutState(
+ MutableSceneTransitionLayoutStateForTests(
SceneA,
transitions {
// Foo is at the top left corner of scene A. We make it disappear during A
@@ -1126,7 +1129,7 @@ class ElementTest {
val state =
rule.runOnUiThread {
- MutableSceneTransitionLayoutState(
+ MutableSceneTransitionLayoutStateForTests(
SceneA,
transitions {
from(SceneA, to = SceneB) { spec = tween(duration, easing = LinearEasing) }
@@ -1331,7 +1334,7 @@ class ElementTest {
val fooSize = 100.dp
val state =
rule.runOnUiThread {
- MutableSceneTransitionLayoutState(
+ MutableSceneTransitionLayoutStateForTests(
SceneA,
transitions {
from(SceneA, to = SceneB) { spec = tween(duration, easing = LinearEasing) }
@@ -1439,7 +1442,7 @@ class ElementTest {
@Test
fun targetStateIsSetEvenWhenNotPlaced() {
// Start directly at A => B but with progress < 0f to overscroll on A.
- val state = rule.runOnUiThread { MutableSceneTransitionLayoutState(SceneA) }
+ val state = rule.runOnUiThread { MutableSceneTransitionLayoutStateForTests(SceneA) }
lateinit var layoutImpl: SceneTransitionLayoutImpl
val scope =
@@ -1473,7 +1476,7 @@ class ElementTest {
fun lastAlphaIsNotSetByOutdatedLayer() {
val state =
rule.runOnUiThread {
- MutableSceneTransitionLayoutState(
+ MutableSceneTransitionLayoutStateForTests(
SceneA,
transitions { from(SceneA, to = SceneB) { fade(TestElements.Foo) } },
)
@@ -1537,7 +1540,7 @@ class ElementTest {
fun fadingElementsDontAppearInstantly() {
val state =
rule.runOnUiThread {
- MutableSceneTransitionLayoutState(
+ MutableSceneTransitionLayoutStateForTests(
SceneA,
transitions { from(SceneA, to = SceneB) { fade(TestElements.Foo) } },
)
@@ -1583,7 +1586,7 @@ class ElementTest {
@Test
fun lastPlacementValuesAreClearedOnNestedElements() {
- val state = rule.runOnIdle { MutableSceneTransitionLayoutState(SceneA) }
+ val state = rule.runOnIdle { MutableSceneTransitionLayoutStateForTests(SceneA) }
@Composable
fun ContentScope.NestedFooBar() {
@@ -1658,7 +1661,7 @@ class ElementTest {
fun currentTransitionSceneIsUsedToComputeElementValues() {
val state =
rule.runOnIdle {
- MutableSceneTransitionLayoutState(
+ MutableSceneTransitionLayoutStateForTests(
SceneA,
transitions {
from(SceneB, to = SceneC) {
@@ -1709,7 +1712,7 @@ class ElementTest {
@Test
fun interruptionDeltasAreProperlyCleaned() {
- val state = rule.runOnIdle { MutableSceneTransitionLayoutState(SceneA) }
+ val state = rule.runOnIdle { MutableSceneTransitionLayoutStateForTests(SceneA) }
@Composable
fun ContentScope.Foo(offset: Dp) {
@@ -1780,7 +1783,7 @@ class ElementTest {
fun transparentElementIsNotImpactingInterruption() {
val state =
rule.runOnIdle {
- MutableSceneTransitionLayoutState(
+ MutableSceneTransitionLayoutStateForTests(
SceneA,
transitions {
from(SceneA, to = SceneB) {
@@ -1856,7 +1859,7 @@ class ElementTest {
@Test
fun replacedTransitionDoesNotTriggerInterruption() {
- val state = rule.runOnIdle { MutableSceneTransitionLayoutState(SceneA) }
+ val state = rule.runOnIdle { MutableSceneTransitionLayoutStateForTests(SceneA) }
@Composable
fun ContentScope.Foo(modifier: Modifier = Modifier) {
@@ -2027,7 +2030,7 @@ class ElementTest {
): SceneTransitionLayoutImpl {
val state =
rule.runOnIdle {
- MutableSceneTransitionLayoutState(
+ MutableSceneTransitionLayoutStateForTests(
from,
transitions { from(from, to = to, preview = preview, builder = transition) },
)
@@ -2174,7 +2177,7 @@ class ElementTest {
)
}
- val state = rule.runOnUiThread { MutableSceneTransitionLayoutState(SceneA) }
+ val state = rule.runOnUiThread { MutableSceneTransitionLayoutStateForTests(SceneA) }
val scope =
rule.setContentAndCreateMainScope {
SceneTransitionLayout(state) {
@@ -2215,7 +2218,7 @@ class ElementTest {
Box(modifier.element(TestElements.Foo).size(50.dp))
}
- val state = rule.runOnUiThread { MutableSceneTransitionLayoutState(SceneA) }
+ val state = rule.runOnUiThread { MutableSceneTransitionLayoutStateForTests(SceneA) }
val scope =
rule.setContentAndCreateMainScope {
SceneTransitionLayout(state) {
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/InterruptionHandlerTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/InterruptionHandlerTest.kt
index 3622369b8ff9..b44f552f844e 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/InterruptionHandlerTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/InterruptionHandlerTest.kt
@@ -39,7 +39,7 @@ class InterruptionHandlerTest {
@Test
fun default() = runMonotonicClockTest {
val state =
- MutableSceneTransitionLayoutState(
+ MutableSceneTransitionLayoutStateForTests(
SceneA,
transitions { /* default interruption handler */ },
)
@@ -62,7 +62,7 @@ class InterruptionHandlerTest {
@Test
fun chainingDisabled() = runMonotonicClockTest {
val state =
- MutableSceneTransitionLayoutState(
+ MutableSceneTransitionLayoutStateForTests(
SceneA,
transitions {
// Handler that animates from currentScene (default) but disables chaining.
@@ -97,7 +97,7 @@ class InterruptionHandlerTest {
fun animateFromOtherScene() = runMonotonicClockTest {
val duration = 500
val state =
- MutableSceneTransitionLayoutState(
+ MutableSceneTransitionLayoutStateForTests(
SceneA,
transitions {
// Handler that animates from the scene that is not currentScene.
@@ -146,7 +146,7 @@ class InterruptionHandlerTest {
@Test
fun animateToFromScene() = runMonotonicClockTest {
- val state = MutableSceneTransitionLayoutStateImpl(SceneA, transitions {})
+ val state = MutableSceneTransitionLayoutStateForTests(SceneA, transitions {})
// Fake a transition from A to B that has a non 0 velocity.
val progressVelocity = 1f
@@ -182,7 +182,7 @@ class InterruptionHandlerTest {
@Test
fun animateToToScene() = runMonotonicClockTest {
- val state = MutableSceneTransitionLayoutStateImpl(SceneA, transitions {})
+ val state = MutableSceneTransitionLayoutStateForTests(SceneA, transitions {})
// Fake a transition from A to B with current scene = A that has a non 0 velocity.
val progressVelocity = -1f
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MovableElementTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MovableElementTest.kt
index 9e1bae577ed2..8d718c1418e0 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MovableElementTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MovableElementTest.kt
@@ -380,7 +380,7 @@ class MovableElementTest {
val state =
rule.runOnUiThread {
- MutableSceneTransitionLayoutState(
+ MutableSceneTransitionLayoutStateForTests(
initialScene = SceneA,
initialOverlays = setOf(OverlayA),
)
@@ -420,7 +420,7 @@ class MovableElementTest {
val state =
rule.runOnUiThread {
- MutableSceneTransitionLayoutState(
+ MutableSceneTransitionLayoutStateForTests(
initialScene = SceneA,
initialOverlays = setOf(OverlayA),
)
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MutableSceneTransitionLayoutStateForTests.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MutableSceneTransitionLayoutStateForTests.kt
new file mode 100644
index 000000000000..4326ec978990
--- /dev/null
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MutableSceneTransitionLayoutStateForTests.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.compose.animation.scene
+
+import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
+import androidx.compose.material3.MotionScheme
+import com.android.compose.animation.scene.content.state.TransitionState
+
+internal fun MutableSceneTransitionLayoutStateForTests(
+ initialScene: SceneKey,
+ transitions: SceneTransitions = SceneTransitions.Empty,
+ initialOverlays: Set<OverlayKey> = emptySet(),
+ canChangeScene: (SceneKey) -> Boolean = { true },
+ canShowOverlay: (OverlayKey) -> Boolean = { true },
+ canHideOverlay: (OverlayKey) -> Boolean = { true },
+ canReplaceOverlay: (from: OverlayKey, to: OverlayKey) -> Boolean = { _, _ -> true },
+ onTransitionStart: (TransitionState.Transition) -> Unit = {},
+ onTransitionEnd: (TransitionState.Transition) -> Unit = {},
+): MutableSceneTransitionLayoutStateImpl {
+ @OptIn(ExperimentalMaterial3ExpressiveApi::class)
+ return MutableSceneTransitionLayoutStateImpl(
+ initialScene,
+ motionScheme = MotionScheme.standard(),
+ transitions,
+ initialOverlays,
+ canChangeScene,
+ canShowOverlay,
+ canHideOverlay,
+ canReplaceOverlay,
+ onTransitionStart,
+ onTransitionEnd,
+ )
+}
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ObservableTransitionStateTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ObservableTransitionStateTest.kt
index 596e2cda95bb..f2c0ca58688c 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ObservableTransitionStateTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ObservableTransitionStateTest.kt
@@ -47,7 +47,9 @@ class ObservableTransitionStateTest {
@Test
fun testObservableTransitionState() = runTest {
val state =
- rule.runOnUiThread { MutableSceneTransitionLayoutState(SceneA, EmptyTestTransitions) }
+ rule.runOnUiThread {
+ MutableSceneTransitionLayoutStateForTests(SceneA, EmptyTestTransitions)
+ }
// Collect the current observable state into [observableState].
// TODO(b/290184746): Use collectValues {} once it is extracted into a library that can be
@@ -106,7 +108,7 @@ class ObservableTransitionStateTest {
@Test
fun observableCurrentScene() = runTestWithSnapshots {
val state =
- MutableSceneTransitionLayoutStateImpl(
+ MutableSceneTransitionLayoutStateForTests(
initialScene = SceneA,
transitions = transitions {},
)
@@ -150,7 +152,7 @@ class ObservableTransitionStateTest {
fun testObservablePreviewTransitionState() = runTest {
val layoutState =
rule.runOnUiThread {
- MutableSceneTransitionLayoutState(
+ MutableSceneTransitionLayoutStateForTests(
SceneA,
transitions = transitions { from(SceneA, to = SceneB, preview = {}) },
)
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/OverlayTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/OverlayTest.kt
index 93fa51654ca1..50bfbfe6d29c 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/OverlayTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/OverlayTest.kt
@@ -73,7 +73,7 @@ class OverlayTest {
@Test
fun showThenHideOverlay() {
- val state = rule.runOnUiThread { MutableSceneTransitionLayoutState(SceneA) }
+ val state = rule.runOnUiThread { MutableSceneTransitionLayoutStateForTests(SceneA) }
lateinit var coroutineScope: CoroutineScope
rule.setContent {
coroutineScope = rememberCoroutineScope()
@@ -115,7 +115,7 @@ class OverlayTest {
@Test
fun multipleOverlays() {
- val state = rule.runOnUiThread { MutableSceneTransitionLayoutState(SceneA) }
+ val state = rule.runOnUiThread { MutableSceneTransitionLayoutStateForTests(SceneA) }
lateinit var coroutineScope: CoroutineScope
rule.setContent {
coroutineScope = rememberCoroutineScope()
@@ -213,7 +213,7 @@ class OverlayTest {
}
}
- val state = rule.runOnUiThread { MutableSceneTransitionLayoutState(SceneA) }
+ val state = rule.runOnUiThread { MutableSceneTransitionLayoutStateForTests(SceneA) }
lateinit var coroutineScope: CoroutineScope
rule.setContent {
coroutineScope = rememberCoroutineScope()
@@ -285,7 +285,7 @@ class OverlayTest {
fun overlayAlignment() {
val state =
rule.runOnUiThread {
- MutableSceneTransitionLayoutState(SceneA, initialOverlays = setOf(OverlayA))
+ MutableSceneTransitionLayoutStateForTests(SceneA, initialOverlays = setOf(OverlayA))
}
var alignment by mutableStateOf(Alignment.Center)
rule.setContent {
@@ -320,7 +320,7 @@ class OverlayTest {
fun overlayMaxSizeIsCurrentSceneSize() {
val state =
rule.runOnUiThread {
- MutableSceneTransitionLayoutState(SceneA, initialOverlays = setOf(OverlayA))
+ MutableSceneTransitionLayoutStateForTests(SceneA, initialOverlays = setOf(OverlayA))
}
val contentTag = "overlayContent"
@@ -742,7 +742,7 @@ class OverlayTest {
@Test
fun overscrollingOverlay_movableElementNotInOverlay() {
- val state = rule.runOnUiThread { MutableSceneTransitionLayoutStateImpl(SceneA) }
+ val state = rule.runOnUiThread { MutableSceneTransitionLayoutStateForTests(SceneA) }
val key = MovableElementKey("Foo", contents = setOf(SceneA))
val movableElementChildTag = "movableElementChildTag"
@@ -769,7 +769,7 @@ class OverlayTest {
@Test
fun overlaysAreModalByDefault() {
- val state = rule.runOnUiThread { MutableSceneTransitionLayoutStateImpl(SceneA) }
+ val state = rule.runOnUiThread { MutableSceneTransitionLayoutStateForTests(SceneA) }
val scrollState = ScrollState(initial = 0)
val scope =
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/PredictiveBackHandlerTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/PredictiveBackHandlerTest.kt
index 4224a0ccfd34..9f15ebd69657 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/PredictiveBackHandlerTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/PredictiveBackHandlerTest.kt
@@ -50,7 +50,7 @@ class PredictiveBackHandlerTest {
@Test
fun testBack() {
- val layoutState = rule.runOnUiThread { MutableSceneTransitionLayoutState(SceneA) }
+ val layoutState = rule.runOnUiThread { MutableSceneTransitionLayoutStateForTests(SceneA) }
rule.setContent {
SceneTransitionLayout(layoutState) {
scene(SceneA, mapOf(Back to SceneB)) { Box(Modifier.fillMaxSize()) }
@@ -70,7 +70,7 @@ class PredictiveBackHandlerTest {
val transitionFrames = 2
val layoutState =
rule.runOnUiThread {
- MutableSceneTransitionLayoutState(
+ MutableSceneTransitionLayoutStateForTests(
SceneA,
transitions =
transitions {
@@ -142,7 +142,7 @@ class PredictiveBackHandlerTest {
fun testPredictiveBackWithPreview() {
val layoutState =
rule.runOnUiThread {
- MutableSceneTransitionLayoutState(
+ MutableSceneTransitionLayoutStateForTests(
SceneA,
transitions = transitions { from(SceneA, to = SceneB, preview = {}) },
)
@@ -192,7 +192,7 @@ class PredictiveBackHandlerTest {
var canChangeSceneCalled = false
val layoutState =
rule.runOnUiThread {
- MutableSceneTransitionLayoutState(
+ MutableSceneTransitionLayoutStateForTests(
SceneA,
canChangeScene = {
canChangeSceneCalled = true
@@ -241,7 +241,7 @@ class PredictiveBackHandlerTest {
fun backDismissesOverlayWithHighestZIndexByDefault() {
val state =
rule.runOnUiThread {
- MutableSceneTransitionLayoutState(
+ MutableSceneTransitionLayoutStateForTests(
SceneA,
initialOverlays = setOf(OverlayA, OverlayB),
)
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt
index f3be5e43c294..c5e4061e834a 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt
@@ -51,7 +51,7 @@ class SceneTransitionLayoutStateTest {
@Test
fun isTransitioningTo_idle() {
- val state = MutableSceneTransitionLayoutStateImpl(SceneA, SceneTransitions.Empty)
+ val state = MutableSceneTransitionLayoutStateForTests(SceneA, SceneTransitions.Empty)
assertThat(state.isTransitioning()).isFalse()
assertThat(state.isTransitioning(from = SceneA)).isFalse()
@@ -61,7 +61,7 @@ class SceneTransitionLayoutStateTest {
@Test
fun isTransitioningTo_transition() = runTest {
- val state = MutableSceneTransitionLayoutStateImpl(SceneA, SceneTransitions.Empty)
+ val state = MutableSceneTransitionLayoutStateForTests(SceneA, SceneTransitions.Empty)
state.startTransitionImmediately(
animationScope = backgroundScope,
transition(from = SceneA, to = SceneB),
@@ -77,13 +77,13 @@ class SceneTransitionLayoutStateTest {
@Test
fun setTargetScene_idleToSameScene() = runMonotonicClockTest {
- val state = MutableSceneTransitionLayoutState(SceneA)
+ val state = MutableSceneTransitionLayoutStateForTests(SceneA)
assertThat(state.setTargetScene(SceneA, animationScope = this)).isNull()
}
@Test
fun setTargetScene_idleToDifferentScene() = runMonotonicClockTest {
- val state = MutableSceneTransitionLayoutState(SceneA)
+ val state = MutableSceneTransitionLayoutStateForTests(SceneA)
val (transition, job) = checkNotNull(state.setTargetScene(SceneB, animationScope = this))
assertThat(state.transitionState).isEqualTo(transition)
@@ -93,7 +93,7 @@ class SceneTransitionLayoutStateTest {
@Test
fun setTargetScene_transitionToSameScene() = runMonotonicClockTest {
- val state = MutableSceneTransitionLayoutState(SceneA)
+ val state = MutableSceneTransitionLayoutStateForTests(SceneA)
val (_, job) = checkNotNull(state.setTargetScene(SceneB, animationScope = this))
assertThat(state.setTargetScene(SceneB, animationScope = this)).isNull()
@@ -104,7 +104,7 @@ class SceneTransitionLayoutStateTest {
@Test
fun setTargetScene_transitionToDifferentScene() = runMonotonicClockTest {
- val state = MutableSceneTransitionLayoutState(SceneA)
+ val state = MutableSceneTransitionLayoutStateForTests(SceneA)
assertThat(state.setTargetScene(SceneB, animationScope = this)).isNotNull()
val (_, job) = checkNotNull(state.setTargetScene(SceneC, animationScope = this))
@@ -115,7 +115,7 @@ class SceneTransitionLayoutStateTest {
@Test
fun setTargetScene_coroutineScopeCancelled() = runMonotonicClockTest {
- val state = MutableSceneTransitionLayoutState(SceneA)
+ val state = MutableSceneTransitionLayoutStateForTests(SceneA)
lateinit var transition: TransitionState.Transition
val job =
@@ -133,7 +133,7 @@ class SceneTransitionLayoutStateTest {
fun setTargetScene_withTransitionKey() = runMonotonicClockTest {
val transitionkey = TransitionKey(debugName = "foo")
val state =
- MutableSceneTransitionLayoutState(
+ MutableSceneTransitionLayoutStateForTests(
SceneA,
transitions =
transitions {
@@ -176,7 +176,7 @@ class SceneTransitionLayoutStateTest {
return { /* do nothing */ }
}
- val state = MutableSceneTransitionLayoutStateImpl(SceneA, EmptyTestTransitions)
+ val state = MutableSceneTransitionLayoutStateForTests(SceneA, EmptyTestTransitions)
val aToB = transition(SceneA, SceneB, onFreezeAndAnimate = ::onFreezeAndAnimate)
val bToC = transition(SceneB, SceneC, onFreezeAndAnimate = ::onFreezeAndAnimate)
val cToA = transition(SceneC, SceneA, onFreezeAndAnimate = ::onFreezeAndAnimate)
@@ -226,7 +226,7 @@ class SceneTransitionLayoutStateTest {
@Test
fun tooManyTransitionsLogsWtfAndClearsTransitions() = runTest {
- val state = MutableSceneTransitionLayoutStateImpl(SceneA, EmptyTestTransitions)
+ val state = MutableSceneTransitionLayoutStateForTests(SceneA, EmptyTestTransitions)
fun startTransition() {
val transition =
@@ -251,7 +251,7 @@ class SceneTransitionLayoutStateTest {
@Test
fun snapToScene() = runMonotonicClockTest {
- val state = MutableSceneTransitionLayoutState(SceneA)
+ val state = MutableSceneTransitionLayoutStateForTests(SceneA)
// Transition to B.
state.setTargetScene(SceneB, animationScope = this)
@@ -266,7 +266,7 @@ class SceneTransitionLayoutStateTest {
@Test
fun snapToScene_freezesCurrentTransition() = runMonotonicClockTest {
- val state = MutableSceneTransitionLayoutStateImpl(SceneA)
+ val state = MutableSceneTransitionLayoutStateForTests(SceneA)
// Start a transition that is never finished. We don't use backgroundScope on purpose so
// that this test would fail if the transition was not frozen when snapping.
@@ -283,7 +283,7 @@ class SceneTransitionLayoutStateTest {
@Test
fun seekToScene() = runMonotonicClockTest {
- val state = MutableSceneTransitionLayoutState(SceneA)
+ val state = MutableSceneTransitionLayoutStateForTests(SceneA)
val progress = Channel<Float>()
val job =
@@ -309,7 +309,7 @@ class SceneTransitionLayoutStateTest {
@Test
fun seekToScene_cancelled() = runMonotonicClockTest {
- val state = MutableSceneTransitionLayoutState(SceneA)
+ val state = MutableSceneTransitionLayoutStateForTests(SceneA)
val progress = Channel<Float>()
val job =
@@ -335,7 +335,7 @@ class SceneTransitionLayoutStateTest {
@Test
fun seekToScene_interrupted() = runMonotonicClockTest {
- val state = MutableSceneTransitionLayoutState(SceneA)
+ val state = MutableSceneTransitionLayoutStateForTests(SceneA)
val progress = Channel<Float>()
val job =
@@ -354,7 +354,7 @@ class SceneTransitionLayoutStateTest {
@Test
fun replacedTransitionIsRemovedFromFinishedTransitions() = runTest {
- val state = MutableSceneTransitionLayoutState(SceneA)
+ val state = MutableSceneTransitionLayoutStateForTests(SceneA)
val aToB =
transition(
@@ -403,7 +403,7 @@ class SceneTransitionLayoutStateTest {
@Test
fun transitionCanBeStartedOnlyOnce() = runTest {
- val state = MutableSceneTransitionLayoutState(SceneA)
+ val state = MutableSceneTransitionLayoutStateForTests(SceneA)
val transition = transition(from = SceneA, to = SceneB)
state.startTransitionImmediately(backgroundScope, transition)
@@ -414,7 +414,7 @@ class SceneTransitionLayoutStateTest {
@Test
fun transitionFinishedWhenScopeIsEmpty() = runTest {
- val state = MutableSceneTransitionLayoutState(SceneA)
+ val state = MutableSceneTransitionLayoutStateForTests(SceneA)
// Start a transition.
val transition = transition(from = SceneA, to = SceneB)
@@ -439,7 +439,7 @@ class SceneTransitionLayoutStateTest {
@Test
fun transitionScopeIsCancelledWhenTransitionIsForceFinished() = runTest {
- val state = MutableSceneTransitionLayoutState(SceneA)
+ val state = MutableSceneTransitionLayoutStateForTests(SceneA)
// Start a transition.
val transition = transition(from = SceneA, to = SceneB)
@@ -458,7 +458,7 @@ class SceneTransitionLayoutStateTest {
@Test
fun badTransitionSpecThrowsMeaningfulMessageWhenStartingTransition() {
val state =
- MutableSceneTransitionLayoutState(
+ MutableSceneTransitionLayoutStateForTests(
SceneA,
transitions {
// This transition definition is bad because they both match when transitioning
@@ -483,7 +483,7 @@ class SceneTransitionLayoutStateTest {
@Test
fun snapToScene_multipleTransitions() = runMonotonicClockTest {
- val state = MutableSceneTransitionLayoutState(SceneA)
+ val state = MutableSceneTransitionLayoutStateForTests(SceneA)
state.startTransitionImmediately(this, transition(SceneA, SceneB))
state.startTransitionImmediately(this, transition(SceneB, SceneC))
state.snapToScene(SceneC)
@@ -498,7 +498,7 @@ class SceneTransitionLayoutStateTest {
val finished = mutableSetOf<TransitionState.Transition>()
val cujWhenStarting = mutableMapOf<TransitionState.Transition, Int?>()
val state =
- MutableSceneTransitionLayoutState(
+ MutableSceneTransitionLayoutStateForTests(
SceneA,
transitions {
// A <=> B.
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt
index e580e3c40690..3c490ae614a3 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt
@@ -17,7 +17,9 @@
package com.android.compose.animation.scene
import androidx.compose.animation.core.FastOutSlowInEasing
+import androidx.compose.animation.core.FiniteAnimationSpec
import androidx.compose.animation.core.LinearEasing
+import androidx.compose.animation.core.spring
import androidx.compose.animation.core.tween
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
@@ -25,9 +27,13 @@ import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.size
+import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.MotionScheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
@@ -60,7 +66,6 @@ import com.android.compose.animation.scene.subjects.assertThat
import com.android.compose.test.assertSizeIsEqualTo
import com.android.compose.test.subjects.DpOffsetSubject
import com.android.compose.test.subjects.assertThat
-import com.android.compose.test.transition
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.CoroutineScope
import org.junit.Assert.assertThrows
@@ -88,7 +93,9 @@ class SceneTransitionLayoutTest {
@Composable
private fun TestContent() {
coroutineScope = rememberCoroutineScope()
- layoutState = remember { MutableSceneTransitionLayoutState(SceneA, EmptyTestTransitions) }
+ layoutState = remember {
+ MutableSceneTransitionLayoutStateForTests(SceneA, EmptyTestTransitions)
+ }
SceneTransitionLayout(state = layoutState, modifier = Modifier.size(LayoutSize)) {
scene(SceneA, userActions = mapOf(Back to SceneB)) {
@@ -317,7 +324,7 @@ class SceneTransitionLayoutTest {
val state =
rule.runOnUiThread {
- MutableSceneTransitionLayoutState(
+ MutableSceneTransitionLayoutStateForTests(
SceneA,
transitions {
from(SceneA, to = SceneB) {
@@ -421,7 +428,7 @@ class SceneTransitionLayoutTest {
assertThrows(IllegalStateException::class.java) {
rule.setContent {
SceneTransitionLayout(
- state = remember { MutableSceneTransitionLayoutState(SceneA) },
+ state = remember { MutableSceneTransitionLayoutStateForTests(SceneA) },
modifier = Modifier.size(LayoutSize),
) {
// from SceneA to SceneA
@@ -436,7 +443,7 @@ class SceneTransitionLayoutTest {
@Test
fun sceneKeyInScope() {
- val state = rule.runOnUiThread { MutableSceneTransitionLayoutState(SceneA) }
+ val state = rule.runOnUiThread { MutableSceneTransitionLayoutStateForTests(SceneA) }
var keyInA: ContentKey? = null
var keyInB: ContentKey? = null
@@ -465,7 +472,7 @@ class SceneTransitionLayoutTest {
lateinit var layoutImpl: SceneTransitionLayoutImpl
rule.setContent {
SceneTransitionLayoutForTesting(
- remember { MutableSceneTransitionLayoutState(SceneA) },
+ remember { MutableSceneTransitionLayoutStateForTests(SceneA) },
onLayoutImpl = { layoutImpl = it },
) {
scene(SceneA) { Box(Modifier.fillMaxSize()) }
@@ -483,7 +490,8 @@ class SceneTransitionLayoutTest {
// The draggable touch slop, i.e. the min px distance a touch pointer must move before it is
// detected as a drag event.
var touchSlop = 0f
- val state = rule.runOnUiThread { MutableSceneTransitionLayoutState(initialScene = SceneA) }
+ val state =
+ rule.runOnUiThread { MutableSceneTransitionLayoutStateForTests(initialScene = SceneA) }
rule.setContent {
touchSlop = LocalViewConfiguration.current.touchSlop
SceneTransitionLayout(state, Modifier.size(layoutWidth, layoutHeight)) {
@@ -511,4 +519,67 @@ class SceneTransitionLayoutTest {
// Fling animation, we are overscrolling now. Progress should always be between [0, 1].
assertThat(transition).hasProgress(1f)
}
+
+ @OptIn(ExperimentalMaterial3ExpressiveApi::class)
+ @Test
+ fun motionSchemeArePassedToSTLState() {
+ // Implementation inspired by MotionScheme.standard()
+ @Suppress("UNCHECKED_CAST")
+ fun motionScheme(animationSpec: FiniteAnimationSpec<Any>) =
+ object : MotionScheme {
+ override fun <T> defaultEffectsSpec() = animationSpec as FiniteAnimationSpec<T>
+
+ override fun <T> defaultSpatialSpec() = animationSpec as FiniteAnimationSpec<T>
+
+ override fun <T> fastEffectsSpec() = animationSpec as FiniteAnimationSpec<T>
+
+ override fun <T> fastSpatialSpec() = animationSpec as FiniteAnimationSpec<T>
+
+ override fun <T> slowEffectsSpec() = animationSpec as FiniteAnimationSpec<T>
+
+ override fun <T> slowSpatialSpec() = animationSpec as FiniteAnimationSpec<T>
+ }
+
+ lateinit var state1: MutableSceneTransitionLayoutState
+ lateinit var state2: MutableSceneTransitionLayoutState
+
+ lateinit var motionScheme1: MotionScheme
+ var motionScheme2 by mutableStateOf(motionScheme(animationSpec = tween(500)))
+ rule.setContent {
+ motionScheme1 = MaterialTheme.motionScheme
+ state1 = rememberMutableSceneTransitionLayoutState(initialScene = SceneA)
+ SceneTransitionLayout(state1) {
+ scene(SceneA, userActions = mapOf(Swipe.Down to SceneB)) {
+ Spacer(Modifier.fillMaxSize())
+ }
+ }
+
+ MaterialTheme(motionScheme = motionScheme2) {
+ // Important: we should read this state inside the MaterialTheme composable.
+ state2 = rememberMutableSceneTransitionLayoutState(initialScene = SceneA)
+ SceneTransitionLayout(state2) {
+ scene(SceneA, userActions = mapOf(Swipe.Down to SceneB)) {
+ Spacer(Modifier.fillMaxSize())
+ }
+ }
+ }
+ }
+
+ assertThat(motionScheme1).isNotNull()
+ assertThat(motionScheme1).isNotEqualTo(motionScheme2)
+
+ assertThat((state1 as MutableSceneTransitionLayoutStateImpl).motionScheme)
+ .isEqualTo(motionScheme1)
+
+ assertThat((state2 as MutableSceneTransitionLayoutStateImpl).motionScheme)
+ .isEqualTo(motionScheme2)
+
+ // Update the MaterialTheme's MotionScheme configuration.
+ motionScheme2 = motionScheme(animationSpec = spring())
+
+ // We just updated the motionScheme2 state, wait for a recomposition.
+ rule.waitForIdle()
+ assertThat((state2 as MutableSceneTransitionLayoutStateImpl).motionScheme)
+ .isEqualTo(motionScheme2)
+ }
}
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt
index e03608466dae..751b31481e3a 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt
@@ -93,7 +93,9 @@ class SwipeToSceneTest {
initialScene: SceneKey = SceneA,
transitions: SceneTransitions = EmptyTestTransitions,
): MutableSceneTransitionLayoutState {
- return rule.runOnUiThread { MutableSceneTransitionLayoutState(initialScene, transitions) }
+ return rule.runOnUiThread {
+ MutableSceneTransitionLayoutStateForTests(initialScene, transitions)
+ }
}
/** The content under test. */
@@ -738,7 +740,7 @@ class SwipeToSceneTest {
fun startEnd_ltrLayout() {
val state =
rule.runOnUiThread {
- MutableSceneTransitionLayoutState(
+ MutableSceneTransitionLayoutStateForTests(
initialScene = SceneA,
transitions =
transitions {
@@ -811,7 +813,7 @@ class SwipeToSceneTest {
fun startEnd_rtlLayout() {
val state =
rule.runOnUiThread {
- MutableSceneTransitionLayoutState(
+ MutableSceneTransitionLayoutStateForTests(
initialScene = SceneA,
transitions =
transitions {
@@ -893,7 +895,9 @@ class SwipeToSceneTest {
Text("Count: $count")
}
- SceneTransitionLayout(remember { MutableSceneTransitionLayoutState(SceneA) }) {
+ SceneTransitionLayout(
+ remember { MutableSceneTransitionLayoutStateForTests(SceneA) }
+ ) {
scene(SceneA) { Box(Modifier.fillMaxSize()) }
}
}
@@ -909,7 +913,7 @@ class SwipeToSceneTest {
@Test
fun swipeToSceneSupportsUpdates() {
- val state = rule.runOnUiThread { MutableSceneTransitionLayoutState(SceneA) }
+ val state = rule.runOnUiThread { MutableSceneTransitionLayoutStateForTests(SceneA) }
rule.setContent {
SceneTransitionLayout(state) {
@@ -943,7 +947,7 @@ class SwipeToSceneTest {
@Test
fun swipeToSceneNodeIsKeptWhenDisabled() {
var hasHorizontalActions by mutableStateOf(false)
- val state = rule.runOnUiThread { MutableSceneTransitionLayoutState(SceneA) }
+ val state = rule.runOnUiThread { MutableSceneTransitionLayoutStateForTests(SceneA) }
var touchSlop = 0f
rule.setContent {
touchSlop = LocalViewConfiguration.current.touchSlop
@@ -983,7 +987,7 @@ class SwipeToSceneTest {
@Test
fun nestedScroll_useFromSourceInfo() {
- val state = rule.runOnUiThread { MutableSceneTransitionLayoutState(SceneA) }
+ val state = rule.runOnUiThread { MutableSceneTransitionLayoutStateForTests(SceneA) }
var touchSlop = 0f
rule.setContent {
touchSlop = LocalViewConfiguration.current.touchSlop
@@ -1033,7 +1037,7 @@ class SwipeToSceneTest {
@Test
fun nestedScroll_ignoreMouseWheel() {
- val state = rule.runOnUiThread { MutableSceneTransitionLayoutState(SceneA) }
+ val state = rule.runOnUiThread { MutableSceneTransitionLayoutStateForTests(SceneA) }
var touchSlop = 0f
rule.setContent {
touchSlop = LocalViewConfiguration.current.touchSlop
@@ -1057,7 +1061,7 @@ class SwipeToSceneTest {
@Test
fun nestedScroll_keepPriorityEvenIfWeCanNoLongerScrollOnThatDirection() {
- val state = rule.runOnUiThread { MutableSceneTransitionLayoutState(SceneA) }
+ val state = rule.runOnUiThread { MutableSceneTransitionLayoutStateForTests(SceneA) }
var touchSlop = 0f
rule.setContent {
touchSlop = LocalViewConfiguration.current.touchSlop
@@ -1097,7 +1101,7 @@ class SwipeToSceneTest {
fun nestedScroll_replaceOverlay() {
val state =
rule.runOnUiThread {
- MutableSceneTransitionLayoutState(SceneA, initialOverlays = setOf(OverlayA))
+ MutableSceneTransitionLayoutStateForTests(SceneA, initialOverlays = setOf(OverlayA))
}
var touchSlop = 0f
rule.setContent {
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt
index aada4a50c89c..e098aac4e18a 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt
@@ -17,9 +17,7 @@
package com.android.compose.animation.scene
import androidx.compose.animation.core.CubicBezierEasing
-import androidx.compose.animation.core.SpringSpec
import androidx.compose.animation.core.TweenSpec
-import androidx.compose.animation.core.spring
import androidx.compose.animation.core.tween
import androidx.compose.ui.unit.IntSize
import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -84,7 +82,7 @@ class TransitionDslTest {
fun defaultTransitionSpec() {
val transitions = transitions { from(SceneA, to = SceneB) }
val transformationSpec = transitions.transitionSpecs.single().transformationSpec(aToB())
- assertThat(transformationSpec.progressSpec).isInstanceOf(SpringSpec::class.java)
+ assertThat(transformationSpec.progressSpec).isNull()
}
@Test
@@ -272,44 +270,10 @@ class TransitionDslTest {
}
@Test
- fun springSpec() {
- val defaultSpec = spring<Float>(stiffness = 1f)
- val specFromAToC = spring<Float>(stiffness = 2f)
- val transitions = transitions {
- defaultMotionSpatialSpec = defaultSpec
-
- from(SceneA, to = SceneB) {
- // Default swipe spec.
- }
- from(SceneA, to = SceneC) { motionSpatialSpec = specFromAToC }
- }
-
- assertThat(transitions.defaultMotionSpatialSpec).isSameInstanceAs(defaultSpec)
-
- // A => B does not have a custom spec.
- assertThat(
- transitions
- .transitionSpec(from = SceneA, to = SceneB, key = null)
- .transformationSpec(aToB())
- .motionSpatialSpec
- )
- .isNull()
-
- // A => C has a custom swipe spec.
- assertThat(
- transitions
- .transitionSpec(from = SceneA, to = SceneC, key = null)
- .transformationSpec(transition(from = SceneA, to = SceneC))
- .motionSpatialSpec
- )
- .isSameInstanceAs(specFromAToC)
- }
-
- @Test
fun transitionIsPassedToBuilder() = runTest {
var transitionPassedToBuilder: TransitionState.Transition? = null
val state =
- MutableSceneTransitionLayoutState(
+ MutableSceneTransitionLayoutStateForTests(
SceneA,
transitions { from(SceneA, to = SceneB) { transitionPassedToBuilder = transition } },
)
@@ -340,7 +304,7 @@ class TransitionDslTest {
}
}
- val state = MutableSceneTransitionLayoutState(SceneA, transitions)
+ val state = MutableSceneTransitionLayoutStateForTests(SceneA, transitions)
assertThrows(IllegalStateException::class.java) {
runBlocking { state.startTransition(transition(from = SceneA, to = SceneB)) }
}
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/NestedElementTransformationTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/NestedElementTransformationTest.kt
index 0da422bcb696..bb511bc27317 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/NestedElementTransformationTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/NestedElementTransformationTest.kt
@@ -37,6 +37,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.compose.animation.scene.ContentScope
import com.android.compose.animation.scene.ElementKey
import com.android.compose.animation.scene.MutableSceneTransitionLayoutState
+import com.android.compose.animation.scene.MutableSceneTransitionLayoutStateForTests
import com.android.compose.animation.scene.Scale
import com.android.compose.animation.scene.SceneKey
import com.android.compose.animation.scene.SceneTransitionLayout
@@ -104,7 +105,9 @@ class NestedElementTransformationTest {
startScene: SceneKey,
transitions: SceneTransitions = SceneTransitions.Empty,
): MutableSceneTransitionLayoutState {
- return rule.runOnUiThread { MutableSceneTransitionLayoutState(startScene, transitions) }
+ return rule.runOnUiThread {
+ MutableSceneTransitionLayoutStateForTests(startScene, transitions)
+ }
}
private val threeNestedStls:
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/NestedSharedElementTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/NestedSharedElementTest.kt
index d8b713625681..83dd6d3eec28 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/NestedSharedElementTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/NestedSharedElementTest.kt
@@ -39,6 +39,7 @@ import com.android.compose.animation.scene.ContentScope
import com.android.compose.animation.scene.Default4FrameLinearTransition
import com.android.compose.animation.scene.Edge
import com.android.compose.animation.scene.MutableSceneTransitionLayoutState
+import com.android.compose.animation.scene.MutableSceneTransitionLayoutStateForTests
import com.android.compose.animation.scene.SceneKey
import com.android.compose.animation.scene.TestElements
import com.android.compose.animation.scene.TestScenes
@@ -94,7 +95,7 @@ class NestedSharedElementTest {
private val nestedState: MutableSceneTransitionLayoutState =
rule.runOnUiThread {
- MutableSceneTransitionLayoutState(
+ MutableSceneTransitionLayoutStateForTests(
Scenes.NestedSceneA,
transitions {
from(
@@ -108,7 +109,7 @@ class NestedSharedElementTest {
private val nestedNestedState: MutableSceneTransitionLayoutState =
rule.runOnUiThread {
- MutableSceneTransitionLayoutState(
+ MutableSceneTransitionLayoutStateForTests(
Scenes.NestedNestedSceneA,
transitions {
from(
diff --git a/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestContentScope.kt b/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestContentScope.kt
index 5cccfb1b319f..6d47babd716a 100644
--- a/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestContentScope.kt
+++ b/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestContentScope.kt
@@ -29,6 +29,6 @@ fun TestContentScope(
currentScene: SceneKey = remember { SceneKey("current") },
content: @Composable ContentScope.() -> Unit,
) {
- val state = remember { MutableSceneTransitionLayoutState(currentScene) }
+ val state = rememberMutableSceneTransitionLayoutState(currentScene)
SceneTransitionLayout(state, modifier) { scene(currentScene, content = content) }
}
diff --git a/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestTransition.kt b/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestTransition.kt
index bc160fc02498..f94a7ed77341 100644
--- a/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestTransition.kt
+++ b/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestTransition.kt
@@ -129,13 +129,12 @@ fun ComposeContentTestRule.testTransition(
builder: TransitionTestBuilder.() -> Unit,
) {
testTransition(
- state =
- runOnUiThread {
- MutableSceneTransitionLayoutState(
- fromScene,
- transitions { from(fromScene, to = toScene, builder = transition) },
- )
- },
+ state = {
+ rememberMutableSceneTransitionLayoutState(
+ fromScene,
+ transitions { from(fromScene, to = toScene, builder = transition) },
+ )
+ },
changeState = changeState,
transitionLayout = { state ->
SceneTransitionLayout(state, layoutModifier) {
@@ -157,13 +156,12 @@ fun ComposeContentTestRule.testShowOverlayTransition(
builder: TransitionTestBuilder.() -> Unit,
) {
testTransition(
- state =
- runOnUiThread {
- MutableSceneTransitionLayoutState(
- fromScene,
- transitions = transitions { from(fromScene, overlay, builder = transition) },
- )
- },
+ state = {
+ rememberMutableSceneTransitionLayoutState(
+ fromScene,
+ transitions = transitions { from(fromScene, overlay, builder = transition) },
+ )
+ },
transitionLayout = { state ->
SceneTransitionLayout(state) {
scene(fromScene) { fromSceneContent() }
@@ -185,14 +183,13 @@ fun ComposeContentTestRule.testHideOverlayTransition(
builder: TransitionTestBuilder.() -> Unit,
) {
testTransition(
- state =
- runOnUiThread {
- MutableSceneTransitionLayoutState(
- toScene,
- initialOverlays = setOf(overlay),
- transitions = transitions { from(overlay, toScene, builder = transition) },
- )
- },
+ state = {
+ rememberMutableSceneTransitionLayoutState(
+ toScene,
+ initialOverlays = setOf(overlay),
+ transitions = transitions { from(overlay, toScene, builder = transition) },
+ )
+ },
transitionLayout = { state ->
SceneTransitionLayout(state) {
scene(toScene) { toSceneContent() }
@@ -218,14 +215,13 @@ fun ComposeContentTestRule.testReplaceOverlayTransition(
builder: TransitionTestBuilder.() -> Unit,
) {
testTransition(
- state =
- runOnUiThread {
- MutableSceneTransitionLayoutState(
- currentScene,
- initialOverlays = setOf(from),
- transitions = transitions { from(from, to, builder = transition) },
- )
- },
+ state = {
+ rememberMutableSceneTransitionLayoutState(
+ currentScene,
+ initialOverlays = setOf(from),
+ transitions = transitions { from(from, to, builder = transition) },
+ )
+ },
transitionLayout = { state ->
SceneTransitionLayout(state) {
scene(currentScene) { currentSceneContent() }
@@ -263,16 +259,14 @@ fun MotionTestRule<ComposeToolkit>.recordTransition(
fromScene: SceneKey = TestScenes.SceneA,
toScene: SceneKey = TestScenes.SceneB,
): RecordedMotion {
- val state =
- toolkit.composeContentTestRule.runOnUiThread {
- MutableSceneTransitionLayoutState(
- fromScene,
- transitions { from(fromScene, to = toScene, builder = transition) },
- )
- }
-
+ lateinit var state: MutableSceneTransitionLayoutState
return recordMotion(
content = { play ->
+ state =
+ rememberMutableSceneTransitionLayoutState(
+ fromScene,
+ transitions { from(fromScene, to = toScene, builder = transition) },
+ )
LaunchedEffect(play) {
if (play) {
state.setTargetScene(toScene, animationScope = this)
@@ -309,7 +303,7 @@ fun ComposeContentTestRule.testTransition(
}
testTransition(
- state = state,
+ state = { state },
changeState = { state -> state.setTargetScene(to, animationScope = this) },
transitionLayout = transitionLayout,
builder = builder,
@@ -323,7 +317,7 @@ fun ComposeContentTestRule.testNestedTransition(
builder: TransitionTestBuilder.() -> Unit,
) {
testTransition(
- state = states[0],
+ state = { states[0] },
changeState = { changeState(states) },
transitionLayout = { transitionLayout(states) },
builder = builder,
@@ -331,16 +325,18 @@ fun ComposeContentTestRule.testNestedTransition(
}
/** Test the transition from [state] to [to]. */
-fun ComposeContentTestRule.testTransition(
- state: MutableSceneTransitionLayoutState,
+private fun ComposeContentTestRule.testTransition(
+ state: @Composable () -> MutableSceneTransitionLayoutState,
changeState: CoroutineScope.(MutableSceneTransitionLayoutState) -> Unit,
transitionLayout: @Composable (state: MutableSceneTransitionLayoutState) -> Unit,
builder: TransitionTestBuilder.() -> Unit,
) {
lateinit var coroutineScope: CoroutineScope
+ lateinit var layoutState: MutableSceneTransitionLayoutState
setContent {
+ layoutState = state()
coroutineScope = rememberCoroutineScope()
- transitionLayout(state)
+ transitionLayout(layoutState)
}
val assertionScope =
@@ -390,7 +386,7 @@ fun ComposeContentTestRule.testTransition(
mainClock.autoAdvance = false
// Change the current scene.
- runOnUiThread { coroutineScope.changeState(state) }
+ runOnUiThread { coroutineScope.changeState(layoutState) }
waitForIdle()
mainClock.advanceTimeByFrame()
waitForIdle()
diff --git a/packages/SystemUI/log/src/com/android/systemui/log/LogBuffer.kt b/packages/SystemUI/log/src/com/android/systemui/log/LogBuffer.kt
index 4b5e9de2cce7..72304a19c17d 100644
--- a/packages/SystemUI/log/src/com/android/systemui/log/LogBuffer.kt
+++ b/packages/SystemUI/log/src/com/android/systemui/log/LogBuffer.kt
@@ -75,6 +75,7 @@ constructor(
private val maxSize: Int,
private val logcatEchoTracker: LogcatEchoTracker,
private val systrace: Boolean = true,
+ private val systraceTrackName: String = DEFAULT_LOGBUFFER_TRACK_NAME,
) : MessageBuffer {
private val buffer = RingBuffer(maxSize) { LogMessageImpl.create() }
@@ -244,10 +245,11 @@ constructor(
}
private fun echoToSystrace(level: LogLevel, tag: String, strMessage: String) {
+ if (!Trace.isEnabled()) return
Trace.instantForTrack(
Trace.TRACE_TAG_APP,
- "UI Events",
- "$name - ${level.shortString} $tag: $strMessage"
+ systraceTrackName,
+ "$name - ${level.shortString} $tag: $strMessage",
)
}
@@ -261,6 +263,10 @@ constructor(
LogLevel.WTF -> Log.wtf(message.tag, strMessage, message.exception)
}
}
+
+ companion object {
+ const val DEFAULT_LOGBUFFER_TRACK_NAME = "UI Events"
+ }
}
private const val TAG = "LogBuffer"
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/fontscaling/FontScalingDialogDelegateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/fontscaling/FontScalingDialogDelegateTest.kt
index 73efea764e59..2713bb0f3e23 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/fontscaling/FontScalingDialogDelegateTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/fontscaling/FontScalingDialogDelegateTest.kt
@@ -316,5 +316,7 @@ class FontScalingDialogDelegateTest : SysuiTestCase() {
dialog.onConfigurationChanged(config)
testableLooper.processAllMessages()
assertThat(doneButton.isEnabled).isTrue()
+
+ dialog.dismiss()
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalDreamStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalDreamStartableTest.kt
index e531e654cd34..00d5afe26f0a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalDreamStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalDreamStartableTest.kt
@@ -16,14 +16,17 @@
package com.android.systemui.communal
+import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
+import android.platform.test.flag.junit.FlagsParameterization
import android.service.dream.dreamManager
-import androidx.test.ext.junit.runners.AndroidJUnit4
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.communal.domain.interactor.communalSceneInteractor
import com.android.systemui.communal.domain.interactor.communalSettingsInteractor
+import com.android.systemui.communal.domain.interactor.setCommunalV2Enabled
import com.android.systemui.flags.Flags.COMMUNAL_SERVICE_ENABLED
import com.android.systemui.flags.fakeFeatureFlagsClassic
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
@@ -48,12 +51,14 @@ import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@EnableFlags(Flags.FLAG_COMMUNAL_HUB)
-@RunWith(AndroidJUnit4::class)
-class CommunalDreamStartableTest : SysuiTestCase() {
+@RunWith(ParameterizedAndroidJunit4::class)
+class CommunalDreamStartableTest(flags: FlagsParameterization) : SysuiTestCase() {
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
@@ -63,26 +68,50 @@ class CommunalDreamStartableTest : SysuiTestCase() {
private val keyguardRepository by lazy { kosmos.fakeKeyguardRepository }
private val powerRepository by lazy { kosmos.fakePowerRepository }
+ init {
+ mSetFlagsRule.setFlagsParameterization(flags)
+ }
+
@Before
fun setUp() {
kosmos.fakeFeatureFlagsClassic.set(COMMUNAL_SERVICE_ENABLED, true)
underTest =
CommunalDreamStartable(
- powerInteractor = kosmos.powerInteractor,
- communalSettingsInteractor = kosmos.communalSettingsInteractor,
- keyguardInteractor = kosmos.keyguardInteractor,
- keyguardTransitionInteractor = kosmos.keyguardTransitionInteractor,
- dreamManager = dreamManager,
- communalSceneInteractor = kosmos.communalSceneInteractor,
- bgScope = kosmos.applicationCoroutineScope,
- )
- .apply { start() }
+ powerInteractor = kosmos.powerInteractor,
+ communalSettingsInteractor = kosmos.communalSettingsInteractor,
+ keyguardInteractor = kosmos.keyguardInteractor,
+ keyguardTransitionInteractor = kosmos.keyguardTransitionInteractor,
+ dreamManager = dreamManager,
+ communalSceneInteractor = kosmos.communalSceneInteractor,
+ bgScope = kosmos.applicationCoroutineScope,
+ )
}
+ @EnableFlags(FLAG_GLANCEABLE_HUB_V2)
+ @Test
+ fun dreamNotStartedWhenTransitioningToHub() =
+ testScope.runTest {
+ // Enable v2 flag and recreate + rerun start method.
+ kosmos.setCommunalV2Enabled(true)
+ underTest.start()
+
+ keyguardRepository.setKeyguardShowing(true)
+ keyguardRepository.setDreaming(false)
+ powerRepository.setScreenPowerState(ScreenPowerState.SCREEN_ON)
+ whenever(dreamManager.canStartDreaming(/* isScreenOn= */ true)).thenReturn(true)
+ runCurrent()
+
+ transition(from = KeyguardState.DREAMING, to = KeyguardState.GLANCEABLE_HUB)
+
+ verify(dreamManager, never()).startDream()
+ }
+
+ @DisableFlags(FLAG_GLANCEABLE_HUB_V2)
@Test
fun startDreamWhenTransitioningToHub() =
testScope.runTest {
+ underTest.start()
keyguardRepository.setKeyguardShowing(true)
keyguardRepository.setDreaming(false)
powerRepository.setScreenPowerState(ScreenPowerState.SCREEN_ON)
@@ -100,6 +129,7 @@ class CommunalDreamStartableTest : SysuiTestCase() {
@EnableFlags(Flags.FLAG_RESTART_DREAM_ON_UNOCCLUDE)
fun restartDreamingWhenTransitioningFromDreamingToOccludedToDreaming() =
testScope.runTest {
+ underTest.start()
keyguardRepository.setDreaming(false)
powerRepository.setScreenPowerState(ScreenPowerState.SCREEN_ON)
whenever(dreamManager.canStartDreaming(/* isScreenOn= */ true)).thenReturn(true)
@@ -122,9 +152,11 @@ class CommunalDreamStartableTest : SysuiTestCase() {
verify(dreamManager).startDream()
}
+ @DisableFlags(FLAG_GLANCEABLE_HUB_V2)
@Test
fun shouldNotStartDreamWhenIneligibleToDream() =
testScope.runTest {
+ underTest.start()
keyguardRepository.setDreaming(false)
powerRepository.setScreenPowerState(ScreenPowerState.SCREEN_ON)
// Not eligible to dream
@@ -134,9 +166,11 @@ class CommunalDreamStartableTest : SysuiTestCase() {
verify(dreamManager, never()).startDream()
}
+ @DisableFlags(FLAG_GLANCEABLE_HUB_V2)
@Test
fun shouldNotStartDreamIfAlreadyDreaming() =
testScope.runTest {
+ underTest.start()
keyguardRepository.setDreaming(true)
powerRepository.setScreenPowerState(ScreenPowerState.SCREEN_ON)
whenever(dreamManager.canStartDreaming(/* isScreenOn= */ true)).thenReturn(true)
@@ -145,9 +179,11 @@ class CommunalDreamStartableTest : SysuiTestCase() {
verify(dreamManager, never()).startDream()
}
+ @DisableFlags(FLAG_GLANCEABLE_HUB_V2)
@Test
fun shouldNotStartDreamForInvalidTransition() =
testScope.runTest {
+ underTest.start()
keyguardRepository.setDreaming(true)
powerRepository.setScreenPowerState(ScreenPowerState.SCREEN_ON)
whenever(dreamManager.canStartDreaming(/* isScreenOn= */ true)).thenReturn(true)
@@ -160,9 +196,11 @@ class CommunalDreamStartableTest : SysuiTestCase() {
}
}
+ @DisableFlags(FLAG_GLANCEABLE_HUB_V2)
@Test
fun shouldNotStartDreamWhenLaunchingWidget() =
testScope.runTest {
+ underTest.start()
keyguardRepository.setKeyguardShowing(true)
keyguardRepository.setDreaming(false)
powerRepository.setScreenPowerState(ScreenPowerState.SCREEN_ON)
@@ -175,9 +213,11 @@ class CommunalDreamStartableTest : SysuiTestCase() {
verify(dreamManager, never()).startDream()
}
+ @DisableFlags(FLAG_GLANCEABLE_HUB_V2)
@Test
fun shouldNotStartDreamWhenOccluded() =
testScope.runTest {
+ underTest.start()
keyguardRepository.setKeyguardShowing(true)
keyguardRepository.setDreaming(false)
powerRepository.setScreenPowerState(ScreenPowerState.SCREEN_ON)
@@ -194,8 +234,16 @@ class CommunalDreamStartableTest : SysuiTestCase() {
kosmos.fakeKeyguardTransitionRepository.sendTransitionSteps(
from = from,
to = to,
- testScope = this
+ testScope = this,
)
runCurrent()
}
+
+ companion object {
+ @JvmStatic
+ @Parameters(name = "{0}")
+ fun getParams(): List<FlagsParameterization> {
+ return FlagsParameterization.allCombinationsOf(FLAG_GLANCEABLE_HUB_V2)
+ }
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt
index 5921e9479bd9..0df584ff4dc1 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt
@@ -58,6 +58,7 @@ import com.android.systemui.communal.domain.interactor.communalInteractor
import com.android.systemui.communal.domain.interactor.communalSettingsInteractor
import com.android.systemui.communal.domain.interactor.setCommunalAvailable
import com.android.systemui.communal.domain.interactor.setCommunalV2ConfigEnabled
+import com.android.systemui.communal.domain.interactor.setCommunalV2Enabled
import com.android.systemui.communal.shared.log.CommunalUiEvent
import com.android.systemui.communal.shared.model.CommunalScenes
import com.android.systemui.complication.ComplicationHostViewController
@@ -747,7 +748,7 @@ class DreamOverlayServiceTest(flags: FlagsParameterization?) : SysuiTestCase() {
@Test
@EnableFlags(Flags.FLAG_DREAM_WAKE_REDIRECT, FLAG_COMMUNAL_HUB)
- @DisableFlags(FLAG_SCENE_CONTAINER)
+ @DisableFlags(FLAG_SCENE_CONTAINER, FLAG_GLANCEABLE_HUB_V2)
@kotlin.Throws(RemoteException::class)
fun testTransitionToGlanceableHub() =
testScope.runTest {
@@ -774,6 +775,7 @@ class DreamOverlayServiceTest(flags: FlagsParameterization?) : SysuiTestCase() {
@Test
@EnableFlags(Flags.FLAG_DREAM_WAKE_REDIRECT, FLAG_SCENE_CONTAINER, FLAG_COMMUNAL_HUB)
+ @DisableFlags(FLAG_GLANCEABLE_HUB_V2)
@kotlin.Throws(RemoteException::class)
fun testTransitionToGlanceableHub_sceneContainer() =
testScope.runTest {
@@ -802,7 +804,29 @@ class DreamOverlayServiceTest(flags: FlagsParameterization?) : SysuiTestCase() {
}
@Test
+ @EnableFlags(Flags.FLAG_DREAM_WAKE_REDIRECT, FLAG_COMMUNAL_HUB, FLAG_GLANCEABLE_HUB_V2)
+ @Throws(RemoteException::class)
+ fun testRedirect_v2Enabled_notTriggered() =
+ testScope.runTest {
+ kosmos.setCommunalV2Enabled(true)
+ // Inform the overlay service of dream starting. Do not show dream complications.
+ client.startDream(
+ mWindowParams,
+ mDreamOverlayCallback,
+ DREAM_COMPONENT,
+ false /*isPreview*/,
+ false, /*shouldShowComplication*/
+ )
+ // Set communal available, verify that onRedirectWake is never called.
+ kosmos.setCommunalAvailable(true)
+ mMainExecutor.runAllReady()
+ runCurrent()
+ verify(mDreamOverlayCallback, never()).onRedirectWake(any())
+ }
+
+ @Test
@EnableFlags(Flags.FLAG_DREAM_WAKE_REDIRECT, FLAG_COMMUNAL_HUB)
+ @DisableFlags(FLAG_GLANCEABLE_HUB_V2)
@Throws(RemoteException::class)
fun testRedirectExit() =
testScope.runTest {
@@ -1347,7 +1371,11 @@ class DreamOverlayServiceTest(flags: FlagsParameterization?) : SysuiTestCase() {
@JvmStatic
@Parameters(name = "{0}")
fun getParams(): List<FlagsParameterization> {
- return FlagsParameterization.allCombinationsOf(FLAG_COMMUNAL_HUB).andSceneContainer()
+ return FlagsParameterization.allCombinationsOf(
+ FLAG_COMMUNAL_HUB,
+ FLAG_GLANCEABLE_HUB_V2,
+ )
+ .andSceneContainer()
}
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfigTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfigTest.kt
index bf49186a7f01..451ebf32c367 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfigTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfigTest.kt
@@ -27,7 +27,7 @@ import android.provider.Settings.Secure.ZEN_DURATION_FOREVER
import android.provider.Settings.Secure.ZEN_DURATION_PROMPT
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
-import com.android.settingslib.notification.modes.EnableZenModeDialog
+import com.android.settingslib.notification.modes.EnableDndDialogFactory
import com.android.settingslib.notification.modes.TestModeBuilder.MANUAL_DND
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.Expandable
@@ -85,7 +85,7 @@ class DoNotDisturbQuickAffordanceConfigTest : SysuiTestCase() {
@Mock private lateinit var zenModeController: ZenModeController
@Mock private lateinit var userTracker: UserTracker
@Mock private lateinit var conditionUri: Uri
- @Mock private lateinit var enableZenModeDialog: EnableZenModeDialog
+ @Mock private lateinit var mEnableDndDialogFactory: EnableDndDialogFactory
@Captor private lateinit var spyZenMode: ArgumentCaptor<Int>
@Captor private lateinit var spyConditionId: ArgumentCaptor<Uri?>
@@ -105,7 +105,7 @@ class DoNotDisturbQuickAffordanceConfigTest : SysuiTestCase() {
testDispatcher,
testScope.backgroundScope,
conditionUri,
- enableZenModeDialog,
+ mEnableDndDialogFactory,
)
}
@@ -322,7 +322,7 @@ class DoNotDisturbQuickAffordanceConfigTest : SysuiTestCase() {
testScope.runTest {
val expandable: Expandable = mock()
secureSettingsRepository.setInt(Settings.Secure.ZEN_DURATION, ZEN_DURATION_PROMPT)
- whenever(enableZenModeDialog.createDialog()).thenReturn(mock())
+ whenever(mEnableDndDialogFactory.createDialog()).thenReturn(mock())
collectLastValue(underTest.lockScreenState)
runCurrent()
@@ -344,7 +344,7 @@ class DoNotDisturbQuickAffordanceConfigTest : SysuiTestCase() {
whenever(zenModeController.isZenAvailable).thenReturn(true)
whenever(zenModeController.zen).thenReturn(ZEN_MODE_OFF)
settings.putInt(Settings.Secure.ZEN_DURATION, ZEN_DURATION_PROMPT)
- whenever(enableZenModeDialog.createDialog()).thenReturn(mock())
+ whenever(mEnableDndDialogFactory.createDialog()).thenReturn(mock())
collectLastValue(underTest.lockScreenState)
runCurrent()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
index fe9da0ddb3c1..88c8b1fbb4ce 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
@@ -19,6 +19,7 @@ package com.android.systemui.keyguard.domain.interactor
import android.app.admin.DevicePolicyManager
import android.os.UserHandle
+import android.view.accessibility.AccessibilityManager
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.internal.widget.LockPatternUtils
@@ -96,6 +97,7 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() {
@Mock private lateinit var shadeInteractor: ShadeInteractor
@Mock private lateinit var logger: KeyguardQuickAffordancesLogger
@Mock private lateinit var metricsLogger: KeyguardQuickAffordancesMetricsLogger
+ @Mock private lateinit var accessibilityManager: AccessibilityManager
private lateinit var underTest: KeyguardQuickAffordanceInteractor
@@ -199,11 +201,13 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() {
backgroundDispatcher = kosmos.testDispatcher,
appContext = context,
communalSettingsInteractor = kosmos.communalSettingsInteractor,
+ accessibilityManager = accessibilityManager,
sceneInteractor = { kosmos.sceneInteractor },
)
kosmos.keyguardQuickAffordanceInteractor = underTest
whenever(shadeInteractor.anyExpansion).thenReturn(MutableStateFlow(0f))
+ whenever(accessibilityManager.isEnabled()).thenReturn(false)
}
@Test
@@ -672,6 +676,22 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() {
}
@Test
+ fun useLongPress_withA11yEnabled_isFalse() =
+ testScope.runTest {
+ whenever(accessibilityManager.isEnabled()).thenReturn(true)
+ val useLongPress by collectLastValue(underTest.useLongPress())
+ assertThat(useLongPress).isFalse()
+ }
+
+ @Test
+ fun useLongPress_withA11yDisabled_isFalse() =
+ testScope.runTest {
+ whenever(accessibilityManager.isEnabled()).thenReturn(false)
+ val useLongPress by collectLastValue(underTest.useLongPress())
+ assertThat(useLongPress).isTrue()
+ }
+
+ @Test
fun useLongPress_whenDocked_isFalse() =
testScope.runTest {
dockManager.setIsDocked(true)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodToPrimaryBouncerTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodToPrimaryBouncerTransitionViewModelTest.kt
index 4936f8559bfb..d782d1e2612c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodToPrimaryBouncerTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodToPrimaryBouncerTransitionViewModelTest.kt
@@ -15,16 +15,25 @@
*/
package com.android.systemui.keyguard.ui.viewmodel
+import android.platform.test.annotations.EnableFlags
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectValues
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.shared.model.TransitionState.FINISHED
+import com.android.systemui.keyguard.shared.model.TransitionState.RUNNING
+import com.android.systemui.keyguard.shared.model.TransitionState.STARTED
import com.android.systemui.keyguard.shared.model.TransitionStep
import com.android.systemui.keyguard.ui.transitions.blurConfig
import com.android.systemui.kosmos.testScope
import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.junit.runner.RunWith
@@ -46,17 +55,33 @@ class AodToPrimaryBouncerTransitionViewModelTest : SysuiTestCase() {
transitionProgress = listOf(0.0f, 0.0f, 0.3f, 0.4f, 0.5f, 1.0f),
startValue = kosmos.blurConfig.maxBlurRadiusPx,
endValue = kosmos.blurConfig.maxBlurRadiusPx,
- transitionFactory = { value, state ->
- TransitionStep(
- from = KeyguardState.AOD,
- to = KeyguardState.PRIMARY_BOUNCER,
- value = value,
- transitionState = state,
- ownerName = "AodToPrimaryBouncerTransitionViewModelTest",
- )
- },
+ transitionFactory = ::step,
actualValuesProvider = { values },
checkInterpolatedValues = false,
)
}
+
+ @Test
+ @EnableFlags(Flags.FLAG_BOUNCER_UI_REVAMP)
+ fun aodToPrimaryBouncerHidesLockscreen() =
+ testScope.runTest {
+ val lockscreenAlpha by collectValues(underTest.lockscreenAlpha)
+ val notificationAlpha by collectValues(underTest.notificationAlpha)
+
+ val transitionSteps = listOf(step(0.0f, STARTED), step(0.5f), step(1.0f, FINISHED))
+ kosmos.fakeKeyguardTransitionRepository.sendTransitionSteps(transitionSteps, testScope)
+ runCurrent()
+
+ lockscreenAlpha.forEach { assertThat(it).isEqualTo(0.0f) }
+ notificationAlpha.forEach { assertThat(it).isEqualTo(0.0f) }
+ }
+
+ private fun step(value: Float, transitionState: TransitionState = RUNNING) =
+ TransitionStep(
+ from = KeyguardState.AOD,
+ to = KeyguardState.PRIMARY_BOUNCER,
+ value = value,
+ transitionState = transitionState,
+ ownerName = "AodToPrimaryBouncerTransitionViewModelTest",
+ )
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DozingToPrimaryBouncerTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DozingToPrimaryBouncerTransitionViewModelTest.kt
index 0d487509a83f..4d58f7ab118e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DozingToPrimaryBouncerTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DozingToPrimaryBouncerTransitionViewModelTest.kt
@@ -16,21 +16,26 @@
package com.android.systemui.keyguard.ui.viewmodel
+import android.platform.test.annotations.EnableFlags
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectValues
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.shared.model.TransitionState.FINISHED
import com.android.systemui.keyguard.shared.model.TransitionState.RUNNING
+import com.android.systemui.keyguard.shared.model.TransitionState.STARTED
import com.android.systemui.keyguard.shared.model.TransitionStep
import com.android.systemui.keyguard.ui.transitions.blurConfig
import com.android.systemui.kosmos.testScope
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
@@ -85,6 +90,21 @@ class DozingToPrimaryBouncerTransitionViewModelTest : SysuiTestCase() {
)
}
+ @Test
+ @EnableFlags(Flags.FLAG_BOUNCER_UI_REVAMP)
+ fun dozingToPrimaryBouncerHidesLockscreen() =
+ testScope.runTest {
+ val lockscreenAlpha by collectValues(underTest.lockscreenAlpha)
+ val notificationAlpha by collectValues(underTest.notificationAlpha)
+
+ val transitionSteps = listOf(step(0.0f, STARTED), step(0.5f), step(1.0f, FINISHED))
+ kosmos.fakeKeyguardTransitionRepository.sendTransitionSteps(transitionSteps, testScope)
+ runCurrent()
+
+ lockscreenAlpha.forEach { assertThat(it).isEqualTo(0.0f) }
+ notificationAlpha.forEach { assertThat(it).isEqualTo(0.0f) }
+ }
+
private fun step(value: Float, state: TransitionState = RUNNING): TransitionStep {
return TransitionStep(
from = KeyguardState.DOZING,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/ModesTileTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/ModesTileTest.kt
index 9173ac969324..f005375a2ef9 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/ModesTileTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/ModesTileTest.kt
@@ -48,6 +48,7 @@ import com.android.systemui.res.R
import com.android.systemui.statusbar.policy.data.repository.zenModeRepository
import com.android.systemui.statusbar.policy.domain.interactor.zenModeInteractor
import com.android.systemui.statusbar.policy.ui.dialog.ModesDialogDelegate
+import com.android.systemui.statusbar.policy.ui.dialog.modesDialogEventLogger
import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.any
import com.android.systemui.util.settings.FakeSettings
@@ -123,7 +124,12 @@ class ModesTileTest : SysuiTestCase() {
)
userActionInteractor =
- ModesTileUserActionInteractor(inputHandler, dialogDelegate, kosmos.zenModeInteractor)
+ ModesTileUserActionInteractor(
+ inputHandler,
+ dialogDelegate,
+ kosmos.zenModeInteractor,
+ kosmos.modesDialogEventLogger,
+ )
underTest =
ModesTile(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/logging/QSTileLoggerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/logging/QSTileLoggerTest.kt
index a06353171c33..6a33b5f58820 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/logging/QSTileLoggerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/logging/QSTileLoggerTest.kt
@@ -54,7 +54,7 @@ class QSTileLoggerTest : SysuiTestCase() {
@Before
fun setup() {
MockitoAnnotations.initMocks(this)
- whenever(logBufferFactory.create(any(), any(), any(), any())).thenReturn(logBuffer)
+ whenever(logBufferFactory.create(any(), any(), any(), any(), any())).thenReturn(logBuffer)
val tileSpec: TileSpec = TileSpec.create("chatty_tile")
underTest =
QSTileLogger(mapOf(tileSpec to chattyLogBuffer), logBufferFactory, statusBarController)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractorTest.kt
index c8b3aba9b846..89b8e9171076 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractorTest.kt
@@ -38,6 +38,7 @@ import com.android.systemui.qs.tiles.impl.modes.domain.model.ModesTileModel
import com.android.systemui.statusbar.policy.data.repository.zenModeRepository
import com.android.systemui.statusbar.policy.domain.interactor.zenModeInteractor
import com.android.systemui.statusbar.policy.ui.dialog.mockModesDialogDelegate
+import com.android.systemui.statusbar.policy.ui.dialog.modesDialogEventLogger
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -60,7 +61,12 @@ class ModesTileUserActionInteractorTest : SysuiTestCase() {
private val zenModeInteractor = kosmos.zenModeInteractor
private val underTest =
- ModesTileUserActionInteractor(inputHandler, mockDialogDelegate, zenModeInteractor)
+ ModesTileUserActionInteractor(
+ inputHandler,
+ mockDialogDelegate,
+ zenModeInteractor,
+ kosmos.modesDialogEventLogger,
+ )
@Test
fun handleClick_active_showsDialog() = runTest {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelTest.kt
index 56a4ed078670..75262a4d058d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelTest.kt
@@ -23,6 +23,7 @@ import android.view.View
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.animation.Expandable
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.kosmos.Kosmos
@@ -35,7 +36,14 @@ import com.android.systemui.statusbar.chips.ui.model.ColorsModel
import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
import com.android.systemui.statusbar.chips.ui.view.ChipBackgroundContainer
import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
+import com.android.systemui.statusbar.core.StatusBarRootModernization
+import com.android.systemui.statusbar.notification.data.model.activeNotificationModel
+import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
+import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationsStore
+import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository
import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel
+import com.android.systemui.statusbar.notification.shared.CallType
+import com.android.systemui.statusbar.phone.ongoingcall.StatusBarChipsModernization
import com.android.systemui.statusbar.phone.ongoingcall.data.repository.ongoingCallRepository
import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallModel
import com.android.systemui.statusbar.phone.ongoingcall.shared.model.inCallModel
@@ -44,6 +52,7 @@ 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.any
import org.mockito.kotlin.mock
import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever
@@ -52,6 +61,7 @@ import org.mockito.kotlin.whenever
@RunWith(AndroidJUnit4::class)
class CallChipViewModelTest : SysuiTestCase() {
private val kosmos = Kosmos()
+ private val notificationListRepository = kosmos.activeNotificationListRepository
private val testScope = kosmos.testScope
private val repo = kosmos.ongoingCallRepository
@@ -65,6 +75,8 @@ class CallChipViewModelTest : SysuiTestCase() {
)
.thenReturn(chipBackgroundView)
}
+ private val mockExpandable: Expandable =
+ mock<Expandable>().apply { whenever(dialogTransitionController(any())).thenReturn(mock()) }
private val underTest by lazy { kosmos.callChipViewModel }
@@ -337,23 +349,25 @@ class CallChipViewModelTest : SysuiTestCase() {
}
@Test
+ @DisableFlags(StatusBarChipsModernization.FLAG_NAME)
fun chip_inCall_nullIntent_nullClickListener() =
testScope.runTest {
val latest by collectLastValue(underTest.chip)
repo.setOngoingCallState(inCallModel(startTimeMs = 1000, intent = null))
- assertThat((latest as OngoingActivityChipModel.Shown).onClickListener).isNull()
+ assertThat((latest as OngoingActivityChipModel.Shown).onClickListenerLegacy).isNull()
}
@Test
+ @DisableFlags(StatusBarChipsModernization.FLAG_NAME)
fun chip_inCall_positiveStartTime_validIntent_clickListenerLaunchesIntent() =
testScope.runTest {
val latest by collectLastValue(underTest.chip)
val pendingIntent = mock<PendingIntent>()
repo.setOngoingCallState(inCallModel(startTimeMs = 1000, intent = pendingIntent))
- val clickListener = (latest as OngoingActivityChipModel.Shown).onClickListener
+ val clickListener = (latest as OngoingActivityChipModel.Shown).onClickListenerLegacy
assertThat(clickListener).isNotNull()
clickListener!!.onClick(chipView)
@@ -364,13 +378,15 @@ class CallChipViewModelTest : SysuiTestCase() {
}
@Test
+ @DisableFlags(StatusBarChipsModernization.FLAG_NAME)
fun chip_inCall_zeroStartTime_validIntent_clickListenerLaunchesIntent() =
testScope.runTest {
val latest by collectLastValue(underTest.chip)
val pendingIntent = mock<PendingIntent>()
repo.setOngoingCallState(inCallModel(startTimeMs = 0, intent = pendingIntent))
- val clickListener = (latest as OngoingActivityChipModel.Shown).onClickListener
+ val clickListener = (latest as OngoingActivityChipModel.Shown).onClickListenerLegacy
+
assertThat(clickListener).isNotNull()
clickListener!!.onClick(chipView)
@@ -380,6 +396,72 @@ class CallChipViewModelTest : SysuiTestCase() {
verify(kosmos.activityStarter).postStartActivityDismissingKeyguard(pendingIntent, null)
}
+ @Test
+ @EnableFlags(StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME)
+ fun chip_inCall_nullIntent_noneClickBehavior() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.chip)
+
+ postOngoingCallNotification(
+ repository = notificationListRepository,
+ startTimeMs = 1000L,
+ intent = null,
+ )
+
+ assertThat((latest as OngoingActivityChipModel.Shown).clickBehavior)
+ .isInstanceOf(OngoingActivityChipModel.ClickBehavior.None::class.java)
+ }
+
+ @Test
+ @EnableFlags(StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME)
+ fun chip_inCall_positiveStartTime_validIntent_clickBehaviorLaunchesIntent() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.chip)
+
+ val pendingIntent = mock<PendingIntent>()
+ postOngoingCallNotification(
+ repository = notificationListRepository,
+ startTimeMs = 1000L,
+ intent = pendingIntent,
+ )
+
+ val clickBehavior = (latest as OngoingActivityChipModel.Shown).clickBehavior
+ assertThat(clickBehavior)
+ .isInstanceOf(OngoingActivityChipModel.ClickBehavior.ExpandAction::class.java)
+ (clickBehavior as OngoingActivityChipModel.ClickBehavior.ExpandAction).onClick(
+ mockExpandable
+ )
+
+ // Ensure that the SysUI didn't modify the notification's intent by verifying it
+ // directly matches the `PendingIntent` set -- see b/212467440.
+ verify(kosmos.activityStarter).postStartActivityDismissingKeyguard(pendingIntent, null)
+ }
+
+ @Test
+ @EnableFlags(StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME)
+ fun chip_inCall_zeroStartTime_validIntent_clickBehaviorLaunchesIntent() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.chip)
+
+ val pendingIntent = mock<PendingIntent>()
+ postOngoingCallNotification(
+ repository = notificationListRepository,
+ startTimeMs = 0L,
+ intent = pendingIntent,
+ )
+
+ val clickBehavior = (latest as OngoingActivityChipModel.Shown).clickBehavior
+ assertThat(clickBehavior)
+ .isInstanceOf(OngoingActivityChipModel.ClickBehavior.ExpandAction::class.java)
+ (clickBehavior as OngoingActivityChipModel.ClickBehavior.ExpandAction).onClick(
+ mockExpandable
+ )
+
+ // Ensure that the SysUI didn't modify the notification's intent by verifying it
+ // directly matches the `PendingIntent` set -- see b/212467440.
+ verify(kosmos.activityStarter).postStartActivityDismissingKeyguard(pendingIntent, null)
+ }
+
companion object {
fun createStatusBarIconViewOrNull(): StatusBarIconView? =
if (StatusBarConnectedDisplays.isEnabled) {
@@ -388,6 +470,27 @@ class CallChipViewModelTest : SysuiTestCase() {
mock<StatusBarIconView>()
}
+ fun postOngoingCallNotification(
+ repository: ActiveNotificationListRepository,
+ startTimeMs: Long,
+ intent: PendingIntent?,
+ ) {
+ repository.activeNotifications.value =
+ ActiveNotificationsStore.Builder()
+ .apply {
+ addIndividualNotif(
+ activeNotificationModel(
+ key = "notif1",
+ whenTime = startTimeMs,
+ callType = CallType.Ongoing,
+ statusBarChipIcon = null,
+ contentIntent = intent,
+ )
+ )
+ }
+ .build()
+ }
+
private val PROMOTED_CONTENT_WITH_COLOR =
PromotedNotificationContentModel.Builder("notif")
.apply {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModelTest.kt
index c511c433d92d..fcf8c834dc12 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModelTest.kt
@@ -17,6 +17,7 @@
package com.android.systemui.statusbar.chips.casttootherdevice.ui.viewmodel
import android.content.DialogInterface
+import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
import android.view.View
import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -25,6 +26,7 @@ import com.android.internal.jank.Cuj
import com.android.systemui.Flags.FLAG_STATUS_BAR_SHOW_AUDIO_ONLY_PROJECTION_CHIP
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.DialogCuj
+import com.android.systemui.animation.Expandable
import com.android.systemui.animation.mockDialogTransitionAnimator
import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.shared.model.Icon
@@ -46,8 +48,10 @@ import com.android.systemui.statusbar.chips.ui.model.ColorsModel
import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
import com.android.systemui.statusbar.chips.ui.view.ChipBackgroundContainer
import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipsViewModelTest.Companion.getStopActionFromDialog
+import com.android.systemui.statusbar.core.StatusBarRootModernization
import com.android.systemui.statusbar.phone.SystemUIDialog
import com.android.systemui.statusbar.phone.mockSystemUIDialogFactory
+import com.android.systemui.statusbar.phone.ongoingcall.StatusBarChipsModernization
import com.android.systemui.statusbar.policy.CastDevice
import com.android.systemui.util.time.fakeSystemClock
import com.google.common.truth.Truth.assertThat
@@ -84,6 +88,8 @@ class CastToOtherDeviceChipViewModelTest : SysuiTestCase() {
)
.thenReturn(chipBackgroundView)
}
+ private val mockExpandable: Expandable =
+ mock<Expandable>().apply { whenever(dialogTransitionController(any())).thenReturn(mock()) }
private val underTest = kosmos.castToOtherDeviceChipViewModel
@@ -263,7 +269,13 @@ class CastToOtherDeviceChipViewModelTest : SysuiTestCase() {
// WHEN the stop action on the dialog is clicked
val dialogStopAction =
- getStopActionFromDialog(latest, chipView, mockScreenCastDialog, kosmos)
+ getStopActionFromDialog(
+ latest,
+ chipView,
+ mockExpandable,
+ mockScreenCastDialog,
+ kosmos,
+ )
dialogStopAction.onClick(mock<DialogInterface>(), 0)
// THEN the chip is immediately hidden...
@@ -296,7 +308,13 @@ class CastToOtherDeviceChipViewModelTest : SysuiTestCase() {
// WHEN the stop action on the dialog is clicked
val dialogStopAction =
- getStopActionFromDialog(latest, chipView, mockGenericCastDialog, kosmos)
+ getStopActionFromDialog(
+ latest,
+ chipView,
+ mockExpandable,
+ mockGenericCastDialog,
+ kosmos,
+ )
dialogStopAction.onClick(mock<DialogInterface>(), 0)
// THEN the chip is immediately hidden...
@@ -416,13 +434,14 @@ class CastToOtherDeviceChipViewModelTest : SysuiTestCase() {
}
@Test
+ @DisableFlags(StatusBarChipsModernization.FLAG_NAME)
fun chip_projectionStateEntireScreen_clickListenerShowsScreenCastDialog() =
testScope.runTest {
val latest by collectLastValue(underTest.chip)
mediaProjectionRepo.mediaProjectionState.value =
MediaProjectionState.Projecting.EntireScreen(CAST_TO_OTHER_DEVICES_PACKAGE)
- val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListener)
+ val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListenerLegacy)
assertThat(clickListener).isNotNull()
clickListener!!.onClick(chipView)
@@ -431,6 +450,7 @@ class CastToOtherDeviceChipViewModelTest : SysuiTestCase() {
}
@Test
+ @DisableFlags(StatusBarChipsModernization.FLAG_NAME)
fun chip_projectionStateSingleTask_clickListenerShowsScreenCastDialog() =
testScope.runTest {
val latest by collectLastValue(underTest.chip)
@@ -442,7 +462,7 @@ class CastToOtherDeviceChipViewModelTest : SysuiTestCase() {
createTask(taskId = 1),
)
- val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListener)
+ val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListenerLegacy)
assertThat(clickListener).isNotNull()
clickListener!!.onClick(chipView)
@@ -451,6 +471,7 @@ class CastToOtherDeviceChipViewModelTest : SysuiTestCase() {
}
@Test
+ @DisableFlags(StatusBarChipsModernization.FLAG_NAME)
fun chip_routerStateCasting_clickListenerShowsGenericCastDialog() =
testScope.runTest {
val latest by collectLastValue(underTest.chip)
@@ -466,7 +487,7 @@ class CastToOtherDeviceChipViewModelTest : SysuiTestCase() {
)
)
- val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListener)
+ val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListenerLegacy)
assertThat(clickListener).isNotNull()
clickListener!!.onClick(chipView)
@@ -480,13 +501,14 @@ class CastToOtherDeviceChipViewModelTest : SysuiTestCase() {
}
@Test
+ @DisableFlags(StatusBarChipsModernization.FLAG_NAME)
fun chip_projectionStateCasting_clickListenerHasCuj() =
testScope.runTest {
val latest by collectLastValue(underTest.chip)
mediaProjectionRepo.mediaProjectionState.value =
MediaProjectionState.Projecting.EntireScreen(CAST_TO_OTHER_DEVICES_PACKAGE)
- val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListener)
+ val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListenerLegacy)
clickListener!!.onClick(chipView)
val cujCaptor = argumentCaptor<DialogCuj>()
@@ -499,6 +521,7 @@ class CastToOtherDeviceChipViewModelTest : SysuiTestCase() {
}
@Test
+ @DisableFlags(StatusBarChipsModernization.FLAG_NAME)
fun chip_routerStateCasting_clickListenerHasCuj() =
testScope.runTest {
val latest by collectLastValue(underTest.chip)
@@ -514,7 +537,7 @@ class CastToOtherDeviceChipViewModelTest : SysuiTestCase() {
)
)
- val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListener)
+ val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListenerLegacy)
clickListener!!.onClick(chipView)
val cujCaptor = argumentCaptor<DialogCuj>()
@@ -525,4 +548,103 @@ class CastToOtherDeviceChipViewModelTest : SysuiTestCase() {
.isEqualTo(Cuj.CUJ_STATUS_BAR_LAUNCH_DIALOG_FROM_CHIP)
assertThat(cujCaptor.firstValue.tag).contains("Cast")
}
+
+ @Test
+ @EnableFlags(StatusBarChipsModernization.FLAG_NAME)
+ fun chip_routerStateCasting_hasClickBehavior() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.chip)
+
+ mediaRouterRepo.castDevices.value =
+ listOf(
+ CastDevice(
+ state = CastDevice.CastState.Connected,
+ id = "id",
+ name = "name",
+ description = "desc",
+ origin = CastDevice.CastOrigin.MediaRouter,
+ )
+ )
+
+ assertThat((latest as OngoingActivityChipModel.Shown).clickBehavior)
+ .isInstanceOf(OngoingActivityChipModel.ClickBehavior.ExpandAction::class.java)
+ }
+
+ @Test
+ @EnableFlags(StatusBarChipsModernization.FLAG_NAME)
+ fun chip_projectionStateCasting_hasClickBehavior() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.chip)
+
+ mediaProjectionRepo.mediaProjectionState.value =
+ MediaProjectionState.Projecting.EntireScreen(CAST_TO_OTHER_DEVICES_PACKAGE)
+
+ assertThat((latest as OngoingActivityChipModel.Shown).clickBehavior)
+ .isInstanceOf(OngoingActivityChipModel.ClickBehavior.ExpandAction::class.java)
+ }
+
+ @Test
+ @EnableFlags(StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME)
+ fun chip_projectionStateEntireScreen_clickBehaviorShowsScreenCastDialog() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.chip)
+ mediaProjectionRepo.mediaProjectionState.value =
+ MediaProjectionState.Projecting.EntireScreen(CAST_TO_OTHER_DEVICES_PACKAGE)
+
+ val expandAction =
+ ((latest as OngoingActivityChipModel.Shown).clickBehavior
+ as OngoingActivityChipModel.ClickBehavior.ExpandAction)
+ expandAction.onClick(mockExpandable)
+
+ verify(kosmos.mockDialogTransitionAnimator)
+ .show(eq(mockScreenCastDialog), any(), anyBoolean())
+ }
+
+ @Test
+ @EnableFlags(StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME)
+ fun chip_projectionStateSingleTask_clickBehaviorShowsScreenCastDialog() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.chip)
+
+ mediaProjectionRepo.mediaProjectionState.value =
+ MediaProjectionState.Projecting.SingleTask(
+ CAST_TO_OTHER_DEVICES_PACKAGE,
+ hostDeviceName = null,
+ createTask(taskId = 1),
+ )
+
+ val expandAction =
+ ((latest as OngoingActivityChipModel.Shown).clickBehavior
+ as OngoingActivityChipModel.ClickBehavior.ExpandAction)
+ expandAction.onClick(mockExpandable)
+
+ verify(kosmos.mockDialogTransitionAnimator)
+ .show(eq(mockScreenCastDialog), any(), anyBoolean())
+ }
+
+ @Test
+ @EnableFlags(StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME)
+ fun chip_routerStateCasting_clickBehaviorShowsGenericCastDialog() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.chip)
+
+ mediaRouterRepo.castDevices.value =
+ listOf(
+ CastDevice(
+ state = CastDevice.CastState.Connected,
+ id = "id",
+ name = "name",
+ description = "desc",
+ origin = CastDevice.CastOrigin.MediaRouter,
+ )
+ )
+
+ val expandAction =
+ ((latest as OngoingActivityChipModel.Shown).clickBehavior
+ as OngoingActivityChipModel.ClickBehavior.ExpandAction)
+ expandAction.onClick(mockExpandable)
+
+ verify(kosmos.mockDialogTransitionAnimator)
+ .show(eq(mockGenericCastDialog), any(), anyBoolean())
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt
index 902db5e10589..eec23d3ffb1a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt
@@ -650,7 +650,7 @@ class NotifChipsViewModelTest : SysuiTestCase() {
)
val chip = latest!![0]
- chip.onClickListener!!.onClick(mock<View>())
+ chip.onClickListenerLegacy!!.onClick(mock<View>())
assertThat(latestChipTap).isEqualTo("clickTest")
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModelTest.kt
index 48d8add6b33a..1f82dcd9c308 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModelTest.kt
@@ -17,12 +17,15 @@
package com.android.systemui.statusbar.chips.screenrecord.ui.viewmodel
import android.content.DialogInterface
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
import android.view.View
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.internal.jank.Cuj
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.DialogCuj
+import com.android.systemui.animation.Expandable
import com.android.systemui.animation.mockDialogTransitionAnimator
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.coroutines.collectLastValue
@@ -41,8 +44,10 @@ import com.android.systemui.statusbar.chips.ui.model.ColorsModel
import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
import com.android.systemui.statusbar.chips.ui.view.ChipBackgroundContainer
import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipsViewModelTest.Companion.getStopActionFromDialog
+import com.android.systemui.statusbar.core.StatusBarRootModernization
import com.android.systemui.statusbar.phone.SystemUIDialog
import com.android.systemui.statusbar.phone.mockSystemUIDialogFactory
+import com.android.systemui.statusbar.phone.ongoingcall.StatusBarChipsModernization
import com.android.systemui.testKosmos
import com.android.systemui.util.time.fakeSystemClock
import com.google.common.truth.Truth.assertThat
@@ -77,6 +82,8 @@ class ScreenRecordChipViewModelTest : SysuiTestCase() {
)
.thenReturn(chipBackgroundView)
}
+ private val mockExpandable: Expandable =
+ mock<Expandable>().apply { whenever(dialogTransitionController(any())).thenReturn(mock()) }
private val underTest = kosmos.screenRecordChipViewModel
@@ -106,7 +113,7 @@ class ScreenRecordChipViewModelTest : SysuiTestCase() {
assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown.Countdown::class.java)
assertThat((latest as OngoingActivityChipModel.Shown).icon).isNull()
- assertThat((latest as OngoingActivityChipModel.Shown).onClickListener).isNull()
+ assertThat((latest as OngoingActivityChipModel.Shown).onClickListenerLegacy).isNull()
}
// The millis we typically get from [ScreenRecordRepository] are around 2995, 1995, and 995.
@@ -177,7 +184,13 @@ class ScreenRecordChipViewModelTest : SysuiTestCase() {
// WHEN the stop action on the dialog is clicked
val dialogStopAction =
- getStopActionFromDialog(latest, chipView, mockSystemUIDialog, kosmos)
+ getStopActionFromDialog(
+ latest,
+ chipView,
+ mockExpandable,
+ mockSystemUIDialog,
+ kosmos,
+ )
dialogStopAction.onClick(mock<DialogInterface>(), 0)
// THEN both the screen record chip and the share-to-app chip are immediately hidden...
@@ -263,13 +276,14 @@ class ScreenRecordChipViewModelTest : SysuiTestCase() {
}
@Test
+ @DisableFlags(StatusBarChipsModernization.FLAG_NAME)
fun chip_notProjecting_clickListenerShowsDialog() =
testScope.runTest {
val latest by collectLastValue(underTest.chip)
screenRecordRepo.screenRecordState.value = ScreenRecordModel.Recording
mediaProjectionRepo.mediaProjectionState.value = MediaProjectionState.NotProjecting
- val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListener)
+ val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListenerLegacy)
assertThat(clickListener).isNotNull()
clickListener!!.onClick(chipView)
@@ -279,6 +293,7 @@ class ScreenRecordChipViewModelTest : SysuiTestCase() {
}
@Test
+ @DisableFlags(StatusBarChipsModernization.FLAG_NAME)
fun chip_projectingEntireScreen_clickListenerShowsDialog() =
testScope.runTest {
val latest by collectLastValue(underTest.chip)
@@ -286,7 +301,7 @@ class ScreenRecordChipViewModelTest : SysuiTestCase() {
mediaProjectionRepo.mediaProjectionState.value =
MediaProjectionState.Projecting.EntireScreen("host.package")
- val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListener)
+ val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListenerLegacy)
assertThat(clickListener).isNotNull()
clickListener!!.onClick(chipView)
@@ -296,6 +311,7 @@ class ScreenRecordChipViewModelTest : SysuiTestCase() {
}
@Test
+ @DisableFlags(StatusBarChipsModernization.FLAG_NAME)
fun chip_projectingSingleTask_clickListenerShowsDialog() =
testScope.runTest {
val latest by collectLastValue(underTest.chip)
@@ -307,7 +323,7 @@ class ScreenRecordChipViewModelTest : SysuiTestCase() {
FakeActivityTaskManager.createTask(taskId = 1),
)
- val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListener)
+ val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListenerLegacy)
assertThat(clickListener).isNotNull()
clickListener!!.onClick(chipView)
@@ -317,22 +333,85 @@ class ScreenRecordChipViewModelTest : SysuiTestCase() {
}
@Test
- fun chip_clickListenerHasCuj() =
+ @DisableFlags(StatusBarChipsModernization.FLAG_NAME)
+ fun chip_clickListenerHasCujLegacy() =
testScope.runTest {
val latest by collectLastValue(underTest.chip)
screenRecordRepo.screenRecordState.value = ScreenRecordModel.Recording
mediaProjectionRepo.mediaProjectionState.value =
MediaProjectionState.Projecting.EntireScreen("host.package")
- val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListener)
+ val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListenerLegacy)
clickListener!!.onClick(chipView)
val cujCaptor = argumentCaptor<DialogCuj>()
verify(kosmos.mockDialogTransitionAnimator)
.showFromView(any(), any(), cujCaptor.capture(), anyBoolean())
-
assertThat(cujCaptor.firstValue.cujType)
.isEqualTo(Cuj.CUJ_STATUS_BAR_LAUNCH_DIALOG_FROM_CHIP)
assertThat(cujCaptor.firstValue.tag).contains("Screen record")
}
+
+ @Test
+ @EnableFlags(StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME)
+ fun chip_recordingState_hasClickBehavior() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.chip)
+ screenRecordRepo.screenRecordState.value = ScreenRecordModel.Recording
+ assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown.Timer::class.java)
+ assertThat((latest as OngoingActivityChipModel.Shown).clickBehavior)
+ .isInstanceOf(OngoingActivityChipModel.ClickBehavior.ExpandAction::class.java)
+ }
+
+ @Test
+ @EnableFlags(StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME)
+ fun chip_notProjecting_expandActionBehaviorShowsDialog() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.chip)
+ screenRecordRepo.screenRecordState.value = ScreenRecordModel.Recording
+ mediaProjectionRepo.mediaProjectionState.value = MediaProjectionState.NotProjecting
+
+ val expandAction =
+ ((latest as OngoingActivityChipModel.Shown).clickBehavior
+ as OngoingActivityChipModel.ClickBehavior.ExpandAction)
+
+ expandAction.onClick(mockExpandable)
+ verify(kosmos.mockDialogTransitionAnimator).show(any(), any(), anyBoolean())
+ }
+
+ @Test
+ @EnableFlags(StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME)
+ fun chip_projectingEntireScreen_expandActionBehaviorShowsDialog() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.chip)
+ screenRecordRepo.screenRecordState.value = ScreenRecordModel.Recording
+
+ val expandAction =
+ ((latest as OngoingActivityChipModel.Shown).clickBehavior
+ as OngoingActivityChipModel.ClickBehavior.ExpandAction)
+
+ expandAction.onClick(mockExpandable)
+ verify(kosmos.mockDialogTransitionAnimator).show(any(), any(), anyBoolean())
+ }
+
+ @Test
+ @EnableFlags(StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME)
+ fun chip_projectingSingleTask_expandActionBehaviorShowsDialog() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.chip)
+ screenRecordRepo.screenRecordState.value = ScreenRecordModel.Recording
+ mediaProjectionRepo.mediaProjectionState.value =
+ MediaProjectionState.Projecting.SingleTask(
+ "host.package",
+ hostDeviceName = null,
+ FakeActivityTaskManager.createTask(taskId = 1),
+ )
+
+ val expandAction =
+ ((latest as OngoingActivityChipModel.Shown).clickBehavior
+ as OngoingActivityChipModel.ClickBehavior.ExpandAction)
+
+ expandAction.onClick(mockExpandable)
+ verify(kosmos.mockDialogTransitionAnimator).show(any(), any(), anyBoolean())
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModelTest.kt
index b3dec2eaa1c6..36fc5aa16407 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModelTest.kt
@@ -17,6 +17,7 @@
package com.android.systemui.statusbar.chips.sharetoapp.ui.viewmodel
import android.content.DialogInterface
+import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
import android.view.View
import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -25,6 +26,7 @@ import com.android.internal.jank.Cuj
import com.android.systemui.Flags.FLAG_STATUS_BAR_SHOW_AUDIO_ONLY_PROJECTION_CHIP
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.DialogCuj
+import com.android.systemui.animation.Expandable
import com.android.systemui.animation.mockDialogTransitionAnimator
import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.shared.model.Icon
@@ -45,8 +47,10 @@ import com.android.systemui.statusbar.chips.ui.model.ColorsModel
import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
import com.android.systemui.statusbar.chips.ui.view.ChipBackgroundContainer
import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipsViewModelTest.Companion.getStopActionFromDialog
+import com.android.systemui.statusbar.core.StatusBarRootModernization
import com.android.systemui.statusbar.phone.SystemUIDialog
import com.android.systemui.statusbar.phone.mockSystemUIDialogFactory
+import com.android.systemui.statusbar.phone.ongoingcall.StatusBarChipsModernization
import com.android.systemui.util.time.fakeSystemClock
import com.google.common.truth.Truth.assertThat
import kotlin.test.Test
@@ -81,6 +85,8 @@ class ShareToAppChipViewModelTest : SysuiTestCase() {
)
.thenReturn(chipBackgroundView)
}
+ private val mockExpandable: Expandable =
+ mock<Expandable>().apply { whenever(dialogTransitionController(any())).thenReturn(mock()) }
private val underTest = kosmos.shareToAppChipViewModel
@@ -215,7 +221,13 @@ class ShareToAppChipViewModelTest : SysuiTestCase() {
// WHEN the stop action on the dialog is clicked
val dialogStopAction =
- getStopActionFromDialog(latest, chipView, mockScreenShareDialog, kosmos)
+ getStopActionFromDialog(
+ latest,
+ chipView,
+ mockExpandable,
+ mockScreenShareDialog,
+ kosmos,
+ )
dialogStopAction.onClick(mock<DialogInterface>(), 0)
// THEN the chip is immediately hidden...
@@ -268,13 +280,14 @@ class ShareToAppChipViewModelTest : SysuiTestCase() {
@Test
@EnableFlags(FLAG_STATUS_BAR_SHOW_AUDIO_ONLY_PROJECTION_CHIP)
+ @DisableFlags(StatusBarChipsModernization.FLAG_NAME)
fun chip_noScreen_clickListenerShowsGenericShareDialog() =
testScope.runTest {
val latest by collectLastValue(underTest.chip)
mediaProjectionRepo.mediaProjectionState.value =
MediaProjectionState.Projecting.NoScreen(NORMAL_PACKAGE)
- val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListener)
+ val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListenerLegacy)
assertThat(clickListener).isNotNull()
clickListener!!.onClick(chipView)
@@ -288,13 +301,14 @@ class ShareToAppChipViewModelTest : SysuiTestCase() {
}
@Test
+ @DisableFlags(StatusBarChipsModernization.FLAG_NAME)
fun chip_entireScreen_clickListenerShowsScreenShareDialog() =
testScope.runTest {
val latest by collectLastValue(underTest.chip)
mediaProjectionRepo.mediaProjectionState.value =
MediaProjectionState.Projecting.EntireScreen(NORMAL_PACKAGE)
- val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListener)
+ val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListenerLegacy)
assertThat(clickListener).isNotNull()
clickListener!!.onClick(chipView)
@@ -308,6 +322,7 @@ class ShareToAppChipViewModelTest : SysuiTestCase() {
}
@Test
+ @DisableFlags(StatusBarChipsModernization.FLAG_NAME)
fun chip_singleTask_clickListenerShowsScreenShareDialog() =
testScope.runTest {
val latest by collectLastValue(underTest.chip)
@@ -318,7 +333,7 @@ class ShareToAppChipViewModelTest : SysuiTestCase() {
createTask(taskId = 1),
)
- val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListener)
+ val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListenerLegacy)
assertThat(clickListener).isNotNull()
clickListener!!.onClick(chipView)
@@ -332,6 +347,7 @@ class ShareToAppChipViewModelTest : SysuiTestCase() {
}
@Test
+ @DisableFlags(StatusBarChipsModernization.FLAG_NAME)
fun chip_clickListenerHasCuj() =
testScope.runTest {
val latest by collectLastValue(underTest.chip)
@@ -342,7 +358,7 @@ class ShareToAppChipViewModelTest : SysuiTestCase() {
createTask(taskId = 1),
)
- val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListener)
+ val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListenerLegacy)
clickListener!!.onClick(chipView)
val cujCaptor = argumentCaptor<DialogCuj>()
@@ -353,4 +369,101 @@ class ShareToAppChipViewModelTest : SysuiTestCase() {
.isEqualTo(Cuj.CUJ_STATUS_BAR_LAUNCH_DIALOG_FROM_CHIP)
assertThat(cujCaptor.firstValue.tag).contains("Share")
}
+
+ @Test
+ @EnableFlags(StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME)
+ fun chip_noScreen_hasClickBehavior() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.chip)
+ mediaProjectionRepo.mediaProjectionState.value =
+ MediaProjectionState.Projecting.NoScreen(NORMAL_PACKAGE)
+
+ assertThat((latest as OngoingActivityChipModel.Shown).clickBehavior)
+ .isInstanceOf(OngoingActivityChipModel.ClickBehavior.ExpandAction::class.java)
+ }
+
+ @Test
+ @EnableFlags(StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME)
+ fun chip_entireScreen_hasClickBehavior() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.chip)
+ mediaProjectionRepo.mediaProjectionState.value =
+ MediaProjectionState.Projecting.EntireScreen(NORMAL_PACKAGE)
+
+ assertThat((latest as OngoingActivityChipModel.Shown).clickBehavior)
+ .isInstanceOf(OngoingActivityChipModel.ClickBehavior.ExpandAction::class.java)
+ }
+
+ @Test
+ @EnableFlags(StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME)
+ fun chip_singleTask_hasClickBehavior() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.chip)
+ mediaProjectionRepo.mediaProjectionState.value =
+ MediaProjectionState.Projecting.SingleTask(
+ NORMAL_PACKAGE,
+ hostDeviceName = null,
+ createTask(taskId = 1),
+ )
+
+ assertThat((latest as OngoingActivityChipModel.Shown).clickBehavior)
+ .isInstanceOf(OngoingActivityChipModel.ClickBehavior.ExpandAction::class.java)
+ }
+
+ @Test
+ @EnableFlags(
+ FLAG_STATUS_BAR_SHOW_AUDIO_ONLY_PROJECTION_CHIP,
+ StatusBarRootModernization.FLAG_NAME,
+ StatusBarChipsModernization.FLAG_NAME,
+ )
+ fun chip_noScreen_clickBehaviorShowsGenericShareDialog() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.chip)
+ mediaProjectionRepo.mediaProjectionState.value =
+ MediaProjectionState.Projecting.NoScreen(NORMAL_PACKAGE)
+
+ val expandAction =
+ ((latest as OngoingActivityChipModel.Shown).clickBehavior
+ as OngoingActivityChipModel.ClickBehavior.ExpandAction)
+ expandAction.onClick(mockExpandable)
+ verify(kosmos.mockDialogTransitionAnimator)
+ .show(eq(mockGenericShareDialog), any(), any())
+ }
+
+ @Test
+ @EnableFlags(StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME)
+ fun chip_entireScreen_clickBehaviorShowsScreenShareDialog() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.chip)
+ mediaProjectionRepo.mediaProjectionState.value =
+ MediaProjectionState.Projecting.EntireScreen(NORMAL_PACKAGE)
+
+ val expandAction =
+ ((latest as OngoingActivityChipModel.Shown).clickBehavior
+ as OngoingActivityChipModel.ClickBehavior.ExpandAction)
+ expandAction.onClick(mockExpandable)
+ verify(kosmos.mockDialogTransitionAnimator)
+ .show(eq(mockScreenShareDialog), any(), any())
+ }
+
+ @Test
+ @EnableFlags(StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME)
+ fun chip_singleTask_clickBehaviorShowsScreenShareDialog() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.chip)
+ mediaProjectionRepo.mediaProjectionState.value =
+ MediaProjectionState.Projecting.SingleTask(
+ NORMAL_PACKAGE,
+ hostDeviceName = null,
+ createTask(taskId = 1),
+ )
+
+ val expandAction =
+ ((latest as OngoingActivityChipModel.Shown).clickBehavior
+ as OngoingActivityChipModel.ClickBehavior.ExpandAction)
+ expandAction.onClick(mockExpandable)
+
+ verify(kosmos.mockDialogTransitionAnimator)
+ .show(eq(mockScreenShareDialog), any(), any())
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/ChipTransitionHelperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/ChipTransitionHelperTest.kt
index 8d4c68de8c79..d099e70c9bc5 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/ChipTransitionHelperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/ChipTransitionHelperTest.kt
@@ -57,7 +57,8 @@ class ChipTransitionHelperTest : SysuiTestCase() {
icon = createIcon(R.drawable.ic_cake),
colors = ColorsModel.Themed,
startTimeMs = 100L,
- onClickListener = null,
+ onClickListenerLegacy = null,
+ clickBehavior = OngoingActivityChipModel.ClickBehavior.None,
)
inputChipFlow.value = newChip
@@ -68,7 +69,8 @@ class ChipTransitionHelperTest : SysuiTestCase() {
OngoingActivityChipModel.Shown.IconOnly(
icon = createIcon(R.drawable.ic_hotspot),
colors = ColorsModel.Themed,
- onClickListener = null,
+ onClickListenerLegacy = null,
+ clickBehavior = OngoingActivityChipModel.ClickBehavior.None,
)
inputChipFlow.value = newerChip
@@ -89,7 +91,8 @@ class ChipTransitionHelperTest : SysuiTestCase() {
icon = createIcon(R.drawable.ic_cake),
colors = ColorsModel.Themed,
startTimeMs = 100L,
- onClickListener = null,
+ onClickListenerLegacy = null,
+ clickBehavior = OngoingActivityChipModel.ClickBehavior.None,
)
inputChipFlow.value = shownChip
@@ -129,7 +132,8 @@ class ChipTransitionHelperTest : SysuiTestCase() {
icon = createIcon(R.drawable.ic_cake),
colors = ColorsModel.Themed,
startTimeMs = 100L,
- onClickListener = null,
+ onClickListenerLegacy = null,
+ clickBehavior = OngoingActivityChipModel.ClickBehavior.None,
)
inputChipFlow.value = shownChip
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipViewModelTest.kt
index e3510f5ce280..fc3af11c30b3 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipViewModelTest.kt
@@ -16,6 +16,8 @@
package com.android.systemui.statusbar.chips.ui.viewmodel
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
import android.view.View
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
@@ -23,14 +25,19 @@ import com.android.internal.jank.Cuj
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.DialogCuj
import com.android.systemui.animation.DialogTransitionAnimator
+import com.android.systemui.animation.Expandable
import com.android.systemui.log.logcatLogBuffer
import com.android.systemui.res.R
import com.android.systemui.statusbar.chips.ui.view.ChipBackgroundContainer
+import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipViewModel.Companion.createDialogLaunchOnClickCallback
import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipViewModel.Companion.createDialogLaunchOnClickListener
+import com.android.systemui.statusbar.core.StatusBarRootModernization
import com.android.systemui.statusbar.phone.SystemUIDialog
+import com.android.systemui.statusbar.phone.ongoingcall.StatusBarChipsModernization
import kotlin.test.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.anyBoolean
+import org.mockito.kotlin.any
import org.mockito.kotlin.eq
import org.mockito.kotlin.mock
import org.mockito.kotlin.verify
@@ -53,8 +60,11 @@ class OngoingActivityChipViewModelTest : SysuiTestCase() {
)
.thenReturn(chipBackgroundView)
}
+ private val mockExpandable: Expandable =
+ mock<Expandable>().apply { whenever(dialogTransitionController(any())).thenReturn(mock()) }
@Test
+ @DisableFlags(StatusBarChipsModernization.FLAG_NAME)
fun createDialogLaunchOnClickListener_showsDialogOnClick() {
val cuj = DialogCuj(Cuj.CUJ_STATUS_BAR_LAUNCH_DIALOG_FROM_CHIP, tag = "Test")
val clickListener =
@@ -68,11 +78,23 @@ class OngoingActivityChipViewModelTest : SysuiTestCase() {
clickListener.onClick(chipView)
verify(dialogTransitionAnimator)
- .showFromView(
- eq(mockSystemUIDialog),
- eq(chipBackgroundView),
- eq(cuj),
- anyBoolean(),
+ .showFromView(eq(mockSystemUIDialog), eq(chipBackgroundView), eq(cuj), anyBoolean())
+ }
+
+ @Test
+ @EnableFlags(StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME)
+ fun createDialogLaunchOnClickCallback_showsDialogOnClick() {
+ val cuj = DialogCuj(Cuj.CUJ_STATUS_BAR_LAUNCH_DIALOG_FROM_CHIP, tag = "Test")
+ val clickCallback =
+ createDialogLaunchOnClickCallback(
+ dialogDelegate,
+ dialogTransitionAnimator,
+ cuj,
+ logcatLogBuffer("OngoingActivityChipViewModelTest"),
+ "tag",
)
+
+ clickCallback.invoke(mockExpandable)
+ verify(dialogTransitionAnimator).show(eq(mockSystemUIDialog), any(), anyBoolean())
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelTest.kt
index 42358cce59a2..a4b6a841d61b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelTest.kt
@@ -26,6 +26,7 @@ import android.view.View
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.animation.Expandable
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.kosmos.Kosmos
@@ -44,6 +45,7 @@ import com.android.systemui.statusbar.chips.ui.view.ChipBackgroundContainer
import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
import com.android.systemui.statusbar.phone.SystemUIDialog
import com.android.systemui.statusbar.phone.mockSystemUIDialogFactory
+import com.android.systemui.statusbar.phone.ongoingcall.StatusBarChipsModernization
import com.android.systemui.statusbar.phone.ongoingcall.data.repository.ongoingCallRepository
import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallModel
import com.android.systemui.statusbar.phone.ongoingcall.shared.model.inCallModel
@@ -91,6 +93,8 @@ class OngoingActivityChipsViewModelTest : SysuiTestCase() {
)
.thenReturn(chipBackgroundView)
}
+ private val mockExpandable: Expandable =
+ mock<Expandable>().apply { whenever(dialogTransitionController(any())).thenReturn(mock()) }
private val underTest = kosmos.ongoingActivityChipsViewModel
@@ -294,7 +298,13 @@ class OngoingActivityChipsViewModelTest : SysuiTestCase() {
// WHEN screen record gets stopped via dialog
val dialogStopAction =
- getStopActionFromDialog(latest, chipView, mockSystemUIDialog, kosmos)
+ getStopActionFromDialog(
+ latest,
+ chipView,
+ mockExpandable,
+ mockSystemUIDialog,
+ kosmos,
+ )
dialogStopAction.onClick(mock<DialogInterface>(), 0)
// THEN the chip is immediately hidden with no animation
@@ -315,7 +325,13 @@ class OngoingActivityChipsViewModelTest : SysuiTestCase() {
// WHEN media projection gets stopped via dialog
val dialogStopAction =
- getStopActionFromDialog(latest, chipView, mockSystemUIDialog, kosmos)
+ getStopActionFromDialog(
+ latest,
+ chipView,
+ mockExpandable,
+ mockSystemUIDialog,
+ kosmos,
+ )
dialogStopAction.onClick(mock<DialogInterface>(), 0)
// THEN the chip is immediately hidden with no animation
@@ -330,6 +346,7 @@ class OngoingActivityChipsViewModelTest : SysuiTestCase() {
fun getStopActionFromDialog(
latest: OngoingActivityChipModel?,
chipView: View,
+ expandable: Expandable,
dialog: SystemUIDialog,
kosmos: Kosmos,
): DialogInterface.OnClickListener {
@@ -349,9 +366,17 @@ class OngoingActivityChipsViewModelTest : SysuiTestCase() {
.create(any<SystemUIDialog.Delegate>())
whenever(kosmos.packageManager.getApplicationInfo(eq(NORMAL_PACKAGE), any<Int>()))
.thenThrow(PackageManager.NameNotFoundException())
- // Click the chip so that we open the dialog and we fill in [dialogStopAction]
- val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListener)
- clickListener!!.onClick(chipView)
+
+ if (StatusBarChipsModernization.isEnabled) {
+ val clickBehavior = (latest as OngoingActivityChipModel.Shown).clickBehavior
+ (clickBehavior as OngoingActivityChipModel.ClickBehavior.ExpandAction).onClick(
+ expandable
+ )
+ } else {
+ val clickListener =
+ ((latest as OngoingActivityChipModel.Shown).onClickListenerLegacy)
+ clickListener!!.onClick(chipView)
+ }
return dialogStopAction
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithNotifsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithNotifsViewModelTest.kt
index 0f42f29e76ee..28f360108e50 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithNotifsViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithNotifsViewModelTest.kt
@@ -25,6 +25,7 @@ import android.view.View
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.animation.Expandable
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.kosmos.testScope
import com.android.systemui.kosmos.useUnconfinedTestDispatcher
@@ -104,6 +105,8 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() {
)
.thenReturn(chipBackgroundView)
}
+ private val mockExpandable: Expandable =
+ mock<Expandable>().apply { whenever(dialogTransitionController(any())).thenReturn(mock()) }
private val underTest by lazy { kosmos.ongoingActivityChipsViewModel }
@@ -679,7 +682,13 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() {
// WHEN screen record gets stopped via dialog
val dialogStopAction =
- getStopActionFromDialog(latest, chipView, mockSystemUIDialog, kosmos)
+ getStopActionFromDialog(
+ latest,
+ chipView,
+ mockExpandable,
+ mockSystemUIDialog,
+ kosmos,
+ )
dialogStopAction.onClick(mock<DialogInterface>(), 0)
// THEN the chip is immediately hidden with no animation
@@ -700,7 +709,13 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() {
// WHEN media projection gets stopped via dialog
val dialogStopAction =
- getStopActionFromDialog(latest, chipView, mockSystemUIDialog, kosmos)
+ getStopActionFromDialog(
+ latest,
+ chipView,
+ mockExpandable,
+ mockSystemUIDialog,
+ kosmos,
+ )
dialogStopAction.onClick(mock<DialogInterface>(), 0)
// THEN the chip is immediately hidden with no animation
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java
index 4ef9792a2ad9..0df1073ca553 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java
@@ -1383,7 +1383,6 @@ public class NotifCollectionTest extends SysuiTestCase {
}
@Test
- @EnableFlags(Flags.FLAG_NOTIFICATIONS_DISMISS_PRUNED_SUMMARIES)
public void testDismissNotificationsIncludesPrunedParents() {
// GIVEN a collection with 2 groups; one has a single child, one has two.
mCollection.addNotificationDismissInterceptor(mInterceptor1);
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractorImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractorImplTest.kt
index 92271198cac0..8d90d38a9eca 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractorImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractorImplTest.kt
@@ -212,7 +212,11 @@ class PromotedNotificationContentExtractorImplTest : SysuiTestCase() {
}
@Test
- @EnableFlags(PromotedNotificationUi.FLAG_NAME, StatusBarNotifChips.FLAG_NAME)
+ @EnableFlags(
+ PromotedNotificationUi.FLAG_NAME,
+ StatusBarNotifChips.FLAG_NAME,
+ android.app.Flags.FLAG_API_RICH_ONGOING,
+ )
fun extractContent_fromProgressStyle() {
val entry = createEntry {
setStyle(ProgressStyle().addProgressSegment(Segment(100)).setProgress(75))
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeHomeStatusBarViewModel.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeHomeStatusBarViewModel.kt
index 937f333b0065..a1c910d48cef 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeHomeStatusBarViewModel.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeHomeStatusBarViewModel.kt
@@ -24,6 +24,8 @@ import com.android.systemui.statusbar.chips.ui.model.MultipleOngoingActivityChip
import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
import com.android.systemui.statusbar.events.shared.model.SystemEventAnimationState.Idle
import com.android.systemui.statusbar.featurepods.popups.shared.model.PopupChipModel
+import com.android.systemui.statusbar.pipeline.shared.ui.model.SystemInfoCombinedVisibilityModel
+import com.android.systemui.statusbar.pipeline.shared.ui.model.VisibilityModel
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
@@ -51,28 +53,15 @@ class FakeHomeStatusBarViewModel(
override val shouldShowOperatorNameView = MutableStateFlow(false)
override val isClockVisible =
- MutableStateFlow(
- HomeStatusBarViewModel.VisibilityModel(
- visibility = View.GONE,
- shouldAnimateChange = false,
- )
- )
+ MutableStateFlow(VisibilityModel(visibility = View.GONE, shouldAnimateChange = false))
override val isNotificationIconContainerVisible =
- MutableStateFlow(
- HomeStatusBarViewModel.VisibilityModel(
- visibility = View.GONE,
- shouldAnimateChange = false,
- )
- )
+ MutableStateFlow(VisibilityModel(visibility = View.GONE, shouldAnimateChange = false))
override val systemInfoCombinedVis =
MutableStateFlow(
- HomeStatusBarViewModel.SystemInfoCombinedVisibilityModel(
- HomeStatusBarViewModel.VisibilityModel(
- visibility = View.GONE,
- shouldAnimateChange = false,
- ),
+ SystemInfoCombinedVisibilityModel(
+ VisibilityModel(visibility = View.GONE, shouldAnimateChange = false),
Idle,
)
)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelImplTest.kt
index 03abcf850d26..e74d009bb909 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelImplTest.kt
@@ -83,12 +83,11 @@ import com.android.systemui.statusbar.phone.SysuiDarkIconDispatcher
import com.android.systemui.statusbar.phone.data.repository.fakeDarkIconRepository
import com.android.systemui.statusbar.pipeline.shared.domain.interactor.setHomeStatusBarIconBlockList
import com.android.systemui.statusbar.pipeline.shared.domain.interactor.setHomeStatusBarInteractorShowOperatorName
-import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.HomeStatusBarViewModel.VisibilityModel
+import com.android.systemui.statusbar.pipeline.shared.ui.model.VisibilityModel
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import org.junit.Before
@@ -423,8 +422,9 @@ class HomeStatusBarViewModelImplTest : SysuiTestCase() {
fun areNotificationsLightsOut_requiresFlagEnabled() =
kosmos.runTest {
assertLogsWtf {
- val flow = underTest.areNotificationsLightsOut
- assertThat(flow).isEqualTo(emptyFlow<Boolean>())
+ val latest by collectLastValue(underTest.areNotificationsLightsOut)
+ // Nothing is emitted
+ assertThat(latest).isNull()
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegateTest.kt
index b2378d2c3aae..2d6315014164 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegateTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegateTest.kt
@@ -66,13 +66,14 @@ class ModesDialogDelegateTest : SysuiTestCase() {
whenever(
mockDialogTransitionAnimator.createActivityTransitionController(
any<SystemUIDialog>(),
- eq(null)
+ eq(null),
)
)
.thenReturn(mockAnimationController)
underTest =
ModesDialogDelegate(
+ context,
kosmos.systemUIDialogFactory,
mockDialogTransitionAnimator,
activityStarter,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/wmshell/TestableBubbleController.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/wmshell/TestableBubbleController.java
index aa71b84d7bbc..75c174229564 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/wmshell/TestableBubbleController.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/wmshell/TestableBubbleController.java
@@ -43,6 +43,7 @@ import com.android.wm.shell.onehanded.OneHandedController;
import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
+import com.android.wm.shell.taskview.TaskViewRepository;
import com.android.wm.shell.taskview.TaskViewTransitions;
import com.android.wm.shell.transition.Transitions;
@@ -76,6 +77,7 @@ public class TestableBubbleController extends BubbleController {
DragAndDropController dragAndDropController,
ShellExecutor shellMainExecutor,
Handler shellMainHandler,
+ TaskViewRepository taskViewRepository,
TaskViewTransitions taskViewTransitions,
Transitions transitions,
SyncTransactionQueue syncQueue,
@@ -86,7 +88,7 @@ public class TestableBubbleController extends BubbleController {
displayInsetsController, displayImeController, userManager, launcherApps,
bubbleLogger, taskStackListener, shellTaskOrganizer, positioner, displayController,
oneHandedOptional, dragAndDropController, shellMainExecutor, shellMainHandler,
- new SyncExecutor(), taskViewTransitions, transitions,
+ new SyncExecutor(), taskViewRepository, taskViewTransitions, transitions,
syncQueue, wmService, bubbleProperties);
setInflateSynchronously(true);
onInit();
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java
index 55be9f79e598..ca98cbf20c3a 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java
@@ -140,6 +140,11 @@ public interface ActivityStarter {
void postStartActivityDismissingKeyguard(Intent intent, int delay,
@Nullable ActivityTransitionAnimator.Controller animationController,
@Nullable String customMessage);
+ /** Posts a start activity intent that dismisses keyguard. */
+ void postStartActivityDismissingKeyguard(Intent intent, int delay,
+ @Nullable ActivityTransitionAnimator.Controller animationController,
+ @Nullable String customMessage,
+ @Nullable UserHandle userHandle);
void postStartActivityDismissingKeyguard(PendingIntent intent);
/**
diff --git a/packages/SystemUI/res/layout/contextual_edu_dialog.xml b/packages/SystemUI/res/layout/contextual_edu_dialog.xml
index 09aa8daa217e..e83d4902d512 100644
--- a/packages/SystemUI/res/layout/contextual_edu_dialog.xml
+++ b/packages/SystemUI/res/layout/contextual_edu_dialog.xml
@@ -29,7 +29,7 @@
android:layout_height="wrap_content"
android:contentDescription="@null"
android:importantForAccessibility="no"
- android:paddingRight="16dp" />
+ android:paddingHorizontal="16dp" />
<TextView
android:id="@+id/edu_message"
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl
index 2b71c87bfa27..d363e524a9f2 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl
@@ -23,7 +23,7 @@ import android.os.IRemoteCallback;
import android.view.MotionEvent;
import com.android.systemui.shared.recents.ISystemUiProxy;
-// Next ID: 36
+// Next ID: 38
oneway interface IOverviewProxy {
void onActiveNavBarRegionChanges(in Region activeRegion) = 11;
@@ -144,4 +144,14 @@ oneway interface IOverviewProxy {
* TouchInteractionService is expected to send the reply once it has finished cleaning up.
*/
void onUnbind(IRemoteCallback reply) = 35;
+
+ /**
+ * Sent when {@link TaskbarDelegate#onDisplayReady} is called.
+ */
+ void onDisplayReady(int displayId) = 36;
+
+ /**
+ * Sent when {@link TaskbarDelegate#onDisplayRemoved} is called.
+ */
+ void onDisplayRemoved(int displayId) = 37;
}
diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
index 71b622aa0608..9b852df88604 100644
--- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
+++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
@@ -61,6 +61,8 @@ import com.android.systemui.plugins.clocks.ClockTickRate
import com.android.systemui.plugins.clocks.WeatherData
import com.android.systemui.plugins.clocks.ZenData
import com.android.systemui.plugins.clocks.ZenData.ZenMode
+import com.android.systemui.power.domain.interactor.PowerInteractor
+import com.android.systemui.power.shared.model.ScreenPowerState
import com.android.systemui.res.R as SysuiR
import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.settings.UserTracker
@@ -106,6 +108,7 @@ constructor(
private val zenModeController: ZenModeController,
private val zenModeInteractor: ZenModeInteractor,
private val userTracker: UserTracker,
+ private val powerInteractor: PowerInteractor,
) {
var loggers =
listOf(
@@ -377,13 +380,13 @@ constructor(
override fun onTimeChanged() {
refreshTime()
}
-
- private fun refreshTime() {
- clock?.smallClock?.events?.onTimeTick()
- clock?.largeClock?.events?.onTimeTick()
- }
}
+ private fun refreshTime() {
+ clock?.smallClock?.events?.onTimeTick()
+ clock?.largeClock?.events?.onTimeTick()
+ }
+
@VisibleForTesting
internal fun listenForDnd(scope: CoroutineScope): Job {
ModesUi.assertInNewMode()
@@ -474,6 +477,7 @@ constructor(
listenForAnyStateToAodTransition(this)
listenForAnyStateToLockscreenTransition(this)
listenForAnyStateToDozingTransition(this)
+ listenForScreenPowerOn(this)
}
}
smallTimeListener?.update(shouldTimeListenerRun)
@@ -643,6 +647,17 @@ constructor(
}
}
+ @VisibleForTesting
+ internal fun listenForScreenPowerOn(scope: CoroutineScope): Job {
+ return scope.launch {
+ powerInteractor.screenPowerState.collect { powerState ->
+ if (powerState != ScreenPowerState.SCREEN_OFF) {
+ refreshTime()
+ }
+ }
+ }
+ }
+
class TimeListener(val clockFace: ClockFaceController, val executor: DelayableExecutor) {
val predrawListener =
ViewTreeObserver.OnPreDrawListener {
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/FullscreenMagnificationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/FullscreenMagnificationController.java
index caf043a1b1be..b2f3df60c82b 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/FullscreenMagnificationController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/FullscreenMagnificationController.java
@@ -200,7 +200,15 @@ public class FullscreenMagnificationController implements ComponentCallbacks {
valueAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(@NonNull Animator animation) {
- mHandler.post(() -> setState(ENABLED));
+ // This could be called when the animation ends or is canceled. Therefore, we need
+ // to check the state of fullscreen magnification for the following actions. We only
+ // update the state to ENABLED when the previous state is ENABLING which implies
+ // fullscreen magnification is experiencing an ongoing create border process.
+ mHandler.post(() -> {
+ if (getState() == ENABLING) {
+ setState(ENABLED);
+ }
+ });
}});
return valueAnimator;
}
@@ -221,7 +229,14 @@ public class FullscreenMagnificationController implements ComponentCallbacks {
valueAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(@NonNull Animator animation) {
- mHandler.post(() -> cleanUpBorder());
+ // This could be called when the animation ends or is canceled. Therefore, we need
+ // to check the state of fullscreen magnification for the following actions. Border
+ // cleanup should only happens after a removal process.
+ mHandler.post(() -> {
+ if (getState() == DISABLING) {
+ cleanUpBorder();
+ }
+ });
}});
return valueAnimator;
}
@@ -250,6 +265,8 @@ public class FullscreenMagnificationController implements ComponentCallbacks {
// If there is an ongoing disable process or it is already disabled, return
return;
}
+ // The state should be updated as early as possible so others could check
+ // the ongoing process.
setState(DISABLING);
mShowHideBorderAnimator = createHideTargetAnimator(mFullscreenBorder);
mShowHideBorderAnimator.start();
@@ -297,10 +314,13 @@ public class FullscreenMagnificationController implements ComponentCallbacks {
// If there is an ongoing enable process or it is already enabled, return
return;
}
+ // The state should be updated as early as possible so others could check
+ // the ongoing process.
+ setState(ENABLING);
+
if (mShowHideBorderAnimator != null) {
mShowHideBorderAnimator.cancel();
}
- setState(ENABLING);
onConfigurationChanged(mContext.getResources().getConfiguration());
mContext.registerComponentCallbacks(this);
diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/view/ViewExt.kt b/packages/SystemUI/src/com/android/systemui/common/ui/view/ViewExt.kt
index f7ea25c8c0ca..b248043067ca 100644
--- a/packages/SystemUI/src/com/android/systemui/common/ui/view/ViewExt.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/ui/view/ViewExt.kt
@@ -68,3 +68,11 @@ fun View.onTouchListener(listener: View.OnTouchListener): DisposableHandle {
setOnTouchListener(listener)
return DisposableHandle { setOnTouchListener(null) }
}
+
+/** A null listener should also set the longClickable property to false */
+fun View.updateLongClickListener(listener: View.OnLongClickListener?) {
+ setOnLongClickListener(listener)
+ if (listener == null) {
+ setLongClickable(false)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/CommunalDreamStartable.kt b/packages/SystemUI/src/com/android/systemui/communal/CommunalDreamStartable.kt
index 1bd541e1088a..6dc7c971baef 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/CommunalDreamStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/CommunalDreamStartable.kt
@@ -91,13 +91,19 @@ constructor(
.launchIn(bgScope)
}
- // Restart the dream underneath the hub in order to support the ability to swipe
- // away the hub to enter the dream.
- startDream
- .sampleFilter(powerInteractor.isAwake) { isAwake ->
- !glanceableHubAllowKeyguardWhenDreaming() && dreamManager.canStartDreaming(isAwake)
- }
- .onEach { dreamManager.startDream() }
- .launchIn(bgScope)
+ // With hub v2, we no longer need to keep the dream running underneath the hub as there is
+ // no more swipe between the hub and dream. We can just start the dream on-demand when the
+ // user presses the dream coin.
+ if (!communalSettingsInteractor.isV2FlagEnabled()) {
+ // Restart the dream underneath the hub in order to support the ability to swipe away
+ // the hub to enter the dream.
+ startDream
+ .sampleFilter(powerInteractor.isAwake) { isAwake ->
+ !glanceableHubAllowKeyguardWhenDreaming() &&
+ dreamManager.canStartDreaming(isAwake)
+ }
+ .onEach { dreamManager.startDream() }
+ .launchIn(bgScope)
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
index 0b2b3687c121..a56a63c0b104 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
@@ -562,6 +562,13 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ
return;
}
+ if (mCommunalSettingsInteractor.isV2FlagEnabled()) {
+ // Dream wake redirect is not needed in V2 as we do not need to keep the dream awake
+ // underneath the hub anymore as there is no more swipe between the dream and hub. SysUI
+ // will automatically transition to the hub when the dream wakes.
+ return;
+ }
+
redirectWake(mCommunalAvailable && !glanceableHubAllowKeyguardWhenDreaming());
}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt b/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt
index 2ed0671a570b..e58807763597 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt
@@ -30,6 +30,9 @@ import com.android.systemui.shade.shared.flag.DualShade
import com.android.systemui.statusbar.notification.collection.SortBySectionTimeFlag
import com.android.systemui.statusbar.notification.emptyshade.shared.ModesEmptyShadeFix
import com.android.systemui.statusbar.notification.interruption.VisualInterruptionRefactor
+import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUi
+import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUiAod
+import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUiForceExpanded
import com.android.systemui.statusbar.notification.shared.NotificationAvalancheSuppression
import com.android.systemui.statusbar.notification.shared.NotificationMinimalism
import com.android.systemui.statusbar.notification.shared.NotificationThrottleHun
@@ -52,6 +55,10 @@ class FlagDependencies @Inject constructor(featureFlags: FeatureFlagsClassic, ha
NotificationMinimalism.token dependsOn NotificationThrottleHun.token
ModesEmptyShadeFix.token dependsOn modesUi
+ PromotedNotificationUiForceExpanded.token dependsOn PromotedNotificationUi.token
+
+ PromotedNotificationUiAod.token dependsOn PromotedNotificationUi.token
+
// SceneContainer dependencies
SceneContainerFlag.getFlagDependencies().forEach { (alpha, beta) -> alpha dependsOn beta }
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutCustomizer.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutCustomizer.kt
index d9e55f89cda5..d8e2dabde8a9 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutCustomizer.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutCustomizer.kt
@@ -16,6 +16,7 @@
package com.android.systemui.keyboard.shortcut.ui.composable
+import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.interaction.MutableInteractionSource
@@ -27,6 +28,7 @@ import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.sizeIn
@@ -211,19 +213,19 @@ private fun DialogButtons(
shape = RoundedCornerShape(50.dp),
onClick = onCancel,
color = Color.Transparent,
- width = 80.dp,
+ modifier = Modifier.heightIn(40.dp),
contentColor = MaterialTheme.colorScheme.primary,
text = stringResource(R.string.shortcut_helper_customize_dialog_cancel_button_label),
+ border = BorderStroke(width = 1.dp, color = MaterialTheme.colorScheme.outlineVariant)
)
Spacer(modifier = Modifier.width(8.dp))
ShortcutHelperButton(
- modifier =
- Modifier.focusRequester(focusRequester).focusProperties {
- canFocus = true
- }, // enable focus on touch/click mode
+ modifier = Modifier
+ .heightIn(40.dp)
+ .focusRequester(focusRequester)
+ .focusProperties { canFocus = true }, // enable focus on touch/click mode
onClick = onConfirm,
color = MaterialTheme.colorScheme.primary,
- width = 116.dp,
contentColor = MaterialTheme.colorScheme.onPrimary,
text = confirmButtonText,
enabled = isConfirmButtonEnabled,
@@ -413,8 +415,7 @@ private fun PromptShortcutModifier(
private fun ActionKeyContainer(defaultModifierKey: ShortcutKey.Icon.ResIdIcon) {
Row(
modifier =
- Modifier.height(48.dp)
- .width(105.dp)
+ Modifier.sizeIn(minWidth = 105.dp, minHeight = 48.dp)
.background(
color = MaterialTheme.colorScheme.surface,
shape = RoundedCornerShape(16.dp),
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt
index bf60c9a52417..0054dd772659 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt
@@ -1,1109 +1 @@
-/*
- * 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.keyboard.shortcut.ui.composable
-
-import android.graphics.drawable.Icon
-import androidx.compose.animation.AnimatedVisibility
-import androidx.compose.animation.core.animateFloatAsState
-import androidx.compose.foundation.BorderStroke
-import androidx.compose.foundation.Image
-import androidx.compose.foundation.background
-import androidx.compose.foundation.border
-import androidx.compose.foundation.focusable
-import androidx.compose.foundation.interaction.MutableInteractionSource
-import androidx.compose.foundation.interaction.collectIsFocusedAsState
-import androidx.compose.foundation.layout.Arrangement
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.BoxScope
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.ExperimentalLayoutApi
-import androidx.compose.foundation.layout.FlowRow
-import androidx.compose.foundation.layout.FlowRowScope
-import androidx.compose.foundation.layout.Row
-import androidx.compose.foundation.layout.Spacer
-import androidx.compose.foundation.layout.WindowInsets
-import androidx.compose.foundation.layout.fillMaxHeight
-import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.height
-import androidx.compose.foundation.layout.heightIn
-import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.layout.size
-import androidx.compose.foundation.layout.width
-import androidx.compose.foundation.layout.wrapContentSize
-import androidx.compose.foundation.lazy.LazyColumn
-import androidx.compose.foundation.lazy.items
-import androidx.compose.foundation.lazy.rememberLazyListState
-import androidx.compose.foundation.rememberScrollState
-import androidx.compose.foundation.shape.CircleShape
-import androidx.compose.foundation.shape.RoundedCornerShape
-import androidx.compose.foundation.verticalScroll
-import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.automirrored.filled.OpenInNew
-import androidx.compose.material.icons.filled.Add
-import androidx.compose.material.icons.filled.DeleteOutline
-import androidx.compose.material.icons.filled.ExpandMore
-import androidx.compose.material.icons.filled.Refresh
-import androidx.compose.material.icons.filled.Search
-import androidx.compose.material.icons.filled.Tune
-import androidx.compose.material3.CenterAlignedTopAppBar
-import androidx.compose.material3.ExperimentalMaterial3Api
-import androidx.compose.material3.HorizontalDivider
-import androidx.compose.material3.Icon
-import androidx.compose.material3.LocalContentColor
-import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.NavigationDrawerItemColors
-import androidx.compose.material3.NavigationDrawerItemDefaults
-import androidx.compose.material3.SearchBar
-import androidx.compose.material3.SearchBarDefaults
-import androidx.compose.material3.Surface
-import androidx.compose.material3.Text
-import androidx.compose.material3.TopAppBarDefaults
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.CompositionLocalProvider
-import androidx.compose.runtime.LaunchedEffect
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.setValue
-import androidx.compose.ui.Alignment
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.focus.FocusDirection
-import androidx.compose.ui.focus.FocusRequester
-import androidx.compose.ui.focus.focusRequester
-import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.graphics.RectangleShape
-import androidx.compose.ui.graphics.Shape
-import androidx.compose.ui.graphics.graphicsLayer
-import androidx.compose.ui.input.key.Key
-import androidx.compose.ui.input.key.key
-import androidx.compose.ui.input.key.onKeyEvent
-import androidx.compose.ui.platform.LocalContext
-import androidx.compose.ui.platform.LocalDensity
-import androidx.compose.ui.platform.LocalFocusManager
-import androidx.compose.ui.res.painterResource
-import androidx.compose.ui.res.stringResource
-import androidx.compose.ui.semantics.Role
-import androidx.compose.ui.semantics.contentDescription
-import androidx.compose.ui.semantics.hideFromAccessibility
-import androidx.compose.ui.semantics.isTraversalGroup
-import androidx.compose.ui.semantics.role
-import androidx.compose.ui.semantics.semantics
-import androidx.compose.ui.text.SpanStyle
-import androidx.compose.ui.text.buildAnnotatedString
-import androidx.compose.ui.text.style.Hyphens
-import androidx.compose.ui.text.withStyle
-import androidx.compose.ui.unit.Density
-import androidx.compose.ui.unit.Dp
-import androidx.compose.ui.unit.dp
-import androidx.compose.ui.unit.sp
-import androidx.compose.ui.util.fastFirstOrNull
-import androidx.compose.ui.util.fastForEach
-import androidx.compose.ui.util.fastForEachIndexed
-import com.android.compose.modifiers.thenIf
-import com.android.compose.ui.graphics.painter.rememberDrawablePainter
-import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType
-import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCommand
-import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCustomizationRequestInfo
-import com.android.systemui.keyboard.shortcut.shared.model.ShortcutIcon
-import com.android.systemui.keyboard.shortcut.shared.model.ShortcutKey
-import com.android.systemui.keyboard.shortcut.shared.model.ShortcutSubCategory
-import com.android.systemui.keyboard.shortcut.ui.model.IconSource
-import com.android.systemui.keyboard.shortcut.ui.model.ShortcutCategoryUi
-import com.android.systemui.keyboard.shortcut.ui.model.ShortcutsUiState
-import com.android.systemui.res.R
-import kotlinx.coroutines.delay
-import com.android.systemui.keyboard.shortcut.shared.model.Shortcut as ShortcutModel
-
-@Composable
-fun ShortcutHelper(
- onSearchQueryChanged: (String) -> Unit,
- onKeyboardSettingsClicked: () -> Unit,
- modifier: Modifier = Modifier,
- shortcutsUiState: ShortcutsUiState,
- useSinglePane: @Composable () -> Boolean = { shouldUseSinglePane() },
- onCustomizationRequested: (ShortcutCustomizationRequestInfo) -> Unit = {},
-) {
- when (shortcutsUiState) {
- is ShortcutsUiState.Active -> {
- ActiveShortcutHelper(
- shortcutsUiState,
- useSinglePane,
- onSearchQueryChanged,
- modifier,
- onKeyboardSettingsClicked,
- onCustomizationRequested,
- )
- }
-
- else -> {
- // No-op for now.
- }
- }
-}
-
-@Composable
-private fun ActiveShortcutHelper(
- shortcutsUiState: ShortcutsUiState.Active,
- useSinglePane: @Composable () -> Boolean,
- onSearchQueryChanged: (String) -> Unit,
- modifier: Modifier,
- onKeyboardSettingsClicked: () -> Unit,
- onCustomizationRequested: (ShortcutCustomizationRequestInfo) -> Unit = {},
-) {
- var selectedCategoryType by
- remember(shortcutsUiState.defaultSelectedCategory) {
- mutableStateOf(shortcutsUiState.defaultSelectedCategory)
- }
- if (useSinglePane()) {
- ShortcutHelperSinglePane(
- shortcutsUiState.searchQuery,
- onSearchQueryChanged,
- shortcutsUiState.shortcutCategories,
- selectedCategoryType,
- onCategorySelected = { selectedCategoryType = it },
- onKeyboardSettingsClicked,
- modifier,
- )
- } else {
- ShortcutHelperTwoPane(
- shortcutsUiState.searchQuery,
- onSearchQueryChanged,
- modifier,
- shortcutsUiState.shortcutCategories,
- selectedCategoryType,
- onCategorySelected = { selectedCategoryType = it },
- onKeyboardSettingsClicked,
- shortcutsUiState.isShortcutCustomizerFlagEnabled,
- onCustomizationRequested,
- shortcutsUiState.shouldShowResetButton,
- )
- }
-}
-
-@Composable private fun shouldUseSinglePane() = hasCompactWindowSize()
-
-@Composable
-private fun ShortcutHelperSinglePane(
- searchQuery: String,
- onSearchQueryChanged: (String) -> Unit,
- categories: List<ShortcutCategoryUi>,
- selectedCategoryType: ShortcutCategoryType?,
- onCategorySelected: (ShortcutCategoryType?) -> Unit,
- onKeyboardSettingsClicked: () -> Unit,
- modifier: Modifier = Modifier,
-) {
- Column(
- modifier =
- modifier
- .fillMaxSize()
- .verticalScroll(rememberScrollState())
- .padding(start = 16.dp, end = 16.dp, top = 26.dp)
- ) {
- TitleBar()
- Spacer(modifier = Modifier.height(6.dp))
- ShortcutsSearchBar(onSearchQueryChanged)
- Spacer(modifier = Modifier.height(16.dp))
- if (categories.isEmpty()) {
- Box(modifier = Modifier.weight(1f)) {
- NoSearchResultsText(horizontalPadding = 16.dp, fillHeight = true)
- }
- } else {
- CategoriesPanelSinglePane(
- searchQuery,
- categories,
- selectedCategoryType,
- onCategorySelected,
- )
- Spacer(modifier = Modifier.weight(1f))
- }
- KeyboardSettings(
- horizontalPadding = 16.dp,
- verticalPadding = 32.dp,
- onClick = onKeyboardSettingsClicked,
- )
- }
-}
-
-@Composable
-private fun CategoriesPanelSinglePane(
- searchQuery: String,
- categories: List<ShortcutCategoryUi>,
- selectedCategoryType: ShortcutCategoryType?,
- onCategorySelected: (ShortcutCategoryType?) -> Unit,
-) {
- Column(verticalArrangement = Arrangement.spacedBy(2.dp)) {
- categories.fastForEachIndexed { index, category ->
- val isExpanded = selectedCategoryType == category.type
- val itemShape =
- if (categories.size == 1) {
- ShortcutHelper.Shapes.singlePaneSingleCategory
- } else if (index == 0) {
- ShortcutHelper.Shapes.singlePaneFirstCategory
- } else if (index == categories.lastIndex) {
- ShortcutHelper.Shapes.singlePaneLastCategory
- } else {
- ShortcutHelper.Shapes.singlePaneCategory
- }
- CategoryItemSinglePane(
- searchQuery = searchQuery,
- category = category,
- isExpanded = isExpanded,
- onClick = {
- onCategorySelected(
- if (isExpanded) {
- null
- } else {
- category.type
- }
- )
- },
- shape = itemShape,
- )
- }
- }
-}
-
-@Composable
-private fun CategoryItemSinglePane(
- searchQuery: String,
- category: ShortcutCategoryUi,
- isExpanded: Boolean,
- onClick: () -> Unit,
- shape: Shape,
-) {
- Surface(color = MaterialTheme.colorScheme.surfaceBright, shape = shape, onClick = onClick) {
- Column {
- Row(
- verticalAlignment = Alignment.CenterVertically,
- modifier = Modifier.fillMaxWidth().heightIn(min = 88.dp).padding(horizontal = 16.dp),
- ) {
- ShortcutCategoryIcon(modifier = Modifier.size(24.dp), source = category.iconSource)
- Spacer(modifier = Modifier.width(16.dp))
- Text(category.label)
- Spacer(modifier = Modifier.weight(1f))
- RotatingExpandCollapseIcon(isExpanded)
- }
- AnimatedVisibility(visible = isExpanded) {
- ShortcutCategoryDetailsSinglePane(searchQuery, category)
- }
- }
- }
-}
-
-@Composable
-fun ShortcutCategoryIcon(
- source: IconSource,
- modifier: Modifier = Modifier,
- contentDescription: String? = null,
- tint: Color = LocalContentColor.current,
-) {
- if (source.imageVector != null) {
- Icon(source.imageVector, contentDescription, modifier, tint)
- } else if (source.painter != null) {
- Image(source.painter, contentDescription, modifier)
- }
-}
-
-@Composable
-private fun RotatingExpandCollapseIcon(isExpanded: Boolean) {
- val expandIconRotationDegrees by
- animateFloatAsState(
- targetValue =
- if (isExpanded) {
- 180f
- } else {
- 0f
- },
- label = "Expand icon rotation animation",
- )
- Icon(
- modifier =
- Modifier.background(
- color = MaterialTheme.colorScheme.surfaceContainerHigh,
- shape = CircleShape,
- )
- .graphicsLayer { rotationZ = expandIconRotationDegrees },
- imageVector = Icons.Default.ExpandMore,
- contentDescription =
- if (isExpanded) {
- stringResource(R.string.shortcut_helper_content_description_collapse_icon)
- } else {
- stringResource(R.string.shortcut_helper_content_description_expand_icon)
- },
- tint = MaterialTheme.colorScheme.onSurface,
- )
-}
-
-@Composable
-private fun ShortcutCategoryDetailsSinglePane(searchQuery: String, category: ShortcutCategoryUi) {
- Column(Modifier.padding(horizontal = 16.dp)) {
- category.subCategories.fastForEach { subCategory ->
- ShortcutSubCategorySinglePane(searchQuery, subCategory)
- }
- }
-}
-
-@Composable
-private fun ShortcutSubCategorySinglePane(searchQuery: String, subCategory: ShortcutSubCategory) {
- // This @Composable is expected to be in a Column.
- SubCategoryTitle(subCategory.label)
- subCategory.shortcuts.fastForEachIndexed { index, shortcut ->
- if (index > 0) {
- HorizontalDivider(color = MaterialTheme.colorScheme.surfaceContainerHigh)
- }
- Shortcut(Modifier.padding(vertical = 24.dp), searchQuery, shortcut)
- }
-}
-
-@Composable
-private fun ShortcutHelperTwoPane(
- searchQuery: String,
- onSearchQueryChanged: (String) -> Unit,
- modifier: Modifier = Modifier,
- categories: List<ShortcutCategoryUi>,
- selectedCategoryType: ShortcutCategoryType?,
- onCategorySelected: (ShortcutCategoryType?) -> Unit,
- onKeyboardSettingsClicked: () -> Unit,
- isShortcutCustomizerFlagEnabled: Boolean,
- onCustomizationRequested: (ShortcutCustomizationRequestInfo) -> Unit = {},
- shouldShowResetButton: Boolean,
-) {
- val selectedCategory = categories.fastFirstOrNull { it.type == selectedCategoryType }
- var isCustomizing by remember { mutableStateOf(false) }
-
- Column(modifier = modifier.fillMaxSize().padding(horizontal = 24.dp)) {
- Row(modifier = Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically) {
- // Keep title centered whether customize button is visible or not.
- Box(modifier = Modifier.fillMaxWidth(), contentAlignment = Alignment.CenterEnd) {
- Box(modifier = Modifier.fillMaxWidth(), contentAlignment = Alignment.Center) {
- TitleBar(isCustomizing)
- }
- if (isShortcutCustomizerFlagEnabled) {
- CustomizationButtonsContainer(
- isCustomizing = isCustomizing,
- onToggleCustomizationMode = { isCustomizing = !isCustomizing },
- onReset = {
- onCustomizationRequested(ShortcutCustomizationRequestInfo.Reset)
- },
- shouldShowResetButton = shouldShowResetButton,
- )
- } else {
- Spacer(modifier = Modifier.width(if (isCustomizing) 69.dp else 133.dp))
- }
- }
- }
- Spacer(modifier = Modifier.height(12.dp))
- Row(Modifier.fillMaxWidth()) {
- StartSidePanel(
- onSearchQueryChanged = onSearchQueryChanged,
- modifier = Modifier.width(240.dp).semantics { isTraversalGroup = true },
- categories = categories,
- onKeyboardSettingsClicked = onKeyboardSettingsClicked,
- selectedCategory = selectedCategoryType,
- onCategoryClicked = { onCategorySelected(it.type) },
- )
- Spacer(modifier = Modifier.width(24.dp))
- EndSidePanel(
- searchQuery,
- Modifier.fillMaxSize().padding(top = 8.dp).semantics { isTraversalGroup = true },
- selectedCategory,
- isCustomizing = isCustomizing,
- onCustomizationRequested = onCustomizationRequested,
- )
- }
- }
-}
-
-@Composable
-private fun CustomizationButtonsContainer(
- isCustomizing: Boolean,
- shouldShowResetButton: Boolean,
- onToggleCustomizationMode: () -> Unit,
- onReset: () -> Unit,
-) {
- Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
- if (isCustomizing) {
- if (shouldShowResetButton) {
- ResetButton(onClick = onReset)
- }
- DoneButton(onClick = onToggleCustomizationMode)
- } else {
- CustomizeButton(onClick = onToggleCustomizationMode)
- }
- }
-}
-
-@Composable
-private fun ResetButton(onClick: () -> Unit) {
- ShortcutHelperButton(
- onClick = onClick,
- color = Color.Transparent,
- width = 99.dp,
- iconSource = IconSource(imageVector = Icons.Default.Refresh),
- text = stringResource(id = R.string.shortcut_helper_reset_button_text),
- contentColor = MaterialTheme.colorScheme.primary,
- border = BorderStroke(color = MaterialTheme.colorScheme.outlineVariant, width = 1.dp),
- )
-}
-
-@Composable
-private fun CustomizeButton(onClick: () -> Unit) {
- ShortcutHelperButton(
- onClick = onClick,
- color = MaterialTheme.colorScheme.secondaryContainer,
- width = 133.dp,
- iconSource = IconSource(imageVector = Icons.Default.Tune),
- text = stringResource(id = R.string.shortcut_helper_customize_button_text),
- contentColor = MaterialTheme.colorScheme.onSecondaryContainer,
- )
-}
-
-@Composable
-private fun DoneButton(onClick: () -> Unit) {
- ShortcutHelperButton(
- onClick = onClick,
- color = MaterialTheme.colorScheme.primary,
- width = 69.dp,
- text = stringResource(R.string.shortcut_helper_done_button_text),
- contentColor = MaterialTheme.colorScheme.onPrimary,
- )
-}
-
-@Composable
-private fun EndSidePanel(
- searchQuery: String,
- modifier: Modifier,
- category: ShortcutCategoryUi?,
- isCustomizing: Boolean,
- onCustomizationRequested: (ShortcutCustomizationRequestInfo) -> Unit = {},
-) {
- val listState = rememberLazyListState()
- LaunchedEffect(key1 = category) { if (category != null) listState.animateScrollToItem(0) }
- if (category == null) {
- NoSearchResultsText(horizontalPadding = 24.dp, fillHeight = false)
- return
- }
- LazyColumn(modifier = modifier, state = listState) {
- items(category.subCategories) { subcategory ->
- SubCategoryContainerDualPane(
- searchQuery = searchQuery,
- subCategory = subcategory,
- isCustomizing = isCustomizing and category.type.includeInCustomization,
- onCustomizationRequested = { requestInfo ->
- when (requestInfo) {
- is ShortcutCustomizationRequestInfo.SingleShortcutCustomization.Add ->
- onCustomizationRequested(requestInfo.copy(categoryType = category.type))
-
- is ShortcutCustomizationRequestInfo.SingleShortcutCustomization.Delete ->
- onCustomizationRequested(requestInfo.copy(categoryType = category.type))
-
- ShortcutCustomizationRequestInfo.Reset ->
- onCustomizationRequested(requestInfo)
- }
- },
- )
- Spacer(modifier = Modifier.height(8.dp))
- }
- }
-}
-
-@Composable
-private fun NoSearchResultsText(horizontalPadding: Dp, fillHeight: Boolean) {
- var modifier = Modifier.fillMaxWidth()
- if (fillHeight) {
- modifier = modifier.fillMaxHeight()
- }
- Text(
- stringResource(R.string.shortcut_helper_no_search_results),
- style = MaterialTheme.typography.bodyMedium,
- color = MaterialTheme.colorScheme.onSurface,
- modifier =
- modifier
- .padding(vertical = 8.dp)
- .background(MaterialTheme.colorScheme.surfaceBright, RoundedCornerShape(28.dp))
- .padding(horizontal = horizontalPadding, vertical = 24.dp),
- )
-}
-
-@Composable
-private fun SubCategoryContainerDualPane(
- searchQuery: String,
- subCategory: ShortcutSubCategory,
- isCustomizing: Boolean,
- onCustomizationRequested: (ShortcutCustomizationRequestInfo) -> Unit,
-) {
- Surface(
- modifier = Modifier.fillMaxWidth(),
- shape = RoundedCornerShape(28.dp),
- color = MaterialTheme.colorScheme.surfaceBright,
- ) {
- Column(Modifier.padding(16.dp)) {
- SubCategoryTitle(subCategory.label)
- Spacer(Modifier.height(8.dp))
- subCategory.shortcuts.fastForEachIndexed { index, shortcut ->
- if (index > 0) {
- HorizontalDivider(
- modifier = Modifier.padding(horizontal = 8.dp),
- color = MaterialTheme.colorScheme.surfaceContainerHigh,
- )
- }
- Shortcut(
- modifier = Modifier.padding(vertical = 8.dp),
- searchQuery = searchQuery,
- shortcut = shortcut,
- isCustomizing = isCustomizing && shortcut.isCustomizable,
- onCustomizationRequested = { requestInfo ->
- when (requestInfo) {
- is ShortcutCustomizationRequestInfo.SingleShortcutCustomization.Add ->
- onCustomizationRequested(
- requestInfo.copy(subCategoryLabel = subCategory.label)
- )
-
- is ShortcutCustomizationRequestInfo.SingleShortcutCustomization.Delete ->
- onCustomizationRequested(
- requestInfo.copy(subCategoryLabel = subCategory.label)
- )
-
- ShortcutCustomizationRequestInfo.Reset ->
- onCustomizationRequested(requestInfo)
- }
- },
- )
- }
- }
- }
-}
-
-@Composable
-private fun SubCategoryTitle(title: String) {
- Text(
- title,
- style = MaterialTheme.typography.titleSmall,
- color = MaterialTheme.colorScheme.primary,
- )
-}
-
-@Composable
-private fun Shortcut(
- modifier: Modifier,
- searchQuery: String,
- shortcut: ShortcutModel,
- isCustomizing: Boolean = false,
- onCustomizationRequested: (ShortcutCustomizationRequestInfo) -> Unit = {},
-) {
- val interactionSource = remember { MutableInteractionSource() }
- val isFocused by interactionSource.collectIsFocusedAsState()
- val focusColor = MaterialTheme.colorScheme.secondary
- Row(
- modifier
- .thenIf(isFocused) {
- Modifier.border(width = 3.dp, color = focusColor, shape = RoundedCornerShape(16.dp))
- }
- .focusable(interactionSource = interactionSource)
- .padding(8.dp)
- .semantics(mergeDescendants = true) { contentDescription = shortcut.contentDescription }
- ) {
- Row(
- modifier =
- Modifier.width(128.dp).align(Alignment.CenterVertically).weight(0.333f).semantics {
- hideFromAccessibility()
- },
- horizontalArrangement = Arrangement.spacedBy(16.dp),
- verticalAlignment = Alignment.CenterVertically,
- ) {
- if (shortcut.icon != null) {
- ShortcutIcon(
- shortcut.icon,
- modifier = Modifier.size(24.dp).semantics { hideFromAccessibility() },
- )
- }
- ShortcutDescriptionText(
- searchQuery = searchQuery,
- shortcut = shortcut,
- modifier = Modifier.semantics { hideFromAccessibility() },
- )
- }
- Spacer(modifier = Modifier.width(24.dp).semantics { hideFromAccessibility() })
- ShortcutKeyCombinations(
- modifier = Modifier.weight(.666f).semantics { hideFromAccessibility() },
- shortcut = shortcut,
- isCustomizing = isCustomizing,
- onAddShortcutRequested = {
- onCustomizationRequested(
- ShortcutCustomizationRequestInfo.SingleShortcutCustomization.Add(
- label = shortcut.label,
- shortcutCommand = shortcut.commands.first(),
- )
- )
- },
- onDeleteShortcutRequested = {
- onCustomizationRequested(
- ShortcutCustomizationRequestInfo.SingleShortcutCustomization.Delete(
- label = shortcut.label,
- shortcutCommand = shortcut.commands.first(),
- )
- )
- },
- )
- }
-}
-
-@Composable
-fun ShortcutIcon(
- icon: ShortcutIcon,
- modifier: Modifier = Modifier,
- contentDescription: String? = null,
-) {
- val context = LocalContext.current
- val drawable =
- remember(icon.packageName, icon.resourceId) {
- Icon.createWithResource(icon.packageName, icon.resourceId).loadDrawable(context)
- } ?: return
- Image(
- painter = rememberDrawablePainter(drawable),
- contentDescription = contentDescription,
- modifier = modifier,
- )
-}
-
-@OptIn(ExperimentalLayoutApi::class)
-@Composable
-private fun ShortcutKeyCombinations(
- modifier: Modifier = Modifier,
- shortcut: ShortcutModel,
- isCustomizing: Boolean = false,
- onAddShortcutRequested: () -> Unit = {},
- onDeleteShortcutRequested: () -> Unit = {},
-) {
- FlowRow(
- modifier = modifier,
- verticalArrangement = Arrangement.spacedBy(8.dp),
- itemVerticalAlignment = Alignment.CenterVertically,
- horizontalArrangement = Arrangement.End,
- ) {
- shortcut.commands.forEachIndexed { index, command ->
- if (index > 0) {
- ShortcutOrSeparator(spacing = 16.dp)
- }
- ShortcutCommandContainer(showBackground = command.isCustom) { ShortcutCommand(command) }
- }
- if (isCustomizing) {
- Spacer(modifier = Modifier.width(16.dp))
- if (shortcut.containsCustomShortcutCommands) {
- DeleteShortcutButton(onDeleteShortcutRequested)
- } else {
- AddShortcutButton(onAddShortcutRequested)
- }
- }
- }
-}
-
-@Composable
-private fun AddShortcutButton(onClick: () -> Unit) {
- ShortcutHelperButton(
- modifier =
- Modifier.border(
- width = 1.dp,
- color = MaterialTheme.colorScheme.outline,
- shape = CircleShape,
- ),
- onClick = onClick,
- color = Color.Transparent,
- width = 32.dp,
- height = 32.dp,
- iconSource = IconSource(imageVector = Icons.Default.Add),
- contentColor = MaterialTheme.colorScheme.primary,
- contentPaddingVertical = 0.dp,
- contentPaddingHorizontal = 0.dp,
- contentDescription = stringResource(R.string.shortcut_helper_add_shortcut_button_label),
- )
-}
-
-@Composable
-private fun DeleteShortcutButton(onClick: () -> Unit) {
- ShortcutHelperButton(
- modifier =
- Modifier.border(
- width = 1.dp,
- color = MaterialTheme.colorScheme.outline,
- shape = CircleShape,
- ),
- onClick = onClick,
- color = Color.Transparent,
- width = 32.dp,
- height = 32.dp,
- iconSource = IconSource(imageVector = Icons.Default.DeleteOutline),
- contentColor = MaterialTheme.colorScheme.primary,
- contentPaddingVertical = 0.dp,
- contentPaddingHorizontal = 0.dp,
- contentDescription = stringResource(R.string.shortcut_helper_delete_shortcut_button_label),
- )
-}
-
-@Composable
-private fun ShortcutCommandContainer(showBackground: Boolean, content: @Composable () -> Unit) {
- if (showBackground) {
- Box(
- modifier =
- Modifier.wrapContentSize()
- .background(
- color = MaterialTheme.colorScheme.outlineVariant,
- shape = RoundedCornerShape(16.dp),
- )
- .padding(4.dp)
- ) {
- content()
- }
- } else {
- content()
- }
-}
-
-@Composable
-private fun ShortcutCommand(command: ShortcutCommand) {
- Row {
- command.keys.forEachIndexed { keyIndex, key ->
- if (keyIndex > 0) {
- Spacer(Modifier.width(4.dp))
- }
- ShortcutKeyContainer {
- if (key is ShortcutKey.Text) {
- ShortcutTextKey(key)
- } else if (key is ShortcutKey.Icon) {
- ShortcutIconKey(key)
- }
- }
- }
- }
-}
-
-@Composable
-private fun ShortcutKeyContainer(shortcutKeyContent: @Composable BoxScope.() -> Unit) {
- Box(
- modifier =
- Modifier.height(36.dp)
- .background(
- color = MaterialTheme.colorScheme.surfaceContainer,
- shape = RoundedCornerShape(12.dp),
- )
- ) {
- shortcutKeyContent()
- }
-}
-
-@Composable
-private fun BoxScope.ShortcutTextKey(key: ShortcutKey.Text) {
- Text(
- text = key.value,
- modifier =
- Modifier.align(Alignment.Center).padding(horizontal = 12.dp).semantics {
- hideFromAccessibility()
- },
- style = MaterialTheme.typography.titleSmall,
- )
-}
-
-@Composable
-private fun BoxScope.ShortcutIconKey(key: ShortcutKey.Icon) {
- Icon(
- painter =
- when (key) {
- is ShortcutKey.Icon.ResIdIcon -> painterResource(key.drawableResId)
- is ShortcutKey.Icon.DrawableIcon -> rememberDrawablePainter(drawable = key.drawable)
- },
- contentDescription = null,
- modifier = Modifier.align(Alignment.Center).padding(6.dp),
- )
-}
-
-@OptIn(ExperimentalLayoutApi::class)
-@Composable
-private fun FlowRowScope.ShortcutOrSeparator(spacing: Dp) {
- Spacer(Modifier.width(spacing))
- Text(
- text = stringResource(R.string.shortcut_helper_key_combinations_or_separator),
- modifier = Modifier.align(Alignment.CenterVertically).semantics { hideFromAccessibility() },
- style = MaterialTheme.typography.titleSmall,
- )
- Spacer(Modifier.width(spacing))
-}
-
-@Composable
-private fun ShortcutDescriptionText(
- searchQuery: String,
- shortcut: ShortcutModel,
- modifier: Modifier = Modifier,
-) {
- Text(
- modifier = modifier,
- text = textWithHighlightedSearchQuery(shortcut.label, searchQuery),
- style = MaterialTheme.typography.titleSmall,
- color = MaterialTheme.colorScheme.onSurface,
- )
-}
-
-@Composable
-private fun textWithHighlightedSearchQuery(text: String, searchValue: String) =
- buildAnnotatedString {
- val searchIndex = text.lowercase().indexOf(searchValue.trim().lowercase())
- val postSearchIndex = searchIndex + searchValue.trim().length
-
- if (searchIndex > 0) {
- val preSearchText = text.substring(0, searchIndex)
- append(preSearchText)
- }
- if (searchIndex >= 0) {
- val searchText = text.substring(searchIndex, postSearchIndex)
- withStyle(style = SpanStyle(background = MaterialTheme.colorScheme.primaryContainer)) {
- append(searchText)
- }
- if (postSearchIndex < text.length) {
- val postSearchText = text.substring(postSearchIndex)
- append(postSearchText)
- }
- } else {
- append(text)
- }
- }
-
-@Composable
-private fun StartSidePanel(
- onSearchQueryChanged: (String) -> Unit,
- modifier: Modifier,
- categories: List<ShortcutCategoryUi>,
- onKeyboardSettingsClicked: () -> Unit,
- selectedCategory: ShortcutCategoryType?,
- onCategoryClicked: (ShortcutCategoryUi) -> Unit,
-) {
- CompositionLocalProvider(
- // Restrict system font scale increases up to a max so categories display correctly.
- LocalDensity provides
- Density(
- density = LocalDensity.current.density,
- fontScale = LocalDensity.current.fontScale.coerceIn(1f, 1.5f),
- )
- ) {
- Column(modifier) {
- ShortcutsSearchBar(onSearchQueryChanged)
- Spacer(modifier = Modifier.heightIn(8.dp))
- CategoriesPanelTwoPane(categories, selectedCategory, onCategoryClicked)
- Spacer(modifier = Modifier.weight(1f))
- KeyboardSettings(
- horizontalPadding = 24.dp,
- verticalPadding = 24.dp,
- onKeyboardSettingsClicked,
- )
- }
- }
-}
-
-@Composable
-private fun CategoriesPanelTwoPane(
- categories: List<ShortcutCategoryUi>,
- selectedCategory: ShortcutCategoryType?,
- onCategoryClicked: (ShortcutCategoryUi) -> Unit,
-) {
- Column {
- categories.fastForEach {
- CategoryItemTwoPane(
- label = it.label,
- iconSource = it.iconSource,
- selected = selectedCategory == it.type,
- onClick = { onCategoryClicked(it) },
- )
- }
- }
-}
-
-@Composable
-private fun CategoryItemTwoPane(
- label: String,
- iconSource: IconSource,
- selected: Boolean,
- onClick: () -> Unit,
- colors: NavigationDrawerItemColors =
- NavigationDrawerItemDefaults.colors(unselectedContainerColor = Color.Transparent),
-) {
- SelectableShortcutSurface(
- selected = selected,
- onClick = onClick,
- modifier = Modifier.semantics { role = Role.Tab }.heightIn(min = 64.dp).fillMaxWidth(),
- shape = RoundedCornerShape(28.dp),
- color = colors.containerColor(selected).value,
- interactionsConfig =
- InteractionsConfig(
- hoverOverlayColor = MaterialTheme.colorScheme.onSurface,
- hoverOverlayAlpha = 0.11f,
- pressedOverlayColor = MaterialTheme.colorScheme.onSurface,
- pressedOverlayAlpha = 0.15f,
- focusOutlineColor = MaterialTheme.colorScheme.secondary,
- focusOutlineStrokeWidth = 3.dp,
- focusOutlinePadding = 2.dp,
- surfaceCornerRadius = 28.dp,
- focusOutlineCornerRadius = 33.dp,
- ),
- ) {
- Row(Modifier.padding(horizontal = 24.dp), verticalAlignment = Alignment.CenterVertically) {
- ShortcutCategoryIcon(
- modifier = Modifier.size(24.dp),
- source = iconSource,
- contentDescription = null,
- tint = colors.iconColor(selected).value,
- )
- Spacer(Modifier.width(12.dp))
- Box(Modifier.weight(1f)) {
- Text(
- fontSize = 18.sp,
- color = colors.textColor(selected).value,
- style = MaterialTheme.typography.titleSmall.copy(hyphens = Hyphens.Auto),
- text = label,
- )
- }
- }
- }
-}
-
-@Composable
-@OptIn(ExperimentalMaterial3Api::class)
-private fun TitleBar(isCustomizing: Boolean = false) {
- val text =
- if (isCustomizing) {
- stringResource(R.string.shortcut_helper_customize_mode_title)
- } else {
- stringResource(R.string.shortcut_helper_title)
- }
- CenterAlignedTopAppBar(
- colors = TopAppBarDefaults.centerAlignedTopAppBarColors(containerColor = Color.Transparent),
- title = {
- Text(
- text = text,
- color = MaterialTheme.colorScheme.onSurface,
- style = MaterialTheme.typography.headlineSmall,
- )
- },
- windowInsets = WindowInsets(top = 0.dp, bottom = 0.dp, left = 0.dp, right = 0.dp),
- expandedHeight = 64.dp,
- )
-}
-
-@Composable
-@OptIn(ExperimentalMaterial3Api::class)
-private fun ShortcutsSearchBar(onQueryChange: (String) -> Unit) {
- // Using an "internal query" to make sure the SearchBar is immediately updated, otherwise
- // the cursor moves to the wrong position sometimes, when waiting for the query to come back
- // from the ViewModel.
- var queryInternal by remember { mutableStateOf("") }
- val focusRequester = remember { FocusRequester() }
- val focusManager = LocalFocusManager.current
- LaunchedEffect(Unit) {
- // TODO(b/272065229): Added minor delay so TalkBack can take focus of search box by default,
- // remove when default a11y focus is fixed.
- delay(50)
- focusRequester.requestFocus()
- }
- SearchBar(
- modifier =
- Modifier.fillMaxWidth().focusRequester(focusRequester).onKeyEvent {
- if (it.key == Key.DirectionDown) {
- focusManager.moveFocus(FocusDirection.Down)
- return@onKeyEvent true
- } else {
- return@onKeyEvent false
- }
- },
- colors = SearchBarDefaults.colors(containerColor = MaterialTheme.colorScheme.surfaceBright),
- query = queryInternal,
- active = false,
- onActiveChange = {},
- onQueryChange = {
- queryInternal = it
- onQueryChange(it)
- },
- onSearch = {},
- leadingIcon = { Icon(Icons.Default.Search, contentDescription = null) },
- placeholder = { Text(text = stringResource(R.string.shortcut_helper_search_placeholder)) },
- windowInsets = WindowInsets(top = 0.dp, bottom = 0.dp, left = 0.dp, right = 0.dp),
- content = {},
- )
-}
-
-@Composable
-private fun KeyboardSettings(horizontalPadding: Dp, verticalPadding: Dp, onClick: () -> Unit) {
- ClickableShortcutSurface(
- onClick = onClick,
- shape = RoundedCornerShape(24.dp),
- color = Color.Transparent,
- modifier =
- Modifier.semantics { role = Role.Button }.fillMaxWidth().padding(horizontal = 12.dp),
- interactionsConfig =
- InteractionsConfig(
- hoverOverlayColor = MaterialTheme.colorScheme.onSurface,
- hoverOverlayAlpha = 0.11f,
- pressedOverlayColor = MaterialTheme.colorScheme.onSurface,
- pressedOverlayAlpha = 0.15f,
- focusOutlineColor = MaterialTheme.colorScheme.secondary,
- focusOutlinePadding = 8.dp,
- focusOutlineStrokeWidth = 3.dp,
- surfaceCornerRadius = 24.dp,
- focusOutlineCornerRadius = 28.dp,
- hoverPadding = 8.dp,
- ),
- ) {
- Row(verticalAlignment = Alignment.CenterVertically) {
- Text(
- text =
- stringResource(id = R.string.shortcut_helper_keyboard_settings_buttons_label),
- color = MaterialTheme.colorScheme.onSurfaceVariant,
- fontSize = 16.sp,
- style = MaterialTheme.typography.titleSmall,
- )
- Spacer(modifier = Modifier.weight(1f))
- Icon(
- imageVector = Icons.AutoMirrored.Default.OpenInNew,
- contentDescription = null,
- tint = MaterialTheme.colorScheme.onSurfaceVariant,
- modifier = Modifier.size(24.dp),
- )
- }
- }
-}
-
-object ShortcutHelper {
-
- object Shapes {
- val singlePaneFirstCategory =
- RoundedCornerShape(
- topStart = Dimensions.SinglePaneCategoryCornerRadius,
- topEnd = Dimensions.SinglePaneCategoryCornerRadius,
- )
- val singlePaneLastCategory =
- RoundedCornerShape(
- bottomStart = Dimensions.SinglePaneCategoryCornerRadius,
- bottomEnd = Dimensions.SinglePaneCategoryCornerRadius,
- )
- val singlePaneSingleCategory =
- RoundedCornerShape(size = Dimensions.SinglePaneCategoryCornerRadius)
- val singlePaneCategory = RectangleShape
- }
-
- object Dimensions {
- val SinglePaneCategoryCornerRadius = 28.dp
- }
-
- internal const val TAG = "ShortcutHelperUI"
-}
+/* * 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.keyboard.shortcut.ui.composable import android.graphics.drawable.Icon import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.core.animateFloatAsState import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.border import androidx.compose.foundation.focusable import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.interaction.collectIsFocusedAsState import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.BoxScope import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.ExperimentalLayoutApi import androidx.compose.foundation.layout.FlowRow import androidx.compose.foundation.layout.FlowRowScope import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.wrapContentSize import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.OpenInNew import androidx.compose.material.icons.filled.Add import androidx.compose.material.icons.filled.DeleteOutline import androidx.compose.material.icons.filled.ExpandMore import androidx.compose.material.icons.filled.Refresh import androidx.compose.material.icons.filled.Search import androidx.compose.material.icons.filled.Tune import androidx.compose.material3.CenterAlignedTopAppBar import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Icon import androidx.compose.material3.LocalContentColor import androidx.compose.material3.MaterialTheme import androidx.compose.material3.NavigationDrawerItemColors import androidx.compose.material3.NavigationDrawerItemDefaults import androidx.compose.material3.SearchBar import androidx.compose.material3.SearchBarDefaults import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.focus.FocusDirection import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.RectangleShape import androidx.compose.ui.graphics.Shape import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.input.key.Key import androidx.compose.ui.input.key.key import androidx.compose.ui.input.key.onKeyEvent import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.semantics.Role import androidx.compose.ui.semantics.contentDescription import androidx.compose.ui.semantics.hideFromAccessibility import androidx.compose.ui.semantics.isTraversalGroup import androidx.compose.ui.semantics.role import androidx.compose.ui.semantics.semantics import androidx.compose.ui.text.SpanStyle import androidx.compose.ui.text.buildAnnotatedString import androidx.compose.ui.text.style.Hyphens import androidx.compose.ui.text.withStyle import androidx.compose.ui.unit.Density import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.compose.ui.util.fastFirstOrNull import androidx.compose.ui.util.fastForEach import androidx.compose.ui.util.fastForEachIndexed import com.android.compose.modifiers.thenIf import com.android.compose.ui.graphics.painter.rememberDrawablePainter import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCommand import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCustomizationRequestInfo import com.android.systemui.keyboard.shortcut.shared.model.ShortcutIcon import com.android.systemui.keyboard.shortcut.shared.model.ShortcutKey import com.android.systemui.keyboard.shortcut.shared.model.ShortcutSubCategory import com.android.systemui.keyboard.shortcut.ui.model.IconSource import com.android.systemui.keyboard.shortcut.ui.model.ShortcutCategoryUi import com.android.systemui.keyboard.shortcut.ui.model.ShortcutsUiState import com.android.systemui.res.R import kotlinx.coroutines.delay import com.android.systemui.keyboard.shortcut.shared.model.Shortcut as ShortcutModel @Composable fun ShortcutHelper( onSearchQueryChanged: (String) -> Unit, onKeyboardSettingsClicked: () -> Unit, modifier: Modifier = Modifier, shortcutsUiState: ShortcutsUiState, useSinglePane: @Composable () -> Boolean = { shouldUseSinglePane() }, onCustomizationRequested: (ShortcutCustomizationRequestInfo) -> Unit = {}, ) { when (shortcutsUiState) { is ShortcutsUiState.Active -> { ActiveShortcutHelper( shortcutsUiState, useSinglePane, onSearchQueryChanged, modifier, onKeyboardSettingsClicked, onCustomizationRequested, ) } else -> { // No-op for now. } } } @Composable private fun ActiveShortcutHelper( shortcutsUiState: ShortcutsUiState.Active, useSinglePane: @Composable () -> Boolean, onSearchQueryChanged: (String) -> Unit, modifier: Modifier, onKeyboardSettingsClicked: () -> Unit, onCustomizationRequested: (ShortcutCustomizationRequestInfo) -> Unit = {}, ) { var selectedCategoryType by remember(shortcutsUiState.defaultSelectedCategory) { mutableStateOf(shortcutsUiState.defaultSelectedCategory) } if (useSinglePane()) { ShortcutHelperSinglePane( shortcutsUiState.searchQuery, onSearchQueryChanged, shortcutsUiState.shortcutCategories, selectedCategoryType, onCategorySelected = { selectedCategoryType = it }, onKeyboardSettingsClicked, modifier, ) } else { ShortcutHelperTwoPane( shortcutsUiState.searchQuery, onSearchQueryChanged, modifier, shortcutsUiState.shortcutCategories, selectedCategoryType, onCategorySelected = { selectedCategoryType = it }, onKeyboardSettingsClicked, shortcutsUiState.isShortcutCustomizerFlagEnabled, onCustomizationRequested, shortcutsUiState.shouldShowResetButton, ) } } @Composable private fun shouldUseSinglePane() = hasCompactWindowSize() @Composable private fun ShortcutHelperSinglePane( searchQuery: String, onSearchQueryChanged: (String) -> Unit, categories: List<ShortcutCategoryUi>, selectedCategoryType: ShortcutCategoryType?, onCategorySelected: (ShortcutCategoryType?) -> Unit, onKeyboardSettingsClicked: () -> Unit, modifier: Modifier = Modifier, ) { Column( modifier = modifier .fillMaxSize() .verticalScroll(rememberScrollState()) .padding(start = 16.dp, end = 16.dp, top = 26.dp) ) { TitleBar() Spacer(modifier = Modifier.height(6.dp)) ShortcutsSearchBar(onSearchQueryChanged) Spacer(modifier = Modifier.height(16.dp)) if (categories.isEmpty()) { Box(modifier = Modifier.weight(1f)) { NoSearchResultsText(horizontalPadding = 16.dp, fillHeight = true) } } else { CategoriesPanelSinglePane( searchQuery, categories, selectedCategoryType, onCategorySelected, ) Spacer(modifier = Modifier.weight(1f)) } KeyboardSettings( horizontalPadding = 16.dp, verticalPadding = 32.dp, onClick = onKeyboardSettingsClicked, ) } } @Composable private fun CategoriesPanelSinglePane( searchQuery: String, categories: List<ShortcutCategoryUi>, selectedCategoryType: ShortcutCategoryType?, onCategorySelected: (ShortcutCategoryType?) -> Unit, ) { Column(verticalArrangement = Arrangement.spacedBy(2.dp)) { categories.fastForEachIndexed { index, category -> val isExpanded = selectedCategoryType == category.type val itemShape = if (categories.size == 1) { ShortcutHelper.Shapes.singlePaneSingleCategory } else if (index == 0) { ShortcutHelper.Shapes.singlePaneFirstCategory } else if (index == categories.lastIndex) { ShortcutHelper.Shapes.singlePaneLastCategory } else { ShortcutHelper.Shapes.singlePaneCategory } CategoryItemSinglePane( searchQuery = searchQuery, category = category, isExpanded = isExpanded, onClick = { onCategorySelected( if (isExpanded) { null } else { category.type } ) }, shape = itemShape, ) } } } @Composable private fun CategoryItemSinglePane( searchQuery: String, category: ShortcutCategoryUi, isExpanded: Boolean, onClick: () -> Unit, shape: Shape, ) { Surface(color = MaterialTheme.colorScheme.surfaceBright, shape = shape, onClick = onClick) { Column { Row( verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth().heightIn(min = 88.dp).padding(horizontal = 16.dp), ) { ShortcutCategoryIcon(modifier = Modifier.size(24.dp), source = category.iconSource) Spacer(modifier = Modifier.width(16.dp)) Text(category.label) Spacer(modifier = Modifier.weight(1f)) RotatingExpandCollapseIcon(isExpanded) } AnimatedVisibility(visible = isExpanded) { ShortcutCategoryDetailsSinglePane(searchQuery, category) } } } } @Composable fun ShortcutCategoryIcon( source: IconSource, modifier: Modifier = Modifier, contentDescription: String? = null, tint: Color = LocalContentColor.current, ) { if (source.imageVector != null) { Icon(source.imageVector, contentDescription, modifier, tint) } else if (source.painter != null) { Image(source.painter, contentDescription, modifier) } } @Composable private fun RotatingExpandCollapseIcon(isExpanded: Boolean) { val expandIconRotationDegrees by animateFloatAsState( targetValue = if (isExpanded) { 180f } else { 0f }, label = "Expand icon rotation animation", ) Icon( modifier = Modifier.background( color = MaterialTheme.colorScheme.surfaceContainerHigh, shape = CircleShape, ) .graphicsLayer { rotationZ = expandIconRotationDegrees }, imageVector = Icons.Default.ExpandMore, contentDescription = if (isExpanded) { stringResource(R.string.shortcut_helper_content_description_collapse_icon) } else { stringResource(R.string.shortcut_helper_content_description_expand_icon) }, tint = MaterialTheme.colorScheme.onSurface, ) } @Composable private fun ShortcutCategoryDetailsSinglePane(searchQuery: String, category: ShortcutCategoryUi) { Column(Modifier.padding(horizontal = 16.dp)) { category.subCategories.fastForEach { subCategory -> ShortcutSubCategorySinglePane(searchQuery, subCategory) } } } @Composable private fun ShortcutSubCategorySinglePane(searchQuery: String, subCategory: ShortcutSubCategory) { // This @Composable is expected to be in a Column. SubCategoryTitle(subCategory.label) subCategory.shortcuts.fastForEachIndexed { index, shortcut -> if (index > 0) { HorizontalDivider(color = MaterialTheme.colorScheme.surfaceContainerHigh) } Shortcut(Modifier.padding(vertical = 24.dp), searchQuery, shortcut) } } @Composable private fun ShortcutHelperTwoPane( searchQuery: String, onSearchQueryChanged: (String) -> Unit, modifier: Modifier = Modifier, categories: List<ShortcutCategoryUi>, selectedCategoryType: ShortcutCategoryType?, onCategorySelected: (ShortcutCategoryType?) -> Unit, onKeyboardSettingsClicked: () -> Unit, isShortcutCustomizerFlagEnabled: Boolean, onCustomizationRequested: (ShortcutCustomizationRequestInfo) -> Unit = {}, shouldShowResetButton: Boolean, ) { val selectedCategory = categories.fastFirstOrNull { it.type == selectedCategoryType } var isCustomizing by remember { mutableStateOf(false) } Column(modifier = modifier.fillMaxSize().padding(horizontal = 24.dp)) { Row(modifier = Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically) { // Keep title centered whether customize button is visible or not. Box(modifier = Modifier.fillMaxWidth(), contentAlignment = Alignment.CenterEnd) { Box(modifier = Modifier.fillMaxWidth(), contentAlignment = Alignment.Center) { TitleBar(isCustomizing) } if (isShortcutCustomizerFlagEnabled) { CustomizationButtonsContainer( isCustomizing = isCustomizing, onToggleCustomizationMode = { isCustomizing = !isCustomizing }, onReset = { onCustomizationRequested(ShortcutCustomizationRequestInfo.Reset) }, shouldShowResetButton = shouldShowResetButton, ) } else { Spacer(modifier = Modifier.width(if (isCustomizing) 69.dp else 133.dp)) } } } Spacer(modifier = Modifier.height(12.dp)) Row(Modifier.fillMaxWidth()) { StartSidePanel( onSearchQueryChanged = onSearchQueryChanged, modifier = Modifier.width(240.dp).semantics { isTraversalGroup = true }, categories = categories, onKeyboardSettingsClicked = onKeyboardSettingsClicked, selectedCategory = selectedCategoryType, onCategoryClicked = { onCategorySelected(it.type) }, ) Spacer(modifier = Modifier.width(24.dp)) EndSidePanel( searchQuery, Modifier.fillMaxSize().padding(top = 8.dp).semantics { isTraversalGroup = true }, selectedCategory, isCustomizing = isCustomizing, onCustomizationRequested = onCustomizationRequested, ) } } } @Composable private fun CustomizationButtonsContainer( isCustomizing: Boolean, shouldShowResetButton: Boolean, onToggleCustomizationMode: () -> Unit, onReset: () -> Unit, ) { Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { if (isCustomizing) { if (shouldShowResetButton) { ResetButton(onClick = onReset) } DoneButton(onClick = onToggleCustomizationMode) } else { CustomizeButton(onClick = onToggleCustomizationMode) } } } @Composable private fun ResetButton(onClick: () -> Unit) { ShortcutHelperButton( modifier = Modifier.heightIn(40.dp), onClick = onClick, color = Color.Transparent, iconSource = IconSource(imageVector = Icons.Default.Refresh), text = stringResource(id = R.string.shortcut_helper_reset_button_text), contentColor = MaterialTheme.colorScheme.primary, border = BorderStroke(color = MaterialTheme.colorScheme.outlineVariant, width = 1.dp), ) } @Composable private fun CustomizeButton(onClick: () -> Unit) { ShortcutHelperButton( modifier = Modifier.heightIn(40.dp), onClick = onClick, color = MaterialTheme.colorScheme.secondaryContainer, iconSource = IconSource(imageVector = Icons.Default.Tune), text = stringResource(id = R.string.shortcut_helper_customize_button_text), contentColor = MaterialTheme.colorScheme.onSecondaryContainer, ) } @Composable private fun DoneButton(onClick: () -> Unit) { ShortcutHelperButton( modifier = Modifier.heightIn(40.dp), onClick = onClick, color = MaterialTheme.colorScheme.primary, text = stringResource(R.string.shortcut_helper_done_button_text), contentColor = MaterialTheme.colorScheme.onPrimary, ) } @Composable private fun EndSidePanel( searchQuery: String, modifier: Modifier, category: ShortcutCategoryUi?, isCustomizing: Boolean, onCustomizationRequested: (ShortcutCustomizationRequestInfo) -> Unit = {}, ) { val listState = rememberLazyListState() LaunchedEffect(key1 = category) { if (category != null) listState.animateScrollToItem(0) } if (category == null) { NoSearchResultsText(horizontalPadding = 24.dp, fillHeight = false) return } LazyColumn(modifier = modifier, state = listState) { items(category.subCategories) { subcategory -> SubCategoryContainerDualPane( searchQuery = searchQuery, subCategory = subcategory, isCustomizing = isCustomizing and category.type.includeInCustomization, onCustomizationRequested = { requestInfo -> when (requestInfo) { is ShortcutCustomizationRequestInfo.SingleShortcutCustomization.Add -> onCustomizationRequested(requestInfo.copy(categoryType = category.type)) is ShortcutCustomizationRequestInfo.SingleShortcutCustomization.Delete -> onCustomizationRequested(requestInfo.copy(categoryType = category.type)) ShortcutCustomizationRequestInfo.Reset -> onCustomizationRequested(requestInfo) } }, ) Spacer(modifier = Modifier.height(8.dp)) } } } @Composable private fun NoSearchResultsText(horizontalPadding: Dp, fillHeight: Boolean) { var modifier = Modifier.fillMaxWidth() if (fillHeight) { modifier = modifier.fillMaxHeight() } Text( stringResource(R.string.shortcut_helper_no_search_results), style = MaterialTheme.typography.bodyMedium, color = MaterialTheme.colorScheme.onSurface, modifier = modifier .padding(vertical = 8.dp) .background(MaterialTheme.colorScheme.surfaceBright, RoundedCornerShape(28.dp)) .padding(horizontal = horizontalPadding, vertical = 24.dp), ) } @Composable private fun SubCategoryContainerDualPane( searchQuery: String, subCategory: ShortcutSubCategory, isCustomizing: Boolean, onCustomizationRequested: (ShortcutCustomizationRequestInfo) -> Unit, ) { Surface( modifier = Modifier.fillMaxWidth(), shape = RoundedCornerShape(28.dp), color = MaterialTheme.colorScheme.surfaceBright, ) { Column(Modifier.padding(16.dp)) { SubCategoryTitle(subCategory.label) Spacer(Modifier.height(8.dp)) subCategory.shortcuts.fastForEachIndexed { index, shortcut -> if (index > 0) { HorizontalDivider( modifier = Modifier.padding(horizontal = 8.dp), color = MaterialTheme.colorScheme.surfaceContainerHigh, ) } Shortcut( modifier = Modifier.padding(vertical = 8.dp), searchQuery = searchQuery, shortcut = shortcut, isCustomizing = isCustomizing && shortcut.isCustomizable, onCustomizationRequested = { requestInfo -> when (requestInfo) { is ShortcutCustomizationRequestInfo.SingleShortcutCustomization.Add -> onCustomizationRequested( requestInfo.copy(subCategoryLabel = subCategory.label) ) is ShortcutCustomizationRequestInfo.SingleShortcutCustomization.Delete -> onCustomizationRequested( requestInfo.copy(subCategoryLabel = subCategory.label) ) ShortcutCustomizationRequestInfo.Reset -> onCustomizationRequested(requestInfo) } }, ) } } } } @Composable private fun SubCategoryTitle(title: String) { Text( title, style = MaterialTheme.typography.titleSmall, color = MaterialTheme.colorScheme.primary, ) } @Composable private fun Shortcut( modifier: Modifier, searchQuery: String, shortcut: ShortcutModel, isCustomizing: Boolean = false, onCustomizationRequested: (ShortcutCustomizationRequestInfo) -> Unit = {}, ) { val interactionSource = remember { MutableInteractionSource() } val isFocused by interactionSource.collectIsFocusedAsState() val focusColor = MaterialTheme.colorScheme.secondary Row( modifier .thenIf(isFocused) { Modifier.border(width = 3.dp, color = focusColor, shape = RoundedCornerShape(16.dp)) } .focusable(interactionSource = interactionSource) .padding(8.dp) .semantics(mergeDescendants = true) { contentDescription = shortcut.contentDescription } ) { Row( modifier = Modifier.width(128.dp).align(Alignment.CenterVertically).weight(0.333f).semantics { hideFromAccessibility() }, horizontalArrangement = Arrangement.spacedBy(16.dp), verticalAlignment = Alignment.CenterVertically, ) { if (shortcut.icon != null) { ShortcutIcon( shortcut.icon, modifier = Modifier.size(24.dp).semantics { hideFromAccessibility() }, ) } ShortcutDescriptionText( searchQuery = searchQuery, shortcut = shortcut, modifier = Modifier.semantics { hideFromAccessibility() }, ) } Spacer(modifier = Modifier.width(24.dp).semantics { hideFromAccessibility() }) ShortcutKeyCombinations( modifier = Modifier.weight(.666f).semantics { hideFromAccessibility() }, shortcut = shortcut, isCustomizing = isCustomizing, onAddShortcutRequested = { onCustomizationRequested( ShortcutCustomizationRequestInfo.SingleShortcutCustomization.Add( label = shortcut.label, shortcutCommand = shortcut.commands.first(), ) ) }, onDeleteShortcutRequested = { onCustomizationRequested( ShortcutCustomizationRequestInfo.SingleShortcutCustomization.Delete( label = shortcut.label, shortcutCommand = shortcut.commands.first(), ) ) }, ) } } @Composable fun ShortcutIcon( icon: ShortcutIcon, modifier: Modifier = Modifier, contentDescription: String? = null, ) { val context = LocalContext.current val drawable = remember(icon.packageName, icon.resourceId) { Icon.createWithResource(icon.packageName, icon.resourceId).loadDrawable(context) } ?: return Image( painter = rememberDrawablePainter(drawable), contentDescription = contentDescription, modifier = modifier, ) } @OptIn(ExperimentalLayoutApi::class) @Composable private fun ShortcutKeyCombinations( modifier: Modifier = Modifier, shortcut: ShortcutModel, isCustomizing: Boolean = false, onAddShortcutRequested: () -> Unit = {}, onDeleteShortcutRequested: () -> Unit = {}, ) { FlowRow( modifier = modifier, verticalArrangement = Arrangement.spacedBy(8.dp), itemVerticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.End, ) { shortcut.commands.forEachIndexed { index, command -> if (index > 0) { ShortcutOrSeparator(spacing = 16.dp) } ShortcutCommandContainer(showBackground = command.isCustom) { ShortcutCommand(command) } } if (isCustomizing) { Spacer(modifier = Modifier.width(16.dp)) if (shortcut.containsCustomShortcutCommands) { DeleteShortcutButton(onDeleteShortcutRequested) } else { AddShortcutButton(onAddShortcutRequested) } } } } @Composable private fun AddShortcutButton(onClick: () -> Unit) { ShortcutHelperButton( modifier = Modifier.size(32.dp), onClick = onClick, color = Color.Transparent, iconSource = IconSource(imageVector = Icons.Default.Add), contentColor = MaterialTheme.colorScheme.primary, contentPaddingVertical = 0.dp, contentPaddingHorizontal = 0.dp, contentDescription = stringResource(R.string.shortcut_helper_add_shortcut_button_label), shape = CircleShape, border = BorderStroke(width = 1.dp, color = MaterialTheme.colorScheme.outline) ) } @Composable private fun DeleteShortcutButton(onClick: () -> Unit) { ShortcutHelperButton( modifier = Modifier.size(32.dp), onClick = onClick, color = Color.Transparent, iconSource = IconSource(imageVector = Icons.Default.DeleteOutline), contentColor = MaterialTheme.colorScheme.primary, contentPaddingVertical = 0.dp, contentPaddingHorizontal = 0.dp, contentDescription = stringResource(R.string.shortcut_helper_delete_shortcut_button_label), shape = CircleShape, border = BorderStroke(width = 1.dp, color = MaterialTheme.colorScheme.outline) ) } @Composable private fun ShortcutCommandContainer(showBackground: Boolean, content: @Composable () -> Unit) { if (showBackground) { Box( modifier = Modifier.wrapContentSize() .background( color = MaterialTheme.colorScheme.outlineVariant, shape = RoundedCornerShape(16.dp), ) .padding(4.dp) ) { content() } } else { content() } } @Composable private fun ShortcutCommand(command: ShortcutCommand) { Row { command.keys.forEachIndexed { keyIndex, key -> if (keyIndex > 0) { Spacer(Modifier.width(4.dp)) } ShortcutKeyContainer { if (key is ShortcutKey.Text) { ShortcutTextKey(key) } else if (key is ShortcutKey.Icon) { ShortcutIconKey(key) } } } } } @Composable private fun ShortcutKeyContainer(shortcutKeyContent: @Composable BoxScope.() -> Unit) { Box( modifier = Modifier.height(36.dp) .background( color = MaterialTheme.colorScheme.surfaceContainer, shape = RoundedCornerShape(12.dp), ) ) { shortcutKeyContent() } } @Composable private fun BoxScope.ShortcutTextKey(key: ShortcutKey.Text) { Text( text = key.value, modifier = Modifier.align(Alignment.Center).padding(horizontal = 12.dp).semantics { hideFromAccessibility() }, style = MaterialTheme.typography.titleSmall, ) } @Composable private fun BoxScope.ShortcutIconKey(key: ShortcutKey.Icon) { Icon( painter = when (key) { is ShortcutKey.Icon.ResIdIcon -> painterResource(key.drawableResId) is ShortcutKey.Icon.DrawableIcon -> rememberDrawablePainter(drawable = key.drawable) }, contentDescription = null, modifier = Modifier.align(Alignment.Center).padding(6.dp), ) } @OptIn(ExperimentalLayoutApi::class) @Composable private fun FlowRowScope.ShortcutOrSeparator(spacing: Dp) { Spacer(Modifier.width(spacing)) Text( text = stringResource(R.string.shortcut_helper_key_combinations_or_separator), modifier = Modifier.align(Alignment.CenterVertically).semantics { hideFromAccessibility() }, style = MaterialTheme.typography.titleSmall, ) Spacer(Modifier.width(spacing)) } @Composable private fun ShortcutDescriptionText( searchQuery: String, shortcut: ShortcutModel, modifier: Modifier = Modifier, ) { Text( modifier = modifier, text = textWithHighlightedSearchQuery(shortcut.label, searchQuery), style = MaterialTheme.typography.titleSmall, color = MaterialTheme.colorScheme.onSurface, ) } @Composable private fun textWithHighlightedSearchQuery(text: String, searchValue: String) = buildAnnotatedString { val searchIndex = text.lowercase().indexOf(searchValue.trim().lowercase()) val postSearchIndex = searchIndex + searchValue.trim().length if (searchIndex > 0) { val preSearchText = text.substring(0, searchIndex) append(preSearchText) } if (searchIndex >= 0) { val searchText = text.substring(searchIndex, postSearchIndex) withStyle(style = SpanStyle(background = MaterialTheme.colorScheme.primaryContainer)) { append(searchText) } if (postSearchIndex < text.length) { val postSearchText = text.substring(postSearchIndex) append(postSearchText) } } else { append(text) } } @Composable private fun StartSidePanel( onSearchQueryChanged: (String) -> Unit, modifier: Modifier, categories: List<ShortcutCategoryUi>, onKeyboardSettingsClicked: () -> Unit, selectedCategory: ShortcutCategoryType?, onCategoryClicked: (ShortcutCategoryUi) -> Unit, ) { CompositionLocalProvider( // Restrict system font scale increases up to a max so categories display correctly. LocalDensity provides Density( density = LocalDensity.current.density, fontScale = LocalDensity.current.fontScale.coerceIn(1f, 1.5f), ) ) { Column(modifier) { ShortcutsSearchBar(onSearchQueryChanged) Spacer(modifier = Modifier.heightIn(8.dp)) CategoriesPanelTwoPane(categories, selectedCategory, onCategoryClicked) Spacer(modifier = Modifier.weight(1f)) KeyboardSettings( horizontalPadding = 24.dp, verticalPadding = 24.dp, onKeyboardSettingsClicked, ) } } } @Composable private fun CategoriesPanelTwoPane( categories: List<ShortcutCategoryUi>, selectedCategory: ShortcutCategoryType?, onCategoryClicked: (ShortcutCategoryUi) -> Unit, ) { Column { categories.fastForEach { CategoryItemTwoPane( label = it.label, iconSource = it.iconSource, selected = selectedCategory == it.type, onClick = { onCategoryClicked(it) }, ) } } } @Composable private fun CategoryItemTwoPane( label: String, iconSource: IconSource, selected: Boolean, onClick: () -> Unit, colors: NavigationDrawerItemColors = NavigationDrawerItemDefaults.colors(unselectedContainerColor = Color.Transparent), ) { SelectableShortcutSurface( selected = selected, onClick = onClick, modifier = Modifier.semantics { role = Role.Tab }.heightIn(min = 64.dp).fillMaxWidth(), shape = RoundedCornerShape(28.dp), color = colors.containerColor(selected).value, interactionsConfig = InteractionsConfig( hoverOverlayColor = MaterialTheme.colorScheme.onSurface, hoverOverlayAlpha = 0.11f, pressedOverlayColor = MaterialTheme.colorScheme.onSurface, pressedOverlayAlpha = 0.15f, focusOutlineColor = MaterialTheme.colorScheme.secondary, focusOutlineStrokeWidth = 3.dp, focusOutlinePadding = 2.dp, surfaceCornerRadius = 28.dp, focusOutlineCornerRadius = 33.dp, ), ) { Row(Modifier.padding(horizontal = 24.dp), verticalAlignment = Alignment.CenterVertically) { ShortcutCategoryIcon( modifier = Modifier.size(24.dp), source = iconSource, contentDescription = null, tint = colors.iconColor(selected).value, ) Spacer(Modifier.width(12.dp)) Box(Modifier.weight(1f)) { Text( fontSize = 18.sp, color = colors.textColor(selected).value, style = MaterialTheme.typography.titleSmall.copy(hyphens = Hyphens.Auto), text = label, ) } } } } @Composable @OptIn(ExperimentalMaterial3Api::class) private fun TitleBar(isCustomizing: Boolean = false) { val text = if (isCustomizing) { stringResource(R.string.shortcut_helper_customize_mode_title) } else { stringResource(R.string.shortcut_helper_title) } CenterAlignedTopAppBar( colors = TopAppBarDefaults.centerAlignedTopAppBarColors(containerColor = Color.Transparent), title = { Text( text = text, color = MaterialTheme.colorScheme.onSurface, style = MaterialTheme.typography.headlineSmall, ) }, windowInsets = WindowInsets(top = 0.dp, bottom = 0.dp, left = 0.dp, right = 0.dp), expandedHeight = 64.dp, ) } @Composable @OptIn(ExperimentalMaterial3Api::class) private fun ShortcutsSearchBar(onQueryChange: (String) -> Unit) { // Using an "internal query" to make sure the SearchBar is immediately updated, otherwise // the cursor moves to the wrong position sometimes, when waiting for the query to come back // from the ViewModel. var queryInternal by remember { mutableStateOf("") } val focusRequester = remember { FocusRequester() } val focusManager = LocalFocusManager.current LaunchedEffect(Unit) { // TODO(b/272065229): Added minor delay so TalkBack can take focus of search box by default, // remove when default a11y focus is fixed. delay(50) focusRequester.requestFocus() } SearchBar( modifier = Modifier.fillMaxWidth().focusRequester(focusRequester).onKeyEvent { if (it.key == Key.DirectionDown) { focusManager.moveFocus(FocusDirection.Down) return@onKeyEvent true } else { return@onKeyEvent false } }, colors = SearchBarDefaults.colors(containerColor = MaterialTheme.colorScheme.surfaceBright), query = queryInternal, active = false, onActiveChange = {}, onQueryChange = { queryInternal = it onQueryChange(it) }, onSearch = {}, leadingIcon = { Icon(Icons.Default.Search, contentDescription = null) }, placeholder = { Text(text = stringResource(R.string.shortcut_helper_search_placeholder)) }, windowInsets = WindowInsets(top = 0.dp, bottom = 0.dp, left = 0.dp, right = 0.dp), content = {}, ) } @Composable private fun KeyboardSettings(horizontalPadding: Dp, verticalPadding: Dp, onClick: () -> Unit) { ClickableShortcutSurface( onClick = onClick, shape = RoundedCornerShape(24.dp), color = Color.Transparent, modifier = Modifier.semantics { role = Role.Button }.fillMaxWidth().padding(horizontal = 12.dp), interactionsConfig = InteractionsConfig( hoverOverlayColor = MaterialTheme.colorScheme.onSurface, hoverOverlayAlpha = 0.11f, pressedOverlayColor = MaterialTheme.colorScheme.onSurface, pressedOverlayAlpha = 0.15f, focusOutlineColor = MaterialTheme.colorScheme.secondary, focusOutlinePadding = 8.dp, focusOutlineStrokeWidth = 3.dp, surfaceCornerRadius = 24.dp, focusOutlineCornerRadius = 28.dp, hoverPadding = 8.dp, ), ) { Row(verticalAlignment = Alignment.CenterVertically) { Text( text = stringResource(id = R.string.shortcut_helper_keyboard_settings_buttons_label), color = MaterialTheme.colorScheme.onSurfaceVariant, fontSize = 16.sp, style = MaterialTheme.typography.titleSmall, ) Spacer(modifier = Modifier.weight(1f)) Icon( imageVector = Icons.AutoMirrored.Default.OpenInNew, contentDescription = null, tint = MaterialTheme.colorScheme.onSurfaceVariant, modifier = Modifier.size(24.dp), ) } } } object ShortcutHelper { object Shapes { val singlePaneFirstCategory = RoundedCornerShape( topStart = Dimensions.SinglePaneCategoryCornerRadius, topEnd = Dimensions.SinglePaneCategoryCornerRadius, ) val singlePaneLastCategory = RoundedCornerShape( bottomStart = Dimensions.SinglePaneCategoryCornerRadius, bottomEnd = Dimensions.SinglePaneCategoryCornerRadius, ) val singlePaneSingleCategory = RoundedCornerShape(size = Dimensions.SinglePaneCategoryCornerRadius) val singlePaneCategory = RectangleShape } object Dimensions { val SinglePaneCategoryCornerRadius = 28.dp } internal const val TAG = "ShortcutHelperUI" } \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/Surfaces.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/Surfaces.kt
index 9a380f495176..981a55cfda5a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/Surfaces.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/Surfaces.kt
@@ -32,7 +32,6 @@ import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
-import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
@@ -45,7 +44,6 @@ import androidx.compose.material3.LocalAbsoluteTonalElevation
import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.LocalTonalElevationEnabled
import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.material3.contentColorFor
import androidx.compose.material3.minimumInteractiveComponentSize
@@ -74,13 +72,14 @@ import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.semantics.Role
import androidx.compose.ui.semantics.role
import androidx.compose.ui.semantics.semantics
+import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.compose.ui.zIndex
-import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.compose.modifiers.thenIf
import com.android.systemui.keyboard.shortcut.ui.model.IconSource
+import com.android.app.tracing.coroutines.launchTraced as launch
/**
* A selectable surface with no default focus/hover indications.
@@ -217,30 +216,37 @@ fun ClickableShortcutSurface(
*/
@Composable
fun ShortcutHelperButton(
- modifier: Modifier = Modifier,
onClick: () -> Unit,
- shape: Shape = RoundedCornerShape(360.dp),
+ contentColor: Color,
color: Color,
- width: Dp,
- height: Dp = 40.dp,
+ modifier: Modifier = Modifier,
+ shape: Shape = RoundedCornerShape(360.dp),
iconSource: IconSource = IconSource(),
text: String? = null,
- contentColor: Color,
contentPaddingHorizontal: Dp = 16.dp,
contentPaddingVertical: Dp = 10.dp,
enabled: Boolean = true,
border: BorderStroke? = null,
contentDescription: String? = null,
) {
- ShortcutHelperButtonSurface(
+ ClickableShortcutSurface(
onClick = onClick,
shape = shape,
- color = color,
- modifier = modifier,
- enabled = enabled,
- width = width,
- height = height,
+ color = color.getDimmedColorIfDisabled(enabled),
border = border,
+ modifier = modifier.semantics { role = Role.Button },
+ interactionsConfig = InteractionsConfig(
+ hoverOverlayColor = MaterialTheme.colorScheme.onSurface,
+ hoverOverlayAlpha = 0.11f,
+ pressedOverlayColor = MaterialTheme.colorScheme.onSurface,
+ pressedOverlayAlpha = 0.15f,
+ focusOutlineColor = MaterialTheme.colorScheme.secondary,
+ focusOutlineStrokeWidth = 3.dp,
+ focusOutlinePadding = 2.dp,
+ surfaceCornerRadius = 28.dp,
+ focusOutlineCornerRadius = 33.dp,
+ ),
+ enabled = enabled
) {
Row(
modifier =
@@ -251,76 +257,45 @@ fun ShortcutHelperButton(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Center,
) {
- if (iconSource.imageVector != null) {
- Icon(
- tint = contentColor,
- imageVector = iconSource.imageVector,
- contentDescription = contentDescription,
- modifier = Modifier.size(20.dp).wrapContentSize(Alignment.Center),
- )
- }
-
- if (iconSource.imageVector != null && text != null)
- Spacer(modifier = Modifier.weight(1f))
-
- if (text != null) {
- Text(
- text,
- color = contentColor,
- fontSize = 14.sp,
- style = MaterialTheme.typography.labelLarge,
- modifier = Modifier.wrapContentSize(Alignment.Center),
- )
- }
+ ShortcutHelperButtonContent(iconSource, contentColor, text, contentDescription)
}
}
}
@Composable
-private fun ShortcutHelperButtonSurface(
- onClick: () -> Unit,
- shape: Shape,
- color: Color,
- modifier: Modifier = Modifier,
- enabled: Boolean,
- width: Dp,
- height: Dp,
- border: BorderStroke?,
- content: @Composable () -> Unit,
+private fun ShortcutHelperButtonContent(
+ iconSource: IconSource,
+ contentColor: Color,
+ text: String?,
+ contentDescription: String?
) {
- if (enabled) {
- ClickableShortcutSurface(
- onClick = onClick,
- shape = shape,
- color = color,
- border = border,
- modifier = modifier.semantics { role = Role.Button }.width(width).height(height),
- interactionsConfig =
- InteractionsConfig(
- hoverOverlayColor = MaterialTheme.colorScheme.onSurface,
- hoverOverlayAlpha = 0.11f,
- pressedOverlayColor = MaterialTheme.colorScheme.onSurface,
- pressedOverlayAlpha = 0.15f,
- focusOutlineColor = MaterialTheme.colorScheme.secondary,
- focusOutlineStrokeWidth = 3.dp,
- focusOutlinePadding = 2.dp,
- surfaceCornerRadius = 28.dp,
- focusOutlineCornerRadius = 33.dp,
- ),
- ) {
- content()
- }
- } else {
- Surface(
- shape = shape,
- color = color.copy(0.38f),
- modifier = modifier.semantics { role = Role.Button }.width(width).height(height),
- ) {
- content()
- }
+ if (iconSource.imageVector != null) {
+ Icon(
+ tint = contentColor,
+ imageVector = iconSource.imageVector,
+ contentDescription = contentDescription,
+ modifier = Modifier.size(20.dp).wrapContentSize(Alignment.Center),
+ )
+ }
+
+ if (iconSource.imageVector != null && text != null)
+ Spacer(modifier = Modifier.width(8.dp))
+
+ if (text != null) {
+ Text(
+ text,
+ color = contentColor,
+ fontSize = 14.sp,
+ style = MaterialTheme.typography.labelLarge,
+ modifier = Modifier.wrapContentSize(Alignment.Center),
+ overflow = TextOverflow.Ellipsis,
+ )
}
}
+private fun Color.getDimmedColorIfDisabled(enabled: Boolean): Color =
+ if (enabled) this else copy(alpha = 0.38f)
+
@Composable
private fun surfaceColorAtElevation(color: Color, elevation: Dp): Color {
return MaterialTheme.colorScheme.applyTonalElevation(color, elevation)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerLockscreenVisibilityManager.kt b/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerLockscreenVisibilityManager.kt
index df41535ff783..58692746d1e0 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerLockscreenVisibilityManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerLockscreenVisibilityManager.kt
@@ -17,6 +17,7 @@
package com.android.systemui.keyguard
import android.app.IActivityTaskManager
+import android.os.RemoteException
import android.util.Log
import android.view.IRemoteAnimationFinishedCallback
import android.view.RemoteAnimationTarget
@@ -265,7 +266,11 @@ constructor(
if (enableNewKeyguardShellTransitions) {
startKeyguardTransition(lockscreenShowing, aodVisible)
} else {
- activityTaskManagerService.setLockScreenShown(lockscreenShowing, aodVisible)
+ try {
+ activityTaskManagerService.setLockScreenShown(lockscreenShowing, aodVisible)
+ } catch (e: RemoteException) {
+ Log.e(TAG, "Remote exception", e)
+ }
}
this.isLockscreenShowing = lockscreenShowing
this.isAodVisible = aodVisible
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfig.kt
index 1b8baf657948..f11ebee46659 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfig.kt
@@ -26,8 +26,8 @@ import android.provider.Settings.Secure.ZEN_DURATION_FOREVER
import android.provider.Settings.Secure.ZEN_DURATION_PROMPT
import android.service.notification.ZenModeConfig
import android.util.Log
-import com.android.settingslib.notification.modes.EnableZenModeDialog
-import com.android.settingslib.notification.modes.ZenModeDialogMetricsLogger
+import com.android.settingslib.notification.modes.EnableDndDialogFactory
+import com.android.settingslib.notification.modes.EnableDndDialogMetricsLogger
import com.android.systemui.animation.Expandable
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
@@ -60,8 +60,7 @@ import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.stateIn
@SysUISingleton
-class DoNotDisturbQuickAffordanceConfig
-constructor(
+class DoNotDisturbQuickAffordanceConfig(
private val context: Context,
private val controller: ZenModeController,
private val interactor: ZenModeInteractor,
@@ -70,7 +69,7 @@ constructor(
@Background private val backgroundDispatcher: CoroutineDispatcher,
@Background private val backgroundScope: CoroutineScope,
private val testConditionId: Uri?,
- testDialog: EnableZenModeDialog?,
+ testDialogFactory: EnableDndDialogFactory?,
) : KeyguardQuickAffordanceConfig {
@Inject
@@ -118,13 +117,13 @@ constructor(
)
.id
- private val dialog: EnableZenModeDialog by lazy {
- testDialog
- ?: EnableZenModeDialog(
+ private val dialogFactory: EnableDndDialogFactory by lazy {
+ testDialogFactory
+ ?: EnableDndDialogFactory(
context,
R.style.Theme_SystemUI_Dialog,
true, /* cancelIsNeutral */
- ZenModeDialogMetricsLogger(context),
+ EnableDndDialogMetricsLogger(context),
)
}
@@ -224,7 +223,7 @@ constructor(
if (interactor.shouldAskForZenDuration(dnd)) {
// NOTE: The dialog handles turning on the mode itself.
return KeyguardQuickAffordanceConfig.OnTriggeredResult.ShowDialog(
- dialog.createDialog(),
+ dialogFactory.createDialog(),
expandable,
)
} else {
@@ -243,7 +242,7 @@ constructor(
settingsValue == ZEN_DURATION_PROMPT ->
KeyguardQuickAffordanceConfig.OnTriggeredResult.ShowDialog(
- dialog.createDialog(),
+ dialogFactory.createDialog(),
expandable,
)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
index 7d8badd232b9..b866fcab2893 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
@@ -22,6 +22,7 @@ import android.app.admin.DevicePolicyManager
import android.content.Context
import android.content.Intent
import android.util.Log
+import android.view.accessibility.AccessibilityManager
import com.android.app.tracing.coroutines.withContextTraced as withContext
import com.android.compose.animation.scene.ObservableTransitionState
import com.android.internal.widget.LockPatternUtils
@@ -92,6 +93,7 @@ constructor(
private val dockManager: DockManager,
private val biometricSettingsRepository: BiometricSettingsRepository,
private val communalSettingsInteractor: CommunalSettingsInteractor,
+ private val accessibilityManager: AccessibilityManager,
@Background private val backgroundDispatcher: CoroutineDispatcher,
@ShadeDisplayAware private val appContext: Context,
private val sceneInteractor: Lazy<SceneInteractor>,
@@ -115,7 +117,10 @@ constructor(
*
* If `false`, the UI goes back to using single taps.
*/
- fun useLongPress(): Flow<Boolean> = dockManager.retrieveIsDocked().map { !it }
+ fun useLongPress(): Flow<Boolean> =
+ dockManager.retrieveIsDocked().map { isDocked ->
+ !isDocked && !accessibilityManager.isEnabled()
+ }
/** Returns an observable for the quick affordance at the given position. */
suspend fun quickAffordance(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt
index 8a2e3dd791c2..f396cf99457f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt
@@ -39,6 +39,7 @@ import com.android.systemui.animation.Expandable
import com.android.systemui.animation.view.LaunchableImageView
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.common.ui.binder.IconViewBinder
+import com.android.systemui.common.ui.view.updateLongClickListener
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.ui.viewmodel.KeyguardQuickAffordanceHapticViewModel
import com.android.systemui.keyguard.ui.viewmodel.KeyguardQuickAffordanceViewModel
@@ -275,6 +276,7 @@ constructor(
)
} else {
view.setOnClickListener(OnClickListener(viewModel, checkNotNull(falsingManager)))
+ view.updateLongClickListener(null)
}
} else {
view.onLongClickListener = null
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToPrimaryBouncerTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToPrimaryBouncerTransitionViewModel.kt
index e3b55874de6f..26bf0bc258e8 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToPrimaryBouncerTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToPrimaryBouncerTransitionViewModel.kt
@@ -16,6 +16,7 @@
package com.android.systemui.keyguard.ui.viewmodel
+import com.android.systemui.Flags
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.domain.interactor.FromAodTransitionInteractor
import com.android.systemui.keyguard.shared.model.Edge
@@ -29,6 +30,7 @@ import com.android.systemui.scene.shared.model.Scenes
import javax.inject.Inject
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.emptyFlow
/**
* Breaks down AOD->PRIMARY BOUNCER transition into discrete steps for corresponding views to
@@ -54,6 +56,12 @@ constructor(blurConfig: BlurConfig, animationFlow: KeyguardTransitionAnimationFl
override val windowBlurRadius: Flow<Float> =
transitionAnimation.immediatelyTransitionTo(blurConfig.maxBlurRadiusPx)
+ val lockscreenAlpha: Flow<Float> =
+ if (Flags.bouncerUiRevamp()) transitionAnimation.immediatelyTransitionTo(0.0f)
+ else emptyFlow()
+
+ val notificationAlpha = lockscreenAlpha
+
override val notificationBlurRadius: Flow<Float> =
transitionAnimation.immediatelyTransitionTo(0.0f)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToPrimaryBouncerTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToPrimaryBouncerTransitionViewModel.kt
index c937d5c6453d..d9ca267f9445 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToPrimaryBouncerTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToPrimaryBouncerTransitionViewModel.kt
@@ -16,6 +16,7 @@
package com.android.systemui.keyguard.ui.viewmodel
+import com.android.systemui.Flags
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.domain.interactor.FromDozingTransitionInteractor.Companion.TO_PRIMARY_BOUNCER_DURATION
import com.android.systemui.keyguard.shared.model.Edge
@@ -29,6 +30,7 @@ import com.android.systemui.scene.shared.model.Scenes
import javax.inject.Inject
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.emptyFlow
/**
* Breaks down DOZING->PRIMARY BOUNCER transition into discrete steps for corresponding views to
@@ -64,6 +66,13 @@ constructor(private val blurConfig: BlurConfig, animationFlow: KeyguardTransitio
},
onFinish = { blurConfig.maxBlurRadiusPx },
)
+
+ val lockscreenAlpha: Flow<Float> =
+ if (Flags.bouncerUiRevamp()) transitionAnimation.immediatelyTransitionTo(0.0f)
+ else emptyFlow()
+
+ val notificationAlpha: Flow<Float> = lockscreenAlpha
+
override val notificationBlurRadius: Flow<Float> =
transitionAnimation.immediatelyTransitionTo(0f)
}
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 eaba5d5a149c..e51e05b8ab61 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
@@ -96,9 +96,12 @@ constructor(
private val aodToGoneTransitionViewModel: AodToGoneTransitionViewModel,
private val aodToLockscreenTransitionViewModel: AodToLockscreenTransitionViewModel,
private val aodToOccludedTransitionViewModel: AodToOccludedTransitionViewModel,
+ private val aodToPrimaryBouncerTransitionViewModel: AodToPrimaryBouncerTransitionViewModel,
private val dozingToGoneTransitionViewModel: DozingToGoneTransitionViewModel,
private val dozingToLockscreenTransitionViewModel: DozingToLockscreenTransitionViewModel,
private val dozingToOccludedTransitionViewModel: DozingToOccludedTransitionViewModel,
+ private val dozingToPrimaryBouncerTransitionViewModel:
+ DozingToPrimaryBouncerTransitionViewModel,
private val dreamingToAodTransitionViewModel: DreamingToAodTransitionViewModel,
private val dreamingToGoneTransitionViewModel: DreamingToGoneTransitionViewModel,
private val dreamingToLockscreenTransitionViewModel: DreamingToLockscreenTransitionViewModel,
@@ -243,9 +246,11 @@ constructor(
aodToGoneTransitionViewModel.lockscreenAlpha(viewState),
aodToLockscreenTransitionViewModel.lockscreenAlpha(viewState),
aodToOccludedTransitionViewModel.lockscreenAlpha(viewState),
+ aodToPrimaryBouncerTransitionViewModel.lockscreenAlpha,
dozingToGoneTransitionViewModel.lockscreenAlpha(viewState),
dozingToLockscreenTransitionViewModel.lockscreenAlpha,
dozingToOccludedTransitionViewModel.lockscreenAlpha(viewState),
+ dozingToPrimaryBouncerTransitionViewModel.lockscreenAlpha,
dreamingToAodTransitionViewModel.lockscreenAlpha,
dreamingToGoneTransitionViewModel.lockscreenAlpha,
dreamingToLockscreenTransitionViewModel.lockscreenAlpha,
diff --git a/packages/SystemUI/src/com/android/systemui/log/LogBufferFactory.kt b/packages/SystemUI/src/com/android/systemui/log/LogBufferFactory.kt
index 6351d7d28d07..c9d6f81dc79c 100644
--- a/packages/SystemUI/src/com/android/systemui/log/LogBufferFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/LogBufferFactory.kt
@@ -18,6 +18,7 @@ package com.android.systemui.log
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dump.DumpManager
+import com.android.systemui.log.LogBuffer.Companion.DEFAULT_LOGBUFFER_TRACK_NAME
import com.android.systemui.log.LogBufferHelper.Companion.adjustMaxSize
import com.android.systemui.log.echo.LogcatEchoTrackerAlways
import javax.inject.Inject
@@ -27,7 +28,7 @@ class LogBufferFactory
@Inject
constructor(
private val dumpManager: DumpManager,
- private val logcatEchoTracker: LogcatEchoTracker
+ private val logcatEchoTracker: LogcatEchoTracker,
) {
@JvmOverloads
fun create(
@@ -35,9 +36,11 @@ constructor(
maxSize: Int,
systrace: Boolean = true,
alwaysLogToLogcat: Boolean = false,
+ systraceTrackName: String = DEFAULT_LOGBUFFER_TRACK_NAME,
): LogBuffer {
val echoTracker = if (alwaysLogToLogcat) LogcatEchoTrackerAlways else logcatEchoTracker
- val buffer = LogBuffer(name, adjustMaxSize(maxSize), echoTracker, systrace)
+ val buffer =
+ LogBuffer(name, adjustMaxSize(maxSize), echoTracker, systrace, systraceTrackName)
dumpManager.registerBuffer(name, buffer)
return buffer
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImpl.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImpl.kt
index 2191f379e812..f1f299aac2b4 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImpl.kt
@@ -45,16 +45,15 @@ import android.media.session.MediaController
import android.media.session.MediaSession
import android.media.session.PlaybackState
import android.net.Uri
-import android.os.Parcelable
import android.os.Process
import android.os.UserHandle
-import android.provider.Settings
import android.service.notification.StatusBarNotification
import android.support.v4.media.MediaMetadataCompat
import android.text.TextUtils
import android.util.Log
import android.util.Pair as APair
import androidx.media.utils.MediaConstants
+import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.app.tracing.traceSection
import com.android.internal.annotations.Keep
import com.android.internal.logging.InstanceId
@@ -86,11 +85,9 @@ import com.android.systemui.media.controls.util.MediaDataUtils
import com.android.systemui.media.controls.util.MediaFlags
import com.android.systemui.media.controls.util.MediaUiEventLogger
import com.android.systemui.media.controls.util.SmallHash
-import com.android.systemui.plugins.BcSmartspaceDataPlugin
import com.android.systemui.res.R
import com.android.systemui.statusbar.NotificationMediaManager.isPlayingState
import com.android.systemui.statusbar.notification.row.HybridGroupManager
-import com.android.systemui.tuner.TunerService
import com.android.systemui.util.Assert
import com.android.systemui.util.Utils
import com.android.systemui.util.concurrency.DelayableExecutor
@@ -103,7 +100,6 @@ import java.util.concurrent.Executor
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
-import com.android.app.tracing.coroutines.launchTraced as launch
import kotlinx.coroutines.withContext
// URI fields to try loading album art from
@@ -152,22 +148,6 @@ internal val EMPTY_SMARTSPACE_MEDIA_DATA =
expiryTimeMs = 0,
)
-const val MEDIA_TITLE_ERROR_MESSAGE = "Invalid media data: title is null or blank."
-
-/**
- * Allow recommendations from smartspace to show in media controls. Requires
- * [Utils.useQsMediaPlayer] to be enabled. On by default, but can be disabled by setting to 0
- */
-private fun allowMediaRecommendations(context: Context): Boolean {
- val flag =
- Settings.Secure.getInt(
- context.contentResolver,
- Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION,
- 1,
- )
- return Utils.useQsMediaPlayer(context) && flag > 0
-}
-
/** A class that facilitates management and loading of Media Data, ready for binding. */
@SysUISingleton
class LegacyMediaDataManagerImpl(
@@ -191,14 +171,13 @@ class LegacyMediaDataManagerImpl(
private var useMediaResumption: Boolean,
private val useQsMediaPlayer: Boolean,
private val systemClock: SystemClock,
- private val tunerService: TunerService,
private val mediaFlags: MediaFlags,
private val logger: MediaUiEventLogger,
private val smartspaceManager: SmartspaceManager?,
private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
private val mediaDataLoader: dagger.Lazy<MediaDataLoader>,
private val mediaLogger: MediaLogger,
-) : Dumpable, BcSmartspaceDataPlugin.SmartspaceTargetListener, MediaDataManager {
+) : Dumpable, MediaDataManager {
companion object {
// UI surface label for subscribing Smartspace updates.
@@ -238,7 +217,6 @@ class LegacyMediaDataManagerImpl(
// There should ONLY be at most one Smartspace media recommendation.
var smartspaceMediaData: SmartspaceMediaData = EMPTY_SMARTSPACE_MEDIA_DATA
@Keep private var smartspaceSession: SmartspaceSession? = null
- private var allowMediaRecommendations = allowMediaRecommendations(context)
private val artworkWidth =
context.resources.getDimensionPixelSize(
@@ -276,7 +254,6 @@ class LegacyMediaDataManagerImpl(
mediaDataFilter: LegacyMediaDataFilterImpl,
smartspaceMediaDataProvider: SmartspaceMediaDataProvider,
clock: SystemClock,
- tunerService: TunerService,
mediaFlags: MediaFlags,
logger: MediaUiEventLogger,
smartspaceManager: SmartspaceManager?,
@@ -306,7 +283,6 @@ class LegacyMediaDataManagerImpl(
Utils.useMediaResumption(context),
Utils.useQsMediaPlayer(context),
clock,
- tunerService,
mediaFlags,
logger,
smartspaceManager,
@@ -372,7 +348,7 @@ class LegacyMediaDataManagerImpl(
context.registerReceiver(appChangeReceiver, uninstallFilter)
// Register for Smartspace data updates.
- smartspaceMediaDataProvider.registerListener(this)
+ // TODO(b/382680767): remove
smartspaceSession =
smartspaceManager?.createSmartspaceSession(
SmartspaceConfig.Builder(context, SMARTSPACE_UI_SURFACE_LABEL).build()
@@ -391,24 +367,9 @@ class LegacyMediaDataManagerImpl(
)
}
smartspaceSession?.let { it.requestSmartspaceUpdate() }
- tunerService.addTunable(
- object : TunerService.Tunable {
- override fun onTuningChanged(key: String?, newValue: String?) {
- allowMediaRecommendations = allowMediaRecommendations(context)
- if (!allowMediaRecommendations) {
- dismissSmartspaceRecommendation(
- key = smartspaceMediaData.targetId,
- delay = 0L,
- )
- }
- }
- },
- Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION,
- )
}
override fun destroy() {
- smartspaceMediaDataProvider.unregisterListener(this)
smartspaceSession?.close()
smartspaceSession = null
context.unregisterReceiver(appChangeReceiver)
@@ -1328,61 +1289,6 @@ class LegacyMediaDataManagerImpl(
}
}
- override fun onSmartspaceTargetsUpdated(targets: List<Parcelable>) {
- if (!allowMediaRecommendations) {
- if (DEBUG) Log.d(TAG, "Smartspace recommendation is disabled in Settings.")
- return
- }
-
- val mediaTargets = targets.filterIsInstance<SmartspaceTarget>()
- when (mediaTargets.size) {
- 0 -> {
- if (!smartspaceMediaData.isActive) {
- return
- }
- if (DEBUG) {
- Log.d(TAG, "Set Smartspace media to be inactive for the data update")
- }
- if (mediaFlags.isPersistentSsCardEnabled()) {
- // Smartspace uses this signal to hide the card (e.g. when it expires or user
- // disconnects headphones), so treat as setting inactive when flag is on
- smartspaceMediaData = smartspaceMediaData.copy(isActive = false)
- notifySmartspaceMediaDataLoaded(
- smartspaceMediaData.targetId,
- smartspaceMediaData,
- )
- } else {
- smartspaceMediaData =
- EMPTY_SMARTSPACE_MEDIA_DATA.copy(
- targetId = smartspaceMediaData.targetId,
- instanceId = smartspaceMediaData.instanceId,
- )
- notifySmartspaceMediaDataRemoved(
- smartspaceMediaData.targetId,
- immediately = false,
- )
- }
- }
- 1 -> {
- val newMediaTarget = mediaTargets.get(0)
- if (smartspaceMediaData.targetId == newMediaTarget.smartspaceTargetId) {
- // The same Smartspace updates can be received. Skip the duplicate updates.
- return
- }
- if (DEBUG) Log.d(TAG, "Forwarding Smartspace media update.")
- smartspaceMediaData = toSmartspaceMediaData(newMediaTarget)
- notifySmartspaceMediaDataLoaded(smartspaceMediaData.targetId, smartspaceMediaData)
- }
- else -> {
- // There should NOT be more than 1 Smartspace media update. When it happens, it
- // indicates a bad state or an error. Reset the status accordingly.
- Log.wtf(TAG, "More than 1 Smartspace Media Update. Resetting the status...")
- notifySmartspaceMediaDataRemoved(smartspaceMediaData.targetId, immediately = false)
- smartspaceMediaData = EMPTY_SMARTSPACE_MEDIA_DATA
- }
- }
- }
-
override fun onNotificationRemoved(key: String) {
Assert.isMainThread()
val removed = mediaEntries.remove(key) ?: return
@@ -1641,7 +1547,6 @@ class LegacyMediaDataManagerImpl(
println("externalListeners: ${mediaDataFilter.listeners}")
println("mediaEntries: $mediaEntries")
println("useMediaResumption: $useMediaResumption")
- println("allowMediaRecommendations: $allowMediaRecommendations")
}
mediaDeviceManager.dump(pw)
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessor.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessor.kt
index 3821f3d8c111..a524db4437a5 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessor.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessor.kt
@@ -45,16 +45,15 @@ import android.media.session.MediaController
import android.media.session.MediaSession
import android.media.session.PlaybackState
import android.net.Uri
-import android.os.Parcelable
import android.os.Process
import android.os.UserHandle
-import android.provider.Settings
import android.service.notification.StatusBarNotification
import android.support.v4.media.MediaMetadataCompat
import android.text.TextUtils
import android.util.Log
import android.util.Pair as APair
import androidx.media.utils.MediaConstants
+import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.app.tracing.traceSection
import com.android.internal.annotations.Keep
import com.android.internal.logging.InstanceId
@@ -87,8 +86,6 @@ import com.android.systemui.media.controls.util.MediaDataUtils
import com.android.systemui.media.controls.util.MediaFlags
import com.android.systemui.media.controls.util.MediaUiEventLogger
import com.android.systemui.media.controls.util.SmallHash
-import com.android.systemui.plugins.ActivityStarter
-import com.android.systemui.plugins.BcSmartspaceDataPlugin
import com.android.systemui.res.R
import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.statusbar.NotificationMediaManager.isPlayingState
@@ -97,8 +94,6 @@ import com.android.systemui.util.Assert
import com.android.systemui.util.Utils
import com.android.systemui.util.concurrency.DelayableExecutor
import com.android.systemui.util.concurrency.ThreadFactory
-import com.android.systemui.util.settings.SecureSettings
-import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
import com.android.systemui.util.time.SystemClock
import java.io.IOException
import java.io.PrintWriter
@@ -106,12 +101,6 @@ import java.util.concurrent.Executor
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.collectLatest
-import kotlinx.coroutines.flow.distinctUntilChanged
-import kotlinx.coroutines.flow.flowOn
-import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.onStart
-import com.android.app.tracing.coroutines.launchTraced as launch
import kotlinx.coroutines.withContext
// URI fields to try loading album art from
@@ -139,12 +128,10 @@ class MediaDataProcessor(
private val mediaControllerFactory: MediaControllerFactory,
private val broadcastDispatcher: BroadcastDispatcher,
private val dumpManager: DumpManager,
- private val activityStarter: ActivityStarter,
private val smartspaceMediaDataProvider: SmartspaceMediaDataProvider,
private var useMediaResumption: Boolean,
private val useQsMediaPlayer: Boolean,
private val systemClock: SystemClock,
- private val secureSettings: SecureSettings,
private val mediaFlags: MediaFlags,
private val logger: MediaUiEventLogger,
private val smartspaceManager: SmartspaceManager?,
@@ -152,7 +139,7 @@ class MediaDataProcessor(
private val mediaDataRepository: MediaDataRepository,
private val mediaDataLoader: dagger.Lazy<MediaDataLoader>,
private val mediaLogger: MediaLogger,
-) : CoreStartable, BcSmartspaceDataPlugin.SmartspaceTargetListener {
+) : CoreStartable {
companion object {
/**
@@ -191,7 +178,6 @@ class MediaDataProcessor(
// There should ONLY be at most one Smartspace media recommendation.
@Keep private var smartspaceSession: SmartspaceSession? = null
- private var allowMediaRecommendations = false
private val artworkWidth =
context.resources.getDimensionPixelSize(
@@ -221,10 +207,8 @@ class MediaDataProcessor(
mediaControllerFactory: MediaControllerFactory,
dumpManager: DumpManager,
broadcastDispatcher: BroadcastDispatcher,
- activityStarter: ActivityStarter,
smartspaceMediaDataProvider: SmartspaceMediaDataProvider,
clock: SystemClock,
- secureSettings: SecureSettings,
mediaFlags: MediaFlags,
logger: MediaUiEventLogger,
smartspaceManager: SmartspaceManager?,
@@ -245,12 +229,10 @@ class MediaDataProcessor(
mediaControllerFactory,
broadcastDispatcher,
dumpManager,
- activityStarter,
smartspaceMediaDataProvider,
Utils.useMediaResumption(context),
Utils.useQsMediaPlayer(context),
clock,
- secureSettings,
mediaFlags,
logger,
smartspaceManager,
@@ -296,7 +278,7 @@ class MediaDataProcessor(
context.registerReceiver(appChangeReceiver, uninstallFilter)
// Register for Smartspace data updates.
- smartspaceMediaDataProvider.registerListener(this)
+ // TODO(b/382680767): remove
smartspaceSession =
smartspaceManager?.createSmartspaceSession(
SmartspaceConfig.Builder(context, SMARTSPACE_UI_SURFACE_LABEL).build()
@@ -314,13 +296,9 @@ class MediaDataProcessor(
}
}
smartspaceSession?.requestSmartspaceUpdate()
-
- // Track media controls recommendation setting.
- applicationScope.launch { trackMediaControlsRecommendationSetting() }
}
fun destroy() {
- smartspaceMediaDataProvider.unregisterListener(this)
smartspaceSession?.close()
smartspaceSession = null
context.unregisterReceiver(appChangeReceiver)
@@ -357,43 +335,6 @@ class MediaDataProcessor(
}
}
- /**
- * Allow recommendations from smartspace to show in media controls. Requires
- * [Utils.useQsMediaPlayer] to be enabled. On by default, but can be disabled by setting to 0
- */
- private suspend fun allowMediaRecommendations(): Boolean {
- return withContext(backgroundDispatcher) {
- val flag =
- secureSettings.getBoolForUser(
- Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION,
- true,
- UserHandle.USER_CURRENT,
- )
-
- useQsMediaPlayer && flag
- }
- }
-
- private suspend fun trackMediaControlsRecommendationSetting() {
- secureSettings
- .observerFlow(UserHandle.USER_ALL, Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION)
- // perform a query at the beginning.
- .onStart { emit(Unit) }
- .map { allowMediaRecommendations() }
- .distinctUntilChanged()
- .flowOn(backgroundDispatcher)
- // only track the most recent emission
- .collectLatest {
- allowMediaRecommendations = it
- if (!allowMediaRecommendations) {
- dismissSmartspaceRecommendation(
- key = mediaDataRepository.smartspaceMediaData.value.targetId,
- delay = 0L,
- )
- }
- }
- }
-
private fun removeAllForPackage(packageName: String) {
Assert.isMainThread()
val toRemove =
@@ -1277,62 +1218,6 @@ class MediaDataProcessor(
}
}
- override fun onSmartspaceTargetsUpdated(targets: List<Parcelable>) {
- if (!allowMediaRecommendations) {
- if (DEBUG) Log.d(TAG, "Smartspace recommendation is disabled in Settings.")
- return
- }
-
- val mediaTargets = targets.filterIsInstance<SmartspaceTarget>()
- val smartspaceMediaData = mediaDataRepository.smartspaceMediaData.value
- when (mediaTargets.size) {
- 0 -> {
- if (!smartspaceMediaData.isActive) {
- return
- }
- if (DEBUG) {
- Log.d(TAG, "Set Smartspace media to be inactive for the data update")
- }
- if (mediaFlags.isPersistentSsCardEnabled()) {
- // Smartspace uses this signal to hide the card (e.g. when it expires or user
- // disconnects headphones), so treat as setting inactive when flag is on
- val recommendation = smartspaceMediaData.copy(isActive = false)
- mediaDataRepository.setRecommendation(recommendation)
- notifySmartspaceMediaDataLoaded(recommendation.targetId, recommendation)
- } else {
- notifySmartspaceMediaDataRemoved(
- smartspaceMediaData.targetId,
- immediately = false,
- )
- mediaDataRepository.setRecommendation(
- SmartspaceMediaData(
- targetId = smartspaceMediaData.targetId,
- instanceId = smartspaceMediaData.instanceId,
- )
- )
- }
- }
- 1 -> {
- val newMediaTarget = mediaTargets.get(0)
- if (smartspaceMediaData.targetId == newMediaTarget.smartspaceTargetId) {
- // The same Smartspace updates can be received. Skip the duplicate updates.
- return
- }
- if (DEBUG) Log.d(TAG, "Forwarding Smartspace media update.")
- val recommendation = toSmartspaceMediaData(newMediaTarget)
- mediaDataRepository.setRecommendation(recommendation)
- notifySmartspaceMediaDataLoaded(recommendation.targetId, recommendation)
- }
- else -> {
- // There should NOT be more than 1 Smartspace media update. When it happens, it
- // indicates a bad state or an error. Reset the status accordingly.
- Log.wtf(TAG, "More than 1 Smartspace Media Update. Resetting the status...")
- notifySmartspaceMediaDataRemoved(smartspaceMediaData.targetId, immediately = false)
- mediaDataRepository.setRecommendation(SmartspaceMediaData())
- }
- }
- }
-
fun onNotificationRemoved(key: String) {
Assert.isMainThread()
val removed = mediaDataRepository.removeMediaEntry(key) ?: return
@@ -1621,7 +1506,6 @@ class MediaDataProcessor(
pw.apply {
println("internalListeners: $internalListeners")
println("useMediaResumption: $useMediaResumption")
- println("allowMediaRecommendations: $allowMediaRecommendations")
}
}
}
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 86e92941256b..975f8f45f9c4 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
@@ -1042,6 +1042,18 @@ constructor(
return null
}
+ if (state.expansion == 1.0f) {
+ val height =
+ if (state.expandedMatchesParentHeight) {
+ heightInSceneContainerPx
+ } else {
+ context.resources.getDimensionPixelSize(
+ R.dimen.qs_media_session_height_expanded
+ )
+ }
+ setBackgroundHeights(height)
+ }
+
// Similar to obtainViewState: Let's create a new measurement
val result =
transitionLayout?.calculateViewState(
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt
index d94424c59376..e19d6e9e0179 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt
@@ -57,6 +57,7 @@ constructor(
// activity and a null second task, so the foreground task will be index 1, but when
// opening the app selector in split screen mode, the foreground task will be the second
// task in index 0.
+ // TODO(346588978): This needs to be updated for mixed groups
val foregroundGroup =
if (groupedTasks.firstOrNull()?.splitBounds != null) groupedTasks.first()
else groupedTasks.elementAtOrNull(1)
@@ -69,7 +70,7 @@ constructor(
it.taskInfo1,
it.taskInfo1.taskId in foregroundTaskIds && it.taskInfo1.isVisible,
userManager.getUserInfo(it.taskInfo1.userId).toUserType(),
- it.splitBounds
+ it.splitBounds,
)
val task2 =
@@ -78,7 +79,7 @@ constructor(
it.taskInfo2!!,
it.taskInfo2!!.taskId in foregroundTaskIds && it.taskInfo2!!.isVisible,
userManager.getUserInfo(it.taskInfo2!!.userId).toUserType(),
- it.splitBounds
+ it.splitBounds,
)
} else null
@@ -92,7 +93,7 @@ constructor(
Integer.MAX_VALUE,
RECENT_IGNORE_UNAVAILABLE,
userTracker.userId,
- backgroundExecutor
+ backgroundExecutor,
) { tasks ->
continuation.resume(tasks)
}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
index 3f14b55e46a1..9270fff61c43 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
@@ -236,11 +236,29 @@ public class TaskbarDelegate implements CommandQueue.Callbacks,
@Override
public void onDisplayReady(int displayId) {
CommandQueue.Callbacks.super.onDisplayReady(displayId);
+ if (mOverviewProxyService.getProxy() == null) {
+ return;
+ }
+
+ try {
+ mOverviewProxyService.getProxy().onDisplayReady(displayId);
+ } catch (RemoteException e) {
+ Log.e(TAG, "onDisplayReady() failed", e);
+ }
}
@Override
public void onDisplayRemoved(int displayId) {
CommandQueue.Callbacks.super.onDisplayRemoved(displayId);
+ if (mOverviewProxyService.getProxy() == null) {
+ return;
+ }
+
+ try {
+ mOverviewProxyService.getProxy().onDisplayRemoved(displayId);
+ } catch (RemoteException e) {
+ Log.e(TAG, "onDisplayRemoved() failed", e);
+ }
}
// Separated into a method to keep setDependencies() clean/readable.
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogV2.kt b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogV2.kt
index f53b6cd29806..57d40638b8df 100644
--- a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogV2.kt
+++ b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogV2.kt
@@ -39,7 +39,6 @@ import androidx.annotation.DrawableRes
import androidx.annotation.WorkerThread
import androidx.core.view.ViewCompat
import androidx.core.view.accessibility.AccessibilityNodeInfoCompat
-import com.android.settingslib.Utils
import com.android.systemui.animation.ViewHierarchyAnimator
import com.android.systemui.res.R
import com.android.systemui.statusbar.phone.SystemUIDialog
@@ -63,7 +62,7 @@ class PrivacyDialogV2(
private val list: List<PrivacyElement>,
private val manageApp: (String, Int, Intent) -> Unit,
private val closeApp: (String, Int) -> Unit,
- private val openPrivacyDashboard: () -> Unit
+ private val openPrivacyDashboard: () -> Unit,
) : SystemUIDialog(context, R.style.Theme_PrivacyDialog) {
private val dismissListeners = mutableListOf<WeakReference<OnDialogDismissed>>()
@@ -192,11 +191,9 @@ class PrivacyDialogV2(
return null
}
val closeAppButton =
- checkNotNull(window).layoutInflater.inflate(
- R.layout.privacy_dialog_card_button,
- expandedLayout,
- false
- ) as Button
+ checkNotNull(window)
+ .layoutInflater
+ .inflate(R.layout.privacy_dialog_card_button, expandedLayout, false) as Button
expandedLayout.addView(closeAppButton)
closeAppButton.id = R.id.privacy_dialog_close_app_button
closeAppButton.setText(R.string.privacy_dialog_close_app_button)
@@ -248,11 +245,9 @@ class PrivacyDialogV2(
private fun configureManageButton(element: PrivacyElement, expandedLayout: ViewGroup): View {
val manageButton =
- checkNotNull(window).layoutInflater.inflate(
- R.layout.privacy_dialog_card_button,
- expandedLayout,
- false
- ) as Button
+ checkNotNull(window)
+ .layoutInflater
+ .inflate(R.layout.privacy_dialog_card_button, expandedLayout, false) as Button
expandedLayout.addView(manageButton)
manageButton.id = R.id.privacy_dialog_manage_app_button
manageButton.setText(
@@ -294,7 +289,7 @@ class PrivacyDialogV2(
itemCard,
AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_CLICK,
context.getString(R.string.privacy_dialog_expand_action),
- null
+ null,
)
val expandedLayout =
@@ -311,7 +306,7 @@ class PrivacyDialogV2(
it!!,
AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_CLICK,
context.getString(R.string.privacy_dialog_expand_action),
- null
+ null,
)
} else {
expandedLayout.visibility = View.VISIBLE
@@ -320,12 +315,12 @@ class PrivacyDialogV2(
it!!,
AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_CLICK,
context.getString(R.string.privacy_dialog_collapse_action),
- null
+ null,
)
}
ViewHierarchyAnimator.animateNextUpdate(
rootView = window!!.decorView,
- excludedViews = setOf(expandedLayout)
+ excludedViews = setOf(expandedLayout),
)
}
}
@@ -345,19 +340,13 @@ class PrivacyDialogV2(
@ColorInt
private fun getForegroundColor(active: Boolean) =
- Utils.getColorAttrDefaultColor(
- context,
- if (active) com.android.internal.R.color.materialColorOnPrimaryFixed
- else com.android.internal.R.color.materialColorOnSurface,
- )
+ if (active) context.getColor(com.android.internal.R.color.materialColorOnPrimaryFixed)
+ else context.getColor(com.android.internal.R.color.materialColorOnSurface)
@ColorInt
private fun getBackgroundColor(active: Boolean) =
- Utils.getColorAttrDefaultColor(
- context,
- if (active) com.android.internal.R.color.materialColorPrimaryFixed
- else com.android.internal.R.color.materialColorSurfaceContainerHigh,
- )
+ if (active) context.getColor(com.android.internal.R.color.materialColorPrimaryFixed)
+ else context.getColor(com.android.internal.R.color.materialColorSurfaceContainerHigh)
private fun getMutableDrawable(@DrawableRes resId: Int) = context.getDrawable(resId)!!.mutate()
@@ -379,7 +368,7 @@ class PrivacyDialogV2(
context.getString(
singleUsageResId,
element.applicationName,
- element.attributionLabel ?: element.proxyLabel
+ element.attributionLabel ?: element.proxyLabel,
)
} else {
val doubleUsageResId: Int =
@@ -389,7 +378,7 @@ class PrivacyDialogV2(
doubleUsageResId,
element.applicationName,
element.attributionLabel,
- element.proxyLabel
+ element.proxyLabel,
)
}
@@ -429,7 +418,7 @@ class PrivacyDialogV2(
return groupInfo.loadSafeLabel(
this,
0f,
- TextUtils.SAFE_STRING_FLAG_FIRST_LINE or TextUtils.SAFE_STRING_FLAG_TRIM
+ TextUtils.SAFE_STRING_FLAG_FIRST_LINE or TextUtils.SAFE_STRING_FLAG_TRIM,
)
}
@@ -472,7 +461,7 @@ class PrivacyDialogV2(
icon: Drawable,
iconSize: Int,
background: Drawable,
- backgroundSize: Int
+ backgroundSize: Int,
): Drawable {
val layered = LayerDrawable(arrayOf(background, icon))
layered.setLayerSize(0, backgroundSize, backgroundSize)
@@ -497,7 +486,7 @@ class PrivacyDialogV2(
val isPhoneCall: Boolean,
val isService: Boolean,
val permGroupName: String,
- val navigationIntent: Intent
+ val navigationIntent: Intent,
) {
private val builder = StringBuilder("PrivacyElement(")
diff --git a/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt b/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt
index b53685ee3cd4..6d9078432ae0 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt
@@ -57,7 +57,6 @@ import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableFloatStateOf
import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.Alignment
@@ -95,6 +94,7 @@ import com.android.compose.animation.scene.MutableSceneTransitionLayoutState
import com.android.compose.animation.scene.SceneKey
import com.android.compose.animation.scene.SceneTransitionLayout
import com.android.compose.animation.scene.content.state.TransitionState
+import com.android.compose.animation.scene.rememberMutableSceneTransitionLayoutState
import com.android.compose.animation.scene.transitions
import com.android.compose.modifiers.height
import com.android.compose.modifiers.padding
@@ -313,8 +313,8 @@ constructor(
*/
@Composable
private fun CollapsableQuickSettingsSTL() {
- val sceneState = remember {
- MutableSceneTransitionLayoutState(
+ val sceneState =
+ rememberMutableSceneTransitionLayoutState(
viewModel.expansionState.toIdleSceneKey(),
transitions =
transitions {
@@ -323,7 +323,6 @@ constructor(
}
},
)
- }
LaunchedEffect(Unit) {
synchronizeQsState(
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
index 04f0b8736598..1e8ef359bb71 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
@@ -42,7 +42,7 @@ import androidx.annotation.Nullable;
import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import com.android.settingslib.notification.modes.EnableZenModeDialog;
+import com.android.settingslib.notification.modes.EnableDndDialogFactory;
import com.android.systemui.Prefs;
import com.android.systemui.animation.DialogCuj;
import com.android.systemui.animation.DialogTransitionAnimator;
@@ -59,7 +59,7 @@ import com.android.systemui.qs.QsEventLogger;
import com.android.systemui.qs.UserSettingObserver;
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.qs.tileimpl.QSTileImpl;
-import com.android.systemui.qs.tiles.dialog.QSZenModeDialogMetricsLogger;
+import com.android.systemui.qs.tiles.dialog.QSEnableDndDialogMetricsLogger;
import com.android.systemui.res.R;
import com.android.systemui.statusbar.phone.SystemUIDialog;
import com.android.systemui.statusbar.policy.ZenModeController;
@@ -84,7 +84,7 @@ public class DndTile extends QSTileImpl<BooleanState> {
private final SharedPreferences mSharedPreferences;
private final UserSettingObserver mSettingZenDuration;
private final DialogTransitionAnimator mDialogTransitionAnimator;
- private final QSZenModeDialogMetricsLogger mQSZenDialogMetricsLogger;
+ private final QSEnableDndDialogMetricsLogger mQSDndDurationDialogLogger;
private boolean mListening;
@@ -121,7 +121,7 @@ public class DndTile extends QSTileImpl<BooleanState> {
refreshState();
}
};
- mQSZenDialogMetricsLogger = new QSZenModeDialogMetricsLogger(mContext);
+ mQSDndDurationDialogLogger = new QSEnableDndDialogMetricsLogger(mContext);
}
public static void setVisible(Context context, boolean visible) {
@@ -201,9 +201,9 @@ public class DndTile extends QSTileImpl<BooleanState> {
}
private Dialog makeZenModeDialog() {
- AlertDialog dialog = new EnableZenModeDialog(mContext, R.style.Theme_SystemUI_Dialog,
+ AlertDialog dialog = new EnableDndDialogFactory(mContext, R.style.Theme_SystemUI_Dialog,
true /* cancelIsNeutral */,
- mQSZenDialogMetricsLogger).createDialog();
+ mQSDndDurationDialogLogger).createDialog();
SystemUIDialog.applyFlags(dialog);
SystemUIDialog.setShowForAllUsers(dialog, true);
SystemUIDialog.registerDismissListener(dialog);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/QSZenModeDialogMetricsLogger.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/QSEnableDndDialogMetricsLogger.java
index b3f66a6bf9dd..5196a22df29a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/QSZenModeDialogMetricsLogger.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/QSEnableDndDialogMetricsLogger.java
@@ -19,7 +19,7 @@ package com.android.systemui.qs.tiles.dialog;
import android.content.Context;
import com.android.internal.logging.UiEventLogger;
-import com.android.settingslib.notification.modes.ZenModeDialogMetricsLogger;
+import com.android.settingslib.notification.modes.EnableDndDialogMetricsLogger;
import com.android.systemui.qs.QSDndEvent;
import com.android.systemui.qs.QSEvents;
@@ -30,10 +30,10 @@ import com.android.systemui.qs.QSEvents;
*
* Other names for DND (Do Not Disturb) include "Zen" and "Priority only".
*/
-public class QSZenModeDialogMetricsLogger extends ZenModeDialogMetricsLogger {
+public class QSEnableDndDialogMetricsLogger extends EnableDndDialogMetricsLogger {
private final UiEventLogger mUiEventLogger = QSEvents.INSTANCE.getQsUiEventsLogger();
- public QSZenModeDialogMetricsLogger(Context context) {
+ public QSEnableDndDialogMetricsLogger(Context context) {
super(context);
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractor.kt
index 594394f68d48..5ce7f0d039c8 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractor.kt
@@ -29,6 +29,7 @@ import com.android.systemui.qs.tiles.impl.modes.domain.model.ModesTileModel
import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction
import com.android.systemui.statusbar.policy.domain.interactor.ZenModeInteractor
import com.android.systemui.statusbar.policy.ui.dialog.ModesDialogDelegate
+import com.android.systemui.statusbar.policy.ui.dialog.ModesDialogEventLogger
import javax.inject.Inject
@SysUISingleton
@@ -39,6 +40,7 @@ constructor(
// TODO(b/353896370): The domain layer should not have to depend on the UI layer.
private val dialogDelegate: ModesDialogDelegate,
private val zenModeInteractor: ZenModeInteractor,
+ private val dialogEventLogger: ModesDialogEventLogger,
) : QSTileUserActionInteractor<ModesTileModel> {
val longClickIntent = Intent(Settings.ACTION_ZEN_MODE_SETTINGS)
@@ -78,7 +80,16 @@ constructor(
Log.wtf(TAG, "Triggered DND but it's null!?")
return
}
- zenModeInteractor.activateMode(dnd)
+
+ if (zenModeInteractor.shouldAskForZenDuration(dnd)) {
+ dialogEventLogger.logOpenDurationDialog(dnd)
+ // NOTE: The dialog handles turning on the mode itself.
+ val dialog = dialogDelegate.makeDndDurationDialog()
+ dialog.show()
+ } else {
+ dialogEventLogger.logModeOn(dnd)
+ zenModeInteractor.activateMode(dnd)
+ }
} else {
zenModeInteractor.deactivateAllModes()
}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt
index c1e8032aa9e5..40e6d284cbc9 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt
@@ -30,6 +30,7 @@ import com.android.compose.animation.scene.UserAction
import com.android.compose.animation.scene.UserActionResult
import com.android.systemui.classifier.Classifier
import com.android.systemui.classifier.domain.interactor.FalsingInteractor
+import com.android.systemui.keyguard.ui.viewmodel.LightRevealScrimViewModel
import com.android.systemui.lifecycle.ExclusiveActivatable
import com.android.systemui.lifecycle.Hydrator
import com.android.systemui.power.domain.interactor.PowerInteractor
@@ -41,6 +42,7 @@ import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.shade.shared.model.ShadeMode
import com.android.systemui.statusbar.domain.interactor.RemoteInputInteractor
import com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer
+import com.android.systemui.wallpapers.ui.viewmodel.WallpaperViewModel
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
@@ -62,6 +64,8 @@ constructor(
private val splitEdgeDetector: SplitEdgeDetector,
private val logger: SceneLogger,
hapticsViewModelFactory: SceneContainerHapticsViewModel.Factory,
+ val lightRevealScrim: LightRevealScrimViewModel,
+ val wallpaperViewModel: WallpaperViewModel,
@Assisted view: View,
@Assisted private val motionEventHandlerReceiver: (MotionEventHandler?) -> Unit,
) : ExclusiveActivatable() {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeStateTraceLogger.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeStateTraceLogger.kt
index 2705cdafb4de..39703ab5602c 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeStateTraceLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeStateTraceLogger.kt
@@ -25,6 +25,8 @@ import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.shade.data.repository.ShadeDisplaysRepository
import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.shade.shared.flag.ShadeWindowGoesAround
+import dagger.Lazy
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
@@ -34,7 +36,7 @@ class ShadeStateTraceLogger
@Inject
constructor(
private val shadeInteractor: ShadeInteractor,
- private val shadeDisplaysRepository: ShadeDisplaysRepository,
+ private val shadeDisplaysRepository: Lazy<ShadeDisplaysRepository>,
@Application private val scope: CoroutineScope,
) : CoreStartable {
override fun start() {
@@ -52,9 +54,11 @@ constructor(
instantForGroup(TRACK_GROUP_NAME, "shadeExpansion", it)
}
}
- launch {
- shadeDisplaysRepository.displayId.collect {
- instantForGroup(TRACK_GROUP_NAME, "displayId", it)
+ if (ShadeWindowGoesAround.isEnabled) {
+ launch {
+ shadeDisplaysRepository.get().displayId.collect {
+ instantForGroup(TRACK_GROUP_NAME, "displayId", it)
+ }
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt
index 2885ce80bda9..a56624ca6fbf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt
@@ -84,7 +84,7 @@ object LiftReveal : LightRevealEffect {
scrim.width * initialWidthMultiplier + -scrim.width * ovalWidthIncreaseAmount,
scrim.height * OVAL_INITIAL_TOP_PERCENT - scrim.height * interpolatedAmount,
scrim.width * (1f - initialWidthMultiplier) + scrim.width * ovalWidthIncreaseAmount,
- scrim.height * OVAL_INITIAL_BOTTOM_PERCENT + scrim.height * interpolatedAmount
+ scrim.height * OVAL_INITIAL_BOTTOM_PERCENT + scrim.height * interpolatedAmount,
)
}
}
@@ -98,7 +98,7 @@ data class LinearLightRevealEffect(private val isVertical: Boolean) : LightRevea
/* controlX1= */ 0.4f,
/* controlY1= */ 0f,
/* controlX2= */ 0.2f,
- /* controlY2= */ 1f
+ /* controlY2= */ 1f,
)
override fun setRevealAmountOnScrim(amount: Float, scrim: LightRevealScrim) {
@@ -109,14 +109,14 @@ data class LinearLightRevealEffect(private val isVertical: Boolean) : LightRevea
scrim.startColorAlpha =
getPercentPastThreshold(
1 - interpolatedAmount,
- threshold = 1 - START_COLOR_REVEAL_PERCENTAGE
+ threshold = 1 - START_COLOR_REVEAL_PERCENTAGE,
)
scrim.revealGradientEndColorAlpha =
1f -
getPercentPastThreshold(
interpolatedAmount,
- threshold = REVEAL_GRADIENT_END_COLOR_ALPHA_START_PERCENTAGE
+ threshold = REVEAL_GRADIENT_END_COLOR_ALPHA_START_PERCENTAGE,
)
// Start changing gradient bounds later to avoid harsh gradient in the beginning
@@ -127,14 +127,14 @@ data class LinearLightRevealEffect(private val isVertical: Boolean) : LightRevea
left = scrim.viewWidth / 2 - (scrim.viewWidth / 2) * gradientBoundsAmount,
top = 0f,
right = scrim.viewWidth / 2 + (scrim.viewWidth / 2) * gradientBoundsAmount,
- bottom = scrim.viewHeight.toFloat()
+ bottom = scrim.viewHeight.toFloat(),
)
} else {
scrim.setRevealGradientBounds(
left = 0f,
top = scrim.viewHeight / 2 - (scrim.viewHeight / 2) * gradientBoundsAmount,
right = scrim.viewWidth.toFloat(),
- bottom = scrim.viewHeight / 2 + (scrim.viewHeight / 2) * gradientBoundsAmount
+ bottom = scrim.viewHeight / 2 + (scrim.viewHeight / 2) * gradientBoundsAmount,
)
}
}
@@ -166,7 +166,7 @@ data class LinearSideLightRevealEffect(private val isVertical: Boolean) : LightR
1f -
getPercentPastThreshold(
amount,
- threshold = REVEAL_GRADIENT_END_COLOR_ALPHA_START_PERCENTAGE
+ threshold = REVEAL_GRADIENT_END_COLOR_ALPHA_START_PERCENTAGE,
)
val gradientBoundsAmount = lerp(GRADIENT_START_BOUNDS_PERCENTAGE, 1f, amount)
@@ -175,14 +175,14 @@ data class LinearSideLightRevealEffect(private val isVertical: Boolean) : LightR
left = -(scrim.viewWidth) * gradientBoundsAmount,
top = -(scrim.viewHeight) * gradientBoundsAmount,
right = (scrim.viewWidth) * gradientBoundsAmount,
- bottom = (scrim.viewHeight) + (scrim.viewHeight) * gradientBoundsAmount
+ bottom = (scrim.viewHeight) + (scrim.viewHeight) * gradientBoundsAmount,
)
} else {
scrim.setRevealGradientBounds(
left = -(scrim.viewWidth) * gradientBoundsAmount,
top = -(scrim.viewHeight) * gradientBoundsAmount,
right = (scrim.viewWidth) + (scrim.viewWidth) * gradientBoundsAmount,
- bottom = (scrim.viewHeight) * gradientBoundsAmount
+ bottom = (scrim.viewHeight) * gradientBoundsAmount,
)
}
}
@@ -212,7 +212,7 @@ data class CircleReveal(
/** Radius of initial state of circle reveal */
val startRadius: Int,
/** Radius of end state of circle reveal */
- val endRadius: Int
+ val endRadius: Int,
) : LightRevealEffect {
override fun setRevealAmountOnScrim(amount: Float, scrim: LightRevealScrim) {
// reveal amount updates already have an interpolator, so we intentionally use the
@@ -225,7 +225,7 @@ data class CircleReveal(
centerX - radius /* left */,
centerY - radius /* top */,
centerX + radius /* right */,
- centerY + radius /* bottom */
+ centerY + radius, /* bottom */
)
}
}
@@ -259,7 +259,7 @@ data class PowerButtonReveal(
powerButtonY - height * interpolatedAmount,
width * (1f + OFF_SCREEN_START_AMOUNT) +
width * INCREASE_MULTIPLIER * interpolatedAmount,
- powerButtonY + height * interpolatedAmount
+ powerButtonY + height * interpolatedAmount,
)
} else if (rotation == RotationUtils.ROTATION_LANDSCAPE) {
setRevealGradientBounds(
@@ -268,7 +268,7 @@ data class PowerButtonReveal(
height * INCREASE_MULTIPLIER * interpolatedAmount,
powerButtonY + width * interpolatedAmount,
(-height * OFF_SCREEN_START_AMOUNT) +
- height * INCREASE_MULTIPLIER * interpolatedAmount
+ height * INCREASE_MULTIPLIER * interpolatedAmount,
)
} else {
// RotationUtils.ROTATION_SEASCAPE
@@ -278,7 +278,7 @@ data class PowerButtonReveal(
height * INCREASE_MULTIPLIER * interpolatedAmount,
(width - powerButtonY) + width * interpolatedAmount,
height * (1f + OFF_SCREEN_START_AMOUNT) +
- height * INCREASE_MULTIPLIER * interpolatedAmount
+ height * INCREASE_MULTIPLIER * interpolatedAmount,
)
}
}
@@ -296,9 +296,9 @@ class LightRevealScrim
@JvmOverloads
constructor(
context: Context?,
- attrs: AttributeSet?,
+ attrs: AttributeSet? = null,
initialWidth: Int? = null,
- initialHeight: Int? = null
+ initialHeight: Int? = null,
) : View(context, attrs) {
private val logString = this::class.simpleName!! + "@" + hashCode()
@@ -322,8 +322,9 @@ constructor(
revealEffect.setRevealAmountOnScrim(value, this)
updateScrimOpaque()
TrackTracer.instantForGroup(
- "scrim", { "light_reveal_amount $logString" },
- (field * 100).toInt()
+ "scrim",
+ { "light_reveal_amount $logString" },
+ (field * 100).toInt(),
)
invalidate()
}
@@ -440,7 +441,7 @@ constructor(
1f,
intArrayOf(Color.TRANSPARENT, Color.WHITE),
floatArrayOf(0f, 1f),
- Shader.TileMode.CLAMP
+ Shader.TileMode.CLAMP,
)
// SRC_OVER ensures that we draw the semitransparent pixels over other views in the same
@@ -466,6 +467,7 @@ constructor(
viewWidth = measuredWidth
viewHeight = measuredHeight
}
+
/**
* Sets bounds for the transparent oval gradient that reveals the views below the scrim. This is
* simply a helper method that sets [revealGradientCenter], [revealGradientWidth], and
@@ -513,7 +515,7 @@ constructor(
gradientPaint.colorFilter =
PorterDuffColorFilter(
getColorWithAlpha(revealGradientEndColor, revealGradientEndColorAlpha),
- PorterDuff.Mode.MULTIPLY
+ PorterDuff.Mode.MULTIPLY,
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModel.kt
index 4fb8f724c971..541a07c47df2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModel.kt
@@ -17,7 +17,7 @@
package com.android.systemui.statusbar.chips.call.ui.viewmodel
import android.view.View
-import com.android.internal.jank.InteractionJankMonitor
+import com.android.internal.jank.Cuj
import com.android.systemui.animation.ActivityTransitionAnimator
import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.shared.model.Icon
@@ -37,6 +37,7 @@ import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
import com.android.systemui.statusbar.chips.ui.view.ChipBackgroundContainer
import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipViewModel
import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
+import com.android.systemui.statusbar.phone.ongoingcall.StatusBarChipsModernization
import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallModel
import com.android.systemui.util.time.SystemClock
import javax.inject.Inject
@@ -92,7 +93,8 @@ constructor(
OngoingActivityChipModel.Shown.IconOnly(
icon = icon,
colors = colors,
- getOnClickListener(state),
+ onClickListenerLegacy = getOnClickListener(state),
+ clickBehavior = getClickBehavior(state),
)
} else {
val startTimeInElapsedRealtime =
@@ -102,7 +104,8 @@ constructor(
icon = icon,
colors = colors,
startTimeMs = startTimeInElapsedRealtime,
- getOnClickListener(state),
+ onClickListenerLegacy = getOnClickListener(state),
+ clickBehavior = getClickBehavior(state),
)
}
}
@@ -116,6 +119,7 @@ constructor(
}
return View.OnClickListener { view ->
+ StatusBarChipsModernization.assertInLegacyMode()
logger.log(TAG, LogLevel.INFO, {}, { "Chip clicked" })
val backgroundView =
view.requireViewById<ChipBackgroundContainer>(R.id.ongoing_activity_chip_background)
@@ -124,12 +128,33 @@ constructor(
state.intent,
ActivityTransitionAnimator.Controller.fromView(
backgroundView,
- InteractionJankMonitor.CUJ_STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP,
+ Cuj.CUJ_STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP,
),
)
}
}
+ private fun getClickBehavior(
+ state: OngoingCallModel.InCall
+ ): OngoingActivityChipModel.ClickBehavior =
+ if (state.intent == null) {
+ OngoingActivityChipModel.ClickBehavior.None
+ } else {
+ OngoingActivityChipModel.ClickBehavior.ExpandAction(
+ onClick = { expandable ->
+ StatusBarChipsModernization.assertInNewMode()
+ val animationController =
+ expandable.activityTransitionController(
+ Cuj.CUJ_STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP
+ )
+ activityStarter.postStartActivityDismissingKeyguard(
+ state.intent,
+ animationController,
+ )
+ }
+ )
+ }
+
companion object {
private val phoneIcon =
Icon.Resource(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModel.kt
index 3422337523f9..baa8eec5f767 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModel.kt
@@ -41,6 +41,7 @@ import com.android.systemui.statusbar.chips.ui.model.ColorsModel
import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
import com.android.systemui.statusbar.chips.ui.viewmodel.ChipTransitionHelper
import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipViewModel
+import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipViewModel.Companion.createDialogLaunchOnClickCallback
import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipViewModel.Companion.createDialogLaunchOnClickListener
import com.android.systemui.util.time.SystemClock
import javax.inject.Inject
@@ -204,13 +205,25 @@ constructor(
colors = ColorsModel.Red,
// TODO(b/332662551): Maybe use a MediaProjection API to fetch this time.
startTimeMs = systemClock.elapsedRealtime(),
- createDialogLaunchOnClickListener(
- createCastScreenToOtherDeviceDialogDelegate(state),
- dialogTransitionAnimator,
- DialogCuj(Cuj.CUJ_STATUS_BAR_LAUNCH_DIALOG_FROM_CHIP, tag = "Cast to other device"),
- logger,
- TAG,
- ),
+ onClickListenerLegacy =
+ createDialogLaunchOnClickListener(
+ createCastScreenToOtherDeviceDialogDelegate(state),
+ dialogTransitionAnimator,
+ DIALOG_CUJ,
+ logger,
+ TAG,
+ ),
+ clickBehavior =
+ OngoingActivityChipModel.ClickBehavior.ExpandAction(
+ onClick =
+ createDialogLaunchOnClickCallback(
+ createCastScreenToOtherDeviceDialogDelegate(state),
+ dialogTransitionAnimator,
+ DIALOG_CUJ,
+ logger,
+ TAG,
+ )
+ ),
)
}
@@ -225,16 +238,24 @@ constructor(
)
),
colors = ColorsModel.Red,
- createDialogLaunchOnClickListener(
- createGenericCastToOtherDeviceDialogDelegate(deviceName),
- dialogTransitionAnimator,
- DialogCuj(
- Cuj.CUJ_STATUS_BAR_LAUNCH_DIALOG_FROM_CHIP,
- tag = "Cast to other device audio only",
+ onClickListenerLegacy =
+ createDialogLaunchOnClickListener(
+ createGenericCastToOtherDeviceDialogDelegate(deviceName),
+ dialogTransitionAnimator,
+ DIALOG_CUJ_AUDIO_ONLY,
+ logger,
+ TAG,
+ ),
+ clickBehavior =
+ OngoingActivityChipModel.ClickBehavior.ExpandAction(
+ createDialogLaunchOnClickCallback(
+ createGenericCastToOtherDeviceDialogDelegate(deviceName),
+ dialogTransitionAnimator,
+ DIALOG_CUJ_AUDIO_ONLY,
+ logger,
+ TAG,
+ )
),
- logger,
- TAG,
- ),
)
}
@@ -256,6 +277,13 @@ constructor(
companion object {
@DrawableRes val CAST_TO_OTHER_DEVICE_ICON = R.drawable.ic_cast_connected
+ private val DIALOG_CUJ =
+ DialogCuj(Cuj.CUJ_STATUS_BAR_LAUNCH_DIALOG_FROM_CHIP, tag = "Cast to other device")
+ private val DIALOG_CUJ_AUDIO_ONLY =
+ DialogCuj(
+ Cuj.CUJ_STATUS_BAR_LAUNCH_DIALOG_FROM_CHIP,
+ tag = "Cast to other device audio only",
+ )
private val TAG = "CastToOtherVM".pad()
}
}
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 18b0dee1c08f..b7cad625b7b8 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
@@ -83,6 +83,7 @@ constructor(
)
}
}
+ val clickBehavior = OngoingActivityChipModel.ClickBehavior.None
val isShowingHeadsUpFromChipTap =
headsUpState is TopPinnedState.Pinned &&
@@ -91,7 +92,12 @@ constructor(
if (isShowingHeadsUpFromChipTap) {
// If the user tapped this chip to show the HUN, we want to just show the icon because
// the HUN will show the rest of the information.
- return OngoingActivityChipModel.Shown.IconOnly(icon, colors, onClickListener)
+ return OngoingActivityChipModel.Shown.IconOnly(
+ icon,
+ colors,
+ onClickListener,
+ clickBehavior,
+ )
}
if (this.promotedContent.shortCriticalText != null) {
@@ -100,6 +106,7 @@ constructor(
colors,
this.promotedContent.shortCriticalText,
onClickListener,
+ clickBehavior,
)
}
@@ -111,11 +118,21 @@ constructor(
// notification will likely just be set to the current time, which would cause the chip
// to always show "now". We don't want early testers to get that experience since it's
// not what will happen at launch, so just don't show any time.
- return OngoingActivityChipModel.Shown.IconOnly(icon, colors, onClickListener)
+ return OngoingActivityChipModel.Shown.IconOnly(
+ icon,
+ colors,
+ onClickListener,
+ clickBehavior,
+ )
}
if (this.promotedContent.time == null) {
- return OngoingActivityChipModel.Shown.IconOnly(icon, colors, onClickListener)
+ return OngoingActivityChipModel.Shown.IconOnly(
+ icon,
+ colors,
+ onClickListener,
+ clickBehavior,
+ )
}
when (this.promotedContent.time.mode) {
PromotedNotificationContentModel.When.Mode.BasicTime -> {
@@ -124,6 +141,7 @@ constructor(
colors,
time = this.promotedContent.time.time,
onClickListener,
+ clickBehavior,
)
}
PromotedNotificationContentModel.When.Mode.CountUp -> {
@@ -132,6 +150,7 @@ constructor(
colors,
startTimeMs = this.promotedContent.time.time,
onClickListener,
+ clickBehavior,
)
}
PromotedNotificationContentModel.When.Mode.CountDown -> {
@@ -141,6 +160,7 @@ constructor(
colors,
startTimeMs = this.promotedContent.time.time,
onClickListener,
+ clickBehavior,
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModel.kt
index 0065593c7b73..7f2327a742e2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModel.kt
@@ -41,6 +41,7 @@ import com.android.systemui.statusbar.chips.ui.model.ColorsModel
import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
import com.android.systemui.statusbar.chips.ui.viewmodel.ChipTransitionHelper
import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipViewModel
+import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipViewModel.Companion.createDialogLaunchOnClickCallback
import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipViewModel.Companion.createDialogLaunchOnClickListener
import com.android.systemui.util.kotlin.pairwise
import com.android.systemui.util.time.SystemClock
@@ -91,16 +92,24 @@ constructor(
),
colors = ColorsModel.Red,
startTimeMs = systemClock.elapsedRealtime(),
- createDialogLaunchOnClickListener(
- createDelegate(state.recordedTask),
- dialogTransitionAnimator,
- DialogCuj(
- Cuj.CUJ_STATUS_BAR_LAUNCH_DIALOG_FROM_CHIP,
- tag = "Screen record",
+ onClickListenerLegacy =
+ createDialogLaunchOnClickListener(
+ createDelegate(state.recordedTask),
+ dialogTransitionAnimator,
+ DIALOG_CUJ,
+ logger,
+ TAG,
+ ),
+ clickBehavior =
+ OngoingActivityChipModel.ClickBehavior.ExpandAction(
+ createDialogLaunchOnClickCallback(
+ dialogDelegate = createDelegate(state.recordedTask),
+ dialogTransitionAnimator = dialogTransitionAnimator,
+ DIALOG_CUJ,
+ logger,
+ TAG,
+ )
),
- logger,
- TAG,
- ),
)
}
}
@@ -154,6 +163,8 @@ constructor(
companion object {
@DrawableRes val ICON = R.drawable.ic_screenrecord
+ private val DIALOG_CUJ =
+ DialogCuj(Cuj.CUJ_STATUS_BAR_LAUNCH_DIALOG_FROM_CHIP, tag = "Screen record")
private val TAG = "ScreenRecordVM".pad()
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModel.kt
index 2af86a51cf70..6654d4a8f104 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModel.kt
@@ -39,6 +39,7 @@ import com.android.systemui.statusbar.chips.ui.model.ColorsModel
import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
import com.android.systemui.statusbar.chips.ui.viewmodel.ChipTransitionHelper
import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipViewModel
+import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipViewModel.Companion.createDialogLaunchOnClickCallback
import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipViewModel.Companion.createDialogLaunchOnClickListener
import com.android.systemui.util.time.SystemClock
import javax.inject.Inject
@@ -128,13 +129,25 @@ constructor(
colors = ColorsModel.Red,
// TODO(b/332662551): Maybe use a MediaProjection API to fetch this time.
startTimeMs = systemClock.elapsedRealtime(),
- createDialogLaunchOnClickListener(
- createShareScreenToAppDialogDelegate(state),
- dialogTransitionAnimator,
- DialogCuj(Cuj.CUJ_STATUS_BAR_LAUNCH_DIALOG_FROM_CHIP, tag = "Share to app"),
- logger,
- TAG,
- ),
+ onClickListenerLegacy =
+ createDialogLaunchOnClickListener(
+ createShareScreenToAppDialogDelegate(state),
+ dialogTransitionAnimator,
+ DIALOG_CUJ,
+ logger,
+ TAG,
+ ),
+ clickBehavior =
+ OngoingActivityChipModel.ClickBehavior.ExpandAction(
+ onClick =
+ createDialogLaunchOnClickCallback(
+ createShareScreenToAppDialogDelegate(state),
+ dialogTransitionAnimator,
+ DIALOG_CUJ,
+ logger,
+ TAG,
+ )
+ ),
)
}
@@ -150,16 +163,24 @@ constructor(
)
),
colors = ColorsModel.Red,
- createDialogLaunchOnClickListener(
- createGenericShareToAppDialogDelegate(),
- dialogTransitionAnimator,
- DialogCuj(
- Cuj.CUJ_STATUS_BAR_LAUNCH_DIALOG_FROM_CHIP,
- tag = "Share to app audio only",
+ onClickListenerLegacy =
+ createDialogLaunchOnClickListener(
+ createGenericShareToAppDialogDelegate(),
+ dialogTransitionAnimator,
+ DIALOG_CUJ_AUDIO_ONLY,
+ logger,
+ TAG,
+ ),
+ clickBehavior =
+ OngoingActivityChipModel.ClickBehavior.ExpandAction(
+ createDialogLaunchOnClickCallback(
+ createGenericShareToAppDialogDelegate(),
+ dialogTransitionAnimator,
+ DIALOG_CUJ_AUDIO_ONLY,
+ logger,
+ TAG,
+ )
),
- logger,
- TAG,
- ),
)
}
@@ -180,6 +201,10 @@ constructor(
companion object {
@DrawableRes val SHARE_TO_APP_ICON = R.drawable.ic_present_to_all
+ private val DIALOG_CUJ =
+ DialogCuj(Cuj.CUJ_STATUS_BAR_LAUNCH_DIALOG_FROM_CHIP, tag = "Share to app")
+ private val DIALOG_CUJ_AUDIO_ONLY =
+ DialogCuj(Cuj.CUJ_STATUS_BAR_LAUNCH_DIALOG_FROM_CHIP, tag = "Share to app audio only")
private val TAG = "ShareToAppVM".pad()
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/binder/OngoingActivityChipBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/binder/OngoingActivityChipBinder.kt
index b0fa9d842480..d46638fac46c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/binder/OngoingActivityChipBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/binder/OngoingActivityChipBinder.kt
@@ -56,7 +56,8 @@ object OngoingActivityChipBinder {
// Data
setChipIcon(chipModel, chipBackgroundView, chipDefaultIconView, iconViewStore)
setChipMainContent(chipModel, chipTextView, chipTimeView, chipShortTimeDeltaView)
- viewBinding.rootView.setOnClickListener(chipModel.onClickListener)
+
+ viewBinding.rootView.setOnClickListener(chipModel.onClickListenerLegacy)
updateChipPadding(
chipModel,
chipBackgroundView,
@@ -424,7 +425,7 @@ object OngoingActivityChipBinder {
// Clickable chips need to be a minimum size for accessibility purposes, but let
// non-clickable chips be smaller.
val minimumWidth =
- if (chipModel.onClickListener != null) {
+ if (chipModel.onClickListenerLegacy != null) {
chipBackgroundView.context.resources.getDimensionPixelSize(
R.dimen.min_clickable_item_size
)
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 1be5842bceeb..6ce3228531d2 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
@@ -35,10 +35,13 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.dimensionResource
import androidx.compose.ui.unit.dp
import androidx.compose.ui.viewinterop.AndroidView
+import com.android.compose.animation.Expandable
+import com.android.systemui.animation.Expandable
import com.android.systemui.common.ui.compose.Icon
import com.android.systemui.res.R
import com.android.systemui.statusbar.chips.ui.compose.modifiers.neverDecreaseWidth
@@ -47,23 +50,42 @@ import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
@Composable
fun OngoingActivityChip(model: OngoingActivityChipModel.Shown, 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.
+ ExpandableChip(
+ color = { Color.Transparent },
+ shape =
+ RoundedCornerShape(
+ dimensionResource(id = R.dimen.ongoing_activity_chip_corner_radius)
+ ),
+ modifier = modifier,
+ ) { expandable ->
+ ChipBody(model, onClick = { clickBehavior.onClick(expandable) })
+ }
+ }
+
+ is OngoingActivityChipModel.ClickBehavior.None -> {
+ ChipBody(model, modifier = modifier)
+ }
+ }
+}
+
+@Composable
+private fun ChipBody(
+ model: OngoingActivityChipModel.Shown,
+ modifier: Modifier = Modifier,
+ onClick: () -> Unit = {},
+) {
val context = LocalContext.current
- val isClickable = model.onClickListener != null
+ val isClickable = onClick != {}
val hasEmbeddedIcon = model.icon is OngoingActivityChipModel.ChipIcon.StatusBarView
// Use a Box with `fillMaxHeight` to create a larger click surface for the chip. The visible
// height of the chip is determined by the height of the background of the Row below.
Box(
contentAlignment = Alignment.Center,
- modifier =
- modifier
- .fillMaxHeight()
- .clickable(
- enabled = isClickable,
- onClick = {
- // TODO(b/372657935): Implement click actions.
- },
- ),
+ modifier = modifier.fillMaxHeight().clickable(enabled = isClickable, onClick = onClick),
) {
Row(
horizontalArrangement = Arrangement.Center,
@@ -206,3 +228,13 @@ private fun ChipContent(viewModel: OngoingActivityChipModel.Shown, modifier: Mod
}
}
}
+
+@Composable
+private fun ExpandableChip(
+ color: () -> Color,
+ shape: Shape,
+ modifier: Modifier = Modifier,
+ content: @Composable (Expandable) -> Unit,
+) {
+ Expandable(color = color(), shape = shape, modifier = modifier.clip(shape)) { content(it) }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/OngoingActivityChipModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/OngoingActivityChipModel.kt
index 956d99e46766..68c8f8cb4254 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/OngoingActivityChipModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/OngoingActivityChipModel.kt
@@ -17,6 +17,7 @@
package com.android.systemui.statusbar.chips.ui.model
import android.view.View
+import com.android.systemui.animation.Expandable
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.statusbar.StatusBarIconView
import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips
@@ -46,17 +47,20 @@ sealed class OngoingActivityChipModel {
open val colors: ColorsModel,
/**
* Listener method to invoke when this chip is clicked. If null, the chip won't be
- * clickable.
+ * clickable. Will be deprecated after [StatusBarChipsModernization] is enabled.
*/
- open val onClickListener: View.OnClickListener?,
+ open val onClickListenerLegacy: View.OnClickListener?,
+ /** Data class that determines how clicks on the chip should be handled. */
+ open val clickBehavior: ClickBehavior,
) : OngoingActivityChipModel() {
/** This chip shows only an icon and nothing else. */
data class IconOnly(
override val icon: ChipIcon,
override val colors: ColorsModel,
- override val onClickListener: View.OnClickListener?,
- ) : Shown(icon, colors, onClickListener) {
+ override val onClickListenerLegacy: View.OnClickListener?,
+ override val clickBehavior: ClickBehavior,
+ ) : Shown(icon, colors, onClickListenerLegacy, clickBehavior) {
override val logName = "Shown.Icon"
}
@@ -74,8 +78,9 @@ sealed class OngoingActivityChipModel {
* [android.widget.Chronometer.setBase].
*/
val startTimeMs: Long,
- override val onClickListener: View.OnClickListener?,
- ) : Shown(icon, colors, onClickListener) {
+ override val onClickListenerLegacy: View.OnClickListener?,
+ override val clickBehavior: ClickBehavior,
+ ) : Shown(icon, colors, onClickListenerLegacy, clickBehavior) {
override val logName = "Shown.Timer"
}
@@ -88,8 +93,9 @@ sealed class OngoingActivityChipModel {
override val colors: ColorsModel,
/** The time of the event that this chip represents. */
val time: Long,
- override val onClickListener: View.OnClickListener?,
- ) : Shown(icon, colors, onClickListener) {
+ override val onClickListenerLegacy: View.OnClickListener?,
+ override val clickBehavior: ClickBehavior,
+ ) : Shown(icon, colors, onClickListenerLegacy, clickBehavior) {
init {
StatusBarNotifChips.assertInNewMode()
}
@@ -105,7 +111,13 @@ sealed class OngoingActivityChipModel {
override val colors: ColorsModel,
/** The number of seconds until an event is started. */
val secondsUntilStarted: Long,
- ) : Shown(icon = null, colors, onClickListener = null) {
+ ) :
+ Shown(
+ icon = null,
+ colors,
+ onClickListenerLegacy = null,
+ clickBehavior = ClickBehavior.None,
+ ) {
override val logName = "Shown.Countdown"
}
@@ -115,8 +127,9 @@ sealed class OngoingActivityChipModel {
override val colors: ColorsModel,
// TODO(b/361346412): Enforce a max length requirement?
val text: String,
- override val onClickListener: View.OnClickListener? = null,
- ) : Shown(icon, colors, onClickListener) {
+ override val onClickListenerLegacy: View.OnClickListener? = null,
+ override val clickBehavior: ClickBehavior,
+ ) : Shown(icon, colors, onClickListenerLegacy, clickBehavior) {
override val logName = "Shown.Text"
}
}
@@ -149,4 +162,13 @@ sealed class OngoingActivityChipModel {
*/
data class SingleColorIcon(val impl: Icon) : ChipIcon
}
+
+ /** Defines the behavior of the chip when it is clicked. */
+ sealed interface ClickBehavior {
+ /** No specific click behavior. */
+ data object None : ClickBehavior
+
+ /** The chip expands into a dialog or activity on click. */
+ data class ExpandAction(val onClick: (Expandable) -> Unit) : ClickBehavior
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipViewModel.kt
index 2fc366b7f078..a978c04d2a2e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipViewModel.kt
@@ -19,6 +19,7 @@ package com.android.systemui.statusbar.chips.ui.viewmodel
import android.view.View
import com.android.systemui.animation.DialogCuj
import com.android.systemui.animation.DialogTransitionAnimator
+import com.android.systemui.animation.Expandable
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.core.LogLevel
import com.android.systemui.res.R
@@ -26,6 +27,7 @@ import com.android.systemui.statusbar.chips.StatusBarChipsLog
import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
import com.android.systemui.statusbar.chips.ui.view.ChipBackgroundContainer
import com.android.systemui.statusbar.phone.SystemUIDialog
+import com.android.systemui.statusbar.phone.ongoingcall.StatusBarChipsModernization
import kotlinx.coroutines.flow.StateFlow
/**
@@ -46,6 +48,7 @@ interface OngoingActivityChipViewModel {
tag: String,
): View.OnClickListener {
return View.OnClickListener { view ->
+ StatusBarChipsModernization.assertInLegacyMode()
logger.log(tag, LogLevel.INFO, {}, { "Chip clicked" })
val dialog = dialogDelegate.createDialog()
val launchableView =
@@ -55,5 +58,28 @@ interface OngoingActivityChipViewModel {
dialogTransitionAnimator.showFromView(dialog, launchableView, cuj)
}
}
+
+ /**
+ * Creates a chip click callback with an [Expandable] parameter that launches a dialog
+ * created by [dialogDelegate].
+ */
+ fun createDialogLaunchOnClickCallback(
+ dialogDelegate: SystemUIDialog.Delegate,
+ dialogTransitionAnimator: DialogTransitionAnimator,
+ cuj: DialogCuj,
+ @StatusBarChipsLog logger: LogBuffer,
+ tag: String,
+ ): (Expandable) -> Unit {
+ return { expandable ->
+ StatusBarChipsModernization.assertInNewMode()
+ logger.log(tag, LogLevel.INFO, {}, { "Chip clicked" })
+ val dialog = dialogDelegate.createDialog()
+
+ val controller = expandable.dialogTransitionController(cuj)
+ if (controller != null) {
+ dialogTransitionAnimator.show(dialog, controller)
+ }
+ }
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
index cf9ee619b734..826329d5da1e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
@@ -39,7 +39,6 @@ import static android.service.notification.NotificationListenerService.REASON_TI
import static android.service.notification.NotificationListenerService.REASON_UNAUTOBUNDLED;
import static android.service.notification.NotificationListenerService.REASON_USER_STOPPED;
-import static com.android.systemui.Flags.notificationsDismissPrunedSummaries;
import static com.android.systemui.statusbar.notification.NotificationUtils.logKey;
import static com.android.systemui.statusbar.notification.collection.NotificationEntry.DismissState.DISMISSED;
import static com.android.systemui.statusbar.notification.collection.NotificationEntry.DismissState.NOT_DISMISSED;
@@ -278,9 +277,7 @@ public class NotifCollection implements Dumpable, PipelineDumpable {
Assert.isMainThread();
checkForReentrantCall();
- if (notificationsDismissPrunedSummaries()) {
- entriesToDismiss = includeSummariesToDismiss(entriesToDismiss);
- }
+ entriesToDismiss = includeSummariesToDismiss(entriesToDismiss);
final int entryCount = entriesToDismiss.size();
final List<NotificationEntry> entriesToLocallyDismiss = new ArrayList<>();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java
index c88dd7af6b24..d401283aa84e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java
@@ -154,10 +154,12 @@ public class FooterView extends StackScrollerDecorView {
DumpUtilsKt.withIncreasedIndent(pw, () -> {
// TODO: b/375010573 - update dumps for redesign
pw.println("visibility: " + DumpUtilsKt.visibilityString(getVisibility()));
- pw.println("manageButton visibility: "
- + DumpUtilsKt.visibilityString(mClearAllButton.getVisibility()));
- pw.println("dismissButton visibility: "
- + DumpUtilsKt.visibilityString(mClearAllButton.getVisibility()));
+ if (mManageOrHistoryButton != null)
+ pw.println("mManageOrHistoryButton visibility: "
+ + DumpUtilsKt.visibilityString(mManageOrHistoryButton.getVisibility()));
+ if (mClearAllButton != null)
+ pw.println("mClearAllButton visibility: "
+ + DumpUtilsKt.visibilityString(mClearAllButton.getVisibility()));
});
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/dagger/NotificationsLogModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/dagger/NotificationsLogModule.kt
index d3359d39e959..6bcce3e21998 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/dagger/NotificationsLogModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/dagger/NotificationsLogModule.kt
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.notification.logging.dagger
+import com.android.app.tracing.TrackGroupUtils.trackGroup
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.LogBufferFactory
@@ -44,7 +45,11 @@ object NotificationsLogModule {
@SysUISingleton
@NotificationHeadsUpLog
fun provideNotificationHeadsUpLogBuffer(factory: LogBufferFactory): LogBuffer {
- return factory.create("NotifHeadsUpLog", 1000)
+ return factory.create(
+ "NotifHeadsUpLog",
+ 1000,
+ systraceTrackName = notifPipelineTrack("NotifHeadsUpLog"),
+ )
}
/** Provides a logging buffer for logs related to inflation of notifications. */
@@ -52,7 +57,11 @@ object NotificationsLogModule {
@SysUISingleton
@NotifInflationLog
fun provideNotifInflationLogBuffer(factory: LogBufferFactory): LogBuffer {
- return factory.create("NotifInflationLog", 250)
+ return factory.create(
+ "NotifInflationLog",
+ 250,
+ systraceTrackName = notifPipelineTrack("NotifInflationLog"),
+ )
}
/** Provides a logging buffer for all logs related to the data layer of notifications. */
@@ -60,7 +69,11 @@ object NotificationsLogModule {
@SysUISingleton
@NotifInteractionLog
fun provideNotifInteractionLogBuffer(factory: LogBufferFactory): LogBuffer {
- return factory.create("NotifInteractionLog", 50)
+ return factory.create(
+ "NotifInteractionLog",
+ 50,
+ systraceTrackName = notifPipelineTrack("NotifInteractionLog"),
+ )
}
/** Provides a logging buffer for notification interruption calculations. */
@@ -68,7 +81,11 @@ object NotificationsLogModule {
@SysUISingleton
@NotificationInterruptLog
fun provideNotificationInterruptLogBuffer(factory: LogBufferFactory): LogBuffer {
- return factory.create("NotifInterruptLog", 100)
+ return factory.create(
+ "NotifInterruptLog",
+ 100,
+ systraceTrackName = notifPipelineTrack("NotifInterruptLog"),
+ )
}
/** Provides a logging buffer for all logs related to notifications on the lockscreen. */
@@ -91,7 +108,12 @@ object NotificationsLogModule {
if (Compile.IS_DEBUG && notifPipelineFlags.isDevLoggingEnabled()) {
maxSize *= 10
}
- return factory.create("NotifLog", maxSize, Compile.IS_DEBUG /* systrace */)
+ return factory.create(
+ "NotifLog",
+ maxSize,
+ /* systrace= */ Compile.IS_DEBUG,
+ systraceTrackName = notifPipelineTrack("NotifLog"),
+ )
}
/** Provides a logging buffer for all logs related to remote input controller. */
@@ -107,7 +129,11 @@ object NotificationsLogModule {
@SysUISingleton
@NotificationRenderLog
fun provideNotificationRenderLogBuffer(factory: LogBufferFactory): LogBuffer {
- return factory.create("NotifRenderLog", 100)
+ return factory.create(
+ "NotifRenderLog",
+ 100,
+ systraceTrackName = notifPipelineTrack("NotifRenderLog"),
+ )
}
/** Provides a logging buffer for all logs related to managing notification sections. */
@@ -150,3 +176,13 @@ object NotificationsLogModule {
return factory.create("VisualStabilityLog", 50, /* maxSize */ false /* systrace */)
}
}
+
+private const val NOTIF_PIPELINE_TRACK_GROUP_NAME = "Notification pipeline"
+
+/**
+ * This generates a track name that is hierarcically collapsed inside
+ * [NOTIF_PIPELINE_TRACK_GROUP_NAME] in perfetto traces.
+ */
+private fun notifPipelineTrack(trackName: String): String {
+ return trackGroup(NOTIF_PIPELINE_TRACK_GROUP_NAME, trackName)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationUiAod.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationUiAod.kt
new file mode 100644
index 000000000000..fa1f32cc367e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationUiAod.kt
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.promoted
+
+import com.android.systemui.Flags
+import com.android.systemui.flags.FlagToken
+import com.android.systemui.flags.RefactorFlagUtils
+
+/** Helper for reading or using the promoted ongoing notifications AOD flag state. */
+object PromotedNotificationUiAod {
+ /** The aconfig flag name */
+ const val FLAG_NAME = Flags.FLAG_AOD_UI_RICH_ONGOING
+
+ /** A token used for dependency declaration */
+ val token: FlagToken
+ get() = FlagToken(FLAG_NAME, isEnabled)
+
+ /** Is the refactor enabled */
+ @JvmStatic
+ inline val isEnabled
+ get() = Flags.aodUiRichOngoing()
+
+ /**
+ * Called to ensure code is only run when the flag is enabled. This protects users from the
+ * unintended behaviors caused by accidentally running new logic, while also crashing on an eng
+ * build to ensure that the refactor author catches issues in testing.
+ */
+ @JvmStatic
+ inline fun isUnexpectedlyInLegacyMode() =
+ RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, FLAG_NAME)
+
+ /**
+ * Called to ensure code is only run when the flag is disabled. This will throw an exception if
+ * the flag is not enabled to ensure that the refactor author catches issues in testing.
+ * Caution!! Using this check incorrectly will cause crashes in nextfood builds!
+ */
+ @JvmStatic
+ inline fun assertInNewMode() = RefactorFlagUtils.assertInNewMode(isEnabled, FLAG_NAME)
+
+ /**
+ * Called to ensure code is only run when the flag is disabled. This will throw an exception if
+ * the flag is enabled to ensure that the refactor author catches issues in testing.
+ */
+ @JvmStatic
+ inline fun assertInLegacyMode() = RefactorFlagUtils.assertInLegacyMode(isEnabled, FLAG_NAME)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationUiForceExpanded.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationUiForceExpanded.kt
new file mode 100644
index 000000000000..cb0d67403521
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationUiForceExpanded.kt
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.promoted
+
+import com.android.systemui.Flags
+import com.android.systemui.flags.FlagToken
+import com.android.systemui.flags.RefactorFlagUtils
+
+/** Helper for reading or using the expanded ui rich ongoing flag state. */
+@Suppress("NOTHING_TO_INLINE")
+object PromotedNotificationUiForceExpanded {
+ /** The aconfig flag name */
+ const val FLAG_NAME = Flags.FLAG_UI_RICH_ONGOING_FORCE_EXPANDED
+
+ /** A token used for dependency declaration */
+ val token: FlagToken
+ get() = FlagToken(FLAG_NAME, isEnabled)
+
+ /** Is the refactor enabled */
+ @JvmStatic
+ inline val isEnabled
+ get() = Flags.uiRichOngoingForceExpanded()
+
+ /**
+ * Called to ensure code is only run when the flag is enabled. This protects users from the
+ * unintended behaviors caused by accidentally running new logic, while also crashing on an eng
+ * build to ensure that the refactor author catches issues in testing.
+ */
+ @JvmStatic
+ inline fun isUnexpectedlyInLegacyMode() =
+ RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, FLAG_NAME)
+
+ /**
+ * Called to ensure code is only run when the flag is enabled. This will throw an exception if
+ * the flag is not enabled to ensure that the refactor author catches issues in testing.
+ * Caution!! Using this check incorrectly will cause crashes in nextfood builds!
+ */
+ @JvmStatic
+ inline fun assertInNewMode() = RefactorFlagUtils.assertInNewMode(isEnabled, FLAG_NAME)
+
+ /**
+ * Called to ensure code is only run when the flag is disabled. This will throw an exception if
+ * the flag is enabled to ensure that the refactor author catches issues in testing.
+ */
+ @JvmStatic
+ inline fun assertInLegacyMode() = RefactorFlagUtils.assertInLegacyMode(isEnabled, FLAG_NAME)
+}
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 495b50869458..f57107141f61 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
@@ -127,7 +127,6 @@ import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrim
import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrollState;
import com.android.systemui.statusbar.notification.stack.ui.view.NotificationScrollView;
import com.android.systemui.statusbar.phone.HeadsUpAppearanceController;
-import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
import com.android.systemui.statusbar.policy.ScrollAdapter;
import com.android.systemui.statusbar.policy.SplitShadeStateController;
import com.android.systemui.util.Assert;
@@ -282,11 +281,9 @@ public class NotificationStackScrollLayout
private boolean mExpandedInThisMotion;
private boolean mShouldShowShelfOnly;
protected boolean mScrollingEnabled;
- private boolean mIsCurrentUserSetup;
protected FooterView mFooterView;
protected EmptyShadeView mEmptyShadeView;
private boolean mClearAllInProgress;
- private FooterClearAllListener mFooterClearAllListener;
private boolean mFlingAfterUpEvent;
/**
* Was the scroller scrolled to the top when the down motion was observed?
@@ -467,7 +464,6 @@ public class NotificationStackScrollLayout
boolean mHeadsUpAnimatingAway;
private Consumer<Boolean> mHeadsUpAnimatingAwayListener;
private int mStatusBarState;
- private int mUpcomingStatusBarState;
private boolean mHeadsUpGoingAwayAnimationsAllowed = true;
private final Runnable mReflingAndAnimateScroll = this::animateScroll;
private int mCornerRadius;
@@ -498,7 +494,6 @@ public class NotificationStackScrollLayout
private float mLastSentExpandedHeight;
private boolean mWillExpand;
private int mGapHeight;
- private boolean mIsRemoteInputActive;
/**
* The extra inset during the full shade transition
@@ -572,10 +567,8 @@ public class NotificationStackScrollLayout
private boolean mDismissUsingRowTranslationX = true;
private ExpandableNotificationRow mTopHeadsUpRow;
private NotificationStackScrollLayoutController.TouchHandler mTouchHandler;
- private final ScreenOffAnimationController mScreenOffAnimationController;
private boolean mShouldUseSplitNotificationShade;
private boolean mShouldSkipTopPaddingAnimationAfterFold = false;
- private boolean mHasFilteredOutSeenNotifications;
@Nullable private SplitShadeStateController mSplitShadeStateController = null;
private boolean mIsSmallLandscapeLockscreenEnabled = false;
private boolean mSuppressHeightUpdates;
@@ -636,9 +629,6 @@ public class NotificationStackScrollLayout
};
@Nullable
- private OnClickListener mManageButtonClickListener;
-
- @Nullable
private WallpaperInteractor mWallpaperInteractor;
public NotificationStackScrollLayout(Context context, AttributeSet attrs) {
@@ -650,8 +640,6 @@ public class NotificationStackScrollLayout
mDebugLines = mFeatureFlags.isEnabled(Flags.NSSL_DEBUG_LINES);
mDebugRemoveAnimation = mFeatureFlags.isEnabled(Flags.NSSL_DEBUG_REMOVE_ANIMATION);
mSectionsManager = Dependency.get(NotificationSectionsManager.class);
- mScreenOffAnimationController =
- Dependency.get(ScreenOffAnimationController.class);
mSectionsManager.initialize(this);
mSections = mSectionsManager.createSectionsForBuckets();
@@ -5403,7 +5391,6 @@ public class NotificationStackScrollLayout
println(pw, "suppressChildrenMeasureLayout", mSuppressChildrenMeasureAndLayout);
println(pw, "scrollY", mAmbientState.getScrollY());
println(pw, "showShelfOnly", mShouldShowShelfOnly);
- println(pw, "isCurrentUserSetup", mIsCurrentUserSetup);
println(pw, "hideAmount", mAmbientState.getHideAmount());
println(pw, "ambientStateSwipingUp", mAmbientState.isSwipingUp());
println(pw, "maxDisplayedNotifications", mMaxDisplayedNotifications);
@@ -6793,10 +6780,6 @@ public class NotificationStackScrollLayout
void onClearAll(@SelectedRows int selectedRows);
}
- interface FooterClearAllListener {
- void onClearAll();
- }
-
interface ClearAllAnimationListener {
void onAnimationEnd(
List<ExpandableNotificationRow> viewsToRemove, @SelectedRows int selectedRows);
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 f0455fc3a22b..c1d022600559 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
@@ -49,9 +49,11 @@ import com.android.systemui.keyguard.ui.viewmodel.AodBurnInViewModel
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.DozingToGlanceableHubTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.DozingToLockscreenTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.DozingToOccludedTransitionViewModel
+import com.android.systemui.keyguard.ui.viewmodel.DozingToPrimaryBouncerTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.GlanceableHubToLockscreenTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.GoneToAodTransitionViewModel
@@ -131,9 +133,12 @@ constructor(
private val aodToGoneTransitionViewModel: AodToGoneTransitionViewModel,
private val aodToLockscreenTransitionViewModel: AodToLockscreenTransitionViewModel,
private val aodToOccludedTransitionViewModel: AodToOccludedTransitionViewModel,
+ private val aodToPrimaryBouncerTransitionViewModel: AodToPrimaryBouncerTransitionViewModel,
dozingToGlanceableHubTransitionViewModel: DozingToGlanceableHubTransitionViewModel,
private val dozingToLockscreenTransitionViewModel: DozingToLockscreenTransitionViewModel,
private val dozingToOccludedTransitionViewModel: DozingToOccludedTransitionViewModel,
+ private val dozingToPrimaryBouncerTransitionViewModel:
+ DozingToPrimaryBouncerTransitionViewModel,
private val dreamingToLockscreenTransitionViewModel: DreamingToLockscreenTransitionViewModel,
private val glanceableHubToLockscreenTransitionViewModel:
GlanceableHubToLockscreenTransitionViewModel,
@@ -554,8 +559,10 @@ constructor(
aodToGoneTransitionViewModel.notificationAlpha(viewState),
aodToLockscreenTransitionViewModel.notificationAlpha,
aodToOccludedTransitionViewModel.lockscreenAlpha(viewState),
+ aodToPrimaryBouncerTransitionViewModel.notificationAlpha,
dozingToLockscreenTransitionViewModel.lockscreenAlpha,
dozingToOccludedTransitionViewModel.lockscreenAlpha(viewState),
+ dozingToPrimaryBouncerTransitionViewModel.notificationAlpha,
dreamingToLockscreenTransitionViewModel.lockscreenAlpha,
goneToAodTransitionViewModel.notificationAlpha,
goneToDreamingTransitionViewModel.lockscreenAlpha,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt
index 4751293a16cc..5a63c0cd84e6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt
@@ -312,6 +312,25 @@ constructor(
}
}
+ override fun postStartActivityDismissingKeyguard(
+ intent: Intent,
+ delay: Int,
+ animationController: ActivityTransitionAnimator.Controller?,
+ customMessage: String?,
+ userHandle: UserHandle?,
+ ) {
+ postOnUiThread(delay) {
+ activityStarterInternal.startActivityDismissingKeyguard(
+ intent = intent,
+ onlyProvisioned = true,
+ dismissShade = true,
+ animationController = animationController,
+ customMessage = customMessage,
+ userHandle = userHandle,
+ )
+ }
+ }
+
override fun dismissKeyguardThenExecute(
action: OnDismissAction,
cancel: Runnable?,
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 d43fed0cbf59..2cd8eafcdb54 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -31,7 +31,6 @@ import android.animation.ValueAnimator;
import android.annotation.IntDef;
import android.graphics.Color;
import android.os.Handler;
-import android.os.Trace;
import android.util.Log;
import android.util.MathUtils;
import android.util.Pair;
@@ -53,6 +52,7 @@ import com.android.keyguard.BouncerPanelExpansionCalculator;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.KeyguardUpdateMonitorCallback;
import com.android.systemui.Dumpable;
+import com.android.systemui.Flags;
import com.android.systemui.animation.ShadeInterpolation;
import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants;
import com.android.systemui.dagger.SysUISingleton;
@@ -82,6 +82,9 @@ import com.android.systemui.util.kotlin.JavaAdapter;
import com.android.systemui.util.wakelock.DelayedWakeLock;
import com.android.systemui.util.wakelock.WakeLock;
+import kotlinx.coroutines.CoroutineDispatcher;
+import kotlinx.coroutines.ExperimentalCoroutinesApi;
+
import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -90,9 +93,6 @@ import java.util.function.Consumer;
import javax.inject.Inject;
-import kotlinx.coroutines.CoroutineDispatcher;
-import kotlinx.coroutines.ExperimentalCoroutinesApi;
-
/**
* Controls both the scrim behind the notifications and in front of the notifications (when a
* security method gets shown).
@@ -226,6 +226,8 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump
private float mAdditionalScrimBehindAlphaKeyguard = 0f;
// Combined scrim behind keyguard alpha of default scrim + additional scrim
private float mScrimBehindAlphaKeyguard = KEYGUARD_SCRIM_ALPHA;
+
+ static final float TRANSPARENT_BOUNCER_SCRIM_ALPHA = 0.54f;
private final float mDefaultScrimAlpha;
private float mRawPanelExpansionFraction;
@@ -340,7 +342,10 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump
LargeScreenShadeInterpolator largeScreenShadeInterpolator) {
mScrimStateListener = lightBarController::setScrimState;
mLargeScreenShadeInterpolator = largeScreenShadeInterpolator;
- mDefaultScrimAlpha = BUSY_SCRIM_ALPHA;
+ // All scrims default alpha need to match bouncer background alpha to make sure the
+ // transitions involving the bouncer are smooth and don't overshoot the bouncer alpha.
+ mDefaultScrimAlpha =
+ Flags.bouncerUiRevamp() ? TRANSPARENT_BOUNCER_SCRIM_ALPHA : BUSY_SCRIM_ALPHA;
mKeyguardStateController = keyguardStateController;
mDarkenWhileDragging = !mKeyguardStateController.canDismissLockScreen();
@@ -974,8 +979,6 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump
mBehindTint,
interpolatedFraction);
}
- } else if (mState == ScrimState.AUTH_SCRIMMED_SHADE) {
- mNotificationsAlpha = (float) Math.pow(getInterpolatedFraction(), 0.8f);
} else if (mState == ScrimState.KEYGUARD || mState == ScrimState.SHADE_LOCKED
|| mState == ScrimState.PULSING || mState == ScrimState.GLANCEABLE_HUB) {
Pair<Integer, Float> result = calculateBackStateForState(mState);
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 8dcb66312558..14937295051d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
@@ -16,17 +16,24 @@
package com.android.systemui.statusbar.phone;
+import static com.android.systemui.statusbar.phone.ScrimController.BUSY_SCRIM_ALPHA;
+import static com.android.systemui.statusbar.phone.ScrimController.TRANSPARENT_BOUNCER_SCRIM_ALPHA;
+
import android.graphics.Color;
import com.android.app.tracing.coroutines.TrackTracer;
+import com.android.systemui.Flags;
import com.android.systemui.dock.DockManager;
import com.android.systemui.res.R;
import com.android.systemui.scrim.ScrimView;
import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
+import kotlinx.coroutines.ExperimentalCoroutinesApi;
+
/**
* Possible states of the ScrimController state machine.
*/
+@ExperimentalCoroutinesApi
public enum ScrimState {
/**
@@ -92,40 +99,19 @@ public enum ScrimState {
}
},
- AUTH_SCRIMMED_SHADE {
- @Override
- public void prepare(ScrimState previousState) {
- // notif scrim alpha values are determined by ScrimController#applyState
- // based on the shade expansion
-
- mFrontTint = mBackgroundColor;
- mFrontAlpha = .66f;
-
- mBehindTint = mBackgroundColor;
- mBehindAlpha = 1f;
- }
- },
-
- AUTH_SCRIMMED {
- @Override
- public void prepare(ScrimState previousState) {
- mNotifTint = previousState.mNotifTint;
- mNotifAlpha = previousState.mNotifAlpha;
-
- mBehindTint = previousState.mBehindTint;
- mBehindAlpha = previousState.mBehindAlpha;
-
- mFrontTint = mBackgroundColor;
- mFrontAlpha = .66f;
- }
- },
-
/**
* Showing password challenge on the keyguard.
*/
BOUNCER {
@Override
public void prepare(ScrimState previousState) {
+ if (Flags.bouncerUiRevamp()) {
+ mBehindAlpha = mClipQsScrim ? 0.0f : TRANSPARENT_BOUNCER_SCRIM_ALPHA;
+ mNotifAlpha = mClipQsScrim ? TRANSPARENT_BOUNCER_SCRIM_ALPHA : 0;
+ mBehindTint = mNotifTint = mSurfaceColor;
+ mFrontAlpha = 0f;
+ return;
+ }
mBehindAlpha = mClipQsScrim ? 1 : mDefaultScrimAlpha;
mBehindTint = mClipQsScrim ? mBackgroundColor : mSurfaceColor;
mNotifAlpha = mClipQsScrim ? mDefaultScrimAlpha : 0;
@@ -136,6 +122,10 @@ public enum ScrimState {
@Override
public void setSurfaceColor(int surfaceColor) {
super.setSurfaceColor(surfaceColor);
+ if (Flags.bouncerUiRevamp()) {
+ mBehindTint = mNotifTint = mSurfaceColor;
+ return;
+ }
if (!mClipQsScrim) {
mBehindTint = mSurfaceColor;
}
@@ -146,15 +136,38 @@ public enum ScrimState {
* Showing password challenge on top of a FLAG_SHOW_WHEN_LOCKED activity.
*/
BOUNCER_SCRIMMED {
+ @ExperimentalCoroutinesApi
@Override
public void prepare(ScrimState previousState) {
+ if (Flags.bouncerUiRevamp()) {
+ mBehindAlpha = 0f;
+ mFrontAlpha = TRANSPARENT_BOUNCER_SCRIM_ALPHA;
+ mFrontTint = mSurfaceColor;
+ return;
+ }
mBehindAlpha = 0;
mFrontAlpha = mDefaultScrimAlpha;
}
+
+ @Override
+ public boolean shouldBlendWithMainColor() {
+ return !Flags.bouncerUiRevamp();
+ }
},
SHADE_LOCKED {
@Override
+ public void setDefaultScrimAlpha(float defaultScrimAlpha) {
+ super.setDefaultScrimAlpha(defaultScrimAlpha);
+ if (!Flags.notificationShadeBlur()) {
+ // Temporary change that prevents the shade from being semi-transparent when
+ // bouncer blur is enabled but notification shade blur is not enabled. This is
+ // required to perf test these two flags independently.
+ mDefaultScrimAlpha = BUSY_SCRIM_ALPHA;
+ }
+ }
+
+ @Override
public void prepare(ScrimState previousState) {
mBehindAlpha = mClipQsScrim ? 1 : mDefaultScrimAlpha;
mNotifAlpha = 1f;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/StatusBarChipsModernization.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/StatusBarChipsModernization.kt
index 2ab2b68bbb2e..9f8b45578903 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/StatusBarChipsModernization.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/StatusBarChipsModernization.kt
@@ -1,18 +1,18 @@
/*
-* 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.
-*/
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
package com.android.systemui.statusbar.phone.ongoingcall
import com.android.systemui.Flags
@@ -44,9 +44,16 @@ object StatusBarChipsModernization {
RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, FLAG_NAME)
/**
+ * Called to ensure code is only run when the flag is enabled. This will throw an exception if
+ * the flag is not enabled to ensure that the refactor author catches issues in testing.
+ */
+ @JvmStatic
+ inline fun assertInNewMode() = RefactorFlagUtils.assertInNewMode(isEnabled, FLAG_NAME)
+
+ /**
* Called to ensure code is only run when the flag is disabled. This will throw an exception if
* the flag is enabled to ensure that the refactor author catches issues in testing.
*/
@JvmStatic
inline fun assertInLegacyMode() = RefactorFlagUtils.assertInLegacyMode(isEnabled, FLAG_NAME)
-} \ No newline at end of file
+}
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 8daa8037c367..7e76d77abe61 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
@@ -41,8 +41,8 @@ import com.android.systemui.statusbar.notification.icon.ui.viewbinder.ConnectedD
import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor
import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragment
import com.android.systemui.statusbar.phone.ongoingcall.StatusBarChipsModernization
+import com.android.systemui.statusbar.pipeline.shared.ui.model.VisibilityModel
import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.HomeStatusBarViewModel
-import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.HomeStatusBarViewModel.VisibilityModel
import javax.inject.Inject
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
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 b1cc208e9b43..9c1171fd1ebc 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
@@ -57,6 +57,7 @@ import com.android.systemui.statusbar.phone.ui.StatusBarIconController
import com.android.systemui.statusbar.pipeline.shared.ui.binder.HomeStatusBarIconBlockListBinder
import com.android.systemui.statusbar.pipeline.shared.ui.binder.HomeStatusBarViewBinder
import com.android.systemui.statusbar.pipeline.shared.ui.binder.StatusBarVisibilityChangeListener
+import com.android.systemui.statusbar.pipeline.shared.ui.model.VisibilityModel
import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.HomeStatusBarViewModel
import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.HomeStatusBarViewModel.HomeStatusBarViewModelFactory
import javax.inject.Inject
@@ -304,11 +305,7 @@ fun StatusBarRoot(
fun Disambiguation(viewModel: HomeStatusBarViewModel) {
val clockVisibilityModel =
viewModel.isClockVisible.collectAsStateWithLifecycle(
- initialValue =
- HomeStatusBarViewModel.VisibilityModel(
- visibility = View.GONE,
- shouldAnimateChange = false,
- )
+ initialValue = VisibilityModel(visibility = View.GONE, shouldAnimateChange = false)
)
if (clockVisibilityModel.value.visibility == View.VISIBLE) {
Box(modifier = Modifier.fillMaxSize().alpha(0.5f), contentAlignment = Alignment.Center) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/model/SystemInfoCombinedVisibilityModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/model/SystemInfoCombinedVisibilityModel.kt
new file mode 100644
index 000000000000..e27225270633
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/model/SystemInfoCombinedVisibilityModel.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.shared.ui.model
+
+import com.android.systemui.log.table.Diffable
+import com.android.systemui.log.table.TableRowLogger
+import com.android.systemui.statusbar.events.shared.model.SystemEventAnimationState
+
+/** The combined visibility + animation state for the system info status bar area */
+data class SystemInfoCombinedVisibilityModel(
+ val baseVisibility: VisibilityModel,
+ val animationState: SystemEventAnimationState,
+) : Diffable<SystemInfoCombinedVisibilityModel> {
+ override fun logDiffs(prevVal: SystemInfoCombinedVisibilityModel, row: TableRowLogger) {
+ if (animationState != prevVal.animationState) {
+ row.logChange(COL_ANIM, animationState.name)
+ }
+
+ baseVisibility.logDiffs(prevVal.baseVisibility, row)
+ }
+
+ override fun logFull(row: TableRowLogger) {
+ row.logChange(COL_ANIM, animationState.name)
+ baseVisibility.logFull(row)
+ }
+
+ companion object {
+ const val COL_ANIM = "animState"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/model/VisibilityModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/model/VisibilityModel.kt
new file mode 100644
index 000000000000..7b39ada72c64
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/model/VisibilityModel.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.shared.ui.model
+
+import android.view.View
+import com.android.systemui.log.table.Diffable
+import com.android.systemui.log.table.TableRowLogger
+import com.android.systemui.util.visibilityString
+
+/** Models the current visibility for a specific child view of status bar. */
+data class VisibilityModel(
+ @View.Visibility val visibility: Int,
+ /** True if a visibility change should be animated. */
+ val shouldAnimateChange: Boolean,
+) : Diffable<VisibilityModel> {
+ override fun logDiffs(prevVal: VisibilityModel, row: TableRowLogger) {
+ if (visibility != prevVal.visibility) {
+ row.logChange(COL_VIS, visibilityString(visibility))
+ }
+
+ if (shouldAnimateChange != prevVal.shouldAnimateChange) {
+ row.logChange(COL_ANIMATE, shouldAnimateChange)
+ }
+ }
+
+ override fun logFull(row: TableRowLogger) {
+ row.logChange(COL_VIS, visibilityString(visibility))
+ row.logChange(COL_ANIMATE, shouldAnimateChange)
+ }
+
+ companion object {
+ const val COL_VIS = "vis"
+ const val COL_ANIMATE = "animate"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModel.kt
index dcd2dbf57b42..5acedf129184 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModel.kt
@@ -28,6 +28,8 @@ import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
import com.android.systemui.keyguard.shared.model.KeyguardState.OCCLUDED
import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.log.table.TableLogBufferFactory
+import com.android.systemui.log.table.logDiffsForTable
import com.android.systemui.plugins.DarkIconDispatcher
import com.android.systemui.scene.domain.interactor.SceneContainerOcclusionInteractor
import com.android.systemui.scene.domain.interactor.SceneInteractor
@@ -39,7 +41,6 @@ import com.android.systemui.statusbar.chips.ui.model.MultipleOngoingActivityChip
import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipsViewModel
import com.android.systemui.statusbar.events.domain.interactor.SystemStatusEventAnimationInteractor
-import com.android.systemui.statusbar.events.shared.model.SystemEventAnimationState
import com.android.systemui.statusbar.events.shared.model.SystemEventAnimationState.Idle
import com.android.systemui.statusbar.featurepods.popups.shared.model.PopupChipModel
import com.android.systemui.statusbar.featurepods.popups.ui.viewmodel.StatusBarPopupChipsViewModel
@@ -53,7 +54,8 @@ import com.android.systemui.statusbar.phone.domain.interactor.DarkIconInteractor
import com.android.systemui.statusbar.phone.domain.interactor.LightsOutInteractor
import com.android.systemui.statusbar.pipeline.shared.domain.interactor.HomeStatusBarIconBlockListInteractor
import com.android.systemui.statusbar.pipeline.shared.domain.interactor.HomeStatusBarInteractor
-import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.HomeStatusBarViewModel.VisibilityModel
+import com.android.systemui.statusbar.pipeline.shared.ui.model.SystemInfoCombinedVisibilityModel
+import com.android.systemui.statusbar.pipeline.shared.ui.model.VisibilityModel
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
@@ -155,19 +157,6 @@ interface HomeStatusBarViewModel {
*/
val areaTint: Flow<StatusBarTintColor>
- /** Models the current visibility for a specific child view of status bar. */
- data class VisibilityModel(
- @View.Visibility val visibility: Int,
- /** True if a visibility change should be animated. */
- val shouldAnimateChange: Boolean,
- )
-
- /** The combined visibility + animation state for the system info status bar area */
- data class SystemInfoCombinedVisibilityModel(
- val baseVisibility: VisibilityModel,
- val animationState: SystemEventAnimationState,
- )
-
/** Interface for the assisted factory, to allow for providing a fake in tests */
interface HomeStatusBarViewModelFactory {
fun create(displayId: Int): HomeStatusBarViewModel
@@ -178,6 +167,7 @@ class HomeStatusBarViewModelImpl
@AssistedInject
constructor(
@Assisted thisDisplayId: Int,
+ tableLoggerFactory: TableLogBufferFactory,
homeStatusBarInteractor: HomeStatusBarInteractor,
homeStatusBarIconBlockListInteractor: HomeStatusBarIconBlockListInteractor,
lightsOutInteractor: LightsOutInteractor,
@@ -196,9 +186,19 @@ constructor(
statusBarContentInsetsViewModelStore: StatusBarContentInsetsViewModelStore,
@Application coroutineScope: CoroutineScope,
) : HomeStatusBarViewModel {
+
+ val tableLogger = tableLoggerFactory.getOrCreate(tableLogBufferName(thisDisplayId), 200)
+
override val isTransitioningFromLockscreenToOccluded: StateFlow<Boolean> =
keyguardTransitionInteractor
.isInTransition(Edge.create(from = LOCKSCREEN, to = OCCLUDED))
+ .distinctUntilChanged()
+ .logDiffsForTable(
+ tableLogBuffer = tableLogger,
+ columnPrefix = "",
+ columnName = COL_LOCK_TO_OCCLUDED,
+ initialValue = false,
+ )
.stateIn(coroutineScope, SharingStarted.WhileSubscribed(), initialValue = false)
override val transitionFromLockscreenToDreamStartedEvent: Flow<Unit> =
@@ -225,20 +225,33 @@ constructor(
// which lives elsewhere.)
currentScene == Scenes.Gone || isOccluded
}
+ .distinctUntilChanged()
+ .logDiffsForTable(
+ tableLogBuffer = tableLogger,
+ columnPrefix = "",
+ columnName = COL_ALLOWED_BY_SCENE,
+ initialValue = false,
+ )
.stateIn(coroutineScope, SharingStarted.WhileSubscribed(), initialValue = false)
override val areNotificationsLightsOut: Flow<Boolean> =
if (NotificationsLiveDataStoreRefactor.isUnexpectedlyInLegacyMode()) {
- emptyFlow()
- } else {
- combine(
- notificationsInteractor.areAnyNotificationsPresent,
- lightsOutInteractor.isLowProfile(thisDisplayId) ?: flowOf(false),
- ) { hasNotifications, isLowProfile ->
- hasNotifications && isLowProfile
- }
- .distinctUntilChanged()
- }
+ emptyFlow()
+ } else {
+ combine(
+ notificationsInteractor.areAnyNotificationsPresent,
+ lightsOutInteractor.isLowProfile(thisDisplayId) ?: flowOf(false),
+ ) { hasNotifications, isLowProfile ->
+ hasNotifications && isLowProfile
+ }
+ .distinctUntilChanged()
+ }
+ .logDiffsForTable(
+ tableLogBuffer = tableLogger,
+ columnPrefix = "",
+ columnName = COL_NOTIF_LIGHTS_OUT,
+ initialValue = false,
+ )
override val areaTint: Flow<StatusBarTintColor> =
darkIconInteractor
@@ -277,19 +290,26 @@ constructor(
override val shouldHomeStatusBarBeVisible =
combine(
- isHomeStatusBarAllowed,
- keyguardInteractor.isSecureCameraActive,
- headsUpNotificationInteractor.statusBarHeadsUpStatus,
- ) { isHomeStatusBarAllowed, isSecureCameraActive, headsUpState ->
- // When launching the camera over the lockscreen, the status icons would typically
- // become visible momentarily before animating out, since we're not yet aware that the
- // launching camera activity is fullscreen. Even once the activity finishes launching,
- // it takes a short time before WM decides that the top app wants to hide the icons and
- // tells us to hide them.
- // To ensure that this high-visibility animation is smooth, keep the icons hidden during
- // a camera launch. See b/257292822.
- headsUpState.isPinned || (isHomeStatusBarAllowed && !isSecureCameraActive)
- }
+ isHomeStatusBarAllowed,
+ keyguardInteractor.isSecureCameraActive,
+ headsUpNotificationInteractor.statusBarHeadsUpStatus,
+ ) { isHomeStatusBarAllowed, isSecureCameraActive, headsUpState ->
+ // When launching the camera over the lockscreen, the status icons would typically
+ // become visible momentarily before animating out, since we're not yet aware that
+ // the launching camera activity is fullscreen. Even once the activity finishes
+ // launching, it takes a short time before WM decides that the top app wants to hide
+ // the icons and tells us to hide them. To ensure that this high-visibility
+ // animation is smooth, keep the icons hidden during a camera launch. See
+ // b/257292822.
+ headsUpState.isPinned || (isHomeStatusBarAllowed && !isSecureCameraActive)
+ }
+ .distinctUntilChanged()
+ .logDiffsForTable(
+ tableLogBuffer = tableLogger,
+ columnPrefix = "",
+ columnName = COL_VISIBLE,
+ initialValue = false,
+ )
private val isAnyChipVisible =
if (StatusBarNotifChips.isEnabled) {
@@ -313,53 +333,73 @@ constructor(
override val shouldShowOperatorNameView: Flow<Boolean> =
combine(
- shouldHomeStatusBarBeVisible,
- hideStartSideContentForHeadsUp,
- homeStatusBarInteractor.visibilityViaDisableFlags,
- homeStatusBarInteractor.shouldShowOperatorName,
- ) {
- shouldStatusBarBeVisible,
- hideStartSideContentForHeadsUp,
- visibilityViaDisableFlags,
- shouldShowOperator ->
- shouldStatusBarBeVisible &&
- !hideStartSideContentForHeadsUp &&
- visibilityViaDisableFlags.isSystemInfoAllowed &&
- shouldShowOperator
- }
+ shouldHomeStatusBarBeVisible,
+ hideStartSideContentForHeadsUp,
+ homeStatusBarInteractor.visibilityViaDisableFlags,
+ homeStatusBarInteractor.shouldShowOperatorName,
+ ) {
+ shouldStatusBarBeVisible,
+ hideStartSideContentForHeadsUp,
+ visibilityViaDisableFlags,
+ shouldShowOperator ->
+ shouldStatusBarBeVisible &&
+ !hideStartSideContentForHeadsUp &&
+ visibilityViaDisableFlags.isSystemInfoAllowed &&
+ shouldShowOperator
+ }
+ .distinctUntilChanged()
+ .logDiffsForTable(
+ tableLogBuffer = tableLogger,
+ columnPrefix = "",
+ columnName = COL_SHOW_OPERATOR_NAME,
+ initialValue = false,
+ )
override val isClockVisible: Flow<VisibilityModel> =
combine(
- shouldHomeStatusBarBeVisible,
- hideStartSideContentForHeadsUp,
- homeStatusBarInteractor.visibilityViaDisableFlags,
- ) { shouldStatusBarBeVisible, hideStartSideContentForHeadsUp, visibilityViaDisableFlags ->
- val showClock =
- shouldStatusBarBeVisible &&
- visibilityViaDisableFlags.isClockAllowed &&
- !hideStartSideContentForHeadsUp
- // Always use View.INVISIBLE here, so that animations work
- VisibilityModel(showClock.toVisibleOrInvisible(), visibilityViaDisableFlags.animate)
- }
+ shouldHomeStatusBarBeVisible,
+ hideStartSideContentForHeadsUp,
+ homeStatusBarInteractor.visibilityViaDisableFlags,
+ ) { shouldStatusBarBeVisible, hideStartSideContentForHeadsUp, visibilityViaDisableFlags
+ ->
+ val showClock =
+ shouldStatusBarBeVisible &&
+ visibilityViaDisableFlags.isClockAllowed &&
+ !hideStartSideContentForHeadsUp
+ // Always use View.INVISIBLE here, so that animations work
+ VisibilityModel(showClock.toVisibleOrInvisible(), visibilityViaDisableFlags.animate)
+ }
+ .distinctUntilChanged()
+ .logDiffsForTable(
+ tableLogBuffer = tableLogger,
+ columnPrefix = COL_PREFIX_CLOCK,
+ initialValue = VisibilityModel(false.toVisibleOrInvisible(), false),
+ )
override val isNotificationIconContainerVisible: Flow<VisibilityModel> =
combine(
- shouldHomeStatusBarBeVisible,
- isAnyChipVisible,
- homeStatusBarInteractor.visibilityViaDisableFlags,
- ) { shouldStatusBarBeVisible, anyChipVisible, visibilityViaDisableFlags ->
- val showNotificationIconContainer =
- if (anyChipVisible) {
- false
- } else {
- shouldStatusBarBeVisible &&
- visibilityViaDisableFlags.areNotificationIconsAllowed
- }
- VisibilityModel(
- showNotificationIconContainer.toVisibleOrGone(),
- visibilityViaDisableFlags.animate,
+ shouldHomeStatusBarBeVisible,
+ isAnyChipVisible,
+ homeStatusBarInteractor.visibilityViaDisableFlags,
+ ) { shouldStatusBarBeVisible, anyChipVisible, visibilityViaDisableFlags ->
+ val showNotificationIconContainer =
+ if (anyChipVisible) {
+ false
+ } else {
+ shouldStatusBarBeVisible &&
+ visibilityViaDisableFlags.areNotificationIconsAllowed
+ }
+ VisibilityModel(
+ showNotificationIconContainer.toVisibleOrGone(),
+ visibilityViaDisableFlags.animate,
+ )
+ }
+ .distinctUntilChanged()
+ .logDiffsForTable(
+ tableLogBuffer = tableLogger,
+ columnPrefix = COL_PREFIX_NOTIF_CONTAINER,
+ initialValue = VisibilityModel(false.toVisibleOrInvisible(), false),
)
- }
private val isSystemInfoVisible =
combine(shouldHomeStatusBarBeVisible, homeStatusBarInteractor.visibilityViaDisableFlags) {
@@ -372,18 +412,19 @@ constructor(
override val systemInfoCombinedVis =
combine(isSystemInfoVisible, animations.animationState) { sysInfoVisible, animationState ->
- HomeStatusBarViewModel.SystemInfoCombinedVisibilityModel(
- sysInfoVisible,
- animationState,
- )
+ SystemInfoCombinedVisibilityModel(sysInfoVisible, animationState)
}
+ .distinctUntilChanged()
+ .logDiffsForTable(
+ tableLogBuffer = tableLogger,
+ columnPrefix = COL_PREFIX_SYSTEM_INFO,
+ initialValue =
+ SystemInfoCombinedVisibilityModel(VisibilityModel(View.VISIBLE, false), Idle),
+ )
.stateIn(
coroutineScope,
SharingStarted.WhileSubscribed(),
- HomeStatusBarViewModel.SystemInfoCombinedVisibilityModel(
- VisibilityModel(View.VISIBLE, false),
- Idle,
- ),
+ SystemInfoCombinedVisibilityModel(VisibilityModel(View.VISIBLE, false), Idle),
)
override val iconBlockList: Flow<List<String>> =
@@ -408,6 +449,19 @@ constructor(
HomeStatusBarViewModel.HomeStatusBarViewModelFactory {
override fun create(displayId: Int): HomeStatusBarViewModelImpl
}
+
+ companion object {
+ private const val COL_LOCK_TO_OCCLUDED = "Lock->Occluded"
+ private const val COL_ALLOWED_BY_SCENE = "allowedByScene"
+ private const val COL_NOTIF_LIGHTS_OUT = "notifLightsOut"
+ private const val COL_SHOW_OPERATOR_NAME = "showOperatorName"
+ private const val COL_VISIBLE = "visible"
+ private const val COL_PREFIX_CLOCK = "clock"
+ private const val COL_PREFIX_NOTIF_CONTAINER = "notifContainer"
+ private const val COL_PREFIX_SYSTEM_INFO = "systemInfo"
+
+ fun tableLogBufferName(displayId: Int) = "HomeStatusBarViewModel[$displayId]"
+ }
}
/** Lookup the color for a given view in the status bar */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegate.kt
index 9ff0d18f0e2b..db5f1301823b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegate.kt
@@ -16,6 +16,8 @@
package com.android.systemui.statusbar.policy.ui.dialog
+import android.app.Dialog
+import android.content.Context
import android.content.Intent
import android.provider.Settings
import android.util.Log
@@ -36,6 +38,7 @@ import com.android.compose.PlatformOutlinedButton
import com.android.compose.theme.PlatformTheme
import com.android.internal.annotations.VisibleForTesting
import com.android.internal.jank.InteractionJankMonitor
+import com.android.settingslib.notification.modes.EnableDndDialogFactory
import com.android.systemui.animation.DialogCuj
import com.android.systemui.animation.DialogTransitionAnimator
import com.android.systemui.animation.Expandable
@@ -43,6 +46,7 @@ import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.dialog.ui.composable.AlertDialogContent
import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.qs.tiles.dialog.QSEnableDndDialogMetricsLogger
import com.android.systemui.res.R
import com.android.systemui.shade.domain.interactor.ShadeDialogContextInteractor
import com.android.systemui.statusbar.phone.ComponentSystemUIDialog
@@ -61,6 +65,7 @@ import kotlinx.coroutines.withContext
class ModesDialogDelegate
@Inject
constructor(
+ val context: Context,
private val sysuiDialogFactory: SystemUIDialogFactory,
private val dialogTransitionAnimator: DialogTransitionAnimator,
private val activityStarter: ActivityStarter,
@@ -72,6 +77,7 @@ constructor(
) : SystemUIDialog.Delegate {
// NOTE: This should only be accessed/written from the main thread.
@VisibleForTesting var currentDialog: ComponentSystemUIDialog? = null
+ private val dndDurationDialogLogger by lazy { QSEnableDndDialogMetricsLogger(context) }
override fun createDialog(): SystemUIDialog {
Assert.isMainThread()
@@ -195,6 +201,26 @@ constructor(
activityStarter.startActivity(intent, /* dismissShade= */ true, animationController)
}
+ /**
+ * Special dialog to ask the user for the duration of DND. Not to be confused with the modes
+ * dialog itself.
+ */
+ fun makeDndDurationDialog(): Dialog {
+ val dialog =
+ EnableDndDialogFactory(
+ context,
+ R.style.Theme_SystemUI_Dialog,
+ /* cancelIsNeutral= */ true,
+ dndDurationDialogLogger,
+ )
+ .createDialog()
+ SystemUIDialog.applyFlags(dialog)
+ SystemUIDialog.setShowForAllUsers(dialog, true)
+ SystemUIDialog.registerDismissListener(dialog)
+ SystemUIDialog.setDialogSize(dialog)
+ return dialog
+ }
+
companion object {
private const val TAG = "ModesDialogDelegate"
private val ZEN_MODE_SETTINGS_INTENT = Intent(Settings.ACTION_ZEN_MODE_SETTINGS)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModel.kt
index 1c13a833ef30..07f1c3470c83 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModel.kt
@@ -16,20 +16,16 @@
package com.android.systemui.statusbar.policy.ui.dialog.viewmodel
-import android.app.Dialog
import android.content.Context
import android.content.Intent
import android.provider.Settings.ACTION_AUTOMATIC_ZEN_RULE_SETTINGS
import android.provider.Settings.EXTRA_AUTOMATIC_ZEN_RULE_ID
-import com.android.settingslib.notification.modes.EnableZenModeDialog
import com.android.settingslib.notification.modes.ZenMode
import com.android.settingslib.notification.modes.ZenModeDescriptions
import com.android.systemui.common.shared.model.asIcon
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
-import com.android.systemui.qs.tiles.dialog.QSZenModeDialogMetricsLogger
import com.android.systemui.res.R
-import com.android.systemui.statusbar.phone.SystemUIDialog
import com.android.systemui.statusbar.policy.domain.interactor.ZenModeInteractor
import com.android.systemui.statusbar.policy.ui.dialog.ModesDialogDelegate
import com.android.systemui.statusbar.policy.ui.dialog.ModesDialogEventLogger
@@ -54,7 +50,6 @@ constructor(
private val dialogDelegate: ModesDialogDelegate,
private val dialogEventLogger: ModesDialogEventLogger,
) {
- private val zenDialogMetricsLogger = QSZenModeDialogMetricsLogger(context)
private val zenModeDescriptions = ZenModeDescriptions(context)
// Modes that should be displayed in the dialog
@@ -112,7 +107,7 @@ constructor(
if (zenModeInteractor.shouldAskForZenDuration(mode)) {
dialogEventLogger.logOpenDurationDialog(mode)
// NOTE: The dialog handles turning on the mode itself.
- val dialog = makeZenModeDialog()
+ val dialog = dialogDelegate.makeDndDurationDialog()
dialog.show()
} else {
dialogEventLogger.logModeOn(mode)
@@ -173,20 +168,4 @@ constructor(
modeDescription ?: context.getString(R.string.zen_mode_off)
}
}
-
- private fun makeZenModeDialog(): Dialog {
- val dialog =
- EnableZenModeDialog(
- context,
- R.style.Theme_SystemUI_Dialog,
- /* cancelIsNeutral= */ true,
- zenDialogMetricsLogger,
- )
- .createDialog()
- SystemUIDialog.applyFlags(dialog)
- SystemUIDialog.setShowForAllUsers(dialog, true)
- SystemUIDialog.registerDismissListener(dialog)
- SystemUIDialog.setDialogSize(dialog)
- return dialog
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java b/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java
index 05ee35b36c7f..8f5fccd7e324 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java
@@ -75,8 +75,7 @@ public class TunerServiceImpl extends TunerService {
private static final String[] RESET_EXCEPTION_LIST = new String[] {
QSHost.TILES_SETTING,
Settings.Secure.DOZE_ALWAYS_ON,
- Settings.Secure.MEDIA_CONTROLS_RESUME,
- Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION
+ Settings.Secure.MEDIA_CONTROLS_RESUME
};
private final Observer mObserver = new Observer();
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderIconProvider.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderIconProvider.kt
index 9cf02f26c9f7..ef147c741bec 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderIconProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderIconProvider.kt
@@ -21,6 +21,7 @@ import android.content.Context
import android.graphics.drawable.Drawable
import android.media.AudioManager
import androidx.annotation.DrawableRes
+import com.android.settingslib.R as SettingsR
import com.android.settingslib.volume.domain.interactor.AudioVolumeInteractor
import com.android.settingslib.volume.shared.model.AudioStream
import com.android.settingslib.volume.shared.model.RingerMode
@@ -30,8 +31,10 @@ import com.android.systemui.statusbar.policy.domain.model.ActiveZenModes
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOf
+@SuppressLint("UseCompatLoadingForDrawables")
class VolumeDialogSliderIconProvider
@Inject
constructor(
@@ -40,7 +43,30 @@ constructor(
private val audioVolumeInteractor: AudioVolumeInteractor,
) {
- @SuppressLint("UseCompatLoadingForDrawables")
+ fun getAudioSharingIcon(isMuted: Boolean): Flow<Drawable> {
+ return flow {
+ val iconRes =
+ if (isMuted) {
+ R.drawable.ic_volume_media_bt_mute
+ } else {
+ R.drawable.ic_volume_media_bt
+ }
+ emit(context.getDrawable(iconRes)!!)
+ }
+ }
+
+ fun getCastIcon(isMuted: Boolean): Flow<Drawable> {
+ return flow {
+ val iconRes =
+ if (isMuted) {
+ SettingsR.drawable.ic_volume_remote_mute
+ } else {
+ SettingsR.drawable.ic_volume_remote
+ }
+ emit(context.getDrawable(iconRes)!!)
+ }
+ }
+
fun getStreamIcon(
stream: Int,
level: Int,
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderViewModel.kt
index 89dd0352afa7..a752f1f78e74 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderViewModel.kt
@@ -57,12 +57,12 @@ private const val VOLUME_UPDATE_GRACE_PERIOD = 1000
class VolumeDialogSliderViewModel
@Inject
constructor(
+ private val sliderType: VolumeDialogSliderType,
private val interactor: VolumeDialogSliderInteractor,
private val visibilityInteractor: VolumeDialogVisibilityInteractor,
@VolumeDialog private val coroutineScope: CoroutineScope,
private val volumeDialogSliderIconProvider: VolumeDialogSliderIconProvider,
private val systemClock: SystemClock,
- private val sliderType: VolumeDialogSliderType,
private val logger: VolumeDialogLogger,
) {
@@ -82,14 +82,24 @@ constructor(
model
.flatMapLatest { streamModel ->
with(streamModel) {
- volumeDialogSliderIconProvider.getStreamIcon(
- stream = stream,
- level = level,
- levelMin = levelMin,
- levelMax = levelMax,
- isMuted = muteSupported && muted,
- isRoutedToBluetooth = routedToBluetooth,
- )
+ val isMuted = muteSupported && muted
+ when (sliderType) {
+ is VolumeDialogSliderType.Stream ->
+ volumeDialogSliderIconProvider.getStreamIcon(
+ stream = sliderType.audioStream,
+ level = level,
+ levelMin = levelMin,
+ levelMax = levelMax,
+ isMuted = isMuted,
+ isRoutedToBluetooth = routedToBluetooth,
+ )
+ is VolumeDialogSliderType.RemoteMediaStream -> {
+ volumeDialogSliderIconProvider.getCastIcon(isMuted)
+ }
+ is VolumeDialogSliderType.AudioSharingStream -> {
+ volumeDialogSliderIconProvider.getAudioSharingIcon(isMuted)
+ }
+ }
}
.map { icon -> streamModel.toStateModel(icon) }
}
diff --git a/packages/SystemUI/src/com/android/systemui/wallet/controller/QuickAccessWalletController.java b/packages/SystemUI/src/com/android/systemui/wallet/controller/QuickAccessWalletController.java
index 411e06ed1339..0c8dc11f4327 100644
--- a/packages/SystemUI/src/com/android/systemui/wallet/controller/QuickAccessWalletController.java
+++ b/packages/SystemUI/src/com/android/systemui/wallet/controller/QuickAccessWalletController.java
@@ -293,7 +293,7 @@ public class QuickAccessWalletController {
intent = getSysUiWalletIntent();
}
startQuickAccessViaIntent(intent, hasCard, activityStarter,
- animationController);
+ animationController, mQuickAccessWalletClient.getUser());
});
}
@@ -323,7 +323,8 @@ public class QuickAccessWalletController {
private void startQuickAccessViaIntent(Intent intent,
boolean hasCard,
ActivityStarter activityStarter,
- ActivityTransitionAnimator.Controller animationController) {
+ ActivityTransitionAnimator.Controller animationController,
+ UserHandle user) {
if (hasCard) {
activityStarter.startActivity(intent, true /* dismissShade */,
animationController, true /* showOverLockscreenWhenLocked */);
@@ -331,7 +332,9 @@ public class QuickAccessWalletController {
activityStarter.postStartActivityDismissingKeyguard(
intent,
/* delay= */ 0,
- animationController);
+ animationController,
+ null,
+ user);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletActivity.java b/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletActivity.java
index 111492c3e227..18e1b6eb323d 100644
--- a/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletActivity.java
@@ -163,15 +163,13 @@ public class WalletActivity extends ComponentActivity implements
if (mKeyguardStateController.isUnlocked()) {
mUiEventLogger.log(WalletUiEvent.QAW_SHOW_ALL);
- mActivityStarter.startActivity(
- mWalletClient.createWalletIntent(), true);
+ startWalletActivity();
finish();
} else {
mUiEventLogger.log(WalletUiEvent.QAW_UNLOCK_FROM_SHOW_ALL_BUTTON);
mKeyguardDismissUtil.executeWhenUnlocked(() -> {
mUiEventLogger.log(WalletUiEvent.QAW_SHOW_ALL);
- mActivityStarter.startActivity(
- mWalletClient.createWalletIntent(), true);
+ startWalletActivity();
finish();
return false;
}, false, true);
@@ -193,6 +191,11 @@ public class WalletActivity extends ComponentActivity implements
});
}
+ private void startWalletActivity() {
+ mActivityStarter.startActivity(mWalletClient.createWalletIntent(), true,
+ null, true, mWalletClient.getUser());
+ }
+
@Override
protected void onStart() {
super.onStart();
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
index bac2c47f51c7..1729a4dfa945 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
@@ -55,6 +55,7 @@ import com.android.systemui.plugins.clocks.ClockTickRate
import com.android.systemui.plugins.clocks.ThemeConfig
import com.android.systemui.plugins.clocks.ZenData
import com.android.systemui.plugins.clocks.ZenData.ZenMode
+import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.res.R
import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.policy.BatteryController
@@ -131,6 +132,7 @@ class ClockEventControllerTest : SysuiTestCase() {
@Mock private lateinit var parentView: View
@Mock private lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor
@Mock private lateinit var userTracker: UserTracker
+ @Mock private lateinit var powerInteractor: PowerInteractor
@Mock private lateinit var zenModeController: ZenModeController
private var zenModeControllerCallback: ZenModeController.Callback? = null
@@ -178,6 +180,7 @@ class ClockEventControllerTest : SysuiTestCase() {
zenModeController,
zenModeInteractor,
userTracker,
+ powerInteractor,
)
underTest.clock = clock
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt
index 82bf5e248a50..a3c3d2cdbb43 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt
@@ -205,6 +205,7 @@ class CustomizationProviderTest : SysuiTestCase() {
biometricSettingsRepository = biometricSettingsRepository,
backgroundDispatcher = testDispatcher,
appContext = mContext,
+ accessibilityManager = mock(),
communalSettingsInteractor = kosmos.communalSettingsInteractor,
sceneInteractor = { kosmos.sceneInteractor },
)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
index 111d819b5a50..21519b0cb38a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
@@ -322,6 +322,7 @@ class KeyguardQuickAffordanceInteractorParameterizedTest : SysuiTestCase() {
biometricSettingsRepository = biometricSettingsRepository,
backgroundDispatcher = testDispatcher,
appContext = mContext,
+ accessibilityManager = mock(),
communalSettingsInteractor = kosmos.communalSettingsInteractor,
sceneInteractor = { kosmos.sceneInteractor },
)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorSceneContainerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorSceneContainerTest.kt
index 8c00047296d1..caf08efc4b32 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorSceneContainerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorSceneContainerTest.kt
@@ -325,6 +325,7 @@ class KeyguardQuickAffordanceInteractorSceneContainerTest : SysuiTestCase() {
biometricSettingsRepository = biometricSettingsRepository,
backgroundDispatcher = testDispatcher,
appContext = mContext,
+ accessibilityManager = mock(),
communalSettingsInteractor = kosmos.communalSettingsInteractor,
sceneInteractor = { kosmos.sceneInteractor },
)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt
index 0b2b86785c75..b5a227104900 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt
@@ -294,6 +294,7 @@ class KeyguardQuickAffordancesCombinedViewModelTest : SysuiTestCase() {
biometricSettingsRepository = biometricSettingsRepository,
backgroundDispatcher = kosmos.testDispatcher,
appContext = mContext,
+ accessibilityManager = mock(),
communalSettingsInteractor = kosmos.communalSettingsInteractor,
sceneInteractor = { kosmos.sceneInteractor },
),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImplTest.kt
index 3ddd4b58211d..2815b97691ad 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImplTest.kt
@@ -41,13 +41,11 @@ import android.os.Bundle
import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
import android.platform.test.flag.junit.FlagsParameterization
-import android.provider.Settings
import android.service.notification.StatusBarNotification
import android.testing.TestableLooper.RunWithLooper
import androidx.media.utils.MediaConstants
import androidx.test.filters.SmallTest
import com.android.dx.mockito.inline.extended.ExtendedMockito
-import com.android.internal.logging.InstanceId
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.Flags
import com.android.systemui.InstanceIdSequenceFake
@@ -65,10 +63,7 @@ import com.android.systemui.media.controls.domain.resume.MediaResumeListener
import com.android.systemui.media.controls.domain.resume.ResumeMediaBrowser
import com.android.systemui.media.controls.shared.mediaLogger
import com.android.systemui.media.controls.shared.mockMediaLogger
-import com.android.systemui.media.controls.shared.model.EXTRA_KEY_TRIGGER_SOURCE
-import com.android.systemui.media.controls.shared.model.EXTRA_VALUE_TRIGGER_PERIODIC
import com.android.systemui.media.controls.shared.model.MediaData
-import com.android.systemui.media.controls.shared.model.SmartspaceMediaData
import com.android.systemui.media.controls.shared.model.SmartspaceMediaDataProvider
import com.android.systemui.media.controls.util.MediaUiEventLogger
import com.android.systemui.media.controls.util.fakeMediaControllerFactory
@@ -76,7 +71,6 @@ import com.android.systemui.media.controls.util.mediaFlags
import com.android.systemui.res.R
import com.android.systemui.statusbar.SbnBuilder
import com.android.systemui.testKosmos
-import com.android.systemui.tuner.TunerService
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
@@ -95,12 +89,10 @@ import org.mockito.ArgumentMatchers.anyBoolean
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Captor
import org.mockito.Mock
-import org.mockito.Mockito
import org.mockito.Mockito.mock
import org.mockito.Mockito.never
import org.mockito.Mockito.reset
import org.mockito.Mockito.verify
-import org.mockito.Mockito.verifyNoMoreInteractions
import org.mockito.Mockito.`when` as whenever
import org.mockito.MockitoSession
import org.mockito.junit.MockitoJUnit
@@ -126,10 +118,6 @@ private const val SESSION_EMPTY_TITLE = ""
private const val USER_ID = 0
private val DISMISS_INTENT = Intent().apply { action = "dismiss" }
-private fun <T> anyObject(): T {
- return Mockito.anyObject<T>()
-}
-
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWithLooper(setAsMainLooper = true)
@@ -168,8 +156,6 @@ class LegacyMediaDataManagerImplTest(flags: FlagsParameterization) : SysuiTestCa
lateinit var remoteCastNotification: StatusBarNotification
@Captor lateinit var mediaDataCaptor: ArgumentCaptor<MediaData>
private val clock = FakeSystemClock()
- @Mock private lateinit var tunerService: TunerService
- @Captor lateinit var tunableCaptor: ArgumentCaptor<TunerService.Tunable>
@Captor lateinit var stateCallbackCaptor: ArgumentCaptor<(String, PlaybackState) -> Unit>
@Captor lateinit var sessionCallbackCaptor: ArgumentCaptor<(String) -> Unit>
@Captor lateinit var smartSpaceConfigBuilderCaptor: ArgumentCaptor<SmartspaceConfig>
@@ -197,13 +183,6 @@ class LegacyMediaDataManagerImplTest(flags: FlagsParameterization) : SysuiTestCa
private val mediaControllerFactory = kosmos.fakeMediaControllerFactory
private val instanceIdSequence = InstanceIdSequenceFake(1 shl 20)
- private val originalSmartspaceSetting =
- Settings.Secure.getInt(
- context.contentResolver,
- Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION,
- 1,
- )
-
private lateinit var staticMockSession: MockitoSession
@Before
@@ -219,11 +198,6 @@ class LegacyMediaDataManagerImplTest(flags: FlagsParameterization) : SysuiTestCa
backgroundExecutor = FakeExecutor(clock)
uiExecutor = FakeExecutor(clock)
smartspaceMediaDataProvider = SmartspaceMediaDataProvider()
- Settings.Secure.putInt(
- context.contentResolver,
- Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION,
- 1,
- )
mediaDataManager =
LegacyMediaDataManagerImpl(
context = context,
@@ -246,7 +220,6 @@ class LegacyMediaDataManagerImplTest(flags: FlagsParameterization) : SysuiTestCa
useMediaResumption = true,
useQsMediaPlayer = true,
systemClock = clock,
- tunerService = tunerService,
mediaFlags = kosmos.mediaFlags,
logger = logger,
smartspaceManager = smartspaceManager,
@@ -254,8 +227,6 @@ class LegacyMediaDataManagerImplTest(flags: FlagsParameterization) : SysuiTestCa
mediaDataLoader = { kosmos.mediaDataLoader },
mediaLogger = kosmos.mediaLogger,
)
- verify(tunerService)
- .addTunable(capture(tunableCaptor), eq(Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION))
verify(mediaTimeoutListener).stateCallback = capture(stateCallbackCaptor)
verify(mediaTimeoutListener).sessionCallback = capture(sessionCallbackCaptor)
session = MediaSession(context, "MediaDataManagerTestSession")
@@ -332,11 +303,6 @@ class LegacyMediaDataManagerImplTest(flags: FlagsParameterization) : SysuiTestCa
staticMockSession.finishMocking()
session.release()
mediaDataManager.destroy()
- Settings.Secure.putInt(
- context.contentResolver,
- Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION,
- originalSmartspaceSetting,
- )
}
@Test
@@ -1236,272 +1202,6 @@ class LegacyMediaDataManagerImplTest(flags: FlagsParameterization) : SysuiTestCa
}
@Test
- fun testOnSmartspaceMediaDataLoaded_hasNewValidMediaTarget_callsListener() {
- smartspaceMediaDataProvider.onTargetsAvailable(listOf(mediaSmartspaceTarget))
- verify(logger).getNewInstanceId()
- val instanceId = instanceIdSequence.lastInstanceId
-
- verify(listener)
- .onSmartspaceMediaDataLoaded(
- eq(KEY_MEDIA_SMARTSPACE),
- eq(
- SmartspaceMediaData(
- targetId = KEY_MEDIA_SMARTSPACE,
- isActive = true,
- packageName = PACKAGE_NAME,
- cardAction = mediaSmartspaceBaseAction,
- recommendations = validRecommendationList,
- dismissIntent = DISMISS_INTENT,
- headphoneConnectionTimeMillis = SMARTSPACE_CREATION_TIME,
- instanceId = InstanceId.fakeInstanceId(instanceId),
- expiryTimeMs = SMARTSPACE_EXPIRY_TIME,
- )
- ),
- eq(false),
- )
- }
-
- @Test
- fun testOnSmartspaceMediaDataLoaded_hasNewInvalidMediaTarget_callsListener() {
- whenever(mediaSmartspaceTarget.iconGrid).thenReturn(listOf())
- smartspaceMediaDataProvider.onTargetsAvailable(listOf(mediaSmartspaceTarget))
- verify(logger).getNewInstanceId()
- val instanceId = instanceIdSequence.lastInstanceId
-
- verify(listener)
- .onSmartspaceMediaDataLoaded(
- eq(KEY_MEDIA_SMARTSPACE),
- eq(
- EMPTY_SMARTSPACE_MEDIA_DATA.copy(
- targetId = KEY_MEDIA_SMARTSPACE,
- isActive = true,
- dismissIntent = DISMISS_INTENT,
- headphoneConnectionTimeMillis = SMARTSPACE_CREATION_TIME,
- instanceId = InstanceId.fakeInstanceId(instanceId),
- expiryTimeMs = SMARTSPACE_EXPIRY_TIME,
- )
- ),
- eq(false),
- )
- }
-
- @Test
- fun testOnSmartspaceMediaDataLoaded_hasNullIntent_callsListener() {
- val recommendationExtras =
- Bundle().apply {
- putString("package_name", PACKAGE_NAME)
- putParcelable("dismiss_intent", null)
- }
- whenever(mediaSmartspaceBaseAction.extras).thenReturn(recommendationExtras)
- whenever(mediaSmartspaceTarget.baseAction).thenReturn(mediaSmartspaceBaseAction)
- whenever(mediaSmartspaceTarget.iconGrid).thenReturn(listOf())
-
- smartspaceMediaDataProvider.onTargetsAvailable(listOf(mediaSmartspaceTarget))
- verify(logger).getNewInstanceId()
- val instanceId = instanceIdSequence.lastInstanceId
-
- verify(listener)
- .onSmartspaceMediaDataLoaded(
- eq(KEY_MEDIA_SMARTSPACE),
- eq(
- EMPTY_SMARTSPACE_MEDIA_DATA.copy(
- targetId = KEY_MEDIA_SMARTSPACE,
- isActive = true,
- dismissIntent = null,
- headphoneConnectionTimeMillis = SMARTSPACE_CREATION_TIME,
- instanceId = InstanceId.fakeInstanceId(instanceId),
- expiryTimeMs = SMARTSPACE_EXPIRY_TIME,
- )
- ),
- eq(false),
- )
- }
-
- @Test
- fun testOnSmartspaceMediaDataLoaded_hasNoneMediaTarget_notCallsListener() {
- smartspaceMediaDataProvider.onTargetsAvailable(listOf())
- verify(logger, never()).getNewInstanceId()
- verify(listener, never())
- .onSmartspaceMediaDataLoaded(anyObject(), anyObject(), anyBoolean())
- }
-
- @Test
- fun testOnSmartspaceMediaDataLoaded_hasNoneMediaTarget_callsRemoveListener() {
- smartspaceMediaDataProvider.onTargetsAvailable(listOf(mediaSmartspaceTarget))
- verify(logger).getNewInstanceId()
-
- smartspaceMediaDataProvider.onTargetsAvailable(listOf())
- uiExecutor.advanceClockToLast()
- uiExecutor.runAllReady()
-
- verify(listener).onSmartspaceMediaDataRemoved(eq(KEY_MEDIA_SMARTSPACE), eq(false))
- verifyNoMoreInteractions(logger)
- }
-
- @Test
- fun testOnSmartspaceMediaDataLoaded_persistentEnabled_headphoneTrigger_isActive() {
- fakeFeatureFlags.set(MEDIA_RETAIN_RECOMMENDATIONS, true)
- smartspaceMediaDataProvider.onTargetsAvailable(listOf(mediaSmartspaceTarget))
- val instanceId = instanceIdSequence.lastInstanceId
-
- verify(listener)
- .onSmartspaceMediaDataLoaded(
- eq(KEY_MEDIA_SMARTSPACE),
- eq(
- SmartspaceMediaData(
- targetId = KEY_MEDIA_SMARTSPACE,
- isActive = true,
- packageName = PACKAGE_NAME,
- cardAction = mediaSmartspaceBaseAction,
- recommendations = validRecommendationList,
- dismissIntent = DISMISS_INTENT,
- headphoneConnectionTimeMillis = SMARTSPACE_CREATION_TIME,
- instanceId = InstanceId.fakeInstanceId(instanceId),
- expiryTimeMs = SMARTSPACE_EXPIRY_TIME,
- )
- ),
- eq(false),
- )
- }
-
- @Test
- fun testOnSmartspaceMediaDataLoaded_persistentEnabled_periodicTrigger_notActive() {
- fakeFeatureFlags.set(MEDIA_RETAIN_RECOMMENDATIONS, true)
- val extras =
- Bundle().apply {
- putString("package_name", PACKAGE_NAME)
- putParcelable("dismiss_intent", DISMISS_INTENT)
- putString(EXTRA_KEY_TRIGGER_SOURCE, EXTRA_VALUE_TRIGGER_PERIODIC)
- }
- whenever(mediaSmartspaceBaseAction.extras).thenReturn(extras)
-
- smartspaceMediaDataProvider.onTargetsAvailable(listOf(mediaSmartspaceTarget))
- val instanceId = instanceIdSequence.lastInstanceId
-
- verify(listener)
- .onSmartspaceMediaDataLoaded(
- eq(KEY_MEDIA_SMARTSPACE),
- eq(
- SmartspaceMediaData(
- targetId = KEY_MEDIA_SMARTSPACE,
- isActive = false,
- packageName = PACKAGE_NAME,
- cardAction = mediaSmartspaceBaseAction,
- recommendations = validRecommendationList,
- dismissIntent = DISMISS_INTENT,
- headphoneConnectionTimeMillis = SMARTSPACE_CREATION_TIME,
- instanceId = InstanceId.fakeInstanceId(instanceId),
- expiryTimeMs = SMARTSPACE_EXPIRY_TIME,
- )
- ),
- eq(false),
- )
- }
-
- @Test
- fun testOnSmartspaceMediaDataLoaded_persistentEnabled_noTargets_inactive() {
- fakeFeatureFlags.set(MEDIA_RETAIN_RECOMMENDATIONS, true)
-
- smartspaceMediaDataProvider.onTargetsAvailable(listOf(mediaSmartspaceTarget))
- val instanceId = instanceIdSequence.lastInstanceId
-
- smartspaceMediaDataProvider.onTargetsAvailable(listOf())
- uiExecutor.advanceClockToLast()
- uiExecutor.runAllReady()
-
- verify(listener)
- .onSmartspaceMediaDataLoaded(
- eq(KEY_MEDIA_SMARTSPACE),
- eq(
- SmartspaceMediaData(
- targetId = KEY_MEDIA_SMARTSPACE,
- isActive = false,
- packageName = PACKAGE_NAME,
- cardAction = mediaSmartspaceBaseAction,
- recommendations = validRecommendationList,
- dismissIntent = DISMISS_INTENT,
- headphoneConnectionTimeMillis = SMARTSPACE_CREATION_TIME,
- instanceId = InstanceId.fakeInstanceId(instanceId),
- expiryTimeMs = SMARTSPACE_EXPIRY_TIME,
- )
- ),
- eq(false),
- )
- verify(listener, never()).onSmartspaceMediaDataRemoved(eq(KEY_MEDIA_SMARTSPACE), eq(false))
- }
-
- @Test
- fun testSetRecommendationInactive_notifiesListeners() {
- fakeFeatureFlags.set(MEDIA_RETAIN_RECOMMENDATIONS, true)
-
- smartspaceMediaDataProvider.onTargetsAvailable(listOf(mediaSmartspaceTarget))
- val instanceId = instanceIdSequence.lastInstanceId
-
- mediaDataManager.setRecommendationInactive(KEY_MEDIA_SMARTSPACE)
- uiExecutor.advanceClockToLast()
- uiExecutor.runAllReady()
-
- verify(listener)
- .onSmartspaceMediaDataLoaded(
- eq(KEY_MEDIA_SMARTSPACE),
- eq(
- SmartspaceMediaData(
- targetId = KEY_MEDIA_SMARTSPACE,
- isActive = false,
- packageName = PACKAGE_NAME,
- cardAction = mediaSmartspaceBaseAction,
- recommendations = validRecommendationList,
- dismissIntent = DISMISS_INTENT,
- headphoneConnectionTimeMillis = SMARTSPACE_CREATION_TIME,
- instanceId = InstanceId.fakeInstanceId(instanceId),
- expiryTimeMs = SMARTSPACE_EXPIRY_TIME,
- )
- ),
- eq(false),
- )
- }
-
- @Test
- fun testOnSmartspaceMediaDataLoaded_settingDisabled_doesNothing() {
- // WHEN media recommendation setting is off
- Settings.Secure.putInt(
- context.contentResolver,
- Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION,
- 0,
- )
- tunableCaptor.value.onTuningChanged(Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION, "0")
-
- smartspaceMediaDataProvider.onTargetsAvailable(listOf(mediaSmartspaceTarget))
-
- // THEN smartspace signal is ignored
- verify(listener, never())
- .onSmartspaceMediaDataLoaded(anyObject(), anyObject(), anyBoolean())
- }
-
- @Test
- fun testMediaRecommendationDisabled_removesSmartspaceData() {
- // GIVEN a media recommendation card is present
- smartspaceMediaDataProvider.onTargetsAvailable(listOf(mediaSmartspaceTarget))
- verify(listener)
- .onSmartspaceMediaDataLoaded(eq(KEY_MEDIA_SMARTSPACE), anyObject(), anyBoolean())
-
- // WHEN the media recommendation setting is turned off
- Settings.Secure.putInt(
- context.contentResolver,
- Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION,
- 0,
- )
- tunableCaptor.value.onTuningChanged(Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION, "0")
-
- // THEN listeners are notified
- uiExecutor.advanceClockToLast()
- foregroundExecutor.advanceClockToLast()
- uiExecutor.runAllReady()
- foregroundExecutor.runAllReady()
- verify(listener).onSmartspaceMediaDataRemoved(eq(KEY_MEDIA_SMARTSPACE), eq(true))
- }
-
- @Test
fun testOnMediaDataChanged_updatesLastActiveTime() {
val currentTime = clock.elapsedRealtime()
addNotificationAndLoad()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorTest.kt
index e5483c0980c0..b9ebce816c5c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorTest.kt
@@ -42,13 +42,11 @@ import android.os.UserHandle
import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
import android.platform.test.flag.junit.FlagsParameterization
-import android.provider.Settings
import android.service.notification.StatusBarNotification
import android.testing.TestableLooper.RunWithLooper
import androidx.media.utils.MediaConstants
import androidx.test.filters.SmallTest
import com.android.dx.mockito.inline.extended.ExtendedMockito
-import com.android.internal.logging.InstanceId
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.Flags
import com.android.systemui.InstanceIdSequenceFake
@@ -71,21 +69,16 @@ import com.android.systemui.media.controls.domain.resume.MediaResumeListener
import com.android.systemui.media.controls.domain.resume.ResumeMediaBrowser
import com.android.systemui.media.controls.shared.mediaLogger
import com.android.systemui.media.controls.shared.mockMediaLogger
-import com.android.systemui.media.controls.shared.model.EXTRA_KEY_TRIGGER_SOURCE
-import com.android.systemui.media.controls.shared.model.EXTRA_VALUE_TRIGGER_PERIODIC
import com.android.systemui.media.controls.shared.model.MediaData
-import com.android.systemui.media.controls.shared.model.SmartspaceMediaData
import com.android.systemui.media.controls.shared.model.SmartspaceMediaDataProvider
import com.android.systemui.media.controls.util.MediaUiEventLogger
import com.android.systemui.media.controls.util.fakeMediaControllerFactory
import com.android.systemui.media.controls.util.mediaFlags
-import com.android.systemui.plugins.activityStarter
import com.android.systemui.res.R
import com.android.systemui.statusbar.SbnBuilder
import com.android.systemui.statusbar.notificationLockscreenUserManager
import com.android.systemui.testKosmos
import com.android.systemui.util.concurrency.FakeExecutor
-import com.android.systemui.util.settings.fakeSettings
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -102,11 +95,9 @@ import org.mockito.ArgumentMatchers.anyBoolean
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Captor
import org.mockito.Mock
-import org.mockito.Mockito
import org.mockito.Mockito.never
import org.mockito.Mockito.reset
import org.mockito.Mockito.verify
-import org.mockito.Mockito.verifyNoMoreInteractions
import org.mockito.MockitoSession
import org.mockito.junit.MockitoJUnit
import org.mockito.kotlin.any
@@ -133,10 +124,6 @@ private const val SESSION_EMPTY_TITLE = ""
private const val USER_ID = 0
private val DISMISS_INTENT = Intent().apply { action = "dismiss" }
-private fun <T> anyObject(): T {
- return Mockito.anyObject<T>()
-}
-
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWithLooper(setAsMainLooper = true)
@@ -146,7 +133,6 @@ class MediaDataProcessorTest(flags: FlagsParameterization) : SysuiTestCase() {
private val kosmos = testKosmos().apply { mediaLogger = mockMediaLogger }
private val testDispatcher = kosmos.testDispatcher
private val testScope = kosmos.testScope
- private val settings = kosmos.fakeSettings
@JvmField @Rule val mockito = MockitoJUnit.rule()
@Mock lateinit var controller: MediaController
@@ -201,7 +187,6 @@ class MediaDataProcessorTest(flags: FlagsParameterization) : SysuiTestCase() {
}
private val fakeFeatureFlags = kosmos.fakeFeatureFlagsClassic
- private val activityStarter = kosmos.activityStarter
private val mediaControllerFactory = kosmos.fakeMediaControllerFactory
private val notificationLockscreenUserManager = kosmos.notificationLockscreenUserManager
private val mediaFilterRepository = kosmos.mediaFilterRepository
@@ -209,13 +194,6 @@ class MediaDataProcessorTest(flags: FlagsParameterization) : SysuiTestCase() {
private val instanceIdSequence = InstanceIdSequenceFake(1 shl 20)
- private val originalSmartspaceSetting =
- Settings.Secure.getInt(
- context.contentResolver,
- Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION,
- 1,
- )
-
private lateinit var staticMockSession: MockitoSession
@Before
@@ -231,11 +209,6 @@ class MediaDataProcessorTest(flags: FlagsParameterization) : SysuiTestCase() {
backgroundExecutor = FakeExecutor(clock)
uiExecutor = FakeExecutor(clock)
smartspaceMediaDataProvider = SmartspaceMediaDataProvider()
- Settings.Secure.putInt(
- context.contentResolver,
- Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION,
- 1,
- )
mediaDataProcessor =
MediaDataProcessor(
context = context,
@@ -248,12 +221,10 @@ class MediaDataProcessorTest(flags: FlagsParameterization) : SysuiTestCase() {
mediaControllerFactory = mediaControllerFactory,
broadcastDispatcher = broadcastDispatcher,
dumpManager = dumpManager,
- activityStarter = activityStarter,
smartspaceMediaDataProvider = smartspaceMediaDataProvider,
useMediaResumption = true,
useQsMediaPlayer = true,
systemClock = clock,
- secureSettings = settings,
mediaFlags = kosmos.mediaFlags,
logger = logger,
smartspaceManager = smartspaceManager,
@@ -355,11 +326,6 @@ class MediaDataProcessorTest(flags: FlagsParameterization) : SysuiTestCase() {
staticMockSession.finishMocking()
session.release()
mediaDataProcessor.destroy()
- Settings.Secure.putInt(
- context.contentResolver,
- Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION,
- originalSmartspaceSetting,
- )
}
@Test
@@ -1255,264 +1221,6 @@ class MediaDataProcessorTest(flags: FlagsParameterization) : SysuiTestCase() {
}
@Test
- fun testOnSmartspaceMediaDataLoaded_hasNewValidMediaTarget_callsListener() {
- smartspaceMediaDataProvider.onTargetsAvailable(listOf(mediaSmartspaceTarget))
- verify(logger).getNewInstanceId()
- val instanceId = instanceIdSequence.lastInstanceId
-
- verify(listener)
- .onSmartspaceMediaDataLoaded(
- eq(KEY_MEDIA_SMARTSPACE),
- eq(
- SmartspaceMediaData(
- targetId = KEY_MEDIA_SMARTSPACE,
- isActive = true,
- packageName = PACKAGE_NAME,
- cardAction = mediaSmartspaceBaseAction,
- recommendations = validRecommendationList,
- dismissIntent = DISMISS_INTENT,
- headphoneConnectionTimeMillis = SMARTSPACE_CREATION_TIME,
- instanceId = InstanceId.fakeInstanceId(instanceId),
- expiryTimeMs = SMARTSPACE_EXPIRY_TIME,
- )
- ),
- eq(false),
- )
- }
-
- @Test
- fun testOnSmartspaceMediaDataLoaded_hasNewInvalidMediaTarget_callsListener() {
- whenever(mediaSmartspaceTarget.iconGrid).thenReturn(listOf())
- smartspaceMediaDataProvider.onTargetsAvailable(listOf(mediaSmartspaceTarget))
- verify(logger).getNewInstanceId()
- val instanceId = instanceIdSequence.lastInstanceId
-
- verify(listener)
- .onSmartspaceMediaDataLoaded(
- eq(KEY_MEDIA_SMARTSPACE),
- eq(
- SmartspaceMediaData(
- targetId = KEY_MEDIA_SMARTSPACE,
- isActive = true,
- dismissIntent = DISMISS_INTENT,
- headphoneConnectionTimeMillis = SMARTSPACE_CREATION_TIME,
- instanceId = InstanceId.fakeInstanceId(instanceId),
- expiryTimeMs = SMARTSPACE_EXPIRY_TIME,
- )
- ),
- eq(false),
- )
- }
-
- @Test
- fun testOnSmartspaceMediaDataLoaded_hasNullIntent_callsListener() {
- val recommendationExtras =
- Bundle().apply {
- putString("package_name", PACKAGE_NAME)
- putParcelable("dismiss_intent", null)
- }
- whenever(mediaSmartspaceBaseAction.extras).thenReturn(recommendationExtras)
- whenever(mediaSmartspaceTarget.baseAction).thenReturn(mediaSmartspaceBaseAction)
- whenever(mediaSmartspaceTarget.iconGrid).thenReturn(listOf())
-
- smartspaceMediaDataProvider.onTargetsAvailable(listOf(mediaSmartspaceTarget))
- verify(logger).getNewInstanceId()
- val instanceId = instanceIdSequence.lastInstanceId
-
- verify(listener)
- .onSmartspaceMediaDataLoaded(
- eq(KEY_MEDIA_SMARTSPACE),
- eq(
- SmartspaceMediaData(
- targetId = KEY_MEDIA_SMARTSPACE,
- isActive = true,
- dismissIntent = null,
- headphoneConnectionTimeMillis = SMARTSPACE_CREATION_TIME,
- instanceId = InstanceId.fakeInstanceId(instanceId),
- expiryTimeMs = SMARTSPACE_EXPIRY_TIME,
- )
- ),
- eq(false),
- )
- }
-
- @Test
- fun testOnSmartspaceMediaDataLoaded_hasNoneMediaTarget_notCallsListener() {
- smartspaceMediaDataProvider.onTargetsAvailable(listOf())
- verify(logger, never()).getNewInstanceId()
- verify(listener, never())
- .onSmartspaceMediaDataLoaded(anyObject(), anyObject(), anyBoolean())
- }
-
- @Test
- fun testOnSmartspaceMediaDataLoaded_hasNoneMediaTarget_callsRemoveListener() {
- smartspaceMediaDataProvider.onTargetsAvailable(listOf(mediaSmartspaceTarget))
- verify(logger).getNewInstanceId()
-
- smartspaceMediaDataProvider.onTargetsAvailable(listOf())
- uiExecutor.advanceClockToLast()
- uiExecutor.runAllReady()
-
- verify(listener).onSmartspaceMediaDataRemoved(eq(KEY_MEDIA_SMARTSPACE), eq(false))
- verifyNoMoreInteractions(logger)
- }
-
- @Test
- fun testOnSmartspaceMediaDataLoaded_persistentEnabled_headphoneTrigger_isActive() {
- fakeFeatureFlags.set(MEDIA_RETAIN_RECOMMENDATIONS, true)
- smartspaceMediaDataProvider.onTargetsAvailable(listOf(mediaSmartspaceTarget))
- val instanceId = instanceIdSequence.lastInstanceId
-
- verify(listener)
- .onSmartspaceMediaDataLoaded(
- eq(KEY_MEDIA_SMARTSPACE),
- eq(
- SmartspaceMediaData(
- targetId = KEY_MEDIA_SMARTSPACE,
- isActive = true,
- packageName = PACKAGE_NAME,
- cardAction = mediaSmartspaceBaseAction,
- recommendations = validRecommendationList,
- dismissIntent = DISMISS_INTENT,
- headphoneConnectionTimeMillis = SMARTSPACE_CREATION_TIME,
- instanceId = InstanceId.fakeInstanceId(instanceId),
- expiryTimeMs = SMARTSPACE_EXPIRY_TIME,
- )
- ),
- eq(false),
- )
- }
-
- @Test
- fun testOnSmartspaceMediaDataLoaded_persistentEnabled_periodicTrigger_notActive() {
- fakeFeatureFlags.set(MEDIA_RETAIN_RECOMMENDATIONS, true)
- val extras =
- Bundle().apply {
- putString("package_name", PACKAGE_NAME)
- putParcelable("dismiss_intent", DISMISS_INTENT)
- putString(EXTRA_KEY_TRIGGER_SOURCE, EXTRA_VALUE_TRIGGER_PERIODIC)
- }
- whenever(mediaSmartspaceBaseAction.extras).thenReturn(extras)
-
- smartspaceMediaDataProvider.onTargetsAvailable(listOf(mediaSmartspaceTarget))
- val instanceId = instanceIdSequence.lastInstanceId
-
- verify(listener)
- .onSmartspaceMediaDataLoaded(
- eq(KEY_MEDIA_SMARTSPACE),
- eq(
- SmartspaceMediaData(
- targetId = KEY_MEDIA_SMARTSPACE,
- isActive = false,
- packageName = PACKAGE_NAME,
- cardAction = mediaSmartspaceBaseAction,
- recommendations = validRecommendationList,
- dismissIntent = DISMISS_INTENT,
- headphoneConnectionTimeMillis = SMARTSPACE_CREATION_TIME,
- instanceId = InstanceId.fakeInstanceId(instanceId),
- expiryTimeMs = SMARTSPACE_EXPIRY_TIME,
- )
- ),
- eq(false),
- )
- }
-
- @Test
- fun testOnSmartspaceMediaDataLoaded_persistentEnabled_noTargets_inactive() {
- fakeFeatureFlags.set(MEDIA_RETAIN_RECOMMENDATIONS, true)
-
- smartspaceMediaDataProvider.onTargetsAvailable(listOf(mediaSmartspaceTarget))
- val instanceId = instanceIdSequence.lastInstanceId
-
- smartspaceMediaDataProvider.onTargetsAvailable(listOf())
- uiExecutor.advanceClockToLast()
- uiExecutor.runAllReady()
-
- verify(listener)
- .onSmartspaceMediaDataLoaded(
- eq(KEY_MEDIA_SMARTSPACE),
- eq(
- SmartspaceMediaData(
- targetId = KEY_MEDIA_SMARTSPACE,
- isActive = false,
- packageName = PACKAGE_NAME,
- cardAction = mediaSmartspaceBaseAction,
- recommendations = validRecommendationList,
- dismissIntent = DISMISS_INTENT,
- headphoneConnectionTimeMillis = SMARTSPACE_CREATION_TIME,
- instanceId = InstanceId.fakeInstanceId(instanceId),
- expiryTimeMs = SMARTSPACE_EXPIRY_TIME,
- )
- ),
- eq(false),
- )
- verify(listener, never()).onSmartspaceMediaDataRemoved(eq(KEY_MEDIA_SMARTSPACE), eq(false))
- }
-
- @Test
- fun testSetRecommendationInactive_notifiesListeners() {
- fakeFeatureFlags.set(MEDIA_RETAIN_RECOMMENDATIONS, true)
-
- smartspaceMediaDataProvider.onTargetsAvailable(listOf(mediaSmartspaceTarget))
- val instanceId = instanceIdSequence.lastInstanceId
-
- mediaDataProcessor.setRecommendationInactive(KEY_MEDIA_SMARTSPACE)
- uiExecutor.advanceClockToLast()
- uiExecutor.runAllReady()
-
- verify(listener)
- .onSmartspaceMediaDataLoaded(
- eq(KEY_MEDIA_SMARTSPACE),
- eq(
- SmartspaceMediaData(
- targetId = KEY_MEDIA_SMARTSPACE,
- isActive = false,
- packageName = PACKAGE_NAME,
- cardAction = mediaSmartspaceBaseAction,
- recommendations = validRecommendationList,
- dismissIntent = DISMISS_INTENT,
- headphoneConnectionTimeMillis = SMARTSPACE_CREATION_TIME,
- instanceId = InstanceId.fakeInstanceId(instanceId),
- expiryTimeMs = SMARTSPACE_EXPIRY_TIME,
- )
- ),
- eq(false),
- )
- }
-
- @Test
- fun testOnSmartspaceMediaDataLoaded_settingDisabled_doesNothing() {
- // WHEN media recommendation setting is off
- settings.putInt(Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION, 0)
- testScope.runCurrent()
-
- smartspaceMediaDataProvider.onTargetsAvailable(listOf(mediaSmartspaceTarget))
-
- // THEN smartspace signal is ignored
- verify(listener, never())
- .onSmartspaceMediaDataLoaded(anyObject(), anyObject(), anyBoolean())
- }
-
- @Test
- fun testMediaRecommendationDisabled_removesSmartspaceData() {
- // GIVEN a media recommendation card is present
- smartspaceMediaDataProvider.onTargetsAvailable(listOf(mediaSmartspaceTarget))
- verify(listener)
- .onSmartspaceMediaDataLoaded(eq(KEY_MEDIA_SMARTSPACE), anyObject(), anyBoolean())
-
- // WHEN the media recommendation setting is turned off
- settings.putInt(Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION, 0)
- testScope.runCurrent()
-
- // THEN listeners are notified
- uiExecutor.advanceClockToLast()
- foregroundExecutor.advanceClockToLast()
- uiExecutor.runAllReady()
- foregroundExecutor.runAllReady()
- verify(listener).onSmartspaceMediaDataRemoved(eq(KEY_MEDIA_SMARTSPACE), eq(true))
- }
-
- @Test
fun testOnMediaDataChanged_updatesLastActiveTime() {
val currentTime = clock.elapsedRealtime()
addNotificationAndLoad()
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 38ddb3e426fa..a02d333d1507 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
@@ -30,7 +30,6 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyFloat;
-import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
@@ -1425,8 +1424,7 @@ public class ScrimControllerTest extends SysuiTestCase {
HashSet<ScrimState> regularStates = new HashSet<>(Arrays.asList(
ScrimState.UNINITIALIZED, ScrimState.KEYGUARD, BOUNCER,
ScrimState.DREAMING, ScrimState.BOUNCER_SCRIMMED, ScrimState.BRIGHTNESS_MIRROR,
- ScrimState.UNLOCKED, SHADE_LOCKED, ScrimState.AUTH_SCRIMMED,
- ScrimState.AUTH_SCRIMMED_SHADE, ScrimState.GLANCEABLE_HUB,
+ ScrimState.UNLOCKED, SHADE_LOCKED, ScrimState.GLANCEABLE_HUB,
ScrimState.GLANCEABLE_HUB_OVER_DREAM));
for (ScrimState state : ScrimState.values()) {
@@ -1451,79 +1449,6 @@ public class ScrimControllerTest extends SysuiTestCase {
}
@Test
- public void testAuthScrim_setClipQSScrimTrue_notifScrimOpaque_whenShadeFullyExpanded() {
- // GIVEN device has an activity showing ('UNLOCKED' state can occur on the lock screen
- // with the camera app occluding the keyguard)
- mScrimController.legacyTransitionTo(ScrimState.UNLOCKED);
- mScrimController.setClipsQsScrim(true);
- mScrimController.setRawPanelExpansionFraction(1);
- // notifications scrim alpha change require calling setQsPosition
- mScrimController.setQsPosition(0, 300);
- finishAnimationsImmediately();
-
- // WHEN the user triggers the auth bouncer
- mScrimController.legacyTransitionTo(ScrimState.AUTH_SCRIMMED_SHADE);
- finishAnimationsImmediately();
-
- assertEquals("Behind scrim should be opaque",
- mScrimBehind.getViewAlpha(), 1, 0.0);
- assertEquals("Notifications scrim should be opaque",
- mNotificationsScrim.getViewAlpha(), 1, 0.0);
-
- assertScrimTinted(Map.of(
- mScrimInFront, true,
- mScrimBehind, true,
- mNotificationsScrim, false
- ));
- }
-
-
- @Test
- public void testAuthScrim_setClipQSScrimFalse_notifScrimOpaque_whenShadeFullyExpanded() {
- // GIVEN device has an activity showing ('UNLOCKED' state can occur on the lock screen
- // with the camera app occluding the keyguard)
- mScrimController.legacyTransitionTo(ScrimState.UNLOCKED);
- mScrimController.setClipsQsScrim(false);
- mScrimController.setRawPanelExpansionFraction(1);
- // notifications scrim alpha change require calling setQsPosition
- mScrimController.setQsPosition(0, 300);
- finishAnimationsImmediately();
-
- // WHEN the user triggers the auth bouncer
- mScrimController.legacyTransitionTo(ScrimState.AUTH_SCRIMMED_SHADE);
- finishAnimationsImmediately();
-
- assertEquals("Behind scrim should be opaque",
- mScrimBehind.getViewAlpha(), 1, 0.0);
- assertEquals("Notifications scrim should be opaque",
- mNotificationsScrim.getViewAlpha(), 1, 0.0);
-
- assertScrimTinted(Map.of(
- mScrimInFront, true,
- mScrimBehind, true,
- mNotificationsScrim, false
- ));
- }
-
- @Test
- public void testAuthScrimKeyguard() {
- // GIVEN device is on the keyguard
- mScrimController.legacyTransitionTo(ScrimState.KEYGUARD);
- finishAnimationsImmediately();
-
- // WHEN the user triggers the auth bouncer
- mScrimController.legacyTransitionTo(ScrimState.AUTH_SCRIMMED);
- finishAnimationsImmediately();
-
- // THEN the front scrim is updated and the KEYGUARD scrims are the same as the
- // KEYGUARD scrim state
- assertScrimAlpha(Map.of(
- mScrimInFront, SEMI_TRANSPARENT,
- mScrimBehind, SEMI_TRANSPARENT,
- mNotificationsScrim, TRANSPARENT));
- }
-
- @Test
public void testScrimsVisible_whenShadeVisible() {
mScrimController.setClipsQsScrim(true);
mScrimController.legacyTransitionTo(ScrimState.UNLOCKED);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wallet/controller/QuickAccessWalletControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/wallet/controller/QuickAccessWalletControllerTest.java
index 733e2edaec84..8e97c86ba507 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wallet/controller/QuickAccessWalletControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wallet/controller/QuickAccessWalletControllerTest.java
@@ -17,6 +17,7 @@
package com.android.systemui.wallet.controller;
import static android.service.quickaccesswallet.Flags.FLAG_LAUNCH_WALLET_OPTION_ON_POWER_DOUBLE_TAP;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotSame;
@@ -35,6 +36,7 @@ import static org.mockito.Mockito.when;
import android.app.PendingIntent;
import android.app.role.RoleManager;
import android.content.Intent;
+import android.os.UserHandle;
import android.platform.test.annotations.EnableFlags;
import android.service.quickaccesswallet.GetWalletCardsRequest;
import android.service.quickaccesswallet.QuickAccessWalletClient;
@@ -59,7 +61,6 @@ import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
-import org.mockito.Spy;
import java.util.List;
@@ -98,6 +99,7 @@ public class QuickAccessWalletControllerTest extends SysuiTestCase {
when(mQuickAccessWalletClient.isWalletServiceAvailable()).thenReturn(true);
when(mQuickAccessWalletClient.isWalletFeatureAvailable()).thenReturn(true);
when(mQuickAccessWalletClient.isWalletFeatureAvailableWhenDeviceLocked()).thenReturn(true);
+ when(mQuickAccessWalletClient.getUser()).thenReturn(UserHandle.of(0));
mClock.setElapsedRealtime(100L);
doAnswer(invocation -> {
@@ -269,7 +271,8 @@ public class QuickAccessWalletControllerTest extends SysuiTestCase {
public void getQuickAccessUiIntent_noCards_noPendingIntent_startsWalletActivity() {
mController.startQuickAccessUiIntent(mActivityStarter, mAnimationController, false);
verify(mActivityStarter).postStartActivityDismissingKeyguard(mIntentCaptor.capture(), eq(0),
- any(ActivityTransitionAnimator.Controller.class));
+ any(ActivityTransitionAnimator.Controller.class), eq(null),
+ eq(UserHandle.of(0)));
Intent intent = mIntentCaptor.getValue();
assertEquals(intent.getAction(), Intent.ACTION_VIEW);
assertEquals(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
index 7508838810ad..fab7922f58e7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -358,6 +358,8 @@ public class BubblesTest extends SysuiTestCase {
private Display mDefaultDisplay;
@Mock
private Lazy<ViewCapture> mLazyViewCapture;
+ @Mock
+ private SyncTransactionQueue mSyncQueue;
private final KosmosJavaAdapter mKosmos = new KosmosJavaAdapter(this);
private ShadeInteractor mShadeInteractor;
@@ -400,8 +402,6 @@ public class BubblesTest extends SysuiTestCase {
if (Transitions.ENABLE_SHELL_TRANSITIONS) {
doReturn(true).when(mTransitions).isRegistered();
}
- mTaskViewRepository = new TaskViewRepository();
- mTaskViewTransitions = new TaskViewTransitions(mTransitions, mTaskViewRepository);
mTestableLooper = TestableLooper.get(this);
@@ -518,6 +518,9 @@ public class BubblesTest extends SysuiTestCase {
Optional.empty(),
Optional.empty(),
syncExecutor);
+ mTaskViewRepository = new TaskViewRepository();
+ mTaskViewTransitions = new TaskViewTransitions(mTransitions, mTaskViewRepository,
+ mShellTaskOrganizer, mSyncQueue);
mBubbleProperties = new FakeBubbleProperties();
mBubbleController = new TestableBubbleController(
mContext,
@@ -542,6 +545,7 @@ public class BubblesTest extends SysuiTestCase {
mock(DragAndDropController.class),
syncExecutor,
mock(Handler.class),
+ mTaskViewRepository,
mTaskViewTransitions,
mTransitions,
mock(SyncTransactionQueue.class),
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorKosmos.kt
index 3b1199a10531..ba64ed78f77c 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorKosmos.kt
@@ -18,6 +18,7 @@ package com.android.systemui.keyguard.domain.interactor
import android.app.admin.devicePolicyManager
import android.content.applicationContext
+import android.view.accessibility.AccessibilityManager
import com.android.internal.widget.lockPatternUtils
import com.android.keyguard.logging.KeyguardQuickAffordancesLogger
import com.android.systemui.animation.dialogTransitionAnimator
@@ -54,6 +55,7 @@ var Kosmos.keyguardQuickAffordanceInteractor by Fixture {
dockManager = dockManager,
biometricSettingsRepository = biometricSettingsRepository,
communalSettingsInteractor = communalSettingsInteractor,
+ accessibilityManager = mock<AccessibilityManager>(),
backgroundDispatcher = testDispatcher,
appContext = applicationContext,
sceneInteractor = { sceneInteractor },
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 abbfa93edd17..1c0f97d294df 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
@@ -56,9 +56,11 @@ val Kosmos.keyguardRootViewModel by Fixture {
aodToGoneTransitionViewModel = aodToGoneTransitionViewModel,
aodToLockscreenTransitionViewModel = aodToLockscreenTransitionViewModel,
aodToOccludedTransitionViewModel = aodToOccludedTransitionViewModel,
+ aodToPrimaryBouncerTransitionViewModel = aodToPrimaryBouncerTransitionViewModel,
dozingToGoneTransitionViewModel = dozingToGoneTransitionViewModel,
dozingToLockscreenTransitionViewModel = dozingToLockscreenTransitionViewModel,
dozingToOccludedTransitionViewModel = dozingToOccludedTransitionViewModel,
+ dozingToPrimaryBouncerTransitionViewModel = dozingToPrimaryBouncerTransitionViewModel,
dreamingToAodTransitionViewModel = dreamingToAodTransitionViewModel,
dreamingToGoneTransitionViewModel = dreamingToGoneTransitionViewModel,
dreamingToLockscreenTransitionViewModel = dreamingToLockscreenTransitionViewModel,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LightRevealScrimViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LightRevealScrimViewModelKosmos.kt
new file mode 100644
index 000000000000..93da1f3300f5
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LightRevealScrimViewModelKosmos.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.keyguard.ui.viewmodel
+
+import com.android.systemui.keyguard.domain.interactor.lightRevealScrimInteractor
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+
+val Kosmos.lightRevealScrimViewModel by Fixture {
+ LightRevealScrimViewModel(interactor = lightRevealScrimInteractor)
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorKosmos.kt
index 174e6532abcf..fcaad6bb28ea 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorKosmos.kt
@@ -31,9 +31,7 @@ import com.android.systemui.media.controls.shared.model.SmartspaceMediaDataProvi
import com.android.systemui.media.controls.util.fakeMediaControllerFactory
import com.android.systemui.media.controls.util.mediaFlags
import com.android.systemui.media.controls.util.mediaUiEventLogger
-import com.android.systemui.plugins.activityStarter
import com.android.systemui.util.Utils
-import com.android.systemui.util.settings.fakeSettings
import com.android.systemui.util.time.systemClock
val Kosmos.mediaDataProcessor by
@@ -49,12 +47,10 @@ val Kosmos.mediaDataProcessor by
mediaControllerFactory = fakeMediaControllerFactory,
broadcastDispatcher = broadcastDispatcher,
dumpManager = dumpManager,
- activityStarter = activityStarter,
smartspaceMediaDataProvider = SmartspaceMediaDataProvider(),
useMediaResumption = Utils.useMediaResumption(applicationContext),
useQsMediaPlayer = Utils.useQsMediaPlayer(applicationContext),
systemClock = systemClock,
- secureSettings = fakeSettings,
mediaFlags = mediaFlags,
logger = mediaUiEventLogger,
smartspaceManager = SmartspaceManager(applicationContext),
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractorKosmos.kt
index 3c37101cfe66..13cbddff8803 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractorKosmos.kt
@@ -20,6 +20,7 @@ import com.android.systemui.kosmos.Kosmos
import com.android.systemui.qs.tiles.base.actions.qsTileIntentUserInputHandler
import com.android.systemui.statusbar.policy.domain.interactor.zenModeInteractor
import com.android.systemui.statusbar.policy.ui.dialog.modesDialogDelegate
+import com.android.systemui.statusbar.policy.ui.dialog.modesDialogEventLogger
import javax.inject.Provider
val Kosmos.modesTileUserActionInteractor: ModesTileUserActionInteractor by
@@ -28,5 +29,6 @@ val Kosmos.modesTileUserActionInteractor: ModesTileUserActionInteractor by
qsTileIntentUserInputHandler,
Provider { modesDialogDelegate }.get(),
zenModeInteractor,
+ modesDialogEventLogger,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneKosmos.kt
index 0eca818e9aac..609f97d0b249 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneKosmos.kt
@@ -4,6 +4,7 @@ import android.view.View
import com.android.compose.animation.scene.ObservableTransitionState
import com.android.systemui.classifier.domain.interactor.falsingInteractor
import com.android.systemui.haptics.msdl.msdlPlayer
+import com.android.systemui.keyguard.ui.viewmodel.lightRevealScrimViewModel
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
import com.android.systemui.power.domain.interactor.powerInteractor
@@ -19,6 +20,7 @@ import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel
import com.android.systemui.scene.ui.viewmodel.splitEdgeDetector
import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.statusbar.domain.interactor.remoteInputInteractor
+import com.android.systemui.wallpapers.ui.viewmodel.wallpaperViewModel
import kotlinx.coroutines.flow.MutableStateFlow
import org.mockito.kotlin.mock
@@ -96,6 +98,8 @@ val Kosmos.sceneContainerViewModelFactory by Fixture {
hapticsViewModelFactory = sceneContainerHapticsViewModelFactory,
view = view,
motionEventHandlerReceiver = motionEventHandlerReceiver,
+ lightRevealScrim = lightRevealScrimViewModel,
+ wallpaperViewModel = wallpaperViewModel,
)
}
}
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 60e092c9709b..8461da77796d 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
@@ -28,9 +28,11 @@ import com.android.systemui.keyguard.ui.viewmodel.aodBurnInViewModel
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.dozingToGlanceableHubTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.dozingToLockscreenTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.dozingToOccludedTransitionViewModel
+import com.android.systemui.keyguard.ui.viewmodel.dozingToPrimaryBouncerTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.dreamingToLockscreenTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.glanceableHubToLockscreenTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.goneToAodTransitionViewModel
@@ -78,9 +80,11 @@ val Kosmos.sharedNotificationContainerViewModel by Fixture {
aodToGoneTransitionViewModel = aodToGoneTransitionViewModel,
aodToLockscreenTransitionViewModel = aodToLockscreenTransitionViewModel,
aodToOccludedTransitionViewModel = aodToOccludedTransitionViewModel,
+ aodToPrimaryBouncerTransitionViewModel = aodToPrimaryBouncerTransitionViewModel,
dozingToGlanceableHubTransitionViewModel = dozingToGlanceableHubTransitionViewModel,
dozingToLockscreenTransitionViewModel = dozingToLockscreenTransitionViewModel,
dozingToOccludedTransitionViewModel = dozingToOccludedTransitionViewModel,
+ dozingToPrimaryBouncerTransitionViewModel = dozingToPrimaryBouncerTransitionViewModel,
dreamingToLockscreenTransitionViewModel = dreamingToLockscreenTransitionViewModel,
goneToAodTransitionViewModel = goneToAodTransitionViewModel,
goneToDozingTransitionViewModel = goneToDozingTransitionViewModel,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelKosmos.kt
index db7e31bb2cb6..f8bf3c3fbbd9 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelKosmos.kt
@@ -21,6 +21,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.log.table.tableLogBufferFactory
import com.android.systemui.scene.domain.interactor.sceneContainerOcclusionInteractor
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.shade.domain.interactor.shadeInteractor
@@ -39,6 +40,7 @@ var Kosmos.homeStatusBarViewModel: HomeStatusBarViewModel by
Kosmos.Fixture {
HomeStatusBarViewModelImpl(
testableContext.displayId,
+ tableLogBufferFactory,
homeStatusBarInteractor,
homeStatusBarIconBlockListInteractor,
lightsOutInteractor,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegateKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegateKosmos.kt
index 6c98d19db5d7..ef043e0177a5 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegateKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegateKosmos.kt
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.policy.ui.dialog
+import android.content.mockedContext
import com.android.systemui.animation.dialogTransitionAnimator
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.mainCoroutineContext
@@ -30,6 +31,7 @@ val Kosmos.mockModesDialogDelegate by Kosmos.Fixture { mock<ModesDialogDelegate>
var Kosmos.modesDialogDelegate: ModesDialogDelegate by
Kosmos.Fixture {
ModesDialogDelegate(
+ mockedContext,
systemUIDialogFactory,
dialogTransitionAnimator,
activityStarter,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/wallpapers/data/repository/WallpaperRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/wallpapers/data/repository/WallpaperRepositoryKosmos.kt
index 1d8c891679aa..ddb9a3ffee6d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/wallpapers/data/repository/WallpaperRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/wallpapers/data/repository/WallpaperRepositoryKosmos.kt
@@ -26,13 +26,11 @@ import com.android.systemui.kosmos.Kosmos.Fixture
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.kosmos.testScope
import com.android.systemui.user.data.repository.userRepository
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-@OptIn(ExperimentalCoroutinesApi::class)
val Kosmos.wallpaperRepository by Fixture {
WallpaperRepositoryImpl(
context = applicationContext,
- scope = testScope,
+ scope = testScope.backgroundScope,
bgDispatcher = testDispatcher,
broadcastDispatcher = broadcastDispatcher,
userRepository = userRepository,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/wallpapers/ui/viewmodel/WallpaperViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/wallpapers/ui/viewmodel/WallpaperViewModelKosmos.kt
new file mode 100644
index 000000000000..bb6596e697a1
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/wallpapers/ui/viewmodel/WallpaperViewModelKosmos.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.wallpapers.ui.viewmodel
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.wallpapers.domain.interactor.wallpaperInteractor
+
+val Kosmos.wallpaperViewModel by Fixture { WallpaperViewModel(interactor = wallpaperInteractor) }
diff --git a/packages/SystemUI/utils/Android.bp b/packages/SystemUI/utils/Android.bp
index 1ef381617c20..1efb11b436ff 100644
--- a/packages/SystemUI/utils/Android.bp
+++ b/packages/SystemUI/utils/Android.bp
@@ -29,4 +29,5 @@ java_library {
"kotlin-stdlib",
"kotlinx_coroutines",
],
+ kotlincflags: ["-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi"],
}
diff --git a/packages/SystemUI/utils/src/com/android/systemui/utils/coroutines/flow/LatestConflated.kt b/packages/SystemUI/utils/src/com/android/systemui/utils/coroutines/flow/LatestConflated.kt
index 5f8c66078483..4cfb7d5f6157 100644
--- a/packages/SystemUI/utils/src/com/android/systemui/utils/coroutines/flow/LatestConflated.kt
+++ b/packages/SystemUI/utils/src/com/android/systemui/utils/coroutines/flow/LatestConflated.kt
@@ -14,12 +14,11 @@
* limitations under the License.
*/
-@file:OptIn(ExperimentalCoroutinesApi::class, ExperimentalTypeInference::class)
+@file:OptIn(ExperimentalTypeInference::class)
package com.android.systemui.utils.coroutines.flow
import kotlin.experimental.ExperimentalTypeInference
-import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.FlowCollector
import kotlinx.coroutines.flow.conflate
diff --git a/ravenwood/texts/ravenwood-annotation-allowed-classes.txt b/ravenwood/texts/ravenwood-annotation-allowed-classes.txt
index f8315fe1e55f..383e75bb5122 100644
--- a/ravenwood/texts/ravenwood-annotation-allowed-classes.txt
+++ b/ravenwood/texts/ravenwood-annotation-allowed-classes.txt
@@ -115,6 +115,7 @@ android.util.UtilConfig
android.util.Xml
android.util.proto.EncodedBuffer
+android.util.proto.ProtoFieldFilter
android.util.proto.ProtoInputStream
android.util.proto.ProtoOutputStream
android.util.proto.ProtoParseException
diff --git a/ravenwood/texts/ravenwood-standard-options.txt b/ravenwood/texts/ravenwood-standard-options.txt
index 3ec3e3ce2946..27223d8b72ff 100644
--- a/ravenwood/texts/ravenwood-standard-options.txt
+++ b/ravenwood/texts/ravenwood-standard-options.txt
@@ -5,6 +5,8 @@
# Keep all classes / methods / fields, but make the methods throw.
--default-throw
+--delete-finals
+
# Uncomment below lines to enable each feature.
#--default-method-call-hook
diff --git a/ravenwood/tools/hoststubgen/hoststubgen-standard-options.txt b/ravenwood/tools/hoststubgen/hoststubgen-standard-options.txt
index 001943c18d6b..9c46a1646560 100644
--- a/ravenwood/tools/hoststubgen/hoststubgen-standard-options.txt
+++ b/ravenwood/tools/hoststubgen/hoststubgen-standard-options.txt
@@ -2,6 +2,8 @@
--debug
+--delete-finals
+
# Uncomment below lines to enable each feature.
#--default-method-call-hook
diff --git a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt
index cc704b2b32ed..985947575a86 100644
--- a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt
+++ b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt
@@ -411,6 +411,8 @@ class HostStubGen(val options: HostStubGenOptions) {
stats = stats,
enablePreTrace = options.enablePreTrace.get,
enablePostTrace = options.enablePostTrace.get,
+ deleteClassFinals = options.deleteFinals.get,
+ deleteMethodFinals = options.deleteFinals.get,
)
outVisitor = BaseAdapter.getVisitor(
classInternalName, classes, outVisitor, filter,
diff --git a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/HostStubGenOptions.kt b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/HostStubGenOptions.kt
index 55e853e3e2fb..ae9276f711e1 100644
--- a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/HostStubGenOptions.kt
+++ b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/HostStubGenOptions.kt
@@ -106,6 +106,8 @@ class HostStubGenOptions(
var cleanUpOnError: SetOnce<Boolean> = SetOnce(false),
+ var deleteFinals: SetOnce<Boolean> = SetOnce(false),
+
var enableClassChecker: SetOnce<Boolean> = SetOnce(false),
var enablePreTrace: SetOnce<Boolean> = SetOnce(false),
var enablePostTrace: SetOnce<Boolean> = SetOnce(false),
@@ -218,6 +220,8 @@ class HostStubGenOptions(
"--gen-keep-all-file" ->
ret.inputJarAsKeepAllFile.set(nextArg())
+ "--delete-finals" -> ret.deleteFinals.set(true)
+
// Following options are for debugging.
"--enable-class-checker" -> ret.enableClassChecker.set(true)
"--no-class-checker" -> ret.enableClassChecker.set(false)
@@ -293,6 +297,7 @@ class HostStubGenOptions(
defaultMethodCallHook=$defaultMethodCallHook,
policyOverrideFiles=${policyOverrideFiles.toTypedArray().contentToString()},
defaultPolicy=$defaultPolicy,
+ deleteFinals=$deleteFinals,
cleanUpOnError=$cleanUpOnError,
enableClassChecker=$enableClassChecker,
enablePreTrace=$enablePreTrace,
diff --git a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/visitors/BaseAdapter.kt b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/visitors/BaseAdapter.kt
index 261ef59c45c7..a08d1d605949 100644
--- a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/visitors/BaseAdapter.kt
+++ b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/visitors/BaseAdapter.kt
@@ -50,7 +50,13 @@ abstract class BaseAdapter(
val errors: HostStubGenErrors,
val stats: HostStubGenStats?,
val enablePreTrace: Boolean,
- val enablePostTrace: Boolean
+ val enablePostTrace: Boolean,
+ val deleteClassFinals: Boolean,
+ val deleteMethodFinals: Boolean,
+ // We don't remove finals from fields, because final fields have a stronger memory
+ // guarantee than non-final fields, see:
+ // https://docs.oracle.com/javase/specs/jls/se22/html/jls-17.html#jls-17.5
+ // i.e. changing a final field to non-final _could_ result in different behavior.
)
protected lateinit var currentPackageName: String
@@ -58,14 +64,33 @@ abstract class BaseAdapter(
protected var redirectionClass: String? = null
protected lateinit var classPolicy: FilterPolicyWithReason
+ private fun isEnum(access: Int): Boolean {
+ return (access and Opcodes.ACC_ENUM) != 0
+ }
+
+ protected fun modifyClassAccess(access: Int): Int {
+ if (options.deleteClassFinals && !isEnum(access)) {
+ return access and Opcodes.ACC_FINAL.inv()
+ }
+ return access
+ }
+
+ protected fun modifyMethodAccess(access: Int): Int {
+ if (options.deleteMethodFinals) {
+ return access and Opcodes.ACC_FINAL.inv()
+ }
+ return access
+ }
+
override fun visit(
version: Int,
- access: Int,
+ origAccess: Int,
name: String,
signature: String?,
superName: String?,
interfaces: Array<String>,
) {
+ val access = modifyClassAccess(origAccess)
super.visit(version, access, name, signature, superName, interfaces)
currentClassName = name
currentPackageName = getPackageNameFromFullClassName(name)
@@ -130,13 +155,14 @@ abstract class BaseAdapter(
}
}
- override fun visitMethod(
- access: Int,
+ final override fun visitMethod(
+ origAccess: Int,
name: String,
descriptor: String,
signature: String?,
exceptions: Array<String>?,
): MethodVisitor? {
+ val access = modifyMethodAccess(origAccess)
if (skipMemberModificationNestCount > 0) {
return super.visitMethod(access, name, descriptor, signature, exceptions)
}
@@ -176,6 +202,7 @@ abstract class BaseAdapter(
if (newAccess == NOT_COMPATIBLE) {
return null
}
+ newAccess = modifyMethodAccess(newAccess)
log.v(
"Emitting %s.%s%s as %s %s", currentClassName, name, descriptor,
diff --git a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/visitors/ImplGeneratingAdapter.kt b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/visitors/ImplGeneratingAdapter.kt
index 567a69e43b58..70e7d46bb6cd 100644
--- a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/visitors/ImplGeneratingAdapter.kt
+++ b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/visitors/ImplGeneratingAdapter.kt
@@ -51,12 +51,13 @@ class ImplGeneratingAdapter(
override fun visit(
version: Int,
- access: Int,
+ origAccess: Int,
name: String,
signature: String?,
superName: String?,
interfaces: Array<String>
) {
+ val access = modifyClassAccess(origAccess)
super.visit(version, access, name, signature, superName, interfaces)
classLoadHooks = filter.getClassLoadHooks(currentClassName)
diff --git a/ravenwood/tools/hoststubgen/test-tiny-framework/golden-output.RELEASE_TARGET_JAVA_21/01-hoststubgen-test-tiny-framework-orig-dump.txt b/ravenwood/tools/hoststubgen/test-tiny-framework/golden-output.RELEASE_TARGET_JAVA_21/01-hoststubgen-test-tiny-framework-orig-dump.txt
index 5e5ca62b49f0..b009b0957919 100644
--- a/ravenwood/tools/hoststubgen/test-tiny-framework/golden-output.RELEASE_TARGET_JAVA_21/01-hoststubgen-test-tiny-framework-orig-dump.txt
+++ b/ravenwood/tools/hoststubgen/test-tiny-framework/golden-output.RELEASE_TARGET_JAVA_21/01-hoststubgen-test-tiny-framework-orig-dump.txt
@@ -7,6 +7,8 @@ public interface android.hosttest.annotation.HostSideTestClassLoadHook extends j
this_class: #x // android/hosttest/annotation/HostSideTestClassLoadHook
super_class: #x // java/lang/Object
interfaces: 1, fields: 0, methods: 1, attributes: 2
+Constant pool:
+{
public abstract java.lang.String value();
descriptor: ()Ljava/lang/String;
flags: (0x0401) ACC_PUBLIC, ACC_ABSTRACT
@@ -30,6 +32,8 @@ public interface android.hosttest.annotation.HostSideTestIgnore extends java.lan
this_class: #x // android/hosttest/annotation/HostSideTestIgnore
super_class: #x // java/lang/Object
interfaces: 1, fields: 0, methods: 0, attributes: 2
+Constant pool:
+{
}
SourceFile: "HostSideTestIgnore.java"
RuntimeVisibleAnnotations:
@@ -50,6 +54,8 @@ public interface android.hosttest.annotation.HostSideTestKeep extends java.lang.
this_class: #x // android/hosttest/annotation/HostSideTestKeep
super_class: #x // java/lang/Object
interfaces: 1, fields: 0, methods: 0, attributes: 2
+Constant pool:
+{
}
SourceFile: "HostSideTestKeep.java"
RuntimeVisibleAnnotations:
@@ -70,6 +76,8 @@ public interface android.hosttest.annotation.HostSideTestRedirect extends java.l
this_class: #x // android/hosttest/annotation/HostSideTestRedirect
super_class: #x // java/lang/Object
interfaces: 1, fields: 0, methods: 0, attributes: 2
+Constant pool:
+{
}
SourceFile: "HostSideTestRedirect.java"
RuntimeVisibleAnnotations:
@@ -90,6 +98,8 @@ public interface android.hosttest.annotation.HostSideTestRedirectionClass extend
this_class: #x // android/hosttest/annotation/HostSideTestRedirectionClass
super_class: #x // java/lang/Object
interfaces: 1, fields: 0, methods: 1, attributes: 2
+Constant pool:
+{
public abstract java.lang.String value();
descriptor: ()Ljava/lang/String;
flags: (0x0401) ACC_PUBLIC, ACC_ABSTRACT
@@ -113,6 +123,8 @@ public interface android.hosttest.annotation.HostSideTestRemove extends java.lan
this_class: #x // android/hosttest/annotation/HostSideTestRemove
super_class: #x // java/lang/Object
interfaces: 1, fields: 0, methods: 0, attributes: 2
+Constant pool:
+{
}
SourceFile: "HostSideTestRemove.java"
RuntimeVisibleAnnotations:
@@ -133,6 +145,8 @@ public interface android.hosttest.annotation.HostSideTestStaticInitializerKeep e
this_class: #x // android/hosttest/annotation/HostSideTestStaticInitializerKeep
super_class: #x // java/lang/Object
interfaces: 1, fields: 0, methods: 0, attributes: 2
+Constant pool:
+{
}
SourceFile: "HostSideTestStaticInitializerKeep.java"
RuntimeVisibleAnnotations:
@@ -153,6 +167,8 @@ public interface android.hosttest.annotation.HostSideTestSubstitute extends java
this_class: #x // android/hosttest/annotation/HostSideTestSubstitute
super_class: #x // java/lang/Object
interfaces: 1, fields: 0, methods: 1, attributes: 2
+Constant pool:
+{
public abstract java.lang.String suffix();
descriptor: ()Ljava/lang/String;
flags: (0x0401) ACC_PUBLIC, ACC_ABSTRACT
@@ -176,6 +192,8 @@ public interface android.hosttest.annotation.HostSideTestThrow extends java.lang
this_class: #x // android/hosttest/annotation/HostSideTestThrow
super_class: #x // java/lang/Object
interfaces: 1, fields: 0, methods: 0, attributes: 2
+Constant pool:
+{
}
SourceFile: "HostSideTestThrow.java"
RuntimeVisibleAnnotations:
@@ -196,6 +214,8 @@ public interface android.hosttest.annotation.HostSideTestWholeClassKeep extends
this_class: #x // android/hosttest/annotation/HostSideTestWholeClassKeep
super_class: #x // java/lang/Object
interfaces: 1, fields: 0, methods: 0, attributes: 2
+Constant pool:
+{
}
SourceFile: "HostSideTestWholeClassKeep.java"
RuntimeVisibleAnnotations:
@@ -216,6 +236,8 @@ public interface android.hosttest.annotation.tests.HostSideTestSuppress extends
this_class: #x // android/hosttest/annotation/tests/HostSideTestSuppress
super_class: #x // java/lang/Object
interfaces: 1, fields: 0, methods: 0, attributes: 2
+Constant pool:
+{
}
SourceFile: "HostSideTestSuppress.java"
RuntimeVisibleAnnotations:
@@ -232,6 +254,8 @@ public class com.android.hoststubgen.test.tinyframework.IPretendingAidl$Stub$Pro
this_class: #x // com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub$Proxy
super_class: #x // java/lang/Object
interfaces: 0, fields: 0, methods: 2, attributes: 3
+Constant pool:
+{
public com.android.hoststubgen.test.tinyframework.IPretendingAidl$Stub$Proxy();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
@@ -273,6 +297,8 @@ public class com.android.hoststubgen.test.tinyframework.IPretendingAidl$Stub
this_class: #x // com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub
super_class: #x // java/lang/Object
interfaces: 0, fields: 0, methods: 2, attributes: 3
+Constant pool:
+{
public com.android.hoststubgen.test.tinyframework.IPretendingAidl$Stub();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
@@ -314,6 +340,8 @@ public interface com.android.hoststubgen.test.tinyframework.IPretendingAidl
this_class: #x // com/android/hoststubgen/test/tinyframework/IPretendingAidl
super_class: #x // java/lang/Object
interfaces: 0, fields: 0, methods: 0, attributes: 3
+Constant pool:
+{
}
SourceFile: "IPretendingAidl.java"
NestMembers:
@@ -331,6 +359,8 @@ public class com.android.hoststubgen.test.tinyframework.R$Nested
this_class: #x // com/android/hoststubgen/test/tinyframework/R$Nested
super_class: #x // java/lang/Object
interfaces: 0, fields: 1, methods: 2, attributes: 3
+Constant pool:
+{
public static int[] ARRAY;
descriptor: [I
flags: (0x0009) ACC_PUBLIC, ACC_STATIC
@@ -376,6 +406,8 @@ public class com.android.hoststubgen.test.tinyframework.R
this_class: #x // com/android/hoststubgen/test/tinyframework/R
super_class: #x // java/lang/Object
interfaces: 0, fields: 0, methods: 1, attributes: 3
+Constant pool:
+{
public com.android.hoststubgen.test.tinyframework.R();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
@@ -396,13 +428,15 @@ InnerClasses:
public static #x= #x of #x; // Nested=class com/android/hoststubgen/test/tinyframework/R$Nested of class com/android/hoststubgen/test/tinyframework/R
## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotations.class
Compiled from "TinyFrameworkAnnotations.java"
-public class com.android.hoststubgen.test.tinyframework.TinyFrameworkAnnotations
+public final class com.android.hoststubgen.test.tinyframework.TinyFrameworkAnnotations
minor version: 0
major version: 65
- flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+ flags: (0x0031) ACC_PUBLIC, ACC_FINAL, ACC_SUPER
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotations
super_class: #x // java/lang/Object
interfaces: 0, fields: 2, methods: 9, attributes: 2
+Constant pool:
+{
public int keep;
descriptor: I
flags: (0x0001) ACC_PUBLIC
@@ -433,9 +467,9 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkAnnotations
x: #x()
android.hosttest.annotation.HostSideTestKeep
- public int addOne(int);
+ public final int addOne(int);
descriptor: (I)I
- flags: (0x0001) ACC_PUBLIC
+ flags: (0x0011) ACC_PUBLIC, ACC_FINAL
Code:
stack=2, locals=2, args_size=2
x: iload_1
@@ -505,18 +539,18 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkAnnotations
0 4 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotations;
0 4 1 value I
- public static native int nativeAddThree(int);
+ public static final native int nativeAddThree(int);
descriptor: (I)I
- flags: (0x0109) ACC_PUBLIC, ACC_STATIC, ACC_NATIVE
+ flags: (0x0119) ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_NATIVE
RuntimeInvisibleAnnotations:
x: #x(#x=s#x)
android.hosttest.annotation.HostSideTestSubstitute(
suffix="_host"
)
- private static int nativeAddThree_host(int);
+ private static final int nativeAddThree_host(int);
descriptor: (I)I
- flags: (0x000a) ACC_PRIVATE, ACC_STATIC
+ flags: (0x001a) ACC_PRIVATE, ACC_STATIC, ACC_FINAL
Code:
stack=2, locals=1, args_size=1
x: iload_0
@@ -578,6 +612,8 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHo
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkClassLoadHook
super_class: #x // java/lang/Object
interfaces: 0, fields: 1, methods: 3, attributes: 2
+Constant pool:
+{
public static final java.util.Set<java.lang.Class<?>> sLoadedClasses;
descriptor: Ljava/util/Set;
flags: (0x0019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL
@@ -640,6 +676,8 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassWideAn
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWideAnnotations
super_class: #x // java/lang/Object
interfaces: 0, fields: 2, methods: 6, attributes: 2
+Constant pool:
+{
public int keep;
descriptor: I
flags: (0x0001) ACC_PUBLIC
@@ -764,6 +802,8 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassWithIn
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWithInitializerDefault
super_class: #x // java/lang/Object
interfaces: 0, fields: 2, methods: 2, attributes: 2
+Constant pool:
+{
public static boolean sInitialized;
descriptor: Z
flags: (0x0009) ACC_PUBLIC, ACC_STATIC
@@ -818,6 +858,8 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassWithIn
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWithInitializerStub
super_class: #x // java/lang/Object
interfaces: 0, fields: 2, methods: 2, attributes: 2
+Constant pool:
+{
public static boolean sInitialized;
descriptor: Z
flags: (0x0009) ACC_PUBLIC, ACC_STATIC
@@ -878,6 +920,8 @@ public final class com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumC
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex
super_class: #x // java/lang/Enum
interfaces: 0, fields: 6, methods: 7, attributes: 3
+Constant pool:
+{
public static final com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumComplex RED;
descriptor: Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
flags: (0x4019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM
@@ -1081,6 +1125,8 @@ public final class com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumS
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple
super_class: #x // java/lang/Enum
interfaces: 0, fields: 3, methods: 5, attributes: 3
+Constant pool:
+{
public static final com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumSimple CAT;
descriptor: Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;
flags: (0x4019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM
@@ -1202,6 +1248,8 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkExceptionTe
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkExceptionTester
super_class: #x // java/lang/Object
interfaces: 0, fields: 0, methods: 2, attributes: 2
+Constant pool:
+{
public com.android.hoststubgen.test.tinyframework.TinyFrameworkExceptionTester();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
@@ -1256,6 +1304,8 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkForTextPoli
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy
super_class: #x // java/lang/Object
interfaces: 0, fields: 2, methods: 17, attributes: 1
+Constant pool:
+{
public int stub;
descriptor: I
flags: (0x0001) ACC_PUBLIC
@@ -1507,6 +1557,8 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkLambdas$Nes
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkLambdas$Nested
super_class: #x // java/lang/Object
interfaces: 0, fields: 2, methods: 8, attributes: 5
+Constant pool:
+{
public final java.util.function.Supplier<java.lang.Integer> mSupplier;
descriptor: Ljava/util/function/Supplier;
flags: (0x0011) ACC_PUBLIC, ACC_FINAL
@@ -1661,6 +1713,8 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkLambdas
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkLambdas
super_class: #x // java/lang/Object
interfaces: 0, fields: 2, methods: 8, attributes: 5
+Constant pool:
+{
public final java.util.function.Supplier<java.lang.Integer> mSupplier;
descriptor: Ljava/util/function/Supplier;
flags: (0x0011) ACC_PUBLIC, ACC_FINAL
@@ -1816,6 +1870,8 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkMethodCallR
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ReplaceTo
super_class: #x // java/lang/Object
interfaces: 0, fields: 0, methods: 3, attributes: 3
+Constant pool:
+{
public com.android.hoststubgen.test.tinyframework.TinyFrameworkMethodCallReplace$ReplaceTo();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
@@ -1873,6 +1929,8 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkMethodCallR
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace
super_class: #x // java/lang/Object
interfaces: 0, fields: 0, methods: 5, attributes: 5
+Constant pool:
+{
public com.android.hoststubgen.test.tinyframework.TinyFrameworkMethodCallReplace();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
@@ -1984,6 +2042,8 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNative
super_class: #x // java/lang/Object
interfaces: 0, fields: 1, methods: 14, attributes: 2
+Constant pool:
+{
int value;
descriptor: I
flags: (0x0000)
@@ -2157,6 +2217,8 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative_host
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host
super_class: #x // java/lang/Object
interfaces: 0, fields: 0, methods: 7, attributes: 2
+Constant pool:
+{
public com.android.hoststubgen.test.tinyframework.TinyFrameworkNative_host();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
@@ -2263,6 +2325,8 @@ class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$1 ex
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$1
super_class: #x // java/lang/Object
interfaces: 1, fields: 0, methods: 3, attributes: 5
+Constant pool:
+{
com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$1(com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses);
descriptor: (Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses;)V
flags: (0x0000)
@@ -2321,6 +2385,8 @@ class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$2 ex
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$2
super_class: #x // java/lang/Object
interfaces: 1, fields: 0, methods: 3, attributes: 5
+Constant pool:
+{
com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$2();
descriptor: ()V
flags: (0x0000)
@@ -2375,6 +2441,8 @@ class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$3 ex
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$3
super_class: #x // java/lang/Object
interfaces: 1, fields: 0, methods: 3, attributes: 5
+Constant pool:
+{
com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$3(com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses);
descriptor: (Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses;)V
flags: (0x0000)
@@ -2433,6 +2501,8 @@ class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$4 ex
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$4
super_class: #x // java/lang/Object
interfaces: 1, fields: 0, methods: 3, attributes: 5
+Constant pool:
+{
com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$4();
descriptor: ()V
flags: (0x0000)
@@ -2487,6 +2557,8 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClass
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$BaseClass
super_class: #x // java/lang/Object
interfaces: 0, fields: 1, methods: 1, attributes: 3
+Constant pool:
+{
public int value;
descriptor: I
flags: (0x0001) ACC_PUBLIC
@@ -2521,6 +2593,8 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClass
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$InnerClass
super_class: #x // java/lang/Object
interfaces: 0, fields: 1, methods: 1, attributes: 3
+Constant pool:
+{
public int value;
descriptor: I
flags: (0x0001) ACC_PUBLIC
@@ -2558,6 +2632,8 @@ class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$Stat
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1
super_class: #x // java/lang/Object
interfaces: 1, fields: 0, methods: 3, attributes: 5
+Constant pool:
+{
com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$StaticNestedClass$1();
descriptor: ()V
flags: (0x0000)
@@ -2613,6 +2689,8 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClass
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$Double$NestedClass
super_class: #x // java/lang/Object
interfaces: 0, fields: 1, methods: 1, attributes: 3
+Constant pool:
+{
public int value;
descriptor: I
flags: (0x0001) ACC_PUBLIC
@@ -2647,6 +2725,8 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClass
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass
super_class: #x // java/lang/Object
interfaces: 0, fields: 1, methods: 2, attributes: 3
+Constant pool:
+{
public int value;
descriptor: I
flags: (0x0001) ACC_PUBLIC
@@ -2694,6 +2774,8 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClass
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$SubClass
super_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$BaseClass
interfaces: 0, fields: 0, methods: 1, attributes: 3
+Constant pool:
+{
public com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$SubClass(int);
descriptor: (I)V
flags: (0x0001) ACC_PUBLIC
@@ -2723,6 +2805,8 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClass
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
super_class: #x // java/lang/Object
interfaces: 0, fields: 2, methods: 4, attributes: 4
+Constant pool:
+{
public final java.util.function.Supplier<java.lang.Integer> mSupplier;
descriptor: Ljava/util/function/Supplier;
flags: (0x0011) ACC_PUBLIC, ACC_FINAL
@@ -2827,6 +2911,8 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkPackageRedi
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkPackageRedirect
super_class: #x // java/lang/Object
interfaces: 0, fields: 0, methods: 2, attributes: 2
+Constant pool:
+{
public com.android.hoststubgen.test.tinyframework.TinyFrameworkPackageRedirect();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
@@ -2869,6 +2955,8 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkRenamedClas
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkRenamedClassCaller
super_class: #x // java/lang/Object
interfaces: 0, fields: 0, methods: 2, attributes: 2
+Constant pool:
+{
public com.android.hoststubgen.test.tinyframework.TinyFrameworkRenamedClassCaller();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
@@ -2911,6 +2999,8 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkToBeRenamed
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkToBeRenamed
super_class: #x // java/lang/Object
interfaces: 0, fields: 1, methods: 2, attributes: 2
+Constant pool:
+{
private final int mValue;
descriptor: I
flags: (0x0012) ACC_PRIVATE, ACC_FINAL
@@ -2958,6 +3048,8 @@ public class com.android.hoststubgen.test.tinyframework.packagetest.A
this_class: #x // com/android/hoststubgen/test/tinyframework/packagetest/A
super_class: #x // java/lang/Object
interfaces: 0, fields: 0, methods: 1, attributes: 1
+Constant pool:
+{
public com.android.hoststubgen.test.tinyframework.packagetest.A();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
@@ -2981,6 +3073,8 @@ public class com.android.hoststubgen.test.tinyframework.packagetest.B
this_class: #x // com/android/hoststubgen/test/tinyframework/packagetest/B
super_class: #x // java/lang/Object
interfaces: 0, fields: 0, methods: 1, attributes: 1
+Constant pool:
+{
public com.android.hoststubgen.test.tinyframework.packagetest.B();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
@@ -3004,6 +3098,8 @@ public class com.android.hoststubgen.test.tinyframework.packagetest.sub.A
this_class: #x // com/android/hoststubgen/test/tinyframework/packagetest/sub/A
super_class: #x // java/lang/Object
interfaces: 0, fields: 0, methods: 1, attributes: 1
+Constant pool:
+{
public com.android.hoststubgen.test.tinyframework.packagetest.sub.A();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
@@ -3027,6 +3123,8 @@ public class com.android.hoststubgen.test.tinyframework.packagetest.sub.B
this_class: #x // com/android/hoststubgen/test/tinyframework/packagetest/sub/B
super_class: #x // java/lang/Object
interfaces: 0, fields: 0, methods: 1, attributes: 1
+Constant pool:
+{
public com.android.hoststubgen.test.tinyframework.packagetest.sub.B();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
@@ -3050,6 +3148,8 @@ public class com.android.hoststubgen.test.tinyframework.subclasstest.C1
this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/C1
super_class: #x // java/lang/Object
interfaces: 0, fields: 0, methods: 1, attributes: 1
+Constant pool:
+{
public com.android.hoststubgen.test.tinyframework.subclasstest.C1();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
@@ -3073,6 +3173,8 @@ public class com.android.hoststubgen.test.tinyframework.subclasstest.C2 extends
this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/C2
super_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/C1
interfaces: 0, fields: 0, methods: 1, attributes: 1
+Constant pool:
+{
public com.android.hoststubgen.test.tinyframework.subclasstest.C2();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
@@ -3096,6 +3198,8 @@ public class com.android.hoststubgen.test.tinyframework.subclasstest.C3 extends
this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/C3
super_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/C2
interfaces: 0, fields: 0, methods: 1, attributes: 1
+Constant pool:
+{
public com.android.hoststubgen.test.tinyframework.subclasstest.C3();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
@@ -3119,6 +3223,8 @@ public class com.android.hoststubgen.test.tinyframework.subclasstest.CA
this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/CA
super_class: #x // java/lang/Object
interfaces: 0, fields: 0, methods: 1, attributes: 1
+Constant pool:
+{
public com.android.hoststubgen.test.tinyframework.subclasstest.CA();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
@@ -3142,6 +3248,8 @@ public class com.android.hoststubgen.test.tinyframework.subclasstest.CB
this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/CB
super_class: #x // java/lang/Object
interfaces: 0, fields: 0, methods: 1, attributes: 1
+Constant pool:
+{
public com.android.hoststubgen.test.tinyframework.subclasstest.CB();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
@@ -3165,6 +3273,8 @@ public class com.android.hoststubgen.test.tinyframework.subclasstest.Class_C1 ex
this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/Class_C1
super_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/C1
interfaces: 0, fields: 0, methods: 1, attributes: 1
+Constant pool:
+{
public com.android.hoststubgen.test.tinyframework.subclasstest.Class_C1();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
@@ -3188,6 +3298,8 @@ public class com.android.hoststubgen.test.tinyframework.subclasstest.Class_C2 ex
this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/Class_C2
super_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/C2
interfaces: 0, fields: 0, methods: 1, attributes: 1
+Constant pool:
+{
public com.android.hoststubgen.test.tinyframework.subclasstest.Class_C2();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
@@ -3211,6 +3323,8 @@ public class com.android.hoststubgen.test.tinyframework.subclasstest.Class_C3 ex
this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/Class_C3
super_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/C3
interfaces: 0, fields: 0, methods: 1, attributes: 1
+Constant pool:
+{
public com.android.hoststubgen.test.tinyframework.subclasstest.Class_C3();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
@@ -3234,6 +3348,8 @@ public class com.android.hoststubgen.test.tinyframework.subclasstest.Class_CA ex
this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/Class_CA
super_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/CA
interfaces: 0, fields: 0, methods: 1, attributes: 1
+Constant pool:
+{
public com.android.hoststubgen.test.tinyframework.subclasstest.Class_CA();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
@@ -3257,6 +3373,8 @@ public class com.android.hoststubgen.test.tinyframework.subclasstest.Class_CB ex
this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/Class_CB
super_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/CB
interfaces: 0, fields: 0, methods: 1, attributes: 1
+Constant pool:
+{
public com.android.hoststubgen.test.tinyframework.subclasstest.Class_CB();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
@@ -3280,6 +3398,8 @@ public class com.android.hoststubgen.test.tinyframework.subclasstest.Class_CB_IA
this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/Class_CB_IA
super_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/CB
interfaces: 1, fields: 0, methods: 1, attributes: 1
+Constant pool:
+{
public com.android.hoststubgen.test.tinyframework.subclasstest.Class_CB_IA();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
@@ -3303,6 +3423,8 @@ public class com.android.hoststubgen.test.tinyframework.subclasstest.Class_I1 im
this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/Class_I1
super_class: #x // java/lang/Object
interfaces: 1, fields: 0, methods: 1, attributes: 1
+Constant pool:
+{
public com.android.hoststubgen.test.tinyframework.subclasstest.Class_I1();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
@@ -3326,6 +3448,8 @@ public class com.android.hoststubgen.test.tinyframework.subclasstest.Class_I1_IA
this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/Class_I1_IA
super_class: #x // java/lang/Object
interfaces: 2, fields: 0, methods: 1, attributes: 1
+Constant pool:
+{
public com.android.hoststubgen.test.tinyframework.subclasstest.Class_I1_IA();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
@@ -3349,6 +3473,8 @@ public class com.android.hoststubgen.test.tinyframework.subclasstest.Class_I2 im
this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/Class_I2
super_class: #x // java/lang/Object
interfaces: 1, fields: 0, methods: 1, attributes: 1
+Constant pool:
+{
public com.android.hoststubgen.test.tinyframework.subclasstest.Class_I2();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
@@ -3372,6 +3498,8 @@ public class com.android.hoststubgen.test.tinyframework.subclasstest.Class_I3 im
this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/Class_I3
super_class: #x // java/lang/Object
interfaces: 1, fields: 0, methods: 1, attributes: 1
+Constant pool:
+{
public com.android.hoststubgen.test.tinyframework.subclasstest.Class_I3();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
@@ -3395,6 +3523,8 @@ public class com.android.hoststubgen.test.tinyframework.subclasstest.Class_I3_IA
this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/Class_I3_IA
super_class: #x // java/lang/Object
interfaces: 2, fields: 0, methods: 1, attributes: 1
+Constant pool:
+{
public com.android.hoststubgen.test.tinyframework.subclasstest.Class_I3_IA();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
@@ -3418,6 +3548,8 @@ public class com.android.hoststubgen.test.tinyframework.subclasstest.Class_IA im
this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/Class_IA
super_class: #x // java/lang/Object
interfaces: 1, fields: 0, methods: 1, attributes: 1
+Constant pool:
+{
public com.android.hoststubgen.test.tinyframework.subclasstest.Class_IA();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
@@ -3441,6 +3573,8 @@ public class com.android.hoststubgen.test.tinyframework.subclasstest.Class_IA_I1
this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/Class_IA_I1
super_class: #x // java/lang/Object
interfaces: 2, fields: 0, methods: 1, attributes: 1
+Constant pool:
+{
public com.android.hoststubgen.test.tinyframework.subclasstest.Class_IA_I1();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
@@ -3464,6 +3598,8 @@ public class com.android.hoststubgen.test.tinyframework.subclasstest.Class_IA_I3
this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/Class_IA_I3
super_class: #x // java/lang/Object
interfaces: 2, fields: 0, methods: 1, attributes: 1
+Constant pool:
+{
public com.android.hoststubgen.test.tinyframework.subclasstest.Class_IA_I3();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
@@ -3487,6 +3623,8 @@ public class com.android.hoststubgen.test.tinyframework.subclasstest.Class_IB im
this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/Class_IB
super_class: #x // java/lang/Object
interfaces: 1, fields: 0, methods: 1, attributes: 1
+Constant pool:
+{
public com.android.hoststubgen.test.tinyframework.subclasstest.Class_IB();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
@@ -3510,6 +3648,8 @@ public class com.android.hoststubgen.test.tinyframework.subclasstest.Class_IB_IA
this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/Class_IB_IA
super_class: #x // java/lang/Object
interfaces: 2, fields: 0, methods: 1, attributes: 1
+Constant pool:
+{
public com.android.hoststubgen.test.tinyframework.subclasstest.Class_IB_IA();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
@@ -3533,6 +3673,8 @@ public class com.android.hoststubgen.test.tinyframework.subclasstest.Class_None
this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/Class_None
super_class: #x // java/lang/Object
interfaces: 0, fields: 0, methods: 1, attributes: 1
+Constant pool:
+{
public com.android.hoststubgen.test.tinyframework.subclasstest.Class_None();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
@@ -3556,6 +3698,8 @@ public interface com.android.hoststubgen.test.tinyframework.subclasstest.I1
this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/I1
super_class: #x // java/lang/Object
interfaces: 0, fields: 0, methods: 0, attributes: 1
+Constant pool:
+{
}
SourceFile: "I1.java"
## Class: com/android/hoststubgen/test/tinyframework/subclasstest/I2.class
@@ -3567,6 +3711,8 @@ public interface com.android.hoststubgen.test.tinyframework.subclasstest.I2 exte
this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/I2
super_class: #x // java/lang/Object
interfaces: 1, fields: 0, methods: 0, attributes: 1
+Constant pool:
+{
}
SourceFile: "I2.java"
## Class: com/android/hoststubgen/test/tinyframework/subclasstest/I3.class
@@ -3578,6 +3724,8 @@ public interface com.android.hoststubgen.test.tinyframework.subclasstest.I3 exte
this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/I3
super_class: #x // java/lang/Object
interfaces: 1, fields: 0, methods: 0, attributes: 1
+Constant pool:
+{
}
SourceFile: "I3.java"
## Class: com/android/hoststubgen/test/tinyframework/subclasstest/IA.class
@@ -3589,6 +3737,8 @@ public interface com.android.hoststubgen.test.tinyframework.subclasstest.IA
this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/IA
super_class: #x // java/lang/Object
interfaces: 0, fields: 0, methods: 0, attributes: 1
+Constant pool:
+{
}
SourceFile: "IA.java"
## Class: com/android/hoststubgen/test/tinyframework/subclasstest/IB.class
@@ -3600,6 +3750,8 @@ public interface com.android.hoststubgen.test.tinyframework.subclasstest.IB
this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/IB
super_class: #x // java/lang/Object
interfaces: 0, fields: 0, methods: 0, attributes: 1
+Constant pool:
+{
}
SourceFile: "IB.java"
## Class: com/supported/UnsupportedClass.class
@@ -3611,6 +3763,8 @@ public class com.supported.UnsupportedClass
this_class: #x // com/supported/UnsupportedClass
super_class: #x // java/lang/Object
interfaces: 0, fields: 1, methods: 2, attributes: 2
+Constant pool:
+{
private final int mValue;
descriptor: I
flags: (0x0012) ACC_PRIVATE, ACC_FINAL
@@ -3658,6 +3812,8 @@ public class com.unsupported.UnsupportedClass
this_class: #x // com/unsupported/UnsupportedClass
super_class: #x // java/lang/Object
interfaces: 0, fields: 0, methods: 2, attributes: 2
+Constant pool:
+{
public com.unsupported.UnsupportedClass(int);
descriptor: (I)V
flags: (0x0001) ACC_PUBLIC
diff --git a/ravenwood/tools/hoststubgen/test-tiny-framework/golden-output/01-hoststubgen-test-tiny-framework-orig-dump.txt b/ravenwood/tools/hoststubgen/test-tiny-framework/golden-output/01-hoststubgen-test-tiny-framework-orig-dump.txt
index 103e152c7e39..ad413425801b 100644
--- a/ravenwood/tools/hoststubgen/test-tiny-framework/golden-output/01-hoststubgen-test-tiny-framework-orig-dump.txt
+++ b/ravenwood/tools/hoststubgen/test-tiny-framework/golden-output/01-hoststubgen-test-tiny-framework-orig-dump.txt
@@ -7,6 +7,8 @@ public interface android.hosttest.annotation.HostSideTestClassLoadHook extends j
this_class: #x // android/hosttest/annotation/HostSideTestClassLoadHook
super_class: #x // java/lang/Object
interfaces: 1, fields: 0, methods: 1, attributes: 2
+Constant pool:
+{
public abstract java.lang.String value();
descriptor: ()Ljava/lang/String;
flags: (0x0401) ACC_PUBLIC, ACC_ABSTRACT
@@ -30,6 +32,8 @@ public interface android.hosttest.annotation.HostSideTestIgnore extends java.lan
this_class: #x // android/hosttest/annotation/HostSideTestIgnore
super_class: #x // java/lang/Object
interfaces: 1, fields: 0, methods: 0, attributes: 2
+Constant pool:
+{
}
SourceFile: "HostSideTestIgnore.java"
RuntimeVisibleAnnotations:
@@ -50,6 +54,8 @@ public interface android.hosttest.annotation.HostSideTestKeep extends java.lang.
this_class: #x // android/hosttest/annotation/HostSideTestKeep
super_class: #x // java/lang/Object
interfaces: 1, fields: 0, methods: 0, attributes: 2
+Constant pool:
+{
}
SourceFile: "HostSideTestKeep.java"
RuntimeVisibleAnnotations:
@@ -70,6 +76,8 @@ public interface android.hosttest.annotation.HostSideTestRedirect extends java.l
this_class: #x // android/hosttest/annotation/HostSideTestRedirect
super_class: #x // java/lang/Object
interfaces: 1, fields: 0, methods: 0, attributes: 2
+Constant pool:
+{
}
SourceFile: "HostSideTestRedirect.java"
RuntimeVisibleAnnotations:
@@ -90,6 +98,8 @@ public interface android.hosttest.annotation.HostSideTestRedirectionClass extend
this_class: #x // android/hosttest/annotation/HostSideTestRedirectionClass
super_class: #x // java/lang/Object
interfaces: 1, fields: 0, methods: 1, attributes: 2
+Constant pool:
+{
public abstract java.lang.String value();
descriptor: ()Ljava/lang/String;
flags: (0x0401) ACC_PUBLIC, ACC_ABSTRACT
@@ -113,6 +123,8 @@ public interface android.hosttest.annotation.HostSideTestRemove extends java.lan
this_class: #x // android/hosttest/annotation/HostSideTestRemove
super_class: #x // java/lang/Object
interfaces: 1, fields: 0, methods: 0, attributes: 2
+Constant pool:
+{
}
SourceFile: "HostSideTestRemove.java"
RuntimeVisibleAnnotations:
@@ -133,6 +145,8 @@ public interface android.hosttest.annotation.HostSideTestStaticInitializerKeep e
this_class: #x // android/hosttest/annotation/HostSideTestStaticInitializerKeep
super_class: #x // java/lang/Object
interfaces: 1, fields: 0, methods: 0, attributes: 2
+Constant pool:
+{
}
SourceFile: "HostSideTestStaticInitializerKeep.java"
RuntimeVisibleAnnotations:
@@ -153,6 +167,8 @@ public interface android.hosttest.annotation.HostSideTestSubstitute extends java
this_class: #x // android/hosttest/annotation/HostSideTestSubstitute
super_class: #x // java/lang/Object
interfaces: 1, fields: 0, methods: 1, attributes: 2
+Constant pool:
+{
public abstract java.lang.String suffix();
descriptor: ()Ljava/lang/String;
flags: (0x0401) ACC_PUBLIC, ACC_ABSTRACT
@@ -176,6 +192,8 @@ public interface android.hosttest.annotation.HostSideTestThrow extends java.lang
this_class: #x // android/hosttest/annotation/HostSideTestThrow
super_class: #x // java/lang/Object
interfaces: 1, fields: 0, methods: 0, attributes: 2
+Constant pool:
+{
}
SourceFile: "HostSideTestThrow.java"
RuntimeVisibleAnnotations:
@@ -196,6 +214,8 @@ public interface android.hosttest.annotation.HostSideTestWholeClassKeep extends
this_class: #x // android/hosttest/annotation/HostSideTestWholeClassKeep
super_class: #x // java/lang/Object
interfaces: 1, fields: 0, methods: 0, attributes: 2
+Constant pool:
+{
}
SourceFile: "HostSideTestWholeClassKeep.java"
RuntimeVisibleAnnotations:
@@ -216,6 +236,8 @@ public interface android.hosttest.annotation.tests.HostSideTestSuppress extends
this_class: #x // android/hosttest/annotation/tests/HostSideTestSuppress
super_class: #x // java/lang/Object
interfaces: 1, fields: 0, methods: 0, attributes: 2
+Constant pool:
+{
}
SourceFile: "HostSideTestSuppress.java"
RuntimeVisibleAnnotations:
@@ -232,6 +254,8 @@ public class com.android.hoststubgen.test.tinyframework.IPretendingAidl$Stub$Pro
this_class: #x // com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub$Proxy
super_class: #x // java/lang/Object
interfaces: 0, fields: 0, methods: 2, attributes: 3
+Constant pool:
+{
public com.android.hoststubgen.test.tinyframework.IPretendingAidl$Stub$Proxy();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
@@ -273,6 +297,8 @@ public class com.android.hoststubgen.test.tinyframework.IPretendingAidl$Stub
this_class: #x // com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub
super_class: #x // java/lang/Object
interfaces: 0, fields: 0, methods: 2, attributes: 3
+Constant pool:
+{
public com.android.hoststubgen.test.tinyframework.IPretendingAidl$Stub();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
@@ -314,6 +340,8 @@ public interface com.android.hoststubgen.test.tinyframework.IPretendingAidl
this_class: #x // com/android/hoststubgen/test/tinyframework/IPretendingAidl
super_class: #x // java/lang/Object
interfaces: 0, fields: 0, methods: 0, attributes: 3
+Constant pool:
+{
}
SourceFile: "IPretendingAidl.java"
NestMembers:
@@ -331,6 +359,8 @@ public class com.android.hoststubgen.test.tinyframework.R$Nested
this_class: #x // com/android/hoststubgen/test/tinyframework/R$Nested
super_class: #x // java/lang/Object
interfaces: 0, fields: 1, methods: 2, attributes: 3
+Constant pool:
+{
public static int[] ARRAY;
descriptor: [I
flags: (0x0009) ACC_PUBLIC, ACC_STATIC
@@ -376,6 +406,8 @@ public class com.android.hoststubgen.test.tinyframework.R
this_class: #x // com/android/hoststubgen/test/tinyframework/R
super_class: #x // java/lang/Object
interfaces: 0, fields: 0, methods: 1, attributes: 3
+Constant pool:
+{
public com.android.hoststubgen.test.tinyframework.R();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
@@ -396,13 +428,15 @@ InnerClasses:
public static #x= #x of #x; // Nested=class com/android/hoststubgen/test/tinyframework/R$Nested of class com/android/hoststubgen/test/tinyframework/R
## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotations.class
Compiled from "TinyFrameworkAnnotations.java"
-public class com.android.hoststubgen.test.tinyframework.TinyFrameworkAnnotations
+public final class com.android.hoststubgen.test.tinyframework.TinyFrameworkAnnotations
minor version: 0
major version: 61
- flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+ flags: (0x0031) ACC_PUBLIC, ACC_FINAL, ACC_SUPER
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotations
super_class: #x // java/lang/Object
interfaces: 0, fields: 2, methods: 9, attributes: 2
+Constant pool:
+{
public int keep;
descriptor: I
flags: (0x0001) ACC_PUBLIC
@@ -433,9 +467,9 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkAnnotations
x: #x()
android.hosttest.annotation.HostSideTestKeep
- public int addOne(int);
+ public final int addOne(int);
descriptor: (I)I
- flags: (0x0001) ACC_PUBLIC
+ flags: (0x0011) ACC_PUBLIC, ACC_FINAL
Code:
stack=2, locals=2, args_size=2
x: iload_1
@@ -505,18 +539,18 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkAnnotations
0 4 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotations;
0 4 1 value I
- public static native int nativeAddThree(int);
+ public static final native int nativeAddThree(int);
descriptor: (I)I
- flags: (0x0109) ACC_PUBLIC, ACC_STATIC, ACC_NATIVE
+ flags: (0x0119) ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_NATIVE
RuntimeInvisibleAnnotations:
x: #x(#x=s#x)
android.hosttest.annotation.HostSideTestSubstitute(
suffix="_host"
)
- private static int nativeAddThree_host(int);
+ private static final int nativeAddThree_host(int);
descriptor: (I)I
- flags: (0x000a) ACC_PRIVATE, ACC_STATIC
+ flags: (0x001a) ACC_PRIVATE, ACC_STATIC, ACC_FINAL
Code:
stack=2, locals=1, args_size=1
x: iload_0
@@ -578,6 +612,8 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHo
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkClassLoadHook
super_class: #x // java/lang/Object
interfaces: 0, fields: 1, methods: 3, attributes: 2
+Constant pool:
+{
public static final java.util.Set<java.lang.Class<?>> sLoadedClasses;
descriptor: Ljava/util/Set;
flags: (0x0019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL
@@ -640,6 +676,8 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassWideAn
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWideAnnotations
super_class: #x // java/lang/Object
interfaces: 0, fields: 2, methods: 6, attributes: 2
+Constant pool:
+{
public int keep;
descriptor: I
flags: (0x0001) ACC_PUBLIC
@@ -764,6 +802,8 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassWithIn
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWithInitializerDefault
super_class: #x // java/lang/Object
interfaces: 0, fields: 2, methods: 2, attributes: 2
+Constant pool:
+{
public static boolean sInitialized;
descriptor: Z
flags: (0x0009) ACC_PUBLIC, ACC_STATIC
@@ -818,6 +858,8 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassWithIn
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWithInitializerStub
super_class: #x // java/lang/Object
interfaces: 0, fields: 2, methods: 2, attributes: 2
+Constant pool:
+{
public static boolean sInitialized;
descriptor: Z
flags: (0x0009) ACC_PUBLIC, ACC_STATIC
@@ -878,6 +920,8 @@ public final class com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumC
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex
super_class: #x // java/lang/Enum
interfaces: 0, fields: 6, methods: 7, attributes: 3
+Constant pool:
+{
public static final com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumComplex RED;
descriptor: Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
flags: (0x4019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM
@@ -1081,6 +1125,8 @@ public final class com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumS
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple
super_class: #x // java/lang/Enum
interfaces: 0, fields: 3, methods: 5, attributes: 3
+Constant pool:
+{
public static final com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumSimple CAT;
descriptor: Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;
flags: (0x4019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM
@@ -1202,6 +1248,8 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkExceptionTe
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkExceptionTester
super_class: #x // java/lang/Object
interfaces: 0, fields: 0, methods: 2, attributes: 2
+Constant pool:
+{
public com.android.hoststubgen.test.tinyframework.TinyFrameworkExceptionTester();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
@@ -1256,6 +1304,8 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkForTextPoli
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy
super_class: #x // java/lang/Object
interfaces: 0, fields: 2, methods: 17, attributes: 1
+Constant pool:
+{
public int stub;
descriptor: I
flags: (0x0001) ACC_PUBLIC
@@ -1507,6 +1557,8 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkLambdas$Nes
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkLambdas$Nested
super_class: #x // java/lang/Object
interfaces: 0, fields: 2, methods: 8, attributes: 5
+Constant pool:
+{
public final java.util.function.Supplier<java.lang.Integer> mSupplier;
descriptor: Ljava/util/function/Supplier;
flags: (0x0011) ACC_PUBLIC, ACC_FINAL
@@ -1661,6 +1713,8 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkLambdas
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkLambdas
super_class: #x // java/lang/Object
interfaces: 0, fields: 2, methods: 8, attributes: 5
+Constant pool:
+{
public final java.util.function.Supplier<java.lang.Integer> mSupplier;
descriptor: Ljava/util/function/Supplier;
flags: (0x0011) ACC_PUBLIC, ACC_FINAL
@@ -1816,6 +1870,8 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkMethodCallR
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ReplaceTo
super_class: #x // java/lang/Object
interfaces: 0, fields: 0, methods: 3, attributes: 3
+Constant pool:
+{
public com.android.hoststubgen.test.tinyframework.TinyFrameworkMethodCallReplace$ReplaceTo();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
@@ -1873,6 +1929,8 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkMethodCallR
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace
super_class: #x // java/lang/Object
interfaces: 0, fields: 0, methods: 5, attributes: 5
+Constant pool:
+{
public com.android.hoststubgen.test.tinyframework.TinyFrameworkMethodCallReplace();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
@@ -1984,6 +2042,8 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNative
super_class: #x // java/lang/Object
interfaces: 0, fields: 1, methods: 14, attributes: 2
+Constant pool:
+{
int value;
descriptor: I
flags: (0x0000)
@@ -2157,6 +2217,8 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative_host
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host
super_class: #x // java/lang/Object
interfaces: 0, fields: 0, methods: 7, attributes: 2
+Constant pool:
+{
public com.android.hoststubgen.test.tinyframework.TinyFrameworkNative_host();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
@@ -2263,6 +2325,8 @@ class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$1 ex
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$1
super_class: #x // java/lang/Object
interfaces: 1, fields: 1, methods: 3, attributes: 5
+Constant pool:
+{
final com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses this$0;
descriptor: Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses;
flags: (0x1010) ACC_FINAL, ACC_SYNTHETIC
@@ -2328,6 +2392,8 @@ class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$2 ex
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$2
super_class: #x // java/lang/Object
interfaces: 1, fields: 0, methods: 3, attributes: 5
+Constant pool:
+{
com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$2();
descriptor: ()V
flags: (0x0000)
@@ -2382,6 +2448,8 @@ class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$3 ex
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$3
super_class: #x // java/lang/Object
interfaces: 1, fields: 1, methods: 3, attributes: 5
+Constant pool:
+{
final com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses this$0;
descriptor: Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses;
flags: (0x1010) ACC_FINAL, ACC_SYNTHETIC
@@ -2447,6 +2515,8 @@ class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$4 ex
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$4
super_class: #x // java/lang/Object
interfaces: 1, fields: 0, methods: 3, attributes: 5
+Constant pool:
+{
com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$4();
descriptor: ()V
flags: (0x0000)
@@ -2501,6 +2571,8 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClass
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$BaseClass
super_class: #x // java/lang/Object
interfaces: 0, fields: 1, methods: 1, attributes: 3
+Constant pool:
+{
public int value;
descriptor: I
flags: (0x0001) ACC_PUBLIC
@@ -2535,6 +2607,8 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClass
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$InnerClass
super_class: #x // java/lang/Object
interfaces: 0, fields: 2, methods: 1, attributes: 3
+Constant pool:
+{
public int value;
descriptor: I
flags: (0x0001) ACC_PUBLIC
@@ -2579,6 +2653,8 @@ class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$Stat
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1
super_class: #x // java/lang/Object
interfaces: 1, fields: 0, methods: 3, attributes: 5
+Constant pool:
+{
com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$StaticNestedClass$1();
descriptor: ()V
flags: (0x0000)
@@ -2634,6 +2710,8 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClass
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$Double$NestedClass
super_class: #x // java/lang/Object
interfaces: 0, fields: 1, methods: 1, attributes: 3
+Constant pool:
+{
public int value;
descriptor: I
flags: (0x0001) ACC_PUBLIC
@@ -2668,6 +2746,8 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClass
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass
super_class: #x // java/lang/Object
interfaces: 0, fields: 1, methods: 2, attributes: 3
+Constant pool:
+{
public int value;
descriptor: I
flags: (0x0001) ACC_PUBLIC
@@ -2715,6 +2795,8 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClass
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$SubClass
super_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$BaseClass
interfaces: 0, fields: 0, methods: 1, attributes: 3
+Constant pool:
+{
public com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$SubClass(int);
descriptor: (I)V
flags: (0x0001) ACC_PUBLIC
@@ -2744,6 +2826,8 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClass
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
super_class: #x // java/lang/Object
interfaces: 0, fields: 2, methods: 4, attributes: 4
+Constant pool:
+{
public final java.util.function.Supplier<java.lang.Integer> mSupplier;
descriptor: Ljava/util/function/Supplier;
flags: (0x0011) ACC_PUBLIC, ACC_FINAL
@@ -2848,6 +2932,8 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkPackageRedi
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkPackageRedirect
super_class: #x // java/lang/Object
interfaces: 0, fields: 0, methods: 2, attributes: 2
+Constant pool:
+{
public com.android.hoststubgen.test.tinyframework.TinyFrameworkPackageRedirect();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
@@ -2890,6 +2976,8 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkRenamedClas
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkRenamedClassCaller
super_class: #x // java/lang/Object
interfaces: 0, fields: 0, methods: 2, attributes: 2
+Constant pool:
+{
public com.android.hoststubgen.test.tinyframework.TinyFrameworkRenamedClassCaller();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
@@ -2932,6 +3020,8 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkToBeRenamed
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkToBeRenamed
super_class: #x // java/lang/Object
interfaces: 0, fields: 1, methods: 2, attributes: 2
+Constant pool:
+{
private final int mValue;
descriptor: I
flags: (0x0012) ACC_PRIVATE, ACC_FINAL
@@ -2979,6 +3069,8 @@ public class com.android.hoststubgen.test.tinyframework.packagetest.A
this_class: #x // com/android/hoststubgen/test/tinyframework/packagetest/A
super_class: #x // java/lang/Object
interfaces: 0, fields: 0, methods: 1, attributes: 1
+Constant pool:
+{
public com.android.hoststubgen.test.tinyframework.packagetest.A();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
@@ -3002,6 +3094,8 @@ public class com.android.hoststubgen.test.tinyframework.packagetest.B
this_class: #x // com/android/hoststubgen/test/tinyframework/packagetest/B
super_class: #x // java/lang/Object
interfaces: 0, fields: 0, methods: 1, attributes: 1
+Constant pool:
+{
public com.android.hoststubgen.test.tinyframework.packagetest.B();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
@@ -3025,6 +3119,8 @@ public class com.android.hoststubgen.test.tinyframework.packagetest.sub.A
this_class: #x // com/android/hoststubgen/test/tinyframework/packagetest/sub/A
super_class: #x // java/lang/Object
interfaces: 0, fields: 0, methods: 1, attributes: 1
+Constant pool:
+{
public com.android.hoststubgen.test.tinyframework.packagetest.sub.A();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
@@ -3048,6 +3144,8 @@ public class com.android.hoststubgen.test.tinyframework.packagetest.sub.B
this_class: #x // com/android/hoststubgen/test/tinyframework/packagetest/sub/B
super_class: #x // java/lang/Object
interfaces: 0, fields: 0, methods: 1, attributes: 1
+Constant pool:
+{
public com.android.hoststubgen.test.tinyframework.packagetest.sub.B();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
@@ -3071,6 +3169,8 @@ public class com.android.hoststubgen.test.tinyframework.subclasstest.C1
this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/C1
super_class: #x // java/lang/Object
interfaces: 0, fields: 0, methods: 1, attributes: 1
+Constant pool:
+{
public com.android.hoststubgen.test.tinyframework.subclasstest.C1();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
@@ -3094,6 +3194,8 @@ public class com.android.hoststubgen.test.tinyframework.subclasstest.C2 extends
this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/C2
super_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/C1
interfaces: 0, fields: 0, methods: 1, attributes: 1
+Constant pool:
+{
public com.android.hoststubgen.test.tinyframework.subclasstest.C2();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
@@ -3117,6 +3219,8 @@ public class com.android.hoststubgen.test.tinyframework.subclasstest.C3 extends
this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/C3
super_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/C2
interfaces: 0, fields: 0, methods: 1, attributes: 1
+Constant pool:
+{
public com.android.hoststubgen.test.tinyframework.subclasstest.C3();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
@@ -3140,6 +3244,8 @@ public class com.android.hoststubgen.test.tinyframework.subclasstest.CA
this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/CA
super_class: #x // java/lang/Object
interfaces: 0, fields: 0, methods: 1, attributes: 1
+Constant pool:
+{
public com.android.hoststubgen.test.tinyframework.subclasstest.CA();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
@@ -3163,6 +3269,8 @@ public class com.android.hoststubgen.test.tinyframework.subclasstest.CB
this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/CB
super_class: #x // java/lang/Object
interfaces: 0, fields: 0, methods: 1, attributes: 1
+Constant pool:
+{
public com.android.hoststubgen.test.tinyframework.subclasstest.CB();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
@@ -3186,6 +3294,8 @@ public class com.android.hoststubgen.test.tinyframework.subclasstest.Class_C1 ex
this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/Class_C1
super_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/C1
interfaces: 0, fields: 0, methods: 1, attributes: 1
+Constant pool:
+{
public com.android.hoststubgen.test.tinyframework.subclasstest.Class_C1();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
@@ -3209,6 +3319,8 @@ public class com.android.hoststubgen.test.tinyframework.subclasstest.Class_C2 ex
this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/Class_C2
super_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/C2
interfaces: 0, fields: 0, methods: 1, attributes: 1
+Constant pool:
+{
public com.android.hoststubgen.test.tinyframework.subclasstest.Class_C2();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
@@ -3232,6 +3344,8 @@ public class com.android.hoststubgen.test.tinyframework.subclasstest.Class_C3 ex
this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/Class_C3
super_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/C3
interfaces: 0, fields: 0, methods: 1, attributes: 1
+Constant pool:
+{
public com.android.hoststubgen.test.tinyframework.subclasstest.Class_C3();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
@@ -3255,6 +3369,8 @@ public class com.android.hoststubgen.test.tinyframework.subclasstest.Class_CA ex
this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/Class_CA
super_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/CA
interfaces: 0, fields: 0, methods: 1, attributes: 1
+Constant pool:
+{
public com.android.hoststubgen.test.tinyframework.subclasstest.Class_CA();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
@@ -3278,6 +3394,8 @@ public class com.android.hoststubgen.test.tinyframework.subclasstest.Class_CB ex
this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/Class_CB
super_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/CB
interfaces: 0, fields: 0, methods: 1, attributes: 1
+Constant pool:
+{
public com.android.hoststubgen.test.tinyframework.subclasstest.Class_CB();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
@@ -3301,6 +3419,8 @@ public class com.android.hoststubgen.test.tinyframework.subclasstest.Class_CB_IA
this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/Class_CB_IA
super_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/CB
interfaces: 1, fields: 0, methods: 1, attributes: 1
+Constant pool:
+{
public com.android.hoststubgen.test.tinyframework.subclasstest.Class_CB_IA();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
@@ -3324,6 +3444,8 @@ public class com.android.hoststubgen.test.tinyframework.subclasstest.Class_I1 im
this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/Class_I1
super_class: #x // java/lang/Object
interfaces: 1, fields: 0, methods: 1, attributes: 1
+Constant pool:
+{
public com.android.hoststubgen.test.tinyframework.subclasstest.Class_I1();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
@@ -3347,6 +3469,8 @@ public class com.android.hoststubgen.test.tinyframework.subclasstest.Class_I1_IA
this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/Class_I1_IA
super_class: #x // java/lang/Object
interfaces: 2, fields: 0, methods: 1, attributes: 1
+Constant pool:
+{
public com.android.hoststubgen.test.tinyframework.subclasstest.Class_I1_IA();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
@@ -3370,6 +3494,8 @@ public class com.android.hoststubgen.test.tinyframework.subclasstest.Class_I2 im
this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/Class_I2
super_class: #x // java/lang/Object
interfaces: 1, fields: 0, methods: 1, attributes: 1
+Constant pool:
+{
public com.android.hoststubgen.test.tinyframework.subclasstest.Class_I2();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
@@ -3393,6 +3519,8 @@ public class com.android.hoststubgen.test.tinyframework.subclasstest.Class_I3 im
this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/Class_I3
super_class: #x // java/lang/Object
interfaces: 1, fields: 0, methods: 1, attributes: 1
+Constant pool:
+{
public com.android.hoststubgen.test.tinyframework.subclasstest.Class_I3();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
@@ -3416,6 +3544,8 @@ public class com.android.hoststubgen.test.tinyframework.subclasstest.Class_I3_IA
this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/Class_I3_IA
super_class: #x // java/lang/Object
interfaces: 2, fields: 0, methods: 1, attributes: 1
+Constant pool:
+{
public com.android.hoststubgen.test.tinyframework.subclasstest.Class_I3_IA();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
@@ -3439,6 +3569,8 @@ public class com.android.hoststubgen.test.tinyframework.subclasstest.Class_IA im
this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/Class_IA
super_class: #x // java/lang/Object
interfaces: 1, fields: 0, methods: 1, attributes: 1
+Constant pool:
+{
public com.android.hoststubgen.test.tinyframework.subclasstest.Class_IA();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
@@ -3462,6 +3594,8 @@ public class com.android.hoststubgen.test.tinyframework.subclasstest.Class_IA_I1
this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/Class_IA_I1
super_class: #x // java/lang/Object
interfaces: 2, fields: 0, methods: 1, attributes: 1
+Constant pool:
+{
public com.android.hoststubgen.test.tinyframework.subclasstest.Class_IA_I1();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
@@ -3485,6 +3619,8 @@ public class com.android.hoststubgen.test.tinyframework.subclasstest.Class_IA_I3
this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/Class_IA_I3
super_class: #x // java/lang/Object
interfaces: 2, fields: 0, methods: 1, attributes: 1
+Constant pool:
+{
public com.android.hoststubgen.test.tinyframework.subclasstest.Class_IA_I3();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
@@ -3508,6 +3644,8 @@ public class com.android.hoststubgen.test.tinyframework.subclasstest.Class_IB im
this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/Class_IB
super_class: #x // java/lang/Object
interfaces: 1, fields: 0, methods: 1, attributes: 1
+Constant pool:
+{
public com.android.hoststubgen.test.tinyframework.subclasstest.Class_IB();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
@@ -3531,6 +3669,8 @@ public class com.android.hoststubgen.test.tinyframework.subclasstest.Class_IB_IA
this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/Class_IB_IA
super_class: #x // java/lang/Object
interfaces: 2, fields: 0, methods: 1, attributes: 1
+Constant pool:
+{
public com.android.hoststubgen.test.tinyframework.subclasstest.Class_IB_IA();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
@@ -3554,6 +3694,8 @@ public class com.android.hoststubgen.test.tinyframework.subclasstest.Class_None
this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/Class_None
super_class: #x // java/lang/Object
interfaces: 0, fields: 0, methods: 1, attributes: 1
+Constant pool:
+{
public com.android.hoststubgen.test.tinyframework.subclasstest.Class_None();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
@@ -3577,6 +3719,8 @@ public interface com.android.hoststubgen.test.tinyframework.subclasstest.I1
this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/I1
super_class: #x // java/lang/Object
interfaces: 0, fields: 0, methods: 0, attributes: 1
+Constant pool:
+{
}
SourceFile: "I1.java"
## Class: com/android/hoststubgen/test/tinyframework/subclasstest/I2.class
@@ -3588,6 +3732,8 @@ public interface com.android.hoststubgen.test.tinyframework.subclasstest.I2 exte
this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/I2
super_class: #x // java/lang/Object
interfaces: 1, fields: 0, methods: 0, attributes: 1
+Constant pool:
+{
}
SourceFile: "I2.java"
## Class: com/android/hoststubgen/test/tinyframework/subclasstest/I3.class
@@ -3599,6 +3745,8 @@ public interface com.android.hoststubgen.test.tinyframework.subclasstest.I3 exte
this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/I3
super_class: #x // java/lang/Object
interfaces: 1, fields: 0, methods: 0, attributes: 1
+Constant pool:
+{
}
SourceFile: "I3.java"
## Class: com/android/hoststubgen/test/tinyframework/subclasstest/IA.class
@@ -3610,6 +3758,8 @@ public interface com.android.hoststubgen.test.tinyframework.subclasstest.IA
this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/IA
super_class: #x // java/lang/Object
interfaces: 0, fields: 0, methods: 0, attributes: 1
+Constant pool:
+{
}
SourceFile: "IA.java"
## Class: com/android/hoststubgen/test/tinyframework/subclasstest/IB.class
@@ -3621,6 +3771,8 @@ public interface com.android.hoststubgen.test.tinyframework.subclasstest.IB
this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/IB
super_class: #x // java/lang/Object
interfaces: 0, fields: 0, methods: 0, attributes: 1
+Constant pool:
+{
}
SourceFile: "IB.java"
## Class: com/supported/UnsupportedClass.class
@@ -3632,6 +3784,8 @@ public class com.supported.UnsupportedClass
this_class: #x // com/supported/UnsupportedClass
super_class: #x // java/lang/Object
interfaces: 0, fields: 1, methods: 2, attributes: 2
+Constant pool:
+{
private final int mValue;
descriptor: I
flags: (0x0012) ACC_PRIVATE, ACC_FINAL
@@ -3679,6 +3833,8 @@ public class com.unsupported.UnsupportedClass
this_class: #x // com/unsupported/UnsupportedClass
super_class: #x // java/lang/Object
interfaces: 0, fields: 0, methods: 2, attributes: 2
+Constant pool:
+{
public com.unsupported.UnsupportedClass(int);
descriptor: (I)V
flags: (0x0001) ACC_PUBLIC
diff --git a/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotations.java b/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotations.java
index 3415deb957ed..674937d15424 100644
--- a/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotations.java
+++ b/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotations.java
@@ -28,7 +28,7 @@ import android.hosttest.annotation.HostSideTestThrow;
@HostSideTestKeep
@HostSideTestClassLoadHook(
"com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHook.onClassLoaded")
-public class TinyFrameworkAnnotations {
+public final class TinyFrameworkAnnotations {
@HostSideTestKeep
public TinyFrameworkAnnotations() {
}
@@ -42,7 +42,7 @@ public class TinyFrameworkAnnotations {
public int remove;
@HostSideTestKeep
- public int addOne(int value) {
+ public final int addOne(int value) {
return value + 1;
}
@@ -61,10 +61,10 @@ public class TinyFrameworkAnnotations {
}
@HostSideTestSubstitute(suffix = "_host")
- public static native int nativeAddThree(int value);
+ public final static native int nativeAddThree(int value);
// This method is private, but at runtime, it'll inherit the visibility of the original method
- private static int nativeAddThree_host(int value) {
+ private final static int nativeAddThree_host(int value) {
return value + 3;
}
diff --git a/services/accessibility/java/com/android/server/accessibility/AutoclickController.java b/services/accessibility/java/com/android/server/accessibility/AutoclickController.java
index b94fa2f59162..8b758d29a2ac 100644
--- a/services/accessibility/java/com/android/server/accessibility/AutoclickController.java
+++ b/services/accessibility/java/com/android/server/accessibility/AutoclickController.java
@@ -39,6 +39,8 @@ import android.view.MotionEvent.PointerProperties;
import android.view.WindowManager;
import android.view.accessibility.AccessibilityManager;
+import androidx.annotation.VisibleForTesting;
+
/**
* Implements "Automatically click on mouse stop" feature.
*
@@ -69,10 +71,10 @@ public class AutoclickController extends BaseEventStreamTransformation {
private final int mUserId;
// Lazily created on the first mouse motion event.
- private ClickScheduler mClickScheduler;
- private AutoclickSettingsObserver mAutoclickSettingsObserver;
- private AutoclickIndicatorScheduler mAutoclickIndicatorScheduler;
- private AutoclickIndicatorView mAutoclickIndicatorView;
+ @VisibleForTesting ClickScheduler mClickScheduler;
+ @VisibleForTesting AutoclickSettingsObserver mAutoclickSettingsObserver;
+ @VisibleForTesting AutoclickIndicatorScheduler mAutoclickIndicatorScheduler;
+ @VisibleForTesting AutoclickIndicatorView mAutoclickIndicatorView;
private WindowManager mWindowManager;
public AutoclickController(Context context, int userId, AccessibilityTraceManager trace) {
@@ -360,7 +362,8 @@ public class AutoclickController extends BaseEventStreamTransformation {
* moving. The click is first scheduled when a mouse movement is detected, and then further
* delayed on every sufficient mouse movement.
*/
- final private class ClickScheduler implements Runnable {
+ @VisibleForTesting
+ final class ClickScheduler implements Runnable {
/**
* Minimal distance pointer has to move relative to anchor in order for movement not to be
* discarded as noise. Anchor is the position of the last MOVE event that was not considered
@@ -474,6 +477,11 @@ public class AutoclickController extends BaseEventStreamTransformation {
}
}
+ @VisibleForTesting
+ int getDelayForTesting() {
+ return mDelay;
+ }
+
/**
* Updates the time at which click sequence should occur.
*
diff --git a/services/accessibility/java/com/android/server/accessibility/AutoclickIndicatorView.java b/services/accessibility/java/com/android/server/accessibility/AutoclickIndicatorView.java
index bf5015176f8c..f87dcdb200bb 100644
--- a/services/accessibility/java/com/android/server/accessibility/AutoclickIndicatorView.java
+++ b/services/accessibility/java/com/android/server/accessibility/AutoclickIndicatorView.java
@@ -28,6 +28,8 @@ import android.view.View;
import android.view.accessibility.AccessibilityManager;
import android.view.animation.LinearInterpolator;
+import androidx.annotation.VisibleForTesting;
+
// A visual indicator for the autoclick feature.
public class AutoclickIndicatorView extends View {
private static final String TAG = AutoclickIndicatorView.class.getSimpleName();
@@ -37,7 +39,7 @@ public class AutoclickIndicatorView extends View {
static final int MINIMAL_ANIMATION_DURATION = 50;
- private float mRadius = AUTOCLICK_CURSOR_AREA_SIZE_DEFAULT;
+ private int mRadius = AUTOCLICK_CURSOR_AREA_SIZE_DEFAULT;
private final Paint mPaint;
@@ -112,6 +114,11 @@ public class AutoclickIndicatorView extends View {
mRadius = radius;
}
+ @VisibleForTesting
+ int getRadiusForTesting() {
+ return mRadius;
+ }
+
public void redrawIndicator() {
showIndicator = true;
invalidate();
diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java b/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java
index 0cbbf6da022b..2c106d31ae59 100644
--- a/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java
@@ -16,8 +16,6 @@
package com.android.server.accessibility.gestures;
-import static android.accessibilityservice.AccessibilityTrace.FLAGS_GESTURE;
-import static android.accessibilityservice.AccessibilityTrace.FLAGS_INPUT_FILTER;
import static android.view.MotionEvent.ACTION_CANCEL;
import static android.view.MotionEvent.ACTION_DOWN;
import static android.view.MotionEvent.ACTION_HOVER_ENTER;
@@ -86,8 +84,6 @@ import java.util.List;
public class TouchExplorer extends BaseEventStreamTransformation
implements GestureManifold.Listener {
- private static final long LOGGING_FLAGS = FLAGS_GESTURE | FLAGS_INPUT_FILTER;
-
// Tag for logging received events.
private static final String LOG_TAG = "TouchExplorer";
@@ -261,10 +257,6 @@ public class TouchExplorer extends BaseEventStreamTransformation
@Override
public void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
- if (mAms.getTraceManager().isA11yTracingEnabledForTypes(LOGGING_FLAGS)) {
- mAms.getTraceManager().logTrace(LOG_TAG + ".onMotionEvent", LOGGING_FLAGS,
- "event=" + event + ";rawEvent=" + rawEvent + ";policyFlags=" + policyFlags);
- }
if (!event.isFromSource(InputDevice.SOURCE_TOUCHSCREEN)) {
super.onMotionEvent(event, rawEvent, policyFlags);
return;
@@ -323,9 +315,8 @@ public class TouchExplorer extends BaseEventStreamTransformation
@Override
public void onAccessibilityEvent(AccessibilityEvent event) {
- if (mAms.getTraceManager().isA11yTracingEnabledForTypes(LOGGING_FLAGS)) {
- mAms.getTraceManager().logTrace(LOG_TAG + ".onAccessibilityEvent",
- LOGGING_FLAGS, "event=" + event);
+ if (DEBUG) {
+ Slog.v(LOG_TAG, "Received A11y Event. event=" + event);
}
final int eventType = event.getEventType();
@@ -383,9 +374,9 @@ public class TouchExplorer extends BaseEventStreamTransformation
@Override
public void onDoubleTapAndHold(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
- if (mAms.getTraceManager().isA11yTracingEnabledForTypes(LOGGING_FLAGS)) {
- mAms.getTraceManager().logTrace(LOG_TAG + ".onDoubleTapAndHold", LOGGING_FLAGS,
- "event=" + event + ";rawEvent=" + rawEvent + ";policyFlags=" + policyFlags);
+ if (DEBUG) {
+ Slog.i(LOG_TAG, "Double tap and hold. event="
+ + event + ";rawEvent=" + rawEvent + ";policyFlags=" + policyFlags);
}
if (mDispatcher.longPressWithTouchEvents(event, policyFlags)) {
sendHoverExitAndTouchExplorationGestureEndIfNeeded(policyFlags);
@@ -403,9 +394,9 @@ public class TouchExplorer extends BaseEventStreamTransformation
@Override
public boolean onDoubleTap(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
- if (mAms.getTraceManager().isA11yTracingEnabledForTypes(LOGGING_FLAGS)) {
- mAms.getTraceManager().logTrace(LOG_TAG + ".onDoubleTap", LOGGING_FLAGS,
- "event=" + event + ";rawEvent=" + rawEvent + ";policyFlags=" + policyFlags);
+ if (DEBUG) {
+ Slog.i(LOG_TAG, "Double tap. event="
+ + event + ";rawEvent=" + rawEvent + ";policyFlags=" + policyFlags);
}
mAms.onTouchInteractionEnd();
// Remove pending event deliveries.
@@ -463,8 +454,8 @@ public class TouchExplorer extends BaseEventStreamTransformation
@Override
public boolean onGestureStarted() {
- if (mAms.getTraceManager().isA11yTracingEnabledForTypes(LOGGING_FLAGS)) {
- mAms.getTraceManager().logTrace(LOG_TAG + ".onGestureStarted", LOGGING_FLAGS);
+ if (DEBUG) {
+ Slog.i(LOG_TAG, "Gesture started.");
}
// We have to perform gesture detection, so
// clear the current state and try to detect.
@@ -479,9 +470,8 @@ public class TouchExplorer extends BaseEventStreamTransformation
@Override
public boolean onGestureCompleted(AccessibilityGestureEvent gestureEvent) {
- if (mAms.getTraceManager().isA11yTracingEnabledForTypes(LOGGING_FLAGS)) {
- mAms.getTraceManager().logTrace(LOG_TAG + ".onGestureCompleted",
- LOGGING_FLAGS, "event=" + gestureEvent);
+ if (DEBUG) {
+ Slog.i(LOG_TAG, "Gesture completed. gestureEvent=" + gestureEvent);
}
endGestureDetection(true);
mSendTouchInteractionEndDelayed.cancel();
@@ -491,10 +481,11 @@ public class TouchExplorer extends BaseEventStreamTransformation
@Override
public boolean onGestureCancelled(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
- if (mAms.getTraceManager().isA11yTracingEnabledForTypes(LOGGING_FLAGS)) {
- mAms.getTraceManager().logTrace(LOG_TAG + ".onGestureCancelled", LOGGING_FLAGS,
- "event=" + event + ";rawEvent=" + rawEvent + ";policyFlags=" + policyFlags);
+ if (DEBUG) {
+ Slog.i(LOG_TAG, "Gesture cancelled. event="
+ + event + ";rawEvent=" + rawEvent + ";policyFlags=" + policyFlags);
}
+
if (mState.isGestureDetecting()) {
endGestureDetection(event.getActionMasked() == ACTION_UP);
return true;
diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
index cffdfbd36532..8ef44ad31021 100644
--- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
+++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
@@ -5666,6 +5666,16 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
}
public boolean canAccessAppWidget(Widget widget, int uid, String packageName) {
+ if (packageName != null
+ && widget.provider != null
+ && isDifferentPackageFromProvider(widget.provider, packageName)
+ && widget.host != null
+ && isDifferentPackageFromHost(widget.host, packageName)) {
+ // An AppWidget can only be accessed by either
+ // 1. The package that provides the AppWidget.
+ // 2. The package that hosts the AppWidget.
+ return false;
+ }
if (isHostInPackageForUid(widget.host, uid, packageName)) {
// Apps hosting the AppWidget have access to it.
return true;
@@ -5768,6 +5778,22 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
&& provider.id.componentName.getPackageName().equals(packageName);
}
+ private boolean isDifferentPackageFromHost(
+ @NonNull final Host host, @NonNull final String packageName) {
+ if (host.id == null || host.id.packageName == null) {
+ return true;
+ }
+ return !packageName.equals(host.id.packageName);
+ }
+
+ private boolean isDifferentPackageFromProvider(
+ @NonNull final Provider provider, @NonNull final String packageName) {
+ if (provider.id == null || provider.id.componentName == null) {
+ return true;
+ }
+ return !packageName.equals(provider.id.componentName.getPackageName());
+ }
+
private boolean isProfileEnabled(int profileId) {
final long identity = Binder.clearCallingIdentity();
try {
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index 0e2e50589217..a37b2b926c9c 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -612,7 +612,7 @@ public class CompanionDeviceManagerService extends SystemService {
@Override
public void enablePermissionsSync(int associationId) {
- if (getCallingUid() != SYSTEM_UID) {
+ if (UserHandle.getAppId(Binder.getCallingUid()) == SYSTEM_UID) {
throw new SecurityException("Caller must be system UID");
}
mSystemDataTransferProcessor.enablePermissionsSync(associationId);
@@ -620,7 +620,7 @@ public class CompanionDeviceManagerService extends SystemService {
@Override
public void disablePermissionsSync(int associationId) {
- if (getCallingUid() != SYSTEM_UID) {
+ if (UserHandle.getAppId(Binder.getCallingUid()) == SYSTEM_UID) {
throw new SecurityException("Caller must be system UID");
}
mSystemDataTransferProcessor.disablePermissionsSync(associationId);
@@ -628,7 +628,7 @@ public class CompanionDeviceManagerService extends SystemService {
@Override
public PermissionSyncRequest getPermissionSyncRequest(int associationId) {
- if (getCallingUid() != SYSTEM_UID) {
+ if (UserHandle.getAppId(Binder.getCallingUid()) == SYSTEM_UID) {
throw new SecurityException("Caller must be system UID");
}
return mSystemDataTransferProcessor.getPermissionSyncRequest(associationId);
@@ -704,7 +704,7 @@ public class CompanionDeviceManagerService extends SystemService {
@Override
public byte[] getBackupPayload(int userId) {
- if (getCallingUid() != SYSTEM_UID) {
+ if (UserHandle.getAppId(Binder.getCallingUid()) == SYSTEM_UID) {
throw new SecurityException("Caller must be system");
}
return mBackupRestoreProcessor.getBackupPayload(userId);
@@ -712,7 +712,7 @@ public class CompanionDeviceManagerService extends SystemService {
@Override
public void applyRestoredPayload(byte[] payload, int userId) {
- if (getCallingUid() != SYSTEM_UID) {
+ if (UserHandle.getAppId(Binder.getCallingUid()) == SYSTEM_UID) {
throw new SecurityException("Caller must be system");
}
mBackupRestoreProcessor.applyRestoredPayload(payload, userId);
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
index 280494544e3b..3508f2ffc4c4 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
@@ -106,7 +106,7 @@ class CompanionDeviceShellCommand extends ShellCommand {
boolean selfManaged = getNextBooleanArg();
final MacAddress macAddress = MacAddress.fromString(address);
mAssociationRequestsProcessor.createAssociation(userId, packageName, macAddress,
- deviceProfile, deviceProfile, /* associatedDevice */ null, false,
+ deviceProfile, deviceProfile, /* associatedDevice */ null, selfManaged,
/* callback */ null, /* resultReceiver */ null, /* deviceIcon */ null);
}
break;
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
index f401e6b66093..c385fbad02a5 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
@@ -490,7 +490,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
? mParams.getBlockedActivities()
: mParams.getAllowedActivities());
- if (Flags.vdmCustomIme() && mParams.getInputMethodComponent() != null) {
+ if (mParams.getInputMethodComponent() != null) {
final String imeId = mParams.getInputMethodComponent().flattenToShortString();
Slog.d(TAG, "Setting custom input method " + imeId + " as default for virtual device "
+ deviceId);
@@ -807,7 +807,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
}
// Clear any previously set custom IME components.
- if (Flags.vdmCustomIme() && mParams.getInputMethodComponent() != null) {
+ if (mParams.getInputMethodComponent() != null) {
InputMethodManagerInternal.get().setVirtualDeviceInputMethodForAllUsers(
mDeviceId, null);
}
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
index 0b335d318d64..4dd8b141386f 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
@@ -820,13 +820,12 @@ public class VirtualDeviceManagerService extends SystemService {
@Override
public void onAuthenticationPrompt(int uid) {
- synchronized (mVirtualDeviceManagerLock) {
- for (int i = 0; i < mVirtualDevices.size(); i++) {
- VirtualDeviceImpl device = mVirtualDevices.valueAt(i);
- device.showToastWhereUidIsRunning(uid,
- R.string.app_streaming_blocked_message_for_fingerprint_dialog,
- Toast.LENGTH_LONG, Looper.getMainLooper());
- }
+ ArrayList<VirtualDeviceImpl> virtualDevicesSnapshot = getVirtualDevicesSnapshot();
+ for (int i = 0; i < virtualDevicesSnapshot.size(); i++) {
+ VirtualDeviceImpl device = virtualDevicesSnapshot.get(i);
+ device.showToastWhereUidIsRunning(uid,
+ R.string.app_streaming_blocked_message_for_fingerprint_dialog,
+ Toast.LENGTH_LONG, Looper.getMainLooper());
}
}
diff --git a/services/contextualsearch/java/com/android/server/contextualsearch/ContextualSearchManagerService.java b/services/contextualsearch/java/com/android/server/contextualsearch/ContextualSearchManagerService.java
index abfb8268bd9a..df47c98d6433 100644
--- a/services/contextualsearch/java/com/android/server/contextualsearch/ContextualSearchManagerService.java
+++ b/services/contextualsearch/java/com/android/server/contextualsearch/ContextualSearchManagerService.java
@@ -98,6 +98,8 @@ 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
+ private static final boolean DEBUG = false;
+
private final Context mContext;
private final ActivityTaskManagerInternal mAtmInternal;
private final PackageManagerInternal mPackageManager;
@@ -121,6 +123,7 @@ public class ContextualSearchManagerService extends SystemService {
final Bundle data,
final int activityIndex,
final int activityCount) {
+
final IContextualSearchCallback callback;
synchronized (mLock) {
callback = mStateCallback;
@@ -160,7 +163,7 @@ public class ContextualSearchManagerService extends SystemService {
public ContextualSearchManagerService(@NonNull Context context) {
super(context);
- if (DEBUG_USER) Log.d(TAG, "ContextualSearchManagerService created");
+ if (DEBUG) Log.d(TAG, "ContextualSearchManagerService created");
mContext = context;
mAtmInternal = Objects.requireNonNull(
LocalServices.getService(ActivityTaskManagerInternal.class));
@@ -206,7 +209,7 @@ public class ContextualSearchManagerService extends SystemService {
mTemporaryHandler.removeMessages(MSG_RESET_TEMPORARY_PACKAGE);
mTemporaryHandler = null;
}
- if (DEBUG_USER) Log.d(TAG, "mTemporaryPackage reset.");
+ if (DEBUG) Log.d(TAG, "mTemporaryPackage reset.");
mTemporaryPackage = null;
updateSecureSetting();
}
@@ -239,7 +242,7 @@ public class ContextualSearchManagerService extends SystemService {
mTemporaryPackage = temporaryPackage;
updateSecureSetting();
mTemporaryHandler.sendEmptyMessageDelayed(MSG_RESET_TEMPORARY_PACKAGE, durationMs);
- if (DEBUG_USER) Log.d(TAG, "mTemporaryPackage set to " + mTemporaryPackage);
+ if (DEBUG) Log.d(TAG, "mTemporaryPackage set to " + mTemporaryPackage);
}
}
@@ -256,7 +259,7 @@ public class ContextualSearchManagerService extends SystemService {
+ durationMs + ")");
}
mTokenValidDurationMs = durationMs;
- if (DEBUG_USER) Log.d(TAG, "mTokenValidDurationMs set to " + durationMs);
+ if (DEBUG) Log.d(TAG, "mTokenValidDurationMs set to " + durationMs);
}
}
@@ -268,12 +271,12 @@ public class ContextualSearchManagerService extends SystemService {
private Intent getResolvedLaunchIntent(int userId) {
synchronized (this) {
- if(DEBUG_USER) Log.d(TAG, "Attempting to getResolvedLaunchIntent");
+ if(DEBUG) Log.d(TAG, "Attempting to getResolvedLaunchIntent");
// If mTemporaryPackage is not null, use it to get the ContextualSearch intent.
String csPkgName = getContextualSearchPackageName();
if (csPkgName.isEmpty()) {
// Return null if csPackageName is not specified.
- if (DEBUG_USER) Log.w(TAG, "getContextualSearchPackageName is empty");
+ if (DEBUG) Log.w(TAG, "getContextualSearchPackageName is empty");
return null;
}
Intent launchIntent = new Intent(
@@ -282,12 +285,12 @@ public class ContextualSearchManagerService extends SystemService {
ResolveInfo resolveInfo = mContext.getPackageManager().resolveActivityAsUser(
launchIntent, MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE, userId);
if (resolveInfo == null) {
- if (DEBUG_USER) Log.w(TAG, "resolveInfo is null");
+ if (DEBUG) Log.w(TAG, "resolveInfo is null");
return null;
}
ComponentName componentName = resolveInfo.getComponentInfo().getComponentName();
if (componentName == null) {
- if (DEBUG_USER) Log.w(TAG, "componentName is null");
+ if (DEBUG) Log.w(TAG, "componentName is null");
return null;
}
launchIntent.setComponent(componentName);
@@ -298,11 +301,11 @@ public class ContextualSearchManagerService extends SystemService {
private Intent getContextualSearchIntent(int entrypoint, int userId, CallbackToken mToken) {
final Intent launchIntent = getResolvedLaunchIntent(userId);
if (launchIntent == null) {
- if (DEBUG_USER) Log.w(TAG, "Failed getContextualSearchIntent: launchIntent is null");
+ if (DEBUG) Log.w(TAG, "Failed getContextualSearchIntent: launchIntent is null");
return null;
}
- if (DEBUG_USER) Log.d(TAG, "Launch component: " + launchIntent.getComponent());
+ if (DEBUG) Log.d(TAG, "Launch component: " + launchIntent.getComponent());
launchIntent.addFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_NO_ANIMATION
| FLAG_ACTIVITY_NO_USER_ACTION | FLAG_ACTIVITY_CLEAR_TASK);
launchIntent.putExtra(
@@ -355,7 +358,7 @@ public class ContextualSearchManagerService extends SystemService {
TYPE_NAVIGATION_BAR_PANEL,
TYPE_POINTER));
} else {
- if (DEBUG_USER) Log.w(TAG, "Can't capture contextual screenshot: mWmInternal is null");
+ if (DEBUG) Log.w(TAG, "Can't capture contextual screenshot: mWmInternal is null");
shb = null;
}
final Bitmap bm = shb != null ? shb.asBitmap() : null;
@@ -429,7 +432,7 @@ public class ContextualSearchManagerService extends SystemService {
mTokenHandler.removeMessages(MSG_INVALIDATE_TOKEN);
mTokenHandler = null;
}
- if (DEBUG_USER) Log.d(TAG, "mToken invalidated.");
+ if (DEBUG) Log.d(TAG, "mToken invalidated.");
mToken = null;
}
}
@@ -459,7 +462,7 @@ public class ContextualSearchManagerService extends SystemService {
@Override
public void startContextualSearch(int entrypoint) {
synchronized (this) {
- if (DEBUG_USER) Log.d(TAG, "startContextualSearch entrypoint: " + entrypoint);
+ if (DEBUG) Log.d(TAG, "startContextualSearch entrypoint: " + entrypoint);
enforcePermission("startContextualSearch");
final int callingUserId = Binder.getCallingUserHandle().getIdentifier();
@@ -474,7 +477,7 @@ public class ContextualSearchManagerService extends SystemService {
getContextualSearchIntent(entrypoint, callingUserId, mToken);
if (launchIntent != null) {
int result = invokeContextualSearchIntent(launchIntent, callingUserId);
- if (DEBUG_USER) Log.d(TAG, "Launch result: " + result);
+ if (DEBUG) Log.d(TAG, "Launch result: " + result);
}
});
}
@@ -484,11 +487,11 @@ public class ContextualSearchManagerService extends SystemService {
public void getContextualSearchState(
@NonNull IBinder token,
@NonNull IContextualSearchCallback callback) {
- if (DEBUG_USER) {
+ if (DEBUG) {
Log.i(TAG, "getContextualSearchState token: " + token + ", callback: " + callback);
}
if (mToken == null || !mToken.getToken().equals(token)) {
- if (DEBUG_USER) {
+ if (DEBUG) {
Log.e(TAG, "getContextualSearchState: invalid token, returning error");
}
try {
diff --git a/services/core/java/com/android/server/BootReceiver.java b/services/core/java/com/android/server/BootReceiver.java
index 1588e0421675..7a5b8660ef7c 100644
--- a/services/core/java/com/android/server/BootReceiver.java
+++ b/services/core/java/com/android/server/BootReceiver.java
@@ -40,7 +40,9 @@ import android.util.AtomicFile;
import android.util.EventLog;
import android.util.Slog;
import android.util.Xml;
+import android.util.proto.ProtoFieldFilter;
import android.util.proto.ProtoOutputStream;
+import android.util.proto.ProtoParseException;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.MetricsLogger;
@@ -49,10 +51,13 @@ import com.android.internal.util.XmlUtils;
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;
import com.android.server.am.DropboxRateLimiter;
+import com.android.server.os.TombstoneProtos.Tombstone;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileDescriptor;
@@ -64,6 +69,7 @@ import java.nio.file.Files;
import java.nio.file.attribute.PosixFilePermissions;
import java.util.HashMap;
import java.util.Iterator;
+import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -392,6 +398,129 @@ public class BootReceiver extends BroadcastReceiver {
writeTimestamps(timestamps);
}
+ /**
+ * Processes a tombstone file and adds it to the DropBox after filtering and applying
+ * rate limiting.
+ * Filtering removes memory sections from the tombstone proto to reduce size while preserving
+ * critical information. The filtered tombstone is then added to DropBox in both proto
+ * and text formats, with the text format derived from the filtered proto.
+ * Rate limiting is applied as it is the case with other crash types.
+ *
+ * @param ctx Context
+ * @param tombstone path to the tombstone
+ * @param processName the name of the process corresponding to the tombstone
+ * @param tmpFileLock the lock for reading/writing tmp files
+ */
+ public static void filterAndAddTombstoneToDropBox(
+ Context ctx, File tombstone, String processName, ReentrantLock tmpFileLock) {
+ final DropBoxManager db = ctx.getSystemService(DropBoxManager.class);
+ if (db == null) {
+ Slog.e(TAG, "Can't log tombstone: DropBoxManager not available");
+ return;
+ }
+ File filteredProto = null;
+ // Check if we should rate limit and abort early if needed.
+ DropboxRateLimiter.RateLimitResult rateLimitResult =
+ sDropboxRateLimiter.shouldRateLimit(TAG_TOMBSTONE_PROTO_WITH_HEADERS, processName);
+ if (rateLimitResult.shouldRateLimit()) return;
+
+ HashMap<String, Long> timestamps = readTimestamps();
+ try {
+ tmpFileLock.lock();
+ Slog.i(TAG, "Filtering tombstone file: " + tombstone.getName());
+ // Create a temporary tombstone without memory sections.
+ filteredProto = createTempTombstoneWithoutMemory(tombstone);
+ Slog.i(TAG, "Generated tombstone file: " + filteredProto.getName());
+
+ if (recordFileTimestamp(tombstone, timestamps)) {
+ // We need to attach the count indicating the number of dropped dropbox entries
+ // due to rate limiting. Do this by enclosing the proto tombsstone in a
+ // container proto that has the dropped entry count and the proto tombstone as
+ // bytes (to avoid the complexity of reading and writing nested protos).
+ Slog.i(TAG, "Adding tombstone " + filteredProto.getName() + " to dropbox");
+ addAugmentedProtoToDropbox(filteredProto, db, rateLimitResult);
+ }
+ // Always add the text version of the tombstone to the DropBox, in order to
+ // match the previous behaviour.
+ Slog.i(TAG, "Adding text tombstone version of " + filteredProto.getName()
+ + " to dropbox");
+ addTextTombstoneFromProtoToDropbox(filteredProto, db, timestamps, rateLimitResult);
+
+ } catch (IOException | ProtoParseException e) {
+ Slog.e(TAG, "Failed to log tombstone '" + tombstone.getName()
+ + "' to DropBox. Error during processing or writing: " + e.getMessage(), e);
+ } finally {
+ if (filteredProto != null) {
+ filteredProto.delete();
+ }
+ tmpFileLock.unlock();
+ }
+ writeTimestamps(timestamps);
+ }
+
+ /**
+ * Creates a temporary tombstone file by filtering out memory mapping fields.
+ * This ensures that the unneeded memory mapping data is removed from the tombstone
+ * before adding it to Dropbox
+ *
+ * @param tombstone the original tombstone file to process
+ * @return a temporary file containing the filtered tombstone data
+ * @throws IOException if an I/O error occurs during processing
+ */
+ private static File createTempTombstoneWithoutMemory(File tombstone) throws IOException {
+ // Process the proto tombstone file and write it to a temporary file
+ File tombstoneProto =
+ File.createTempFile(tombstone.getName(), ".pb.tmp", TOMBSTONE_TMP_DIR);
+ ProtoFieldFilter protoFilter =
+ new ProtoFieldFilter(fieldNumber -> fieldNumber != (int) Tombstone.MEMORY_MAPPINGS);
+
+ try (FileInputStream fis = new FileInputStream(tombstone);
+ BufferedInputStream bis = new BufferedInputStream(fis);
+ FileOutputStream fos = new FileOutputStream(tombstoneProto);
+ BufferedOutputStream bos = new BufferedOutputStream(fos)) {
+ protoFilter.filter(bis, bos);
+ return tombstoneProto;
+ }
+ }
+
+ private static void addTextTombstoneFromProtoToDropbox(File tombstone, DropBoxManager db,
+ HashMap<String, Long> timestamps, DropboxRateLimiter.RateLimitResult rateLimitResult) {
+ File tombstoneTextFile = null;
+
+ try {
+ tombstoneTextFile = File.createTempFile(tombstone.getName(),
+ ".pb.txt.tmp", TOMBSTONE_TMP_DIR);
+
+ // Create a ProcessBuilder to execute pbtombstone
+ ProcessBuilder pb = new ProcessBuilder("/system/bin/pbtombstone", tombstone.getPath());
+ pb.redirectOutput(tombstoneTextFile);
+ Process process = pb.start();
+
+ // Wait 10 seconds for the process to complete
+ if (!process.waitFor(10, TimeUnit.SECONDS)) {
+ Slog.e(TAG, "pbtombstone timed out");
+ process.destroyForcibly();
+ return;
+ }
+
+ int exitCode = process.exitValue();
+ if (exitCode != 0) {
+ Slog.e(TAG, "pbtombstone failed with exit code " + exitCode);
+ } else {
+ final String headers = getBootHeadersToLogAndUpdate()
+ + rateLimitResult.createHeader();
+ addFileToDropBox(db, timestamps, headers, tombstoneTextFile.getPath(), LOG_SIZE,
+ TAG_TOMBSTONE);
+ }
+ } catch (IOException | InterruptedException e) {
+ Slog.e(TAG, "Failed to process tombstone with pbtombstone", e);
+ } finally {
+ if (tombstoneTextFile != null) {
+ tombstoneTextFile.delete();
+ }
+ }
+ }
+
private static void addAugmentedProtoToDropbox(
File tombstone, DropBoxManager db,
DropboxRateLimiter.RateLimitResult rateLimitResult) throws IOException {
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index 2216f2769826..f7eaa159be30 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -2553,13 +2553,12 @@ public final class ProcessList {
final AppZygote appZygote = createAppZygoteForProcessIfNeeded(app);
// We can't isolate app data and storage data as parent zygote already did that.
- startResult = appZygote.getProcess().start(entryPoint,
- app.processName, uid, uid, gids, runtimeFlags, mountExternal,
+ startResult = appZygote.startProcess(entryPoint,
+ app.processName, uid, gids, runtimeFlags, mountExternal,
app.info.targetSdkVersion, seInfo, requiredAbi, instructionSet,
- app.info.dataDir, null, app.info.packageName,
- /*zygotePolicyFlags=*/ ZYGOTE_POLICY_FLAG_EMPTY, isTopApp,
- app.getDisabledCompatChanges(), pkgDataInfoMap, allowlistedAppDataInfoMap,
- false, false, false,
+ app.info.dataDir, app.info.packageName, isTopApp,
+ app.getDisabledCompatChanges(), pkgDataInfoMap,
+ allowlistedAppDataInfoMap,
new String[]{PROC_START_SEQ_IDENT + app.getStartSeq()});
} else {
regularZygote = true;
diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
index d3a52543f321..a54a66312b1d 100644
--- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
+++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
@@ -16,12 +16,23 @@
package com.android.server.am;
+import static com.android.aconfig_new_storage.Flags.enableAconfigStorageDaemon;
+import static com.android.aconfig_new_storage.Flags.enableAconfigdFromMainline;
+import static com.android.aconfig_new_storage.Flags.supportClearLocalOverridesImmediately;
+import static com.android.aconfig_new_storage.Flags.supportImmediateLocalOverrides;
+
+import static java.util.concurrent.Executors.newSingleThreadScheduledExecutor;
+
+import android.aconfigd.Aconfigd.StorageRequestMessage;
+import android.aconfigd.Aconfigd.StorageRequestMessages;
+import android.aconfigd.Aconfigd.StorageReturnMessage;
+import android.aconfigd.Aconfigd.StorageReturnMessages;
import android.annotation.NonNull;
import android.content.ContentResolver;
import android.database.ContentObserver;
-import android.net.Uri;
-import android.net.LocalSocketAddress;
import android.net.LocalSocket;
+import android.net.LocalSocketAddress;
+import android.net.Uri;
import android.os.AsyncTask;
import android.os.Build;
import android.os.SystemProperties;
@@ -35,28 +46,13 @@ import android.util.proto.ProtoOutputStream;
import com.android.internal.annotations.VisibleForTesting;
import com.android.providers.settings.Flags;
-import android.aconfigd.Aconfigd.StorageRequestMessage;
-import android.aconfigd.Aconfigd.StorageRequestMessages;
-import android.aconfigd.Aconfigd.StorageReturnMessage;
-import android.aconfigd.Aconfigd.StorageReturnMessages;
-import static com.android.aconfig_new_storage.Flags.enableAconfigStorageDaemon;
-import static com.android.aconfig_new_storage.Flags.supportImmediateLocalOverrides;
-import static com.android.aconfig_new_storage.Flags.supportClearLocalOverridesImmediately;
-import static com.android.aconfig_new_storage.Flags.enableAconfigdFromMainline;
-
+import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.DataOutputStream;
-import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.HashSet;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.List;
-import java.util.ArrayList;
-import java.util.Set;
-import static java.util.concurrent.Executors.newSingleThreadScheduledExecutor;
/**
* Maps system settings to system properties.
@@ -271,6 +267,7 @@ public class SettingsToPropertiesMapper {
"wear_sysui",
"wear_system_managed_surfaces",
"wear_watchfaces",
+ "web_apps_on_chromeos_and_android",
"window_surfaces",
"windowing_frontend",
"xr",
diff --git a/services/core/java/com/android/server/am/UidObserverController.java b/services/core/java/com/android/server/am/UidObserverController.java
index f58e3f85bb26..23edafcc454b 100644
--- a/services/core/java/com/android/server/am/UidObserverController.java
+++ b/services/core/java/com/android/server/am/UidObserverController.java
@@ -78,6 +78,7 @@ public class UidObserverController {
* This is for verifying the UID report flow.
*/
private static final boolean VALIDATE_UID_STATES = true;
+ @GuardedBy("mLock")
private final ActiveUids mValidateUids;
UidObserverController(@NonNull Handler handler) {
@@ -285,31 +286,30 @@ public class UidObserverController {
}
mUidObservers.finishBroadcast();
- if (VALIDATE_UID_STATES && mUidObservers.getRegisteredCallbackCount() > 0) {
- for (int j = 0; j < numUidChanges; ++j) {
- final ChangeRecord item = mActiveUidChanges[j];
- if ((item.change & UidRecord.CHANGE_GONE) != 0) {
- mValidateUids.remove(item.uid);
- } else {
- UidRecord validateUid = mValidateUids.get(item.uid);
- if (validateUid == null) {
- validateUid = new UidRecord(item.uid, null);
- mValidateUids.put(item.uid, validateUid);
- }
- if ((item.change & UidRecord.CHANGE_IDLE) != 0) {
- validateUid.setIdle(true);
- } else if ((item.change & UidRecord.CHANGE_ACTIVE) != 0) {
- validateUid.setIdle(false);
+ synchronized (mLock) {
+ if (VALIDATE_UID_STATES && mUidObservers.getRegisteredCallbackCount() > 0) {
+ for (int j = 0; j < numUidChanges; ++j) {
+ final ChangeRecord item = mActiveUidChanges[j];
+ if ((item.change & UidRecord.CHANGE_GONE) != 0) {
+ mValidateUids.remove(item.uid);
+ } else {
+ UidRecord validateUid = mValidateUids.get(item.uid);
+ if (validateUid == null) {
+ validateUid = new UidRecord(item.uid, null);
+ mValidateUids.put(item.uid, validateUid);
+ }
+ if ((item.change & UidRecord.CHANGE_IDLE) != 0) {
+ validateUid.setIdle(true);
+ } else if ((item.change & UidRecord.CHANGE_ACTIVE) != 0) {
+ validateUid.setIdle(false);
+ }
+ validateUid.setSetProcState(item.procState);
+ validateUid.setCurProcState(item.procState);
+ validateUid.setSetCapability(item.capability);
+ validateUid.setCurCapability(item.capability);
}
- validateUid.setSetProcState(item.procState);
- validateUid.setCurProcState(item.procState);
- validateUid.setSetCapability(item.capability);
- validateUid.setCurCapability(item.capability);
}
}
- }
-
- synchronized (mLock) {
for (int j = 0; j < numUidChanges; j++) {
final ChangeRecord changeRecord = mActiveUidChanges[j];
changeRecord.isPending = false;
@@ -436,7 +436,9 @@ public class UidObserverController {
}
UidRecord getValidateUidRecord(int uid) {
- return mValidateUids.get(uid);
+ synchronized (mLock) {
+ return mValidateUids.get(uid);
+ }
}
void dump(@NonNull PrintWriter pw, @Nullable String dumpPackage) {
@@ -491,12 +493,16 @@ public class UidObserverController {
boolean dumpValidateUids(@NonNull PrintWriter pw, @Nullable String dumpPackage, int dumpAppId,
@NonNull String header, boolean needSep) {
- return mValidateUids.dump(pw, dumpPackage, dumpAppId, header, needSep);
+ synchronized (mLock) {
+ return mValidateUids.dump(pw, dumpPackage, dumpAppId, header, needSep);
+ }
}
void dumpValidateUidsProto(@NonNull ProtoOutputStream proto, @Nullable String dumpPackage,
int dumpAppId, long fieldId) {
- mValidateUids.dumpProto(proto, dumpPackage, dumpAppId, fieldId);
+ synchronized (mLock) {
+ mValidateUids.dumpProto(proto, dumpPackage, dumpAppId, fieldId);
+ }
}
static final class ChangeRecord {
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index d2073aaa834c..8e09e3b8e112 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -33,6 +33,7 @@ import static android.app.AppOpsManager.MODE_DEFAULT;
import static android.app.AppOpsManager.MODE_ERRORED;
import static android.app.AppOpsManager.MODE_FOREGROUND;
import static android.app.AppOpsManager.MODE_IGNORED;
+import static android.app.AppOpsManager.OP_BLUETOOTH_CONNECT;
import static android.app.AppOpsManager.OP_CAMERA;
import static android.app.AppOpsManager.OP_CAMERA_SANDBOXED;
import static android.app.AppOpsManager.OP_FLAGS_ALL;
@@ -3267,6 +3268,11 @@ public class AppOpsService extends IAppOpsService.Stub {
packageName);
}
if (!isIncomingPackageValid(packageName, UserHandle.getUserId(uid))) {
+ // TODO(b/333931259): Remove extra logging after this issue is diagnosed.
+ if (code == OP_BLUETOOTH_CONNECT) {
+ Slog.e(TAG, "noting OP_BLUETOOTH_CONNECT returned MODE_ERRORED as incoming "
+ + "package: " + packageName + " and uid: " + uid + " is invalid");
+ }
return new SyncNotedAppOp(AppOpsManager.MODE_ERRORED, code, attributionTag,
packageName);
}
@@ -3306,6 +3312,13 @@ public class AppOpsService extends IAppOpsService.Stub {
}
} catch (SecurityException e) {
logVerifyAndGetBypassFailure(uid, e, "noteOperation");
+ // TODO(b/333931259): Remove extra logging after this issue is diagnosed.
+ if (code == OP_BLUETOOTH_CONNECT) {
+ Slog.e(TAG, "noting OP_BLUETOOTH_CONNECT returned MODE_ERRORED as"
+ + " verifyAndGetBypass returned a SecurityException for package: "
+ + packageName + " and uid: " + uid + " and attributionTag: "
+ + attributionTag, e);
+ }
return new SyncNotedAppOp(AppOpsManager.MODE_ERRORED, code, attributionTag,
packageName);
}
@@ -3323,6 +3336,17 @@ public class AppOpsService extends IAppOpsService.Stub {
if (DEBUG) Slog.d(TAG, "noteOperation: no op for code " + code + " uid " + uid
+ " package " + packageName + "flags: " +
AppOpsManager.flagsToString(flags));
+ // TODO(b/333931259): Remove extra logging after this issue is diagnosed.
+ if (code == OP_BLUETOOTH_CONNECT) {
+ Slog.e(TAG, "noting OP_BLUETOOTH_CONNECT returned MODE_ERRORED as"
+ + " #getOpsLocked returned null for"
+ + " uid: " + uid
+ + " packageName: " + packageName
+ + " attributionTag: " + attributionTag
+ + " pvr.isAttributionTagValid: " + pvr.isAttributionTagValid
+ + " pvr.bypass: " + pvr.bypass);
+ Slog.e(TAG, "mUidStates.get(" + uid + "): " + mUidStates.get(uid));
+ }
return new SyncNotedAppOp(AppOpsManager.MODE_ERRORED, code, attributionTag,
packageName);
}
@@ -3367,6 +3391,11 @@ public class AppOpsService extends IAppOpsService.Stub {
attributedOp.rejected(uidState.getState(), flags);
scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag,
virtualDeviceId, flags, uidMode);
+ // TODO(b/333931259): Remove extra logging after this issue is diagnosed.
+ if (code == OP_BLUETOOTH_CONNECT && uidMode == MODE_ERRORED) {
+ Slog.e(TAG, "noting OP_BLUETOOTH_CONNECT returned MODE_ERRORED as"
+ + " uid mode is MODE_ERRORED");
+ }
return new SyncNotedAppOp(uidMode, code, attributionTag, packageName);
}
} else {
@@ -3386,6 +3415,11 @@ public class AppOpsService extends IAppOpsService.Stub {
attributedOp.rejected(uidState.getState(), flags);
scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag,
virtualDeviceId, flags, mode);
+ // TODO(b/333931259): Remove extra logging after this issue is diagnosed.
+ if (code == OP_BLUETOOTH_CONNECT && mode == MODE_ERRORED) {
+ Slog.e(TAG, "noting OP_BLUETOOTH_CONNECT returned MODE_ERRORED as"
+ + " package mode is MODE_ERRORED");
+ }
return new SyncNotedAppOp(mode, code, attributionTag, packageName);
}
}
diff --git a/services/core/java/com/android/server/appop/DiscreteOpsDbHelper.java b/services/core/java/com/android/server/appop/DiscreteOpsDbHelper.java
index 695032e6476c..86f5d9bd637f 100644
--- a/services/core/java/com/android/server/appop/DiscreteOpsDbHelper.java
+++ b/services/core/java/com/android/server/appop/DiscreteOpsDbHelper.java
@@ -27,9 +27,13 @@ import android.database.sqlite.SQLiteException;
import android.database.sqlite.SQLiteOpenHelper;
import android.database.sqlite.SQLiteRawStatement;
import android.os.Environment;
+import android.os.SystemClock;
+import android.permission.flags.Flags;
import android.util.IntArray;
import android.util.Slog;
+import com.android.internal.util.FrameworkStatsLog;
+
import java.io.File;
import java.util.ArrayList;
import java.util.List;
@@ -76,6 +80,10 @@ class DiscreteOpsDbHelper extends SQLiteOpenHelper {
if (opEvents.isEmpty()) {
return;
}
+ long startTime = 0;
+ if (Flags.sqliteDiscreteOpEventLoggingEnabled()) {
+ startTime = SystemClock.elapsedRealtime();
+ }
SQLiteDatabase db = getWritableDatabase();
// TODO (b/383157289) what if database is busy and can't start a transaction? will read
@@ -117,6 +125,11 @@ class DiscreteOpsDbHelper extends SQLiteOpenHelper {
+ " file size (bytes) : " + getDatabaseFile().length(), exception);
}
}
+ if (Flags.sqliteDiscreteOpEventLoggingEnabled()) {
+ long timeTaken = SystemClock.elapsedRealtime() - startTime;
+ FrameworkStatsLog.write(FrameworkStatsLog.SQLITE_DISCRETE_OP_EVENT_REPORTED,
+ -1, timeTaken, getDatabaseFile().length());
+ }
}
private void bindTextOrNull(SQLiteRawStatement statement, int index, @Nullable String text) {
@@ -181,7 +194,10 @@ class DiscreteOpsDbHelper extends SQLiteOpenHelper {
uidFilter, packageNameFilter,
attributionTagFilter, opCodesFilter, opFlagsFilter);
String sql = buildSql(conditions, orderByColumn, limit);
-
+ long startTime = 0;
+ if (Flags.sqliteDiscreteOpEventLoggingEnabled()) {
+ startTime = SystemClock.elapsedRealtime();
+ }
SQLiteDatabase db = getReadableDatabase();
List<DiscreteOpsSqlRegistry.DiscreteOp> results = new ArrayList<>();
db.beginTransactionReadOnly();
@@ -225,6 +241,11 @@ class DiscreteOpsDbHelper extends SQLiteOpenHelper {
} finally {
db.endTransaction();
}
+ if (Flags.sqliteDiscreteOpEventLoggingEnabled()) {
+ long timeTaken = SystemClock.elapsedRealtime() - startTime;
+ FrameworkStatsLog.write(FrameworkStatsLog.SQLITE_DISCRETE_OP_EVENT_REPORTED,
+ timeTaken, -1, getDatabaseFile().length());
+ }
return results;
}
diff --git a/services/core/java/com/android/server/appop/HistoricalRegistry.java b/services/core/java/com/android/server/appop/HistoricalRegistry.java
index ba391d0a9995..5aa2a6b60106 100644
--- a/services/core/java/com/android/server/appop/HistoricalRegistry.java
+++ b/services/core/java/com/android/server/appop/HistoricalRegistry.java
@@ -205,18 +205,8 @@ final class HistoricalRegistry {
mContext = context;
if (Flags.enableSqliteAppopsAccesses()) {
mDiscreteRegistry = new DiscreteOpsSqlRegistry(context);
- if (DiscreteOpsXmlRegistry.getDiscreteOpsDir().exists()) {
- DiscreteOpsSqlRegistry sqlRegistry = (DiscreteOpsSqlRegistry) mDiscreteRegistry;
- DiscreteOpsXmlRegistry xmlRegistry = new DiscreteOpsXmlRegistry(context);
- DiscreteOpsMigrationHelper.migrateDiscreteOpsToSqlite(xmlRegistry, sqlRegistry);
- }
} else {
mDiscreteRegistry = new DiscreteOpsXmlRegistry(context);
- if (DiscreteOpsDbHelper.getDatabaseFile().exists()) { // roll-back sqlite
- DiscreteOpsSqlRegistry sqlRegistry = new DiscreteOpsSqlRegistry(context);
- DiscreteOpsXmlRegistry xmlRegistry = (DiscreteOpsXmlRegistry) mDiscreteRegistry;
- DiscreteOpsMigrationHelper.migrateDiscreteOpsToXml(sqlRegistry, xmlRegistry);
- }
}
}
@@ -267,6 +257,19 @@ final class HistoricalRegistry {
}
}
}
+ if (Flags.enableSqliteAppopsAccesses()) {
+ if (DiscreteOpsXmlRegistry.getDiscreteOpsDir().exists()) {
+ DiscreteOpsSqlRegistry sqlRegistry = (DiscreteOpsSqlRegistry) mDiscreteRegistry;
+ DiscreteOpsXmlRegistry xmlRegistry = new DiscreteOpsXmlRegistry(mContext);
+ DiscreteOpsMigrationHelper.migrateDiscreteOpsToSqlite(xmlRegistry, sqlRegistry);
+ }
+ } else {
+ if (DiscreteOpsDbHelper.getDatabaseFile().exists()) { // roll-back sqlite
+ DiscreteOpsSqlRegistry sqlRegistry = new DiscreteOpsSqlRegistry(mContext);
+ DiscreteOpsXmlRegistry xmlRegistry = (DiscreteOpsXmlRegistry) mDiscreteRegistry;
+ DiscreteOpsMigrationHelper.migrateDiscreteOpsToXml(sqlRegistry, xmlRegistry);
+ }
+ }
}
private boolean isPersistenceInitializedMLocked() {
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index c125d2d2e8cd..12b666fc458a 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -11953,7 +11953,7 @@ public class AudioService extends IAudioService.Stub
mLoudnessCodecHelper.startLoudnessCodecUpdates(sessionId);
}
- /** @see LoudnessCodecController#release() */
+ /** @see LoudnessCodecController#close() */
@Override
public void stopLoudnessCodecUpdates(int sessionId) {
mLoudnessCodecHelper.stopLoudnessCodecUpdates(sessionId);
diff --git a/services/core/java/com/android/server/display/BrightnessTracker.java b/services/core/java/com/android/server/display/BrightnessTracker.java
index c19d2c9091c3..21c9b876ab46 100644
--- a/services/core/java/com/android/server/display/BrightnessTracker.java
+++ b/services/core/java/com/android/server/display/BrightnessTracker.java
@@ -16,6 +16,9 @@
package com.android.server.display;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.app.ActivityManager;
@@ -1181,6 +1184,14 @@ public class BrightnessTracker {
}
public RootTaskInfo getFocusedStack() throws RemoteException {
+ if (UserManager.isVisibleBackgroundUsersEnabled()) {
+ // In MUMD (Multiple Users on Multiple Displays) system, the top most focused stack
+ // could be on the secondary display with a user signed on its display so get the
+ // root task info only on the default display.
+ return ActivityTaskManager.getService().getRootTaskInfoOnDisplay(
+ WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_UNDEFINED,
+ Display.DEFAULT_DISPLAY);
+ }
return ActivityTaskManager.getService().getFocusedRootTaskInfo();
}
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index ca001b9c7e6d..3598b9b2e879 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -29,6 +29,7 @@ import static android.Manifest.permission.MODIFY_HDR_CONVERSION_MODE;
import static android.Manifest.permission.RESTRICT_DISPLAY_MODES;
import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_CACHED;
import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_GONE;
+import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE;
import static android.hardware.display.DisplayManagerGlobal.InternalEventFlag;
import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED;
import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR;
@@ -380,6 +381,8 @@ public final class DisplayManagerService extends SystemService {
private final SparseArray<DisplayPowerController> mDisplayPowerControllers =
new SparseArray<>();
+ private int mMaxImportanceForRRCallbacks = IMPORTANCE_VISIBLE;
+
/** {@link DisplayBlanker} used by all {@link DisplayPowerController}s. */
private final DisplayBlanker mDisplayBlanker = new DisplayBlanker() {
// Synchronized to avoid race conditions when updating multiple display states.
@@ -3445,8 +3448,11 @@ public final class DisplayManagerService extends SystemService {
}
private void sendDisplayEventFrameRateOverrideLocked(int displayId) {
+ int event = (mFlags.isFramerateOverrideTriggersRrCallbacksEnabled())
+ ? DisplayManagerGlobal.EVENT_DISPLAY_REFRESH_RATE_CHANGED
+ : DisplayManagerGlobal.EVENT_DISPLAY_BASIC_CHANGED;
Message msg = mHandler.obtainMessage(MSG_DELIVER_DISPLAY_EVENT_FRAME_RATE_OVERRIDE,
- displayId, DisplayManagerGlobal.EVENT_DISPLAY_BASIC_CHANGED);
+ displayId, event);
mHandler.sendMessage(msg);
}
@@ -3633,6 +3639,7 @@ public final class DisplayManagerService extends SystemService {
pw.println(" mWifiDisplayScanRequestCount=" + mWifiDisplayScanRequestCount);
pw.println(" mStableDisplaySize=" + mStableDisplaySize);
pw.println(" mMinimumBrightnessCurve=" + mMinimumBrightnessCurve);
+ pw.println(" mMaxImportanceForRRCallbacks=" + mMaxImportanceForRRCallbacks);
if (mUserPreferredMode != null) {
pw.println(" mUserPreferredMode=" + mUserPreferredMode);
@@ -3761,6 +3768,10 @@ public final class DisplayManagerService extends SystemService {
}
}
+ void overrideMaxImportanceForRRCallbacks(int importance) {
+ mMaxImportanceForRRCallbacks = importance;
+ }
+
boolean requestDisplayPower(int displayId, int requestedState) {
synchronized (mSyncRoot) {
final var display = mLogicalDisplayMapper.getDisplayLocked(displayId);
@@ -4144,6 +4155,18 @@ public final class DisplayManagerService extends SystemService {
mPackageName = packageNames == null ? null : packageNames[0];
}
+ public boolean shouldReceiveRefreshRateWithChangeUpdate(int event) {
+ if (mFlags.isRefreshRateEventForForegroundAppsEnabled()
+ && event == DisplayManagerGlobal.EVENT_DISPLAY_REFRESH_RATE_CHANGED) {
+ int procState = mActivityManagerInternal.getUidProcessState(mUid);
+ int importance = ActivityManager.RunningAppProcessInfo
+ .procStateToImportance(procState);
+ return importance <= mMaxImportanceForRRCallbacks || mUid <= Process.SYSTEM_UID;
+ }
+
+ return true;
+ }
+
public void updateEventFlagsMask(@InternalEventFlag long internalEventFlag) {
mInternalEventFlagsMask.set(internalEventFlag);
}
@@ -4251,6 +4274,11 @@ public final class DisplayManagerService extends SystemService {
}
}
+ if (!shouldReceiveRefreshRateWithChangeUpdate(event)) {
+ // The client is not visible to the user and is not a system service, so do nothing.
+ return true;
+ }
+
try {
transmitDisplayEvent(displayId, event);
return true;
@@ -4382,26 +4410,34 @@ public final class DisplayManagerService extends SystemService {
// would be unusual to do so. The method returns true on success.
// This is only used if {@link deferDisplayEventsWhenFrozen()} is true.
public boolean dispatchPending() {
+ Event[] pending;
+ synchronized (mCallback) {
+ if (mPendingEvents == null || mPendingEvents.isEmpty() || !mAlive) {
+ return true;
+ }
+ if (!isReadyLocked()) {
+ return false;
+ }
+ pending = new Event[mPendingEvents.size()];
+ pending = mPendingEvents.toArray(pending);
+ mPendingEvents.clear();
+ }
try {
- synchronized (mCallback) {
- if (mPendingEvents == null || mPendingEvents.isEmpty() || !mAlive) {
- return true;
- }
- if (!isReadyLocked()) {
- return false;
+ for (int i = 0; i < pending.length; i++) {
+ Event displayEvent = pending[i];
+ if (DEBUG) {
+ Slog.d(TAG, "Send pending display event #" + i + " "
+ + displayEvent.displayId + "/"
+ + displayEvent.event + " to " + mUid + "/" + mPid);
}
- for (int i = 0; i < mPendingEvents.size(); i++) {
- Event displayEvent = mPendingEvents.get(i);
- if (DEBUG) {
- Slog.d(TAG, "Send pending display event #" + i + " "
- + displayEvent.displayId + "/"
- + displayEvent.event + " to " + mUid + "/" + mPid);
- }
- transmitDisplayEvent(displayEvent.displayId, displayEvent.event);
+
+ if (!shouldReceiveRefreshRateWithChangeUpdate(displayEvent.event)) {
+ continue;
}
- mPendingEvents.clear();
- return true;
+
+ transmitDisplayEvent(displayEvent.displayId, displayEvent.event);
}
+ return true;
} catch (RemoteException ex) {
Slog.w(TAG, "Failed to notify process "
+ mPid + " that display topology changed, assuming it died.", ex);
diff --git a/services/core/java/com/android/server/display/DisplayManagerShellCommand.java b/services/core/java/com/android/server/display/DisplayManagerShellCommand.java
index f6b2591ea440..e23756fece25 100644
--- a/services/core/java/com/android/server/display/DisplayManagerShellCommand.java
+++ b/services/core/java/com/android/server/display/DisplayManagerShellCommand.java
@@ -112,6 +112,8 @@ class DisplayManagerShellCommand extends ShellCommand {
return requestDisplayPower(Display.STATE_UNKNOWN);
case "power-off":
return requestDisplayPower(Display.STATE_OFF);
+ case "override-max-importance-rr-callbacks":
+ return overrideMaxImportanceForRRCallbacks();
default:
return handleDefaultCommands(cmd);
}
@@ -631,4 +633,21 @@ class DisplayManagerShellCommand extends ShellCommand {
mService.requestDisplayPower(displayId, state);
return 0;
}
+
+ private int overrideMaxImportanceForRRCallbacks() {
+ final String importanceString = getNextArg();
+ if (importanceString == null) {
+ getErrPrintWriter().println("Error: no importance specified");
+ return 1;
+ }
+ final int importance;
+ try {
+ importance = Integer.parseInt(importanceString);
+ } catch (NumberFormatException e) {
+ getErrPrintWriter().println("Error: invalid importance: '" + importanceString + "'");
+ return 1;
+ }
+ mService.overrideMaxImportanceForRRCallbacks(importance);
+ return 0;
+ }
}
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 43aa6f46da78..d435144b28c6 100644
--- a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
+++ b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
@@ -263,6 +263,16 @@ public class DisplayManagerFlags {
Flags::baseDensityForExternalDisplays
);
+ private final FlagState mFramerateOverrideTriggersRrCallbacks = new FlagState(
+ Flags.FLAG_FRAMERATE_OVERRIDE_TRIGGERS_RR_CALLBACKS,
+ Flags::framerateOverrideTriggersRrCallbacks
+ );
+
+ private final FlagState mRefreshRateEventForForegroundApps = new FlagState(
+ Flags.FLAG_REFRESH_RATE_EVENT_FOR_FOREGROUND_APPS,
+ Flags::refreshRateEventForForegroundApps
+ );
+
/**
* @return {@code true} if 'port' is allowed in display layout configuration file.
*/
@@ -564,6 +574,22 @@ public class DisplayManagerFlags {
return mBaseDensityForExternalDisplays.isEnabled();
}
+ /**
+ * @return {@code true} if the flag triggering refresh rate callbacks when framerate is
+ * overridden is enabled
+ */
+ public boolean isFramerateOverrideTriggersRrCallbacksEnabled() {
+ return mFramerateOverrideTriggersRrCallbacks.isEnabled();
+ }
+
+
+ /**
+ * @return {@code true} if the flag for sending refresh rate events only for the apps in
+ * foreground is enabled
+ */
+ public boolean isRefreshRateEventForForegroundAppsEnabled() {
+ return mRefreshRateEventForForegroundApps.isEnabled();
+ }
/**
* dumps all flagstates
@@ -620,6 +646,8 @@ public class DisplayManagerFlags {
pw.println(" " + mSubscribeGranularDisplayEvents);
pw.println(" " + mEnableDisplayContentModeManagementFlagState);
pw.println(" " + mBaseDensityForExternalDisplays);
+ pw.println(" " + mFramerateOverrideTriggersRrCallbacks);
+ pw.println(" " + mRefreshRateEventForForegroundApps);
}
private static class FlagState {
diff --git a/services/core/java/com/android/server/display/feature/display_flags.aconfig b/services/core/java/com/android/server/display/feature/display_flags.aconfig
index 00a9dcb71b4b..63cd2d73336a 100644
--- a/services/core/java/com/android/server/display/feature/display_flags.aconfig
+++ b/services/core/java/com/android/server/display/feature/display_flags.aconfig
@@ -479,3 +479,25 @@ flag {
bug: "382954433"
is_fixed_read_only: true
}
+
+flag {
+ name: "framerate_override_triggers_rr_callbacks"
+ namespace: "display_manager"
+ description: "Feature flag to trigger the RR callbacks when framerate overridding happens."
+ bug: "390113266"
+ is_fixed_read_only: true
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
+ name: "refresh_rate_event_for_foreground_apps"
+ namespace: "display_manager"
+ description: "Send Refresh Rate events only for the apps in foreground."
+ bug: "390107600"
+ is_fixed_read_only: true
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/services/core/java/com/android/server/input/InputSettingsObserver.java b/services/core/java/com/android/server/input/InputSettingsObserver.java
index e25ea4b43827..d1f07cb8ae23 100644
--- a/services/core/java/com/android/server/input/InputSettingsObserver.java
+++ b/services/core/java/com/android/server/input/InputSettingsObserver.java
@@ -90,6 +90,8 @@ class InputSettingsObserver extends ContentObserver {
(reason) -> updateTouchpadRightClickZoneEnabled()),
Map.entry(Settings.System.getUriFor(Settings.System.TOUCHPAD_SYSTEM_GESTURES),
(reason) -> updateTouchpadSystemGesturesEnabled()),
+ Map.entry(Settings.System.getUriFor(Settings.System.TOUCHPAD_ACCELERATION_ENABLED),
+ (reason) -> updateTouchpadAccelerationEnabled()),
Map.entry(Settings.System.getUriFor(Settings.System.SHOW_TOUCHES),
(reason) -> updateShowTouches()),
Map.entry(Settings.System.getUriFor(Settings.System.POINTER_LOCATION),
@@ -241,6 +243,11 @@ class InputSettingsObserver extends ContentObserver {
mNative.setTouchpadSystemGesturesEnabled(InputSettings.useTouchpadSystemGestures(mContext));
}
+ private void updateTouchpadAccelerationEnabled() {
+ mNative.setTouchpadAccelerationEnabled(
+ InputSettings.isTouchpadAccelerationEnabled(mContext));
+ }
+
private void updateShowTouches() {
mNative.setShowTouches(getBoolean(Settings.System.SHOW_TOUCHES, false));
}
diff --git a/services/core/java/com/android/server/input/NativeInputManagerService.java b/services/core/java/com/android/server/input/NativeInputManagerService.java
index 4d38c8401e2d..f34338a397db 100644
--- a/services/core/java/com/android/server/input/NativeInputManagerService.java
+++ b/services/core/java/com/android/server/input/NativeInputManagerService.java
@@ -158,6 +158,8 @@ interface NativeInputManagerService {
void setTouchpadSystemGesturesEnabled(boolean enabled);
+ void setTouchpadAccelerationEnabled(boolean enabled);
+
void setShowTouches(boolean enabled);
void setNonInteractiveDisplays(int[] displayIds);
@@ -463,6 +465,9 @@ interface NativeInputManagerService {
public native void setTouchpadSystemGesturesEnabled(boolean enabled);
@Override
+ public native void setTouchpadAccelerationEnabled(boolean enabled);
+
+ @Override
public native void setShowTouches(boolean enabled);
@Override
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 45c7cffd462b..7b81fc92e83d 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -2189,7 +2189,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl.
if (mVdmInternal == null) {
mVdmInternal = LocalServices.getService(VirtualDeviceManagerInternal.class);
}
- if (mVdmInternal == null || !android.companion.virtual.flags.Flags.vdmCustomIme()) {
+ if (mVdmInternal == null) {
return currentMethodId;
}
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubService.java b/services/core/java/com/android/server/location/contexthub/ContextHubService.java
index 2615a76ac279..2b0ca145372b 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubService.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubService.java
@@ -334,7 +334,7 @@ public class ContextHubService extends IContextHubService.Stub {
if (Flags.offloadApi() && Flags.offloadImplementation()) {
HubInfoRegistry registry;
try {
- registry = new HubInfoRegistry(mContextHubWrapper);
+ registry = new HubInfoRegistry(mContext, mContextHubWrapper);
mEndpointManager =
new ContextHubEndpointManager(
mContext, mContextHubWrapper, registry, mTransactionManager);
@@ -821,6 +821,13 @@ public class ContextHubService extends IContextHubService.Stub {
mHubInfoRegistry.unregisterEndpointDiscoveryCallback(callback);
}
+ @android.annotation.EnforcePermission(android.Manifest.permission.ACCESS_CONTEXT_HUB)
+ @Override
+ public void onDiscoveryCallbackFinished() throws RemoteException {
+ super.onDiscoveryCallbackFinished_enforcePermission();
+ mHubInfoRegistry.onDiscoveryCallbackFinished();
+ }
+
private void checkEndpointDiscoveryPreconditions() {
if (mHubInfoRegistry == null) {
Log.e(TAG, "Hub endpoint registry failed to initialize");
diff --git a/services/core/java/com/android/server/location/contexthub/HubInfoRegistry.java b/services/core/java/com/android/server/location/contexthub/HubInfoRegistry.java
index bf54fd720d42..711383bbca37 100644
--- a/services/core/java/com/android/server/location/contexthub/HubInfoRegistry.java
+++ b/services/core/java/com/android/server/location/contexthub/HubInfoRegistry.java
@@ -16,12 +16,18 @@
package com.android.server.location.contexthub;
+import android.content.Context;
import android.hardware.contexthub.HubEndpointInfo;
import android.hardware.contexthub.HubServiceInfo;
import android.hardware.contexthub.IContextHubEndpointDiscoveryCallback;
import android.hardware.location.HubInfo;
-import android.os.DeadObjectException;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.PowerManager;
+import android.os.PowerManager.WakeLock;
+import android.os.Process;
import android.os.RemoteException;
+import android.os.WorkSource;
import android.util.ArrayMap;
import android.util.IndentingPrintWriter;
import android.util.Log;
@@ -34,10 +40,15 @@ import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
+import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.BiConsumer;
class HubInfoRegistry implements ContextHubHalEndpointCallback.IEndpointLifecycleCallback {
private static final String TAG = "HubInfoRegistry";
+
+ /** The duration of wakelocks acquired during discovery callbacks */
+ private static final long WAKELOCK_TIMEOUT_MILLIS = 5 * 1000;
+
private final Object mLock = new Object();
private final IContextHubWrapper mContextHubWrapper;
@@ -53,21 +64,37 @@ class HubInfoRegistry implements ContextHubHalEndpointCallback.IEndpointLifecycl
* A wrapper class that is used to store arguments to
* ContextHubManager.registerEndpointCallback.
*/
- private static class DiscoveryCallback {
+ private static class DiscoveryCallback implements IBinder.DeathRecipient {
+ private final HubInfoRegistry mHubInfoRegistry;
private final IContextHubEndpointDiscoveryCallback mCallback;
private final Optional<Long> mEndpointId;
private final Optional<String> mServiceDescriptor;
- DiscoveryCallback(IContextHubEndpointDiscoveryCallback callback, long endpointId) {
+ // True if the binder death recipient fired
+ private final AtomicBoolean mBinderDied = new AtomicBoolean(false);
+
+ DiscoveryCallback(
+ HubInfoRegistry registry,
+ IContextHubEndpointDiscoveryCallback callback,
+ long endpointId)
+ throws RemoteException {
+ mHubInfoRegistry = registry;
mCallback = callback;
mEndpointId = Optional.of(endpointId);
mServiceDescriptor = Optional.empty();
+ attachDeathRecipient();
}
- DiscoveryCallback(IContextHubEndpointDiscoveryCallback callback, String serviceDescriptor) {
+ DiscoveryCallback(
+ HubInfoRegistry registry,
+ IContextHubEndpointDiscoveryCallback callback,
+ String serviceDescriptor)
+ throws RemoteException {
+ mHubInfoRegistry = registry;
mCallback = callback;
mEndpointId = Optional.empty();
mServiceDescriptor = Optional.of(serviceDescriptor);
+ attachDeathRecipient();
}
public IContextHubEndpointDiscoveryCallback getCallback() {
@@ -79,6 +106,10 @@ class HubInfoRegistry implements ContextHubHalEndpointCallback.IEndpointLifecycl
* @return true if info matches
*/
public boolean isMatch(HubEndpointInfo info) {
+ if (mBinderDied.get()) {
+ Log.w(TAG, "Callback died, isMatch returning false");
+ return false;
+ }
if (mEndpointId.isPresent()) {
return mEndpointId.get() == info.getIdentifier().getEndpoint();
}
@@ -91,6 +122,17 @@ class HubInfoRegistry implements ContextHubHalEndpointCallback.IEndpointLifecycl
}
return false;
}
+
+ @Override
+ public void binderDied() {
+ Log.d(TAG, "Binder died for discovery callback");
+ mBinderDied.set(true);
+ mHubInfoRegistry.unregisterEndpointDiscoveryCallback(mCallback);
+ }
+
+ private void attachDeathRecipient() throws RemoteException {
+ mCallback.asBinder().linkToDeath(this, 0 /* flags */);
+ }
}
/* The list of discovery callbacks registered with the service */
@@ -99,7 +141,11 @@ class HubInfoRegistry implements ContextHubHalEndpointCallback.IEndpointLifecycl
private final Object mCallbackLock = new Object();
- HubInfoRegistry(IContextHubWrapper contextHubWrapper) throws InstantiationException {
+ /** Wakelock held while endpoint callbacks are being invoked */
+ private final WakeLock mWakeLock;
+
+ HubInfoRegistry(Context context, IContextHubWrapper contextHubWrapper)
+ throws InstantiationException {
mContextHubWrapper = contextHubWrapper;
try {
refreshCachedHubs();
@@ -109,6 +155,16 @@ class HubInfoRegistry implements ContextHubHalEndpointCallback.IEndpointLifecycl
Log.e(TAG, error, e);
throw new InstantiationException(error);
}
+
+ PowerManager powerManager = context.getSystemService(PowerManager.class);
+ if (powerManager == null) {
+ String error = "PowerManager was null";
+ Log.e(TAG, error);
+ throw new InstantiationError(error);
+ }
+ mWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
+ mWakeLock.setWorkSource(new WorkSource(Process.myUid(), context.getPackageName()));
+ mWakeLock.setReferenceCounted(true);
}
/** Retrieve the list of hubs available. */
@@ -178,12 +234,7 @@ class HubInfoRegistry implements ContextHubHalEndpointCallback.IEndpointLifecycl
try {
cb.onEndpointsStarted(infoList);
} catch (RemoteException e) {
- if (e instanceof DeadObjectException) {
- Log.w(TAG, "onEndpointStarted: callback died, unregistering");
- unregisterEndpointDiscoveryCallback(cb);
- } else {
- Log.e(TAG, "Exception while calling onEndpointsStarted", e);
- }
+ Log.e(TAG, "Exception while calling onEndpointsStarted", e);
}
});
}
@@ -208,12 +259,7 @@ class HubInfoRegistry implements ContextHubHalEndpointCallback.IEndpointLifecycl
cb.onEndpointsStopped(
infoList, ContextHubServiceUtil.toAppHubEndpointReason(reason));
} catch (RemoteException e) {
- if (e instanceof DeadObjectException) {
- Log.w(TAG, "onEndpointStopped: callback died, unregistering");
- unregisterEndpointDiscoveryCallback(cb);
- } else {
- Log.e(TAG, "Exception while calling onEndpointsStopped", e);
- }
+ Log.e(TAG, "Exception while calling onEndpointsStopped", e);
}
});
}
@@ -254,7 +300,11 @@ class HubInfoRegistry implements ContextHubHalEndpointCallback.IEndpointLifecycl
Objects.requireNonNull(callback, "callback cannot be null");
synchronized (mCallbackLock) {
checkCallbackAlreadyRegistered(callback);
- mEndpointDiscoveryCallbacks.add(new DiscoveryCallback(callback, endpointId));
+ try {
+ mEndpointDiscoveryCallbacks.add(new DiscoveryCallback(this, callback, endpointId));
+ } catch (RemoteException e) {
+ Log.e(TAG, "RemoteException while adding discovery callback", e);
+ }
}
}
@@ -264,7 +314,12 @@ class HubInfoRegistry implements ContextHubHalEndpointCallback.IEndpointLifecycl
Objects.requireNonNull(callback, "callback cannot be null");
synchronized (mCallbackLock) {
checkCallbackAlreadyRegistered(callback);
- mEndpointDiscoveryCallbacks.add(new DiscoveryCallback(callback, serviceDescriptor));
+ try {
+ mEndpointDiscoveryCallbacks.add(
+ new DiscoveryCallback(this, callback, serviceDescriptor));
+ } catch (RemoteException e) {
+ Log.e(TAG, "RemoteException while adding discovery callback", e);
+ }
}
}
@@ -282,6 +337,11 @@ class HubInfoRegistry implements ContextHubHalEndpointCallback.IEndpointLifecycl
}
}
+ /* package */
+ void onDiscoveryCallbackFinished() {
+ releaseWakeLock();
+ }
+
private void checkCallbackAlreadyRegistered(
IContextHubEndpointDiscoveryCallback callback) {
synchronized (mCallbackLock) {
@@ -315,6 +375,7 @@ class HubInfoRegistry implements ContextHubHalEndpointCallback.IEndpointLifecycl
}
}
+ acquireWakeLock();
consumer.accept(
discoveryCallback.getCallback(),
infoList.toArray(new HubEndpointInfo[infoList.size()]));
@@ -322,6 +383,26 @@ class HubInfoRegistry implements ContextHubHalEndpointCallback.IEndpointLifecycl
}
}
+ private void acquireWakeLock() {
+ Binder.withCleanCallingIdentity(
+ () -> {
+ mWakeLock.acquire(WAKELOCK_TIMEOUT_MILLIS);
+ });
+ }
+
+ private void releaseWakeLock() {
+ Binder.withCleanCallingIdentity(
+ () -> {
+ if (mWakeLock.isHeld()) {
+ try {
+ mWakeLock.release();
+ } catch (RuntimeException e) {
+ Log.e(TAG, "Releasing the wakelock fails - ", e);
+ }
+ }
+ });
+ }
+
void dump(IndentingPrintWriter ipw) {
synchronized (mLock) {
dumpLocked(ipw);
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 e47cbdc3546f..6ad7ea7b768f 100644
--- a/services/core/java/com/android/server/media/quality/MediaQualityService.java
+++ b/services/core/java/com/android/server/media/quality/MediaQualityService.java
@@ -24,6 +24,7 @@ import static android.media.quality.AmbientBacklightEvent.AMBIENT_BACKLIGHT_EVEN
import android.annotation.NonNull;
import android.content.ContentValues;
import android.content.Context;
+import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
@@ -51,6 +52,7 @@ import android.media.quality.SoundProfile;
import android.media.quality.SoundProfileHandle;
import android.os.Binder;
import android.os.Bundle;
+import android.os.Environment;
import android.os.IBinder;
import android.os.PersistableBundle;
import android.os.RemoteCallbackList;
@@ -69,6 +71,7 @@ import com.android.server.utils.Slogf;
import org.json.JSONException;
import org.json.JSONObject;
+import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
@@ -88,6 +91,10 @@ public class MediaQualityService extends SystemService {
private static final boolean DEBUG = false;
private static final String TAG = "MediaQualityService";
+ private static final String ALLOWLIST = "allowlist";
+ private static final String PICTURE_PROFILE_PREFERENCE = "picture_profile_preference";
+ private static final String SOUND_PROFILE_PREFERENCE = "sound_profile_preference";
+ private static final String COMMA_DELIMITER = ",";
private static final int MAX_UUID_GENERATION_ATTEMPTS = 10;
private final Context mContext;
private final MediaQualityDbHelper mMediaQualityDbHelper;
@@ -98,6 +105,8 @@ public class MediaQualityService extends SystemService {
private final Map<String, AmbientBacklightCallbackRecord> mCallbackRecords = new HashMap<>();
private final PackageManager mPackageManager;
private final SparseArray<UserState> mUserStates = new SparseArray<>();
+ private SharedPreferences mPictureProfileSharedPreference;
+ private SharedPreferences mSoundProfileSharedPreference;
public MediaQualityService(Context context) {
super(context);
@@ -109,6 +118,19 @@ public class MediaQualityService extends SystemService {
mMediaQualityDbHelper = new MediaQualityDbHelper(mContext);
mMediaQualityDbHelper.setWriteAheadLoggingEnabled(true);
mMediaQualityDbHelper.setIdleConnectionTimeout(30);
+
+ // 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
+ // build the path manually below using the same policy that appears in ContextImpl.
+ final Context deviceContext = mContext.createDeviceProtectedStorageContext();
+ final File pictureProfilePrefs = new File(Environment.getDataSystemDirectory(),
+ PICTURE_PROFILE_PREFERENCE);
+ mPictureProfileSharedPreference = deviceContext.getSharedPreferences(
+ pictureProfilePrefs, Context.MODE_PRIVATE);
+ final File soundProfilePrefs = new File(Environment.getDataSystemDirectory(),
+ SOUND_PROFILE_PREFERENCE);
+ mSoundProfileSharedPreference = deviceContext.getSharedPreferences(
+ soundProfilePrefs, Context.MODE_PRIVATE);
}
@Override
@@ -1291,6 +1313,11 @@ public class MediaQualityService extends SystemService {
notifyOnPictureProfileError(null, PictureProfile.ERROR_NO_PERMISSION,
Binder.getCallingUid(), Binder.getCallingPid());
}
+ String allowlist = mPictureProfileSharedPreference.getString(ALLOWLIST, null);
+ if (allowlist != null) {
+ String[] stringArray = allowlist.split(COMMA_DELIMITER);
+ return new ArrayList<>(Arrays.asList(stringArray));
+ }
return new ArrayList<>();
}
@@ -1300,6 +1327,9 @@ public class MediaQualityService extends SystemService {
notifyOnPictureProfileError(null, PictureProfile.ERROR_NO_PERMISSION,
Binder.getCallingUid(), Binder.getCallingPid());
}
+ SharedPreferences.Editor editor = mPictureProfileSharedPreference.edit();
+ editor.putString(ALLOWLIST, String.join(COMMA_DELIMITER, packages));
+ editor.commit();
}
@Override
@@ -1308,6 +1338,11 @@ public class MediaQualityService extends SystemService {
notifyOnSoundProfileError(null, SoundProfile.ERROR_NO_PERMISSION,
Binder.getCallingUid(), Binder.getCallingPid());
}
+ String allowlist = mSoundProfileSharedPreference.getString(ALLOWLIST, null);
+ if (allowlist != null) {
+ String[] stringArray = allowlist.split(COMMA_DELIMITER);
+ return new ArrayList<>(Arrays.asList(stringArray));
+ }
return new ArrayList<>();
}
@@ -1317,6 +1352,9 @@ public class MediaQualityService extends SystemService {
notifyOnSoundProfileError(null, SoundProfile.ERROR_NO_PERMISSION,
Binder.getCallingUid(), Binder.getCallingPid());
}
+ SharedPreferences.Editor editor = mSoundProfileSharedPreference.edit();
+ editor.putString(ALLOWLIST, String.join(COMMA_DELIMITER, packages));
+ editor.commit();
}
@Override
@@ -1347,7 +1385,7 @@ public class MediaQualityService extends SystemService {
try {
if (mMediaQuality != null) {
if (mMediaQuality.isAutoPqSupported()) {
- mMediaQuality.getAutoPqEnabled();
+ return mMediaQuality.getAutoPqEnabled();
}
}
} catch (RemoteException e) {
@@ -1379,7 +1417,7 @@ public class MediaQualityService extends SystemService {
try {
if (mMediaQuality != null) {
if (mMediaQuality.isAutoSrSupported()) {
- mMediaQuality.getAutoSrEnabled();
+ return mMediaQuality.getAutoSrEnabled();
}
}
} catch (RemoteException e) {
@@ -1411,7 +1449,7 @@ public class MediaQualityService extends SystemService {
try {
if (mMediaQuality != null) {
if (mMediaQuality.isAutoAqSupported()) {
- mMediaQuality.getAutoAqEnabled();
+ return mMediaQuality.getAutoAqEnabled();
}
}
} catch (RemoteException e) {
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index adf6c1b94fbd..588e87924b7d 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -356,6 +356,7 @@ import com.android.internal.util.Preconditions;
import com.android.internal.util.XmlUtils;
import com.android.internal.util.function.TriPredicate;
import com.android.internal.widget.LockPatternUtils;
+import com.android.modules.expresslog.Counter;
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;
import com.android.server.DeviceIdleInternal;
@@ -762,7 +763,7 @@ public class NotificationManagerService extends SystemService {
private int mWarnRemoteViewsSizeBytes;
private int mStripRemoteViewsSizeBytes;
- private String[] mDefaultUnsupportedAdjustments;
+ protected String[] mDefaultUnsupportedAdjustments;
@VisibleForTesting
protected boolean mShowReviewPermissionsNotification;
@@ -3037,10 +3038,9 @@ public class NotificationManagerService extends SystemService {
switch(atomTag) {
case PACKAGE_NOTIFICATION_PREFERENCES:
if (notificationClassificationUi()) {
- Set<String> pkgs = mAssistants.getPackagesWithKeyTypeAdjustmentSettings();
mPreferencesHelper.pullPackagePreferencesStats(data,
getAllUsersNotificationPermissions(),
- getPackageSpecificAdjustmentKeyTypes(pkgs));
+ new ArrayMap<>());
} else {
mPreferencesHelper.pullPackagePreferencesStats(data,
getAllUsersNotificationPermissions());
@@ -4378,8 +4378,8 @@ public class NotificationManagerService extends SystemService {
public @NonNull List<String> getUnsupportedAdjustmentTypes() {
checkCallerIsSystemOrSystemUiOrShell();
synchronized (mNotificationLock) {
- return new ArrayList(mAssistants.mNasUnsupported.getOrDefault(
- UserHandle.getUserId(Binder.getCallingUid()), new HashSet<>()));
+ return new ArrayList(mAssistants.getUnsupportedAdjustments(
+ UserHandle.getUserId(Binder.getCallingUid())));
}
}
@@ -4401,16 +4401,16 @@ public class NotificationManagerService extends SystemService {
@Override
@FlaggedApi(android.app.Flags.FLAG_NOTIFICATION_CLASSIFICATION_UI)
- public @NonNull int[] getAllowedAdjustmentKeyTypesForPackage(String pkg) {
+ public @NonNull String[] getTypeAdjustmentDeniedPackages() {
checkCallerIsSystemOrSystemUiOrShell();
- return mAssistants.getAllowedAdjustmentKeyTypesForPackage(pkg);
+ return mAssistants.getTypeAdjustmentDeniedPackages();
}
+ @Override
@FlaggedApi(android.app.Flags.FLAG_NOTIFICATION_CLASSIFICATION_UI)
- public void setAssistantAdjustmentKeyTypeStateForPackage(String pkg, int type,
- boolean enabled) {
+ public void setTypeAdjustmentForPackageState(String pkg, boolean enabled) {
checkCallerIsSystemOrSystemUiOrShell();
- mAssistants.setAssistantAdjustmentKeyTypeStateForPackage(pkg, type, enabled);
+ mAssistants.setTypeAdjustmentForPackageState(pkg, enabled);
handleSavePolicyFile();
}
@@ -7137,6 +7137,13 @@ public class NotificationManagerService extends SystemService {
Slog.e(TAG, "exiting pullStats: bad request");
return 0;
}
+
+ @Override
+ public void incrementCounter(String metricId) {
+ if (android.app.Flags.nmBinderPerfLogNmThrottling() && metricId != null) {
+ Counter.logIncrementWithUid(metricId, Binder.getCallingUid());
+ }
+ }
};
private void handleNotificationPermissionChange(String pkg, @UserIdInt int userId) {
@@ -7215,11 +7222,12 @@ public class NotificationManagerService extends SystemService {
toRemove.add(potentialKey);
}
if (notificationClassification() && adjustments.containsKey(KEY_TYPE)) {
+ mAssistants.setNasUnsupportedDefaults(r.getSbn().getNormalizedUserId());
if (!mAssistants.isAdjustmentKeyTypeAllowed(adjustments.getInt(KEY_TYPE))) {
toRemove.add(potentialKey);
} else if (notificationClassificationUi()
&& !mAssistants.isTypeAdjustmentAllowedForPackage(
- r.getSbn().getPackageName(), adjustments.getInt(KEY_TYPE))) {
+ r.getSbn().getPackageName())) {
toRemove.add(potentialKey);
}
}
@@ -7556,24 +7564,6 @@ public class NotificationManagerService extends SystemService {
return allPermissions;
}
- @VisibleForTesting
- @FlaggedApi(android.app.Flags.FLAG_NOTIFICATION_CLASSIFICATION_UI)
- protected @NonNull Map<String, Set<Integer>> getPackageSpecificAdjustmentKeyTypes(
- Set<String> pkgs) {
- ArrayMap<String, Set<Integer>> pkgToAllowedTypes = new ArrayMap<>();
- for (String pkg : pkgs) {
- int[] allowedTypesArray = mAssistants.getAllowedAdjustmentKeyTypesForPackage(pkg);
- if (allowedTypesArray != null) {
- Set<Integer> allowedTypes = new ArraySet<Integer>();
- for (int i : allowedTypesArray) {
- allowedTypes.add(i);
- }
- pkgToAllowedTypes.append(pkg, allowedTypes);
- }
- }
- return pkgToAllowedTypes;
- }
-
private void dumpJson(PrintWriter pw, @NonNull DumpFilter filter,
ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> pkgPermissions) {
JSONObject dump = new JSONObject();
@@ -11886,14 +11876,10 @@ public class NotificationManagerService extends SystemService {
static final String TAG_ENABLED_NOTIFICATION_ASSISTANTS = "enabled_assistants";
private static final String ATT_TYPES = "types";
- private static final String ATT_DENIED = "denied_adjustments";
+ private static final String ATT_DENIED = "user_denied_adjustments";
private static final String ATT_ENABLED_TYPES = "enabled_key_types";
- private static final String ATT_NAS_UNSUPPORTED = "unsupported_adjustments";
- // Encapsulates a list of packages and the bundle types enabled for each package.
- private static final String TAG_TYPES_ENABLED_FOR_APPS = "types_enabled_for_apps";
- // Encapsulates the bundle types enabled for a package.
- private static final String ATT_APP_ENABLED_TYPES = "app_enabled_types";
- private static final String ATT_PACKAGE = "package";
+ private static final String ATT_NAS_UNSUPPORTED = "nas_unsupported_adjustments";
+ private static final String ATT_TYPES_DENIED_APPS = "types_denied_apps";
private final Object mLock = new Object();
@@ -11909,14 +11895,8 @@ public class NotificationManagerService extends SystemService {
@GuardedBy("mLock")
private Map<Integer, HashSet<String>> mNasUnsupported = new ArrayMap<>();
- // Types of classifications (aka bundles) enabled/allowed for this package.
- // If the set is NULL (or package is not in the list), default classification allow list
- // (the global one) should be used.
- // If the set is empty, that indicates the package explicitly has all classifications
- // disallowed.
@GuardedBy("mLock")
- private Map<String, Set<Integer>> mClassificationTypePackagesEnabledTypes =
- new ArrayMap<>();
+ private Set<String> mClassificationTypeDeniedPackages = new ArraySet<>();
protected ComponentName mDefaultFromConfig = null;
@@ -11994,9 +11974,6 @@ public class NotificationManagerService extends SystemService {
}
} else {
mAllowedAdjustmentKeyTypes.addAll(List.of(DEFAULT_ALLOWED_ADJUSTMENT_KEY_TYPES));
- if (mDefaultUnsupportedAdjustments != null) {
- mAllowedAdjustments.removeAll(List.of(mDefaultUnsupportedAdjustments));
- }
}
}
@@ -12120,104 +12097,41 @@ public class NotificationManagerService extends SystemService {
}
}
- /**
- * Returns whether the type adjustment is allowed for this particular package.
- * If no package-specific restrictions have been set, defaults to the same value as
- * isAdjustmentKeyTypeAllowed(type).
- */
@FlaggedApi(android.app.Flags.FLAG_NOTIFICATION_CLASSIFICATION_UI)
- protected boolean isTypeAdjustmentAllowedForPackage(String pkg,
- @Adjustment.Types int type) {
+ protected @NonNull boolean isTypeAdjustmentAllowedForPackage(String pkg) {
synchronized (mLock) {
if (notificationClassificationUi()) {
- if (mClassificationTypePackagesEnabledTypes.containsKey(pkg)) {
- Set<Integer> enabled = mClassificationTypePackagesEnabledTypes.get(pkg);
- if (enabled != null) {
- return enabled.contains(type);
- }
- }
- // If mClassificationTypePackagesEnabledTypes does not contain the pkg, or
- // the stored set is null, return the default.
- return isAdjustmentKeyTypeAllowed(type);
+ return !mClassificationTypeDeniedPackages.contains(pkg);
}
}
- return false;
- }
-
- @FlaggedApi(android.app.Flags.FLAG_NOTIFICATION_CLASSIFICATION_UI)
- protected @NonNull Set<String> getPackagesWithKeyTypeAdjustmentSettings() {
- if (notificationClassificationUi()) {
- Set<String> packagesWithModifications = new ArraySet<String>();
- synchronized (mLock) {
- for (String pkg : mClassificationTypePackagesEnabledTypes.keySet()) {
- if (mClassificationTypePackagesEnabledTypes.get(pkg) != null) {
- packagesWithModifications.add(pkg);
- }
- }
- }
- return packagesWithModifications;
- }
- return new ArraySet<String>();
+ return true;
}
@FlaggedApi(android.app.Flags.FLAG_NOTIFICATION_CLASSIFICATION_UI)
- protected @NonNull int[] getAllowedAdjustmentKeyTypesForPackage(String pkg) {
+ protected @NonNull String[] getTypeAdjustmentDeniedPackages() {
synchronized (mLock) {
if (notificationClassificationUi()) {
- if (mClassificationTypePackagesEnabledTypes.containsKey(pkg)) {
- Set<Integer> enabled = mClassificationTypePackagesEnabledTypes.get(pkg);
- if (enabled != null) {
- // Convert Set to int[] for return.
- int[] returnEnabled = new int[enabled.size()];
- int i = 0;
- for (int val: enabled) {
- returnEnabled[i] = val;
- i++;
- }
- return returnEnabled;
- }
- }
- // If package is not in the map, or the value is null, return the default.
- return getAllowedAdjustmentKeyTypes();
+ return mClassificationTypeDeniedPackages.toArray(new String[0]);
}
}
- return new int[]{};
+ return new String[]{};
}
/**
* Set whether a particular package can have its notification channels adjusted to have a
* different type by NotificationAssistants.
- * Note: once this method is called to enable or disable a specific type for a package,
- * the global default is set as the starting point, and the type is enabled/disabled from
- * there. Future changes to the global default will not apply automatically to this package.
*/
@FlaggedApi(android.app.Flags.FLAG_NOTIFICATION_CLASSIFICATION_UI)
- public void setAssistantAdjustmentKeyTypeStateForPackage(String pkg,
- @Adjustment.Types int type,
- boolean enabled) {
+ public void setTypeAdjustmentForPackageState(String pkg, boolean enabled) {
if (!notificationClassificationUi()) {
return;
}
synchronized (mLock) {
- Set<Integer> enabledTypes = null;
- if (mClassificationTypePackagesEnabledTypes.containsKey(pkg)) {
- enabledTypes = mClassificationTypePackagesEnabledTypes.get(pkg);
- }
- if (enabledTypes == null) {
- // Use global default to start.
- enabledTypes = new ArraySet<Integer>();
- // Convert from int[] to Set<Integer>
- for (int value : getAllowedAdjustmentKeyTypes()) {
- enabledTypes.add(value);
- }
- }
-
if (enabled) {
- enabledTypes.add(type);
+ mClassificationTypeDeniedPackages.remove(pkg);
} else {
- enabledTypes.remove(type);
+ mClassificationTypeDeniedPackages.add(pkg);
}
- mClassificationTypePackagesEnabledTypes.put(pkg, enabledTypes);
}
}
@@ -12579,7 +12493,7 @@ public class NotificationManagerService extends SystemService {
}
} else {
if (android.service.notification.Flags.notificationClassification()) {
- mNasUnsupported.put(userId, new HashSet<>());
+ setNasUnsupportedDefaults(userId);
}
}
super.setPackageOrComponentEnabled(pkgOrComponent, userId, isPrimary, enabled, userSet);
@@ -12620,8 +12534,8 @@ public class NotificationManagerService extends SystemService {
if (!android.service.notification.Flags.notificationClassification()) {
return;
}
- HashSet<String> disabledAdjustments =
- mNasUnsupported.getOrDefault(info.userid, new HashSet<>());
+ setNasUnsupportedDefaults(info.userid);
+ HashSet<String> disabledAdjustments = mNasUnsupported.get(info.userid);
if (supported) {
disabledAdjustments.remove(key);
} else {
@@ -12637,7 +12551,15 @@ public class NotificationManagerService extends SystemService {
if (!android.service.notification.Flags.notificationClassification()) {
return new HashSet<>();
}
- return mNasUnsupported.getOrDefault(userId, new HashSet<>());
+ setNasUnsupportedDefaults(userId);
+ return mNasUnsupported.get(userId);
+ }
+
+ private void setNasUnsupportedDefaults(@UserIdInt int userId) {
+ if (mNasUnsupported != null && !mNasUnsupported.containsKey(userId)) {
+ mNasUnsupported.put(userId, new HashSet(List.of(mDefaultUnsupportedAdjustments)));
+ handleSavePolicyFile();
+ }
}
@Override
@@ -12684,25 +12606,16 @@ public class NotificationManagerService extends SystemService {
TextUtils.join(",", mAllowedAdjustmentKeyTypes));
out.endTag(null, ATT_ENABLED_TYPES);
if (notificationClassificationUi()) {
- out.startTag(null, TAG_TYPES_ENABLED_FOR_APPS);
- for (String pkg: mClassificationTypePackagesEnabledTypes.keySet()) {
- Set<Integer> allowedTypes =
- mClassificationTypePackagesEnabledTypes.get(pkg);
- if (allowedTypes != null) {
- out.startTag(null, ATT_APP_ENABLED_TYPES);
- out.attribute(null, ATT_PACKAGE, pkg);
- out.attribute(null, ATT_TYPES, TextUtils.join(",", allowedTypes));
- out.endTag(null, ATT_APP_ENABLED_TYPES);
- }
- }
- out.endTag(null, TAG_TYPES_ENABLED_FOR_APPS);
+ out.startTag(null, ATT_TYPES_DENIED_APPS);
+ out.attribute(null, ATT_TYPES,
+ TextUtils.join(",", mClassificationTypeDeniedPackages));
+ out.endTag(null, ATT_TYPES_DENIED_APPS);
}
}
}
@Override
- protected void readExtraTag(String tag, TypedXmlPullParser parser) throws IOException,
- XmlPullParserException {
+ protected void readExtraTag(String tag, TypedXmlPullParser parser) throws IOException {
if (!notificationClassification()) {
return;
}
@@ -12729,25 +12642,12 @@ public class NotificationManagerService extends SystemService {
}
}
}
- } else if (TAG_TYPES_ENABLED_FOR_APPS.equals(tag)) {
- final int appsOuterDepth = parser.getDepth();
+ } else if (notificationClassificationUi() && ATT_TYPES_DENIED_APPS.equals(tag)) {
+ final String apps = XmlUtils.readStringAttribute(parser, ATT_TYPES);
synchronized (mLock) {
- mClassificationTypePackagesEnabledTypes.clear();
- while (XmlUtils.nextElementWithin(parser, appsOuterDepth)) {
- if (!ATT_APP_ENABLED_TYPES.equals(parser.getName())) {
- continue;
- }
- final String app = XmlUtils.readStringAttribute(parser, ATT_PACKAGE);
- Set<Integer> allowedTypes = new ArraySet<>();
- final String typesString = XmlUtils.readStringAttribute(parser, ATT_TYPES);
- if (!TextUtils.isEmpty(typesString)) {
- allowedTypes = Arrays.stream(typesString.split(","))
- .map(Integer::valueOf)
- .collect(Collectors.toSet());
- }
- // Empty type list is allowed, because empty type list signifies the user
- // has manually cleared the package of allowed types.
- mClassificationTypePackagesEnabledTypes.put(app, allowedTypes);
+ mClassificationTypeDeniedPackages.clear();
+ if (!TextUtils.isEmpty(apps)) {
+ mClassificationTypeDeniedPackages.addAll(Arrays.asList(apps.split(",")));
}
}
}
@@ -12772,7 +12672,7 @@ public class NotificationManagerService extends SystemService {
List<String> unsupportedAdjustments = new ArrayList(
mNasUnsupported.getOrDefault(
UserHandle.getUserId(Binder.getCallingUid()),
- new HashSet<>())
+ new HashSet(List.of(mDefaultUnsupportedAdjustments)))
);
bundlesAllowed = !unsupportedAdjustments.contains(Adjustment.KEY_TYPE);
}
diff --git a/services/core/java/com/android/server/notification/ZenLog.java b/services/core/java/com/android/server/notification/ZenLog.java
index 1aa5ac046ae9..7e853d9d2d0b 100644
--- a/services/core/java/com/android/server/notification/ZenLog.java
+++ b/services/core/java/com/android/server/notification/ZenLog.java
@@ -16,6 +16,7 @@
package com.android.server.notification;
+import android.annotation.Nullable;
import android.app.NotificationManager;
import android.content.ComponentName;
import android.media.AudioManager;
@@ -26,6 +27,7 @@ import android.provider.Settings.Global;
import android.service.notification.IConditionProvider;
import android.service.notification.NotificationListenerService;
import android.service.notification.ZenModeConfig;
+import android.service.notification.ZenModeConfig.ConfigOrigin;
import android.service.notification.ZenModeDiff;
import android.util.LocalLog;
@@ -119,16 +121,17 @@ public class ZenLog {
append(TYPE_UNSUBSCRIBE, uri + "," + subscribeResult(provider, e));
}
- public static void traceConfig(String reason, ComponentName triggeringComponent,
- ZenModeConfig oldConfig, ZenModeConfig newConfig, int callingUid) {
+ public static void traceConfig(@ConfigOrigin int origin, String reason,
+ @Nullable ComponentName triggeringComponent, ZenModeConfig oldConfig,
+ ZenModeConfig newConfig, int callingUid) {
ZenModeDiff.ConfigDiff diff = new ZenModeDiff.ConfigDiff(oldConfig, newConfig);
- if (diff == null || !diff.hasDiff()) {
- append(TYPE_CONFIG, reason + " no changes");
+ if (!diff.hasDiff()) {
+ append(TYPE_CONFIG, reason + " (" + originToString(origin) + ") no changes");
} else {
- append(TYPE_CONFIG, reason
- + " - " + triggeringComponent + " : " + callingUid
- + ",\n" + (newConfig != null ? newConfig.toString() : null)
- + ",\n" + diff);
+ append(TYPE_CONFIG, reason + " (" + originToString(origin) + ") from uid " + callingUid
+ + (triggeringComponent != null ? " - " + triggeringComponent : "") + ",\n"
+ + (newConfig != null ? newConfig.toString() : null) + ",\n"
+ + diff);
}
}
@@ -241,7 +244,22 @@ public class ZenLog {
}
}
- private static String componentToString(ComponentName component) {
+ private static String originToString(@ConfigOrigin int origin) {
+ return switch (origin) {
+ case ZenModeConfig.ORIGIN_UNKNOWN -> "ORIGIN_UNKNOWN";
+ case ZenModeConfig.ORIGIN_INIT -> "ORIGIN_INIT";
+ case ZenModeConfig.ORIGIN_INIT_USER -> "ORIGIN_INIT_USER";
+ case ZenModeConfig.ORIGIN_USER_IN_SYSTEMUI -> "ORIGIN_USER_IN_SYSTEMUI";
+ case ZenModeConfig.ORIGIN_APP -> "ORIGIN_APP";
+ case ZenModeConfig.ORIGIN_SYSTEM -> "ORIGIN_SYSTEM";
+ case ZenModeConfig.ORIGIN_RESTORE_BACKUP -> "ORIGIN_RESTORE_BACKUP";
+ case ZenModeConfig.ORIGIN_USER_IN_APP -> "ORIGIN_USER_IN_APP";
+ default -> origin + "??";
+ };
+ }
+
+ @Nullable
+ private static String componentToString(@Nullable ComponentName component) {
return component != null ? component.toShortString() : null;
}
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index 0a63f3fb36d0..b39b6fde6258 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -16,6 +16,7 @@
package com.android.server.notification;
+import static android.app.AutomaticZenRule.TYPE_DRIVING;
import static android.app.AutomaticZenRule.TYPE_UNKNOWN;
import static android.app.NotificationManager.AUTOMATIC_RULE_STATUS_ACTIVATED;
import static android.app.NotificationManager.AUTOMATIC_RULE_STATUS_DEACTIVATED;
@@ -46,6 +47,7 @@ import static android.service.notification.ZenModeConfig.isImplicitRuleId;
import static com.android.internal.util.FrameworkStatsLog.DND_MODE_RULE;
import static com.android.internal.util.Preconditions.checkArgument;
+import static com.android.server.notification.Flags.preventZenDeviceEffectsWhileDriving;
import static java.util.Objects.requireNonNull;
@@ -659,7 +661,8 @@ public class ZenModeHelper {
mContext.getString(R.string.zen_mode_implicit_deactivated),
STATE_FALSE);
setAutomaticZenRuleStateLocked(newConfig, Collections.singletonList(rule),
- deactivated, ORIGIN_APP, callingUid);
+ deactivated, ORIGIN_APP,
+ "applyGlobalZenModeAsImplicitZenRule: " + callingPkg, callingUid);
}
} else {
// Either create a new rule with a default ZenPolicy, or update an existing rule's
@@ -971,26 +974,27 @@ public class ZenModeHelper {
if (Flags.modesApi()) {
if (rule != null && canManageAutomaticZenRule(rule, callingUid)) {
setAutomaticZenRuleStateLocked(newConfig, Collections.singletonList(rule),
- condition, origin, callingUid);
+ condition, origin, "setAzrState: " + rule.id, callingUid);
}
} else {
ArrayList<ZenRule> rules = new ArrayList<>();
rules.add(rule); // rule may be null and throw NPE in the next method.
- setAutomaticZenRuleStateLocked(newConfig, rules, condition, origin, callingUid);
+ setAutomaticZenRuleStateLocked(newConfig, rules, condition, origin,
+ "setAzrState: " + (rule != null ? rule.id : "null!"), callingUid);
}
}
}
- void setAutomaticZenRuleStateFromConditionProvider(UserHandle user, Uri ruleDefinition,
+ void setAutomaticZenRuleStateFromConditionProvider(UserHandle user, Uri ruleConditionId,
Condition condition, @ConfigOrigin int origin, int callingUid) {
- checkSetRuleStateOrigin("setAutomaticZenRuleState(Uri ruleDefinition)", origin);
+ checkSetRuleStateOrigin("setAutomaticZenRuleStateFromConditionProvider", origin);
ZenModeConfig newConfig;
synchronized (mConfigLock) {
ZenModeConfig config = getConfigLocked(user);
if (config == null) return;
newConfig = config.copy();
- List<ZenRule> matchingRules = findMatchingRules(newConfig, ruleDefinition, condition);
+ List<ZenRule> matchingRules = findMatchingRules(newConfig, ruleConditionId, condition);
if (Flags.modesApi()) {
for (int i = matchingRules.size() - 1; i >= 0; i--) {
if (!canManageAutomaticZenRule(matchingRules.get(i), callingUid)) {
@@ -998,13 +1002,14 @@ public class ZenModeHelper {
}
}
}
- setAutomaticZenRuleStateLocked(newConfig, matchingRules, condition, origin, callingUid);
+ setAutomaticZenRuleStateLocked(newConfig, matchingRules, condition, origin,
+ "setAzrStateFromCps: " + ruleConditionId, callingUid);
}
}
@GuardedBy("mConfigLock")
private void setAutomaticZenRuleStateLocked(ZenModeConfig config, List<ZenRule> rules,
- Condition condition, @ConfigOrigin int origin, int callingUid) {
+ Condition condition, @ConfigOrigin int origin, String reason, int callingUid) {
if (rules == null || rules.isEmpty()) return;
if (!Flags.modesUi()) {
@@ -1015,7 +1020,7 @@ public class ZenModeHelper {
for (ZenRule rule : rules) {
applyConditionAndReconsiderOverride(rule, condition, origin);
- setConfigLocked(config, rule.component, origin, "conditionChanged", callingUid);
+ setConfigLocked(config, rule.component, origin, reason, callingUid);
}
}
@@ -2111,13 +2116,14 @@ public class ZenModeHelper {
}
@GuardedBy("mConfigLock")
- private boolean setConfigLocked(ZenModeConfig config, ComponentName triggeringComponent,
- @ConfigOrigin int origin, String reason, int callingUid) {
+ private boolean setConfigLocked(ZenModeConfig config,
+ @Nullable ComponentName triggeringComponent, @ConfigOrigin int origin, String reason,
+ int callingUid) {
return setConfigLocked(config, origin, reason, triggeringComponent, true /*setRingerMode*/,
callingUid);
}
- void setConfig(ZenModeConfig config, ComponentName triggeringComponent,
+ void setConfig(ZenModeConfig config, @Nullable ComponentName triggeringComponent,
@ConfigOrigin int origin, String reason, int callingUid) {
synchronized (mConfigLock) {
setConfigLocked(config, triggeringComponent, origin, reason, callingUid);
@@ -2126,7 +2132,7 @@ public class ZenModeHelper {
@GuardedBy("mConfigLock")
private boolean setConfigLocked(ZenModeConfig config, @ConfigOrigin int origin,
- String reason, ComponentName triggeringComponent, boolean setRingerMode,
+ String reason, @Nullable ComponentName triggeringComponent, boolean setRingerMode,
int callingUid) {
final long identity = Binder.clearCallingIdentity();
try {
@@ -2149,7 +2155,7 @@ public class ZenModeHelper {
mConfigs.put(config.user, config);
}
if (DEBUG) Log.d(TAG, "setConfigLocked reason=" + reason, new Throwable());
- ZenLog.traceConfig(reason, triggeringComponent, mConfig, config, callingUid);
+ ZenLog.traceConfig(origin, reason, triggeringComponent, mConfig, config, callingUid);
// send some broadcasts
Policy newPolicy = getNotificationPolicy(config);
@@ -2375,6 +2381,26 @@ public class ZenModeHelper {
}
if (Flags.modesApi()) {
+ // Prevent other rules from applying grayscale if Driving is active (but allow it
+ // if _Driving itself_ wants grayscale).
+ if (Flags.modesUi() && preventZenDeviceEffectsWhileDriving()) {
+ boolean hasActiveDriving = false;
+ boolean hasActiveDrivingWithGrayscale = false;
+ for (ZenRule rule : mConfig.automaticRules.values()) {
+ if (rule.isActive() && rule.type == TYPE_DRIVING) {
+ hasActiveDriving = true;
+ if (rule.zenDeviceEffects != null
+ && rule.zenDeviceEffects.shouldDisplayGrayscale()) {
+ hasActiveDrivingWithGrayscale = true;
+ break; // Further rules won't affect decision.
+ }
+ }
+ }
+ if (hasActiveDriving && !hasActiveDrivingWithGrayscale) {
+ deviceEffectsBuilder.setShouldDisplayGrayscale(false);
+ }
+ }
+
ZenDeviceEffects deviceEffects = deviceEffectsBuilder.build();
if (!deviceEffects.equals(mConsolidatedDeviceEffects)) {
mConsolidatedDeviceEffects = deviceEffects;
diff --git a/services/core/java/com/android/server/notification/flags.aconfig b/services/core/java/com/android/server/notification/flags.aconfig
index b4a8aee66c7c..822ff48c831c 100644
--- a/services/core/java/com/android/server/notification/flags.aconfig
+++ b/services/core/java/com/android/server/notification/flags.aconfig
@@ -190,3 +190,13 @@ flag {
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ name: "prevent_zen_device_effects_while_driving"
+ namespace: "systemui"
+ description: "Don't apply certain device effects (such as grayscale) from active zen rules, if a rule of TYPE_DRIVING is active"
+ bug: "390389174"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/services/core/java/com/android/server/os/NativeTombstoneManager.java b/services/core/java/com/android/server/os/NativeTombstoneManager.java
index f23d7823be94..33c122964d77 100644
--- a/services/core/java/com/android/server/os/NativeTombstoneManager.java
+++ b/services/core/java/com/android/server/os/NativeTombstoneManager.java
@@ -137,16 +137,26 @@ public final class NativeTombstoneManager {
return;
}
- String processName = "UNKNOWN";
final boolean isProtoFile = filename.endsWith(".pb");
- File protoPath = isProtoFile ? path : new File(path.getAbsolutePath() + ".pb");
- Optional<TombstoneFile> parsedTombstone = handleProtoTombstone(protoPath, isProtoFile);
- if (parsedTombstone.isPresent()) {
- processName = parsedTombstone.get().getProcessName();
+ // Only process the pb tombstone output, the text version will be generated in
+ // BootReceiver.filterAndAddTombstoneToDropBox through pbtombstone
+ if (Flags.protoTombstone() && !isProtoFile) {
+ return;
}
- BootReceiver.addTombstoneToDropBox(mContext, path, isProtoFile, processName, mTmpFileLock);
+ File protoPath = isProtoFile ? path : new File(path.getAbsolutePath() + ".pb");
+
+ final String processName = handleProtoTombstone(protoPath, isProtoFile)
+ .map(TombstoneFile::getProcessName)
+ .orElse("UNKNOWN");
+
+ if (Flags.protoTombstone()) {
+ BootReceiver.filterAndAddTombstoneToDropBox(mContext, path, processName, mTmpFileLock);
+ } else {
+ BootReceiver.addTombstoneToDropBox(mContext, path, isProtoFile,
+ processName, mTmpFileLock);
+ }
// TODO(b/339371242): An optimizer on WearOS is misbehaving and this member is being garbage
// collected as it's never referenced inside this class outside of the constructor. But,
// it's a file watcher, and needs to stay alive to do its job. So, add a cheap check here to
diff --git a/services/core/java/com/android/server/os/core_os_flags.aconfig b/services/core/java/com/android/server/os/core_os_flags.aconfig
index efdc9b8c164f..5e35cf5f02d3 100644
--- a/services/core/java/com/android/server/os/core_os_flags.aconfig
+++ b/services/core/java/com/android/server/os/core_os_flags.aconfig
@@ -3,7 +3,7 @@ container: "system"
flag {
name: "proto_tombstone"
- namespace: "proto_tombstone_ns"
+ namespace: "stability"
description: "Use proto tombstones as source of truth for adding to dropbox"
bug: "323857385"
}
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 4cca85590967..8eb5b6f11cb2 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -856,39 +856,45 @@ final class InstallPackageHelper {
if (DEBUG_INSTALL) Log.v(TAG, "+ starting restore round-trip " + token);
final boolean succeeded = request.getReturnCode() == PackageManager.INSTALL_SUCCEEDED;
- if (succeeded && doRestore) {
- // Pass responsibility to the Backup Manager. It will perform a
- // restore if appropriate, then pass responsibility back to the
- // Package Manager to run the post-install observer callbacks
- // and broadcasts.
- request.closeFreezer();
- doRestore = performBackupManagerRestore(userId, token, request);
- }
-
- // If this is an update to a package that might be potentially downgraded, then we
- // need to check with the rollback manager whether there's any userdata that might
- // need to be snapshotted or restored for the package.
- //
- // TODO(narayan): Get this working for cases where userId == UserHandle.USER_ALL.
- if (succeeded && !doRestore && update) {
- doRestore = performRollbackManagerRestore(userId, token, request);
- }
-
- if (succeeded && doRestore && !request.hasPostInstallRunnable()) {
- boolean hasNeverBeenRestored =
- packageSetting != null && packageSetting.isPendingRestore();
- request.setPostInstallRunnable(() -> {
- // Permissions should be restored on each user that has the app installed for the
- // first time, unless it's an unarchive install for an archived app, in which case
- // the permissions should be restored on each user that has the app updated.
- int[] userIdsToRestorePermissions = hasNeverBeenRestored
- ? request.getUpdateBroadcastUserIds()
- : request.getFirstTimeBroadcastUserIds();
- for (int restorePermissionUserId : userIdsToRestorePermissions) {
- mPm.restorePermissionsAndUpdateRolesForNewUserInstall(request.getName(),
- restorePermissionUserId);
- }
- });
+ if (succeeded) {
+ request.onRestoreStarted();
+ if (doRestore) {
+ // Pass responsibility to the Backup Manager. It will perform a
+ // restore if appropriate, then pass responsibility back to the
+ // Package Manager to run the post-install observer callbacks
+ // and broadcasts.
+ // Note: MUST close freezer before backup/restore, otherwise test
+ // of CtsBackupHostTestCases will fail.
+ request.closeFreezer();
+ doRestore = performBackupManagerRestore(userId, token, request);
+ }
+
+ // If this is an update to a package that might be potentially downgraded, then we
+ // need to check with the rollback manager whether there's any userdata that might
+ // need to be snapshotted or restored for the package.
+ //
+ // TODO(narayan): Get this working for cases where userId == UserHandle.USER_ALL.
+ if (!doRestore && update) {
+ doRestore = performRollbackManagerRestore(userId, token, request);
+ }
+
+ if (doRestore && !request.hasPostInstallRunnable()) {
+ boolean hasNeverBeenRestored =
+ packageSetting != null && packageSetting.isPendingRestore();
+ request.setPostInstallRunnable(() -> {
+ // Permissions should be restored on each user that has the app installed for
+ // the first time, unless it's an unarchive install for an archived app, in
+ // which case the permissions should be restored on each user that has the
+ // app updated.
+ int[] userIdsToRestorePermissions = hasNeverBeenRestored
+ ? request.getUpdateBroadcastUserIds()
+ : request.getFirstTimeBroadcastUserIds();
+ for (int restorePermissionUserId : userIdsToRestorePermissions) {
+ mPm.restorePermissionsAndUpdateRolesForNewUserInstall(request.getName(),
+ restorePermissionUserId);
+ }
+ });
+ }
}
if (doRestore) {
@@ -898,8 +904,11 @@ final class InstallPackageHelper {
}
}
} else {
- // No restore possible, or the Backup Manager was mysteriously not
- // available -- just fire the post-install work request directly.
+ // No restore possible, or the Backup Manager was mysteriously not available.
+ // we don't need to wait for restore to complete before closing the freezer,
+ // so we can close the freezer right away.
+ // Also just fire the post-install work request directly.
+ request.closeFreezer();
if (DEBUG_INSTALL) Log.v(TAG, "No restore - queue post-install for " + token);
Trace.asyncTraceBegin(TRACE_TAG_PACKAGE_MANAGER, "postInstall", token);
diff --git a/services/core/java/com/android/server/pm/InstallRequest.java b/services/core/java/com/android/server/pm/InstallRequest.java
index c96c160deb0f..fbf5db5d9635 100644
--- a/services/core/java/com/android/server/pm/InstallRequest.java
+++ b/services/core/java/com/android/server/pm/InstallRequest.java
@@ -1016,6 +1016,18 @@ final class InstallRequest {
}
}
+ public void onRestoreStarted() {
+ if (mPackageMetrics != null) {
+ mPackageMetrics.onStepStarted(PackageMetrics.STEP_RESTORE);
+ }
+ }
+
+ public void onRestoreFinished() {
+ if (mPackageMetrics != null) {
+ mPackageMetrics.onStepFinished(PackageMetrics.STEP_RESTORE);
+ }
+ }
+
public void onDexoptFinished(DexoptResult dexoptResult) {
// Only report external profile warnings when installing from adb. The goal is to warn app
// developers if they have provided bad external profiles, so it's not beneficial to report
diff --git a/services/core/java/com/android/server/pm/PackageHandler.java b/services/core/java/com/android/server/pm/PackageHandler.java
index 9916be680374..7b1eb58d25f4 100644
--- a/services/core/java/com/android/server/pm/PackageHandler.java
+++ b/services/core/java/com/android/server/pm/PackageHandler.java
@@ -99,6 +99,7 @@ final class PackageHandler extends Handler {
}
break;
}
+ request.onRestoreFinished();
request.closeFreezer();
request.onInstallCompleted();
request.runPostInstallRunnable();
diff --git a/services/core/java/com/android/server/pm/PackageMetrics.java b/services/core/java/com/android/server/pm/PackageMetrics.java
index 856d6a726da5..994ee421790c 100644
--- a/services/core/java/com/android/server/pm/PackageMetrics.java
+++ b/services/core/java/com/android/server/pm/PackageMetrics.java
@@ -72,6 +72,7 @@ final class PackageMetrics {
public static final int STEP_COMMIT = 4;
public static final int STEP_DEXOPT = 5;
public static final int STEP_FREEZE_INSTALL = 6;
+ public static final int STEP_RESTORE = 7;
@IntDef(prefix = {"STEP_"}, value = {
STEP_PREPARE,
@@ -79,7 +80,8 @@ final class PackageMetrics {
STEP_RECONCILE,
STEP_COMMIT,
STEP_DEXOPT,
- STEP_FREEZE_INSTALL
+ STEP_FREEZE_INSTALL,
+ STEP_RESTORE
})
@Retention(RetentionPolicy.SOURCE)
public @interface StepInt {
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
index 9d840d0c0d35..b905041b59e5 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -26,6 +26,7 @@ import static android.app.AppOpsManager.ATTRIBUTION_FLAGS_NONE;
import static android.app.AppOpsManager.MODE_ALLOWED;
import static android.app.AppOpsManager.MODE_ERRORED;
import static android.app.AppOpsManager.MODE_IGNORED;
+import static android.app.AppOpsManager.OP_BLUETOOTH_CONNECT;
import static android.content.pm.ApplicationInfo.AUTO_REVOKE_DISALLOWED;
import static android.content.pm.ApplicationInfo.AUTO_REVOKE_DISCOURAGED;
import static android.permission.flags.Flags.serverSideAttributionRegistration;
@@ -1668,7 +1669,22 @@ public class PermissionManagerService extends IPermissionManager.Stub {
throw new SecurityException(msg + ":" + e.getMessage());
}
}
- return Math.max(checkedOpResult, notedOpResult);
+ int result = Math.max(checkedOpResult, notedOpResult);
+ // TODO(b/333931259): Remove extra logging after this issue is diagnosed.
+ if (op == OP_BLUETOOTH_CONNECT && result == MODE_ERRORED) {
+ if (result == checkedOpResult) {
+ Slog.e(LOG_TAG, "BLUETOOTH_CONNECT permission hard denied as"
+ + " checkOp for resolvedAttributionSource "
+ + resolvedAttributionSource + " and op " + op
+ + " returned MODE_ERRORED");
+ } else {
+ Slog.e(LOG_TAG, "BLUETOOTH_CONNECT permission hard denied as"
+ + " noteOp for resolvedAttributionSource "
+ + resolvedAttributionSource + " and op " + notedOp
+ + " returned MODE_ERRORED");
+ }
+ }
+ return result;
}
}
diff --git a/services/core/java/com/android/server/power/OWNERS b/services/core/java/com/android/server/power/OWNERS
index c1fad33fef0f..a1531a810c3c 100644
--- a/services/core/java/com/android/server/power/OWNERS
+++ b/services/core/java/com/android/server/power/OWNERS
@@ -2,6 +2,8 @@ michaelwr@google.com
santoscordon@google.com
petsjonkin@google.com
brup@google.com
+flc@google.com
+wilczynskip@google.com
per-file ThermalManagerService.java=file:/THERMAL_OWNERS
per-file LowPowerStandbyController.java=qingxun@google.com
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 36c337c29def..7f2c68ff60b1 100644
--- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
+++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
@@ -4899,7 +4899,7 @@ public class StatsPullAtomService extends SystemService {
Slog.e(TAG, "Disconnected from keystore service. Cannot pull.", e);
return StatsManager.PULL_SKIP;
} catch (ServiceSpecificException e) {
- Slog.e(TAG, "pulling keystore metrics failed", e);
+ Slog.e(TAG, "Pulling keystore atom with tag " + atomTag + " failed", e);
return StatsManager.PULL_SKIP;
} finally {
Binder.restoreCallingIdentity(callingToken);
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index b7b4cc0b6861..48dd2ebc7044 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -87,6 +87,7 @@ import android.service.quicksettings.TileService;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.IndentingPrintWriter;
+import android.util.IntArray;
import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
@@ -124,6 +125,7 @@ import com.android.server.policy.GlobalActionsProvider;
import com.android.server.power.ShutdownCheckPoints;
import com.android.server.power.ShutdownThread;
import com.android.server.wm.ActivityTaskManagerInternal;
+import com.android.systemui.shared.Flags;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -341,15 +343,19 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D
@Override
public void onDisplayAdded(int displayId) {
- synchronized (mLock) {
- mDisplayUiState.put(displayId, new UiState());
+ if (Flags.statusBarConnectedDisplays()) {
+ synchronized (mLock) {
+ mDisplayUiState.put(displayId, new UiState());
+ }
}
}
@Override
public void onDisplayRemoved(int displayId) {
- synchronized (mLock) {
- mDisplayUiState.remove(displayId);
+ if (Flags.statusBarConnectedDisplays()) {
+ synchronized (mLock) {
+ mDisplayUiState.remove(displayId);
+ }
}
}
@@ -1320,53 +1326,66 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D
return mTracingEnabled;
}
- // TODO(b/117478341): make it aware of multi-display if needed.
@Override
public void disable(int what, IBinder token, String pkg) {
disableForUser(what, token, pkg, mCurrentUserId);
}
- // TODO(b/117478341): make it aware of multi-display if needed.
+ /**
+ * Disable additional status bar features for user for all displays. Pass the bitwise-or of the
+ * {@code #DISABLE_*} flags. To re-enable everything, pass {@code #DISABLE_NONE}.
+ *
+ * Warning: Only pass {@code #DISABLE_*} flags into this function, do not use
+ * {@code #DISABLE2_*} flags.
+ */
@Override
public void disableForUser(int what, IBinder token, String pkg, int userId) {
enforceStatusBar();
enforceValidCallingUser();
synchronized (mLock) {
- disableLocked(DEFAULT_DISPLAY, userId, what, token, pkg, 1);
+ IntArray displayIds = new IntArray();
+ for (int i = 0; i < mDisplayUiState.size(); i++) {
+ displayIds.add(mDisplayUiState.keyAt(i));
+ }
+ disableLocked(displayIds, userId, what, token, pkg, 1);
}
}
- // TODO(b/117478341): make it aware of multi-display if needed.
/**
- * Disable additional status bar features. Pass the bitwise-or of the DISABLE2_* flags.
- * To re-enable everything, pass {@link #DISABLE2_NONE}.
+ * Disable additional status bar features. Pass the bitwise-or of the {@code #DISABLE2_*} flags.
+ * To re-enable everything, pass {@code #DISABLE2_NONE}.
*
- * Warning: Only pass DISABLE2_* flags into this function, do not use DISABLE_* flags.
+ * Warning: Only pass {@code #DISABLE2_*} flags into this function, do not use
+ * {@code #DISABLE_*} flags.
*/
@Override
public void disable2(int what, IBinder token, String pkg) {
disable2ForUser(what, token, pkg, mCurrentUserId);
}
- // TODO(b/117478341): make it aware of multi-display if needed.
/**
- * Disable additional status bar features for a given user. Pass the bitwise-or of the
- * DISABLE2_* flags. To re-enable everything, pass {@link #DISABLE_NONE}.
+ * Disable additional status bar features for a given user for all displays. Pass the bitwise-or
+ * of the {@code #DISABLE2_*} flags. To re-enable everything, pass {@code #DISABLE2_NONE}.
*
- * Warning: Only pass DISABLE2_* flags into this function, do not use DISABLE_* flags.
+ * Warning: Only pass {@code #DISABLE2_*} flags into this function, do not use
+ * {@code #DISABLE_*} flags.
*/
@Override
public void disable2ForUser(int what, IBinder token, String pkg, int userId) {
enforceStatusBar();
synchronized (mLock) {
- disableLocked(DEFAULT_DISPLAY, userId, what, token, pkg, 2);
+ IntArray displayIds = new IntArray();
+ for (int i = 0; i < mDisplayUiState.size(); i++) {
+ displayIds.add(mDisplayUiState.keyAt(i));
+ }
+ disableLocked(displayIds, userId, what, token, pkg, 2);
}
}
- private void disableLocked(int displayId, int userId, int what, IBinder token, String pkg,
- int whichFlag) {
+ private void disableLocked(IntArray displayIds, int userId, int what, IBinder token,
+ String pkg, int whichFlag) {
// It's important that the the callback and the call to mBar get done
// in the same order when multiple threads are calling this function
// so they are paired correctly. The messages on the handler will be
@@ -1376,18 +1395,27 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D
// Ensure state for the current user is applied, even if passed a non-current user.
final int net1 = gatherDisableActionsLocked(mCurrentUserId, 1);
final int net2 = gatherDisableActionsLocked(mCurrentUserId, 2);
- final UiState state = getUiState(displayId);
- if (!state.disableEquals(net1, net2)) {
- state.setDisabled(net1, net2);
- mHandler.post(() -> mNotificationDelegate.onSetDisabled(net1));
- IStatusBar bar = mBar;
- if (bar != null) {
- try {
- bar.disable(displayId, net1, net2);
- } catch (RemoteException ex) {
+ boolean shouldCallNotificationOnSetDisabled = false;
+ IStatusBar bar = mBar;
+ for (int displayId : displayIds.toArray()) {
+ final UiState state = getUiState(displayId);
+ if (!state.disableEquals(net1, net2)) {
+ shouldCallNotificationOnSetDisabled = true;
+ state.setDisabled(net1, net2);
+ if (bar != null) {
+ try {
+ // TODO(b/388244660): Create IStatusBar#disableForAllDisplays to avoid
+ // multiple IPC calls.
+ bar.disable(displayId, net1, net2);
+ } catch (RemoteException ex) {
+ Slog.e(TAG, "Unable to disable Status bar.", ex);
+ }
}
}
}
+ if (shouldCallNotificationOnSetDisabled) {
+ mHandler.post(() -> mNotificationDelegate.onSetDisabled(net1));
+ }
}
/**
@@ -1539,7 +1567,8 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D
if (SPEW) Slog.d(TAG, "setDisableFlags(0x" + Integer.toHexString(flags) + ")");
synchronized (mLock) {
- disableLocked(displayId, mCurrentUserId, flags, mSysUiVisToken, cause, 1);
+ disableLocked(IntArray.wrap(new int[]{displayId}), mCurrentUserId, flags,
+ mSysUiVisToken, cause, 1);
}
}
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 0226650ec560..f4870d5548cf 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -406,7 +406,7 @@ import java.util.function.Predicate;
/**
* An entry in the history task, representing an activity.
*/
-final class ActivityRecord extends WindowToken implements WindowManagerService.AppFreezeListener {
+final class ActivityRecord extends WindowToken {
private static final String TAG = TAG_WITH_CLASS_NAME ? "ActivityRecord" : TAG_ATM;
private static final String TAG_ADD_REMOVE = TAG + POSTFIX_ADD_REMOVE;
private static final String TAG_APP = TAG + POSTFIX_APP;
@@ -736,9 +736,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
*/
boolean mAllowCrossUidActivitySwitchFromBelow;
- /** Have we been asked to have this token keep the screen frozen? */
- private boolean mFreezingScreen;
-
// These are used for determining when all windows associated with
// an activity have been drawn, so they can be made visible together
// at the same time.
@@ -784,11 +781,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
@NonNull
final AppCompatController mAppCompatController;
- // Whether the activity is eligible to be letterboxed for fixed orientation with respect to its
- // requested orientation, even when it's letterbox for another reason (e.g., size compat mode)
- // and therefore #isLetterboxedForFixedOrientationAndAspectRatio returns false.
- private boolean mIsEligibleForFixedOrientationLetterbox;
-
/**
* Whether the activity is to be displayed. See {@link android.R.attr#windowNoDisplay}.
*/
@@ -2829,7 +2821,10 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
}
void removeStartingWindow() {
- boolean prevEligibleForLetterboxEducation = isEligibleForLetterboxEducation();
+ final AppCompatLetterboxPolicy letterboxPolicy = mAppCompatController
+ .getAppCompatLetterboxPolicy();
+ boolean prevEligibleForLetterboxEducation =
+ letterboxPolicy.isEligibleForLetterboxEducation();
if (mStartingData != null
&& mStartingData.mRemoveAfterTransaction == AFTER_TRANSITION_FINISH) {
@@ -2842,8 +2837,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
removeStartingWindowAnimation(true /* prepareAnimation */);
final Task task = getTask();
- if (prevEligibleForLetterboxEducation != isEligibleForLetterboxEducation()
- && task != null) {
+ if (task != null && prevEligibleForLetterboxEducation
+ != letterboxPolicy.isEligibleForLetterboxEducation()) {
// Trigger TaskInfoChanged to update the letterbox education.
task.dispatchTaskInfoChangedIfNeeded(true /* force */);
}
@@ -4513,8 +4508,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
removeIfPossible();
}
- stopFreezingScreen(true, true);
-
final DisplayContent dc = getDisplayContent();
if (dc.mFocusedApp == this) {
ProtoLog.v(WM_DEBUG_FOCUS_LIGHT,
@@ -5795,9 +5788,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
+ " visibleRequested=%b, isInTransition=%b, runningAnimation=%b, caller=%s",
this, isVisible(), mVisibleRequested, isInTransition(), runningAnimation,
Debug.getCallers(5));
- if (!visible) {
- stopFreezingScreen(true, true);
- } else {
+ if (visible) {
// If we are being set visible, and the starting window is not yet displayed,
// then make sure it doesn't get displayed.
if (mStartingWindow != null && !mStartingWindow.isDrawn()
@@ -5805,9 +5796,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
mStartingWindow.clearPolicyVisibilityFlag(LEGACY_POLICY_VISIBILITY);
mStartingWindow.mLegacyPolicyVisibilityAfterAnim = false;
}
- // We are becoming visible, so better freeze the screen with the windows that are
- // getting visible so we also wait for them.
- forAllWindows(mWmService::makeWindowFreezingScreenIfNeededLocked, true);
}
// dispatchTaskInfoChangedIfNeeded() right after ActivityRecord#setVisibility() can report
// the stale visible state, because the state will be updated after the app transition.
@@ -6844,123 +6832,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
rootTask.removeLaunchTickMessages();
}
- boolean mayFreezeScreenLocked() {
- return mayFreezeScreenLocked(app);
- }
-
- private boolean mayFreezeScreenLocked(WindowProcessController app) {
- // Only freeze the screen if this activity is currently attached to
- // an application, and that application is not blocked or unresponding.
- // In any other case, we can't count on getting the screen unfrozen,
- // so it is best to leave as-is.
- return hasProcess() && !app.isCrashing() && !app.isNotResponding();
- }
-
- void startFreezingScreenLocked(WindowProcessController app, int configChanges) {
- if (mayFreezeScreenLocked(app)) {
- if (getParent() == null) {
- Slog.w(TAG_WM,
- "Attempted to freeze screen with non-existing app token: " + token);
- return;
- }
-
- // Window configuration changes only effect windows, so don't require a screen freeze.
- int freezableConfigChanges = configChanges & ~(CONFIG_WINDOW_CONFIGURATION);
- if (freezableConfigChanges == 0 && okToDisplay()) {
- ProtoLog.v(WM_DEBUG_ORIENTATION, "Skipping set freeze of %s", token);
- return;
- }
-
- startFreezingScreen();
- }
- }
-
- void startFreezingScreen() {
- startFreezingScreen(ROTATION_UNDEFINED /* overrideOriginalDisplayRotation */);
- }
-
- void startFreezingScreen(int overrideOriginalDisplayRotation) {
- if (mTransitionController.isShellTransitionsEnabled()) {
- return;
- }
- ProtoLog.i(WM_DEBUG_ORIENTATION,
- "Set freezing of %s: visible=%b freezing=%b visibleRequested=%b. %s",
- token, isVisible(), mFreezingScreen, mVisibleRequested,
- new RuntimeException().fillInStackTrace());
- if (!mVisibleRequested) {
- return;
- }
-
- // If the override is given, the rotation of display doesn't change but we still want to
- // cover the activity whose configuration is changing by freezing the display and running
- // the rotation animation.
- final boolean forceRotation = overrideOriginalDisplayRotation != ROTATION_UNDEFINED;
- if (!mFreezingScreen) {
- mFreezingScreen = true;
- mWmService.registerAppFreezeListener(this);
- mWmService.mAppsFreezingScreen++;
- if (mWmService.mAppsFreezingScreen == 1) {
- if (forceRotation) {
- // Make sure normal rotation animation will be applied.
- mDisplayContent.getDisplayRotation().cancelSeamlessRotation();
- }
- mWmService.startFreezingDisplay(0 /* exitAnim */, 0 /* enterAnim */,
- mDisplayContent, overrideOriginalDisplayRotation);
- mWmService.mH.removeMessages(H.APP_FREEZE_TIMEOUT);
- mWmService.mH.sendEmptyMessageDelayed(H.APP_FREEZE_TIMEOUT, 2000);
- }
- }
- if (forceRotation) {
- // The rotation of the real display won't change, so in order to unfreeze the screen
- // via {@link #checkAppWindowsReadyToShow}, the windows have to be able to call
- // {@link WindowState#reportResized} (it is skipped if the window is freezing) to update
- // the drawn state.
- return;
- }
- final int count = mChildren.size();
- for (int i = 0; i < count; i++) {
- final WindowState w = mChildren.get(i);
- w.onStartFreezingScreen();
- }
- }
-
- boolean isFreezingScreen() {
- return mFreezingScreen;
- }
-
- @Override
- public void onAppFreezeTimeout() {
- Slog.w(TAG_WM, "Force clearing freeze: " + this);
- stopFreezingScreen(true, true);
- }
-
- void stopFreezingScreen(boolean unfreezeSurfaceNow, boolean force) {
- if (!mFreezingScreen) {
- return;
- }
- ProtoLog.v(WM_DEBUG_ORIENTATION,
- "Clear freezing of %s force=%b", this, force);
- final int count = mChildren.size();
- boolean unfrozeWindows = false;
- for (int i = 0; i < count; i++) {
- final WindowState w = mChildren.get(i);
- unfrozeWindows |= w.onStopFreezingScreen();
- }
- if (force || unfrozeWindows) {
- ProtoLog.v(WM_DEBUG_ORIENTATION, "No longer freezing: %s", this);
- mFreezingScreen = false;
- mWmService.unregisterAppFreezeListener(this);
- mWmService.mAppsFreezingScreen--;
- mWmService.mLastFinishedFreezeSource = this;
- }
- if (unfreezeSurfaceNow) {
- if (unfrozeWindows) {
- mWmService.mWindowPlacerLocked.performSurfacePlacement();
- }
- mWmService.stopFreezingDisplayLocked();
- }
- }
-
void onFirstWindowDrawn(WindowState win) {
firstWindowDrawn = true;
// stop tracking
@@ -7103,24 +6974,11 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
return;
}
- // The token has now changed state to having all windows shown... what to do, what to do?
- if (mFreezingScreen) {
- showAllWindowsLocked();
- stopFreezingScreen(false, true);
- ProtoLog.i(WM_DEBUG_ORIENTATION,
- "Setting mOrientationChangeComplete=true because wtoken %s "
- + "numInteresting=%d numDrawn=%d",
- this, mNumInterestingWindows, mNumDrawnWindows);
- // This will set mOrientationChangeComplete and cause a pass through layout.
- setAppLayoutChanges(FINISH_LAYOUT_REDO_WALLPAPER,
- "checkAppWindowsReadyToShow: freezingScreen");
- } else {
- setAppLayoutChanges(FINISH_LAYOUT_REDO_ANIM, "checkAppWindowsReadyToShow");
+ setAppLayoutChanges(FINISH_LAYOUT_REDO_ANIM, "checkAppWindowsReadyToShow");
- // We can now show all of the drawn windows!
- if (!getDisplayContent().mOpeningApps.contains(this) && canShowWindows()) {
- showAllWindowsLocked();
- }
+ // We can now show all of the drawn windows!
+ if (!getDisplayContent().mOpeningApps.contains(this) && canShowWindows()) {
+ showAllWindowsLocked();
}
}
@@ -7206,10 +7064,10 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
if (DEBUG_STARTING_WINDOW_VERBOSE && w == mStartingWindow) {
Slog.d(TAG, "updateWindows: starting " + w + " isOnScreen=" + w.isOnScreen()
- + " allDrawn=" + allDrawn + " freezingScreen=" + mFreezingScreen);
+ + " allDrawn=" + allDrawn);
}
- if (allDrawn && !mFreezingScreen) {
+ if (allDrawn) {
return false;
}
@@ -7250,10 +7108,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
mNumDrawnWindows++;
if (DEBUG_VISIBILITY || WM_DEBUG_ORIENTATION.isLogToLogcat()) {
- Slog.v(TAG, "tokenMayBeDrawn: "
- + this + " w=" + w + " numInteresting=" + mNumInterestingWindows
- + " freezingScreen=" + mFreezingScreen
- + " mAppFreezing=" + w.mAppFreezing);
+ Slog.v(TAG, "tokenMayBeDrawn: " + this + " w=" + w
+ + " numInteresting=" + mNumInterestingWindows);
}
isInterestingAndDrawn = true;
@@ -8201,8 +8057,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
}
mDisplayContent.mPinnedTaskController.onCancelFixedRotationTransform();
- // Perform rotation animation according to the rotation of this activity.
- startFreezingScreen(originalDisplayRotation);
// This activity may relaunch or perform configuration change so once it has reported drawn,
// the screen can be unfrozen.
ensureActivityConfiguration();
@@ -8440,9 +8294,11 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
mTmpConfig.updateFrom(resolvedConfig);
newParentConfiguration = mTmpConfig;
}
-
- mAppCompatController.getAspectRatioPolicy().reset();
- mIsEligibleForFixedOrientationLetterbox = false;
+ final AppCompatAspectRatioPolicy aspectRatioPolicy =
+ mAppCompatController.getAspectRatioPolicy();
+ aspectRatioPolicy.reset();
+ mAppCompatController.getAppCompatLetterboxPolicy()
+ .resetFixedOrientationLetterboxEligibility();
mResolveConfigHint.resolveTmpOverrides(mDisplayContent, newParentConfiguration,
isFixedRotationTransforming());
@@ -8472,12 +8328,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
// If activity in fullscreen mode is letterboxed because of fixed orientation then bounds
// are already calculated in resolveFixedOrientationConfiguration.
// Don't apply aspect ratio if app is overridden to fullscreen by device user/manufacturer.
- if (!mAppCompatController.getAspectRatioPolicy()
- .isLetterboxedForFixedOrientationAndAspectRatio()
- && !mAppCompatController.getAspectRatioOverrides()
- .hasFullscreenOverride()) {
- resolveAspectRatioRestriction(newParentConfiguration);
- }
+ aspectRatioPolicy.resolveAspectRatioRestrictionIfNeeded(newParentConfiguration);
final AppCompatDisplayInsets appCompatDisplayInsets = getAppCompatDisplayInsets();
final AppCompatSizeCompatModePolicy scmPolicy =
mAppCompatController.getSizeCompatModePolicy();
@@ -8509,8 +8360,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
// Fixed orientation letterboxing is possible on both large screen devices
// with ignoreOrientationRequest enabled and on phones in split screen even with
// ignoreOrientationRequest disabled.
- && (mAppCompatController.getAspectRatioPolicy()
- .isLetterboxedForFixedOrientationAndAspectRatio()
+ && (aspectRatioPolicy.isLetterboxedForFixedOrientationAndAspectRatio()
// Limiting check for aspect ratio letterboxing to devices with enabled
// ignoreOrientationRequest. This avoids affecting phones where apps may
// not expect the change of smallestScreenWidthDp after rotation which is
@@ -8518,7 +8368,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
// accurate on phones shouldn't make the big difference and is expected
// to be already well-tested by apps.
|| (isIgnoreOrientationRequest
- && mAppCompatController.getAspectRatioPolicy().isAspectRatioApplied()))) {
+ && aspectRatioPolicy.isAspectRatioApplied()))) {
// TODO(b/264034555): Use mDisplayContent to calculate smallestScreenWidthDp from all
// rotations and only re-calculate if parent bounds have non-orientation size change.
resolvedConfig.smallestScreenWidthDp =
@@ -8785,28 +8635,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
}
/**
- * Whether this activity is eligible for letterbox eduction.
- *
- * <p>Conditions that need to be met:
- *
- * <ul>
- * <li>{@link AppCompatConfiguration#getIsEducationEnabled} is true.
- * <li>The activity is eligible for fixed orientation letterbox.
- * <li>The activity is in fullscreen.
- * <li>The activity is portrait-only.
- * <li>The activity doesn't have a starting window (education should only be displayed
- * once the starting window is removed in {@link #removeStartingWindow}).
- * </ul>
- */
- boolean isEligibleForLetterboxEducation() {
- return mWmService.mAppCompatConfiguration.getIsEducationEnabled()
- && mIsEligibleForFixedOrientationLetterbox
- && getWindowingMode() == WINDOWING_MODE_FULLSCREEN
- && getRequestedConfigurationOrientation() == ORIENTATION_PORTRAIT
- && mStartingWindow == null;
- }
-
- /**
* In some cases, applying insets to bounds changes the orientation. For example, if a
* close-to-square display rotates to portrait to respect a portrait orientation activity, after
* insets such as the status and nav bars are applied, the activity may actually have a
@@ -8910,11 +8738,11 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
// If the activity requires a different orientation (either by override or activityInfo),
// make it fit the available bounds by scaling down its bounds.
final int forcedOrientation = getRequestedConfigurationOrientation();
+ final boolean isEligibleForFixedOrientationLetterbox = mAppCompatController
+ .getAppCompatLetterboxPolicy()
+ .resolveFixedOrientationLetterboxEligibility(forcedOrientation, parentOrientation);
- mIsEligibleForFixedOrientationLetterbox = forcedOrientation != ORIENTATION_UNDEFINED
- && forcedOrientation != parentOrientation;
-
- if (!mIsEligibleForFixedOrientationLetterbox && (forcedOrientation == ORIENTATION_UNDEFINED
+ if (!isEligibleForFixedOrientationLetterbox && (forcedOrientation == ORIENTATION_UNDEFINED
|| orientationRespectedWithInsets)) {
return;
}
@@ -9007,37 +8835,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
new Rect(resolvedBounds));
}
- /**
- * Resolves aspect ratio restrictions for an activity. If the bounds are restricted by
- * aspect ratio, the position will be adjusted later in {@link #updateResolvedBoundsPosition
- * within parent's app bounds to balance the visual appearance. The policy of aspect ratio has
- * higher priority than the requested override bounds.
- */
- private void resolveAspectRatioRestriction(Configuration newParentConfiguration) {
- final Configuration resolvedConfig = getResolvedOverrideConfiguration();
- final Rect parentAppBounds = mResolveConfigHint.mParentAppBoundsOverride;
- final Rect parentBounds = newParentConfiguration.windowConfiguration.getBounds();
- final Rect resolvedBounds = resolvedConfig.windowConfiguration.getBounds();
- // Use tmp bounds to calculate aspect ratio so we can know whether the activity should use
- // restricted size (resolved bounds may be the requested override bounds).
- mTmpBounds.setEmpty();
- final AppCompatAspectRatioPolicy aspectRatioPolicy = mAppCompatController
- .getAspectRatioPolicy();
- aspectRatioPolicy.applyAspectRatioForLetterbox(mTmpBounds, parentAppBounds, parentBounds);
- // If the out bounds is not empty, it means the activity cannot fill parent's app bounds,
- // then they should be aligned later in #updateResolvedBoundsPosition()
- if (!mTmpBounds.isEmpty()) {
- resolvedBounds.set(mTmpBounds);
- }
- if (!resolvedBounds.isEmpty() && !resolvedBounds.equals(parentBounds)) {
- // Compute the configuration based on the resolved bounds. If aspect ratio doesn't
- // restrict, the bounds should be the requested override bounds.
- mResolveConfigHint.mTmpOverrideDisplayInfo = getFixedRotationTransformDisplayInfo();
- computeConfigByResolveHint(resolvedConfig, newParentConfiguration);
- aspectRatioPolicy.setLetterboxBoundsForAspectRatio(new Rect(resolvedBounds));
- }
- }
-
@Override
public Rect getBounds() {
// TODO(b/268458693): Refactor configuration inheritance in case of translucent activities
@@ -9443,11 +9240,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
mLastReportedConfiguration);
if (shouldRelaunchLocked(changes, mTmpConfig)) {
- // Aha, the activity isn't handling the change, so DIE DIE DIE.
- if (mVisible && mAtmService.mTmpUpdateConfigurationResult.mIsUpdating
- && !mTransitionController.isShellTransitionsEnabled()) {
- startFreezingScreenLocked(app, mAtmService.mTmpUpdateConfigurationResult.changes);
- }
final boolean displayMayChange = mTmpConfig.windowConfiguration.getDisplayRotation()
!= getWindowConfiguration().getDisplayRotation()
|| !mTmpConfig.windowConfiguration.getMaxBounds().equals(
@@ -9455,10 +9247,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
final boolean isAppResizeOnly = !displayMayChange
&& (changes & ~(CONFIG_SCREEN_SIZE | CONFIG_SMALLEST_SCREEN_SIZE
| CONFIG_ORIENTATION | CONFIG_SCREEN_LAYOUT)) == 0;
- // Do not preserve window if it is freezing screen because the original window won't be
- // able to update drawn state that causes freeze timeout.
// TODO(b/258618073): Always preserve if possible.
- final boolean preserveWindow = isAppResizeOnly && !mFreezingScreen;
+ final boolean preserveWindow = isAppResizeOnly;
final boolean hasResizeChange = hasResizeChange(changes & ~info.getRealConfigChanged());
if (hasResizeChange) {
final boolean isDragResizing = task.isDragResizing();
@@ -9754,7 +9544,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
scheduleStopForRestartProcess();
});
} else {
- startFreezingScreen();
scheduleStopForRestartProcess();
}
}
@@ -10141,7 +9930,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
proto.write(OVERRIDE_ORIENTATION, getOverrideOrientation());
proto.write(SHOULD_SEND_COMPAT_FAKE_FOCUS, shouldSendCompatFakeFocus());
final AppCompatCameraOverrides cameraOverrides =
- mAppCompatController.getAppCompatCameraOverrides();
+ mAppCompatController.getCameraOverrides();
proto.write(SHOULD_FORCE_ROTATE_FOR_CAMERA_COMPAT,
cameraOverrides.shouldForceRotateForCameraCompat());
proto.write(SHOULD_REFRESH_ACTIVITY_FOR_CAMERA_COMPAT,
diff --git a/services/core/java/com/android/server/wm/ActivityRefresher.java b/services/core/java/com/android/server/wm/ActivityRefresher.java
index 597f75a52f1c..25e38b307b5e 100644
--- a/services/core/java/com/android/server/wm/ActivityRefresher.java
+++ b/services/core/java/com/android/server/wm/ActivityRefresher.java
@@ -77,10 +77,10 @@ class ActivityRefresher {
final boolean cycleThroughStop =
mWmService.mAppCompatConfiguration
.isCameraCompatRefreshCycleThroughStopEnabled()
- && !activity.mAppCompatController.getAppCompatCameraOverrides()
+ && !activity.mAppCompatController.getCameraOverrides()
.shouldRefreshActivityViaPauseForCameraCompat();
- activity.mAppCompatController.getAppCompatCameraOverrides().setIsRefreshRequested(true);
+ activity.mAppCompatController.getCameraOverrides().setIsRefreshRequested(true);
ProtoLog.v(WM_DEBUG_STATES,
"Refreshing activity for freeform camera compatibility treatment, "
+ "activityRecord=%s", activity);
@@ -97,25 +97,25 @@ class ActivityRefresher {
}
}, REFRESH_CALLBACK_TIMEOUT_MS);
} catch (RemoteException e) {
- activity.mAppCompatController.getAppCompatCameraOverrides()
+ activity.mAppCompatController.getCameraOverrides()
.setIsRefreshRequested(false);
}
}
boolean isActivityRefreshing(@NonNull ActivityRecord activity) {
- return activity.mAppCompatController.getAppCompatCameraOverrides().isRefreshRequested();
+ return activity.mAppCompatController.getCameraOverrides().isRefreshRequested();
}
void onActivityRefreshed(@NonNull ActivityRecord activity) {
// TODO(b/333060789): can we tell that refresh did not happen by observing the activity
// state?
- activity.mAppCompatController.getAppCompatCameraOverrides().setIsRefreshRequested(false);
+ activity.mAppCompatController.getCameraOverrides().setIsRefreshRequested(false);
}
private boolean shouldRefreshActivity(@NonNull ActivityRecord activity,
@NonNull Configuration newConfig, @NonNull Configuration lastReportedConfig) {
return mWmService.mAppCompatConfiguration.isCameraCompatRefreshEnabled()
- && activity.mAppCompatController.getAppCompatCameraOverrides()
+ && activity.mAppCompatController.getCameraOverrides()
.shouldRefreshActivityForCameraCompat()
&& ArrayUtils.find(mEvaluators.toArray(), evaluator ->
((Evaluator) evaluator)
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index bdbd0d15d982..8e4d4be693f8 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -124,6 +124,7 @@ import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
import android.os.IBinder;
+import android.os.OperationCanceledException;
import android.os.RemoteException;
import android.os.Trace;
import android.os.UserHandle;
@@ -3246,11 +3247,14 @@ class ActivityStarter {
private void sendCanNotEmbedActivityError(TaskFragment taskFragment,
@EmbeddingCheckResult int result) {
final String errMsg;
- switch(result) {
+ boolean fatalError = true;
+ switch (result) {
case EMBEDDING_DISALLOWED_NEW_TASK: {
errMsg = "Cannot embed " + mStartActivity + " that launched on another task"
+ ",mLaunchMode=" + launchModeToString(mLaunchMode)
+ ",mLaunchFlag=" + Integer.toHexString(mLaunchFlags);
+ // This is a known possible scenario, which should not be a fatal error.
+ fatalError = false;
break;
}
case EMBEDDING_DISALLOWED_MIN_DIMENSION_VIOLATION: {
@@ -3270,7 +3274,8 @@ class ActivityStarter {
mService.mWindowOrganizerController.sendTaskFragmentOperationFailure(
taskFragment.getTaskFragmentOrganizer(), mRequest.errorCallbackToken,
taskFragment, OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT,
- new SecurityException(errMsg));
+ fatalError ? new SecurityException(errMsg)
+ : new OperationCanceledException(errMsg));
} else {
// If the taskFragment is not organized, just dump error message as warning logs.
Slog.w(TAG, errMsg);
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 7321f28e4fcd..d4f9c0901162 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -509,7 +509,6 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
int changes;
// If the activity was relaunched to match the new configuration.
boolean activityRelaunched;
- boolean mIsUpdating;
}
/** Current sequencing integer of the configuration, for skipping old configurations. */
@@ -4694,14 +4693,12 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
if (values != null) {
changes = updateGlobalConfigurationLocked(values, initLocale, persistent, userId);
mTmpUpdateConfigurationResult.changes = changes;
- mTmpUpdateConfigurationResult.mIsUpdating = true;
}
if (!deferResume) {
kept = ensureConfigAndVisibilityAfterUpdate(starting, changes);
}
} finally {
- mTmpUpdateConfigurationResult.mIsUpdating = false;
continueWindowLayout();
}
mTmpUpdateConfigurationResult.activityRelaunched = !kept;
diff --git a/services/core/java/com/android/server/wm/AppCompatAspectRatioOverrides.java b/services/core/java/com/android/server/wm/AppCompatAspectRatioOverrides.java
index 086b11c25a8d..fa04955f975b 100644
--- a/services/core/java/com/android/server/wm/AppCompatAspectRatioOverrides.java
+++ b/services/core/java/com/android/server/wm/AppCompatAspectRatioOverrides.java
@@ -272,7 +272,7 @@ class AppCompatAspectRatioOverrides {
final boolean isLandscape = isFixedOrientationLandscape(
mActivityRecord.getOverrideOrientation());
final AppCompatCameraOverrides cameraOverrides =
- mActivityRecord.mAppCompatController.getAppCompatCameraOverrides();
+ mActivityRecord.mAppCompatController.getCameraOverrides();
// Don't resize to split screen size when in book mode if letterbox position is centered
return (isBookMode && isNotCenteredHorizontally || isTabletopMode && isLandscape)
|| cameraOverrides.isCameraCompatSplitScreenAspectRatioAllowed()
diff --git a/services/core/java/com/android/server/wm/AppCompatAspectRatioPolicy.java b/services/core/java/com/android/server/wm/AppCompatAspectRatioPolicy.java
index 4ecd0bec9880..ab1778a1a32e 100644
--- a/services/core/java/com/android/server/wm/AppCompatAspectRatioPolicy.java
+++ b/services/core/java/com/android/server/wm/AppCompatAspectRatioPolicy.java
@@ -56,6 +56,8 @@ class AppCompatAspectRatioPolicy {
@NonNull
private final AppCompatAspectRatioState mAppCompatAspectRatioState;
+ private final Rect mTmpBounds = new Rect();
+
AppCompatAspectRatioPolicy(@NonNull ActivityRecord activityRecord,
@NonNull TransparentPolicy transparentPolicy,
@NonNull AppCompatOverrides appCompatOverrides) {
@@ -222,6 +224,45 @@ class AppCompatAspectRatioPolicy {
return getMaxAspectRatio() != 0 || getMinAspectRatio() != 0;
}
+ /**
+ * Resolves aspect ratio restrictions for an activity. If the bounds are restricted by
+ * aspect ratio, the position will be adjusted later in {@link #updateResolvedBoundsPosition}
+ * within parent's app bounds to balance the visual appearance. The policy of aspect ratio has
+ * higher priority than the requested override bounds.
+ */
+ void resolveAspectRatioRestrictionIfNeeded(@NonNull Configuration newParentConfiguration) {
+ // If activity in fullscreen mode is letterboxed because of fixed orientation then bounds
+ // are already calculated in resolveFixedOrientationConfiguration.
+ // Don't apply aspect ratio if app is overridden to fullscreen by device user/manufacturer.
+ if (isLetterboxedForFixedOrientationAndAspectRatio()
+ || getOverrides().hasFullscreenOverride()) {
+ return;
+ }
+ final Configuration resolvedConfig = mActivityRecord.getResolvedOverrideConfiguration();
+ final Rect parentAppBounds =
+ mActivityRecord.mResolveConfigHint.mParentAppBoundsOverride;
+ final Rect parentBounds = newParentConfiguration.windowConfiguration.getBounds();
+ final Rect resolvedBounds = resolvedConfig.windowConfiguration.getBounds();
+ // Use tmp bounds to calculate aspect ratio so we can know whether the activity should
+ // use restricted size (resolved bounds may be the requested override bounds).
+ mTmpBounds.setEmpty();
+ applyAspectRatioForLetterbox(mTmpBounds, parentAppBounds, parentBounds);
+ // If the out bounds is not empty, it means the activity cannot fill parent's app
+ // bounds, then they should be aligned later in #updateResolvedBoundsPosition().
+ if (!mTmpBounds.isEmpty()) {
+ resolvedBounds.set(mTmpBounds);
+ }
+ if (!resolvedBounds.isEmpty() && !resolvedBounds.equals(parentBounds)) {
+ // Compute the configuration based on the resolved bounds. If aspect ratio doesn't
+ // restrict, the bounds should be the requested override bounds.
+ // TODO(b/384473893): Improve ActivityRecord usage here.
+ mActivityRecord.mResolveConfigHint.mTmpOverrideDisplayInfo =
+ mActivityRecord.getFixedRotationTransformDisplayInfo();
+ mActivityRecord.computeConfigByResolveHint(resolvedConfig, newParentConfiguration);
+ setLetterboxBoundsForAspectRatio(new Rect(resolvedBounds));
+ }
+ }
+
private boolean isParentFullscreenPortrait() {
final WindowContainer<?> parent = mActivityRecord.getParent();
return parent != null
@@ -364,6 +405,11 @@ class AppCompatAspectRatioPolicy {
&& !dc.getIgnoreOrientationRequest();
}
+ @NonNull
+ private AppCompatAspectRatioOverrides getOverrides() {
+ return mActivityRecord.mAppCompatController.getAspectRatioOverrides();
+ }
+
private static class AppCompatAspectRatioState {
// Whether the aspect ratio restrictions applied to the activity bounds
// in applyAspectRatio().
diff --git a/services/core/java/com/android/server/wm/AppCompatCameraPolicy.java b/services/core/java/com/android/server/wm/AppCompatCameraPolicy.java
index 6074608422d1..276c7d2cbaa0 100644
--- a/services/core/java/com/android/server/wm/AppCompatCameraPolicy.java
+++ b/services/core/java/com/android/server/wm/AppCompatCameraPolicy.java
@@ -277,7 +277,7 @@ class AppCompatCameraPolicy {
*/
static boolean shouldOverrideMinAspectRatioForCamera(@NonNull ActivityRecord activityRecord) {
return AppCompatCameraPolicy.isCameraRunningAndWindowingModeEligible(activityRecord)
- && activityRecord.mAppCompatController.getAppCompatCameraOverrides()
+ && activityRecord.mAppCompatController.getCameraOverrides()
.isOverrideMinAspectRatioForCameraEnabled();
}
}
diff --git a/services/core/java/com/android/server/wm/AppCompatController.java b/services/core/java/com/android/server/wm/AppCompatController.java
index cc9cd90fae06..b7d8aff66ec8 100644
--- a/services/core/java/com/android/server/wm/AppCompatController.java
+++ b/services/core/java/com/android/server/wm/AppCompatController.java
@@ -94,8 +94,8 @@ class AppCompatController {
}
@NonNull
- AppCompatCameraOverrides getAppCompatCameraOverrides() {
- return mAppCompatOverrides.getAppCompatCameraOverrides();
+ AppCompatCameraOverrides getCameraOverrides() {
+ return mAppCompatOverrides.getCameraOverrides();
}
@NonNull
diff --git a/services/core/java/com/android/server/wm/AppCompatLetterboxPolicy.java b/services/core/java/com/android/server/wm/AppCompatLetterboxPolicy.java
index 449458665b63..6a8040ab5248 100644
--- a/services/core/java/com/android/server/wm/AppCompatLetterboxPolicy.java
+++ b/services/core/java/com/android/server/wm/AppCompatLetterboxPolicy.java
@@ -16,6 +16,9 @@
package com.android.server.wm;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
+import static android.content.res.Configuration.ORIENTATION_UNDEFINED;
import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
@@ -28,6 +31,7 @@ import static com.android.server.wm.AppCompatLetterboxUtils.calculateLetterboxPo
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.content.res.Configuration.Orientation;
import android.graphics.Point;
import android.graphics.Rect;
import android.view.SurfaceControl;
@@ -55,6 +59,11 @@ class AppCompatLetterboxPolicy {
private boolean mLastShouldShowLetterboxUi;
+ // Whether the activity is eligible to be letterboxed for fixed orientation with respect to its
+ // requested orientation, even when it's letterbox for another reason (e.g., size compat mode)
+ // and therefore #isLetterboxedForFixedOrientationAndAspectRatio returns false.
+ private boolean mIsEligibleForFixedOrientationLetterbox;
+
AppCompatLetterboxPolicy(@NonNull ActivityRecord activityRecord,
@NonNull AppCompatConfiguration appCompatConfiguration) {
mActivityRecord = activityRecord;
@@ -66,6 +75,10 @@ class AppCompatLetterboxPolicy {
mAppCompatConfiguration = appCompatConfiguration;
}
+ void resetFixedOrientationLetterboxEligibility() {
+ mIsEligibleForFixedOrientationLetterbox = false;
+ }
+
/** Cleans up {@link Letterbox} if it exists.*/
void stop() {
mLetterboxPolicyState.stop();
@@ -91,6 +104,43 @@ class AppCompatLetterboxPolicy {
mLetterboxPolicyState.getLetterboxInnerBounds(outBounds);
}
+ /**
+ * Checks if the current activity is eligible to be letterboxed because of a fixed orientation.
+ *
+ * @param forcedOrientation The requeste orientation
+ * @param parentOrientation The orientation of the parent container.
+ * @return {@code true} if the activity can be letterboxed because of the requested fixed
+ * orientation.
+ */
+ boolean resolveFixedOrientationLetterboxEligibility(@Orientation int forcedOrientation,
+ @Orientation int parentOrientation) {
+ mIsEligibleForFixedOrientationLetterbox = forcedOrientation != ORIENTATION_UNDEFINED
+ && forcedOrientation != parentOrientation;
+ return mIsEligibleForFixedOrientationLetterbox;
+ }
+
+ /**
+ * Whether this activity is eligible for letterbox eduction.
+ *
+ * <p>Conditions that need to be met:
+ *
+ * <ul>
+ * <li>{@link AppCompatConfiguration#getIsEducationEnabled} is true.
+ * <li>The activity is eligible for fixed orientation letterbox.
+ * <li>The activity is in fullscreen.
+ * <li>The activity is portrait-only.
+ * <li>The activity doesn't have a starting window (education should only be displayed
+ * once the starting window is removed in {@link #removeStartingWindow}).
+ * </ul>
+ */
+ boolean isEligibleForLetterboxEducation() {
+ return mAppCompatConfiguration.getIsEducationEnabled()
+ && mIsEligibleForFixedOrientationLetterbox
+ && mActivityRecord.getWindowingMode() == WINDOWING_MODE_FULLSCREEN
+ && mActivityRecord.getRequestedConfigurationOrientation() == ORIENTATION_PORTRAIT
+ && mActivityRecord.mStartingWindow == null;
+ }
+
@Nullable
LetterboxDetails getLetterboxDetails() {
final WindowState w = mActivityRecord.findMainWindow();
diff --git a/services/core/java/com/android/server/wm/AppCompatOrientationPolicy.java b/services/core/java/com/android/server/wm/AppCompatOrientationPolicy.java
index 6202f8070dd4..35fa39dab900 100644
--- a/services/core/java/com/android/server/wm/AppCompatOrientationPolicy.java
+++ b/services/core/java/com/android/server/wm/AppCompatOrientationPolicy.java
@@ -100,7 +100,7 @@ class AppCompatOrientationPolicy {
}
if (displayContent != null
- && mAppCompatOverrides.getAppCompatCameraOverrides()
+ && mAppCompatOverrides.getCameraOverrides()
.isOverrideOrientationOnlyForCameraEnabled()
&& !AppCompatCameraPolicy
.isActivityEligibleForOrientationOverride(mActivityRecord)) {
diff --git a/services/core/java/com/android/server/wm/AppCompatOverrides.java b/services/core/java/com/android/server/wm/AppCompatOverrides.java
index 2d0ff9be2133..811a39c6a78e 100644
--- a/services/core/java/com/android/server/wm/AppCompatOverrides.java
+++ b/services/core/java/com/android/server/wm/AppCompatOverrides.java
@@ -29,7 +29,7 @@ public class AppCompatOverrides {
@NonNull
private final AppCompatOrientationOverrides mOrientationOverrides;
@NonNull
- private final AppCompatCameraOverrides mAppCompatCameraOverrides;
+ private final AppCompatCameraOverrides mCameraOverrides;
@NonNull
private final AppCompatAspectRatioOverrides mAspectRatioOverrides;
@NonNull
@@ -46,10 +46,10 @@ public class AppCompatOverrides {
@NonNull AppCompatConfiguration appCompatConfiguration,
@NonNull OptPropFactory optPropBuilder,
@NonNull AppCompatDeviceStateQuery appCompatDeviceStateQuery) {
- mAppCompatCameraOverrides = new AppCompatCameraOverrides(activityRecord,
+ mCameraOverrides = new AppCompatCameraOverrides(activityRecord,
appCompatConfiguration, optPropBuilder);
mOrientationOverrides = new AppCompatOrientationOverrides(activityRecord,
- appCompatConfiguration, optPropBuilder, mAppCompatCameraOverrides);
+ appCompatConfiguration, optPropBuilder, mCameraOverrides);
mReachabilityOverrides = new AppCompatReachabilityOverrides(activityRecord,
appCompatConfiguration, appCompatDeviceStateQuery);
mAspectRatioOverrides = new AppCompatAspectRatioOverrides(activityRecord,
@@ -69,8 +69,8 @@ public class AppCompatOverrides {
}
@NonNull
- AppCompatCameraOverrides getAppCompatCameraOverrides() {
- return mAppCompatCameraOverrides;
+ AppCompatCameraOverrides getCameraOverrides() {
+ return mCameraOverrides;
}
@NonNull
diff --git a/services/core/java/com/android/server/wm/AppCompatUtils.java b/services/core/java/com/android/server/wm/AppCompatUtils.java
index 1ab0868b37d1..67f5b9b03bb7 100644
--- a/services/core/java/com/android/server/wm/AppCompatUtils.java
+++ b/services/core/java/com/android/server/wm/AppCompatUtils.java
@@ -150,10 +150,12 @@ final class AppCompatUtils {
appCompatTaskInfo.setTopActivityInSizeCompat(top.fillsParent());
}
// Whether the direct top activity is eligible for letterbox education.
- appCompatTaskInfo.setEligibleForLetterboxEducation(
- isTopActivityResumed && top.isEligibleForLetterboxEducation());
- appCompatTaskInfo.setLetterboxEducationEnabled(top.mAppCompatController
- .getAppCompatLetterboxOverrides().isLetterboxEducationEnabled());
+ appCompatTaskInfo.setEligibleForLetterboxEducation(isTopActivityResumed
+ && top.mAppCompatController.getAppCompatLetterboxPolicy()
+ .isEligibleForLetterboxEducation());
+ appCompatTaskInfo.setLetterboxEducationEnabled(
+ top.mAppCompatController.getAppCompatLetterboxOverrides()
+ .isLetterboxEducationEnabled());
final AppCompatAspectRatioOverrides aspectRatioOverrides =
top.mAppCompatController.getAspectRatioOverrides();
diff --git a/services/core/java/com/android/server/wm/AppTransitionController.java b/services/core/java/com/android/server/wm/AppTransitionController.java
index a972ecbcac9e..0a2f6852f6e6 100644
--- a/services/core/java/com/android/server/wm/AppTransitionController.java
+++ b/services/core/java/com/android/server/wm/AppTransitionController.java
@@ -1195,27 +1195,12 @@ public class AppTransitionController {
private boolean transitionGoodToGo(ArraySet<? extends WindowContainer> apps,
ArrayMap<WindowContainer, Integer> outReasons) {
ProtoLog.v(WM_DEBUG_APP_TRANSITIONS,
- "Checking %d opening apps (frozen=%b timeout=%b)...", apps.size(),
- mService.mDisplayFrozen, mDisplayContent.mAppTransition.isTimeout());
+ "Checking %d opening apps (timeout=%b)...", apps.size(),
+ mDisplayContent.mAppTransition.isTimeout());
if (mDisplayContent.mAppTransition.isTimeout()) {
return true;
}
- final ScreenRotationAnimation screenRotationAnimation = mService.mRoot.getDisplayContent(
- Display.DEFAULT_DISPLAY).getRotationAnimation();
-
- // Imagine the case where we are changing orientation due to an app transition, but a
- // previous orientation change is still in progress. We won't process the orientation
- // change for our transition because we need to wait for the rotation animation to
- // finish.
- // If we start the app transition at this point, we will interrupt it halfway with a
- // new rotation animation after the old one finally finishes. It's better to defer the
- // app transition.
- if (screenRotationAnimation != null && screenRotationAnimation.isAnimating()
- && mDisplayContent.getDisplayRotation().needsUpdate()) {
- ProtoLog.v(WM_DEBUG_APP_TRANSITIONS,
- "Delaying app transition for screen rotation animation to finish");
- return false;
- }
+
for (int i = 0; i < apps.size(); i++) {
WindowContainer wc = apps.valueAt(i);
final ActivityRecord activity = getAppFromContainer(wc);
diff --git a/services/core/java/com/android/server/wm/AppWarnings.java b/services/core/java/com/android/server/wm/AppWarnings.java
index 601b17c46c03..576e5d5d0cd2 100644
--- a/services/core/java/com/android/server/wm/AppWarnings.java
+++ b/services/core/java/com/android/server/wm/AppWarnings.java
@@ -51,6 +51,7 @@ import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
import android.util.Xml;
+import android.view.ContextThemeWrapper;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.ArrayUtils;
@@ -498,10 +499,21 @@ class AppWarnings {
}
}
if (!hasPackageFlag(userId, ar.packageName, FLAG_HIDE_PAGE_SIZE_MISMATCH)) {
+ Context context = getUiContextForActivity(ar);
+ // PageSizeMismatchDialog has link in message which should open in browser.
+ // Starting activity from non-activity context is not allowed and flag
+ // FLAG_ACTIVITY_NEW_TASK is needed to start activity.
+ context = new ContextThemeWrapper(context, context.getThemeResId()) {
+ @Override
+ public void startActivity(Intent intent) {
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ super.startActivity(intent);
+ }
+ };
pageSizeMismatchDialog =
new PageSizeMismatchDialog(
AppWarnings.this,
- getUiContextForActivity(ar),
+ context,
ar.info.applicationInfo,
userId,
warning);
diff --git a/services/core/java/com/android/server/wm/AsyncRotationController.java b/services/core/java/com/android/server/wm/AsyncRotationController.java
index 6b6f0111305c..d3fd0e3199a3 100644
--- a/services/core/java/com/android/server/wm/AsyncRotationController.java
+++ b/services/core/java/com/android/server/wm/AsyncRotationController.java
@@ -135,8 +135,7 @@ class AsyncRotationController extends FadeAnimationController implements Consume
// decides not to perform seamless rotation, it only affects whether to use fade animation
// when the windows are drawn. If the windows are not too slow (after rotation animation is
// done) to be drawn, the visual result can still look smooth.
- mHasScreenRotationAnimation =
- displayContent.getRotationAnimation() != null || mTransitionOp == OP_CHANGE;
+ mHasScreenRotationAnimation = mTransitionOp == OP_CHANGE;
if (mHasScreenRotationAnimation) {
// Hide the windows immediately because screen should have been covered by screenshot.
mHideImmediately = true;
diff --git a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
index b9febb83b780..119709e86551 100644
--- a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
+++ b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
@@ -1036,8 +1036,10 @@ public class BackgroundActivityStartController {
// Normal apps with visible app window will be allowed to start activity if app switching
// is allowed, or apps like live wallpaper with non app visible window will be allowed.
+ // The home app can start apps even if app switches are usually disallowed.
final boolean appSwitchAllowedOrFg = state.mAppSwitchState == APP_SWITCH_ALLOW
- || state.mAppSwitchState == APP_SWITCH_FG_ONLY;
+ || state.mAppSwitchState == APP_SWITCH_FG_ONLY
+ || isHomeApp(state.mCallingUid, state.mCallingPackage);
if (appSwitchAllowedOrFg && state.mCallingUidHasVisibleActivity) {
return new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW,
/*background*/ false, "callingUid has visible window");
diff --git a/services/core/java/com/android/server/wm/CameraCompatFreeformPolicy.java b/services/core/java/com/android/server/wm/CameraCompatFreeformPolicy.java
index f5bc9f0f9a47..230cd336d7c4 100644
--- a/services/core/java/com/android/server/wm/CameraCompatFreeformPolicy.java
+++ b/services/core/java/com/android/server/wm/CameraCompatFreeformPolicy.java
@@ -221,7 +221,7 @@ final class CameraCompatFreeformPolicy implements CameraStateMonitor.CameraCompa
}
boolean isCameraRunningAndWindowingModeEligible(@NonNull ActivityRecord activity) {
- return activity.mAppCompatController.getAppCompatCameraOverrides()
+ return activity.mAppCompatController.getCameraOverrides()
.shouldApplyFreeformTreatmentForCameraCompat()
&& activity.inFreeformWindowingMode()
&& mCameraStateMonitor.isCameraRunningForActivity(activity);
@@ -232,7 +232,7 @@ final class CameraCompatFreeformPolicy implements CameraStateMonitor.CameraCompa
// different camera compat aspect ratio set: this allows per-app camera compat override
// aspect ratio to be smaller than the default.
return isInFreeformCameraCompatMode(activity) && !activity.mAppCompatController
- .getAppCompatCameraOverrides().isOverrideMinAspectRatioForCameraEnabled();
+ .getCameraOverrides().isOverrideMinAspectRatioForCameraEnabled();
}
boolean isInFreeformCameraCompatMode(@NonNull ActivityRecord activity) {
@@ -307,7 +307,7 @@ final class CameraCompatFreeformPolicy implements CameraStateMonitor.CameraCompa
boolean isTreatmentEnabledForActivity(@NonNull ActivityRecord activity,
boolean checkOrientation) {
int orientation = activity.getRequestedConfigurationOrientation();
- return activity.mAppCompatController.getAppCompatCameraOverrides()
+ return activity.mAppCompatController.getCameraOverrides()
.shouldApplyFreeformTreatmentForCameraCompat()
&& mCameraStateMonitor.isCameraRunningForActivity(activity)
&& (!checkOrientation || orientation != ORIENTATION_UNDEFINED)
@@ -333,6 +333,6 @@ final class CameraCompatFreeformPolicy implements CameraStateMonitor.CameraCompa
|| !mCameraStateMonitor.isCameraWithIdRunningForActivity(topActivity, cameraId)) {
return false;
}
- return topActivity.mAppCompatController.getAppCompatCameraOverrides().isRefreshRequested();
+ return topActivity.mAppCompatController.getCameraOverrides().isRefreshRequested();
}
}
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 5435d8f164da..60c80a31f637 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -128,7 +128,6 @@ import static com.android.server.wm.DisplayContentProto.MIN_SIZE_OF_RESIZEABLE_T
import static com.android.server.wm.DisplayContentProto.OPENING_APPS;
import static com.android.server.wm.DisplayContentProto.RESUMED_ACTIVITY;
import static com.android.server.wm.DisplayContentProto.ROOT_DISPLAY_AREA;
-import static com.android.server.wm.DisplayContentProto.SCREEN_ROTATION_ANIMATION;
import static com.android.server.wm.DisplayContentProto.SLEEP_TOKENS;
import static com.android.server.wm.EventLogTags.IMF_REMOVE_IME_SCREENSHOT;
import static com.android.server.wm.EventLogTags.IMF_SHOW_IME_SCREENSHOT;
@@ -150,7 +149,6 @@ import static com.android.server.wm.WindowManagerService.H.WINDOW_HIDE_TIMEOUT;
import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_PLACING_SURFACES;
import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_WILL_ASSIGN_LAYERS;
import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_WILL_PLACE_SURFACES;
-import static com.android.server.wm.WindowManagerService.WINDOWS_FREEZING_SCREENS_TIMEOUT;
import static com.android.server.wm.WindowManagerService.dipToPixel;
import static com.android.server.wm.WindowState.EXCLUSION_LEFT;
import static com.android.server.wm.WindowState.EXCLUSION_RIGHT;
@@ -630,8 +628,6 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
/** Windows removed since {@link #mCurrentFocus} was set to null. Used for ANR blaming. */
final ArrayList<WindowState> mWinRemovedSinceNullFocus = new ArrayList<>();
- private ScreenRotationAnimation mScreenRotationAnimation;
-
/**
* Sequence number for the current layout pass.
*/
@@ -1594,8 +1590,6 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
mTransitionController.setDisplaySyncMethod(startBounds, endBounds, this);
collectDisplayChange(transition);
}
- } else if (mLastHasContent) {
- mWmService.startFreezingDisplay(0 /* exitAnim */, 0 /* enterAnim */, this);
}
sendNewConfiguration();
}
@@ -1638,7 +1632,6 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
// DisplayContent#updateRotationUnchecked.
if (mWaitingForConfig) {
mWaitingForConfig = false;
- mWmService.mLastFinishedFreezeSource = "config-unchanged";
setLayoutNeeded();
mWmService.mWindowPlacerLocked.performSurfacePlacement();
}
@@ -1647,8 +1640,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
@Override
boolean onDescendantOrientationChanged(@Nullable WindowContainer requestingContainer) {
- final Configuration config = updateOrientation(
- requestingContainer, false /* forceUpdate */);
+ final Configuration config = updateOrientationAndComputeConfig(false /* forceUpdate */);
// If display rotation class tells us that it doesn't consider app requested orientation,
// this display won't rotate just because of an app changes its requested orientation. Thus
// it indicates that this display chooses not to handle this request.
@@ -1696,28 +1688,17 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
/**
* Update orientation of the display, returning a non-null new Configuration if it has
* changed from the current orientation. If a non-null configuration is returned, someone must
- * call {@link WindowManagerService#setNewDisplayOverrideConfiguration(Configuration,
- * DisplayContent)} to tell the window manager it can unfreeze the screen. This will typically
- * be done by calling {@link #sendNewConfiguration}.
+ * request a change transition or call {@link #sendNewConfiguration}.
*
- * @param freezeDisplayWindow Freeze the app window if the orientation is changed.
* @param forceUpdate See {@link DisplayRotation#updateRotationUnchecked(boolean)}
*/
- Configuration updateOrientation(WindowContainer<?> freezeDisplayWindow, boolean forceUpdate) {
+ Configuration updateOrientationAndComputeConfig(boolean forceUpdate) {
if (!mDisplayReady) {
return null;
}
Configuration config = null;
if (updateOrientation(forceUpdate)) {
- // If we changed the orientation but mOrientationChangeComplete is already true,
- // we used seamless rotation, and we don't need to freeze the screen.
- if (freezeDisplayWindow != null && !mWmService.mRoot.mOrientationChangeComplete) {
- final ActivityRecord activity = freezeDisplayWindow.asActivityRecord();
- if (activity != null && activity.mayFreezeScreenLocked()) {
- activity.startFreezingScreen();
- }
- }
config = new Configuration();
computeScreenConfiguration(config);
}
@@ -2254,8 +2235,6 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
mDisplayRotation.isRotatingSeamlessly() && !shellTransitions;
final Transaction transaction =
shellTransitions ? getSyncTransaction() : getPendingTransaction();
- ScreenRotationAnimation screenRotationAnimation = rotateSeamlessly
- ? null : getRotationAnimation();
// We need to update our screen size information to match the new rotation. If the rotation
// has actually changed then this method will return true and, according to the comment at
// the top of the method, the caller is obligated to call computeNewConfigurationLocked().
@@ -2263,12 +2242,6 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
// #computeScreenConfiguration() later.
updateDisplayAndOrientation(null /* outConfig */);
- // NOTE: We disable the rotation in the emulator because
- // it doesn't support hardware OpenGL emulation yet.
- if (screenRotationAnimation != null && screenRotationAnimation.hasScreenshot()) {
- screenRotationAnimation.setRotation(transaction, rotation);
- }
-
if (!shellTransitions) {
forAllWindows(w -> {
w.seamlesslyRotateIfAllowed(transaction, oldRotation, rotation, rotateSeamlessly);
@@ -2932,20 +2905,6 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
@ScreenOrientation
@Override
int getOrientation() {
- if (mWmService.mDisplayFrozen) {
- if (mWmService.mPolicy.isKeyguardLocked()) {
- // Use the last orientation the while the display is frozen with the keyguard
- // locked. This could be the keyguard forced orientation or from a SHOW_WHEN_LOCKED
- // window. We don't want to check the show when locked window directly though as
- // things aren't stable while the display is frozen, for example the window could be
- // momentarily unavailable due to activity relaunch.
- ProtoLog.v(WM_DEBUG_ORIENTATION,
- "Display id=%d is frozen while keyguard locked, return %d",
- mDisplayId, getLastOrientation());
- return getLastOrientation();
- }
- }
-
final int compatOrientation = mAppCompatCameraPolicy.getOrientation();
if (compatOrientation != SCREEN_ORIENTATION_UNSPECIFIED) {
mLastOrientationSource = null;
@@ -3413,12 +3372,10 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
mAppTransition.removeAppTransitionTimeoutCallbacks();
mTransitionController.unregisterLegacyListener(mFixedRotationTransitionListener);
handleAnimatingStoppedAndTransition();
- mWmService.stopFreezingDisplayLocked();
mDeviceStateController.unregisterDeviceStateCallback(mDeviceStateConsumer);
super.removeImmediately();
if (DEBUG_DISPLAY) Slog.v(TAG_WM, "Removing display=" + this);
mPointerEventDispatcher.dispose();
- setRotationAnimation(null);
// Unlink death from remote to clear the reference from binder -> mRemoteInsetsDeath
// -> this DisplayContent.
setRemoteInsetsController(null);
@@ -3514,24 +3471,6 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
RotationUtils.rotateBounds(inOutBounds, mTmpRect, oldRotation, newRotation);
}
- public void setRotationAnimation(ScreenRotationAnimation screenRotationAnimation) {
- final ScreenRotationAnimation prev = mScreenRotationAnimation;
- mScreenRotationAnimation = screenRotationAnimation;
- if (prev != null) {
- prev.kill();
- }
-
- // Hide the windows which are not significant in rotation animation. So that the windows
- // don't need to block the unfreeze time.
- if (screenRotationAnimation != null && screenRotationAnimation.hasScreenshot()) {
- startAsyncRotationIfNeeded();
- }
- }
-
- public ScreenRotationAnimation getRotationAnimation() {
- return mScreenRotationAnimation;
- }
-
/**
* Collects this display into an already-collecting transition.
*/
@@ -3595,12 +3534,6 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
}
}
- /** If the display is in transition, there should be a screenshot covering it. */
- @Override
- boolean inTransition() {
- return mScreenRotationAnimation != null || super.inTransition();
- }
-
@Override
public void dumpDebug(ProtoOutputStream proto, long fieldId,
@WindowTracingLogLevel int logLevel) {
@@ -3616,10 +3549,6 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
proto.write(DPI, mBaseDisplayDensity);
mDisplayInfo.dumpDebug(proto, DISPLAY_INFO);
mDisplayRotation.dumpDebug(proto, DISPLAY_ROTATION);
- final ScreenRotationAnimation screenRotationAnimation = getRotationAnimation();
- if (screenRotationAnimation != null) {
- screenRotationAnimation.dumpDebug(proto, SCREEN_ROTATION_ANIMATION);
- }
mDisplayFrames.dumpDebug(proto, DISPLAY_FRAMES);
proto.write(MIN_SIZE_OF_RESIZEABLE_TASK_DP, mMinSizeOfResizeableTaskDp);
if (mTransitionController.isShellTransitionsEnabled()) {
@@ -3766,16 +3695,6 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
pw.println();
- final ScreenRotationAnimation rotationAnimation = getRotationAnimation();
- if (rotationAnimation != null) {
- pw.println(" mScreenRotationAnimation:");
- rotationAnimation.printTo(subPrefix, pw);
- } else if (dumpAll) {
- pw.println(" no ScreenRotationAnimation ");
- }
-
- pw.println();
-
// Dump root task references
final Task rootHomeTask = getDefaultTaskDisplayArea().getRootHomeTask();
if (rootHomeTask != null) {
@@ -5068,13 +4987,6 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
return win != null;
}
- void onWindowFreezeTimeout() {
- Slog.w(TAG_WM, "Window freeze timeout expired.");
- mWmService.mWindowsFreezingScreen = WINDOWS_FREEZING_SCREENS_TIMEOUT;
-
- mWmService.mWindowPlacerLocked.performSurfacePlacement();
- }
-
/**
* Callbacks when the given type of {@link WindowContainer} animation finished running in the
* hierarchy.
@@ -5297,8 +5209,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
boolean okToDisplay(boolean ignoreFrozen, boolean ignoreScreenOn) {
if (mDisplayId == DEFAULT_DISPLAY) {
- return (!mWmService.mDisplayFrozen || ignoreFrozen)
- && mWmService.mDisplayEnabled
+ return mWmService.mDisplayEnabled
&& (ignoreScreenOn || mWmService.mPolicy.isScreenOn());
}
return mDisplayInfo.state == Display.STATE_ON;
@@ -5443,10 +5354,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
// We skip IME windows so they're processed just above their target.
// Note that this method check should align with {@link
// WindowState#applyImeWindowsIfNeeded} in case of any state mismatch.
- return dc.mImeLayeringTarget != null
- // Make sure that the IME window won't be skipped to report that it has
- // completed the orientation change.
- && !dc.mWmService.mDisplayFrozen;
+ return dc.mImeLayeringTarget != null;
}
/** Like {@link #forAllWindows}, but ignores {@link #skipImeWindowsDuringTraversal} */
@@ -6427,14 +6335,12 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
changes = performDisplayOverrideConfigUpdate(values);
}
mAtmService.mTmpUpdateConfigurationResult.changes = changes;
- mAtmService.mTmpUpdateConfigurationResult.mIsUpdating = true;
}
if (!deferResume) {
kept = mAtmService.ensureConfigAndVisibilityAfterUpdate(starting, changes);
}
} finally {
- mAtmService.mTmpUpdateConfigurationResult.mIsUpdating = false;
mAtmService.continueWindowLayout();
}
@@ -6490,7 +6396,6 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
mCurrentOverrideConfigurationChanges = 0;
if (mWaitingForConfig) {
mWaitingForConfig = false;
- mWmService.mLastFinishedFreezeSource = "new-config";
}
mAtmService.addWindowLayoutReasons(
ActivityTaskManagerService.LAYOUT_REASON_CONFIG_CHANGED);
@@ -7086,13 +6991,6 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
}
@Override
- public void onAppTransitionCancelledLocked(boolean keyguardGoingAwayCancelled) {
- // It is only needed when freezing display in legacy transition.
- if (mTransitionController.isShellTransitionsEnabled()) return;
- continueUpdateOrientationForDiffOrienLaunchingApp();
- }
-
- @Override
public void onAppTransitionTimeoutLocked() {
continueUpdateOrientationForDiffOrienLaunchingApp();
}
diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java
index f53bc700de05..f8c17550caba 100644
--- a/services/core/java/com/android/server/wm/DisplayRotation.java
+++ b/services/core/java/com/android/server/wm/DisplayRotation.java
@@ -22,12 +22,8 @@ import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
import static android.view.Display.TYPE_EXTERNAL;
import static android.view.Display.TYPE_OVERLAY;
import static android.view.Display.TYPE_VIRTUAL;
-import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_CROSSFADE;
-import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_JUMPCUT;
-import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_ROTATE;
import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS;
-import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_ANIM;
import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_ORIENTATION;
import static com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs.LID_OPEN;
import static com.android.server.wm.DisplayRotationProto.FIXED_TO_USER_ROTATION_MODE;
@@ -41,10 +37,7 @@ import static com.android.server.wm.DisplayRotationReversionController.REVERSION
import static com.android.server.wm.DisplayRotationReversionController.REVERSION_TYPE_NOSENSOR;
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.WINDOWS_FREEZING_SCREENS_ACTIVE;
-import static com.android.server.wm.WindowManagerService.WINDOW_FREEZE_TIMEOUT_DURATION;
-import android.annotation.AnimRes;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -104,13 +97,6 @@ public class DisplayRotation {
private static final int ROTATION_UNDEFINED = -1;
- private static class RotationAnimationPair {
- @AnimRes
- int mEnter;
- @AnimRes
- int mExit;
- }
-
@Nullable
final FoldController mFoldController;
@@ -130,7 +116,6 @@ public class DisplayRotation {
private final int mCarDockRotation;
private final int mDeskDockRotation;
private final int mUndockedHdmiRotation;
- private final RotationAnimationPair mTmpRotationAnim = new RotationAnimationPair();
private final RotationHistory mRotationHistory = new RotationHistory();
private final RotationLockHistory mRotationLockHistory = new RotationLockHistory();
@@ -540,14 +525,6 @@ public class DisplayRotation {
ProtoLog.v(WM_DEBUG_ORIENTATION, "Deferring rotation, animation in progress.");
return false;
}
- if (mService.mDisplayFrozen) {
- // Even if the screen rotation animation has finished (e.g. isAnimating returns
- // false), there is still some time where we haven't yet unfrozen the display. We
- // also need to abort rotation here.
- ProtoLog.v(WM_DEBUG_ORIENTATION,
- "Deferring rotation, still finishing previous rotation");
- return false;
- }
if (mDisplayContent.mFixedRotationTransitionListener.shouldDeferRotation()) {
// Makes sure that after the transition is finished, updateOrientation() can see
@@ -644,17 +621,13 @@ public class DisplayRotation {
return true;
}
- mService.mWindowsFreezingScreen = WINDOWS_FREEZING_SCREENS_ACTIVE;
- mService.mH.sendNewMessageDelayed(WindowManagerService.H.WINDOW_FREEZE_TIMEOUT,
- mDisplayContent, WINDOW_FREEZE_TIMEOUT_DURATION);
-
if (shouldRotateSeamlessly(oldRotation, rotation, forceUpdate)) {
// The screen rotation animation uses a screenshot to freeze the screen while windows
// resize underneath. When we are rotating seamlessly, we allow the elements to
// transition to their rotated state independently and without a freeze required.
prepareSeamlessRotation();
} else {
- prepareNormalRotationAnimation();
+ cancelSeamlessRotation();
}
// Give a remote handler (system ui) some time to reposition things.
@@ -695,12 +668,6 @@ public class DisplayRotation {
}
}
- void prepareNormalRotationAnimation() {
- cancelSeamlessRotation();
- final RotationAnimationPair anim = selectRotationAnimation();
- mService.startFreezingDisplay(anim.mExit, anim.mEnter, mDisplayContent);
- }
-
/**
* This ensures that normal rotation animation is used. E.g. {@link #mRotatingSeamlessly} was
* set by previous {@link #updateRotationUnchecked}, but another orientation change happens
@@ -820,79 +787,6 @@ public class DisplayRotation {
}
}
- /**
- * Returns the animation to run for a rotation transition based on the top fullscreen windows
- * {@link android.view.WindowManager.LayoutParams#rotationAnimation} and whether it is currently
- * fullscreen and frontmost.
- */
- private RotationAnimationPair selectRotationAnimation() {
- // If the screen is off or non-interactive, force a jumpcut.
- final boolean forceJumpcut = !mDisplayPolicy.isScreenOnFully()
- || !mService.mPolicy.okToAnimate(false /* ignoreScreenOn */);
- final WindowState topFullscreen = mDisplayPolicy.getTopFullscreenOpaqueWindow();
- ProtoLog.i(WM_DEBUG_ANIM, "selectRotationAnimation topFullscreen=%s"
- + " rotationAnimation=%d forceJumpcut=%b",
- topFullscreen,
- topFullscreen == null ? 0 : topFullscreen.getAttrs().rotationAnimation,
- forceJumpcut);
- if (forceJumpcut) {
- mTmpRotationAnim.mExit = R.anim.rotation_animation_jump_exit;
- mTmpRotationAnim.mEnter = R.anim.rotation_animation_enter;
- return mTmpRotationAnim;
- }
- if (topFullscreen != null) {
- int animationHint = topFullscreen.getRotationAnimationHint();
- if (animationHint < 0 && mDisplayPolicy.isTopLayoutFullscreen()) {
- animationHint = topFullscreen.getAttrs().rotationAnimation;
- }
- switch (animationHint) {
- case ROTATION_ANIMATION_CROSSFADE:
- case ROTATION_ANIMATION_SEAMLESS: // Crossfade is fallback for seamless.
- mTmpRotationAnim.mExit = R.anim.rotation_animation_xfade_exit;
- mTmpRotationAnim.mEnter = R.anim.rotation_animation_enter;
- break;
- case ROTATION_ANIMATION_JUMPCUT:
- mTmpRotationAnim.mExit = R.anim.rotation_animation_jump_exit;
- mTmpRotationAnim.mEnter = R.anim.rotation_animation_enter;
- break;
- case ROTATION_ANIMATION_ROTATE:
- default:
- mTmpRotationAnim.mExit = mTmpRotationAnim.mEnter = 0;
- break;
- }
- } else {
- mTmpRotationAnim.mExit = mTmpRotationAnim.mEnter = 0;
- }
- return mTmpRotationAnim;
- }
-
- /**
- * Validate whether the current top fullscreen has specified the same
- * {@link android.view.WindowManager.LayoutParams#rotationAnimation} value as that being passed
- * in from the previous top fullscreen window.
- *
- * @param exitAnimId exiting resource id from the previous window.
- * @param enterAnimId entering resource id from the previous window.
- * @param forceDefault For rotation animations only, if true ignore the animation values and
- * just return false.
- * @return {@code true} if the previous values are still valid, false if they should be replaced
- * with the default.
- */
- boolean validateRotationAnimation(int exitAnimId, int enterAnimId, boolean forceDefault) {
- switch (exitAnimId) {
- case R.anim.rotation_animation_xfade_exit:
- case R.anim.rotation_animation_jump_exit:
- // These are the only cases that matter.
- if (forceDefault) {
- return false;
- }
- final RotationAnimationPair anim = selectRotationAnimation();
- return exitAnimId == anim.mExit && enterAnimId == anim.mEnter;
- default:
- return true;
- }
- }
-
void restoreSettings(int userRotationMode, int userRotation, int fixedToUserRotation) {
mFixedToUserRotation = fixedToUserRotation;
diff --git a/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java b/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java
index 3c199dba565b..dceacc36c97e 100644
--- a/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java
@@ -253,10 +253,10 @@ final class DisplayRotationCompatPolicy implements CameraStateMonitor.CameraComp
!= lastReportedConfig.windowConfiguration.getDisplayRotation());
return isTreatmentEnabledForDisplay()
&& isTreatmentEnabledForActivity(activity)
- && activity.mAppCompatController.getAppCompatCameraOverrides()
+ && activity.mAppCompatController.getCameraOverrides()
.shouldRefreshActivityForCameraCompat()
&& (displayRotationChanged
- || activity.mAppCompatController.getAppCompatCameraOverrides()
+ || activity.mAppCompatController.getCameraOverrides()
.isCameraCompatSplitScreenAspectRatioAllowed());
}
@@ -281,7 +281,7 @@ final class DisplayRotationCompatPolicy implements CameraStateMonitor.CameraComp
boolean isActivityEligibleForOrientationOverride(@NonNull ActivityRecord activity) {
return isTreatmentEnabledForDisplay()
&& isCameraRunningAndWindowingModeEligible(activity, /* mustBeFullscreen */ true)
- && activity.mAppCompatController.getAppCompatCameraOverrides()
+ && activity.mAppCompatController.getCameraOverrides()
.shouldForceRotateForCameraCompat();
}
@@ -325,7 +325,7 @@ final class DisplayRotationCompatPolicy implements CameraStateMonitor.CameraComp
// handle dynamic changes so we shouldn't force rotate them.
&& activity.getOverrideOrientation() != SCREEN_ORIENTATION_NOSENSOR
&& activity.getOverrideOrientation() != SCREEN_ORIENTATION_LOCKED
- && activity.mAppCompatController.getAppCompatCameraOverrides()
+ && activity.mAppCompatController.getCameraOverrides()
.shouldForceRotateForCameraCompat();
}
@@ -457,14 +457,14 @@ final class DisplayRotationCompatPolicy implements CameraStateMonitor.CameraComp
private boolean shouldRecomputeConfigurationForCameraCompat(
@NonNull ActivityRecord activityRecord) {
final AppCompatCameraOverrides overrides = activityRecord.mAppCompatController
- .getAppCompatCameraOverrides();
+ .getCameraOverrides();
return overrides.isOverrideOrientationOnlyForCameraEnabled()
|| overrides.isCameraCompatSplitScreenAspectRatioAllowed()
|| shouldOverrideMinAspectRatio(activityRecord);
}
private boolean shouldOverrideMinAspectRatio(@NonNull ActivityRecord activityRecord) {
- return activityRecord.mAppCompatController.getAppCompatCameraOverrides()
+ return activityRecord.mAppCompatController.getCameraOverrides()
.isOverrideMinAspectRatioForCameraEnabled()
&& isCameraRunningAndWindowingModeEligible(activityRecord,
/* mustBeFullscreen= */ true);
diff --git a/services/core/java/com/android/server/wm/DragDropController.java b/services/core/java/com/android/server/wm/DragDropController.java
index 5b0cc9ab6909..c418349e6158 100644
--- a/services/core/java/com/android/server/wm/DragDropController.java
+++ b/services/core/java/com/android/server/wm/DragDropController.java
@@ -51,8 +51,8 @@ import android.window.IGlobalDragListener;
import android.window.IUnhandledDragCallback;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.hidden_from_bootclasspath.com.android.window.flags.Flags;
import com.android.server.wm.WindowManagerInternal.IDragDropCallback;
+import com.android.window.flags.Flags;
import java.util.Objects;
import java.util.Random;
@@ -121,13 +121,6 @@ class DragDropController {
}
@VisibleForTesting
- void cleanupListeners() {
- if (Flags.enableConnectedDisplaysDnd()) {
- mService.mDisplayManager.unregisterTopologyListener(mDisplayTopologyListener);
- }
- }
-
- @VisibleForTesting
Handler getHandler() {
return mHandler;
}
@@ -498,7 +491,8 @@ class DragDropController {
}
}
- private void handleDisplayTopologyChange(DisplayTopology unused) {
+ @VisibleForTesting
+ void handleDisplayTopologyChange(DisplayTopology unused) {
synchronized (mService.mGlobalLock) {
if (mDragState == null) {
return;
diff --git a/services/core/java/com/android/server/wm/PageSizeMismatchDialog.java b/services/core/java/com/android/server/wm/PageSizeMismatchDialog.java
index 29922f0f85c5..24235ef2d585 100644
--- a/services/core/java/com/android/server/wm/PageSizeMismatchDialog.java
+++ b/services/core/java/com/android/server/wm/PageSizeMismatchDialog.java
@@ -24,8 +24,10 @@ import android.content.pm.ApplicationInfo;
import android.content.pm.PackageItemInfo;
import android.content.pm.PackageManager;
import android.text.Html;
+import android.text.method.LinkMovementMethod;
import android.view.Window;
import android.view.WindowManager;
+import android.widget.TextView;
import com.android.internal.R;
@@ -69,6 +71,14 @@ class PageSizeMismatchDialog extends AppWarnings.BaseDialog {
mDialog.create();
final Window window = mDialog.getWindow();
- window.setType(WindowManager.LayoutParams.TYPE_PHONE);
+ window.setType(WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY);
+ }
+
+ @Override
+ public void show() {
+ super.show();
+ // Make the links in dialog clickable
+ final TextView msgTxt = (TextView) mDialog.findViewById(android.R.id.message);
+ msgTxt.setMovementMethod(LinkMovementMethod.getInstance());
}
}
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 7a3eb67bf94e..e2b5c839fb0d 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -78,11 +78,9 @@ import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYOUT_REPEAT
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WINDOW_TRACE;
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.H.WINDOW_FREEZE_TIMEOUT;
import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_NORMAL;
import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_PLACING_SURFACES;
import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_WILL_PLACE_SURFACES;
-import static com.android.server.wm.WindowManagerService.WINDOWS_FREEZING_SCREENS_NONE;
import static com.android.server.wm.WindowSurfacePlacer.SET_UPDATE_ROTATION;
import static com.android.server.wm.WindowSurfacePlacer.SET_WALLPAPER_ACTION_PENDING;
@@ -200,12 +198,6 @@ class RootWindowContainer extends WindowContainer<DisplayContent>
private boolean mSustainedPerformanceModeEnabled = false;
private boolean mSustainedPerformanceModeCurrent = false;
- // During an orientation change, we track whether all windows have rendered
- // at the new orientation, and this will be false from changing orientation until that occurs.
- // For seamless rotation cases this always stays true, as the windows complete their orientation
- // changes 1 by 1 without disturbing global state.
- boolean mOrientationChangeComplete = true;
-
private final Handler mHandler;
private String mCloseSystemDialogsReason;
@@ -841,20 +833,6 @@ class RootWindowContainer extends WindowContainer<DisplayContent>
}
}
- if (mWmService.mDisplayFrozen) {
- ProtoLog.v(WM_DEBUG_ORIENTATION,
- "With display frozen, orientationChangeComplete=%b",
- mOrientationChangeComplete);
- }
- if (mOrientationChangeComplete) {
- if (mWmService.mWindowsFreezingScreen != WINDOWS_FREEZING_SCREENS_NONE) {
- mWmService.mWindowsFreezingScreen = WINDOWS_FREEZING_SCREENS_NONE;
- mWmService.mLastFinishedFreezeSource = mLastWindowFreezeSource;
- mWmService.mH.removeMessages(WINDOW_FREEZE_TIMEOUT);
- }
- mWmService.stopFreezingDisplayLocked();
- }
-
// Destroy the surface of any windows that are no longer visible.
i = mWmService.mDestroySurface.size();
if (i > 0) {
@@ -881,13 +859,11 @@ class RootWindowContainer extends WindowContainer<DisplayContent>
}
}
- if (!mWmService.mDisplayFrozen) {
- // Post these on a handler such that we don't call into power manager service while
- // holding the window manager lock to avoid lock contention with power manager lock.
- mHandler.obtainMessage(SET_SCREEN_BRIGHTNESS_OVERRIDE, mDisplayBrightnessOverrides)
- .sendToTarget();
- mHandler.obtainMessage(SET_USER_ACTIVITY_TIMEOUT, mUserActivityTimeout).sendToTarget();
- }
+ // Post these on a handler such that we don't call into power manager service while
+ // holding the window manager lock to avoid lock contention with power manager lock.
+ mHandler.obtainMessage(SET_SCREEN_BRIGHTNESS_OVERRIDE, mDisplayBrightnessOverrides)
+ .sendToTarget();
+ mHandler.obtainMessage(SET_USER_ACTIVITY_TIMEOUT, mUserActivityTimeout).sendToTarget();
if (mSustainedPerformanceModeCurrent != mSustainedPerformanceModeEnabled) {
mSustainedPerformanceModeEnabled = mSustainedPerformanceModeCurrent;
@@ -902,8 +878,7 @@ class RootWindowContainer extends WindowContainer<DisplayContent>
}
if (!mWmService.mWaitingForDrawnCallbacks.isEmpty()
- || (mOrientationChangeComplete && !isLayoutNeeded()
- && !mUpdateRotation)) {
+ || (!isLayoutNeeded() && !mUpdateRotation)) {
mWmService.checkDrawnWindowsLocked();
}
@@ -991,7 +966,7 @@ class RootWindowContainer extends WindowContainer<DisplayContent>
private void handleResizingWindows() {
for (int i = mWmService.mResizingWindows.size() - 1; i >= 0; i--) {
WindowState win = mWmService.mResizingWindows.get(i);
- if (win.mAppFreezing || win.getDisplayContent().mWaitingForConfig) {
+ if (win.getDisplayContent().mWaitingForConfig) {
// Don't remove this window until rotation has completed and is not waiting for the
// complete configuration.
continue;
@@ -1092,12 +1067,6 @@ class RootWindowContainer extends WindowContainer<DisplayContent>
mUpdateRotation = true;
doRequest = true;
}
- if (mOrientationChangeComplete) {
- mLastWindowFreezeSource = mWmService.mAnimator.mLastWindowFreezeSource;
- if (mWmService.mWindowsFreezingScreen != WINDOWS_FREEZING_SCREENS_NONE) {
- doRequest = true;
- }
- }
return doRequest;
}
@@ -1765,7 +1734,7 @@ class RootWindowContainer extends WindowContainer<DisplayContent>
// Force-update the orientation from the WindowManager, since we need the true configuration
// to send to the client now.
final Configuration config =
- displayContent.updateOrientation(starting, true /* forceUpdate */);
+ displayContent.updateOrientationAndComputeConfig(true /* forceUpdate */);
// Visibilities may change so let the starting activity have a chance to report. Can't do it
// when visibility is changed in each AppWindowToken because it may trigger wrong
// configuration push because the visibility of some activities may not be updated yet.
diff --git a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
deleted file mode 100644
index 4bd294be22b6..000000000000
--- a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
+++ /dev/null
@@ -1,825 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.os.Trace.TRACE_TAG_WINDOW_MANAGER;
-import static android.util.RotationUtils.deltaRotation;
-import static android.view.WindowManagerPolicyConstants.SCREEN_FREEZE_LAYER_BASE;
-
-import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_ORIENTATION;
-import static com.android.internal.protolog.WmProtoLogGroups.WM_SHOW_SURFACE_ALLOC;
-import static com.android.server.wm.AnimationSpecProto.ROTATE;
-import static com.android.server.wm.RotationAnimationSpecProto.DURATION_MS;
-import static com.android.server.wm.RotationAnimationSpecProto.END_LUMA;
-import static com.android.server.wm.RotationAnimationSpecProto.START_LUMA;
-import static com.android.server.wm.ScreenRotationAnimationProto.ANIMATION_RUNNING;
-import static com.android.server.wm.ScreenRotationAnimationProto.STARTED;
-import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_SCREEN_ROTATION;
-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.utils.CoordinateTransforms.computeRotationMatrix;
-
-import android.animation.ArgbEvaluator;
-import android.content.Context;
-import android.graphics.Color;
-import android.graphics.Matrix;
-import android.graphics.Point;
-import android.graphics.Rect;
-import android.hardware.HardwareBuffer;
-import android.os.Trace;
-import android.util.Slog;
-import android.util.proto.ProtoOutputStream;
-import android.view.DisplayInfo;
-import android.view.Surface;
-import android.view.Surface.OutOfResourcesException;
-import android.view.SurfaceControl;
-import android.view.animation.Animation;
-import android.view.animation.AnimationUtils;
-import android.view.animation.Transformation;
-import android.window.ScreenCapture;
-
-import com.android.internal.R;
-import com.android.internal.policy.TransitionAnimation;
-import com.android.internal.protolog.ProtoLog;
-import com.android.server.wm.SurfaceAnimator.AnimationType;
-import com.android.server.wm.SurfaceAnimator.OnAnimationFinishedCallback;
-
-import java.io.PrintWriter;
-
-/**
- * This class handles the rotation animation when the device is rotated.
- *
- * <p>
- * The screen rotation animation is composed of 4 different part:
- * <ul>
- * <li> The screenshot: <p>
- * A screenshot of the whole screen prior the change of orientation is taken to hide the
- * element resizing below. The screenshot is then animated to rotate and cross-fade to
- * the new orientation with the content in the new orientation.
- *
- * <li> The windows on the display: <p>y
- * Once the device is rotated, the screen and its content are in the new orientation. The
- * animation first rotate the new content into the old orientation to then be able to
- * animate to the new orientation
- *
- * <li> The Background color frame: <p>
- * To have the animation seem more seamless, we add a color transitioning background behind the
- * exiting and entering layouts. We compute the brightness of the start and end
- * layouts and transition from the two brightness values as grayscale underneath the animation
- *
- * <li> The entering Blackframe: <p>
- * The enter Blackframe is similar to the exit Blackframe but is only used when a custom
- * rotation animation is used and matches the new content size instead of the screenshot.
- * </ul>
- *
- * Each part has its own Surface which are then animated by {@link SurfaceAnimator}s.
- */
-class ScreenRotationAnimation {
- private static final String TAG = TAG_WITH_CLASS_NAME ? "ScreenRotationAnimation" : TAG_WM;
-
- private final Context mContext;
- private final DisplayContent mDisplayContent;
- private final float[] mTmpFloats = new float[9];
- private final Transformation mRotateExitTransformation = new Transformation();
- private final Transformation mRotateEnterTransformation = new Transformation();
- // Complete transformations being applied.
- private final Matrix mSnapshotInitialMatrix = new Matrix();
- private final WindowManagerService mService;
- /** Only used for custom animations and not screen rotation. */
- private SurfaceControl mEnterBlackFrameLayer;
- /** This layer contains the actual screenshot that is to be faded out. */
- private SurfaceControl mScreenshotLayer;
- private SurfaceControl[] mRoundedCornerOverlay;
- /**
- * Only used for screen rotation and not custom animations. Layered behind all other layers
- * to avoid showing any "empty" spots
- */
- private SurfaceControl mBackColorSurface;
- private BlackFrame mEnteringBlackFrame;
-
- private final int mOriginalRotation;
- private final int mOriginalWidth;
- private final int mOriginalHeight;
- private int mCurRotation;
-
- // The current active animation to move from the old to the new rotated
- // state. Which animation is run here will depend on the old and new
- // rotations.
- private Animation mRotateExitAnimation;
- private Animation mRotateEnterAnimation;
- private Animation mRotateAlphaAnimation;
- private boolean mStarted;
- private boolean mAnimRunning;
- private boolean mFinishAnimReady;
- private long mFinishAnimStartTime;
- private SurfaceRotationAnimationController mSurfaceRotationAnimationController;
- /** Intensity of light/whiteness of the layout before rotation occurs. */
- private float mStartLuma;
- /** Intensity of light/whiteness of the layout after rotation occurs. */
- private float mEndLuma;
-
- ScreenRotationAnimation(DisplayContent displayContent, @Surface.Rotation int originalRotation) {
- mService = displayContent.mWmService;
- mContext = mService.mContext;
- mDisplayContent = displayContent;
- final Rect currentBounds = displayContent.getBounds();
- final int width = currentBounds.width();
- final int height = currentBounds.height();
-
- // Screenshot does NOT include rotation!
- final DisplayInfo displayInfo = displayContent.getDisplayInfo();
- final int realOriginalRotation = displayInfo.rotation;
-
- mOriginalRotation = originalRotation;
- // If the delta is not zero, the rotation of display may not change, but we still want to
- // apply rotation animation because there should be a top app shown as rotated. So the
- // specified original rotation customizes the direction of animation to have better look
- // when restoring the rotated app to the same rotation as current display.
- final int delta = deltaRotation(originalRotation, realOriginalRotation);
- final boolean flipped = delta == Surface.ROTATION_90 || delta == Surface.ROTATION_270;
- mOriginalWidth = flipped ? height : width;
- mOriginalHeight = flipped ? width : height;
- final int logicalWidth = displayInfo.logicalWidth;
- final int logicalHeight = displayInfo.logicalHeight;
- final boolean isSizeChanged =
- logicalWidth > mOriginalWidth == logicalHeight > mOriginalHeight
- && (logicalWidth != mOriginalWidth || logicalHeight != mOriginalHeight);
- mSurfaceRotationAnimationController = new SurfaceRotationAnimationController();
-
- // Check whether the current screen contains any secure content.
- boolean isSecure = displayContent.hasSecureWindowOnScreen();
- final int displayId = displayContent.getDisplayId();
- final SurfaceControl.Transaction t = mService.mTransactionFactory.get();
-
- try {
- final ScreenCapture.ScreenshotHardwareBuffer screenshotBuffer;
- if (isSizeChanged) {
- // Temporarily not skip screenshot for the rounded corner overlays and screenshot
- // the whole display to include the rounded corner overlays.
- setSkipScreenshotForRoundedCornerOverlays(false, t);
- ScreenCapture.LayerCaptureArgs captureArgs =
- new ScreenCapture.LayerCaptureArgs.Builder(
- displayContent.getSurfaceControl())
- .setCaptureSecureLayers(true)
- .setAllowProtected(true)
- .setSourceCrop(new Rect(0, 0, width, height))
- .setHintForSeamlessTransition(true)
- .build();
- screenshotBuffer = ScreenCapture.captureLayers(captureArgs);
- } else {
- ScreenCapture.LayerCaptureArgs captureArgs =
- new ScreenCapture.LayerCaptureArgs.Builder(
- displayContent.getSurfaceControl())
- .setCaptureSecureLayers(true)
- .setAllowProtected(true)
- .setSourceCrop(new Rect(0, 0, width, height))
- .setHintForSeamlessTransition(true)
- .build();
- screenshotBuffer = ScreenCapture.captureLayers(captureArgs);
- }
-
- if (screenshotBuffer == null) {
- Slog.w(TAG, "Unable to take screenshot of display " + displayId);
- return;
- }
-
- // If the screenshot contains secure layers, we have to make sure the
- // screenshot surface we display it in also has FLAG_SECURE so that
- // the user can not screenshot secure layers via the screenshot surface.
- if (screenshotBuffer.containsSecureLayers()) {
- isSecure = true;
- }
-
- mBackColorSurface = displayContent.makeChildSurface(null)
- .setName("BackColorSurface")
- .setColorLayer()
- .setCallsite("ScreenRotationAnimation")
- .build();
-
- String name = "RotationLayer";
- mScreenshotLayer = displayContent.makeOverlay()
- .setName(name)
- .setOpaque(true)
- .setSecure(isSecure)
- .setCallsite("ScreenRotationAnimation")
- .setBLASTLayer()
- .build();
- // This is the way to tell the input system to exclude this surface from occlusion
- // detection since we don't have a window for it. We do this because this window is
- // generated by the system as well as its content.
- InputMonitor.setTrustedOverlayInputInfo(mScreenshotLayer, t, displayId, name);
-
- mEnterBlackFrameLayer = displayContent.makeOverlay()
- .setName("EnterBlackFrameLayer")
- .setContainerLayer()
- .setCallsite("ScreenRotationAnimation")
- .build();
-
- HardwareBuffer hardwareBuffer = screenshotBuffer.getHardwareBuffer();
- Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER,
- "ScreenRotationAnimation#getMedianBorderLuma");
- mStartLuma = TransitionAnimation.getBorderLuma(hardwareBuffer,
- screenshotBuffer.getColorSpace());
- Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
-
- t.setLayer(mScreenshotLayer, SCREEN_FREEZE_LAYER_BASE);
- t.reparent(mBackColorSurface, displayContent.getSurfaceControl());
- // If hdr layers are on-screen, e.g. picture-in-picture mode, the screenshot of
- // rotation animation is an sdr image containing tone-mapping hdr content, then
- // disable dimming effect to get avoid of hdr content being dimmed during animation.
- t.setDimmingEnabled(mScreenshotLayer, !screenshotBuffer.containsHdrLayers());
- t.setLayer(mBackColorSurface, -1);
- t.setColor(mBackColorSurface, new float[]{mStartLuma, mStartLuma, mStartLuma});
- t.setAlpha(mBackColorSurface, 1);
- t.setBuffer(mScreenshotLayer, hardwareBuffer);
- t.setColorSpace(mScreenshotLayer, screenshotBuffer.getColorSpace());
- t.show(mScreenshotLayer);
- t.show(mBackColorSurface);
- hardwareBuffer.close();
-
- if (mRoundedCornerOverlay != null) {
- for (SurfaceControl sc : mRoundedCornerOverlay) {
- if (sc.isValid()) {
- t.hide(sc);
- }
- }
- }
-
- } catch (OutOfResourcesException e) {
- Slog.w(TAG, "Unable to allocate freeze surface", e);
- }
-
- // If display size is changed with the same orientation, map the bounds of screenshot to
- // the new logical display size. Currently pending transaction and RWC#mDisplayTransaction
- // are merged to global transaction, so it can be synced with display change when calling
- // DisplayManagerInternal#performTraversal(transaction).
- if (mScreenshotLayer != null && isSizeChanged) {
- displayContent.getPendingTransaction().setGeometry(mScreenshotLayer,
- new Rect(0, 0, mOriginalWidth, mOriginalHeight),
- new Rect(0, 0, logicalWidth, logicalHeight), Surface.ROTATION_0);
- }
-
- ProtoLog.i(WM_SHOW_SURFACE_ALLOC,
- " FREEZE %s: CREATE", mScreenshotLayer);
- if (originalRotation == realOriginalRotation) {
- setRotation(t, realOriginalRotation);
- } else {
- // If the given original rotation is different from real original display rotation,
- // this is playing non-zero degree rotation animation without display rotation change,
- // so the snapshot doesn't need to be transformed.
- mCurRotation = realOriginalRotation;
- mSnapshotInitialMatrix.reset();
- setRotationTransform(t, mSnapshotInitialMatrix);
- }
- t.apply();
- }
-
- void setSkipScreenshotForRoundedCornerOverlays(
- boolean skipScreenshot, SurfaceControl.Transaction t) {
- mDisplayContent.forAllWindows(w -> {
- if (!w.mToken.mRoundedCornerOverlay || !w.isVisible() || !w.mWinAnimator.hasSurface()) {
- return;
- }
- t.setSkipScreenshot(w.mWinAnimator.mSurfaceControl, skipScreenshot);
- }, false);
- if (!skipScreenshot) {
- // Use sync apply to apply the change immediately, so that the next
- // SC.captureDisplay can capture the screen decor layers.
- t.apply(true /* sync */);
- }
- }
-
- public void dumpDebug(ProtoOutputStream proto, long fieldId) {
- final long token = proto.start(fieldId);
- proto.write(STARTED, mStarted);
- proto.write(ANIMATION_RUNNING, mAnimRunning);
- proto.end(token);
- }
-
- boolean hasScreenshot() {
- return mScreenshotLayer != null;
- }
-
- private void setRotationTransform(SurfaceControl.Transaction t, Matrix matrix) {
- if (mScreenshotLayer == null) {
- return;
- }
- matrix.getValues(mTmpFloats);
- float x = mTmpFloats[Matrix.MTRANS_X];
- float y = mTmpFloats[Matrix.MTRANS_Y];
- t.setPosition(mScreenshotLayer, x, y);
- t.setMatrix(mScreenshotLayer,
- mTmpFloats[Matrix.MSCALE_X], mTmpFloats[Matrix.MSKEW_Y],
- mTmpFloats[Matrix.MSKEW_X], mTmpFloats[Matrix.MSCALE_Y]);
-
- t.setAlpha(mScreenshotLayer, (float) 1.0);
- t.show(mScreenshotLayer);
- }
-
- public void printTo(String prefix, PrintWriter pw) {
- pw.print(prefix); pw.print("mSurface="); pw.print(mScreenshotLayer);
- pw.print(prefix);
- pw.print("mEnteringBlackFrame=");
- pw.println(mEnteringBlackFrame);
- if (mEnteringBlackFrame != null) {
- mEnteringBlackFrame.printTo(prefix + " ", pw);
- }
- pw.print(prefix); pw.print("mCurRotation="); pw.print(mCurRotation);
- pw.print(" mOriginalRotation="); pw.println(mOriginalRotation);
- pw.print(prefix); pw.print("mOriginalWidth="); pw.print(mOriginalWidth);
- pw.print(" mOriginalHeight="); pw.println(mOriginalHeight);
- pw.print(prefix); pw.print("mStarted="); pw.print(mStarted);
- pw.print(" mAnimRunning="); pw.print(mAnimRunning);
- pw.print(" mFinishAnimReady="); pw.print(mFinishAnimReady);
- pw.print(" mFinishAnimStartTime="); pw.println(mFinishAnimStartTime);
- pw.print(prefix); pw.print("mRotateExitAnimation="); pw.print(mRotateExitAnimation);
- pw.print(" "); mRotateExitTransformation.printShortString(pw); pw.println();
- pw.print(prefix); pw.print("mRotateEnterAnimation="); pw.print(mRotateEnterAnimation);
- pw.print(" "); mRotateEnterTransformation.printShortString(pw); pw.println();
- pw.print(prefix); pw.print("mSnapshotInitialMatrix=");
- mSnapshotInitialMatrix.dump(pw); pw.println();
- }
-
- public void setRotation(SurfaceControl.Transaction t, int rotation) {
- mCurRotation = rotation;
-
- // Compute the transformation matrix that must be applied
- // to the snapshot to make it stay in the same original position
- // with the current screen rotation.
- int delta = deltaRotation(rotation, mOriginalRotation);
- computeRotationMatrix(delta, mOriginalWidth, mOriginalHeight, mSnapshotInitialMatrix);
- setRotationTransform(t, mSnapshotInitialMatrix);
- }
-
- /**
- * Returns true if animating.
- */
- private boolean startAnimation(SurfaceControl.Transaction t, long maxAnimationDuration,
- float animationScale, int finalWidth, int finalHeight, int exitAnim, int enterAnim) {
- if (mScreenshotLayer == null) {
- // Can't do animation.
- return false;
- }
- if (mStarted) {
- return true;
- }
-
- mStarted = true;
-
- // Figure out how the screen has moved from the original rotation.
- int delta = deltaRotation(mCurRotation, mOriginalRotation);
-
- final boolean customAnim;
- if (exitAnim != 0 && enterAnim != 0) {
- customAnim = true;
- mRotateExitAnimation = AnimationUtils.loadAnimation(mContext, exitAnim);
- mRotateEnterAnimation = AnimationUtils.loadAnimation(mContext, enterAnim);
- mRotateAlphaAnimation = AnimationUtils.loadAnimation(mContext,
- R.anim.screen_rotate_alpha);
- } else {
- customAnim = false;
- switch (delta) { /* Counter-Clockwise Rotations */
- case Surface.ROTATION_0:
- mRotateExitAnimation = AnimationUtils.loadAnimation(mContext,
- R.anim.screen_rotate_0_exit);
- mRotateEnterAnimation = AnimationUtils.loadAnimation(mContext,
- R.anim.rotation_animation_enter);
- break;
- case Surface.ROTATION_90:
- mRotateExitAnimation = AnimationUtils.loadAnimation(mContext,
- R.anim.screen_rotate_plus_90_exit);
- mRotateEnterAnimation = AnimationUtils.loadAnimation(mContext,
- R.anim.screen_rotate_plus_90_enter);
- break;
- case Surface.ROTATION_180:
- mRotateExitAnimation = AnimationUtils.loadAnimation(mContext,
- R.anim.screen_rotate_180_exit);
- mRotateEnterAnimation = AnimationUtils.loadAnimation(mContext,
- R.anim.screen_rotate_180_enter);
- break;
- case Surface.ROTATION_270:
- mRotateExitAnimation = AnimationUtils.loadAnimation(mContext,
- R.anim.screen_rotate_minus_90_exit);
- mRotateEnterAnimation = AnimationUtils.loadAnimation(mContext,
- R.anim.screen_rotate_minus_90_enter);
- break;
- }
- }
-
- ProtoLog.d(WM_DEBUG_ORIENTATION, "Start rotation animation. customAnim=%s, "
- + "mCurRotation=%s, mOriginalRotation=%s",
- customAnim, Surface.rotationToString(mCurRotation),
- Surface.rotationToString(mOriginalRotation));
-
- mRotateExitAnimation.initialize(finalWidth, finalHeight, mOriginalWidth, mOriginalHeight);
- mRotateExitAnimation.restrictDuration(maxAnimationDuration);
- mRotateExitAnimation.scaleCurrentDuration(animationScale);
- mRotateEnterAnimation.initialize(finalWidth, finalHeight, mOriginalWidth, mOriginalHeight);
- mRotateEnterAnimation.restrictDuration(maxAnimationDuration);
- mRotateEnterAnimation.scaleCurrentDuration(animationScale);
-
- mAnimRunning = false;
- mFinishAnimReady = false;
- mFinishAnimStartTime = -1;
-
- if (customAnim) {
- mRotateAlphaAnimation.restrictDuration(maxAnimationDuration);
- mRotateAlphaAnimation.scaleCurrentDuration(animationScale);
- }
-
- if (customAnim && mEnteringBlackFrame == null) {
- try {
- Rect outer = new Rect(-finalWidth, -finalHeight,
- finalWidth * 2, finalHeight * 2);
- Rect inner = new Rect(0, 0, finalWidth, finalHeight);
- mEnteringBlackFrame = new BlackFrame(mService.mTransactionFactory, t, outer, inner,
- SCREEN_FREEZE_LAYER_BASE, mDisplayContent, false, mEnterBlackFrameLayer);
- } catch (OutOfResourcesException e) {
- Slog.w(TAG, "Unable to allocate black surface", e);
- }
- }
-
- if (customAnim) {
- mSurfaceRotationAnimationController.startCustomAnimation();
- } else {
- mSurfaceRotationAnimationController.startScreenRotationAnimation();
- }
-
- return true;
- }
-
- /**
- * Returns true if animating.
- */
- public boolean dismiss(SurfaceControl.Transaction t, long maxAnimationDuration,
- float animationScale, int finalWidth, int finalHeight, int exitAnim, int enterAnim) {
- if (mScreenshotLayer == null) {
- // Can't do animation.
- return false;
- }
- if (!mStarted) {
- mEndLuma = TransitionAnimation.getBorderLuma(mDisplayContent.getWindowingLayer(),
- finalWidth, finalHeight);
- startAnimation(t, maxAnimationDuration, animationScale, finalWidth, finalHeight,
- exitAnim, enterAnim);
- }
- if (!mStarted) {
- return false;
- }
- mFinishAnimReady = true;
- return true;
- }
-
- public void kill() {
- if (mSurfaceRotationAnimationController != null) {
- mSurfaceRotationAnimationController.cancel();
- mSurfaceRotationAnimationController = null;
- }
-
- if (mScreenshotLayer != null) {
- ProtoLog.i(WM_SHOW_SURFACE_ALLOC, " FREEZE %s: DESTROY", mScreenshotLayer);
- SurfaceControl.Transaction t = mService.mTransactionFactory.get();
- if (mScreenshotLayer.isValid()) {
- t.remove(mScreenshotLayer);
- }
- mScreenshotLayer = null;
-
- if (mEnterBlackFrameLayer != null) {
- if (mEnterBlackFrameLayer.isValid()) {
- t.remove(mEnterBlackFrameLayer);
- }
- mEnterBlackFrameLayer = null;
- }
- if (mBackColorSurface != null) {
- if (mBackColorSurface.isValid()) {
- t.remove(mBackColorSurface);
- }
- mBackColorSurface = null;
- }
-
- if (mRoundedCornerOverlay != null) {
- if (mDisplayContent.getRotationAnimation() == null
- || mDisplayContent.getRotationAnimation() == this) {
- setSkipScreenshotForRoundedCornerOverlays(true, t);
- for (SurfaceControl sc : mRoundedCornerOverlay) {
- if (sc.isValid()) {
- t.show(sc);
- }
- }
- }
- mRoundedCornerOverlay = null;
- }
- t.apply();
- }
-
- if (mEnteringBlackFrame != null) {
- mEnteringBlackFrame.kill();
- mEnteringBlackFrame = null;
- }
- if (mRotateExitAnimation != null) {
- mRotateExitAnimation.cancel();
- mRotateExitAnimation = null;
- }
- if (mRotateEnterAnimation != null) {
- mRotateEnterAnimation.cancel();
- mRotateEnterAnimation = null;
- }
- if (mRotateAlphaAnimation != null) {
- mRotateAlphaAnimation.cancel();
- mRotateAlphaAnimation = null;
- }
- }
-
- public boolean isAnimating() {
- return mSurfaceRotationAnimationController != null
- && mSurfaceRotationAnimationController.isAnimating();
- }
-
- public boolean isRotating() {
- return mCurRotation != mOriginalRotation;
- }
-
- /**
- * Utility class that runs a {@link ScreenRotationAnimation} on the {@link
- * SurfaceAnimationRunner}.
- * <p>
- * The rotation animation supports both screen rotation and custom animations
- *
- * For custom animations:
- * <ul>
- * <li>
- * The screenshot layer which has an added animation of it's alpha channel
- * ("screen_rotate_alpha") and that will be applied along with the custom animation.
- * </li>
- * <li> A device layer that is animated with the provided custom animation </li>
- * </ul>
- *
- * For screen rotation:
- * <ul>
- * <li> A rotation layer that is both rotated and faded out during a single animation </li>
- * <li> A device layer that is both rotated and faded in during a single animation </li>
- * <li> A background color layer that transitions colors behind the first two layers </li>
- * </ul>
- *
- * {@link ScreenRotationAnimation#startAnimation(
- * SurfaceControl.Transaction, long, float, int, int, int, int)}.
- * </ul>
- *
- * <p>
- * Thus an {@link LocalAnimationAdapter.AnimationSpec} is created for each of
- * this three {@link SurfaceControl}s which then delegates the animation to the
- * {@link ScreenRotationAnimation}.
- */
- class SurfaceRotationAnimationController {
- private SurfaceAnimator mDisplayAnimator;
- private SurfaceAnimator mScreenshotRotationAnimator;
- private SurfaceAnimator mRotateScreenAnimator;
- private SurfaceAnimator mEnterBlackFrameAnimator;
-
- void startCustomAnimation() {
- try {
- mService.mSurfaceAnimationRunner.deferStartingAnimations();
- mRotateScreenAnimator = startScreenshotAlphaAnimation();
- mDisplayAnimator = startDisplayRotation();
- if (mEnteringBlackFrame != null) {
- mEnterBlackFrameAnimator = startEnterBlackFrameAnimation();
- }
- } finally {
- mService.mSurfaceAnimationRunner.continueStartingAnimations();
- }
- }
-
- /**
- * Start the rotation animation of the display and the screenshot on the
- * {@link SurfaceAnimationRunner}.
- */
- void startScreenRotationAnimation() {
- try {
- mService.mSurfaceAnimationRunner.deferStartingAnimations();
- mDisplayAnimator = startDisplayRotation();
- mScreenshotRotationAnimator = startScreenshotRotationAnimation();
- startColorAnimation();
- } finally {
- mService.mSurfaceAnimationRunner.continueStartingAnimations();
- }
- }
-
- private SimpleSurfaceAnimatable.Builder initializeBuilder() {
- return new SimpleSurfaceAnimatable.Builder()
- .setSyncTransactionSupplier(mDisplayContent::getSyncTransaction)
- .setPendingTransactionSupplier(mDisplayContent::getPendingTransaction)
- .setCommitTransactionRunnable(mDisplayContent::commitPendingTransaction)
- .setAnimationLeashSupplier(mDisplayContent::makeOverlay);
- }
-
- private SurfaceAnimator startDisplayRotation() {
- SurfaceAnimator animator = startAnimation(initializeBuilder()
- .setAnimationLeashParent(mDisplayContent.getSurfaceControl())
- .setSurfaceControl(mDisplayContent.getWindowingLayer())
- .setParentSurfaceControl(mDisplayContent.getSurfaceControl())
- .setWidth(mDisplayContent.getSurfaceWidth())
- .setHeight(mDisplayContent.getSurfaceHeight())
- .build(),
- createWindowAnimationSpec(mRotateEnterAnimation),
- this::onAnimationEnd);
-
- // Crop the animation leash to avoid extended wallpaper from showing over
- // mBackColorSurface
- Rect displayBounds = mDisplayContent.getBounds();
- mDisplayContent.getPendingTransaction()
- .setWindowCrop(animator.mLeash, displayBounds.width(), displayBounds.height());
- return animator;
- }
-
- private SurfaceAnimator startScreenshotAlphaAnimation() {
- return startAnimation(initializeBuilder()
- .setSurfaceControl(mScreenshotLayer)
- .setAnimationLeashParent(mDisplayContent.getOverlayLayer())
- .setWidth(mDisplayContent.getSurfaceWidth())
- .setHeight(mDisplayContent.getSurfaceHeight())
- .build(),
- createWindowAnimationSpec(mRotateAlphaAnimation),
- this::onAnimationEnd);
- }
-
- private SurfaceAnimator startEnterBlackFrameAnimation() {
- return startAnimation(initializeBuilder()
- .setSurfaceControl(mEnterBlackFrameLayer)
- .setAnimationLeashParent(mDisplayContent.getOverlayLayer())
- .build(),
- createWindowAnimationSpec(mRotateEnterAnimation),
- this::onAnimationEnd);
- }
-
- private SurfaceAnimator startScreenshotRotationAnimation() {
- return startAnimation(initializeBuilder()
- .setSurfaceControl(mScreenshotLayer)
- .setAnimationLeashParent(mDisplayContent.getOverlayLayer())
- .build(),
- createWindowAnimationSpec(mRotateExitAnimation),
- this::onAnimationEnd);
- }
-
-
- /**
- * Applies the color change from {@link #mStartLuma} to {@link #mEndLuma} as a
- * grayscale color
- */
- private void startColorAnimation() {
- int colorTransitionMs = mContext.getResources().getInteger(
- R.integer.config_screen_rotation_color_transition);
- final SurfaceAnimationRunner runner = mService.mSurfaceAnimationRunner;
- final float[] rgbTmpFloat = new float[3];
- final int startColor = Color.rgb(mStartLuma, mStartLuma, mStartLuma);
- final int endColor = Color.rgb(mEndLuma, mEndLuma, mEndLuma);
- final long duration = colorTransitionMs * (long) mService.getCurrentAnimatorScale();
- final ArgbEvaluator va = ArgbEvaluator.getInstance();
- runner.startAnimation(
- new LocalAnimationAdapter.AnimationSpec() {
- @Override
- public long getDuration() {
- return duration;
- }
-
- @Override
- public void apply(SurfaceControl.Transaction t, SurfaceControl leash,
- long currentPlayTime) {
- final float fraction = getFraction(currentPlayTime);
- final int color = (Integer) va.evaluate(fraction, startColor, endColor);
- Color middleColor = Color.valueOf(color);
- rgbTmpFloat[0] = middleColor.red();
- rgbTmpFloat[1] = middleColor.green();
- rgbTmpFloat[2] = middleColor.blue();
- if (leash.isValid()) {
- t.setColor(leash, rgbTmpFloat);
- }
- }
-
- @Override
- public void dump(PrintWriter pw, String prefix) {
- pw.println(prefix + "startLuma=" + mStartLuma
- + " endLuma=" + mEndLuma
- + " durationMs=" + colorTransitionMs);
- }
-
- @Override
- public void dumpDebugInner(ProtoOutputStream proto) {
- final long token = proto.start(ROTATE);
- proto.write(START_LUMA, mStartLuma);
- proto.write(END_LUMA, mEndLuma);
- proto.write(DURATION_MS, colorTransitionMs);
- proto.end(token);
- }
- },
- mBackColorSurface, mDisplayContent.getPendingTransaction(), null);
- }
-
- private WindowAnimationSpec createWindowAnimationSpec(Animation mAnimation) {
- return new WindowAnimationSpec(mAnimation, new Point(0, 0) /* position */,
- false /* canSkipFirstFrame */, 0 /* WindowCornerRadius */);
- }
-
- /**
- * Start an animation defined by animationSpec on a new {@link SurfaceAnimator}.
- *
- * @param animatable The animatable used for the animation.
- * @param animationSpec The spec of the animation.
- * @param animationFinishedCallback Callback passed to the {@link SurfaceAnimator}
- * and called when the animation finishes.
- * @return The newly created {@link SurfaceAnimator} that as been started.
- */
- private SurfaceAnimator startAnimation(
- SurfaceAnimator.Animatable animatable,
- LocalAnimationAdapter.AnimationSpec animationSpec,
- OnAnimationFinishedCallback animationFinishedCallback) {
- SurfaceAnimator animator = new SurfaceAnimator(
- animatable, animationFinishedCallback, mService);
-
- LocalAnimationAdapter localAnimationAdapter = new LocalAnimationAdapter(
- animationSpec, mService.mSurfaceAnimationRunner);
- animator.startAnimation(mDisplayContent.getPendingTransaction(),
- localAnimationAdapter, false, ANIMATION_TYPE_SCREEN_ROTATION);
- return animator;
- }
-
- private void onAnimationEnd(@AnimationType int type, AnimationAdapter anim) {
- synchronized (mService.mGlobalLock) {
- if (isAnimating()) {
- ProtoLog.v(WM_DEBUG_ORIENTATION,
- "ScreenRotation still animating: type: %d\n"
- + "mDisplayAnimator: %s\n"
- + "mEnterBlackFrameAnimator: %s\n"
- + "mRotateScreenAnimator: %s\n"
- + "mScreenshotRotationAnimator: %s",
- type,
- mDisplayAnimator != null
- ? mDisplayAnimator.isAnimating() : null,
- mEnterBlackFrameAnimator != null
- ? mEnterBlackFrameAnimator.isAnimating() : null,
- mRotateScreenAnimator != null
- ? mRotateScreenAnimator.isAnimating() : null,
- mScreenshotRotationAnimator != null
- ? mScreenshotRotationAnimator.isAnimating() : null
- );
- return;
- }
- ProtoLog.d(WM_DEBUG_ORIENTATION, "ScreenRotationAnimation onAnimationEnd");
- mEnterBlackFrameAnimator = null;
- mScreenshotRotationAnimator = null;
- mRotateScreenAnimator = null;
- mService.mAnimator.mBulkUpdateParams |= WindowSurfacePlacer.SET_UPDATE_ROTATION;
- if (mDisplayContent.getRotationAnimation() == ScreenRotationAnimation.this) {
- // It also invokes kill().
- mDisplayContent.setRotationAnimation(null);
- mDisplayContent.mAppCompatCameraPolicy.onScreenRotationAnimationFinished();
- } else {
- kill();
- }
- mService.updateRotation(false, false);
- }
- }
-
- public void cancel() {
- if (mEnterBlackFrameAnimator != null) {
- mEnterBlackFrameAnimator.cancelAnimation();
- }
- if (mScreenshotRotationAnimator != null) {
- mScreenshotRotationAnimator.cancelAnimation();
- }
-
- if (mRotateScreenAnimator != null) {
- mRotateScreenAnimator.cancelAnimation();
- }
-
- if (mDisplayAnimator != null) {
- mDisplayAnimator.cancelAnimation();
- }
-
- if (mBackColorSurface != null) {
- mService.mSurfaceAnimationRunner.onAnimationCancelled(mBackColorSurface);
- }
- }
-
- public boolean isAnimating() {
- return mDisplayAnimator != null && mDisplayAnimator.isAnimating()
- || mEnterBlackFrameAnimator != null && mEnterBlackFrameAnimator.isAnimating()
- || mRotateScreenAnimator != null && mRotateScreenAnimator.isAnimating()
- || mScreenshotRotationAnimator != null
- && mScreenshotRotationAnimator.isAnimating();
- }
- }
-}
diff --git a/services/core/java/com/android/server/wm/SimpleSurfaceAnimatable.java b/services/core/java/com/android/server/wm/SimpleSurfaceAnimatable.java
deleted file mode 100644
index 3b3db890f67e..000000000000
--- a/services/core/java/com/android/server/wm/SimpleSurfaceAnimatable.java
+++ /dev/null
@@ -1,330 +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 android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.view.SurfaceControl;
-
-import java.util.function.BiConsumer;
-import java.util.function.Consumer;
-import java.util.function.Supplier;
-
-/**
- * An implementation of {@link SurfaceAnimator.Animatable} that is instantiated
- * using a builder pattern for more convenience over reimplementing the whole interface.
- * <p>
- * Use {@link SimpleSurfaceAnimatable.Builder} to create a new instance of this class.
- *
- * @see com.android.server.wm.SurfaceAnimator.Animatable
- */
-public class SimpleSurfaceAnimatable implements SurfaceAnimator.Animatable {
- private final int mWidth;
- private final int mHeight;
- private final boolean mShouldDeferAnimationFinish;
- private final SurfaceControl mAnimationLeashParent;
- private final SurfaceControl mSurfaceControl;
- private final SurfaceControl mParentSurfaceControl;
- private final Runnable mCommitTransactionRunnable;
- private final Supplier<SurfaceControl.Builder> mAnimationLeashFactory;
- private final Supplier<SurfaceControl.Transaction> mSyncTransaction;
- private final Supplier<SurfaceControl.Transaction> mPendingTransaction;
- private final BiConsumer<SurfaceControl.Transaction, SurfaceControl> mOnAnimationLeashCreated;
- private final Consumer<SurfaceControl.Transaction> mOnAnimationLeashLost;
- private final Consumer<Runnable> mOnAnimationFinished;
-
- /**
- * Use {@link SimpleSurfaceAnimatable.Builder} to create a new instance.
- */
- private SimpleSurfaceAnimatable(Builder builder) {
- mWidth = builder.mWidth;
- mHeight = builder.mHeight;
- mShouldDeferAnimationFinish = builder.mShouldDeferAnimationFinish;
- mAnimationLeashParent = builder.mAnimationLeashParent;
- mSurfaceControl = builder.mSurfaceControl;
- mParentSurfaceControl = builder.mParentSurfaceControl;
- mCommitTransactionRunnable = builder.mCommitTransactionRunnable;
- mAnimationLeashFactory = builder.mAnimationLeashFactory;
- mOnAnimationLeashCreated = builder.mOnAnimationLeashCreated;
- mOnAnimationLeashLost = builder.mOnAnimationLeashLost;
- mSyncTransaction = builder.mSyncTransactionSupplier;
- mPendingTransaction = builder.mPendingTransactionSupplier;
- mOnAnimationFinished = builder.mOnAnimationFinished;
- }
-
- @Override
- public SurfaceControl.Transaction getSyncTransaction() {
- return mSyncTransaction.get();
- }
-
- @NonNull
- @Override
- public SurfaceControl.Transaction getPendingTransaction() {
- return mPendingTransaction.get();
- }
-
- @Override
- public void commitPendingTransaction() {
- mCommitTransactionRunnable.run();
- }
-
- @Override
- public void onAnimationLeashCreated(SurfaceControl.Transaction t, SurfaceControl leash) {
- if (mOnAnimationLeashCreated != null) {
- mOnAnimationLeashCreated.accept(t, leash);
- }
-
- }
-
- @Override
- public void onAnimationLeashLost(SurfaceControl.Transaction t) {
- if (mOnAnimationLeashLost != null) {
- mOnAnimationLeashLost.accept(t);
- }
- }
-
- @Override
- @NonNull
- public SurfaceControl.Builder makeAnimationLeash() {
- return mAnimationLeashFactory.get();
- }
-
- @Override
- public SurfaceControl getAnimationLeashParent() {
- return mAnimationLeashParent;
- }
-
- @Override
- @Nullable
- public SurfaceControl getSurfaceControl() {
- return mSurfaceControl;
- }
-
- @Override
- public SurfaceControl getParentSurfaceControl() {
- return mParentSurfaceControl;
- }
-
- @Override
- public int getSurfaceWidth() {
- return mWidth;
- }
-
- @Override
- public int getSurfaceHeight() {
- return mHeight;
- }
-
- @Override
- public boolean shouldDeferAnimationFinish(Runnable endDeferFinishCallback) {
- if (mOnAnimationFinished != null) {
- mOnAnimationFinished.accept(endDeferFinishCallback);
- }
- return mShouldDeferAnimationFinish;
- }
-
- /**
- * Builder class to create a {@link SurfaceAnimator.Animatable} without having to
- * create a new class that implements the interface.
- */
- static class Builder {
- private int mWidth = -1;
- private int mHeight = -1;
- private boolean mShouldDeferAnimationFinish = false;
-
- @Nullable
- private SurfaceControl mAnimationLeashParent = null;
-
- @Nullable
- private SurfaceControl mSurfaceControl = null;
-
- @Nullable
- private SurfaceControl mParentSurfaceControl = null;
- private Runnable mCommitTransactionRunnable;
-
- @Nullable
- private BiConsumer<SurfaceControl.Transaction, SurfaceControl> mOnAnimationLeashCreated =
- null;
-
- @Nullable
- private Consumer<SurfaceControl.Transaction> mOnAnimationLeashLost = null;
-
- @Nullable
- private Consumer<Runnable> mOnAnimationFinished = null;
-
- @NonNull
- private Supplier<SurfaceControl.Transaction> mSyncTransactionSupplier;
-
- @NonNull
- private Supplier<SurfaceControl.Transaction> mPendingTransactionSupplier;
-
- @NonNull
- private Supplier<SurfaceControl.Builder> mAnimationLeashFactory;
-
- /**
- * Set the runnable to be called when
- * {@link SurfaceAnimator.Animatable#commitPendingTransaction()}
- * is called.
- *
- * @see SurfaceAnimator.Animatable#commitPendingTransaction()
- */
- public SimpleSurfaceAnimatable.Builder setCommitTransactionRunnable(
- @NonNull Runnable commitTransactionRunnable) {
- mCommitTransactionRunnable = commitTransactionRunnable;
- return this;
- }
-
- /**
- * Set the callback called when
- * {@link SurfaceAnimator.Animatable#onAnimationLeashCreated(SurfaceControl.Transaction,
- * SurfaceControl)} is called
- *
- * @see SurfaceAnimator.Animatable#onAnimationLeashCreated(SurfaceControl.Transaction,
- * SurfaceControl)
- */
- public SimpleSurfaceAnimatable.Builder setOnAnimationLeashCreated(
- @Nullable BiConsumer<SurfaceControl.Transaction, SurfaceControl>
- onAnimationLeashCreated) {
- mOnAnimationLeashCreated = onAnimationLeashCreated;
- return this;
- }
-
- /**
- * Set the callback called when
- * {@link SurfaceAnimator.Animatable#onAnimationLeashLost(SurfaceControl.Transaction)}
- * (SurfaceControl.Transaction, SurfaceControl)} is called
- *
- * @see SurfaceAnimator.Animatable#onAnimationLeashLost(SurfaceControl.Transaction)
- */
- public SimpleSurfaceAnimatable.Builder setOnAnimationLeashLost(
- @Nullable Consumer<SurfaceControl.Transaction> onAnimationLeashLost) {
- mOnAnimationLeashLost = onAnimationLeashLost;
- return this;
- }
-
- /**
- * @see SurfaceAnimator.Animatable#getSyncTransaction()
- */
- public Builder setSyncTransactionSupplier(
- @NonNull Supplier<SurfaceControl.Transaction> syncTransactionSupplier) {
- mSyncTransactionSupplier = syncTransactionSupplier;
- return this;
- }
-
- /**
- * @see SurfaceAnimator.Animatable#getPendingTransaction()
- */
- public Builder setPendingTransactionSupplier(
- @NonNull Supplier<SurfaceControl.Transaction> pendingTransactionSupplier) {
- mPendingTransactionSupplier = pendingTransactionSupplier;
- return this;
- }
-
- /**
- * Set the {@link Supplier} responsible for creating a new animation leash.
- *
- * @see SurfaceAnimator.Animatable#makeAnimationLeash()
- */
- public SimpleSurfaceAnimatable.Builder setAnimationLeashSupplier(
- @NonNull Supplier<SurfaceControl.Builder> animationLeashFactory) {
- mAnimationLeashFactory = animationLeashFactory;
- return this;
- }
-
- /**
- * @see SurfaceAnimator.Animatable#getAnimationLeashParent()
- */
- public SimpleSurfaceAnimatable.Builder setAnimationLeashParent(
- SurfaceControl animationLeashParent) {
- mAnimationLeashParent = animationLeashParent;
- return this;
- }
-
- /**
- * @see SurfaceAnimator.Animatable#getSurfaceControl()
- */
- public SimpleSurfaceAnimatable.Builder setSurfaceControl(
- @NonNull SurfaceControl surfaceControl) {
- mSurfaceControl = surfaceControl;
- return this;
- }
-
- /**
- * @see SurfaceAnimator.Animatable#getParentSurfaceControl()
- */
- public SimpleSurfaceAnimatable.Builder setParentSurfaceControl(
- SurfaceControl parentSurfaceControl) {
- mParentSurfaceControl = parentSurfaceControl;
- return this;
- }
-
- /**
- * Default to -1.
- *
- * @see SurfaceAnimator.Animatable#getSurfaceWidth()
- */
- public SimpleSurfaceAnimatable.Builder setWidth(int width) {
- mWidth = width;
- return this;
- }
-
- /**
- * Default to -1.
- *
- * @see SurfaceAnimator.Animatable#getSurfaceHeight()
- */
- public SimpleSurfaceAnimatable.Builder setHeight(int height) {
- mHeight = height;
- return this;
- }
-
- /**
- * Set the value returned by
- * {@link SurfaceAnimator.Animatable#shouldDeferAnimationFinish(Runnable)}.
- *
- * @param onAnimationFinish will be called with the runnable to execute when the animation
- * needs to be finished.
- * @see SurfaceAnimator.Animatable#shouldDeferAnimationFinish(Runnable)
- */
- public SimpleSurfaceAnimatable.Builder setShouldDeferAnimationFinish(
- boolean shouldDeferAnimationFinish,
- @Nullable Consumer<Runnable> onAnimationFinish) {
- mShouldDeferAnimationFinish = shouldDeferAnimationFinish;
- mOnAnimationFinished = onAnimationFinish;
- return this;
- }
-
- public SurfaceAnimator.Animatable build() {
- if (mSyncTransactionSupplier == null) {
- throw new IllegalArgumentException("mSyncTransactionSupplier cannot be null");
- }
- if (mPendingTransactionSupplier == null) {
- throw new IllegalArgumentException("mPendingTransactionSupplier cannot be null");
- }
- if (mAnimationLeashFactory == null) {
- throw new IllegalArgumentException("mAnimationLeashFactory cannot be null");
- }
- if (mCommitTransactionRunnable == null) {
- throw new IllegalArgumentException("mCommitTransactionRunnable cannot be null");
- }
- if (mSurfaceControl == null) {
- throw new IllegalArgumentException("mSurfaceControl cannot be null");
- }
- return new SimpleSurfaceAnimatable(this);
- }
- }
-}
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 7a88338d8ac5..c6136f316c3e 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -5101,6 +5101,7 @@ class Task extends TaskFragment {
mTranslucentActivityWaiting = r;
mPendingConvertFromTranslucentActivity = r;
mUndrawnActivitiesBelowTopTranslucent.clear();
+ updateTaskDescription();
mHandler.sendEmptyMessageDelayed(TRANSLUCENT_TIMEOUT_MSG, TRANSLUCENT_CONVERSION_TIMEOUT);
}
@@ -5110,6 +5111,7 @@ class Task extends TaskFragment {
+ " but is " + r);
}
mPendingConvertFromTranslucentActivity = null;
+ updateTaskDescription();
}
/**
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 1993053c16cd..fc7437b95e03 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -2092,12 +2092,6 @@ class TaskFragment extends WindowContainer<WindowContainer> {
ProtoLog.v(WM_DEBUG_STATES, "App died during pause, not stopping: %s", prev);
prev = null;
}
- // It is possible the activity was freezing the screen before it was paused.
- // In that case go ahead and remove the freeze this activity has on the screen
- // since it is no longer visible.
- if (prev != null) {
- prev.stopFreezingScreen(true /* unfreezeNow */, true /* force */);
- }
}
if (resumeNext) {
diff --git a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
index c6a16795dd8c..aaae160084a9 100644
--- a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
@@ -842,7 +842,6 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr
return;
}
validateAndGetState(organizer);
- Slog.w(TAG, "onTaskFragmentError ", exception);
addPendingEvent(new PendingTaskFragmentEvent.Builder(
PendingTaskFragmentEvent.EVENT_ERROR, organizer)
.setErrorCallbackToken(errorCallbackToken)
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 75cefdff2b0b..37cc0d22c063 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -1626,6 +1626,11 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
for (int i = 0; i < mConfigAtEndActivities.size(); ++i) {
final ActivityRecord target = mConfigAtEndActivities.get(i);
final SurfaceControl targetLeash = target.getSurfaceControl();
+ if (targetLeash == null) {
+ // activity may have been removed. In this case, no need to sync, just update state.
+ target.resumeConfigurationDispatch();
+ continue;
+ }
if (target.getSyncGroup() == null || target.getSyncGroup().isIgnoring(target)) {
if (syncId < 0) {
final BLASTSyncEngine.SyncGroup sg = mSyncEngine.prepareSyncSet(
diff --git a/services/core/java/com/android/server/wm/WindowAnimator.java b/services/core/java/com/android/server/wm/WindowAnimator.java
index 790ae1eef0c3..80137a298ac2 100644
--- a/services/core/java/com/android/server/wm/WindowAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowAnimator.java
@@ -146,7 +146,6 @@ public class WindowAnimator {
boolean rootAnimating = false;
mCurrentTime = frameTimeNs / TimeUtils.NANOS_PER_MS;
mBulkUpdateParams = 0;
- root.mOrientationChangeComplete = true;
if (DEBUG_WINDOW_TRACE) {
Slog.i(TAG, "!!! animate: entry time=" + mCurrentTime);
}
@@ -203,8 +202,7 @@ public class WindowAnimator {
}
final boolean hasPendingLayoutChanges = root.hasPendingLayoutChanges(this);
- final boolean doRequest = (mBulkUpdateParams != 0 || root.mOrientationChangeComplete)
- && root.copyAnimToLayoutParams();
+ final boolean doRequest = mBulkUpdateParams != 0 && root.copyAnimToLayoutParams();
if (hasPendingLayoutChanges || doRequest) {
mService.mWindowPlacerLocked.requestTraversal();
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 1754d7346220..0e9b4233aff7 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -31,7 +31,6 @@ import static android.app.ActivityManagerInternal.ALLOW_FULL_ONLY;
import static android.app.ActivityManagerInternal.ALLOW_NON_FULL;
import static android.app.AppOpsManager.OP_SYSTEM_ALERT_WINDOW;
import static android.app.StatusBarManager.DISABLE_MASK;
-import static android.app.WindowConfiguration.ROTATION_UNDEFINED;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED;
import static android.content.pm.PackageManager.FEATURE_FREEFORM_WINDOW_MANAGEMENT;
@@ -116,13 +115,11 @@ import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_WINDOW_MOV
import static com.android.internal.protolog.WmProtoLogGroups.WM_ERROR;
import static com.android.internal.protolog.WmProtoLogGroups.WM_SHOW_TRANSACTIONS;
import static com.android.internal.util.FrameworkStatsLog.SENSITIVE_NOTIFICATION_APP_PROTECTION_APPLIED;
-import static com.android.internal.util.LatencyTracker.ACTION_ROTATE_SCREEN;
import static com.android.server.LockGuard.INDEX_WINDOW;
import static com.android.server.LockGuard.installLock;
import static com.android.server.policy.PhoneWindowManager.TRACE_WAIT_FOR_ALL_WINDOWS_DRAWN_METHOD;
import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
import static com.android.server.wm.ActivityTaskManagerService.DEMOTE_TOP_REASON_EXPANDED_NOTIFICATION_SHADE;
-import static com.android.server.wm.ActivityTaskManagerService.POWER_MODE_REASON_CHANGE_DISPLAY;
import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND;
import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND_FLOATING;
import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_BACKGROUND_SOLID_COLOR;
@@ -151,7 +148,6 @@ 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.WindowManagerInternal.OnWindowRemovedListener;
import static com.android.server.wm.WindowManagerServiceDumpProto.BACK_NAVIGATION;
-import static com.android.server.wm.WindowManagerServiceDumpProto.DISPLAY_FROZEN;
import static com.android.server.wm.WindowManagerServiceDumpProto.FOCUSED_APP;
import static com.android.server.wm.WindowManagerServiceDumpProto.FOCUSED_DISPLAY_ID;
import static com.android.server.wm.WindowManagerServiceDumpProto.FOCUSED_WINDOW;
@@ -251,7 +247,6 @@ import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseIntArray;
-import android.util.TimeUtils;
import android.util.TypedValue;
import android.util.proto.ProtoOutputStream;
import android.view.Choreographer;
@@ -403,8 +398,6 @@ public class WindowManagerService extends IWindowManager.Stub
static final int LAYOUT_REPEAT_THRESHOLD = 4;
- static final boolean PROFILE_ORIENTATION = false;
-
/** The maximum length we will accept for a loaded animation duration:
* this is 10 seconds.
*/
@@ -742,19 +735,8 @@ public class WindowManagerService extends IWindowManager.Stub
@Nullable
private Runnable mPointerDownOutsideFocusRunnable;
- boolean mDisplayFrozen = false;
- long mDisplayFreezeTime = 0;
- int mLastDisplayFreezeDuration = 0;
- Object mLastFinishedFreezeSource = null;
boolean mSwitchingUser = false;
- final static int WINDOWS_FREEZING_SCREENS_NONE = 0;
- final static int WINDOWS_FREEZING_SCREENS_ACTIVE = 1;
- final static int WINDOWS_FREEZING_SCREENS_TIMEOUT = 2;
- int mWindowsFreezingScreen = WINDOWS_FREEZING_SCREENS_NONE;
-
- int mAppsFreezingScreen = 0;
-
@VisibleForTesting
boolean mPerDisplayFocusEnabled;
@@ -764,11 +746,6 @@ public class WindowManagerService extends IWindowManager.Stub
// Number of windows whose insets state have been changed.
int mWindowsInsetsChanged = 0;
- // This is held as long as we have the screen frozen, to give us time to
- // perform a rotation animation when turning off shows the lock screen which
- // changes the orientation.
- private final PowerManager.WakeLock mScreenFrozenLock;
-
final TaskSnapshotController mTaskSnapshotController;
final SnapshotController mSnapshotController;
@@ -1053,12 +1030,6 @@ public class WindowManagerService extends IWindowManager.Stub
final DragDropController mDragDropController;
- /** For frozen screen animations. */
- private int mExitAnimId, mEnterAnimId;
-
- /** The display that the rotation animation is applying to. */
- private int mFrozenDisplayId = INVALID_DISPLAY;
-
/** Skip repeated ActivityRecords initialization. Note that AppWindowsToken's version of this
* is a long initialized to Long.MIN_VALUE so that it doesn't match this value on startup. */
int mTransactionSequence;
@@ -1161,12 +1132,6 @@ public class WindowManagerService extends IWindowManager.Stub
}
};
- final ArrayList<AppFreezeListener> mAppFreezeListeners = new ArrayList<>();
-
- interface AppFreezeListener {
- void onAppFreezeTimeout();
- }
-
private final ScreenRecordingCallbackController mScreenRecordingCallbackController;
private volatile boolean mDisableSecureWindows = false;
@@ -1347,9 +1312,6 @@ public class WindowManagerService extends IWindowManager.Stub
mAnimationsDisabled = mPowerManagerInternal
.getLowPowerState(ServiceType.ANIMATION).batterySaverEnabled;
}
- mScreenFrozenLock = mPowerManager.newWakeLock(
- PowerManager.PARTIAL_WAKE_LOCK, "SCREEN_FROZEN");
- mScreenFrozenLock.setReferenceCounted(false);
mRotationWatcherController = new RotationWatcherController(this);
mDisplayNotificationController = new DisplayWindowListenerController(this);
@@ -5601,11 +5563,8 @@ public class WindowManagerService extends IWindowManager.Stub
// -------------------------------------------------------------
final class H extends android.os.Handler {
- public static final int WINDOW_FREEZE_TIMEOUT = 11;
-
public static final int PERSIST_ANIMATION_SCALE = 14;
public static final int ENABLE_SCREEN = 16;
- public static final int APP_FREEZE_TIMEOUT = 17;
public static final int REPORT_WINDOWS_CHANGE = 19;
public static final int REPORT_HARD_KEYBOARD_STATUS_CHANGE = 22;
@@ -5645,14 +5604,6 @@ public class WindowManagerService extends IWindowManager.Stub
Slog.v(TAG_WM, "handleMessage: entry what=" + msg.what);
}
switch (msg.what) {
- case WINDOW_FREEZE_TIMEOUT: {
- final DisplayContent displayContent = (DisplayContent) msg.obj;
- synchronized (mGlobalLock) {
- displayContent.onWindowFreezeTimeout();
- }
- break;
- }
-
case PERSIST_ANIMATION_SCALE: {
Settings.Global.putFloat(mContext.getContentResolver(),
Settings.Global.WINDOW_ANIMATION_SCALE, mWindowAnimationScaleSetting);
@@ -5691,17 +5642,6 @@ public class WindowManagerService extends IWindowManager.Stub
break;
}
- case APP_FREEZE_TIMEOUT: {
- synchronized (mGlobalLock) {
- ProtoLog.w(WM_ERROR, "App freeze timeout expired.");
- mWindowsFreezingScreen = WINDOWS_FREEZING_SCREENS_TIMEOUT;
- for (int i = mAppFreezeListeners.size() - 1; i >= 0; --i) {
- mAppFreezeListeners.get(i).onAppFreezeTimeout();
- }
- }
- break;
- }
-
case REPORT_WINDOWS_CHANGE: {
if (mWindowsChanged) {
synchronized (mGlobalLock) {
@@ -6310,22 +6250,6 @@ public class WindowManagerService extends IWindowManager.Stub
return win;
}
- void makeWindowFreezingScreenIfNeededLocked(WindowState w) {
- // If the screen is currently frozen, then keep it frozen until this window draws at its
- // new orientation.
- if (mFrozenDisplayId != INVALID_DISPLAY && mFrozenDisplayId == w.getDisplayId()
- && mWindowsFreezingScreen != WINDOWS_FREEZING_SCREENS_TIMEOUT) {
- ProtoLog.v(WM_DEBUG_ORIENTATION, "Changing surface while display frozen: %s", w);
- if (mWindowsFreezingScreen == WINDOWS_FREEZING_SCREENS_NONE) {
- mWindowsFreezingScreen = WINDOWS_FREEZING_SCREENS_ACTIVE;
- // XXX should probably keep timeout from
- // when we first froze the display.
- mH.sendNewMessageDelayed(H.WINDOW_FREEZE_TIMEOUT, w.getDisplayContent(),
- WINDOW_FREEZE_TIMEOUT_DURATION);
- }
- }
- }
-
void checkDrawnWindowsLocked() {
if (mWaitingForDrawnCallbacks.isEmpty()) {
return;
@@ -6394,188 +6318,6 @@ public class WindowManagerService extends IWindowManager.Stub
return changed;
}
- void startFreezingDisplay(int exitAnim, int enterAnim) {
- startFreezingDisplay(exitAnim, enterAnim, getDefaultDisplayContentLocked());
- }
-
- void startFreezingDisplay(int exitAnim, int enterAnim, DisplayContent displayContent) {
- startFreezingDisplay(exitAnim, enterAnim, displayContent,
- ROTATION_UNDEFINED /* overrideOriginalRotation */);
- }
-
- void startFreezingDisplay(int exitAnim, int enterAnim, DisplayContent displayContent,
- int overrideOriginalRotation) {
- if (mDisplayFrozen || displayContent.getDisplayRotation().isRotatingSeamlessly()) {
- return;
- }
-
- if (!displayContent.isReady() || !displayContent.getDisplayPolicy().isScreenOnFully()
- || displayContent.getDisplayInfo().state == Display.STATE_OFF
- || !displayContent.okToAnimate()) {
- // No need to freeze the screen before the display is ready, if the screen is off,
- // or we can't currently animate.
- return;
- }
-
- displayContent.requestDisplayUpdate(() -> {
- Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "WMS.doStartFreezingDisplay");
- doStartFreezingDisplay(exitAnim, enterAnim, displayContent, overrideOriginalRotation);
- Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
- });
- }
-
- private void doStartFreezingDisplay(int exitAnim, int enterAnim, DisplayContent displayContent,
- int overrideOriginalRotation) {
- ProtoLog.d(WM_DEBUG_ORIENTATION,
- "startFreezingDisplayLocked: exitAnim=%d enterAnim=%d called by %s",
- exitAnim, enterAnim, Debug.getCallers(8));
- mScreenFrozenLock.acquire();
- // Apply launch power mode to reduce screen frozen time because orientation change may
- // relaunch activity and redraw windows. This may also help speed up user switching.
- mAtmService.startPowerMode(POWER_MODE_REASON_CHANGE_DISPLAY);
-
- mDisplayFrozen = true;
- mDisplayFreezeTime = SystemClock.elapsedRealtime();
- mLastFinishedFreezeSource = null;
-
- // {@link mDisplayFrozen} prevents us from freezing on multiple displays at the same time.
- // As a result, we only track the display that has initially froze the screen.
- mFrozenDisplayId = displayContent.getDisplayId();
-
- mInputManagerCallback.freezeInputDispatchingLw();
-
- if (displayContent.mAppTransition.isTransitionSet()) {
- displayContent.mAppTransition.freeze();
- }
-
- if (PROFILE_ORIENTATION) {
- File file = new File("/data/system/frozen");
- Debug.startMethodTracing(file.toString(), 8 * 1024 * 1024);
- }
-
- mLatencyTracker.onActionStart(ACTION_ROTATE_SCREEN);
- mExitAnimId = exitAnim;
- mEnterAnimId = enterAnim;
-
- final int originalRotation = overrideOriginalRotation != ROTATION_UNDEFINED
- ? overrideOriginalRotation
- : displayContent.getDisplayInfo().rotation;
- displayContent.setRotationAnimation(new ScreenRotationAnimation(displayContent,
- originalRotation));
- }
-
- void stopFreezingDisplayLocked() {
- if (!mDisplayFrozen) {
- return;
- }
-
- final DisplayContent displayContent = mRoot.getDisplayContent(mFrozenDisplayId);
- final int numOpeningApps;
- final boolean waitingForConfig;
- final boolean waitingForRemoteDisplayChange;
- if (displayContent != null) {
- numOpeningApps = displayContent.mOpeningApps.size();
- waitingForConfig = displayContent.mWaitingForConfig;
- waitingForRemoteDisplayChange = displayContent.mRemoteDisplayChangeController
- .isWaitingForRemoteDisplayChange();
- } else {
- waitingForConfig = waitingForRemoteDisplayChange = false;
- numOpeningApps = 0;
- }
- if (waitingForConfig || waitingForRemoteDisplayChange || mAppsFreezingScreen > 0
- || mWindowsFreezingScreen == WINDOWS_FREEZING_SCREENS_ACTIVE
- || numOpeningApps > 0) {
- ProtoLog.d(WM_DEBUG_ORIENTATION, "stopFreezingDisplayLocked: Returning "
- + "waitingForConfig=%b, waitingForRemoteDisplayChange=%b, "
- + "mAppsFreezingScreen=%d, mWindowsFreezingScreen=%d, "
- + "mOpeningApps.size()=%d",
- waitingForConfig, waitingForRemoteDisplayChange,
- mAppsFreezingScreen, mWindowsFreezingScreen,
- numOpeningApps);
- return;
- }
-
- Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "WMS.doStopFreezingDisplayLocked-"
- + mLastFinishedFreezeSource);
- doStopFreezingDisplayLocked(displayContent);
- Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
- }
-
- private void doStopFreezingDisplayLocked(DisplayContent displayContent) {
- ProtoLog.d(WM_DEBUG_ORIENTATION,
- "stopFreezingDisplayLocked: Unfreezing now");
-
- // We must make a local copy of the displayId as it can be potentially overwritten later on
- // in this method. For example, {@link startFreezingDisplayLocked} may be called as a result
- // of update rotation, but we reference the frozen display after that call in this method.
- mFrozenDisplayId = INVALID_DISPLAY;
- mDisplayFrozen = false;
- mInputManagerCallback.thawInputDispatchingLw();
- mLastDisplayFreezeDuration = (int)(SystemClock.elapsedRealtime() - mDisplayFreezeTime);
- StringBuilder sb = new StringBuilder(128);
- sb.append("Screen frozen for ");
- TimeUtils.formatDuration(mLastDisplayFreezeDuration, sb);
- if (mLastFinishedFreezeSource != null) {
- sb.append(" due to ");
- sb.append(mLastFinishedFreezeSource);
- }
- ProtoLog.i(WM_ERROR, "%s", sb.toString());
- mH.removeMessages(H.APP_FREEZE_TIMEOUT);
- if (PROFILE_ORIENTATION) {
- Debug.stopMethodTracing();
- }
-
- boolean updateRotation = false;
-
- ScreenRotationAnimation screenRotationAnimation = displayContent == null ? null
- : displayContent.getRotationAnimation();
- if (screenRotationAnimation != null && screenRotationAnimation.hasScreenshot()) {
- ProtoLog.i(WM_DEBUG_ORIENTATION, "**** Dismissing screen rotation animation");
- DisplayInfo displayInfo = displayContent.getDisplayInfo();
- // Get rotation animation again, with new top window
- if (!displayContent.getDisplayRotation().validateRotationAnimation(
- mExitAnimId, mEnterAnimId, false /* forceDefault */)) {
- mExitAnimId = mEnterAnimId = 0;
- }
- if (screenRotationAnimation.dismiss(mTransaction, MAX_ANIMATION_DURATION,
- getTransitionAnimationScaleLocked(), displayInfo.logicalWidth,
- displayInfo.logicalHeight, mExitAnimId, mEnterAnimId)) {
- mTransaction.apply();
- } else {
- screenRotationAnimation.kill();
- displayContent.setRotationAnimation(null);
- updateRotation = true;
- }
- } else {
- if (screenRotationAnimation != null) {
- screenRotationAnimation.kill();
- displayContent.setRotationAnimation(null);
- }
- updateRotation = true;
- }
-
- boolean configChanged;
-
- // While the display is frozen we don't re-compute the orientation
- // to avoid inconsistent states. However, something interesting
- // could have actually changed during that time so re-evaluate it
- // now to catch that.
- configChanged = displayContent != null && displayContent.updateOrientation();
-
- mScreenFrozenLock.release();
-
- if (updateRotation && displayContent != null) {
- ProtoLog.d(WM_DEBUG_ORIENTATION, "Performing post-rotate rotation");
- configChanged |= displayContent.updateRotationUnchecked();
- }
-
- if (configChanged) {
- displayContent.sendNewConfiguration();
- }
- mAtmService.endPowerMode(POWER_MODE_REASON_CHANGE_DISPLAY);
- mLatencyTracker.onActionEnd(ACTION_ROTATE_SCREEN);
- }
-
static int getPropertyInt(String[] tokens, int index, int defUnits, int defDps,
DisplayMetrics dm) {
if (index < tokens.length) {
@@ -6876,7 +6618,6 @@ public class WindowManagerService extends IWindowManager.Stub
if (imeWindow != null) {
imeWindow.writeIdentifierToProto(proto, INPUT_METHOD_WINDOW);
}
- proto.write(DISPLAY_FROZEN, mDisplayFrozen);
proto.write(FOCUSED_DISPLAY_ID, topFocusedDisplayContent.getDisplayId());
proto.write(HARD_KEYBOARD_AVAILABLE, mHardKeyboardAvailable);
@@ -6999,13 +6740,6 @@ public class WindowManagerService extends IWindowManager.Stub
pw.print(' '); pw.print(dc.mMinSizeOfResizeableTaskDp);
});
pw.print(" mBlurEnabled="); pw.println(mBlurController.getBlurEnabled());
- pw.print(" mLastDisplayFreezeDuration=");
- TimeUtils.formatDuration(mLastDisplayFreezeDuration, pw);
- if ( mLastFinishedFreezeSource != null) {
- pw.print(" due to ");
- pw.print(mLastFinishedFreezeSource);
- }
- pw.println();
pw.print(" mDisableSecureWindows="); pw.println(mDisableSecureWindows);
mInputManagerCallback.dump(pw, " ");
@@ -7025,9 +6759,6 @@ public class WindowManagerService extends IWindowManager.Stub
mRoot.dumpLayoutNeededDisplayIds(pw);
pw.print(" mTransactionSequence="); pw.println(mTransactionSequence);
- pw.print(" mDisplayFrozen="); pw.print(mDisplayFrozen);
- pw.print(" windows="); pw.print(mWindowsFreezingScreen);
- pw.print(" apps="); pw.println(mAppsFreezingScreen);
final DisplayContent defaultDisplayContent = getDefaultDisplayContentLocked();
pw.print(" mRotation="); pw.println(defaultDisplayContent.getRotation());
pw.print(" mLastOrientation=");
@@ -8905,16 +8636,6 @@ public class WindowManagerService extends IWindowManager.Stub
}
}
- void registerAppFreezeListener(AppFreezeListener listener) {
- if (!mAppFreezeListeners.contains(listener)) {
- mAppFreezeListeners.add(listener);
- }
- }
-
- void unregisterAppFreezeListener(AppFreezeListener listener) {
- mAppFreezeListeners.remove(listener);
- }
-
/** Called to inform window manager if non-Vr UI shoul be disabled or not. */
public void disableNonVrUi(boolean disable) {
synchronized (mGlobalLock) {
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 3c3a180f4da1..a1755e4d9d3b 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -52,6 +52,7 @@ 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;
import static android.window.WindowContainerTransaction.Change.CHANGE_RELATIVE_BOUNDS;
+import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REMOVE_ROOT_TASK;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_KEYGUARD_STATE;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_ADD_INSETS_FRAME_PROVIDER;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_ADD_TASK_FRAGMENT_OPERATION;
@@ -1131,6 +1132,23 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
}
break;
}
+ case HIERARCHY_OP_TYPE_REMOVE_ROOT_TASK: {
+ final WindowContainer wc = WindowContainer.fromBinder(hop.getContainer());
+ if (wc == null || wc.asTask() == null || !wc.isAttached()
+ || !wc.asTask().isRootTask() || !wc.asTask().mCreatedByOrganizer) {
+ Slog.e(TAG, "Attempt to remove invalid task: " + wc);
+ break;
+ }
+ final Task task = wc.asTask();
+ if (task.isVisibleRequested() || task.isVisible()) {
+ effects |= TRANSACT_EFFECTS_LIFECYCLE;
+ }
+ // Removes its leaves, but not itself.
+ mService.mTaskSupervisor.removeRootTask(task);
+ // Now that the root has no leaves, remove it too. .
+ task.remove(true /* withTransition */, "remove-root-task-through-hierarchyOp");
+ break;
+ }
case HIERARCHY_OP_TYPE_SET_LAUNCH_ROOT: {
final WindowContainer wc = WindowContainer.fromBinder(hop.getContainer());
if (wc == null || !wc.isAttached()) {
@@ -1380,7 +1398,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
break;
}
case HIERARCHY_OP_TYPE_RESTORE_TRANSIENT_ORDER: {
- if (!com.android.wm.shell.Flags.enableShellTopTaskTracking()) {
+ if (!com.android.wm.shell.Flags.enableRecentsBookendTransition()) {
// Only allow restoring transient order when finishing a transition
if (!chain.isFinishing()) break;
}
@@ -1416,7 +1434,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
final TaskDisplayArea taskDisplayArea = thisTask.getTaskDisplayArea();
taskDisplayArea.moveRootTaskBehindRootTask(thisTask.getRootTask(), restoreAt);
- if (com.android.wm.shell.Flags.enableShellTopTaskTracking()) {
+ if (com.android.wm.shell.Flags.enableRecentsBookendTransition()) {
// Because we are in a transient launch transition, the requested visibility of
// tasks does not actually change for the transient-hide tasks, but we do want
// the restoration of these transient-hide tasks to top to be a part of this
diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java
index 26bc09f8aa9c..30dde543b9d4 100644
--- a/services/core/java/com/android/server/wm/WindowProcessController.java
+++ b/services/core/java/com/android/server/wm/WindowProcessController.java
@@ -969,14 +969,8 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio
return canUpdate;
}
+ // TODO(365884835): remove this method with external callers.
public void stopFreezingActivities() {
- synchronized (mAtm.mGlobalLock) {
- int i = mActivities.size();
- while (i > 0) {
- i--;
- mActivities.get(i).stopFreezingScreen(true /* unfreezeNow */, true /* force */);
- }
- }
}
void finishActivities() {
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index da58470edc1e..a8f22eaeabb8 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -218,7 +218,6 @@ import android.util.DisplayMetrics;
import android.util.MergedConfiguration;
import android.util.Slog;
import android.util.SparseArray;
-import android.util.TimeUtils;
import android.util.proto.ProtoOutputStream;
import android.view.Display;
import android.view.DisplayInfo;
@@ -366,7 +365,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
boolean mPermanentlyHidden; // the window should never be shown again
// This is a non-system overlay window that is currently force hidden.
private boolean mForceHideNonSystemOverlayWindow;
- boolean mAppFreezing;
boolean mHidden = true; // Used to determine if to show child windows.
private boolean mDragResizing;
private boolean mDragResizingChangeReported = true;
@@ -601,11 +599,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
*/
int mLastVisibleLayoutRotation = -1;
- /**
- * How long we last kept the screen frozen.
- */
- int mLastFreezeDuration;
-
/** Is this window now (or just being) removed? */
boolean mRemoved;
@@ -1475,7 +1468,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
consumeInsetsChange();
onResizeHandled();
- mWmService.makeWindowFreezingScreenIfNeededLocked(this);
// Reset the drawn state if the window need to redraw for the change, so the transition
// can wait until it has finished drawing to start.
@@ -1700,7 +1692,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
@Override
boolean hasContentToDisplay() {
- if (!mAppFreezing && isDrawn() && (mViewVisibility == View.VISIBLE
+ if (!isDrawn() && (mViewVisibility == View.VISIBLE
|| (isAnimating(TRANSITION | PARENTS)
&& !getDisplayContent().mAppTransition.isTransitionSet()))) {
return true;
@@ -1912,7 +1904,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
*/
boolean isInteresting() {
return mActivityRecord != null
- && (!mActivityRecord.isFreezingScreen() || !mAppFreezing)
&& mViewVisibility == View.VISIBLE;
}
@@ -2398,12 +2389,12 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
ProtoLog.v(WM_DEBUG_APP_TRANSITIONS,
"Remove %s: mSurfaceControl=%s mAnimatingExit=%b mRemoveOnExit=%b "
+ "mHasSurface=%b surfaceShowing=%b animating=%b app-animation=%b "
- + "mDisplayFrozen=%b callers=%s",
+ + "callers=%s",
this, mWinAnimator.mSurfaceControl, mAnimatingExit, mRemoveOnExit,
mHasSurface, mWinAnimator.getShown(),
isAnimating(TRANSITION | PARENTS),
mActivityRecord != null && mActivityRecord.isAnimating(PARENTS | TRANSITION),
- mWmService.mDisplayFrozen, Debug.getCallers(6));
+ Debug.getCallers(6));
// First, see if we need to run an animation. If we do, we have to hold off on removing the
// window until the animation is done. If the display is frozen, just remove immediately,
@@ -3266,32 +3257,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
}
}
- void onStartFreezingScreen() {
- mAppFreezing = true;
- for (int i = mChildren.size() - 1; i >= 0; --i) {
- final WindowState c = mChildren.get(i);
- c.onStartFreezingScreen();
- }
- }
-
- boolean onStopFreezingScreen() {
- boolean unfrozeWindows = false;
- for (int i = mChildren.size() - 1; i >= 0; --i) {
- final WindowState c = mChildren.get(i);
- unfrozeWindows |= c.onStopFreezingScreen();
- }
-
- if (!mAppFreezing) {
- return unfrozeWindows;
- }
-
- mAppFreezing = false;
-
- mLastFreezeDuration = 0;
- setDisplayLayoutNeeded();
- return true;
- }
-
boolean destroySurface(boolean cleanupOnResume, boolean appStopped) {
boolean destroyedSomething = false;
@@ -4192,16 +4157,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
+ " mDestroying=" + mDestroying
+ " mRemoved=" + mRemoved);
}
- if (mAppFreezing) {
- pw.println(prefix + " configOrientationChanging="
- + (getLastReportedConfiguration().orientation != getConfiguration().orientation)
- + " mAppFreezing=" + mAppFreezing);
- }
- if (mLastFreezeDuration != 0) {
- pw.print(prefix + "mLastFreezeDuration=");
- TimeUtils.formatDuration(mLastFreezeDuration, pw);
- pw.println();
- }
pw.print(prefix + "mForceSeamlesslyRotate=" + mForceSeamlesslyRotate
+ " seamlesslyRotate: pending=");
if (mPendingSeamlessRotate != null) {
@@ -4882,7 +4837,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
c.updateReportedVisibility(results);
}
- if (mAppFreezing || mViewVisibility != View.VISIBLE
+ if (mViewVisibility != View.VISIBLE
|| mAttrs.type == TYPE_APPLICATION_STARTING
|| mDestroying) {
return;
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index 298580e4bb81..1d8d867f8dcb 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -303,8 +303,6 @@ class WindowStateAnimator {
resetDrawState();
- mService.makeWindowFreezingScreenIfNeededLocked(w);
-
int flags = SurfaceControl.HIDDEN;
final WindowManager.LayoutParams attrs = w.mAttrs;
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index 911c686c711f..e1f3f0ef5615 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -357,6 +357,7 @@ public:
void setTouchpadRightClickZoneEnabled(bool enabled);
void setTouchpadThreeFingerTapShortcutEnabled(bool enabled);
void setTouchpadSystemGesturesEnabled(bool enabled);
+ void setTouchpadAccelerationEnabled(bool enabled);
void setInputDeviceEnabled(uint32_t deviceId, bool enabled);
void setShowTouches(bool enabled);
void setNonInteractiveDisplays(const std::set<ui::LogicalDisplayId>& displayIds);
@@ -540,6 +541,10 @@ private:
// True to enable system gestures (three- and four-finger swipes) on touchpads.
bool touchpadSystemGesturesEnabled{true};
+ // True if the speed of the pointer will increase as the user moves
+ // their finger faster on the touchpad.
+ bool touchpadAccelerationEnabled{true};
+
// True if a pointer icon should be shown for stylus pointers.
bool stylusPointerIconEnabled{false};
@@ -869,6 +874,7 @@ void NativeInputManager::getReaderConfiguration(InputReaderConfiguration* outCon
outConfig->touchpadThreeFingerTapShortcutEnabled =
mLocked.touchpadThreeFingerTapShortcutEnabled;
outConfig->touchpadSystemGesturesEnabled = mLocked.touchpadSystemGesturesEnabled;
+ outConfig->touchpadAccelerationEnabled = mLocked.touchpadAccelerationEnabled;
outConfig->disabledDevices = mLocked.disabledInputDevices;
@@ -1666,6 +1672,21 @@ void NativeInputManager::setTouchpadSystemGesturesEnabled(bool enabled) {
InputReaderConfiguration::Change::TOUCHPAD_SETTINGS);
}
+void NativeInputManager::setTouchpadAccelerationEnabled(bool enabled) {
+ { // acquire lock
+ std::scoped_lock _l(mLock);
+
+ if (mLocked.touchpadAccelerationEnabled == enabled) {
+ return;
+ }
+
+ mLocked.touchpadAccelerationEnabled = enabled;
+ } // release lock
+
+ mInputManager->getReader().requestRefreshConfiguration(
+ InputReaderConfiguration::Change::TOUCHPAD_SETTINGS);
+}
+
void NativeInputManager::setInputDeviceEnabled(uint32_t deviceId, bool enabled) {
bool refresh = false;
@@ -2644,6 +2665,13 @@ static void nativeSetTouchpadSystemGesturesEnabled(JNIEnv* env, jobject nativeIm
im->setTouchpadSystemGesturesEnabled(enabled);
}
+static void nativeSetTouchpadAccelerationEnabled(JNIEnv* env, jobject nativeImplObj,
+ jboolean enabled) {
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
+
+ im->setTouchpadAccelerationEnabled(enabled);
+}
+
static void nativeSetShowTouches(JNIEnv* env, jobject nativeImplObj, jboolean enabled) {
NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
@@ -3354,6 +3382,7 @@ static const JNINativeMethod gInputManagerMethods[] = {
{"setTouchpadThreeFingerTapShortcutEnabled", "(Z)V",
(void*)nativeSetTouchpadThreeFingerTapShortcutEnabled},
{"setTouchpadSystemGesturesEnabled", "(Z)V", (void*)nativeSetTouchpadSystemGesturesEnabled},
+ {"setTouchpadAccelerationEnabled", "(Z)V", (void*)nativeSetTouchpadAccelerationEnabled},
{"setShowTouches", "(Z)V", (void*)nativeSetShowTouches},
{"setNonInteractiveDisplays", "([I)V", (void*)nativeSetNonInteractiveDisplays},
{"reloadCalibration", "()V", (void*)nativeReloadCalibration},
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java b/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java
index 76d16e19e774..a81a0b3251c8 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java
@@ -73,9 +73,6 @@ import java.util.stream.Collectors;
class ActiveAdmin {
- private final int userId;
- public final boolean isPermissionBased;
-
private static final String TAG_DISABLE_KEYGUARD_FEATURES = "disable-keyguard-features";
private static final String TAG_TEST_ONLY_ADMIN = "test-only-admin";
private static final String TAG_DISABLE_CAMERA = "disable-camera";
@@ -364,23 +361,8 @@ class ActiveAdmin {
private static final int PROVISIONING_CONTEXT_LENGTH_LIMIT = 1000;
ActiveAdmin(DeviceAdminInfo info, boolean isParent) {
- this.userId = -1;
this.info = info;
this.isParent = isParent;
- this.isPermissionBased = false;
- }
-
- ActiveAdmin(int userId, boolean permissionBased) {
- if (Flags.activeAdminCleanup()) {
- throw new UnsupportedOperationException("permission based admin no longer supported");
- }
- if (permissionBased == false) {
- throw new IllegalArgumentException("Can only pass true for permissionBased admin");
- }
- this.userId = userId;
- this.isPermissionBased = permissionBased;
- this.isParent = false;
- this.info = null;
}
ActiveAdmin getParentActiveAdmin() {
@@ -397,16 +379,10 @@ class ActiveAdmin {
}
int getUid() {
- if (isPermissionBased) {
- return -1;
- }
return info.getActivityInfo().applicationInfo.uid;
}
public UserHandle getUserHandle() {
- if (isPermissionBased) {
- return UserHandle.of(userId);
- }
return UserHandle.of(UserHandle.getUserId(info.getActivityInfo().applicationInfo.uid));
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyData.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyData.java
index c937e10a28ce..89c8b560ea95 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyData.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyData.java
@@ -21,7 +21,6 @@ import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.app.admin.DeviceAdminInfo;
import android.app.admin.DevicePolicyManager;
-import android.app.admin.flags.Flags;
import android.content.ComponentName;
import android.os.FileUtils;
import android.os.PersistableBundle;
@@ -125,24 +124,6 @@ class DevicePolicyData {
final ArrayList<ActiveAdmin> mAdminList = new ArrayList<>();
final ArrayList<ComponentName> mRemovingAdmins = new ArrayList<>();
- /**
- * @deprecated Do not use. Policies set by permission holders must go into DevicePolicyEngine.
- */
- @Deprecated
- ActiveAdmin mPermissionBasedAdmin;
-
- // Create or get the permission-based admin. The permission-based admin will not have a
- // DeviceAdminInfo or ComponentName.
- ActiveAdmin createOrGetPermissionBasedAdmin(int userId) {
- if (Flags.activeAdminCleanup()) {
- throw new UnsupportedOperationException("permission based admin no longer supported");
- }
- if (mPermissionBasedAdmin == null) {
- mPermissionBasedAdmin = new ActiveAdmin(userId, /* permissionBased= */ true);
- }
- return mPermissionBasedAdmin;
- }
-
// TODO(b/35385311): Keep track of metadata in TrustedCertificateStore instead.
final ArraySet<String> mAcceptedCaCertificates = new ArraySet<>();
@@ -282,12 +263,6 @@ class DevicePolicyData {
}
}
- if (!Flags.activeAdminCleanup() && policyData.mPermissionBasedAdmin != null) {
- out.startTag(null, "permission-based-admin");
- policyData.mPermissionBasedAdmin.writeToXml(out);
- out.endTag(null, "permission-based-admin");
- }
-
if (policyData.mPasswordOwner >= 0) {
out.startTag(null, "password-owner");
out.attributeInt(null, "value", policyData.mPasswordOwner);
@@ -495,7 +470,6 @@ class DevicePolicyData {
policy.mLockTaskPackages.clear();
policy.mAdminList.clear();
policy.mAdminMap.clear();
- policy.mPermissionBasedAdmin = null;
policy.mAffiliationIds.clear();
policy.mOwnerInstalledCaCerts.clear();
policy.mUserControlDisabledPackages = null;
@@ -523,11 +497,6 @@ class DevicePolicyData {
} catch (RuntimeException e) {
Slogf.w(TAG, e, "Failed loading admin %s", name);
}
- } else if (!Flags.activeAdminCleanup() && "permission-based-admin".equals(tag)) {
-
- ActiveAdmin ap = new ActiveAdmin(policy.mUserId, /* permissionBased= */ true);
- ap.readFromXml(parser, /* overwritePolicies= */ false);
- policy.mPermissionBasedAdmin = ap;
} else if ("delegation".equals(tag)) {
// Parse delegation info.
final String delegatePackage = parser.getAttributeValue(null,
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index e960abd3b313..4c2c858c86de 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -3448,8 +3448,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
EnforcingAdmin enforcingAdmin =
EnforcingAdmin.createEnterpriseEnforcingAdmin(
admin.info.getComponent(),
- admin.getUserHandle().getIdentifier(),
- admin);
+ admin.getUserHandle().getIdentifier()
+ );
mDevicePolicyEngine.setGlobalPolicy(
PolicyDefinition.SECURITY_LOGGING,
enforcingAdmin,
@@ -3692,8 +3692,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
int userId = admin.getUserHandle().getIdentifier();
EnforcingAdmin enforcingAdmin = EnforcingAdmin.createEnterpriseEnforcingAdmin(
admin.info.getComponent(),
- userId,
- admin);
+ userId
+ );
Integer passwordComplexity = mDevicePolicyEngine.getLocalPolicySetByAdmin(
PolicyDefinition.PASSWORD_COMPLEXITY,
@@ -3985,8 +3985,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
final int N = admins.size();
for (int i = 0; i < N; i++) {
ActiveAdmin admin = admins.get(i);
- if (((!Flags.activeAdminCleanup() && admin.isPermissionBased)
- || admin.info.usesPolicy(DeviceAdminInfo.USES_POLICY_EXPIRE_PASSWORD))
+ if ((admin.info.usesPolicy(DeviceAdminInfo.USES_POLICY_EXPIRE_PASSWORD))
&& admin.passwordExpirationTimeout > 0L
&& now >= admin.passwordExpirationDate - EXPIRATION_GRACE_PERIOD_MS
&& admin.passwordExpirationDate > 0L) {
@@ -4167,10 +4166,10 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
EnforcingAdmin oldAdmin =
EnforcingAdmin.createEnterpriseEnforcingAdmin(
- outgoingReceiver, userHandle, adminToTransfer);
+ outgoingReceiver, userHandle);
EnforcingAdmin newAdmin =
EnforcingAdmin.createEnterpriseEnforcingAdmin(
- incomingReceiver, userHandle, adminToTransfer);
+ incomingReceiver, userHandle);
mDevicePolicyEngine.transferPolicies(oldAdmin, newAdmin);
@@ -4470,7 +4469,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
mDevicePolicyEngine.removePoliciesForAdmin(
EnforcingAdmin.createEnterpriseEnforcingAdmin(
- adminReceiver, userHandle, admin));
+ adminReceiver, userHandle));
}
private boolean canSetPasswordQualityOnParent(String packageName, final CallerIdentity caller) {
@@ -4525,10 +4524,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
who, DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD, parent);
if (Flags.unmanagedModeMigration()) {
- enforcingAdmin = EnforcingAdmin.createEnterpriseEnforcingAdmin(who,
- userId,
- getActiveAdminForCallerLocked(who,
- DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD));
+ getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD);
+ enforcingAdmin = EnforcingAdmin.createEnterpriseEnforcingAdmin(who, userId);
}
// If setPasswordQuality is called on the parent, ensure that
// the primary admin does not have password complexity state (this is an
@@ -5584,17 +5581,13 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
Preconditions.checkArgument(!calledOnParent || isProfileOwner(caller));
final ActiveAdmin activeAdmin;
- if (Flags.activeAdminCleanup()) {
- if (admin.hasAuthority(EnforcingAdmin.DPC_AUTHORITY)) {
- synchronized (getLockObject()) {
- activeAdmin = getActiveAdminUncheckedLocked(
- admin.getComponentName(), admin.getUserId());
- }
- } else {
- activeAdmin = null;
+ if (admin.hasAuthority(EnforcingAdmin.DPC_AUTHORITY)) {
+ synchronized (getLockObject()) {
+ activeAdmin = getActiveAdminUncheckedLocked(
+ admin.getComponentName(), admin.getUserId());
}
} else {
- activeAdmin = admin.getActiveAdmin();
+ activeAdmin = null;
}
// We require the caller to explicitly clear any password quality requirements set
@@ -6331,12 +6324,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
caller.getPackageName(),
getAffectedUser(parent)
);
- if (Flags.activeAdminCleanup()) {
- adminComponent = enforcingAdmin.getComponentName();
- } else {
- ActiveAdmin admin = enforcingAdmin.getActiveAdmin();
- adminComponent = admin == null ? null : admin.info.getComponent();
- }
+ adminComponent = enforcingAdmin.getComponentName();
} else {
ActiveAdmin admin = getActiveAdminOrCheckPermissionForCallerLocked(
null,
@@ -7824,19 +7812,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
calledByProfileOwnerOnOrgOwnedDevice, calledOnParentInstance);
}
- int userId;
- ActiveAdmin admin = null;
- if (Flags.activeAdminCleanup()) {
- userId = enforcingAdmin.getUserId();
- Slogf.i(LOG_TAG, "wipeDataWithReason(%s): admin=%s, user=%d", wipeReasonForUser,
- enforcingAdmin, userId);
- } else {
- admin = enforcingAdmin.getActiveAdmin();
- userId = admin != null ? admin.getUserHandle().getIdentifier()
- : caller.getUserId();
- Slogf.i(LOG_TAG, "wipeDataWithReason(%s): admin=%s, user=%d", wipeReasonForUser, admin,
- userId);
- }
+ int userId = enforcingAdmin.getUserId();
+ Slogf.i(LOG_TAG, "wipeDataWithReason(%s): admin=%s, user=%d", wipeReasonForUser,
+ enforcingAdmin, userId);
if (calledByProfileOwnerOnOrgOwnedDevice) {
// When wipeData is called on the parent instance, it implies wiping the entire device.
@@ -7858,38 +7836,14 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
final String adminName;
final ComponentName adminComp;
- if (Flags.activeAdminCleanup()) {
- adminComp = enforcingAdmin.getComponentName();
- adminName = adminComp != null
- ? adminComp.flattenToShortString()
- : enforcingAdmin.getPackageName();
- event.setAdmin(enforcingAdmin.getPackageName());
- // Not including any HSUM handling here because the "else" branch in the "flag off"
- // case below is unreachable under normal circumstances and for permission-based
- // callers admin won't be null.
- } else {
- if (admin != null) {
- if (admin.isPermissionBased) {
- adminComp = null;
- adminName = caller.getPackageName();
- event.setAdmin(adminName);
- } else {
- adminComp = admin.info.getComponent();
- adminName = adminComp.flattenToShortString();
- event.setAdmin(adminComp);
- }
- } else {
- adminComp = null;
- adminName = mInjector.getPackageManager().getPackagesForUid(caller.getUid())[0];
- Slogf.i(LOG_TAG, "Logging wipeData() event admin as " + adminName);
- event.setAdmin(adminName);
- if (mInjector.userManagerIsHeadlessSystemUserMode()) {
- // On headless system user mode, the call is meant to factory reset the whole
- // device, otherwise the caller could simply remove the current user.
- userId = UserHandle.USER_SYSTEM;
- }
- }
- }
+ adminComp = enforcingAdmin.getComponentName();
+ adminName = adminComp != null
+ ? adminComp.flattenToShortString()
+ : enforcingAdmin.getPackageName();
+ event.setAdmin(enforcingAdmin.getPackageName());
+ // Not including any HSUM handling here because the "else" branch in the "flag off"
+ // case below is unreachable under normal circumstances and for permission-based
+ // callers admin won't be null.
event.write();
String internalReason = String.format(
@@ -8375,8 +8329,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
List<ActiveAdmin> admins = getActiveAdminsForLockscreenPoliciesLocked(userHandle);
for (int i = 0; i < admins.size(); i++) {
ActiveAdmin admin = admins.get(i);
- if ((!Flags.activeAdminCleanup() && admin.isPermissionBased)
- || admin.info.usesPolicy(DeviceAdminInfo.USES_POLICY_EXPIRE_PASSWORD)) {
+ if (admin.info.usesPolicy(DeviceAdminInfo.USES_POLICY_EXPIRE_PASSWORD)) {
affectedUserIds.add(admin.getUserHandle().getIdentifier());
long timeout = admin.passwordExpirationTimeout;
admin.passwordExpirationDate =
@@ -8470,9 +8423,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
*/
private int getUserIdToWipeForFailedPasswords(ActiveAdmin admin) {
final int userId = admin.getUserHandle().getIdentifier();
- if (!Flags.activeAdminCleanup() && admin.isPermissionBased) {
- return userId;
- }
final ComponentName component = admin.info.getComponent();
return isProfileOwnerOfOrganizationOwnedDevice(component, userId)
? getProfileParentId(userId) : userId;
@@ -10282,8 +10232,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
setGlobalSettingDeviceOwnerType(DEVICE_OWNER_TYPE_DEFAULT);
mDevicePolicyEngine.removePoliciesForAdmin(
- EnforcingAdmin.createEnterpriseEnforcingAdmin(
- admin.info.getComponent(), userId, admin));
+ EnforcingAdmin.createEnterpriseEnforcingAdmin(admin.info.getComponent(), userId));
}
private void clearApplicationRestrictions(int userId) {
@@ -10433,8 +10382,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
setNetworkLoggingActiveInternal(false);
mDevicePolicyEngine.removePoliciesForAdmin(
- EnforcingAdmin.createEnterpriseEnforcingAdmin(
- admin.info.getComponent(), userId, admin));
+ EnforcingAdmin.createEnterpriseEnforcingAdmin(admin.info.getComponent(), userId));
}
@Override
@@ -16449,8 +16397,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
if (admin.mPasswordPolicy.quality < minPasswordQuality) {
return false;
}
- return (!Flags.activeAdminCleanup() && admin.isPermissionBased)
- || admin.info.usesPolicy(DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD);
+ return admin.info.usesPolicy(DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD);
}
@Override
@@ -20918,8 +20865,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
if (profileOwner != null) {
EnforcingAdmin admin = EnforcingAdmin.createEnterpriseEnforcingAdmin(
profileOwner.info.getComponent(),
- profileUserId,
- profileOwner);
+ profileUserId);
mDevicePolicyEngine.setLocalPolicy(
PolicyDefinition.PERSONAL_APPS_SUSPENDED,
admin,
@@ -23517,27 +23463,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
*
* @param callerPackageName The package name of the calling application.
* @param adminPolicy The admin policy that should grant holders permission.
- * @param permission The name of the permission being checked.
- * @param targetUserId The userId of the user which the caller needs permission to act on.
- * @throws SecurityException if the caller has not been granted the given permission,
- * the associated cross-user permission if the caller's user is different to the target user.
- */
- private void enforcePermission(String permission, int adminPolicy,
- String callerPackageName, int targetUserId) throws SecurityException {
- if (hasAdminPolicy(adminPolicy, callerPackageName)) {
- return;
- }
- enforcePermission(permission, callerPackageName, targetUserId);
- }
-
- /**
- * Checks if the calling process has been granted permission to apply a device policy on a
- * specific user.
- * The given permission will be checked along with its associated cross-user permission if it
- * exists and the target user is different to the calling user.
- *
- * @param callerPackageName The package name of the calling application.
- * @param adminPolicy The admin policy that should grant holders permission.
* @param permissions The names of the permissions being checked.
* @param targetUserId The userId of the user which the caller needs permission to act on.
* @throws SecurityException if the caller has not been granted the given permission,
@@ -23670,24 +23595,21 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
ComponentName component;
synchronized (getLockObject()) {
if (who != null) {
- admin = getActiveAdminUncheckedLocked(who, userId);
component = who;
} else {
admin = getDeviceOrProfileOwnerAdminLocked(userId);
component = admin.info.getComponent();
}
}
- return EnforcingAdmin.createEnterpriseEnforcingAdmin(component, userId, admin);
+ return EnforcingAdmin.createEnterpriseEnforcingAdmin(component, userId);
}
- // Check for non-DPC active admins.
+ // Check for DA active admins.
admin = getActiveAdminForCaller(who, caller);
if (admin != null) {
- return EnforcingAdmin.createDeviceAdminEnforcingAdmin(admin.info.getComponent(), userId,
- admin);
+ return EnforcingAdmin.createDeviceAdminEnforcingAdmin(
+ admin.info.getComponent(), userId);
}
- admin = Flags.activeAdminCleanup()
- ? null : getUserData(userId).createOrGetPermissionBasedAdmin(userId);
- return EnforcingAdmin.createEnforcingAdmin(caller.getPackageName(), userId, admin);
+ return EnforcingAdmin.createEnforcingAdmin(caller.getPackageName(), userId);
}
private EnforcingAdmin getEnforcingAdminForPackage(@Nullable ComponentName who,
@@ -23699,19 +23621,17 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
admin = getActiveAdminUncheckedLocked(who, userId);
}
if (admin != null) {
- return EnforcingAdmin.createEnterpriseEnforcingAdmin(who, userId, admin);
+ return EnforcingAdmin.createEnterpriseEnforcingAdmin(who, userId);
}
} else {
- // Check for non-DPC active admins.
+ // Check for DA active admins.
admin = getActiveAdminUncheckedLocked(who, userId);
if (admin != null) {
- return EnforcingAdmin.createDeviceAdminEnforcingAdmin(who, userId, admin);
+ return EnforcingAdmin.createDeviceAdminEnforcingAdmin(who, userId);
}
}
}
- admin = Flags.activeAdminCleanup()
- ? null : getUserData(userId).createOrGetPermissionBasedAdmin(userId);
- return EnforcingAdmin.createEnforcingAdmin(packageName, userId, admin);
+ return EnforcingAdmin.createEnforcingAdmin(packageName, userId);
}
private int getAffectedUser(boolean calledOnParent) {
@@ -24427,9 +24347,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
&& admin.getParentActiveAdmin().disableScreenCapture))) {
EnforcingAdmin enforcingAdmin = EnforcingAdmin.createEnterpriseEnforcingAdmin(
- admin.info.getComponent(),
- admin.getUserHandle().getIdentifier(),
- admin);
+ admin.info.getComponent(), admin.getUserHandle().getIdentifier());
mDevicePolicyEngine.setGlobalPolicy(
PolicyDefinition.SCREEN_CAPTURE_DISABLED,
enforcingAdmin,
@@ -24442,8 +24360,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
if (profileOwner != null && profileOwner.disableScreenCapture) {
EnforcingAdmin enforcingAdmin = EnforcingAdmin.createEnterpriseEnforcingAdmin(
profileOwner.info.getComponent(),
- profileOwner.getUserHandle().getIdentifier(),
- profileOwner);
+ profileOwner.getUserHandle().getIdentifier());
mDevicePolicyEngine.setLocalPolicy(
PolicyDefinition.SCREEN_CAPTURE_DISABLED,
enforcingAdmin,
@@ -24485,10 +24402,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
private void setLockTaskPolicyInPolicyEngine(
ActiveAdmin admin, int userId, List<String> packages, int features) {
EnforcingAdmin enforcingAdmin =
- EnforcingAdmin.createEnterpriseEnforcingAdmin(
- admin.info.getComponent(),
- userId,
- admin);
+ EnforcingAdmin.createEnterpriseEnforcingAdmin(admin.info.getComponent(), userId);
mDevicePolicyEngine.setLocalPolicy(
PolicyDefinition.LOCK_TASK,
enforcingAdmin,
@@ -24503,9 +24417,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(userInfo.id);
if (admin != null) {
EnforcingAdmin enforcingAdmin = EnforcingAdmin.createEnterpriseEnforcingAdmin(
- admin.info.getComponent(),
- admin.getUserHandle().getIdentifier(),
- admin);
+ admin.info.getComponent(), admin.getUserHandle().getIdentifier());
if (admin.permittedInputMethods != null) {
mDevicePolicyEngine.setLocalPolicy(
PolicyDefinition.PERMITTED_INPUT_METHODS,
@@ -24536,9 +24448,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(userInfo.id);
if (admin != null) {
EnforcingAdmin enforcingAdmin = EnforcingAdmin.createEnterpriseEnforcingAdmin(
- admin.info.getComponent(),
- admin.getUserHandle().getIdentifier(),
- admin);
+ admin.info.getComponent(), admin.getUserHandle().getIdentifier());
for (String accountType : admin.accountTypesWithManagementDisabled) {
mDevicePolicyEngine.setLocalPolicy(
PolicyDefinition.ACCOUNT_MANAGEMENT_DISABLED(accountType),
@@ -24569,9 +24479,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(userInfo.id);
if (admin != null && admin.protectedPackages != null) {
EnforcingAdmin enforcingAdmin = EnforcingAdmin.createEnterpriseEnforcingAdmin(
- admin.info.getComponent(),
- admin.getUserHandle().getIdentifier(),
- admin);
+ admin.info.getComponent(), admin.getUserHandle().getIdentifier());
if (isDeviceOwner(admin)) {
mDevicePolicyEngine.setGlobalPolicy(
PolicyDefinition.USER_CONTROLLED_DISABLED_PACKAGES,
@@ -24599,10 +24507,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
if (admin == null) continue;
ComponentName adminComponent = admin.info.getComponent();
int userId = userInfo.id;
- EnforcingAdmin enforcingAdmin = EnforcingAdmin.createEnterpriseEnforcingAdmin(
- adminComponent,
- userId,
- admin);
+ EnforcingAdmin enforcingAdmin =
+ EnforcingAdmin.createEnterpriseEnforcingAdmin(adminComponent, userId);
int ownerType;
if (isDeviceOwner(admin)) {
ownerType = OWNER_TYPE_DEVICE_OWNER;
@@ -24635,9 +24541,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(userInfo.id);
if (admin == null) continue;
EnforcingAdmin enforcingAdmin = EnforcingAdmin.createEnterpriseEnforcingAdmin(
- admin.info.getComponent(),
- userInfo.id,
- admin);
+ admin.info.getComponent(), userInfo.id);
runner.accept(admin, enforcingAdmin);
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/EnforcingAdmin.java b/services/devicepolicy/java/com/android/server/devicepolicy/EnforcingAdmin.java
index 5a0b079b6a24..aca331564a40 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/EnforcingAdmin.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/EnforcingAdmin.java
@@ -23,7 +23,6 @@ import android.app.admin.DeviceAdminAuthority;
import android.app.admin.DpcAuthority;
import android.app.admin.RoleAuthority;
import android.app.admin.UnknownAuthority;
-import android.app.admin.flags.Flags;
import android.content.ComponentName;
import android.os.UserHandle;
@@ -80,36 +79,24 @@ final class EnforcingAdmin {
private final int mUserId;
private final boolean mIsRoleAuthority;
private final boolean mIsSystemAuthority;
- private final ActiveAdmin mActiveAdmin;
- static EnforcingAdmin createEnforcingAdmin(@NonNull String packageName, int userId,
- ActiveAdmin admin) {
+ static EnforcingAdmin createEnforcingAdmin(@NonNull String packageName, int userId) {
Objects.requireNonNull(packageName);
- return new EnforcingAdmin(packageName, userId, admin);
+ return new EnforcingAdmin(packageName, userId);
}
static EnforcingAdmin createEnterpriseEnforcingAdmin(
@NonNull ComponentName componentName, int userId) {
Objects.requireNonNull(componentName);
return new EnforcingAdmin(
- componentName.getPackageName(), componentName, Set.of(DPC_AUTHORITY), userId,
- /* activeAdmin=*/ null);
+ componentName.getPackageName(), componentName, Set.of(DPC_AUTHORITY), userId);
}
- static EnforcingAdmin createEnterpriseEnforcingAdmin(
- @NonNull ComponentName componentName, int userId, ActiveAdmin activeAdmin) {
- Objects.requireNonNull(componentName);
- return new EnforcingAdmin(
- componentName.getPackageName(), componentName, Set.of(DPC_AUTHORITY), userId,
- activeAdmin);
- }
-
- static EnforcingAdmin createDeviceAdminEnforcingAdmin(ComponentName componentName, int userId,
- ActiveAdmin activeAdmin) {
+ static EnforcingAdmin createDeviceAdminEnforcingAdmin(ComponentName componentName, int userId) {
Objects.requireNonNull(componentName);
return new EnforcingAdmin(
componentName.getPackageName(), componentName, Set.of(DEVICE_ADMIN_AUTHORITY),
- userId, activeAdmin);
+ userId);
}
static EnforcingAdmin createSystemEnforcingAdmin(@NonNull String systemEntity) {
@@ -124,24 +111,20 @@ final class EnforcingAdmin {
if (DpcAuthority.DPC_AUTHORITY.equals(authority)) {
return new EnforcingAdmin(
admin.getPackageName(), admin.getComponentName(),
- Set.of(DPC_AUTHORITY), admin.getUserHandle().getIdentifier(),
- /* activeAdmin = */ null);
+ Set.of(DPC_AUTHORITY), admin.getUserHandle().getIdentifier());
} else if (DeviceAdminAuthority.DEVICE_ADMIN_AUTHORITY.equals(authority)) {
return new EnforcingAdmin(
admin.getPackageName(), admin.getComponentName(),
- Set.of(DEVICE_ADMIN_AUTHORITY), admin.getUserHandle().getIdentifier(),
- /* activeAdmin = */ null);
+ Set.of(DEVICE_ADMIN_AUTHORITY), admin.getUserHandle().getIdentifier());
} else if (authority instanceof RoleAuthority roleAuthority) {
return new EnforcingAdmin(
admin.getPackageName(), admin.getComponentName(),
Set.of(DEVICE_ADMIN_AUTHORITY), admin.getUserHandle().getIdentifier(),
- /* activeAdmin = */ null,
/* isRoleAuthority = */ true);
}
// TODO(b/324899199): Consider supporting android.app.admin.SystemAuthority.
return new EnforcingAdmin(admin.getPackageName(), admin.getComponentName(),
- Set.of(), admin.getUserHandle().getIdentifier(),
- /* activeAdmin = */ null);
+ Set.of(), admin.getUserHandle().getIdentifier());
}
static String getRoleAuthorityOf(String roleName) {
@@ -167,7 +150,7 @@ final class EnforcingAdmin {
private EnforcingAdmin(
String packageName, @Nullable ComponentName componentName, Set<String> authorities,
- int userId, @Nullable ActiveAdmin activeAdmin) {
+ int userId) {
Objects.requireNonNull(packageName);
Objects.requireNonNull(authorities);
@@ -179,10 +162,9 @@ final class EnforcingAdmin {
mComponentName = componentName;
mAuthorities = new HashSet<>(authorities);
mUserId = userId;
- mActiveAdmin = activeAdmin;
}
- private EnforcingAdmin(String packageName, int userId, ActiveAdmin activeAdmin) {
+ private EnforcingAdmin(String packageName, int userId) {
Objects.requireNonNull(packageName);
// Only role authorities use this constructor.
@@ -194,7 +176,6 @@ final class EnforcingAdmin {
mComponentName = null;
// authorities will be loaded when needed
mAuthorities = null;
- mActiveAdmin = activeAdmin;
}
/** Constructor for System authorities. */
@@ -210,12 +191,11 @@ final class EnforcingAdmin {
mUserId = UserHandle.USER_SYSTEM;
mComponentName = null;
mAuthorities = getSystemAuthority(systemEntity);
- mActiveAdmin = null;
}
private EnforcingAdmin(
String packageName, @Nullable ComponentName componentName, Set<String> authorities,
- int userId, @Nullable ActiveAdmin activeAdmin, boolean isRoleAuthority) {
+ int userId, boolean isRoleAuthority) {
Objects.requireNonNull(packageName);
Objects.requireNonNull(authorities);
@@ -226,7 +206,6 @@ final class EnforcingAdmin {
mComponentName = componentName;
mAuthorities = new HashSet<>(authorities);
mUserId = userId;
- mActiveAdmin = activeAdmin;
}
private static Set<String> getRoleAuthoritiesOrDefault(String packageName, int userId) {
@@ -295,14 +274,6 @@ final class EnforcingAdmin {
}
@Nullable
- public ActiveAdmin getActiveAdmin() {
- if (Flags.activeAdminCleanup()) {
- throw new UnsupportedOperationException("getActiveAdmin() no longer supported");
- }
- return mActiveAdmin;
- }
-
- @Nullable
ComponentName getComponentName() {
return mComponentName;
}
@@ -419,7 +390,7 @@ final class EnforcingAdmin {
return null;
}
// TODO(b/281697976): load active admin
- return new EnforcingAdmin(packageName, userId, null);
+ return new EnforcingAdmin(packageName, userId);
} else if (isSystemAuthority) {
if (systemEntity == null) {
Slogf.wtf(TAG, "Error parsing EnforcingAdmin with SystemAuthority, "
@@ -439,7 +410,7 @@ final class EnforcingAdmin {
? null : new ComponentName(packageName, className);
Set<String> authorities = Set.of(authoritiesStr.split(ATTR_AUTHORITIES_SEPARATOR));
// TODO(b/281697976): load active admin
- return new EnforcingAdmin(packageName, componentName, authorities, userId, null);
+ return new EnforcingAdmin(packageName, componentName, authorities, userId);
}
}
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index fadab1f8832e..25e9f8a38f89 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -1527,6 +1527,8 @@ public final class SystemServer implements Dumpable {
boolean disableCameraService = SystemProperties.getBoolean("config.disable_cameraservice",
false);
+ boolean isDesktop = context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_PC);
+
boolean isWatch = RoSystemFeatures.hasFeatureWatch(context);
boolean isArc = context.getPackageManager().hasSystemFeature(
@@ -1656,7 +1658,7 @@ public final class SystemServer implements Dumpable {
t.traceEnd();
}
- if (!isTv) {
+ if (!isTv && !isDesktop) {
t.traceBegin("StartVibratorManagerService");
mSystemServiceManager.startService(VibratorManagerService.Lifecycle.class);
t.traceEnd();
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
index 02e5470e8673..e8b28ac19a85 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
@@ -23,6 +23,7 @@ import static android.Manifest.permission.CAPTURE_VIDEO_OUTPUT;
import static android.Manifest.permission.CONTROL_DISPLAY_BRIGHTNESS;
import static android.Manifest.permission.MANAGE_DISPLAYS;
import static android.Manifest.permission.MODIFY_USER_PREFERRED_DISPLAY_MODE;
+import static android.app.ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND;
import static android.hardware.display.DisplayManager.SWITCHING_TYPE_NONE;
import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED;
import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR;
@@ -2123,14 +2124,41 @@ public class DisplayManagerServiceTest {
}
}
+ @Test
+ public void test_displayChangedNotified_displayInfoFramerateOverridden() {
+ DisplayManagerService displayManager =
+ new DisplayManagerService(mContext, mShortMockedInjector);
+ DisplayManagerService.BinderService displayManagerBinderService =
+ displayManager.new BinderService();
+ when(mMockFlags.isFramerateOverrideTriggersRrCallbacksEnabled()).thenReturn(false);
+
+ registerDefaultDisplays(displayManager);
+ displayManager.onBootPhase(SystemService.PHASE_WAIT_FOR_DEFAULT_DISPLAY);
+
+ FakeDisplayDevice displayDevice = createFakeDisplayDevice(displayManager, new float[]{60f});
+ FakeDisplayManagerCallback callback = registerDisplayListenerCallback(displayManager,
+ displayManagerBinderService, displayDevice);
+
+ int myUid = Process.myUid();
+ updateFrameRateOverride(displayManager, displayDevice,
+ new DisplayEventReceiver.FrameRateOverride[]{
+ new DisplayEventReceiver.FrameRateOverride(myUid, 30f),
+ });
+ waitForIdleHandler(displayManager.getDisplayHandler());
+ assertThat(callback.receivedEvents()).contains(EVENT_DISPLAY_BASIC_CHANGED);
+ callback.clear();
+ }
+
/**
* Tests that there is a display change notification if the frame rate override
* list is updated.
*/
@Test
- public void testShouldNotifyChangeWhenDisplayInfoFrameRateOverrideChanged() {
+ public void test_refreshRateChangedNotified_displayInfoFramerateOverridden() {
+ when(mMockFlags.isFramerateOverrideTriggersRrCallbacksEnabled()).thenReturn(true);
+
DisplayManagerService displayManager =
- new DisplayManagerService(mContext, mShortMockedInjector);
+ new DisplayManagerService(mContext, mBasicInjector);
DisplayManagerService.BinderService displayManagerBinderService =
displayManager.new BinderService();
registerDefaultDisplays(displayManager);
@@ -2146,7 +2174,7 @@ public class DisplayManagerServiceTest {
new DisplayEventReceiver.FrameRateOverride(myUid, 30f),
});
waitForIdleHandler(displayManager.getDisplayHandler());
- assertThat(callback.receivedEvents()).contains(EVENT_DISPLAY_BASIC_CHANGED);
+ assertThat(callback.receivedEvents()).contains(EVENT_DISPLAY_REFRESH_RATE_CHANGED);
callback.clear();
updateFrameRateOverride(displayManager, displayDevice,
@@ -2155,7 +2183,7 @@ public class DisplayManagerServiceTest {
new DisplayEventReceiver.FrameRateOverride(1234, 30f),
});
waitForIdleHandler(displayManager.getDisplayHandler());
- assertThat(callback.receivedEvents()).doesNotContain(EVENT_DISPLAY_BASIC_CHANGED);
+ assertThat(callback.receivedEvents()).doesNotContain(EVENT_DISPLAY_REFRESH_RATE_CHANGED);
updateFrameRateOverride(displayManager, displayDevice,
new DisplayEventReceiver.FrameRateOverride[]{
@@ -2164,7 +2192,7 @@ public class DisplayManagerServiceTest {
new DisplayEventReceiver.FrameRateOverride(5678, 30f),
});
waitForIdleHandler(displayManager.getDisplayHandler());
- assertThat(callback.receivedEvents()).contains(EVENT_DISPLAY_BASIC_CHANGED);
+ assertThat(callback.receivedEvents()).contains(EVENT_DISPLAY_REFRESH_RATE_CHANGED);
callback.clear();
updateFrameRateOverride(displayManager, displayDevice,
@@ -2173,7 +2201,7 @@ public class DisplayManagerServiceTest {
new DisplayEventReceiver.FrameRateOverride(5678, 30f),
});
waitForIdleHandler(displayManager.getDisplayHandler());
- assertThat(callback.receivedEvents()).contains(EVENT_DISPLAY_BASIC_CHANGED);
+ assertThat(callback.receivedEvents()).contains(EVENT_DISPLAY_REFRESH_RATE_CHANGED);
callback.clear();
updateFrameRateOverride(displayManager, displayDevice,
@@ -2181,7 +2209,7 @@ public class DisplayManagerServiceTest {
new DisplayEventReceiver.FrameRateOverride(5678, 30f),
});
waitForIdleHandler(displayManager.getDisplayHandler());
- assertThat(callback.receivedEvents()).doesNotContain(EVENT_DISPLAY_BASIC_CHANGED);
+ assertThat(callback.receivedEvents()).doesNotContain(EVENT_DISPLAY_REFRESH_RATE_CHANGED);
}
/**
@@ -2317,6 +2345,29 @@ public class DisplayManagerServiceTest {
callback.clear();
}
+ @Test
+ public void test_doesNotNotifyRefreshRateChanged_whenAppInBackground() {
+ when(mMockFlags.isRefreshRateEventForForegroundAppsEnabled()).thenReturn(true);
+ DisplayManagerService displayManager =
+ new DisplayManagerService(mContext, mBasicInjector);
+ DisplayManagerService.BinderService displayManagerBinderService =
+ displayManager.new BinderService();
+ displayManager.windowManagerAndInputReady();
+ registerDefaultDisplays(displayManager);
+ displayManager.onBootPhase(SystemService.PHASE_WAIT_FOR_DEFAULT_DISPLAY);
+
+ FakeDisplayDevice displayDevice = createFakeDisplayDevice(displayManager, new float[]{60f});
+ FakeDisplayManagerCallback callback = registerDisplayListenerCallback(displayManager,
+ displayManagerBinderService, displayDevice);
+
+ when(mMockActivityManagerInternal.getUidProcessState(Process.myUid()))
+ .thenReturn(PROCESS_STATE_TRANSIENT_BACKGROUND);
+ updateRenderFrameRate(displayManager, displayDevice, 30f);
+ waitForIdleHandler(displayManager.getDisplayHandler());
+ assertEquals(0, callback.receivedEvents().size());
+ callback.clear();
+ }
+
/**
* Tests that the DisplayInfo is updated correctly with a render frame rate
*/
diff --git a/services/tests/mockingservicestests/src/com/android/server/power/ScreenUndimDetectorTest.java b/services/tests/mockingservicestests/src/com/android/server/power/ScreenUndimDetectorTest.java
index e8e1dacd6fcf..8ce05e2fa115 100644
--- a/services/tests/mockingservicestests/src/com/android/server/power/ScreenUndimDetectorTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/power/ScreenUndimDetectorTest.java
@@ -40,6 +40,7 @@ import com.android.modules.utils.testing.TestableDeviceConfig;
import org.junit.After;
import org.junit.Before;
+import org.junit.Ignore;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
@@ -329,6 +330,7 @@ public class ScreenUndimDetectorTest {
}
}
+ @Ignore("b/387389929")
@Test
public void recordScreenPolicy_otherTransitions_doesNotReset() {
DeviceConfig.setProperty(NAMESPACE_ATTENTION_MANAGER_SERVICE,
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AutoclickControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AutoclickControllerTest.java
new file mode 100644
index 000000000000..acce813ff659
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AutoclickControllerTest.java
@@ -0,0 +1,242 @@
+/*
+ * Copyright 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.accessibility;
+
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
+import static com.android.server.testutils.MockitoUtilsKt.eq;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.verify;
+import static org.testng.AssertJUnit.assertEquals;
+import static org.testng.AssertJUnit.assertNotNull;
+import static org.testng.AssertJUnit.assertNull;
+
+import android.content.Context;
+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.testing.AndroidTestingRunner;
+import android.testing.TestableContext;
+import android.testing.TestableLooper;
+import android.view.InputDevice;
+import android.view.MotionEvent;
+import android.view.WindowManager;
+import android.view.accessibility.AccessibilityManager;
+
+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;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+/** Test cases for {@link AutoclickController}. */
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+public class AutoclickControllerTest {
+
+ @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+ @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
+
+ @Rule
+ public TestableContext mTestableContext =
+ new TestableContext(getInstrumentation().getContext());
+
+ private TestableLooper mTestableLooper;
+ @Mock private AccessibilityTraceManager mMockTrace;
+ @Mock private WindowManager mMockWindowManager;
+ private AutoclickController mController;
+
+ @Before
+ public void setUp() {
+ mTestableLooper = TestableLooper.get(this);
+ mTestableContext.addMockSystemService(Context.WINDOW_SERVICE, mMockWindowManager);
+ mController =
+ new AutoclickController(mTestableContext, mTestableContext.getUserId(), mMockTrace);
+ }
+
+ @After
+ public void tearDown() {
+ mTestableLooper.processAllMessages();
+ }
+
+ @Test
+ public void onMotionEvent_lazyInitClickScheduler() {
+ assertNull(mController.mClickScheduler);
+
+ injectFakeMouseActionDownEvent();
+
+ assertNotNull(mController.mClickScheduler);
+ }
+
+ @Test
+ public void onMotionEvent_nonMouseSource_notInitClickScheduler() {
+ assertNull(mController.mClickScheduler);
+
+ injectFakeNonMouseActionDownEvent();
+
+ assertNull(mController.mClickScheduler);
+ }
+
+ @Test
+ public void onMotionEvent_lazyInitAutoclickSettingsObserver() {
+ assertNull(mController.mAutoclickSettingsObserver);
+
+ injectFakeMouseActionDownEvent();
+
+ assertNotNull(mController.mAutoclickSettingsObserver);
+ }
+
+ @Test
+ @EnableFlags(com.android.server.accessibility.Flags.FLAG_ENABLE_AUTOCLICK_INDICATOR)
+ public void onMotionEvent_flagOn_lazyInitAutoclickIndicatorScheduler() {
+ assertNull(mController.mAutoclickIndicatorScheduler);
+
+ injectFakeMouseActionDownEvent();
+
+ assertNotNull(mController.mAutoclickIndicatorScheduler);
+ }
+
+ @Test
+ @DisableFlags(com.android.server.accessibility.Flags.FLAG_ENABLE_AUTOCLICK_INDICATOR)
+ public void onMotionEvent_flagOff_notInitAutoclickIndicatorScheduler() {
+ assertNull(mController.mAutoclickIndicatorScheduler);
+
+ injectFakeMouseActionDownEvent();
+
+ assertNull(mController.mAutoclickIndicatorScheduler);
+ }
+
+ @Test
+ @EnableFlags(com.android.server.accessibility.Flags.FLAG_ENABLE_AUTOCLICK_INDICATOR)
+ public void onMotionEvent_flagOn_lazyInitAutoclickIndicatorView() {
+ assertNull(mController.mAutoclickIndicatorView);
+
+ injectFakeMouseActionDownEvent();
+
+ assertNotNull(mController.mAutoclickIndicatorView);
+ }
+
+ @Test
+ @DisableFlags(com.android.server.accessibility.Flags.FLAG_ENABLE_AUTOCLICK_INDICATOR)
+ public void onMotionEvent_flagOff_notInitAutoclickIndicatorView() {
+ assertNull(mController.mAutoclickIndicatorView);
+
+ injectFakeMouseActionDownEvent();
+
+ assertNull(mController.mAutoclickIndicatorView);
+ }
+
+ @Test
+ @EnableFlags(com.android.server.accessibility.Flags.FLAG_ENABLE_AUTOCLICK_INDICATOR)
+ public void onMotionEvent_flagOn_addAutoclickIndicatorViewToWindowManager() {
+ injectFakeMouseActionDownEvent();
+
+ verify(mMockWindowManager).addView(eq(mController.mAutoclickIndicatorView), any());
+ }
+
+ @Test
+ @EnableFlags(com.android.server.accessibility.Flags.FLAG_ENABLE_AUTOCLICK_INDICATOR)
+ public void onDestroy_flagOn_removeAutoclickIndicatorViewToWindowManager() {
+ injectFakeMouseActionDownEvent();
+
+ mController.onDestroy();
+
+ verify(mMockWindowManager).removeView(mController.mAutoclickIndicatorView);
+ }
+
+ @Test
+ public void onMotionEvent_initClickSchedulerDelayFromSetting() {
+ injectFakeMouseActionDownEvent();
+
+ int delay =
+ Settings.Secure.getIntForUser(
+ mTestableContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_AUTOCLICK_DELAY,
+ AccessibilityManager.AUTOCLICK_DELAY_DEFAULT,
+ mTestableContext.getUserId());
+ assertEquals(delay, mController.mClickScheduler.getDelayForTesting());
+ }
+
+ @Test
+ @EnableFlags(com.android.server.accessibility.Flags.FLAG_ENABLE_AUTOCLICK_INDICATOR)
+ public void onMotionEvent_flagOn_initCursorAreaSizeFromSetting() {
+ injectFakeMouseActionDownEvent();
+
+ int size =
+ Settings.Secure.getIntForUser(
+ mTestableContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_AUTOCLICK_CURSOR_AREA_SIZE,
+ AccessibilityManager.AUTOCLICK_CURSOR_AREA_SIZE_DEFAULT,
+ mTestableContext.getUserId());
+ assertEquals(size, mController.mAutoclickIndicatorView.getRadiusForTesting());
+ }
+
+ @Test
+ public void onDestroy_clearClickScheduler() {
+ injectFakeMouseActionDownEvent();
+
+ mController.onDestroy();
+
+ assertNull(mController.mClickScheduler);
+ }
+
+ @Test
+ public void onDestroy_clearAutoclickSettingsObserver() {
+ injectFakeMouseActionDownEvent();
+
+ mController.onDestroy();
+
+ assertNull(mController.mAutoclickSettingsObserver);
+ }
+
+ @Test
+ @EnableFlags(com.android.server.accessibility.Flags.FLAG_ENABLE_AUTOCLICK_INDICATOR)
+ public void onDestroy_flagOn_clearAutoclickIndicatorScheduler() {
+ injectFakeMouseActionDownEvent();
+
+ mController.onDestroy();
+
+ assertNull(mController.mAutoclickIndicatorScheduler);
+ }
+
+ private void injectFakeMouseActionDownEvent() {
+ MotionEvent event = getFakeMotionDownEvent();
+ event.setSource(InputDevice.SOURCE_MOUSE);
+ mController.onMotionEvent(event, event, /* policyFlags= */ 0);
+ }
+
+ private void injectFakeNonMouseActionDownEvent() {
+ MotionEvent event = getFakeMotionDownEvent();
+ event.setSource(InputDevice.SOURCE_KEYBOARD);
+ mController.onMotionEvent(event, event, /* policyFlags= */ 0);
+ }
+
+ private MotionEvent getFakeMotionDownEvent() {
+ return MotionEvent.obtain(
+ /* downTime= */ 0,
+ /* eventTime= */ 0,
+ /* action= */ MotionEvent.ACTION_DOWN,
+ /* x= */ 0,
+ /* y= */ 0,
+ /* metaState= */ 0);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java
index 39206dcf21ef..3b32701b5169 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java
@@ -1612,7 +1612,7 @@ public class ShortcutManagerTest2 extends BaseShortcutManagerTest {
);
}
- public void testThrottling() {
+ public void disabled_testThrottling() {
final ShortcutInfo si1 = makeShortcut("shortcut1");
assertTrue(mManager.setDynamicShortcuts(list(si1)));
@@ -1685,7 +1685,7 @@ public class ShortcutManagerTest2 extends BaseShortcutManagerTest {
assertEquals(START_TIME + INTERVAL * 9, mManager.getRateLimitResetTime());
}
- public void testThrottling_rewind() {
+ public void disabled_testThrottling_rewind() {
final ShortcutInfo si1 = makeShortcut("shortcut1");
assertTrue(mManager.setDynamicShortcuts(list(si1)));
@@ -1715,7 +1715,7 @@ public class ShortcutManagerTest2 extends BaseShortcutManagerTest {
assertEquals(3, mManager.getRemainingCallCount());
}
- public void testThrottling_perPackage() {
+ public void disabled_testThrottling_perPackage() {
final ShortcutInfo si1 = makeShortcut("shortcut1");
assertTrue(mManager.setDynamicShortcuts(list(si1)));
@@ -1847,7 +1847,7 @@ public class ShortcutManagerTest2 extends BaseShortcutManagerTest {
});
}
- public void testThrottling_foreground() throws Exception {
+ public void disabled_testThrottling_foreground() throws Exception {
prepareCrossProfileDataSet();
dumpsysOnLogcat("Before save & load");
diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest3.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest3.java
index aad06c6d6c0e..81ebf867fe2d 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest3.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest3.java
@@ -171,11 +171,12 @@ public class ShortcutManagerTest3 extends BaseShortcutManagerTest {
.haveRanksInOrder("ms1");
}
- public void testSetDynamicShortcuts_withManifestShortcuts() {
- runTestWithManifestShortcuts(() -> testSetDynamicShortcuts_noManifestShortcuts());
+ public void disabled_testSetDynamicShortcuts_withManifestShortcuts() {
+ runTestWithManifestShortcuts(() ->
+ disabled_testAddDynamicShortcuts_noManifestShortcuts());
}
- public void testAddDynamicShortcuts_noManifestShortcuts() {
+ public void disabled_testAddDynamicShortcuts_noManifestShortcuts() {
mManager.addDynamicShortcuts(list(
shortcut("s1", A1)
));
@@ -264,8 +265,8 @@ public class ShortcutManagerTest3 extends BaseShortcutManagerTest {
.haveIds("s1", "s2", "s4", "s5", "s10");
}
- public void testAddDynamicShortcuts_withManifestShortcuts() {
- runTestWithManifestShortcuts(() -> testAddDynamicShortcuts_noManifestShortcuts());
+ public void disabled_testAddDynamicShortcuts_withManifestShortcuts() {
+ runTestWithManifestShortcuts(() -> disabled_testAddDynamicShortcuts_noManifestShortcuts());
}
public void testUpdateShortcuts_noManifestShortcuts() {
diff --git a/services/tests/servicestests/src/com/android/server/statusbar/StatusBarManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/statusbar/StatusBarManagerServiceTest.java
index 148c96850d34..263ada8b36f6 100644
--- a/services/tests/servicestests/src/com/android/server/statusbar/StatusBarManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/statusbar/StatusBarManagerServiceTest.java
@@ -69,6 +69,7 @@ import android.os.Binder;
import android.os.Looper;
import android.os.RemoteException;
import android.os.UserHandle;
+import android.platform.test.annotations.EnableFlags;
import android.service.quicksettings.TileService;
import android.testing.TestableContext;
@@ -79,6 +80,7 @@ import com.android.internal.statusbar.IStatusBar;
import com.android.server.LocalServices;
import com.android.server.policy.GlobalActionsProvider;
import com.android.server.wm.ActivityTaskManagerInternal;
+import com.android.systemui.shared.Flags;
import libcore.junit.util.compat.CoreCompatChangeRule;
@@ -105,6 +107,7 @@ public class StatusBarManagerServiceTest {
TEST_SERVICE);
private static final CharSequence APP_NAME = "AppName";
private static final CharSequence TILE_LABEL = "Tile label";
+ private static final int SECONDARY_DISPLAY_ID = 2;
@Rule
public final TestableContext mContext =
@@ -749,6 +752,29 @@ public class StatusBarManagerServiceTest {
}
@Test
+ @EnableFlags(Flags.FLAG_STATUS_BAR_CONNECTED_DISPLAYS)
+ public void testDisableForAllDisplays() throws Exception {
+ int user1Id = 0;
+ mockUidCheck();
+ mockCurrentUserCheck(user1Id);
+
+ mStatusBarManagerService.onDisplayAdded(SECONDARY_DISPLAY_ID);
+
+ int expectedFlags = DISABLE_MASK & DISABLE_BACK;
+ String pkg = mContext.getPackageName();
+
+ // before disabling
+ assertEquals(DISABLE_NONE,
+ mStatusBarManagerService.getDisableFlags(mMockStatusBar, user1Id)[0]);
+
+ // disable
+ mStatusBarManagerService.disable(expectedFlags, mMockStatusBar, pkg);
+
+ verify(mMockStatusBar).disable(0, expectedFlags, 0);
+ verify(mMockStatusBar).disable(SECONDARY_DISPLAY_ID, expectedFlags, 0);
+ }
+
+ @Test
public void testSetHomeDisabled() throws Exception {
int expectedFlags = DISABLE_MASK & DISABLE_HOME;
String pkg = mContext.getPackageName();
@@ -851,6 +877,29 @@ public class StatusBarManagerServiceTest {
}
@Test
+ @EnableFlags(Flags.FLAG_STATUS_BAR_CONNECTED_DISPLAYS)
+ public void testDisable2ForAllDisplays() throws Exception {
+ int user1Id = 0;
+ mockUidCheck();
+ mockCurrentUserCheck(user1Id);
+
+ mStatusBarManagerService.onDisplayAdded(SECONDARY_DISPLAY_ID);
+
+ int expectedFlags = DISABLE2_MASK & DISABLE2_NOTIFICATION_SHADE;
+ String pkg = mContext.getPackageName();
+
+ // before disabling
+ assertEquals(DISABLE_NONE,
+ mStatusBarManagerService.getDisableFlags(mMockStatusBar, user1Id)[0]);
+
+ // disable
+ mStatusBarManagerService.disable2(expectedFlags, mMockStatusBar, pkg);
+
+ verify(mMockStatusBar).disable(0, 0, expectedFlags);
+ verify(mMockStatusBar).disable(SECONDARY_DISPLAY_ID, 0, expectedFlags);
+ }
+
+ @Test
public void testSetQuickSettingsDisabled2() throws Exception {
int expectedFlags = DISABLE2_MASK & DISABLE2_QUICK_SETTINGS;
String pkg = mContext.getPackageName();
@@ -1092,6 +1141,7 @@ public class StatusBarManagerServiceTest {
// disable
mStatusBarManagerService.disableForUser(expectedUser1Flags, mMockStatusBar, pkg, user1Id);
mStatusBarManagerService.disableForUser(expectedUser2Flags, mMockStatusBar, pkg, user2Id);
+
// check that right flag is disabled
assertEquals(expectedUser1Flags,
mStatusBarManagerService.getDisableFlags(mMockStatusBar, user1Id)[0]);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java
index 1df8e3deb84b..d1afa38cf3f6 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java
@@ -17,15 +17,12 @@ package com.android.server.notification;
import static android.os.UserHandle.USER_ALL;
import static android.service.notification.Adjustment.KEY_IMPORTANCE;
+import static android.service.notification.Adjustment.KEY_TYPE;
import static android.service.notification.Adjustment.TYPE_CONTENT_RECOMMENDATION;
import static android.service.notification.Adjustment.TYPE_NEWS;
-import static android.service.notification.Adjustment.TYPE_OTHER;
import static android.service.notification.Adjustment.TYPE_PROMOTION;
-import static android.service.notification.Adjustment.TYPE_SOCIAL_MEDIA;
-import static android.service.notification.Flags.notificationClassification;
import static com.android.server.notification.NotificationManagerService.DEFAULT_ALLOWED_ADJUSTMENTS;
-import static com.android.server.notification.NotificationManagerService.DEFAULT_ALLOWED_ADJUSTMENT_KEY_TYPES;
import static com.google.common.truth.Truth.assertThat;
@@ -160,17 +157,6 @@ public class NotificationAssistantsTest extends UiServiceTestCase {
mAssistants.readXml(parser, mNm::canUseManagedServices, false, USER_ALL);
}
- private void setDefaultAllowedAdjustmentKeyTypes(NotificationAssistants assistants) {
- assistants.setAssistantAdjustmentKeyTypeState(TYPE_OTHER, false);
- assistants.setAssistantAdjustmentKeyTypeState(TYPE_PROMOTION, false);
- assistants.setAssistantAdjustmentKeyTypeState(TYPE_SOCIAL_MEDIA, false);
- assistants.setAssistantAdjustmentKeyTypeState(TYPE_NEWS, false);
- assistants.setAssistantAdjustmentKeyTypeState(TYPE_CONTENT_RECOMMENDATION, false);
-
- for (int type : DEFAULT_ALLOWED_ADJUSTMENT_KEY_TYPES) {
- assistants.setAssistantAdjustmentKeyTypeState(type, true);
- }
- }
@Before
public void setUp() throws Exception {
@@ -180,10 +166,8 @@ public class NotificationAssistantsTest extends UiServiceTestCase {
mContext.getOrCreateTestableResources().addOverride(
com.android.internal.R.string.config_defaultAssistantAccessComponent,
mCn.flattenToString());
+ mNm.mDefaultUnsupportedAdjustments = new String[] {};
mAssistants = spy(mNm.new NotificationAssistants(mContext, mLock, mUserProfiles, miPm));
- if (notificationClassification()) {
- setDefaultAllowedAdjustmentKeyTypes(mAssistants);
- }
when(mNm.getBinderService()).thenReturn(mINm);
mContext.ensureTestableResources();
@@ -678,7 +662,7 @@ public class NotificationAssistantsTest extends UiServiceTestCase {
mAssistants.disallowAdjustmentType(Adjustment.KEY_RANKING_SCORE);
assertThat(mAssistants.getAllowedAssistantAdjustments())
.doesNotContain(Adjustment.KEY_RANKING_SCORE);
- assertThat(mAssistants.getAllowedAssistantAdjustments()).contains(Adjustment.KEY_TYPE);
+ assertThat(mAssistants.getAllowedAssistantAdjustments()).contains(KEY_TYPE);
}
@Test
@@ -727,7 +711,7 @@ public class NotificationAssistantsTest extends UiServiceTestCase {
mAssistants.setAssistantAdjustmentKeyTypeState(TYPE_CONTENT_RECOMMENDATION, true);
assertThat(mAssistants.getAllowedAdjustmentKeyTypes()).asList()
- .containsExactly(TYPE_PROMOTION, TYPE_CONTENT_RECOMMENDATION);
+ .containsExactlyElementsIn(List.of(TYPE_PROMOTION, TYPE_CONTENT_RECOMMENDATION));
}
@Test
@@ -748,7 +732,7 @@ public class NotificationAssistantsTest extends UiServiceTestCase {
writeXmlAndReload(USER_ALL);
assertThat(mAssistants.getAllowedAdjustmentKeyTypes()).asList()
- .containsExactly(TYPE_NEWS, TYPE_CONTENT_RECOMMENDATION);
+ .containsExactlyElementsIn(List.of(TYPE_NEWS, TYPE_CONTENT_RECOMMENDATION));
}
@Test
@@ -765,168 +749,98 @@ public class NotificationAssistantsTest extends UiServiceTestCase {
@Test
@EnableFlags({android.service.notification.Flags.FLAG_NOTIFICATION_CLASSIFICATION,
android.app.Flags.FLAG_NOTIFICATION_CLASSIFICATION_UI})
- public void testGetPackagesWithKeyTypeAdjustmentSettings() throws Exception {
+ public void testGetTypeAdjustmentDeniedPackages() throws Exception {
String pkg = "my.package";
String pkg2 = "my.package.2";
- setDefaultAllowedAdjustmentKeyTypes(mAssistants);
- assertThat(mAssistants.isTypeAdjustmentAllowedForPackage(pkg, TYPE_PROMOTION)).isTrue();
- assertThat(mAssistants.getPackagesWithKeyTypeAdjustmentSettings()).isEmpty();
+ assertThat(mAssistants.isTypeAdjustmentAllowedForPackage(pkg)).isTrue();
+ assertThat(mAssistants.getTypeAdjustmentDeniedPackages()).isEmpty();
- mAssistants.setAssistantAdjustmentKeyTypeStateForPackage(pkg, TYPE_PROMOTION, true);
- assertThat(mAssistants.getPackagesWithKeyTypeAdjustmentSettings())
+ mAssistants.setTypeAdjustmentForPackageState(pkg, true);
+ assertThat(mAssistants.getTypeAdjustmentDeniedPackages()).isEmpty();
+ mAssistants.setTypeAdjustmentForPackageState(pkg, false);
+ assertThat(mAssistants.getTypeAdjustmentDeniedPackages()).asList()
.containsExactly(pkg);
- mAssistants.setAssistantAdjustmentKeyTypeStateForPackage(pkg, TYPE_PROMOTION, false);
- assertThat(mAssistants.getPackagesWithKeyTypeAdjustmentSettings())
+ mAssistants.setTypeAdjustmentForPackageState(pkg2, true);
+ assertThat(mAssistants.getTypeAdjustmentDeniedPackages()).asList()
.containsExactly(pkg);
- mAssistants.setAssistantAdjustmentKeyTypeStateForPackage(pkg2, TYPE_NEWS, true);
- mAssistants.setAssistantAdjustmentKeyTypeStateForPackage(pkg2, TYPE_PROMOTION, false);
- assertThat(mAssistants.getPackagesWithKeyTypeAdjustmentSettings())
+ mAssistants.setTypeAdjustmentForPackageState(pkg2, false);
+ assertThat(mAssistants.getTypeAdjustmentDeniedPackages()).asList()
.containsExactly(pkg, pkg2);
}
@Test
@EnableFlags(android.app.Flags.FLAG_NOTIFICATION_CLASSIFICATION_UI)
- public void testSetAssistantAdjustmentKeyTypeStateForPackage_usesGlobalDefault() {
- String pkg = "my.package";
- setDefaultAllowedAdjustmentKeyTypes(mAssistants);
- assertThat(mAssistants.isTypeAdjustmentAllowedForPackage(pkg, TYPE_PROMOTION)).isTrue();
- assertThat(mAssistants.isTypeAdjustmentAllowedForPackage(pkg, TYPE_NEWS)).isFalse();
- assertThat(mAssistants.getAllowedAdjustmentKeyTypesForPackage(pkg)).asList()
- .containsExactlyElementsIn(DEFAULT_ALLOWED_ADJUSTMENT_KEY_TYPES);
- }
-
- @Test
- @EnableFlags(android.app.Flags.FLAG_NOTIFICATION_CLASSIFICATION_UI)
- public void testSetAssistantAdjustmentKeyTypeStateForPackage_allowsAndDenies() {
- setDefaultAllowedAdjustmentKeyTypes(mAssistants);
- // Given that a package is set to have a type adjustment allowed,
- String pkg = "my.package";
- mAssistants.setAssistantAdjustmentKeyTypeStateForPackage(pkg, TYPE_NEWS, true);
+ public void testSetTypeAdjustmentForPackageState_allowsAndDenies() {
+ // Given that a package is allowed to have its type adjusted,
+ String allowedPackage = "allowed.package";
+ assertThat(mAssistants.getTypeAdjustmentDeniedPackages()).isEmpty();
+ mAssistants.setTypeAdjustmentForPackageState(allowedPackage, true);
- // The newly set state is the combination of the global default and the newly set type.
- assertThat(mAssistants.getAllowedAdjustmentKeyTypesForPackage(pkg)).asList()
- .containsExactly(TYPE_NEWS, TYPE_PROMOTION);
- assertThat(mAssistants.isTypeAdjustmentAllowedForPackage(pkg, TYPE_NEWS)).isTrue();
+ assertThat(mAssistants.getTypeAdjustmentDeniedPackages()).isEmpty();
+ assertTrue(mAssistants.isTypeAdjustmentAllowedForPackage(allowedPackage));
// Set type adjustment disallowed for this package
- mAssistants.setAssistantAdjustmentKeyTypeStateForPackage(pkg, TYPE_NEWS, false);
- mAssistants.setAssistantAdjustmentKeyTypeStateForPackage(pkg, TYPE_PROMOTION, false);
+ mAssistants.setTypeAdjustmentForPackageState(allowedPackage, false);
// Then the package is marked as denied
- assertThat(mAssistants.getAllowedAdjustmentKeyTypesForPackage(pkg)).isEmpty();
- assertThat(mAssistants.isTypeAdjustmentAllowedForPackage(pkg, TYPE_NEWS)).isFalse();
+ assertThat(mAssistants.getTypeAdjustmentDeniedPackages()).asList()
+ .containsExactly(allowedPackage);
+ assertFalse(mAssistants.isTypeAdjustmentAllowedForPackage(allowedPackage));
// Set type adjustment allowed again
- mAssistants.setAssistantAdjustmentKeyTypeStateForPackage(pkg, TYPE_NEWS, true);
- mAssistants.setAssistantAdjustmentKeyTypeStateForPackage(pkg, TYPE_PROMOTION, true);
+ mAssistants.setTypeAdjustmentForPackageState(allowedPackage, true);
// Then the package is marked as allowed again
- assertThat(mAssistants.getAllowedAdjustmentKeyTypesForPackage(pkg)).asList()
- .containsExactly(TYPE_NEWS, TYPE_PROMOTION);
- assertThat(mAssistants.isTypeAdjustmentAllowedForPackage(pkg, TYPE_NEWS)).isTrue();
- assertThat(mAssistants.isTypeAdjustmentAllowedForPackage(pkg, TYPE_PROMOTION)).isTrue();
-
- // Set type adjustment promotions false,
- mAssistants.setAssistantAdjustmentKeyTypeStateForPackage(pkg, TYPE_PROMOTION, false);
- assertThat(mAssistants.getAllowedAdjustmentKeyTypesForPackage(pkg)).asList()
- .containsExactly(TYPE_NEWS);
- assertThat(mAssistants.isTypeAdjustmentAllowedForPackage(pkg, TYPE_NEWS)).isTrue();
- assertThat(mAssistants.isTypeAdjustmentAllowedForPackage(pkg, TYPE_PROMOTION)).isFalse();
+ assertThat(mAssistants.getTypeAdjustmentDeniedPackages()).isEmpty();
+ assertTrue(mAssistants.isTypeAdjustmentAllowedForPackage(allowedPackage));
}
@Test
@EnableFlags(android.app.Flags.FLAG_NOTIFICATION_CLASSIFICATION_UI)
- public void testSetAssistantAdjustmentKeyTypeStateForPackage_allowsMultiplePkgs() {
- setDefaultAllowedAdjustmentKeyTypes(mAssistants);
- // Given packages allowed to have their type adjusted to TYPE_NEWS,
- String allowedPkg1 = "allowed.Pkg1";
- String allowedPkg2 = "allowed.Pkg2";
- String allowedPkg3 = "allowed.Pkg3";
- // Set type adjustment allowed for these packages
- mAssistants.setAssistantAdjustmentKeyTypeStateForPackage(allowedPkg1, TYPE_NEWS, true);
- mAssistants.setAssistantAdjustmentKeyTypeStateForPackage(allowedPkg2, TYPE_NEWS, true);
- mAssistants.setAssistantAdjustmentKeyTypeStateForPackage(allowedPkg3, TYPE_NEWS, true);
-
- // The newly set state is the combination of the global default and the newly set type.
- assertThat(mAssistants.getAllowedAdjustmentKeyTypesForPackage(allowedPkg1)).asList()
- .containsExactly(TYPE_NEWS, TYPE_PROMOTION);
- assertThat(mAssistants.getAllowedAdjustmentKeyTypesForPackage(allowedPkg2)).asList()
- .containsExactly(TYPE_NEWS, TYPE_PROMOTION);
- assertThat(mAssistants.getAllowedAdjustmentKeyTypesForPackage(allowedPkg3)).asList()
- .containsExactly(TYPE_NEWS, TYPE_PROMOTION);
- assertThat(mAssistants.isTypeAdjustmentAllowedForPackage(allowedPkg1, TYPE_NEWS)).isTrue();
- assertThat(mAssistants.isTypeAdjustmentAllowedForPackage(allowedPkg2, TYPE_NEWS)).isTrue();
- assertThat(mAssistants.isTypeAdjustmentAllowedForPackage(allowedPkg3, TYPE_NEWS)).isTrue();
-
- // And when we deny some of them,
- mAssistants.setAssistantAdjustmentKeyTypeStateForPackage(allowedPkg2, TYPE_NEWS, false);
- mAssistants.setAssistantAdjustmentKeyTypeStateForPackage(allowedPkg2, TYPE_PROMOTION,
- false);
- mAssistants.setAssistantAdjustmentKeyTypeStateForPackage(allowedPkg3, TYPE_PROMOTION,
- false);
-
- // Then the rest of the original packages are still marked as allowed.
- assertThat(mAssistants.getAllowedAdjustmentKeyTypesForPackage(allowedPkg1)).asList()
- .containsExactly(TYPE_NEWS, TYPE_PROMOTION);
- assertThat(mAssistants.getAllowedAdjustmentKeyTypesForPackage(allowedPkg2)).isEmpty();
- assertThat(mAssistants.getAllowedAdjustmentKeyTypesForPackage(allowedPkg3)).asList()
- .containsExactly(TYPE_NEWS);
- assertThat(mAssistants.isTypeAdjustmentAllowedForPackage(allowedPkg1, TYPE_NEWS)).isTrue();
- assertThat(mAssistants.isTypeAdjustmentAllowedForPackage(allowedPkg2, TYPE_NEWS)).isFalse();
- assertThat(mAssistants.isTypeAdjustmentAllowedForPackage(allowedPkg3, TYPE_NEWS)).isTrue();
+ public void testSetAssistantAdjustmentKeyTypeStateForPackage_deniesMultiple() {
+ // Given packages not allowed to have their type adjusted,
+ String deniedPkg1 = "denied.Pkg1";
+ String deniedPkg2 = "denied.Pkg2";
+ String deniedPkg3 = "denied.Pkg3";
+ // Set type adjustment disallowed for these packages
+ mAssistants.setTypeAdjustmentForPackageState(deniedPkg1, false);
+ mAssistants.setTypeAdjustmentForPackageState(deniedPkg2, false);
+ mAssistants.setTypeAdjustmentForPackageState(deniedPkg3, false);
+
+ // Then the packages are marked as denied
+ assertThat(mAssistants.getTypeAdjustmentDeniedPackages()).asList()
+ .containsExactlyElementsIn(List.of(deniedPkg1, deniedPkg2, deniedPkg3));
+ assertFalse(mAssistants.isTypeAdjustmentAllowedForPackage(deniedPkg1));
+ assertFalse(mAssistants.isTypeAdjustmentAllowedForPackage(deniedPkg2));
+ assertFalse(mAssistants.isTypeAdjustmentAllowedForPackage(deniedPkg3));
+
+ // And when we re-allow one of them,
+ mAssistants.setTypeAdjustmentForPackageState(deniedPkg2, true);
+
+ // Then the rest of the original packages are still marked as denied.
+ assertThat(mAssistants.getTypeAdjustmentDeniedPackages()).asList()
+ .containsExactlyElementsIn(List.of(deniedPkg1, deniedPkg3));
+ assertFalse(mAssistants.isTypeAdjustmentAllowedForPackage(deniedPkg1));
+ assertTrue(mAssistants.isTypeAdjustmentAllowedForPackage(deniedPkg2));
+ assertFalse(mAssistants.isTypeAdjustmentAllowedForPackage(deniedPkg3));
}
@Test
@EnableFlags(android.app.Flags.FLAG_NOTIFICATION_CLASSIFICATION_UI)
public void testSetAssistantAdjustmentKeyTypeStateForPackage_readWriteXml() throws Exception {
- setDefaultAllowedAdjustmentKeyTypes(mAssistants);
mAssistants.loadDefaultsFromConfig(true);
String deniedPkg1 = "denied.Pkg1";
String allowedPkg2 = "allowed.Pkg2";
- String allowedPkg3 = "allowed.Pkg3";
+ String deniedPkg3 = "denied.Pkg3";
// Set type adjustment disallowed or allowed for these packages
- mAssistants.setAssistantAdjustmentKeyTypeStateForPackage(deniedPkg1, TYPE_PROMOTION, false);
- mAssistants.setAssistantAdjustmentKeyTypeStateForPackage(allowedPkg2, TYPE_NEWS, true);
- mAssistants.setAssistantAdjustmentKeyTypeStateForPackage(allowedPkg3, TYPE_NEWS, true);
- mAssistants.setAssistantAdjustmentKeyTypeStateForPackage(allowedPkg3, TYPE_SOCIAL_MEDIA,
- true);
+ mAssistants.setTypeAdjustmentForPackageState(deniedPkg1, false);
+ mAssistants.setTypeAdjustmentForPackageState(allowedPkg2, true);
+ mAssistants.setTypeAdjustmentForPackageState(deniedPkg3, false);
writeXmlAndReload(USER_ALL);
- assertThat(mAssistants.getAllowedAdjustmentKeyTypesForPackage(deniedPkg1)).isEmpty();
- assertThat(mAssistants.getAllowedAdjustmentKeyTypesForPackage(allowedPkg2)).asList()
- .containsExactly(TYPE_NEWS, TYPE_PROMOTION);
- assertThat(mAssistants.getAllowedAdjustmentKeyTypesForPackage(allowedPkg3)).asList()
- .containsExactly(TYPE_NEWS, TYPE_SOCIAL_MEDIA, TYPE_PROMOTION);
- }
-
- @Test
- @EnableFlags(android.app.Flags.FLAG_NOTIFICATION_CLASSIFICATION_UI)
- public void testSetAssistantAdjustmentKeyTypeStateForPackage_noGlobalImpact() throws Exception {
- setDefaultAllowedAdjustmentKeyTypes(mAssistants);
- // When the global state is changed,
- mAssistants.setAssistantAdjustmentKeyTypeState(TYPE_NEWS, true);
-
- // The package state reflects the global state.
- String pkg = "my.package";
- assertThat(mAssistants.isTypeAdjustmentAllowedForPackage(pkg, TYPE_PROMOTION)).isTrue();
- assertThat(mAssistants.isTypeAdjustmentAllowedForPackage(pkg, TYPE_NEWS)).isTrue();
- assertThat(mAssistants.getAllowedAdjustmentKeyTypesForPackage(pkg)).asList()
- .containsExactly(TYPE_NEWS, TYPE_PROMOTION);
-
- // Once the package specific state is modified,
- mAssistants.setAssistantAdjustmentKeyTypeStateForPackage(pkg, TYPE_SOCIAL_MEDIA, true);
-
- // The package specific state combines the global state with those modifications
- assertThat(mAssistants.isTypeAdjustmentAllowedForPackage(pkg, TYPE_SOCIAL_MEDIA)).isTrue();
- assertThat(mAssistants.getAllowedAdjustmentKeyTypesForPackage(pkg)).asList()
- .containsExactly(TYPE_NEWS, TYPE_PROMOTION, TYPE_SOCIAL_MEDIA);
-
- // And further changes to the global state are ignored.
- mAssistants.setAssistantAdjustmentKeyTypeState(TYPE_NEWS, false);
- assertThat(mAssistants.isTypeAdjustmentAllowedForPackage(pkg, TYPE_NEWS)).isTrue();
- assertThat(mAssistants.getAllowedAdjustmentKeyTypesForPackage(pkg)).asList()
- .containsExactly(TYPE_NEWS, TYPE_PROMOTION, TYPE_SOCIAL_MEDIA);
+ assertThat(mAssistants.getTypeAdjustmentDeniedPackages()).asList()
+ .containsExactlyElementsIn(List.of(deniedPkg1, deniedPkg3));
}
@Test
@@ -944,7 +858,7 @@ public class NotificationAssistantsTest extends UiServiceTestCase {
mAssistants.new ManagedServiceInfo(null, mCn, userId, false, null, 35, 2345256);
// Ensure bundling is enabled
- mAssistants.setAdjustmentTypeSupportedState(info, Adjustment.KEY_TYPE, true);
+ mAssistants.setAdjustmentTypeSupportedState(info, KEY_TYPE, true);
// Enable these specific bundle types
mAssistants.setAssistantAdjustmentKeyTypeState(TYPE_PROMOTION, false);
mAssistants.setAssistantAdjustmentKeyTypeState(TYPE_NEWS, true);
@@ -978,7 +892,7 @@ public class NotificationAssistantsTest extends UiServiceTestCase {
.isEqualTo(NotificationProtoEnums.TYPE_CONTENT_RECOMMENDATION);
// Disable the top-level bundling setting
- mAssistants.setAdjustmentTypeSupportedState(info, Adjustment.KEY_TYPE, false);
+ mAssistants.setAdjustmentTypeSupportedState(info, KEY_TYPE, false);
// Enable these specific bundle types
mAssistants.setAssistantAdjustmentKeyTypeState(TYPE_PROMOTION, true);
mAssistants.setAssistantAdjustmentKeyTypeState(TYPE_NEWS, false);
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 fdb6a6802b7e..43302264964e 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -369,9 +369,6 @@ import org.mockito.MockitoAnnotations;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
-import platform.test.runner.parameterized.ParameterizedAndroidJunit4;
-import platform.test.runner.parameterized.Parameters;
-
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
@@ -387,6 +384,9 @@ import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.function.Consumer;
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4;
+import platform.test.runner.parameterized.Parameters;
+
@SmallTest
@RunWith(ParameterizedAndroidJunit4.class)
@RunWithLooper
@@ -790,6 +790,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
TestableResources tr = mContext.getOrCreateTestableResources();
tr.addOverride(com.android.internal.R.string.config_defaultSearchSelectorPackageName,
SEARCH_SELECTOR_PKG);
+ tr.addOverride(R.array.config_notificationDefaultUnsupportedAdjustments,
+ new String[] {KEY_TYPE});
doAnswer(invocation -> {
mOnPermissionChangeListener = invocation.getArgument(2);
@@ -7662,7 +7664,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
when(mAssistants.isSameUser(any(), anyInt())).thenReturn(true);
when(mAssistants.isServiceTokenValidLocked(any())).thenReturn(true);
when(mAssistants.isAdjustmentKeyTypeAllowed(anyInt())).thenReturn(true);
- when(mAssistants.isTypeAdjustmentAllowedForPackage(anyString(), anyInt())).thenReturn(true);
+ when(mAssistants.isTypeAdjustmentAllowedForPackage(anyString())).thenReturn(true);
// Set up notifications that will be adjusted
final NotificationRecord r1 = spy(generateNotificationRecord(
@@ -17512,7 +17514,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
NotificationManagerService.WorkerHandler.class);
mService.setHandler(handler);
when(mAssistants.isAdjustmentKeyTypeAllowed(anyInt())).thenReturn(true);
- when(mAssistants.isTypeAdjustmentAllowedForPackage(anyString(), anyInt())).thenReturn(true);
+ when(mAssistants.isTypeAdjustmentAllowedForPackage(anyString())).thenReturn(true);
Bundle signals = new Bundle();
signals.putInt(KEY_TYPE, TYPE_NEWS);
@@ -17556,11 +17558,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
NotificationManagerService.WorkerHandler.class);
mService.setHandler(handler);
when(mAssistants.isAdjustmentKeyTypeAllowed(anyInt())).thenReturn(true);
- when(mAssistants.isTypeAdjustmentAllowedForPackage(anyString(), eq(TYPE_NEWS)))
- .thenReturn(true);
- // Blocking adjustments for a different type does nothing
- when(mAssistants.isTypeAdjustmentAllowedForPackage(anyString(), eq(TYPE_PROMOTION)))
- .thenReturn(false);
+ when(mAssistants.isTypeAdjustmentAllowedForPackage(anyString())).thenReturn(true);
Bundle signals = new Bundle();
signals.putInt(KEY_TYPE, TYPE_NEWS);
@@ -17575,9 +17573,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
assertThat(r.getChannel().getId()).isEqualTo(NEWS_ID);
- // When we block adjustments for this package/type
- when(mAssistants.isTypeAdjustmentAllowedForPackage(anyString(), eq(TYPE_PROMOTION)))
- .thenReturn(false);
+ // When we block adjustments for this package
+ when(mAssistants.isTypeAdjustmentAllowedForPackage(anyString())).thenReturn(false);
signals.putInt(KEY_TYPE, TYPE_PROMOTION);
mBinderService.applyAdjustmentFromAssistant(null, adjustment);
@@ -17907,7 +17904,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
when(mAssistants.isSameUser(any(), anyInt())).thenReturn(true);
when(mAssistants.isServiceTokenValidLocked(any())).thenReturn(true);
when(mAssistants.isAdjustmentKeyTypeAllowed(anyInt())).thenReturn(true);
- when(mAssistants.isTypeAdjustmentAllowedForPackage(anyString(), anyInt())).thenReturn(true);
+ when(mAssistants.isTypeAdjustmentAllowedForPackage(anyString())).thenReturn(true);
// Post a single notification
final boolean hasOriginalSummary = false;
@@ -17947,7 +17944,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
when(mAssistants.isSameUser(any(), anyInt())).thenReturn(true);
when(mAssistants.isServiceTokenValidLocked(any())).thenReturn(true);
when(mAssistants.isAdjustmentKeyTypeAllowed(anyInt())).thenReturn(true);
- when(mAssistants.isTypeAdjustmentAllowedForPackage(anyString(), anyInt())).thenReturn(true);
+ when(mAssistants.isTypeAdjustmentAllowedForPackage(anyString())).thenReturn(true);
// Post grouped notifications
final String originalGroupName = "originalGroup";
@@ -17996,7 +17993,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
when(mAssistants.isSameUser(any(), anyInt())).thenReturn(true);
when(mAssistants.isServiceTokenValidLocked(any())).thenReturn(true);
when(mAssistants.isAdjustmentKeyTypeAllowed(anyInt())).thenReturn(true);
- when(mAssistants.isTypeAdjustmentAllowedForPackage(anyString(), anyInt())).thenReturn(true);
+ when(mAssistants.isTypeAdjustmentAllowedForPackage(anyString())).thenReturn(true);
// Post grouped notifications
final String originalGroupName = "originalGroup";
@@ -18047,7 +18044,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
when(mAssistants.isSameUser(any(), anyInt())).thenReturn(true);
when(mAssistants.isServiceTokenValidLocked(any())).thenReturn(true);
when(mAssistants.isAdjustmentKeyTypeAllowed(anyInt())).thenReturn(true);
- when(mAssistants.isTypeAdjustmentAllowedForPackage(anyString(), anyInt())).thenReturn(true);
+ when(mAssistants.isTypeAdjustmentAllowedForPackage(anyString())).thenReturn(true);
// Post a single notification
final boolean hasOriginalSummary = false;
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
index 6ef078b6da8a..31b9cf72584c 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
@@ -17,8 +17,10 @@
package com.android.server.notification;
import static android.app.AutomaticZenRule.TYPE_BEDTIME;
+import static android.app.AutomaticZenRule.TYPE_DRIVING;
import static android.app.AutomaticZenRule.TYPE_IMMERSIVE;
import static android.app.AutomaticZenRule.TYPE_SCHEDULE_TIME;
+import static android.app.AutomaticZenRule.TYPE_THEATER;
import static android.app.AutomaticZenRule.TYPE_UNKNOWN;
import static android.app.Flags.FLAG_BACKUP_RESTORE_LOGGING;
import static android.app.Flags.FLAG_MODES_API;
@@ -87,6 +89,7 @@ import static com.android.os.dnd.DNDProtoEnums.PEOPLE_STARRED;
import static com.android.os.dnd.DNDProtoEnums.ROOT_CONFIG;
import static com.android.os.dnd.DNDProtoEnums.STATE_ALLOW;
import static com.android.os.dnd.DNDProtoEnums.STATE_DISALLOW;
+import static com.android.server.notification.Flags.FLAG_PREVENT_ZEN_DEVICE_EFFECTS_WHILE_DRIVING;
import static com.android.server.notification.ZenModeEventLogger.ACTIVE_RULE_TYPE_MANUAL;
import static com.android.server.notification.ZenModeHelper.RULE_LIMIT_PER_PACKAGE;
@@ -211,7 +214,9 @@ import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
+import java.io.PrintWriter;
import java.io.Reader;
+import java.io.StringWriter;
import java.time.Instant;
import java.time.ZoneOffset;
import java.time.temporal.ChronoUnit;
@@ -5516,8 +5521,72 @@ public class ZenModeHelperTest extends UiServiceTestCase {
eq(ORIGIN_INIT_USER));
}
+ @Test
+ @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI, FLAG_PREVENT_ZEN_DEVICE_EFFECTS_WHILE_DRIVING})
+ public void testDeviceEffects_allowsGrayscale() {
+ mZenModeHelper.setDeviceEffectsApplier(mDeviceEffectsApplier);
+ reset(mDeviceEffectsApplier);
+ ZenDeviceEffects grayscale = new ZenDeviceEffects.Builder()
+ .setShouldDisplayGrayscale(true)
+ .build();
+ String ruleId = addRuleWithEffects(TYPE_THEATER, grayscale);
+
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, ruleId, CONDITION_TRUE,
+ ORIGIN_APP, CUSTOM_PKG_UID);
+ mTestableLooper.processAllMessages();
+
+ verify(mDeviceEffectsApplier).apply(eq(grayscale), anyInt());
+ }
+
+ @Test
+ @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI, FLAG_PREVENT_ZEN_DEVICE_EFFECTS_WHILE_DRIVING})
+ public void testDeviceEffects_whileDriving_avoidsGrayscale() {
+ mZenModeHelper.setDeviceEffectsApplier(mDeviceEffectsApplier);
+ reset(mDeviceEffectsApplier);
+ ZenDeviceEffects grayscale = new ZenDeviceEffects.Builder()
+ .setShouldDisplayGrayscale(true)
+ .build();
+ String ruleWithGrayscale = addRuleWithEffects(TYPE_THEATER, grayscale);
+ String drivingRule = addRuleWithEffects(TYPE_DRIVING, null);
+
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, ruleWithGrayscale,
+ CONDITION_TRUE, ORIGIN_APP, CUSTOM_PKG_UID);
+ mTestableLooper.processAllMessages();
+
+ verify(mDeviceEffectsApplier).apply(eq(grayscale), anyInt());
+
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, drivingRule, CONDITION_TRUE,
+ ORIGIN_APP, CUSTOM_PKG_UID);
+ mTestableLooper.processAllMessages();
+
+ verify(mDeviceEffectsApplier).apply(eq(NO_EFFECTS), anyInt());
+ }
+
+ @Test
+ @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI, FLAG_PREVENT_ZEN_DEVICE_EFFECTS_WHILE_DRIVING})
+ public void testDeviceEffects_whileDrivingWithGrayscale_allowsGrayscale() {
+ mZenModeHelper.setDeviceEffectsApplier(mDeviceEffectsApplier);
+ reset(mDeviceEffectsApplier);
+ ZenDeviceEffects grayscale = new ZenDeviceEffects.Builder()
+ .setShouldDisplayGrayscale(true)
+ .build();
+ String weirdoDrivingWithGrayscale = addRuleWithEffects(TYPE_DRIVING, grayscale);
+
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, weirdoDrivingWithGrayscale,
+ CONDITION_TRUE, ORIGIN_APP, CUSTOM_PKG_UID);
+ mTestableLooper.processAllMessages();
+
+ verify(mDeviceEffectsApplier).apply(eq(grayscale), anyInt());
+ }
+
private String addRuleWithEffects(ZenDeviceEffects effects) {
+ return addRuleWithEffects(TYPE_UNKNOWN, effects);
+ }
+
+ private String addRuleWithEffects(int type, @Nullable ZenDeviceEffects effects) {
AutomaticZenRule rule = new AutomaticZenRule.Builder("Test", CONDITION_ID)
+ .setPackage(mContext.getPackageName())
+ .setType(type)
.setDeviceEffects(effects)
.build();
return mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT, mContext.getPackageName(),
@@ -7406,6 +7475,43 @@ public class ZenModeHelperTest extends UiServiceTestCase {
.isEqualTo(mZenModeHelper.getDefaultZenPolicy());
}
+ @Test
+ public void setAutomaticZenRuleState_logsOriginToZenLog() {
+ AutomaticZenRule azr = new AutomaticZenRule.Builder("Rule", Uri.parse("cond"))
+ .setPackage(mPkg)
+ .build();
+ String ruleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT, mPkg, azr,
+ ORIGIN_APP, "adding", CUSTOM_PKG_UID);
+ ZenLog.clear();
+
+ // User enables manually from QS:
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, ruleId,
+ new Condition(azr.getConditionId(), "", STATE_TRUE), ORIGIN_USER_IN_SYSTEMUI,
+ 123456);
+
+ assertThat(getZenLog()).contains(
+ "config: setAzrState: " + ruleId + " (ORIGIN_USER_IN_SYSTEMUI) from uid " + 1234);
+ }
+
+ @Test
+ public void setAutomaticZenRuleStateFromConditionProvider_logsOriginToZenLog() {
+ AutomaticZenRule azr = new AutomaticZenRule.Builder("Rule", Uri.parse("cond/cond"))
+ .setOwner(new ComponentName(CUSTOM_PKG_NAME, "SomeConditionProvider"))
+ .setPackage(mPkg)
+ .build();
+ String ruleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT, mPkg, azr,
+ ORIGIN_APP, "adding", CUSTOM_PKG_UID);
+ ZenLog.clear();
+
+ // App enables rule through CPS
+ mZenModeHelper.setAutomaticZenRuleStateFromConditionProvider(UserHandle.CURRENT,
+ Uri.parse("cond/cond"), new Condition(azr.getConditionId(), "", STATE_TRUE),
+ ORIGIN_APP, CUSTOM_PKG_UID);
+
+ assertThat(getZenLog()).contains(
+ "config: setAzrStateFromCps: cond/cond (ORIGIN_APP) from uid " + CUSTOM_PKG_UID);
+ }
+
private static void addZenRule(ZenModeConfig config, String id, String ownerPkg, int zenMode,
@Nullable ZenPolicy zenPolicy) {
ZenRule rule = new ZenRule();
@@ -7530,6 +7636,12 @@ public class ZenModeHelperTest extends UiServiceTestCase {
assertThat(dndProto.getNotificationList().getNumber()).isEqualTo(STATE_ALLOW);
}
+ private static String getZenLog() {
+ StringWriter zenLogWriter = new StringWriter();
+ ZenLog.dump(new PrintWriter(zenLogWriter), "");
+ return zenLogWriter.toString();
+ }
+
private static void withoutWtfCrash(Runnable test) {
Log.TerribleFailureHandler oldHandler = Log.setWtfHandler((tag, what, system) -> {
});
diff --git a/services/tests/wmtests/Android.bp b/services/tests/wmtests/Android.bp
index 08ae6371559a..b328fc2d5868 100644
--- a/services/tests/wmtests/Android.bp
+++ b/services/tests/wmtests/Android.bp
@@ -101,7 +101,6 @@ android_test {
"testng",
"truth",
"wmtests-support",
- "display_flags_lib",
],
libs: [
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index c88d5153ed66..65150e7b48fc 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -2006,7 +2006,6 @@ public class ActivityRecordTests extends WindowTestsBase {
display.continueUpdateOrientationForDiffOrienLaunchingApp();
assertTrue(display.isFixedRotationLaunchingApp(activity));
- activity.stopFreezingScreen(true /* unfreezeSurfaceNow */, true /* force */);
// Simulate the rotation has been updated to previous one, e.g. sensor updates before the
// remote rotation is completed.
doReturn(originalRotation).when(displayRotation).rotationForOrientation(
@@ -2015,14 +2014,10 @@ public class ActivityRecordTests extends WindowTestsBase {
final DisplayInfo rotatedInfo = activity.getFixedRotationTransformDisplayInfo();
activity.finishFixedRotationTransform();
- final ScreenRotationAnimation rotationAnim = display.getRotationAnimation();
- assertNotNull(rotationAnim);
// Because the display doesn't rotate, the rotated activity needs to cancel the fixed
// rotation. There should be a rotation animation to cover the change of activity.
verify(activity).onCancelFixedRotationTransform(rotatedInfo.rotation);
- assertTrue(activity.isFreezingScreen());
- assertFalse(displayRotation.isRotatingSeamlessly());
// Simulate the remote rotation has completed and the configuration doesn't change, then
// the rotated activity should also be restored by clearing the transform.
@@ -2041,8 +2036,6 @@ public class ActivityRecordTests extends WindowTestsBase {
activity.setVisibleRequested(false);
clearInvocations(activity);
activity.onCancelFixedRotationTransform(originalRotation);
- // The implementation of cancellation must be executed.
- verify(activity).startFreezingScreen(originalRotation);
}
@Test
@@ -2599,19 +2592,16 @@ public class ActivityRecordTests extends WindowTestsBase {
final TestWindowState appWindow = createWindowState(attrs, activity);
activity.addWindow(appWindow);
spyOn(appWindow);
- doNothing().when(appWindow).onStartFreezingScreen();
// Set initial orientation and update.
activity.setOrientation(SCREEN_ORIENTATION_LANDSCAPE);
- mDisplayContent.updateOrientation(null /* freezeThisOneIfNeeded */,
- false /* forceUpdate */);
+ mDisplayContent.updateOrientationAndComputeConfig(false /* forceUpdate */);
assertEquals(SCREEN_ORIENTATION_LANDSCAPE, mDisplayContent.getLastOrientation());
appWindow.mResizeReported = false;
// Update the orientation to perform 180 degree rotation and check that resize was reported.
activity.setOrientation(SCREEN_ORIENTATION_REVERSE_LANDSCAPE);
- mDisplayContent.updateOrientation(null /* freezeThisOneIfNeeded */,
- false /* forceUpdate */);
+ mDisplayContent.updateOrientationAndComputeConfig(false /* forceUpdate */);
// In this test, DC will not get config update. Set the waiting flag to false.
mDisplayContent.mWaitingForConfig = false;
mWm.mRoot.performSurfacePlacement();
@@ -2635,8 +2625,6 @@ public class ActivityRecordTests extends WindowTestsBase {
final TestWindowState appWindow = createWindowState(attrs, activity);
activity.addWindow(appWindow);
spyOn(appWindow);
- doNothing().when(appWindow).onStartFreezingScreen();
- doNothing().when(mWm).startFreezingDisplay(anyInt(), anyInt(), any(), anyInt());
// Set initial orientation and update.
performRotation(displayRotation, Surface.ROTATION_90);
@@ -2795,9 +2783,6 @@ public class ActivityRecordTests extends WindowTestsBase {
assertEquals(Configuration.ORIENTATION_PORTRAIT, displayConfig.orientation);
assertEquals(Configuration.ORIENTATION_PORTRAIT, activityConfig.orientation);
- // Unblock the rotation animation, so the further orientation updates won't be ignored.
- unblockDisplayRotation(activity.mDisplayContent);
-
final ActivityRecord topActivity = createActivityRecord(activity.getTask());
topActivity.setOrientation(SCREEN_ORIENTATION_LANDSCAPE);
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRefresherTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRefresherTests.java
index 63dafcd0b5a8..c667d76f36ab 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRefresherTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRefresherTests.java
@@ -103,7 +103,7 @@ public class ActivityRefresherTests extends WindowTestsBase {
@Test
public void testShouldRefreshActivity_refreshDisabledForActivity() throws Exception {
configureActivityAndDisplay();
- when(mActivity.mAppCompatController.getAppCompatCameraOverrides()
+ when(mActivity.mAppCompatController.getCameraOverrides()
.shouldRefreshActivityForCameraCompat()).thenReturn(false);
mActivityRefresher.addEvaluator(mEvaluatorTrue);
@@ -161,7 +161,7 @@ public class ActivityRefresherTests extends WindowTestsBase {
configureActivityAndDisplay();
mActivityRefresher.addEvaluator(mEvaluatorTrue);
doReturn(true)
- .when(mActivity.mAppCompatController.getAppCompatCameraOverrides())
+ .when(mActivity.mAppCompatController.getCameraOverrides())
.shouldRefreshActivityViaPauseForCameraCompat();
mActivityRefresher.onActivityConfigurationChanging(mActivity, mNewConfig, mOldConfig);
@@ -174,7 +174,7 @@ public class ActivityRefresherTests extends WindowTestsBase {
configureActivityAndDisplay();
mActivityRefresher.addEvaluator(mEvaluatorTrue);
doReturn(true)
- .when(mActivity.mAppCompatController.getAppCompatCameraOverrides())
+ .when(mActivity.mAppCompatController.getCameraOverrides())
.shouldRefreshActivityViaPauseForCameraCompat();
mActivityRefresher.onActivityRefreshed(mActivity);
@@ -188,7 +188,7 @@ public class ActivityRefresherTests extends WindowTestsBase {
private void assertActivityRefreshRequested(boolean refreshRequested,
boolean cycleThroughStop) throws Exception {
- verify(mActivity.mAppCompatController.getAppCompatCameraOverrides(),
+ verify(mActivity.mAppCompatController.getCameraOverrides(),
times(refreshRequested ? 1 : 0)).setIsRefreshRequested(true);
final RefreshCallbackItem refreshCallbackItem =
@@ -212,9 +212,9 @@ public class ActivityRefresherTests extends WindowTestsBase {
.build()
.getTopMostActivity();
- spyOn(mActivity.mAppCompatController.getAppCompatCameraOverrides());
+ spyOn(mActivity.mAppCompatController.getCameraOverrides());
doReturn(true).when(mActivity).inFreeformWindowingMode();
doReturn(true).when(mActivity.mAppCompatController
- .getAppCompatCameraOverrides()).shouldRefreshActivityForCameraCompat();
+ .getCameraOverrides()).shouldRefreshActivityForCameraCompat();
}
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatCameraOverridesTest.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatCameraOverridesTest.java
index 1d138e4a48d9..4ad1cd192eb6 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppCompatCameraOverridesTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatCameraOverridesTest.java
@@ -393,7 +393,7 @@ public class AppCompatCameraOverridesTest extends WindowTestsBase {
}
private AppCompatCameraOverrides getAppCompatCameraOverrides() {
- return activity().top().mAppCompatController.getAppCompatCameraOverrides();
+ return activity().top().mAppCompatController.getCameraOverrides();
}
}
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/CameraCompatFreeformPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/CameraCompatFreeformPolicyTests.java
index 576d17af2e79..716f86418bcb 100644
--- a/services/tests/wmtests/src/com/android/server/wm/CameraCompatFreeformPolicyTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/CameraCompatFreeformPolicyTests.java
@@ -401,7 +401,7 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase {
throws Exception {
configureActivity(SCREEN_ORIENTATION_PORTRAIT);
- doReturn(false).when(mActivity.mAppCompatController.getAppCompatCameraOverrides())
+ doReturn(false).when(mActivity.mAppCompatController.getCameraOverrides())
.shouldRefreshActivityForCameraCompat();
mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
@@ -431,7 +431,7 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase {
public void testOnActivityConfigurationChanging_cycleThroughStopDisabledForApp()
throws Exception {
configureActivity(SCREEN_ORIENTATION_PORTRAIT);
- doReturn(true).when(mActivity.mAppCompatController.getAppCompatCameraOverrides())
+ doReturn(true).when(mActivity.mAppCompatController.getCameraOverrides())
.shouldRefreshActivityViaPauseForCameraCompat();
mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
@@ -477,7 +477,7 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase {
configureActivity(SCREEN_ORIENTATION_PORTRAIT);
final float configAspectRatio = 1.5f;
mWm.mAppCompatConfiguration.setCameraCompatAspectRatio(configAspectRatio);
- doReturn(true).when(mActivity.mAppCompatController.getAppCompatCameraOverrides())
+ doReturn(true).when(mActivity.mAppCompatController.getCameraOverrides())
.isOverrideMinAspectRatioForCameraEnabled();
mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
@@ -608,7 +608,7 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase {
.build();
mActivity.mAppCompatController.getSizeCompatModePolicy().clearSizeCompatMode();
- spyOn(mActivity.mAppCompatController.getAppCompatCameraOverrides());
+ spyOn(mActivity.mAppCompatController.getCameraOverrides());
spyOn(mActivity.info);
doReturn(mActivity).when(mDisplayContent).topRunningActivity(anyBoolean());
@@ -630,7 +630,7 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase {
private void assertActivityRefreshRequested(boolean refreshRequested,
boolean cycleThroughStop) throws Exception {
- verify(mActivity.mAppCompatController.getAppCompatCameraOverrides(),
+ verify(mActivity.mAppCompatController.getCameraOverrides(),
times(refreshRequested ? 1 : 0)).setIsRefreshRequested(true);
final RefreshCallbackItem refreshCallbackItem =
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index d76a907ba010..0964ebed9d25 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -1714,7 +1714,6 @@ public class DisplayContentTests extends WindowTestsBase {
@Test
public void testFinishFixedRotationNoAppTransitioningTask() {
- unblockDisplayRotation(mDisplayContent);
final ActivityRecord app = createActivityRecord(mDisplayContent);
final Task task = app.getTask();
final ActivityRecord app2 = new ActivityBuilder(mWm.mAtmService).setTask(task).build();
@@ -1742,7 +1741,6 @@ public class DisplayContentTests extends WindowTestsBase {
public void testFixedRotationWithPip() {
final DisplayContent displayContent = mDefaultDisplay;
displayContent.setIgnoreOrientationRequest(false);
- unblockDisplayRotation(displayContent);
// Unblock the condition in PinnedTaskController#continueOrientationChangeIfNeeded.
doNothing().when(displayContent).prepareAppTransition(anyInt());
// Make resume-top really update the activity state.
@@ -1762,7 +1760,6 @@ public class DisplayContentTests extends WindowTestsBase {
assertTrue(displayContent.hasTopFixedRotationLaunchingApp());
assertTrue(displayContent.mPinnedTaskController.shouldDeferOrientationChange());
- verify(mWm, never()).startFreezingDisplay(anyInt(), anyInt(), any(), anyInt());
clearInvocations(pinnedTask);
// Assume that the PiP enter animation is done then the new bounds are set. Expect the
@@ -1799,7 +1796,6 @@ public class DisplayContentTests extends WindowTestsBase {
@Test
public void testNoFixedRotationOnResumedScheduledApp() {
- unblockDisplayRotation(mDisplayContent);
final ActivityRecord app = new ActivityBuilder(mAtm).setCreateTask(true).build();
app.setVisible(false);
app.setState(ActivityRecord.State.RESUMED, "test");
@@ -1811,7 +1807,7 @@ public class DisplayContentTests extends WindowTestsBase {
// The condition should reject using fixed rotation because the resumed client in real case
// might get display info immediately. And the fixed rotation adjustments haven't arrived
// client side so the info may be inconsistent with the requested orientation.
- verify(mDisplayContent).updateOrientation(eq(app), anyBoolean());
+ verify(mDisplayContent).updateOrientationAndComputeConfig(anyBoolean());
assertFalse(app.isFixedRotationTransforming());
assertFalse(mDisplayContent.hasTopFixedRotationLaunchingApp());
}
@@ -1863,9 +1859,6 @@ public class DisplayContentTests extends WindowTestsBase {
@Test
public void testSecondaryInternalDisplayRotationFollowsDefaultDisplay() {
- // Skip freezing so the unrelated conditions in updateRotationUnchecked won't disturb.
- doNothing().when(mWm).startFreezingDisplay(anyInt(), anyInt(), any(), anyInt());
-
final DisplayRotationCoordinator coordinator =
mRootWindowContainer.getDisplayRotationCoordinator();
final DisplayContent defaultDisplayContent = mDisplayContent;
@@ -1921,9 +1914,6 @@ public class DisplayContentTests extends WindowTestsBase {
@Test
public void testSecondaryNonInternalDisplayDoesNotFollowDefaultDisplay() {
- // Skip freezing so the unrelated conditions in updateRotationUnchecked won't disturb.
- doNothing().when(mWm).startFreezingDisplay(anyInt(), anyInt(), any(), anyInt());
-
final DisplayRotationCoordinator coordinator =
mRootWindowContainer.getDisplayRotationCoordinator();
@@ -1994,20 +1984,11 @@ public class DisplayContentTests extends WindowTestsBase {
}
};
- // kill any existing rotation animation (vestigial from test setup).
- dc.setRotationAnimation(null);
-
mWm.updateRotation(true /* alwaysSendConfiguration */, false /* forceRelayout */);
- // If remote rotation is not finished, the display should not be able to unfreeze.
- mWm.stopFreezingDisplayLocked();
- assertTrue(mWm.mDisplayFrozen);
assertTrue(called[0]);
waitUntilHandlersIdle();
assertTrue(continued[0]);
-
- mWm.stopFreezingDisplayLocked();
- assertFalse(mWm.mDisplayFrozen);
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java
index 23c767c87e4f..02b796f5440f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java
@@ -267,7 +267,7 @@ public final class DisplayRotationCompatPolicyTests extends WindowTestsBase {
public void testTreatmentDisabledPerApp_noForceRotationOrRefresh()
throws Exception {
configureActivity(SCREEN_ORIENTATION_PORTRAIT);
- doReturn(false).when(mActivity.mAppCompatController.getAppCompatCameraOverrides())
+ doReturn(false).when(mActivity.mAppCompatController.getCameraOverrides())
.shouldForceRotateForCameraCompat();
mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
@@ -469,7 +469,7 @@ public final class DisplayRotationCompatPolicyTests extends WindowTestsBase {
throws Exception {
configureActivity(SCREEN_ORIENTATION_PORTRAIT);
- doReturn(false).when(mActivity.mAppCompatController.getAppCompatCameraOverrides())
+ doReturn(false).when(mActivity.mAppCompatController.getCameraOverrides())
.shouldRefreshActivityForCameraCompat();
mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
@@ -496,7 +496,7 @@ public final class DisplayRotationCompatPolicyTests extends WindowTestsBase {
throws Exception {
configureActivity(SCREEN_ORIENTATION_PORTRAIT);
doReturn(false).when(mActivity
- .mAppCompatController.getAppCompatCameraOverrides())
+ .mAppCompatController.getCameraOverrides())
.isCameraCompatSplitScreenAspectRatioAllowed();
mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
@@ -509,7 +509,7 @@ public final class DisplayRotationCompatPolicyTests extends WindowTestsBase {
public void testOnActivityConfigurationChanging_splitScreenAspectRatioAllowed_refresh()
throws Exception {
configureActivity(SCREEN_ORIENTATION_PORTRAIT);
- doReturn(true).when(mActivity.mAppCompatController.getAppCompatCameraOverrides())
+ doReturn(true).when(mActivity.mAppCompatController.getCameraOverrides())
.isCameraCompatSplitScreenAspectRatioAllowed();
mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
@@ -535,7 +535,7 @@ public final class DisplayRotationCompatPolicyTests extends WindowTestsBase {
public void testOnActivityConfigurationChanging_cycleThroughStopDisabledForApp()
throws Exception {
configureActivity(SCREEN_ORIENTATION_PORTRAIT);
- doReturn(true).when(mActivity.mAppCompatController.getAppCompatCameraOverrides())
+ doReturn(true).when(mActivity.mAppCompatController.getCameraOverrides())
.shouldRefreshActivityViaPauseForCameraCompat();
mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
@@ -599,7 +599,7 @@ public final class DisplayRotationCompatPolicyTests extends WindowTestsBase {
ActivityInfo.UNIVERSAL_RESIZABLE_BY_DEFAULT);
spyOn(mActivity.mAtmService.getLifecycleManager());
- spyOn(mActivity.mAppCompatController.getAppCompatCameraOverrides());
+ spyOn(mActivity.mAppCompatController.getCameraOverrides());
doReturn(mActivity).when(mDisplayContent).topRunningActivity(anyBoolean());
doReturn(naturalOrientation).when(mDisplayContent).getNaturalOrientation();
@@ -611,7 +611,7 @@ public final class DisplayRotationCompatPolicyTests extends WindowTestsBase {
private void assertActivityRefreshRequested(boolean refreshRequested,
boolean cycleThroughStop) throws Exception {
- verify(mActivity.mAppCompatController.getAppCompatCameraOverrides(),
+ verify(mActivity.mAppCompatController.getCameraOverrides(),
times(refreshRequested ? 1 : 0)).setIsRefreshRequested(true);
final RefreshCallbackItem refreshCallbackItem =
diff --git a/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java
index 6ced26920ff4..523b7235140a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java
@@ -32,7 +32,6 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
-import static com.android.server.display.feature.flags.Flags.FLAG_DISPLAY_TOPOLOGY;
import static com.android.server.wm.DragDropController.MSG_UNHANDLED_DROP_LISTENER_TIMEOUT;
import static org.junit.Assert.assertEquals;
@@ -58,7 +57,6 @@ import android.content.Intent;
import android.content.pm.ShortcutServiceInternal;
import android.graphics.PixelFormat;
import android.graphics.Rect;
-import android.hardware.display.VirtualDisplay;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
@@ -69,7 +67,6 @@ import android.os.RemoteException;
import android.os.UserHandle;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.annotations.Presubmit;
-import android.platform.test.annotations.RequiresFlagsEnabled;
import android.view.DragEvent;
import android.view.InputChannel;
import android.view.SurfaceControl;
@@ -83,14 +80,12 @@ import androidx.test.filters.SmallTest;
import com.android.server.LocalServices;
import com.android.server.pm.UserManagerInternal;
-import com.android.server.wm.utils.VirtualDisplayTestRule;
import com.android.window.flags.Flags;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
-import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
@@ -111,8 +106,6 @@ import java.util.function.Consumer;
@Presubmit
@RunWith(WindowTestRunner.class)
public class DragDropControllerTests extends WindowTestsBase {
- @Rule
- public VirtualDisplayTestRule mVirtualDisplayTestRule = new VirtualDisplayTestRule();
private static final int TIMEOUT_MS = 3000;
private static final int TEST_UID = 12345;
private static final int TEST_PROFILE_UID = 12345 * UserHandle.PER_USER_RANGE;
@@ -228,17 +221,13 @@ public class DragDropControllerTests extends WindowTestsBase {
@After
public void tearDown() throws Exception {
- // Besides TestDragDropController, WMService also creates another DragDropController in
- // test, and since listeners are added on instantiation, it has to be cleared here as well.
- mTarget.cleanupListeners();
- mWm.mDragDropController.cleanupListeners();
+ final CountDownLatch latch;
if (!mTarget.dragDropActiveLocked()) {
return;
}
if (mToken != null) {
mTarget.cancelDragAndDrop(mToken, false);
}
- final CountDownLatch latch;
latch = new CountDownLatch(1);
mTarget.setOnClosedCallbackLocked(latch::countDown);
if (mTarget.mIsAccessibilityDrag) {
@@ -577,10 +566,8 @@ public class DragDropControllerTests extends WindowTestsBase {
}
@Test
- @RequiresFlagsEnabled(FLAG_DISPLAY_TOPOLOGY)
@EnableFlags(Flags.FLAG_ENABLE_CONNECTED_DISPLAYS_DND)
public void testDragCancelledOnTopologyChange() {
- VirtualDisplay virtualDisplay = createVirtualDisplay();
// Necessary for now since DragState.sendDragStartedLocked() will recycle drag events
// immediately after dispatching, which is a problem when using mockito arguments captor
// because it returns and modifies the same drag event.
@@ -590,13 +577,8 @@ public class DragDropControllerTests extends WindowTestsBase {
startDrag(0, 0, View.DRAG_FLAG_GLOBAL | View.DRAG_FLAG_GLOBAL_URI_READ,
ClipData.newPlainText("label", "text"), (surface) -> {
- final CountDownLatch latch = new CountDownLatch(1);
- mTarget.setOnClosedCallbackLocked(latch::countDown);
-
- // Release virtual display to trigger drag-and-drop cancellation.
- virtualDisplay.release();
- assertTrue(awaitInWmLock(() -> latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)));
-
+ // Simulate display topology change to trigger drag-and-drop cancellation.
+ mTarget.handleDisplayTopologyChange(null /* displayTopology */);
assertEquals(2, dragEvents.size());
assertEquals(ACTION_DRAG_ENDED, dragEvents.getLast().getAction());
});
@@ -979,12 +961,4 @@ public class DragDropControllerTests extends WindowTestsBase {
assertNotNull(mToken);
r.run();
}
-
- private VirtualDisplay createVirtualDisplay() {
- final int width = 800;
- final int height = 600;
- final String name = getClass().getSimpleName() + "_VirtualDisplay";
- return mVirtualDisplayTestRule.createDisplayManagerAttachedVirtualDisplay(name, width,
- height);
- }
}
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 a84b374f9487..2630565f2785 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -4683,7 +4683,8 @@ public class SizeCompatTests extends WindowTestsBase {
prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT);
- assertFalse(mActivity.isEligibleForLetterboxEducation());
+ assertFalse(mActivity.mAppCompatController.getAppCompatLetterboxPolicy()
+ .isEligibleForLetterboxEducation());
}
@Test
@@ -4694,7 +4695,8 @@ public class SizeCompatTests extends WindowTestsBase {
prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT);
- assertFalse(mActivity.isEligibleForLetterboxEducation());
+ assertFalse(mActivity.mAppCompatController.getAppCompatLetterboxPolicy()
+ .isEligibleForLetterboxEducation());
}
@Test
@@ -4716,7 +4718,8 @@ public class SizeCompatTests extends WindowTestsBase {
false /*moveParents*/, "test");
organizer.mPrimary.setBounds(0, 0, 1000, 600);
- assertFalse(mActivity.isEligibleForLetterboxEducation());
+ assertFalse(mActivity.mAppCompatController.getAppCompatLetterboxPolicy()
+ .isEligibleForLetterboxEducation());
assertEquals(WINDOWING_MODE_MULTI_WINDOW, mActivity.getWindowingMode());
}
@@ -4728,7 +4731,8 @@ public class SizeCompatTests extends WindowTestsBase {
prepareUnresizable(mActivity, SCREEN_ORIENTATION_LANDSCAPE);
- assertFalse(mActivity.isEligibleForLetterboxEducation());
+ assertFalse(mActivity.mAppCompatController.getAppCompatLetterboxPolicy()
+ .isEligibleForLetterboxEducation());
assertTrue(mActivity.mAppCompatController.getAspectRatioPolicy()
.isLetterboxedForFixedOrientationAndAspectRatio());
}
@@ -4745,14 +4749,16 @@ public class SizeCompatTests extends WindowTestsBase {
createWindowState(new WindowManager.LayoutParams(TYPE_APPLICATION_STARTING),
mActivity));
- assertFalse(mActivity.isEligibleForLetterboxEducation());
+ assertFalse(mActivity.mAppCompatController.getAppCompatLetterboxPolicy()
+ .isEligibleForLetterboxEducation());
// Verify that after removing the starting window isEligibleForLetterboxEducation returns
// true and mTask.dispatchTaskInfoChangedIfNeeded is called.
spyOn(mTask);
mActivity.removeStartingWindow();
- assertTrue(mActivity.isEligibleForLetterboxEducation());
+ assertTrue(mActivity.mAppCompatController.getAppCompatLetterboxPolicy()
+ .isEligibleForLetterboxEducation());
verify(mTask).dispatchTaskInfoChangedIfNeeded(true);
}
@@ -4768,14 +4774,16 @@ public class SizeCompatTests extends WindowTestsBase {
createWindowState(new WindowManager.LayoutParams(TYPE_APPLICATION_STARTING),
mActivity));
- assertFalse(mActivity.isEligibleForLetterboxEducation());
+ assertFalse(mActivity.mAppCompatController.getAppCompatLetterboxPolicy()
+ .isEligibleForLetterboxEducation());
// Verify that after removing the starting window isEligibleForLetterboxEducation still
// returns false and mTask.dispatchTaskInfoChangedIfNeeded isn't called.
spyOn(mTask);
mActivity.removeStartingWindow();
- assertFalse(mActivity.isEligibleForLetterboxEducation());
+ assertFalse(mActivity.mAppCompatController.getAppCompatLetterboxPolicy()
+ .isEligibleForLetterboxEducation());
verify(mTask, never()).dispatchTaskInfoChangedIfNeeded(true);
}
@@ -4787,7 +4795,8 @@ public class SizeCompatTests extends WindowTestsBase {
prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT);
- assertTrue(mActivity.isEligibleForLetterboxEducation());
+ assertTrue(mActivity.mAppCompatController.getAppCompatLetterboxPolicy()
+ .isEligibleForLetterboxEducation());
assertTrue(mActivity.mAppCompatController.getAspectRatioPolicy()
.isLetterboxedForFixedOrientationAndAspectRatio());
}
@@ -4802,7 +4811,8 @@ public class SizeCompatTests extends WindowTestsBase {
rotateDisplay(mActivity.mDisplayContent, ROTATION_90);
- assertTrue(mActivity.isEligibleForLetterboxEducation());
+ assertTrue(mActivity.mAppCompatController.getAppCompatLetterboxPolicy()
+ .isEligibleForLetterboxEducation());
assertFalse(mActivity.mAppCompatController.getAspectRatioPolicy()
.isLetterboxedForFixedOrientationAndAspectRatio());
assertTrue(mActivity.inSizeCompatMode());
diff --git a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
index a95093d7e113..59335d3bb135 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
@@ -264,6 +264,7 @@ public class SystemServicesTestRule implements TestRule {
spyOn(dmg);
doNothing().when(dmg).registerDisplayListener(
any(), any(Executor.class), anyLong(), anyString());
+ doNothing().when(dmg).registerTopologyListener(any(Executor.class), any(), anyString());
}
private void setUpLocalServices() {
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
index 38d3d2fec942..724d7e7c111c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
@@ -92,6 +92,7 @@ import android.util.Xml;
import android.view.Display;
import android.view.DisplayInfo;
import android.view.SurfaceControl;
+import android.view.WindowInsetsController;
import android.window.TaskFragmentOrganizer;
import androidx.test.filters.MediumTest;
@@ -2109,6 +2110,43 @@ public class TaskTests extends WindowTestsBase {
assertEquals(Color.RED, task.getTaskDescription().getBackgroundColor());
}
+ @Test
+ public void testUpdateTopOpaqueSystemBarsAppearanceWhenActivityBecomesTransparent() {
+ final Task task = createTask(mDisplayContent);
+ final ActivityRecord activity = createActivityRecord(task);
+ final ActivityManager.TaskDescription td = new ActivityManager.TaskDescription();
+ td.setSystemBarsAppearance(
+ WindowInsetsController.APPEARANCE_TRANSPARENT_CAPTION_BAR_BACKGROUND);
+ activity.setTaskDescription(td);
+
+ assertEquals(WindowInsetsController.APPEARANCE_TRANSPARENT_CAPTION_BAR_BACKGROUND,
+ task.getTaskDescription().getTopOpaqueSystemBarsAppearance());
+
+ activity.setOccludesParent(false);
+
+ assertEquals(0, task.getTaskDescription().getTopOpaqueSystemBarsAppearance());
+ }
+
+ @Test
+ public void testUpdateTopOpaqueSystemBarsAppearanceWhenActivityBecomesOpaque() {
+ final Task task = createTask(mDisplayContent);
+ final ActivityRecord activity = createActivityRecord(task);
+ activity.setOccludesParent(false);
+
+ final ActivityManager.TaskDescription td = new ActivityManager.TaskDescription();
+ td.setSystemBarsAppearance(
+ WindowInsetsController.APPEARANCE_TRANSPARENT_CAPTION_BAR_BACKGROUND);
+ activity.setTaskDescription(td);
+
+ assertEquals(0, task.getTaskDescription().getTopOpaqueSystemBarsAppearance());
+
+ activity.setOccludesParent(true);
+
+ assertEquals(WindowInsetsController.APPEARANCE_TRANSPARENT_CAPTION_BAR_BACKGROUND,
+ task.getTaskDescription().getTopOpaqueSystemBarsAppearance());
+
+ }
+
private Task getTestTask() {
return new TaskBuilder(mSupervisor).setCreateActivity(true).build();
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTransactionTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTransactionTests.java
index e9ece5dbdcc4..369600c3f8d7 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTransactionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTransactionTests.java
@@ -79,6 +79,34 @@ public class WindowContainerTransactionTests extends WindowTestsBase {
}
@Test
+ public void testRemoveRootTask() {
+ final Task rootTask = createTask(mDisplayContent);
+ final Task task = createTaskInRootTask(rootTask, 0 /* userId */);
+ final ActivityRecord activity = createActivityRecord(mDisplayContent, task);
+ final TaskDisplayArea taskDisplayArea = mDisplayContent.getDefaultTaskDisplayArea();
+
+ WindowContainerTransaction wct = new WindowContainerTransaction();
+ WindowContainerToken token = rootTask.getTaskInfo().token;
+ wct.removeTask(token);
+ applyTransaction(wct);
+
+ // There is still an activity to be destroyed, so the task is not removed immediately.
+ assertNotNull(task.getParent());
+ assertTrue(rootTask.hasChild());
+ assertTrue(task.hasChild());
+ assertTrue(activity.finishing);
+
+ activity.destroyed("testRemoveRootTask");
+ // Assert that the container was removed after the activity is destroyed.
+ assertNull(task.getParent());
+ assertEquals(0, task.getChildCount());
+ assertNull(activity.getParent());
+ assertNull(taskDisplayArea.getTask(task1 -> task1.mTaskId == rootTask.mTaskId));
+ verify(mAtm.getLockTaskController(), atLeast(1)).clearLockedTask(task);
+ verify(mAtm.getLockTaskController(), atLeast(1)).clearLockedTask(rootTask);
+ }
+
+ @Test
public void testDesktopMode_tasksAreBroughtToFront() {
final TestDesktopOrganizer desktopOrganizer = new TestDesktopOrganizer(mAtm);
TaskDisplayArea tda = desktopOrganizer.mDefaultTDA;
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 f6f473b4964d..cff172f55601 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
@@ -855,12 +855,6 @@ public class WindowStateTests extends WindowTestsBase {
startingApp.updateResizingWindowIfNeeded();
assertTrue(mWm.mResizingWindows.contains(startingApp));
assertTrue(startingApp.isDrawn());
-
- // Even if the display is frozen, invisible requested window should not be affected.
- mWm.startFreezingDisplay(0, 0, mDisplayContent);
- startingApp.getWindowFrames().setInsetsChanged(true);
- startingApp.updateResizingWindowIfNeeded();
- assertTrue(startingApp.isDrawn());
}
@SetupWindows(addWindows = W_ABOVE_ACTIVITY)
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index 37d2a7511d98..2c390c504e9f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -1010,20 +1010,6 @@ public class WindowTestsBase extends SystemServiceTestsBase {
waitUntilWindowAnimatorIdle();
}
- /**
- * Avoids rotating screen disturbed by some conditions. It is usually used for the default
- * display that is not the instance of {@link TestDisplayContent} (it bypasses the conditions).
- *
- * @see DisplayRotation#updateRotationUnchecked
- */
- void unblockDisplayRotation(DisplayContent dc) {
- dc.mOpeningApps.clear();
- mWm.mAppsFreezingScreen = 0;
- mWm.stopFreezingDisplayLocked();
- // The rotation animation won't actually play, it needs to be cleared manually.
- dc.setRotationAnimation(null);
- }
-
static void resizeDisplay(DisplayContent displayContent, int width, int height) {
displayContent.updateBaseDisplayMetrics(width, height, displayContent.mBaseDisplayDensity,
displayContent.mBaseDisplayPhysicalXDpi, displayContent.mBaseDisplayPhysicalYDpi);
diff --git a/telecomm/java/android/telecom/Call.java b/telecomm/java/android/telecom/Call.java
index a52614d5cda1..531f51604507 100644
--- a/telecomm/java/android/telecom/Call.java
+++ b/telecomm/java/android/telecom/Call.java
@@ -52,6 +52,8 @@ import java.util.concurrent.CopyOnWriteArrayList;
* Represents an ongoing phone call that the in-call app should present to the user.
*/
public final class Call {
+ private static final String LOG_TAG = "TelecomCall";
+
/**
* The state of a {@code Call} when newly created.
*/
@@ -2912,6 +2914,11 @@ public final class Call {
}
} catch (BadParcelableException e) {
return false;
+ } catch (ClassCastException e) {
+ Log.e(LOG_TAG, e, "areBundlesEqual: failure comparing bundle key %s", key);
+ // until we know what is causing this, we should rethrow -- this is still not
+ // expected.
+ throw e;
}
}
}
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 24fb8c5da2d7..da4165553e57 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -180,7 +180,7 @@ import java.util.stream.IntStream;
* permission-protected. Your application cannot access the protected
* information unless it has the appropriate permissions declared in
* its manifest file. Where permissions apply, they are noted in the
- * the methods through which you access the protected information.
+ * methods through which you access the protected information.
*
* <p>TelephonyManager is intended for use on devices that implement
* {@link android.content.pm.PackageManager#FEATURE_TELEPHONY FEATURE_TELEPHONY}. On devices
@@ -633,11 +633,14 @@ public class TelephonyManager {
}
/**
- * Returns the multi SIM variant
- * Returns DSDS for Dual SIM Dual Standby
- * Returns DSDA for Dual SIM Dual Active
- * Returns TSTS for Triple SIM Triple Standby
- * Returns UNKNOWN for others
+ * Returns the multi SIM variant.
+ *
+ * <ul>
+ * <li>Returns DSDS for Dual SIM Dual Standby.</li>
+ * <li>Returns DSDA for Dual SIM Dual Active.</li>
+ * <li>Returns TSTS for Triple SIM Triple Standby.</li>
+ * <li>Returns UNKNOWN for others.</li>
+ * </ul>
*/
/** {@hide} */
@UnsupportedAppUsage
@@ -657,10 +660,14 @@ public class TelephonyManager {
/**
* Returns the number of phones available.
- * Returns 0 if none of voice, sms, data is not supported
- * Returns 1 for Single standby mode (Single SIM functionality).
- * Returns 2 for Dual standby mode (Dual SIM functionality).
- * Returns 3 for Tri standby mode (Tri SIM functionality).
+ *
+ * <ul>
+ * <li>Returns 0 if none of voice, sms, data is supported.</li>
+ * <li>Returns 1 for Single standby mode (Single SIM functionality).</li>
+ * <li>Returns 2 for Dual standby mode (Dual SIM functionality).</li>
+ * <li>Returns 3 for Tri standby mode (Tri SIM functionality).</li>
+ * </ul>
+ *
* @deprecated Use {@link #getActiveModemCount} instead.
*/
@Deprecated
@@ -671,10 +678,12 @@ public class TelephonyManager {
/**
* Returns the number of logical modems currently configured to be activated.
*
- * Returns 0 if none of voice, sms, data is not supported
- * Returns 1 for Single standby mode (Single SIM functionality).
- * Returns 2 for Dual standby mode (Dual SIM functionality).
- * Returns 3 for Tri standby mode (Tri SIM functionality).
+ * <ul>
+ * <li>Returns 0 if none of voice, sms, data is supported.</li>
+ * <li>Returns 1 for Single standby mode (Single SIM functionality).</li>
+ * <li>Returns 2 for Dual standby mode (Dual SIM functionality).</li>
+ * <li>Returns 3 for Tri standby mode (Tri SIM functionality).</li>
+ * </ul>
*/
public int getActiveModemCount() {
int modemCount = 1;